Trading
Placing orders, filling orders, and the on-chain order book mechanics.
Concepts
Outcome tokens: Every binary market has two tokens - YES (index 0) and NO (index 1). These are ERC-1155 tokens on the Gnosis CTF. If YES wins, each YES token is worth $1.00. NO tokens are worth nothing.
Prices: On the USDC scale. 500000 = $0.50 = 50% probability. Range: 1 to 999999.
Sides:
- BUY - you deposit USDC and want outcome tokens
- SELL - you deposit outcome tokens and want USDC
Matching: Orders don't auto-execute. They sit on-chain until someone (a keeper, a frontend, a bot) calls a fill function to match two compatible orders.
Placing orders
Buy order
"I want to buy YES tokens at $0.65 each, 100 shares."
// Approve USDC first (one-time)
usdc.approve(address(orderBook), type(uint256).max);
// Place the order - locks 65 USDC (0.65 * 100)
uint256 orderId = orderBook.placeBuyOrder(
conditionId, // which market
0, // outcomeIndex: 0 = YES, 1 = NO
650000, // price: $0.65
100e6 // amount: 100 shares
);Your USDC is locked in the OrderBook until the order is filled or you cancel.
Sell order
"I have 50 YES tokens and want to sell at $0.80 each."
// Approve OrderBook to transfer your outcome tokens (one-time)
ctf.setApprovalForAll(address(orderBook), true);
// Place the order - locks 50 YES tokens
uint256 orderId = orderBook.placeSellOrder(
conditionId, // which market
0, // outcomeIndex: 0 = YES
800000, // price: $0.80 minimum
50e6 // amount: 50 shares
);Batch orders
Place multiple buy orders in one transaction:
uint256[] memory orderIds = orderBook.placeBuyOrders(
conditionIds, // bytes32[]
outcomeIndexes, // uint256[]
prices, // uint256[]
amounts // uint256[]
);Filling orders
Anyone can match orders. This is how trading actually happens.
Buy vs Buy (opposite outcomes)
One person bets YES, another bets NO. Their combined USDC splits into outcome tokens.
// Order 0: BUY YES at $0.65, 100 shares
// Order 1: BUY NO at $0.40, 100 shares
// Combined: $0.65 + $0.40 = $1.05 >= $1.00 ✓ (prices cross)
orderBook.fillBuyVsBuy(orderId0, orderId1, 100e6);Rules:
- Both must be BUY orders
- Same market, opposite outcomes
- Prices must sum to >= $1.00 (
price0 + price1 >= 1e6) - If prices sum to > $1.00, surplus is refunded proportionally
Buy vs Sell (same outcome)
A buyer wants tokens, a seller has tokens.
// Buyer: BUY YES at $0.70
// Seller: SELL YES at $0.65
// Trade executes at seller's price ($0.65). Buyer gets a deal.
orderBook.fillBuyVsSell(buyOrderId, sellOrderId, 50e6);Rules:
- One BUY, one SELL
- Same market, same outcome
- Buyer's price >= seller's price
- Trade executes at seller's price
- Buyer's excess USDC is refunded
Cancelling orders
// Cancel your own order - get your USDC or tokens back
orderBook.cancelOrder(orderId);
// Cancel multiple orders
orderBook.cancelOrders(orderIds); // uint256[]
// After a market resolves, anyone can cancel stale orders
orderBook.cancelExpiredOrder(orderId);Cancellation refunds the remaining unfilled portion:
- BUY orders: refund USDC
- SELL orders: refund outcome tokens
Fees
0.1% on every fill. Deducted automatically, sent to Treasury.
Fee calculation rounds up: (amount * 10 + 9999) / 10000. No trade ever has a zero fee.
Minimum order
0.01 USDC (1e4). Orders where price * amount / 1e6 < 1e4 revert.
Minimum fill
Fills must be at least MIN_ORDER_COST (1e4) shares to prevent dust griefing. This is a share amount check, not a USDC cost check. Exception: the final fill that completes an order can be any size. This lets orders fill to exactly zero remaining without getting stuck on dust.
Order lifecycle
PLACED → PARTIALLY FILLED → FULLY FILLED
│
└──→ CANCELLED (refund remaining)Orders are never deleted from storage. Check filled == amount for fully filled, cancelled for cancelled.
Reading order data
(
address maker,
OrderBook.Side side,
bool cancelled,
bytes32 conditionId,
uint256 outcomeIndex,
uint256 price,
uint256 amount,
uint256 filled
) = orderBook.orders(orderId);
uint256 remaining = amount - filled;
bool isActive = !cancelled && remaining > 0;