Skip to main content
XO follows the canonical CLOB wire conventions almost exactly. The gotchas below catch most integration bugs.

Prices

  • Tick size is 0.001 globally. Submit a price that’s not a multiple of the tick and the order is rejected (400 "invalid price tick").
  • Prices are strings, not JSON numbers, on every field listed above (except the f64 minimum_tick_size returned by /tick-size).
  • Empty-book sentinels: /midpoint returns "0.000", /spread returns the bare "0", /last-trade-price defaults to "0.5" with side: "". Don’t assume they all share one default.

Sizes

The CTFOrder makerAmount/takerAmount you sign on-chain are in micro-shares × tick math. The orderbook converts to human-share decimal strings on the way out. Don’t confuse the two when reconciling sizes across REST and WS surfaces.

Timestamps

XO emits timestamps in three different formats depending on the field. Mismatched parsing here is the most common integration bug. When in doubt: if it’s in a book snapshot or a WS frame, it’s stringified ms. If it’s a trade match_time or an order’s expiration/created_at, it’s seconds. The SDK uses TimestampSeconds<String> and TimestampMilliSeconds<String> types to enforce this — your code should too.

IDs

Pagination cursors

MarketsPage, OrdersPage, TradesPage all share:
next_cursor is always a string, never null. The literal "LTE=" is the canonical CLOB end-of-stream sentinel — when you see it, stop paginating.

EIP-712 order signing

Domain (mainnet):
Order typehash (13 fields — signature is NOT in the typehash):
Legacy keys taker and feeRateBps are ignored on parse and must not be signed — the typehash above is authoritative. Fees are not part of the signed order; see Fees. signatureType values: For signatureType = 3 the orderbook enforces maker == signer (both set to the smart account) and validates the 65-byte ECDSA signature — produced by the owner EOA — via IERC1271(maker).isValidSignature(orderHash, signature). See Smart accounts for the full hashing reference, including the exact Solidity hashOrder function the exchange runs.

L1 ClobAuth domain (auth, not orders)

The auth headers for POST /auth/api-key and GET /auth/derive-api-key use a separate EIP-712 domain — no verifyingContract field:
The message is the literal string "This message attests that I control the given wallet" by SDK convention. Don’t confuse this domain with the order-signing domain above — they’re separate.