Skip to content

Prices & Order Book

Prices are USDC per share, strictly between 0 and 1, on a fixed tick grid configured per market. An order whose implied price doesn’t land on a tick is rejected at insert.

You don’t send a price. You send two amounts, and the price is their ratio:

BUY price = maker_amount / taker_amount (collateral offered ÷ shares wanted)
SELL price = taker_amount / maker_amount (collateral wanted ÷ shares offered)

Buying 100 shares at $0.50: makerAmount = "50000000", takerAmount = "100000000". Both are raw 6-decimal units.

Internally the order manager carries price as a rust_decimal::Decimal with fixed tick-size rounding, and sizes as u64 raw units. Every value on the wire is a decimal string, never a float.

One book per token ID — so a market with two sides has two books, and the NO book is the mirror of the YES book.

Orders are sorted by price → time → order ID:

ASKS (sellers) ordered by price ascending
0.54 × 800
0.53 × 1500
0.52 × 900 ← best ask
──────────────────────────────── spread
0.48 × 1200 ← best bid
0.47 × 600
0.45 × 2000
BIDS (buyers) ordered by price descending

The order ID tiebreak matters: two orders that arrive in the same instant still have a deterministic, reproducible sequence, so matching is replayable.

Each book is a BTreeMap, giving O(log n) insert and delete with ordered iteration for the price-time walk.

Terminal window
curl -s "http://127.0.0.1:8080/v1/order-book-summary?condition_id=$CONDITION_ID&token_id=$TOKEN_ID" \
-H "Authorization: Bearer $CLOB_TOKEN" | jq
{
"market": "0xdemo-condition",
"asset_id": "100",
"bids": [{ "price": "0.48", "size": "1200" }],
"asks": [{ "price": "0.52", "size": "900" }],
"hash": "9f2c7a…"
}

hash is a SHA-256 over the canonical JSON of the other fields, recomputed on every change. Two uses:

  • Change detection — compare hashes instead of diffing levels.
  • Consistency — confirm a snapshot you cached matches what a stream event describes.

GET /v1/book?token_id=… returns the aggregated summary and is the simpler call when you just want top-of-book.

size at a level is the total across every resting order there, not one order. To see individual orders:

Terminal window
# Your own resting orders, original form
curl -s "http://127.0.0.1:8080/v1/orders?market=$CONDITION_ID" \
-H "Authorization: Bearer $CLOB_TOKEN"

GET /v1/orders and GET /v1/orders/value-range are always scoped to your authenticated address. Other traders’ order hashes and owners are never published — you see aggregate depth, not the participants.

Terminal window
curl -s "http://127.0.0.1:8080/v1/trades/last?market=$CONDITION_ID" \
-H "Authorization: Bearer $CLOB_TOKEN"
curl -s "http://127.0.0.1:8080/v1/trades?market=$CONDITION_ID&limit=50" \
-H "Authorization: Bearer $CLOB_TOKEN"

Trade history is served from the ledger, not the book — fully matched orders are gone from the book and only exist in the ledger.

For charting, the Platform API’s /api/markets/{market_id}/price-history/ is the aggregated series the webapp uses.

The book is off-chain state. The on-chain Market account carries its own price record, advanced only by real settlements:

FieldMeaning
last_price_yesLast executed YES price, 1e6 fixed point; starts at 500_000
price_cumulative_yesRunning integral of last_price_yes × dt
total_volumeCumulative collateral notional of settled fills

That’s what a parlay leg prices against — a one-hour TWAP, not the spot book — so a momentary book dislocation can’t be used to mint a mispriced slip. → CLOB Markets as Legs

Two protections apply during matching:

  • Price distortion guard — a taker walking the book is stopped if the fill price drifts beyond a configured bound from the reference price (0.02 in the demo config). This caps the damage from a fat-finger market order sweeping a thin book.
  • Self-trade filter — liquidity from the same maker is filtered out of the walk, so you cannot cross your own resting orders.

→ Match Types