Orderbook
OddMaki's fully on-chain Central Limit Order Book (CLOB) — order types, price-time priority, and matching mechanics.
OddMaki runs a fully on-chain CLOB. Every order, fill, and state change happens on Base — no off-chain sequencer, no centralized matching.
CLOB Overview
Each market has two independent books (YES and NO), each with a BUY and SELL side. Four facets handle orderbook operations:
| Facet | Role |
|---|---|
LimitOrdersFacet | Place, cancel, expire orders |
MarketOrdersFacet | Immediate execution (FOK/FAK) |
MatchingFacet | Match crossing orders |
OrderBookFacet | Read-only queries |
Order Types
Limit orders — Resting orders at a specific price tick. Remain on the book until filled, cancelled, or expired.
const hash = await client.trade.placeOrderSimple({
marketId: 1n,
outcomeId: 0n, // YES
side: 'BUY',
price: '0.65', // $0.65
amount: '100', // 100 tokens
expiry: 0n, // GTC (no expiry)
});Fill-or-Kill (FOK) — Execute the entire order immediately or revert. No partial fills.
Fill-and-Kill (FAK) — Fill as much as possible immediately, cancel the remainder.
const result = await client.trade.placeMarketOrder({
marketId: 1n,
outcomeId: 0n,
collateralAmount: parseAmount('50', 6), // spend up to 50 USDC
maxPriceTick: 80n, // slippage protection
orderType: 'FAK',
});Price-Time Priority
Prices are integer ticks where price = tick × tickSize / 1e18. With a 1% tick size, prices range from $0.01 (tick 1) to $0.99 (tick 99).
| Side | Price Priority | Time Priority |
|---|---|---|
| BUY | Higher ticks first | FIFO within same tick |
| SELL | Lower ticks first | FIFO within same tick |
Normal fills execute at the ask price (seller's price).
Order Lifecycle
- Place — Order is validated, collateral escrowed, and inserted into the tick-level FIFO queue
- Match — Anyone calls
matchOrders(marketId, maxSteps)to trigger the matching engine - Fill — Three settlement paths: Normal Fill, Mint-to-Fill, Merge-to-Fill (see Matching & Settlement)
- Cancel — Owner cancels, collateral refunded
- Expire — Past-expiry orders are cleaned up inline during matching or via
expireOrders()
The matching engine evaluates paths in fixed priority per step:
- Normal Fill (YES) → 2. Normal Fill (NO) → 3. Mint-to-Fill → 4. Merge-to-Fill → 5. No fill → break
Gas Optimization
- Doubly-linked tick lists — O(1) insert/remove at head/tail, O(1) top-of-book lookup
- FIFO queues via order pointers — No array operations, constant-gas enqueue/dequeue
- Inline expiry cleanup — Matching cleans up expired orders on-the-fly (up to 10 per attempt)
- Conditional storage writes —
topOfBookonly updated when the best price changes - Storage deletion refunds — Filled/cancelled orders are deleted for gas rebates
| Constant | Value | Purpose |
|---|---|---|
MAX_ITERATIONS | 50 | Max levels walked per market order |
MAX_INLINE_EXPIRY_RETRIES | 10 | Max expired-order cleanups per fill attempt |
MAX_EXPIRE_BATCH | 100 | Max orders per expireOrders call |
What's Next
- Matching & Settlement → — Normal Fill, Mint-to-Fill, Merge-to-Fill explained
- Token Mechanics → — how outcome tokens are minted and burned