Skip to content

Spot markets

OrderBook is a fully collateralized on-chain FIFO Spot market. It manages orders and passive-liquidity accounting while AccountCore holds and moves the assets.

Market creation

Governance uses SpotRouter.deploySpotMarket after:

  1. enabling both assets in AccountCore;
  2. whitelisting them in SpotRouter; and
  3. setting the implementation used for new orderbook proxies.

The router computes a configuration-dependent CREATE2 salt, deploys and initializes an ERC-1967 proxy, optionally propagates the shared authority, registers immutable parameters in SpotEngine, and authorizes the book in AccountCore.

The immutable market configuration includes base/quote assets, size and price precision, tick size, passive spread, min/max quote notional, and market fee caps. Base and quote must differ; one side may be native MON, but a native/native pair is invalid.

Execution state

State New orders, matching, swaps, packed replace User cancel Passive mint Passive burn / fee claim Protocol cancel
ACTIVE Yes Yes Yes Yes Yes
SOFT_PAUSED No Yes No Yes Yes
HARD_PAUSED No No No Yes Yes

SpotRouter.toggleSpotMarkets is the only Spot market-state administration path. Emergency, ops, or governance may tighten state; only governance or ops may loosen it. SpotEngine is an immutable registry and has no separate active flag.

Native order flow

batch cancels the requested slots first, then processes orders in array order. Each order includes side, quantity, limit price, GTC/IOC/FOK time in force, optional post-only instruction, and optional minSizeAfterBlock.

  • GTC matches first and rests an admissible residual.
  • IOC discards the residual.
  • FOK reverts the complete action unless fully filled.
  • Post-only must be GTC. A crossing post-only order is skipped as a successful no-op.

Prices must align to tickSize, and requested sizes and notionals must fit market bounds. During ordinary order matching, encountering the same account ID's resting order cancels that maker order and continues; swap instead reverts on the same condition. Distinct sibling subaccounts have distinct IDs and are not treated as self-trades. Quote conversion is shared across reserve, matching, settlement, swaps, and passive math.

A requested GTC must meet the market minimum notional. After a partial fill, however, the residual is not rechecked against that configured minimum before resting. A smaller residual may remain live if it still maps to a nonzero atomic quote amount; it continues to affect BBO, post-only, FOK, and passive-mint decisions until filled or cancelled.

replaceBySlotPacked is the market-maker hot path. It accepts fixed 32-byte upsert/cancel operations keyed by maker slot, rejects duplicate slots, and releases the old order before reserving a replacement. If a replacement crosses, it flushes the released reserve before matching so that value is available to settlement; an admissible residual may then reuse the same slot.

Reserve and settlement model

  • Resting bid: reserve quote notional plus the snapshotted maker fee.
  • Resting ask: reserve base size.
  • Cancellation/reduction: release only the unfilled residual reserve.
  • Maker fill: consume the filled portion directly from reserved balance; do not transiently expose it as free balance.
  • Taker fill: debit input and credit output from free balance.
  • Protocol fee: credit the configured fee collector.
  • Builder fee: accrue to the approved builder in the charged asset.

Batch reserve deltas are netted per base/quote token before storage is updated. Any balance, fee, or settlement failure reverts both the AccountCore changes and FIFO mutation.

Unexecutable dust cannot remain at the queue head. A maker residual that cannot settle at least one atomic quote unit is canceled; after a real taker fill, a similarly unexecutable taker tail is treated as complete for IOC/FOK handling.

Exact-input swaps

swap walks active FIFO and passive liquidity with a fee-inclusive exact input and a minimum-output guard:

  • buy: spend quote, receive base;
  • sell: spend base, receive quote.

The result reports input actually used and output actually received after applicable fees. Buy-side affordability is recomputed at each ask price. estimateSwap applies visible-book and fee math only; it does not validate the account's free balance or builder approval, and the book or fee policy may change before submission.

SpotPeriphery aggregates estimate calls and offers deposit helpers only. It has no trade-execution entrypoint.

Passive liquidity bands

A band spans lowPrice to lowPrice + passiveSpreadTicks * tickSize and may hold quote at the low edge, base at the high edge, or both. AccountCore locks contributed assets while the orderbook tracks inventory, shares, and Q128 fee-growth accumulators.

  • Empty-band shares use low-edge principal value as the initial scale.
  • In a live band, base deposits are valued conservatively at the low edge and quote deposits against the high-edge band value. This prevents mint/burn from becoming a free swap through the spread.
  • minSharesOut and deadlines protect mints. Batch mint clips invalid legs against the live BBO and skips entries with no valid leg.
  • Burns return pro-rata principal and fees. Claims advance fee-growth checkpoints without burning shares.
  • Passive fees are spread proceeds; protocol taker and builder fees are accounted separately.

Passive liquidity is represented by a synthetic marker at the head of the price queue, so passive depth executes before ordinary FIFO orders at the same price. Bid and ask tree visibility remains side-specific even though the marker is shared.

Post-fill hooks

A maker may configure one hook. After a resting order is fully removed at a match boundary, the hook receives bounded context and may request:

  • replenishment of the filled slot;
  • replacement of one opposite-side slot, optionally bound to an expected order ID; or
  • both as a non-crossing pair.

For the hook codec, an expected sibling order ID of 0 explicitly skips that sibling identity check. This differs from the trading-wallet codec, where expected ID 0 means the slot must be empty.

The callback has a governance-set gas stipend. Callback failure, malformed output, stale slot identity, invalid price/size, insufficient reserve, or a crossing plan fails open: the triggering fill remains valid and the refresh is discarded atomically.

Initialization sets the hook-order minimum notional equal to the market minimum. Later governance updates must set it strictly above the market minimum and no higher than the market maximum.

Accepted replenishment is synchronous and may be consumed by the same taker action. The per-callback gas stipend does not cap the total number of valid refreshes that a large taker can trigger, so integrators must bound transaction gas and order size.

Primary views

  • bestBidAsk() and getL2Book(levels) expose book liquidity. Empty bid is type(uint32).max and empty ask is 0. At this snapshot, Spot L2 can overstate executable size for an expired minSizeAfterBlock order until that order is touched; clients needing executable depth must apply the same stale-size cap as matching.
  • getOrderId(accountId, slot) identifies the order currently occupying a slot.
  • makerLockedReserves(accountId) derives the maker's active reserve in this market.
  • getPassiveBand(lowPrice) and getPassivePosition(positionId) expose passive state.
  • lastTradeObservation() is the last execution observation, not an oracle.