Place Orders
The order
Section titled “The order”An order says: I give up makerAmount of one asset to receive takerAmount
of the other. Price is the ratio, not a field.
{ "salt": 1234567890, "maker": "7xKX…", // your address "signer": "7xKX…", // must equal maker "taker": "11111111111111111111111111111111", // zero address "tokenId": "100", "makerAmount": "50000000", // raw units, 6 decimals "takerAmount": "100000000", "side": "BUY", "expiration": "0", "nonce": "0", "feeRateBps": "0", "signatureType": 0, "signature": "a1b2…" // ed25519 over the order hash}Wire names are camelCase. Every amount is a decimal string of raw token units — USDC and outcome tokens both use 6 decimals.
Fields
Section titled “Fields”| Field | Type | Notes |
|---|---|---|
salt | i64 | Makes otherwise identical orders distinct |
maker | string | Base58 Solana address; owns the token accounts |
signer | string | Must equal maker — the program settles EOA orders only |
taker | string | Must be the zero address; orders are public |
tokenId | string | The asset — one side of one market |
makerAmount | string | What you give up |
takerAmount | string | What you want |
side | string | "BUY" or "SELL" |
expiration | string | GTD unix seconds; "0" for GTC and FOK |
nonce | string | Must be "0" — there is no on-chain nonce account |
feeRateBps | string | Must cover the taker fee if the order is marketable |
signatureType | u32 | 0 = EOA. 1/2 (legacy wallet types) are rejected |
signature | string | 64-byte ed25519, hex or base58 |
Direction
Section titled “Direction”side | You give | You get | Price |
|---|---|---|---|
BUY | collateral (USDC) | outcome tokens | makerAmount / takerAmount |
SELL | outcome tokens | collateral | takerAmount / makerAmount |
Buy 100 shares at $0.50 → makerAmount: "50000000", takerAmount: "100000000".
Sell 100 shares at $0.60 → makerAmount: "100000000", takerAmount: "60000000".
Signing
Section titled “Signing”Hash the order with the domain-separated layout, then sign the hash:
sha256( b"CTF_EXCHANGE_ORDER_V1" ‖ salt:u64LE ‖ maker:32B ‖ signer:32B ‖ taker:32B ‖ token_id_len:u64LE ‖ token_id:UTF8 ‖ maker_amount:u64LE ‖ taker_amount:u64LE ‖ expiration:u64LE ‖ nonce:u64LE ‖ fee_rate_bps:u16LE ‖ side:u8 ‖ signature_type:u8 )
side: BUY=0, SELL=1 signature_type: EOA=0The signature is not part of the hash.
cat order.json | cargo run -p sign-order --quiet -- --wallet aliceThis hash is the order’s identity everywhere — the returned orderID, the
de-dup key, and the digest the on-chain program recomputes and verifies
against a top-level ed25519 instruction at settlement.
Submitting
Section titled “Submitting”curl -s -X POST http://127.0.0.1:8080/v1/orders \ -H "Authorization: Bearer $CLOB_TOKEN" \ -H "Content-Type: application/json" \ -d '{"order": { … }, "owner": "7xKX…", "orderType": "GTC"}'{ "success": true, "errorMsg": "", "orderID": "0x7f3a…", "transactionsHashes": ["5Kx…"], "status": "matched"}owner is accepted but ignored — the authenticated SIWS address is always
the owner.
Order types
Section titled “Order types”| Type | expiration | Behaviour |
|---|---|---|
GTC | "0" | Rests until filled or cancelled |
GTD | future unix seconds | Rests until it expires, then is ejected |
FOK | "0" | Fills completely on arrival or is killed — never rests |
Reading the response
Section titled “Reading the response”success: true means accepted, not filled. Read status:
status | Meaning |
|---|---|
live | Resting on the book |
matched | Crossed and executed; see transactionsHashes |
delayed | Parked by the pending service (game-start window) |
unmatched | Could not fill — typically a killed FOK |
A rejection is success: false with a human-readable errorMsg and an empty
orderID.
What the validator checks
Section titled “What the validator checks”In order, from manager/insert_order.rs:
- Resolve the token ID.
- Find the order book for that token.
- Build the
ProcessedOrder— price/size math and the order hash. - Validate on insert: signature, expiration, fee rate, tick size, and on-chain status / nonce / balance over JSON-RPC.
- Duplicate check in the books.
- Market readiness.
- Rounding config lookup by tick size.
- Amount and tick-size validation.
- Unique-order registry check.
- Balance and allowance via the risk service — reserves the maker amount.
- Fee-rate validation (marketable orders pay the taker fee).
- GTD orders registered on the expiry pending service.
- Match, insert, or schedule.
Common rejections
Section titled “Common rejections”| Reason | Fix |
|---|---|
| Signature invalid | The hash layout must match byte for byte — check field order and LE encoding |
signatureType not 0 | Only EOA orders settle on-chain |
signer != maker | They must be the same key |
taker not zero address | Bilateral orders aren’t supported |
nonce != "0" | There is no on-chain nonce account |
| Price off the tick grid | Round makerAmount/takerAmount so the ratio lands on a tick |
| Fee rate too low | A marketable order must carry at least the taker fee |
| Insufficient balance | Fund the ATA; remember earlier orders hold reservations |
| No book for that token | The market isn’t in the catalogue or isn’t ready |
| Duplicate order | Change the salt |
A missing delegate allowance is a warning at insert, not a rejection — but settlement will fail on-chain. Set it first:
cargo run -p delegate --quiet -- set --keypair ~/.config/solana/id.json --amount 1000000000Practical notes
Section titled “Practical notes”- Reservations are real. Step 10 reserves the maker amount, so a $100 balance cannot back two $100 orders. Cancel to free it.
- Fee rate depends on role. An order that is marketable on arrival pays the taker fee. Pricing inside the spread and pricing through it are different orders as far as the validator is concerned.
- Game-start markets delay. A
delayedstatus isn’t an error — the order is held and inserted when the window opens. - Self-trades are filtered. Your own resting liquidity is excluded from the walk; you can’t cross yourself.