Skip to content

Place Orders

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.

FieldTypeNotes
salti64Makes otherwise identical orders distinct
makerstringBase58 Solana address; owns the token accounts
signerstringMust equal maker — the program settles EOA orders only
takerstringMust be the zero address; orders are public
tokenIdstringThe asset — one side of one market
makerAmountstringWhat you give up
takerAmountstringWhat you want
sidestring"BUY" or "SELL"
expirationstringGTD unix seconds; "0" for GTC and FOK
noncestringMust be "0" — there is no on-chain nonce account
feeRateBpsstringMust cover the taker fee if the order is marketable
signatureTypeu320 = EOA. 1/2 (legacy wallet types) are rejected
signaturestring64-byte ed25519, hex or base58
sideYou giveYou getPrice
BUYcollateral (USDC)outcome tokensmakerAmount / takerAmount
SELLoutcome tokenscollateraltakerAmount / makerAmount

Buy 100 shares at $0.50 → makerAmount: "50000000", takerAmount: "100000000". Sell 100 shares at $0.60 → makerAmount: "100000000", takerAmount: "60000000".

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=0

The signature is not part of the hash.

Terminal window
cat order.json | cargo run -p sign-order --quiet -- --wallet alice

This 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.

Terminal window
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.

TypeexpirationBehaviour
GTC"0"Rests until filled or cancelled
GTDfuture unix secondsRests until it expires, then is ejected
FOK"0"Fills completely on arrival or is killed — never rests

success: true means accepted, not filled. Read status:

statusMeaning
liveResting on the book
matchedCrossed and executed; see transactionsHashes
delayedParked by the pending service (game-start window)
unmatchedCould not fill — typically a killed FOK

A rejection is success: false with a human-readable errorMsg and an empty orderID.

In order, from manager/insert_order.rs:

  1. Resolve the token ID.
  2. Find the order book for that token.
  3. Build the ProcessedOrder — price/size math and the order hash.
  4. Validate on insert: signature, expiration, fee rate, tick size, and on-chain status / nonce / balance over JSON-RPC.
  5. Duplicate check in the books.
  6. Market readiness.
  7. Rounding config lookup by tick size.
  8. Amount and tick-size validation.
  9. Unique-order registry check.
  10. Balance and allowance via the risk service — reserves the maker amount.
  11. Fee-rate validation (marketable orders pay the taker fee).
  12. GTD orders registered on the expiry pending service.
  13. Match, insert, or schedule.
ReasonFix
Signature invalidThe hash layout must match byte for byte — check field order and LE encoding
signatureType not 0Only EOA orders settle on-chain
signer != makerThey must be the same key
taker not zero addressBilateral orders aren’t supported
nonce != "0"There is no on-chain nonce account
Price off the tick gridRound makerAmount/takerAmount so the ratio lands on a tick
Fee rate too lowA marketable order must carry at least the taker fee
Insufficient balanceFund the ATA; remember earlier orders hold reservations
No book for that tokenThe market isn’t in the catalogue or isn’t ready
Duplicate orderChange the salt

A missing delegate allowance is a warning at insert, not a rejection — but settlement will fail on-chain. Set it first:

Terminal window
cargo run -p delegate --quiet -- set --keypair ~/.config/solana/id.json --amount 1000000000
  • 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 delayed status 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.

→ Manage Orders · Match Types