Activation notice — block 25,599,540
LIVE since block 25,599,540. Node 0.9.16 swapped in at block 25,599,539 on 2026-10-04, and every node rule below turned on one block later. Gateway 0.9.16 went live in the same window, a few minutes after the node swap. The gateway rows below shipped with it. This page is the record of what changed at that boundary.
{"type":"account_state","address":"0x…"} carries the live height if you need to
check where the chain is.
Every change below moves at ONE height, so the chain gains one boundary for the whole release.
Why one block above the swap. The outgoing node commits block 25,599,539 and only then halts, so that block still ran the old rules. The new rules apply from the first block after it.
order_status answers for a batch_cancel leg
| Surface | Before the height | Now |
|---|---|---|
order_status for an order a batch_cancel leg removed | unknown | canceled |
Why. A batch_cancel carries a verdict per leg, so the node can prove which
legs removed an order. It records only those. A refused leg leaves the order's
earlier terminal state untouched.
What to do. Nothing.
contractAddress on a deployment receipt
| Surface | Before the height | Now |
|---|---|---|
eth_getTransactionReceipt contractAddress for a successful deployment | null | the address of the deployed contract |
Why. The node derives the address from the sender and the nonce when it
stores the receipt of a successful deployment. A call, a failed deployment and
a receipt stored before the height keep null. There is no backfill.
What to do. For a receipt stored before the height, compute the address locally from the sender and the nonce.
mtfStatus for two transactions at one nonce
One EVM block can hold two transactions from one sender at the same nonce. When
the node refuses the first before it runs (for example insufficient_funds),
the nonce stays free, and the second transaction runs.
| Surface | Before the height | Now |
|---|---|---|
mtfStatus and status of the second transaction | bad_nonce, 0x0 | what really happened to it, for example success, 0x1 |
Why. Before the height, the node counted the refused transaction as if it used the nonce.
What to do. Nothing. A bad_nonce receipt from before the height, next to a
refused transaction at the same nonce, can be wrong. Read the sender's nonce or
the contract code to confirm.
An open-interest cap on every native perp market
| Surface | Before the height | Now |
|---|---|---|
markets_meta oi_cap | present only on a market with a governance-set cap. No market had one, so every market was uncapped | present on every native perp market: the lower of the governance-set cap and the capacity cap. On a deployer market, the cap its deployer set: see below |
markets_meta oi_cap_usd and oi_cap_bound | absent | present with oi_cap. oi_cap_bound reads "deployer" on a deployer market |
markets_meta max_market_order_ntl and active_asset_data max_trade_size | null on every market | a number on every native perp market that has a mark, and on every deployer market that has a cap |
Why. A liquidation can leave a deficit, and the protocol pays it from the insurance fund and its other backstops. With no cap, open interest had no bound against that money. The chain now derives a cap from what the backstops can pay, and it recomputes the cap every block. No vote was needed: the cap applies from the height. See how the capacity cap works. The capacity cap covers native perp markets only. The Metaliquidity vault backstop never takes a deployer market's risk, so a deployer market gets its cap from its deployer instead.
What to do.
- Keep the
nullbranch formax_market_order_ntlandmax_trade_size. A market that has never had a mark still readsnull. - Expect
MARKET_OI_CAPon an order that opens, extends or flips a position and is priced through the committed mark, while the market is at its cap or when the order's new exposure would pass the cap. On a self-priced market, every such order is refused. A passive order rests, and the chain cancels it if the mark moves through it while the market is at its cap. An order that can only close its owner's position passes. - Do not cache
oi_cap. It changes as the capacity and the mark change.
The cap never closes a position.
A deployer sets its market's open-interest cap
| Surface | Before the height | Now |
|---|---|---|
perp_set_oi_cap | unknown variant | accepted from the market's deployer, or from a delegate that holds bit 9 |
perp_set_sub_deployer_perms permissions with bit 9 set | refused: bits 9-15 were reserved | accepted. 1023 is every bit |
The mask of a delegate added with perp_set_sub_deployers | 511 | 1023 |
perp_activate_market on a market that has a cap | set the cap to the governance default max_oi | keeps the cap. Only a market with no cap starts at max_oi |
markets_meta oi_cap_bound on a deployer market | absent | "deployer" |
| A Metaliquidity vault order that opens, extends or flips a position on a deployer market | accepted | refused, PRECONDITION_FAILED: metaliquidity vault cannot open or extend a position on a MIP-3 market |
Why. A deployer market prices from its own deployer, and the protocol's backstops never take its risk. The capacity cap measures what those backstops can pay, so it does not fit a deployer market. The deployer owns the market's risk, so the deployer sets the cap. For the same reason, the Metaliquidity vault does not trade a deployer market. Its depositors did not deposit to carry a price the protocol does not control.
What to do.
- As a deployer, set your cap with
perp_set_oi_cap. The cap is in whole units of the base asset, not lots and not USD. A market you did not set carries the governance default it started at. - Send
0to remove the cap. Activation fills an empty cap with the governance default, so send0again after you deactivate and activate the market. - To let a delegate set the cap, grant bit 9. A delegate added with
perp_set_sub_deployersholds every bit, bit 9 included. - Accept
"deployer"as a value ofoi_cap_bound. - A lower cap closes no position. It stops new exposure only, by the at-cap rules.
- The reference market maker refuses to start when its list names a deployer market. Remove such a market from the list.
A deficit is charged to the market that produced it
| Surface | Before the height | Now |
|---|---|---|
| The insurance fund a cross account's deficit draws | the fund of one market: the highest asset id among the markets the liquidation touched | the fund of each market where the account realized a loss in the liquidation run, in proportion to that loss |
Why. The open-interest cap on a native market is sized from the insurance fund of that market. A loss on one market must not drain the insurance fund of another market. See which market pays.
What to do. Nothing. No action or read changes.
Liquidation and price rules
| Surface | Before the height | Now |
|---|---|---|
| The ADL haircut | reached only the gains that the netting step itself realized | reaches every gain realized on the market in the current 60-second window or the one before it: any fill, netting at mark or a delisting settlement. It never takes a winner below its maintenance margin |
| The index of a self-priced market | the mid of the best bid and the best ask over every resting order, whatever its age | a mid of the bid and ask VWAPs over rested depth only, smoothed over several updates |
| The 60 s count that marks an oracle price stale | ran only while at least one asset got a fresh price | runs on the block clock, so it also runs when no asset gets a fresh price |
| A delisting vote that names no price | closed every position at the risk mark | closes every position at the six-hour average, moved into the maintenance band of the risk mark, while the oracle price is fresh |
| A delisting vote that names a price | any price | refused when the price is more than 20% from the six-hour average and outside the maintenance band, while the oracle price is fresh |
Why. Each rule closes a way to move a price, or a loss, onto other traders. A short burst of orders no longer moves a self-priced index, and a delisting settles near a recent average rather than at one instant.
What to do. Nothing. No action shape changes. A delisting vote that names a price outside both bands is now refused before it counts.
A bridge re-issue lane and spot tokens as portfolio-margin collateral
| Surface | Before the height | Now |
|---|---|---|
A stranded_on_retired_domain withdrawal | no recovery lane | governance can re-issue it with a new nonce |
A disputed withdrawal that reads released | no recovery lane | governance can re-issue it the same way |
bridge_withdrawal_history after a re-issue | — | a NEW entry: same amount_units and dst_addr, a higher nonce, awaiting_cosignatures. The old entry stays terminal |
markets_meta spot.tokens | a spot token that governance registers was absent | the token appears, with its size and wei decimals |
| A spot token as portfolio-margin collateral | never counted | counts when governance sets a positive collateral weight for that token and its price perpetual, native, listed and not self-priced, gives its mark. No token has a weight yet |
| The perpetual that gives a collateral token its mark | — | the perpetual governance names for the token or, when none is named, the perpetual with the same symbol. A token whose symbol differs from its perpetual, such as a bridged gBTC priced by BTC, needs the named mapping |
node_gov asset on a pm_collateral_haircut or pm_collateral_price_asset vote | — | a spot token id, not a market id |
node_gov changes[].field on a SetDynamicRiskParam enactment | — | can be pm_collateral_price_asset, the perpetual that gives the token its mark |
node_gov category on a re-issue vote | — | bridge_reissue |
| An unsigned queued withdrawal that must not pay | no lane | governance can void it and refund the full debit, fee included |
bridge_withdrawal_history after a void | — | the entry finishes with status: "voided" and open: false |
node_bridge_outbox removed record after a void | — | status: "voided" |
node_gov category on a void vote | — | bridge_void |
| A queued withdrawal while withdrawals are halted | validators kept signing it | validators sign nothing until the halt lifts; it stays awaiting_cosignatures |
node_gov category on an ArmFeatures or SPAN shock-grid vote | circle_promotion_attest | arm_features or pm_shock_grid |
node_gov category on a collateral weight or price-perpetual vote | — | pm_collateral, not dynamic_risk. coin is empty: asset is a spot token id |
A SetDynamicRiskParam vote that sets a collateral field AND a market setting | — | refused. A collateral vote carries only collateral fields |
Why. A deployment rotation can strand a withdrawal, and a dispute on the destination contract can make one unpayable. Before the height, neither had a recovery lane. A re-issue pays the original withdrawal once, on the destination chain. It never debits the user again and never credits the user on the exchange. Governance re-issues a withdrawal once. A withdrawal that no validator has signed can instead be voided: governance removes it and refunds the full debit on the exchange, and nothing pays it on the destination chain.
A spot token takes its mark from its price perpetual: the one governance names for the token or, when none is named, the perpetual with the same symbol. So the collateral and a position on that perpetual share one price in the scenario grid. A self-priced perpetual has no external price, so its token never counts.
What to do.
- Before any re-issue, confirm on the destination chain that the old withdrawal was not paid. See the danger box.
- After a re-issue, track the NEW entry. Its
message_idis new. - Read the collateral weight per token, not per market. The planned weights are
0.95for BTC and ETH and0.8for SOL and BNB, once those spot tokens exist. They are planned, not set. - Expect no credit from a token while the oracle of its perpetual is stale, or before the oracle has sourced a price for it.
node_gov labels every governance round
| Surface | Before the height | Now |
|---|---|---|
node_gov category on a fixed-round vote with no label of its own, for example DisableDex | the label of the nearest labeled base below it, such as oracle_weights | the label of its own round, such as disable_dex |
node_gov sub_id on that vote | the distance from that lower base, such as 12000000 | 0 |
node_gov round on a Listing, MintTreasury, BurnTreasury or SetPopulationTarget vote | a round that another action also used: 13,000,000, 13,000,000, 14,000,000 and 15,000,000, in that order | a round of its own: 48,000,000, 49,000,000, 50,000,000 and 51,000,000, in that order |
Why. Before the height, a node labeled only eight bases. Thirty fixed rounds had no base of their own, so each one read as a different vote kind. The category table gives every round its own label.
Every round now has one action. Before the height, three rounds were shared by more than one action. A validator has one vote slot per round, so its vote for one of those actions replaced its vote for the other.
What to do.
- Filter a vote kind by
action.actionis the exact filter on every record. - Do not split a vote kind by
categoryon a record written before the height. The old label on those records does not change.
Reads after the fill-tape retirement
From this height, the node keeps no committed fill tape: no trade ring for a
market and no fill ring for an account. The
node_trades and
node_fills streams do not change, so
the archive keeps every print. The reads below read the archive or the
gateway's own 24-hour trade window instead of the node rings.
| Surface | Served by | Now |
|---|---|---|
markets day_ntl_vlm, perp and spot | node | A node holds no 24-hour window. It serves "0" with day_ntl_vlm_lower_bound_from equal to the top-level time |
markets time | node | New top-level field: the block time of the read |
markets day_ntl_vlm | gateway | The gateway replaces day_ntl_vlm with the sum from its own window. It removes day_ntl_vlm_lower_bound_from only when its data covers the whole 24 hours. Otherwise the marker is the oldest instant its data covers |
markets spot prev_day_px | gateway | When the gateway data covers the whole 24 hours, the price of the first print in that window |
WebSocket markets day_ntl_vlm and spot prev_day_px | gateway | The same two rules on every snapshot and delta row. The window ends at the row's time |
trades, un-ranged | gateway | The ask also reaches the archive. The answer merges the node answer, the gateway window and the archive, with no duplicate tid |
trades, a row from the gateway window | gateway | No hash key and no block key, the same as an archive row that has no block |
WS trades on-subscribe snapshot | gateway | The node snapshot is empty, so the gateway serves the snapshot from its own window |
user_twap_slice_fills | gateway | The answer merges the node answer with the archive fills of the same address that carry a twap_id |
order_status for a filled order | gateway | The legs come from the archive when the ask carries address |
WS l2_book and bbo time on a spot pair | node | The time of the newest print the serving node saw since it started. 0 until the first print |
Why. The node rings were bounded, and they were part of the committed state. The archive and the gateway already held the same prints, and they hold more of them. A ring of 256 prints covered minutes on a busy market, so the gateway's 24-hour window is the fuller figure.
What to do.
- Read a
marketsrow that still carriesday_ntl_vlm_lower_bound_fromas a lower bound. A marker within seconds of the current time means the serving layer held no window:"0"is then no data, never a quiet market. - Send
addressonorder_status. Without it, the answer carries nofills, and a filled order can answerunknown. - Keep reading
tradesrows with and withouthash. A missinghashmeans "not recorded". - Expect no type change. No field changes its type, and no field is removed.
user_ledger_updates is removed
| Surface | Before the height | Now |
|---|---|---|
POST /info user_ledger_updates | 200 with updates: [] | 410 with code: "UNKNOWN_TYPE" and details.use: "user_non_funding_ledger_updates". A node that you call directly answers 400 UNKNOWN_TYPE |
Why. This read could only answer []. The node keeps no per-account ledger
history. The archive keeps every balance movement, in the stream's own record
shape: a signed delta and a token coin.
user_non_funding_ledger_updates serves
those records. One question, one read.
What to do.
- Call
user_non_funding_ledger_updatesfor the balance ledger history of an account. - Read
deltaas a signed change, not as an unsigned amount. - For live balance movement, keep the
ledger_updatesWS channel. It does not change.
Referral codes, a referee discount and caps
The height changed no fee. Each new parameter started at a value that reproduced the old behaviour: a 10% share, no discount, codes off, no cap. Governance turns the program on with votes. Two rules changed at the height itself, with no vote: a liquidation fill pays no referrer share, and an account that already has referees cannot bind to a referrer.
| Surface | Before the height | Now |
|---|---|---|
register_referral_code | unknown variant | accepted while codes are on. Refused with referral codes are not enabled while they are off |
set_referrer_by_code | unknown variant | binds the sender to the account that holds the code |
set_referrer from an account that has referees | accepted | refused: an account with referees cannot set a referrer |
set_referrer or set_referrer_by_code from an account that holds a referral code | — | refused: an account with a referral code cannot set a referrer |
set_referrer to an address with no referral code, while codes are on | — | refused: referrer has no referral code |
| The referrer share | a fixed 10% of the taker fee | referrer_share_bps of the taker fee actually paid. Default 1000, which is 10% |
| The referee discount | none | referee_discount_permille off the taker rate. The larger of it and the staking discount applies, never their sum. Default 0 |
| The share and the discount after the caps | — | each stops when the referee's taker volume since the bind reaches its cap. Default 0, which is no cap |
| A liquidation fill of a referee | paid the referrer share | pays no share and gets no referee discount |
vote_global kinds 131 to 135 | unknown kind | set_referrer_share_bps, set_referee_discount_permille, set_referral_code_min_volume_usd, set_referee_discount_cap_usd, set_referrer_reward_cap_usd |
fee_schedule | referrer_share_bps only | also referee_discount_permille, referral_code_min_volume_usd, referee_discount_cap_usd, referrer_reward_cap_usd, and user.referee_discount_permille |
referral_state | user, address, claimable_rewards, referrer | also referrer_code, code, referee, referrer_stats, code_requirement |
referral_code, referral_referees, referral_leaderboard | UNKNOWN_TYPE | new reads |
node_actions action_type | — | can be RegisterReferralCode or SetReferrerByCode |
Why. A referrer can now hand out a short code instead of an address, and a referee can see its discount, its counters and its caps. Governance can set the share, the discount and the caps, so the program changes without a release. The code minimum makes each referrer identity trade before it earns, and while codes are on, a bind by address needs a code holder, so a trader cannot bind a fresh address to itself for free. Referrals are single-level: before the height the node checked only one direction, and now it checks both. A liquidation fill is not flow the referrer brought, so it pays no share. See the referral program.
What to do.
- Bind by code. Governance turned the program on 2026-10-05, with a 10% share,
a 50‰ (5%) discount, a 10,000 USDC code minimum, a 25,000,000 USDC discount
cap and a 1,000,000,000 USDC share cap. Since then,
set_referrerto an address that holds no code fails withreferrer has no referral code. Read the values in force fromfee_schedule. - Send a referral code in lowercase. The node refuses an uppercase letter and does not fold it.
- A binding made before the height keeps its referrer. It reads
bound_ms: 0and zero counters, and its caps count from the height. referral_refereeslists every referee, a referee bound before the height included. Its counters start at the height.
One claim for the referral and broker credits
| Surface | Before the height | Now |
|---|---|---|
claim_referral_rewards | drained the referral credit only | drains the referral credit and the broker-code credit |
claim_broker_rewards and claim_builder_rewards | drained the broker-code credit only | drain the broker-code credit and the referral credit |
node_actions result of a ClaimReferralRewards or ClaimBuilderRewards row | null | claimed, referral and broker, in USDC |
referral_state referrer_stats.claimed | — | grows by the referral part only, whichever action the sender used |
Why. One fill can pay an account a referral credit and a broker credit. A trader who earns both wants one claim, not two. No action is added, no action is removed and no signing type changes. Both actions now do the same thing.
What to do.
- Show one claimable figure: the sum of
claimable_rewardsfromreferral_stateand frombroker_state. - Send one claim action. A second claim is harmless: it claims
0. - The
/exchangereply still reports no amount. Read the amount from the claim'snode_actionsrow.