Activation notice — block 11,550,001
LIVE since block 11,550,001. Node 0.9.10 swapped in at block 11,550,000 on 2026-09-16, and every node rule below turned on one block later. This page is the record of what changed at that boundary, because a client written against the older behaviour breaks on the rows below.
The gateway rows at the end have no height. The gateway ships on its own schedule. Gateway 0.9.8 is the first to carry them, from 2026-09-16, and every later gateway carries them too. They are on this page because two of them also need the node side, so a client meets both halves on the same day.
{"type":"account_state","address":"0x…"} carries the live height if you need to
check where the chain is.
Several behaviours move at ONE height, so the chain gains one boundary rather than several. The rows below are the ones a caller can observe. The release also carries six rules with no height pin. Anything else in the release changes no request, response, or rejection you can see.
Why the pin is one block above the swap. The outgoing binary commits block 11,550,000 and only then halts, so that block still runs the old rules. Pinning the change at the swap height itself would mean the live chain and a replay of it disagreed about that one block.
Breaking: vault_modify signs a new type
The EIP-712 type string gains six words, so every field the action applies is inside the digest:
MetaFluxTransaction:VaultModify(string metafluxChain,uint64 vaultId,string newName,bool hasNewLockPeriodSecs,uint64 newLockPeriodSecs,bool hasNewManagementFeeBps,uint16 newManagementFeeBps,bool hasNewPaused,bool newPaused,uint64 nonce)
Each optional field signs as two words: a presence bool, then the value. An
absent key and a key sent as 0 are different digests, so one signature covers
exactly one wire form.
A signature made with the four-field string stopped verifying at this height. A client that did not move cannot reach the action at all. Update the signing type, then re-sign.
Why. Before the height the digest bound new_name alone, and the handler
applied the fee and the pause flag from the wire payload. So a relay could add a
fee change or a pause to a signature the leader gave for a rename.
Checklist
- Move to the new type string — see typed-data signing.
- Send the exact field set you signed. An added or removed key is refused.
- Use a client SDK release that signs the new type. An older SDK cannot sign the action.
Every raw-size row states its own size plane
node_fills, node_trades and node_order_statuses each gain a sz_decimals
field. It is the plane THAT ROW was written on, and it is what you divide the
row's raw size by. See data streams.
Why it matters, and why it is not cosmetic. A market's size precision can
RISE by a governance listing vote, and that vote is available from this height.
The vote multiplies every stored lot count, so no real quantity moves. But a row
written before the vote keeps the smaller lot count. A reader that divides every
row by the market's CURRENT precision reports each of those older rows 10^Δ too
small. That error is silent: the numbers stay well-formed and understate.
Rows written before this height carry no sz_decimals. An absent value means
"not recorded". Fall back to the market's current precision for those rows only.
The fallback is exact only while no raise has enacted since that row was written.
Checklist
- Read
sz_decimalsper ROW. Treat an absent value as the market's current precision, and only that. - Stop caching a market's precision across a read. It is per-row now.
- A perp's precision NEVER comes from a spot token, even where the names match. Read it from the market.
Two ceilings change unit, with no change to their stored values.
per_market_limits.max_oi and max_oi_per_second are WHOLE UNITS of the base
asset from this height, not raw lots. Each is one number for every perp, and a
lot is a different real quantity on each market. So a shared lot count could not
state one real limit: the same value meant a thousand times more real size on a
three-decimal market than on a whole-unit one.
The faucet gives one claim per address, ever
Once ever is a claim COUNT from this height, not a value cap. Before the height, the committed row accumulated toward 3000 USDC / 10 MTF, so it bounded a lifetime VALUE: an address that asked for less kept the remainder and could come back. Now the row records that the address has claimed. Asking for less than the full grant spends the slot.
The faucet reads that committed row before it queues, and it answers 429 address already funded at once. Before the height, that refusal came from a set in the
faucet node's memory, so a node restart re-opened every address.
The per-IP window did not change on testnet. The same release moved the window's DEFAULT from one minute to one day. The window is a node setting, and the testnet faucet still runs one minute. It is node-local and it resets when that node restarts, so it is a speed bump, not an anti-sybil control.
Checklist
- Ask for the FULL grant. There is no second call for the remainder.
- Treat
429 address already fundedas final for that address. - Do not build on the per-IP window. See faucet limits.
statuses gains parked, and the array gets longer
A TP/SL or stop leg accepted off the book reports
{"parked":{"oid","cloid"}}. Before
the height, a position_tpsl group answered an empty statuses array, and a mixed
normal_tpsl batch answered fewer entries than it sent legs.
The same token lands on the order_updates
feed and in the node's node_order_statuses stream.
A closed union breaks on it. A client that parses statuses into a closed
enum fails the WHOLE response on the new key. Add the arm first.
parked is the term across this reference.
order_status answers the legacy
token triggered for the same state, and that one endpoint did not change.
A replayed nonce gets a verdict
An action the block builder drops as a replay answers
NONCE_REPLAYED at HTTP 200. Before the
height the node dropped it in silence. The caller waited out the order window, and
the gateway then answered a 502.
nonce_must_increase and nonce_too_small never existed on this API. Branch on
the code.
cloid dedup runs per leg
batch_order checks every leg that
carries a cloid, and scale_order
checks its ladder handle. Two legs of one action that share a cloid refuse the
whole action.
Why. Every perp order a client SDK sends is a batch, and before the height the batch path did not dedup. So a retry placed the order twice.
Two more consequences:
- A ladder handle is reserved. A later single order that reuses it is refused. Before the height that order JOINED the group instead.
- An attempt the COMMIT refused gives its
cloidback, so a re-signed retry may reuse the handle. Before the height the node kept the handle of a refused order.
order_status stops answering unknown twice
A cancelled SPOT order answers canceled. A spot order or scale rung that
neither rests nor matches answers rejected, with
reason: "Order could not immediately match against any resting orders." Before
the height both answered unknown, because neither wrote a fill the ring could
serve.
A parked leg also resolves by cloid from committed state, so it keeps resolving
after a node restart. Its trigger object carries cloid, and a parked row on
open_orders carries it too. Before the height a parked leg resolved by cloid
only while the node's own index survived, and a parked row read null.
Three silent accepts become refusals
Each is a commit verdict, so it arrives as a
200 carrying the rejection envelope.
| Action | Refusal | Before the height |
|---|---|---|
agent_set_abstraction | PRECONDITION_FAILED — agentSetAbstraction is not available; the account owner must sign userSetAbstraction | Accepted, and wrote nothing. It never set a config |
update_leverage | MARKET_NOT_FOUND — no perp market for asset | Wrote a leverage row for a market that does not exist |
c_deposit / c_withdraw | PRECONDITION_FAILED — amount is finer than the token's wei_decimals | Committed a sub-wei amount and left dust no ledger row can render |
Rules with no height pin
The rules below have no height pin. They turned on when node 0.9.10 took over, so they also apply from block 11,550,001.
| Rule | Before the swap |
|---|---|
A spot order that the spot balance cannot fund at all is refused: ASSET_INSUFFICIENT_BALANCE, insufficient spot balance. A spot scale_order rung and a spot chase_order leg get the same refusal | Accepted as a no-op that answered filled with total_sz: "0". An unfunded chase leg answered chase leg did not rest |
A split standard account has no reservations. Its perp and option orders are admitted against the perp wallet with no cap. available_to_trade has no cap, account_state omits reservations, and user_set_abstraction kinds 1–3 are refused with a split standard account has no reservations | The perp and option reservations capped the account, so a split account with no perp reservation opened no perp position |
usd_class_transfer from a short spot wallet answers insufficient spot balance, with ASSET_INSUFFICIENT_BALANCE | insufficient spot USDC balance |
A perp delist closes every open position and marks the market settled. markets gains settled and settled_px, orders get market settled — trading closed, and ledger_updates gains cause: "delist_settlement" | A delist halted the market and left its positions open |
| Funding stops while closing is disabled or the market is settled | Funding settled on every market, whatever its flags |
A staking_state delegation row carries lock_months and reward_weight | The row carried neither key |
Gateway rows
These rows ship with the gateway, not at a block height. Gateway 0.9.8 is the first to carry them, from 2026-09-16.
Four relocated reads answer 410 instead of 400
spot_meta, all_mids, active_asset_ctx and user_events answer 410
UNKNOWN_TYPE with details.use, which names the read to call instead. Before
gateway 0.9.8 they answered a bare 400, even though this reference names a
replacement for each.
No working call broke: a 400 and a 410 both mean "do not call this name".
Branch on error.code rather than on the status, and a client is correct on both
sides.
Read-side and WebSocket rows
active_asset_datarefuses a spot pair, an unknown coin and a coin that names no perp, with{"channel":"error","data":{"error":"market not found"}}. This row needs both halves: the node refusal from block 11,550,001 and the gateway's. Before, the channel answered a zeroed snapshot that blanked your ownaddressandcoin.- A subscribe answers ONE snapshot frame.
open_ordersstays the exception. Before, some subscribes answered twois_snapshot: trueframes, and the two could disagree. trades:limitcaps the merged answer. Before, the gateway capped each source on its own, so a ranged ask could return up to twice the number you asked for.trades.last_tradeis the newest print in THIS answer, which is what the reference has always said. This row also needs both halves. Before, the node stamped the MARKET's newest print on a windowed page, so the page read as if it ran later than it did.candle_snapshot:coverage.reaches_newestis proved against the newest bar the store holds, not against yourend_time. Before, a windowed ask readtrueon its first page.- An archive-served bar stamps
Tast + interval − 1, like every other source. Before, it stampedt + interval, so the convention changed at the join seam. - An archive trade bar's volume is divided by the size plane THAT BAR states. A
bar that states none falls back to the market's current precision. Before, every
bar used the current precision, which reads
10^Δtoo small after a raise. The plane is not a field on the served bar. Whether the archive states it on every bar is not verified yet.