Skip to main content
Orders are signed off-chain with EIP-712 typed data and submitted to POST /v1/orders. This page covers exactly what to sign and how to compute the amounts so the matching engine accepts your order.
Amount math is unforgiving. maker_amount and taker_amount must be derived with BigInt integer-tick math at the collateral’s decimals (18 on BSC), with BUY ceiling and SELL flooring the collateral. Anything signed with float arithmetic or the wrong decimals is rejected at ingest with Drifted signed amounts.

EIP-712 domain

verifyingContract is the market’s ctf_exchange_address — read it from the market object. Do not hard-code it; it can differ per chain.

The Order struct

The typed-data primary type is Order, with these 12 fields in this order:
The order kind (LIMIT / MARKET / GTD) is not part of the signed struct — it travels on the REST request body only as order_type. The signed struct carries side as a number, not the human-readable string.

Outcome → token

Pick the token_id for the outcome you want from GET /v1/markets/{condition_id}/tokens:
To buy YES, sign with outcome: 1 and the YES token_id.

Prices are 2-decimal ticks

A price is a YES probability in [0.01, 1.00], quantized to 2 decimal places (100 ticks). Sign at sub-tick precision and the on-chain crossing check can reject the match, so round first:

Amount math

Read decimals from the market’s /tokens response (18 on BSC). Then, with price (already quantized) and size as decimal strings:
All values are scaled to the collateral’s decimals. BUY ceils the collateral and SELL floors it — this asymmetric rounding is what keeps the on-chain crossing check satisfied. It costs at most 1 wei.
Stay in BigInt the entire way. Number, parseFloat, and * price on a float silently corrupt 18-decimal amounts by a few wei, which the ingest validator rejects. Compute collateral as size × price at full scale, then ceil (BUY) or floor (SELL).

Complete example (viem)

Submit the order

The request body wraps the signed struct in snake_case, plus the human-readable order_type, outcome, price, and size:
You can batch up to 15 orders per request. The response always returns status: OPEN for accepted orders — matching is asynchronous:
Each item in results[] is either a success or an inline { "error": { "code", "message" } }. A mixed array is normal — always inspect every item. Fills and status transitions arrive on the user WebSocket channel, not here.

Idempotency

Set an fs-idempotency-key header on POST /v1/orders to make retries safe. Replaying the same key + same body returns the cached result with _idempotent_replayed: true. The same key + a different body returns IDEMPOTENCY_CONFLICT (409). Idempotency is honored on order placement only.

Cancelling

  • POST /v1/orders/cancel — cancel by hash (up to 100), by filter (condition_id + chain_id + optional outcome/side), or all (empty body).
  • POST /v1/orders/{order_hash}/cancel-onchain — returns unsigned calldata for a hard on-chain cancel against the CTF Exchange. You submit and pay for that transaction yourself; the backend does not send it.