Order Matching
This chapter describes how orders are submitted, matched, filled, and settled in the on-chain perpetual futures order book.
1. Order types
An order can be order:
- Market — immediate-or-cancel (IOC). Specifies a
max_slippagerelative to the oracle price. Any unfilled remainder after matching is discarded (unless nothing filled at all, which is an error). - Limit — good-till-cancelled (GTC). Specifies a
limit_price. Any unfilled remainder is stored as a resting order on the book. Ifpost_onlyis set, the order is rejected if it would cross the best price on the opposite side — it takes a fast path and never enters the matching engine.
Resting orders on the book are stored as:
| Field | Description |
|---|---|
user | Owner address |
size | Signed quantity (positive = buy, negative = sell) |
reduce_only | If true, can only close an existing position |
reserved_margin | Margin locked for this order |
The pair ID, order ID, and limit price are part of the storage key.
2. Order decomposition
Before matching, every fill is decomposed into a closing and an opening portion based on the user’s current position:
| Order direction | Current position | Closing size | Opening size |
|---|---|---|---|
| Buy (+) | Short (−) | ||
| Sell (−) | Long (+) | ||
| Same direction | Any |
Both closing and opening carry the same sign as the original order size (or are zero). For reduce-only orders, the opening portion is forced to zero — if the resulting fillable size is zero, the transaction is rejected.
3. Target price
The target price defines the worst acceptable execution price for the taker:
Market orders (bid/buy):
Market orders (ask/sell):
Limit orders: (oracle price is ignored).
A price constraint is violated when:
- Bid:
- Ask:
4. Matching engine
The matching engine iterates the opposite side of the book in price-time priority:
- A bid (buy) walks the asks in ascending price order (cheapest first).
- An ask (sell) walks the bids in descending price order (most expensive first). Bids are stored with bitwise-NOT inverted prices so that ascending iteration over storage keys yields descending real prices.
At each resting order the engine checks two termination conditions:
- — the taker is fully filled.
- The resting order’s price violates the taker’s price constraint.
If neither condition is met, the fill size is:
After each fill the maker order is updated: reserved margin is released proportionally, and if fully filled the order is removed from the book and open_order_count is decremented.
5. Pre-match margin check
Before matching begins, the taker’s margin is verified (skipped for reduce-only orders). The check ensures the user can afford the worst case — a 100 % fill:
where is the initial margin assuming the full order fills (see Margin §5) and is
This prevents a taker from submitting orders they cannot collateralise.
6. Self-trade prevention
The exchange uses EXPIRE_MAKER mode. When the taker encounters their own resting order on the opposite side:
- The maker (resting) order is cancelled (removed from the book).
- The taker’s
open_order_countandreserved_marginare decremented. - The taker continues matching deeper in the book — no fill occurs for the self-matched order.
7. Fill execution
Each fill between taker and maker is executed as follows:
7a. Funding settlement
Accrued funding is settled on the user’s existing position before the fill:
The negated accrued funding is added to the user’s PnL (positive accrued funding is a cost to longs).
7b. Closing PnL
For the closing portion of the fill:
Long closing (selling to close):
Short closing (buying to close):
The position size is reduced by the closing amount. If the position is fully closed, it is removed from state.
7c. Opening position
For the opening portion of the fill:
- New position: entry price is set to the fill price.
- Existing position (same direction): entry price is blended as a weighted average:
7d. OI update
Open interest is updated per side:
- Closing a long:
- Closing a short:
- Opening a long:
- Opening a short:
8. Trading fees
Fees are charged on every fill:
The fee rate differs by role:
| Role | Rate | Example value |
|---|---|---|
| Taker | taker_fee_rate | 0.1 % |
| Maker | maker_fee_rate | 0 % |
Fees are always positive (absolute value of fill size is used). They are routed to the vault via the settlement loop described below.
9. PnL settlement
After all fills in an order are complete, PnLs and fees are settled atomically as in-place USD margin adjustments. No token conversions occur during settlement — all values are pure UsdValue arithmetic.
9a. Fee loop
For each non-vault user with a non-zero fee:
Fees from the vault to itself are skipped (no-op). Processing fees first ensures collected fees augment before any vault losses are absorbed.
9b. PnL loop
Non-vault users:
A user’s margin can go negative temporarily — the outer function handles bad debt (see Liquidation).
Vault:
A negative represents a deficit (bad debt not yet recovered via ADL).
10. Unfilled remainder
After matching completes:
- Market orders: the unfilled remainder is silently discarded. If nothing was filled at all, the transaction reverts with “no liquidity at acceptable price”.
- Limit orders (GTC): the unfilled remainder is stored as a resting order. Storage requires:
open_order_count<max_open_orders- Price is aligned to the pair’s tick size ()
- Sufficient available margin (skipped for reduce-only orders) — see below
Margin reservation (non-reduce-only):
The unfilled portion’s margin requirement is computed and checked against available margin (see Margin §7–§8):
If the check passes, reserved_margin is increased by and open_order_count is incremented. This is the 0 %-fill scenario check — it ensures the user can afford the order even if nothing fills immediately.
Post-only limit orders take a fast path that bypasses the matching engine entirely. They are rejected if they would cross the best price on the opposite side:
- Buy:
- Sell:
If the opposite book is empty, the order always succeeds.
11. Open interest constraint
Each pair has a parameter enforcing a per-side cap:
- Long opening:
- Short opening:
The constraint is checked before matching and does not apply to reduce-only orders (which have zero opening size). Long and short OI limits are independent but share the same parameter.
12. Order cancellation
Single cancel
A user can cancel any individual resting order by its order ID.
On cancellation:
- The order is removed from the book.
reserved_marginis released (subtracted from the user’s total).open_order_countis decremented.- If the user state is now empty (no positions, no open orders, no pending unlocks), it is deleted from storage.
Bulk cancel
A user can cancel all of their resting orders across both sides of the book in a single transaction. The contract iterates the user’s resting orders, removing each one and releasing margin. The same cleanup logic applies — if the user state becomes empty after all orders are removed, it is deleted.