> Polymarket CLOB Deep Dive
Polymarket CLOB Deep Dive
Deep technical reference for Polymarket's Central Limit Order Book β the unified order book model, token structure (neg-risk vs standard), API endpoints, order lifecycle, py-clob-client SDK usage, and resolution / redemption mechanics.
Unified order book
Polymarket uses a unified book β there are no separate order books for YES and NO. The core principle:
BUY YES at X = SELL NO at (1 β X)
When two complementary BUY orders arrive (e.g., BUY YES at $0.60 and BUY NO at $0.40), the matching engine recognizes they sum to $1.00 and executes both by splitting $1.00 USDC.e from the collateral pool into 1 YES token + 1 NO token. Tokens are created on-demand through this split, not pre-minted.
The reverse (two SELL orders at complementary prices) merges tokens back into $1.00 USDC.e. This means every BUY YES order simultaneously appears as a SELL NO on the other side, effectively doubling liquidity.
Token structure
Standard (non-neg-risk) markets
A single market with two outcomes. Example: "Purdue vs Nebraska"
outcomes = ["Purdue Boilermakers", "Nebraska Cornhuskers"]clobTokenIds = [purdue_token, nebraska_token]- One order book where buying Purdue = selling Nebraska
- Token prices are complementary: if Purdue is at $0.90, Nebraska is at $0.10
Neg-risk markets
Multiple separate markets under one event, each with YES/NO outcomes. Example: "La Liga: Real Madrid vs Elche"
- Market 1: "Will Real Madrid win?" β
clobTokenIds = [YES, NO] - Market 2: "Will Elche win?" β same structure
- Market 3: "Will it draw?" β same structure
Each market has its own independent order book. The Neg Risk Adapter contract converts between them: holding 1 NO token in any market can be converted into 1 YES token in every other market.
clobTokenIds semantics
| Market type | clobTokenIds[0] | clobTokenIds[1] |
|-------------|-------------------|-------------------|
| Neg-risk | YES token | NO token |
| Non-neg-risk 2-way | First team's win token | Second team's win token |
Bot bug I hit: Buying
clobTokenIds[1]as "NO" is correct for neg-risk but wrong for non-neg-risk β it buys the second team's token instead. Must checkevent.negRiskand handle accordingly.
Identifying market type
event.negRisk = truein Gamma API- All markets have
outcomes = ["Yes", "No"]β neg-risk - Two named outcomes β non-neg-risk
CLOB API endpoints
GET /midpoint
Returns (best_bid + best_ask) / 2. No auth required.
GET https://clob.polymarket.com/midpoint?token_id=TOKEN_ID
β {"mid": "0.45"}
If spread > $0.10, the Polymarket UI shows last-trade price instead. Returns 404 if the orderbook is closed.
GET /price
Returns top-of-book price for a specific side.
GET https://clob.polymarket.com/price?token_id=TOKEN_ID&side=BUY
β {"price": 0.45}
side=BUYβ best ask (what you'd pay)side=SELLβ best bid (what you'd receive)
GET /book
Full order book with all price levels.
GET https://clob.polymarket.com/book?token_id=TOKEN_ID
Returns bids (highest first), asks (lowest first), plus neg_risk, tick_size, last_trade_price, min_order_size.
Endpoint comparison
| Endpoint | Returns | Use case |
|----------|---------|----------|
| /midpoint | (best_bid + best_ask) / 2 | Quick probability estimate |
| /price?side=BUY | Best ask | Actual execution price for buying |
| /price?side=SELL | Best bid | Actual execution price for selling |
| /book | Full depth | Liquidity analysis |
Order lifecycle
Placement
- Create the order locally (sign with EIP-712).
- Post to the CLOB operator.
- Receive initial status:
live,matched,delayed, orunmatched.
Initial statuses
| Status | Meaning |
|--------|---------|
| live | Resting on the book |
| matched | Immediately matched |
| delayed | Sports markets β 3-second hold before matching |
| unmatched | Was marketable but delay expired without match |
Settlement
MATCHED β MINED β CONFIRMED (success), or β RETRYING β FAILED.
Order types
| Type | Behavior | |------|----------| | GTC | Good-til-cancelled, rests indefinitely | | GTD | Good-til-date, expires at timestamp | | FOK | Fill-or-kill, must fill entirely and immediately | | FAK | Fill-and-kill, fills what's available, cancels rest |
Sports 3-second delay
Sports markets have a mandatory secondsDelay (typically 3 s) on marketable orders. Prevents front-running during live events. Visible in Gamma market data as "secondsDelay": 3.
py-clob-client SDK
Order creation
One-step (simple):
from py_clob_client.clob_types import OrderArgs, PartialCreateOrderOptions
from py_clob_client.order_builder.constants import BUY, SELL
resp = client.create_and_post_order(
OrderArgs(token_id="...", price=0.50, size=10, side=BUY),
options=PartialCreateOrderOptions(tick_size="0.01", neg_risk=True),
)
Two-step (more control):
signed = client.create_order(order_args, options)
resp = client.post_order(signed, OrderType.GTC)
Critical: the
neg_riskparameter affects how the order is signed β different exchange contracts for neg-risk vs standard. Wrong value β "not enough balance / allowance" error. Default isTrue(neg-risk), which fails silently for non-neg-risk markets.
Balance and allowance
from py_clob_client.clob_types import BalanceAllowanceParams, AssetType
# Sync for conditional tokens
client.update_balance_allowance(
BalanceAllowanceParams(asset_type=AssetType.CONDITIONAL, token_id="...")
)
# Sync for USDC collateral
client.update_balance_allowance(
BalanceAllowanceParams(asset_type=AssetType.COLLATERAL)
)
Key methods
| Method | Purpose |
|--------|---------|
| get_midpoint(token_id) | Midpoint price |
| get_price(token_id, side) | Best bid / ask |
| get_order_book(token_id) | Full depth |
| get_neg_risk(token_id) | Check market type |
| get_tick_size(token_id) | Required tick size |
| create_and_post_order(args, options) | Create + sign + post |
| cancel(order_id) | Cancel by ID |
| get_order(order_id) | Check status |
| update_balance_allowance(params) | Sync allowance with CLOB |
Resolution and redemption
Resolution flow
- Market end-condition met.
- Anyone proposes resolution with a $750 USDC.e bond.
- 2-hour challenge window β if undisputed, accepted.
- If disputed: second proposal + another challenge window (4β5 days).
- If disputed twice: UMA token-holder vote (5β6 days).
Payout vector: YES wins = [1, 0], NO wins = [0, 1].
Redemption
The Polymarket UI auto-redeems winning positions, but programmatic wallets must redeem manually.
On-chain redemption:
ctf.redeemPositions(
collateralToken=USDC_E, # 0x2791Bca1f2de4661ED88A30C99A7a9449Aa84174
parentCollectionId=bytes32(0),
conditionId=CONDITION_ID,
indexSets=[1, 2],
)
Burns the entire token balance for that condition. Only works after resolution.
Neg-risk redemption gap: for neg-risk markets, standard CTF redemption doesn't release USDC directly. The Neg Risk Adapter wraps tokens differently. Workaround: place exit SELL orders at $0.999 before resolution.
py-clob-clienthas no redeem method (see Issue #139).
Contract addresses (Polygon)
| Contract | Address |
|----------|---------|
| USDC.e | 0x2791Bca1f2de4661ED88A30C99A7a9449Aa84174 |
| CTF (ERC1155) | 0x4D97DCd97eC945f40cF65F87097ACe5EA0476045 |
| CTF Exchange | 0x4bFb41d5B3570DeFd03C39a9A4D8dE6Bd8B8982E |
| NegRisk CTF Exchange | 0xC5d563A36AE78145C45a50134d48A1215220f80a |
| NegRisk Adapter | 0xd91E80cF2E7be2e162c6513ceD06f1dD0dA35296 |
Required: setApprovalForAll on CTF contract for all three operator addresses, then sync via update_balance_allowance().
Open questions
- Auto-redemption for programmatic wallets β likely only works through UI / proxy wallet, not raw EOAs.
- Native USDC migration β Circle replacing USDC.e with native USDC may change collateral address.
- Exit orders as redemption workaround β SELL at $0.999 before resolution fails with allowance errors. Need
update_balance_allowanceper token right after BUY. /bookendpoint staleness β known stale-data bug reported Feb 2026.
Connection points
- This is the protocol-level reference behind polycli β most of the CLI's surface area (
poly markets,poly orderbook,poly address) maps directly to the endpoints documented here. - Pairs with LS-LMSR Deep Dive β that one covers the AMM math (Hanson's LMSR + Othman/Sandholm's liquidity-sensitive extension) used by older prediction-market venues; this one covers the order-book model Polymarket actually uses today.