# AGENTS.md — Building on the Polymarket API (CLOB V2 / pUSD, 2026) Rules for AI coding agents (Claude Code, Cursor, Copilot, etc.) writing code against Polymarket. Follow these so you produce **current, correct, safe** code on the first try. ## Use the V2 SDKs — the V1 line is dead - **Python:** `py-clob-client-v2` (import `py_clob_client_v2`). **Do NOT use `py-clob-client`** — it is the V1 line (latest 0.34.x), its repo is **archived and non-functional**. There is no "0.40" — if you see it, it's wrong. - **Node/TS:** `@polymarket/clob-client-v2`. Avoid the superseded V1 `@polymarket/clob-client`. - **Rust:** `polymarket_client_sdk_v2` (official, repo `Polymarket/rs-clob-client-v2`). - **New projects** may use the unified **beta** SDKs: `polymarket-client` (PyPI) / `@polymarket/client` (npm). - Install ONLY these exact package names. Do not install similarly-named packages you can't verify (slopsquatting risk). ## Canonical base URLs (don't invent endpoints) - CLOB (order book + trading): `https://clob.polymarket.com` - Gamma (market discovery): `https://gamma-api.polymarket.com` - Data (history/positions/activity): `https://data-api.polymarket.com` - WebSocket — CLOB: `wss://ws-subscriptions-clob.polymarket.com/ws/` (channels: `market` public, `user` authed, `sports`, `rfq`) - WebSocket — RTDS price feeds: `wss://ws-live-data.polymarket.com` (topics: `crypto_prices`, `crypto_prices_chainlink`, `equity_prices`, `comments`) - Specs for codegen: `docs.polymarket.com/api-spec/clob-openapi.yaml`, `/api-spec/data-openapi.yaml`, `/asyncapi.json`. If unsure of a path, fetch the spec or the docs — do not guess. ## The one correct way to place an order (Python V2) ```python from py_clob_client_v2 import (ClobClient, OrderArgs, OrderType, PartialCreateOrderOptions, Side, OrderPayload) creds = ClobClient(host="https://clob.polymarket.com", chain_id=137, key=os.environ["PK"]).create_or_derive_api_key() # derive once client = ClobClient(host="https://clob.polymarket.com", chain_id=137, key=os.environ["PK"], creds=creds) # creds in the constructor resp = client.create_and_post_order( # builds + signs + posts in ONE call order_args=OrderArgs(token_id=TOKEN_ID, price=0.45, side=Side.BUY, size=10), options=PartialCreateOrderOptions(tick_size=TICK, neg_risk=NEG_RISK), # read BOTH off the market order_type=OrderType.GTC, # GTC | GTD | FOK | FAK ) client.cancel_order(OrderPayload(orderID=resp["orderID"])) ``` - There is **no `set_api_creds`** in V2 — pass `creds=` to the constructor. The method is `create_or_derive_api_key()`. - `side` is the **`Side.BUY`/`Side.SELL` enum**, not the string `"BUY"`. - `tick_size` is per market (`"0.01"` or `"0.001"`) and `neg_risk` is per market — **read both off Gamma** (`orderPriceMinTickSize`, `negRisk`). Wrong tick or missing `neg_risk` → the order is rejected. - Minimum order size is **5 shares**; round price to the tick. Batch up to **15** orders via one `POST /orders`; `post_only=True` keeps an order strictly maker. ## Collateral is pUSD, not USDC.e - Settlement token is **pUSD** (ERC-20, 1:1 USDC): `0xC011a7E12a19f7B1f670d46F03B03f3342E82DFB`. - Before trading, approve **pUSD + conditional tokens** to all three V2 contracts: CTF Exchange `0xE111180000d2663C0091e4f400237545B87B996B`, Neg Risk CTF Exchange `0xe2222d279d744050d28e00520010520000310F59`, Neg Risk Adapter `0xd91E80cF2E7be2e162c6513ceD06f1dD0dA35296`. - API-only traders wrap USDC.e → pUSD via `CollateralOnramp.wrap()` (`0x93070a847efEf7F70739046A929D47a521F5B8ee`). ## Fees (only takers pay, and the rate curves) - **Makers pay 0** and earn a rebate; only **takers** pay. The fee is **not flat**: `fee = shares × feeRate × p × (1 - p)` — it **peaks at p = 0.50** and shrinks toward the 1¢/99¢ edges. - Per-category taker `feeRate`: Crypto **0.07**, Economics/Culture/Weather **0.05**, Finance/Politics/Tech **0.04**, Sports **0.03**, **Geopolitics & world events = 0 (fee-free)**. Read the live rate with `GET /fee-rate-bps?token_id=`. - Maker rebate ≈ **20% of taker fees in crypto, ~25% in other categories**, paid **daily in pUSD** (min $1 accrued). This is why edge-free bots quote (make) rather than cross (take). ## Negative-risk (multi-outcome) markets - Multi-candidate events are **neg-risk**: a No share in one outcome converts to a Yes share in every other (Neg Risk Adapter). - They run on **different contracts**, so you **must** pass `neg_risk=True`. Read `market.negRisk` off Gamma — never guess. ## Conditional Token Framework (CTF) - Outcome tokens are ERC-1155; every Yes/No pair is backed by exactly **$1.00 pUSD**. - `splitPosition` (pUSD → Yes+No), `mergePositions` (Yes+No → pUSD, no resolution wait), `redeemPositions` (winning token → $1.00 after resolution). CTF arb: Yes+No < $1 → buy both + merge; > $1 → split + sell both. - Read split/merge/redeem/reward history per wallet via Data `GET /activity?type=SPLIT,MERGE,REDEEM,REWARD`. ## Order lifecycle — "matched" is not "done" - Order states: `live` (resting), `matched`, `delayed` (some markets hold a marketable order ~**250 ms** taker delay), `unmatched`. - The trade then settles on-chain: `MATCHED → MINED → CONFIRMED` (terminal OK) or `RETRYING` / `FAILED` (terminal). **Wait for `CONFIRMED`** before updating state or placing a dependent order; handle `FAILED` explicitly. - On a WebSocket drop, **re-fetch the REST `/book` snapshot** before trusting deltas again. ## Auth - **Reading market data is keyless** (Gamma, Data, public CLOB). Do not add auth to public calls; do not skip it on private ones. - Trading: L1 EIP-712 (ClobAuth domain version `"1"`) to derive API creds, then L2 HMAC on every authenticated request (headers `POLY_ADDRESS`, `POLY_SIGNATURE`, `POLY_TIMESTAMP`, `POLY_API_KEY`, `POLY_PASSPHRASE`). - Signature types: 0 EOA, 1 POLY_PROXY (email/Magic), 2 POLY_GNOSIS_SAFE, 3 POLY_1271 (deposit wallet, recommended for new accounts). For types 1–3 pass both the signing key AND the `funder` address. - **Deposit wallets (type 3):** as of 2026-06 the Python/TS V2 clients have an open bug — use the **Rust** client for deposit-wallet trading. ## Gotchas (get these right) - Gamma returns `clobTokenIds`, `outcomes`, `outcomePrices` as **JSON-encoded strings** — `json.loads()` / `JSON.parse()` them. - **Never hardcode which token is Yes/No** — read the parsed `outcomes` array and map by name (the array is authoritative). - `conditionId` ≠ `token_id`: the CLOB trades on **token_id**. Mixing them is the most common first-bot bug. - Rate limits (throttled, not rejected): CLOB ~9,000/10s (`/book` 1,500, `/price` 500), order placement 5,000/10s burst + 120,000/10min; Gamma 4,000/10s (`/markets` 300); Data 1,000/10s (`/trades` 200). Send a WS `PING` ~every 10s. ## Hard rules — do NOT - Do **not** hardcode private keys, API secrets, or passphrases. Use environment variables; never commit, log, or echo them. - Do **not** place live orders in examples or tests. Default to **read-only / paper / dry-run**; live trading requires an explicit `--live` flag. - Do **not** trade on a failed or empty API response — **fail closed** (never assume a price). - Do **not** invent endpoints, fields, or SDK methods. If you can't verify it, fetch the docs/spec. ## Money rail (this is real money on Polygon) Generated trading code must default to paper mode, require an explicit opt-in for live, get a human review before the first live run, and enforce position and loss limits + a kill switch. ## Pointers - Docs zone: https://polymarkets.co.il/en/api/ - Recipes: https://polymarkets.co.il/en/api/polymarket-api-recipes/ · Errors: https://polymarkets.co.il/en/api/polymarket-api-errors/ - llms.txt: https://polymarkets.co.il/en/api/llms.txt · Official: https://docs.polymarket.com