Orderbook internals¶
Spot and perpetual markets share page-backed active-order storage and FIFO mechanics through ActiveOrderBookCore. They deliberately do not share settlement: Spot mutates custody through ISpotBalanceAccount, while Perp builds journals for AccountCore and PerpEngine.
Inheritance shape¶
ActiveOrderBookState
-> ActiveOrderBookCore
-> OrderBookState -> Spot modules -> OrderBook
-> PerpOrderBookState -> Perp modules -> PerpOrderBook
The shared core owns market precision, execution state, maker/queue/tree anchors, order and trade ID allocation, access resolution, packed trade capture, and raw quote conversion. Product modules own order validation, fees, reserve/exposure accounting, fills, special liquidity, and product-specific events.
Three active-book structures¶
Maker pages¶
MakerPageLib gives each account ID one 128-slot page:
- one metadata word with initialization, allocation bitmap, primed count, and live count;
- 62 real order slots (
0..61), one packed word per order; - a dedicated post-fill-hook slot; and
- reserved/spare page space.
An order word stores size, price, price-queue entry index, side, reduce-only bit, maker-fee snapshot, order ID, and minSizeAfterBlock. Spot leaves reduce-only unset; Perp uses it.
Queue encoding reserves logical slot 62 for the passive-liquidity marker and 63 for tombstones, which is why a maker page is capped at 62 real orders.
Price queues¶
PriceQueueLib groups 16 adjacent ticks into one page and maintains an independent doubly linked FIFO queue for each tick. A group has 504 linked-list entries and no spill page; exhausting it reverts placement even if maker slots remain. Queue entries contain only account ID and maker slot; the full order remains in the maker page. Removal uses tombstones and a bounded free-entry cache. Although internal storage helpers can manipulate links, every public live replacement is reinserted at the tail.
New resting orders append at the tail. Matching consumes from the head. A price level disappears from the tree when its same-side queue becomes empty.
Dual price tree¶
DualPageTreeMath stores bid and ask occupancy as hierarchical bitmaps keyed by tick. It finds the next executable price without scanning empty ticks. Bid and ask visibility is independent, including when Spot passive liquidity shares a synthetic marker at a tick.
The tree also stores compact common market metadata and last-trade observation. Product-reserved fields keep Spot fee/hook settings out of the product-neutral core.
Order lifecycle¶
- Resolve the direct account or delegated
TRADEsigner. - Validate market state, tick alignment, size, notional, TIF, and product-specific risk.
- Match against the best opposing tick and FIFO head.
- Update or remove each maker slot and queue entry; append packed trade records.
- Settle Spot fills or append Perp exposure/fill journals.
- For a GTC residual, allocate a maker slot, append it to the price tail, set the tree bit, and append a live book update.
- Flush product accounting and packed events. Any downstream failure reverts the entire action.
Same-account handling is product and entrypoint specific: Perp and ordinary Spot order matching cancel the same account ID's resting head and continue without a fill, while Spot swap reverts. Sibling subaccounts are separate IDs. A fresh crossing post-only request is skipped without allocating an order ID or slot; in packed replacement, cancel-first semantics have already removed the old order before the crossing replacement is skipped. FOK is atomic across the complete action, subject to Spot's rule that an unexecutable atomic-quote tail after a real fill is complete.
Packed slot replacement¶
Both products accept fixed 32-byte operations:
[7:0] maker slot
[15:8] flags
[47:16] price
[143:48] size / base lots
[175:144] minSizeAfterBlock
[255:176] reserved by the format
Spot flags are buy (0x01) and ALO/post-only (0x08). Perp additionally uses reduce-only (0x04). A strict zero operation except for slot is a cancel. Duplicate slots in one payload revert. Spot accepts an empty operation array and performs no order mutation, but the wrapper still resolves or registers the account and may initialize its maker page; Perp requires at least one 32-byte operation. Spot currently ignores the reserved high 80 bits, while Perp rejects a nonzero reserved region. Canonical encoders should write zero for both products.
Replacement is cancel-first. Accounting releases the old residual claim before evaluating the new operation. Every live replacement receives a new order ID and moves to FIFO tail, including a same-price replacement. A crossing order is removed from its old level before matching; an admissible residual reuses the requested maker slot.
Spot nets reserve and release totals within settlement windows. A crossing replacement may flush releases before matching, and a mixed native batch may use separate release and reserve windows. Perp nets local order-exposure and maker-fee-reserve journals before one AccountCore action.
Stale-size visibility¶
A nonzero minSizeAfterBlock must be in the future when the order is created. It becomes stale only when block.number > minSizeAfterBlock.
After that point, matching exposes:
min(stored size, minimum executable size at the order price)
The larger hidden portion is not executable. When a stale order is touched, settlement still uses the true stored before/after state so reserve and perpetual exposure release remain exact. The order is not automatically deleted at the threshold.
At this snapshot, product views are not uniform: Perp getL2Book applies the visible-size cap. Spot matching and swap estimation also apply it, but Spot getL2Book can include the full stored size until touch. Consumers that need executable Spot L2 must apply the block-based cap themselves.
Priming and Monad storage¶
Maker and queue pages can be pre-initialized through public priming helpers. Priming changes storage warmth/cost characteristics but does not create liquidity. Page alignment and packed words are designed for Monad's page-based storage model; changing layout or sentinel values is an upgrade-sensitive protocol change.
Important invariants¶
- A live maker slot points to exactly one live queue entry, and that entry points back to the same account/slot.
- A side's tree bit exists if and only if that side has visible liquidity at the tick.
- FIFO order changes only through explicit cancel/reinsert/move semantics.
- Order IDs and trade IDs are market-local and monotonic.
TradesPacked.updatedSizeis authoritative after a fill. Perp dust can report a nonzero visible residual that a following non-live update deletes. Spot stale-visible dust instead reports zero, frees the slot immediately, and may append a later non-live description of the discarded residual; that record is an identity-checked no-op against the already-cleared slot.- Product accounting must succeed before the local book mutation becomes final.