All articles
Polymarket·May 11, 2026·5 min read

Building a Polymarket Arbitrage Bot: CLOB API and Signed Orders

Build a Polymarket arbitrage bot: CLOB REST/WebSocket API, EIP-712 signed orders, and cross-market spread detection explained with code.

Why arbitrage windows even exist here

Polymarket's Central Limit Order Book (CLOB) is not an AMM. There's no bonding curve smoothing out every trade, no x*y=k pool guaranteeing a price for any size. It's a standard order book — bids and asks, price-time priority, matched by an off-chain operator and settled on-chain via signed orders. That structure is exactly why comparing CLOB and AMM prediction markets matters before you write a line of bot code: an AMM guarantees continuous pricing but bleeds you to impermanent-loss-style slippage, while a CLOB gives you real depth and real gaps. Gaps are what you're hunting.

Two flavors of gap show up constantly on Polymarket:

  • Complementary-outcome mispricing. For a binary market, YES + NO should sum to ~$1.00 (minus fees). When it drifts to $0.985 or $1.015, you can buy or sell both legs and lock a spread regardless of resolution.
  • Cross-market correlation mispricing. Two markets that are logically linked — "Will X win the primary" and "Will X win the general" — sometimes price the linked outcome inconsistently. This needs a model of the relationship, not just order book math.

The first is mechanical and low-risk. The second is where real edge lives, and where most of the false starts happen because people underestimate resolution risk — a topic worth understanding cold before you size a position, since UMA's optimistic oracle governs how Polymarket markets actually resolve and disputed resolutions can strand capital in a "correct" arb for weeks.

The CLOB API surface you actually need

Polymarket exposes a REST API for market data and order submission, plus a WebSocket feed for live book updates. For arbitrage you care about three endpoints:

  1. GET /markets and GET /book?token_id=... — snapshot the order book for a specific outcome token.
  2. wss://ws-subscriptions-clob.polymarket.com/ws/market — subscribe to book and price_change events per token so you're not polling REST and eating latency.
  3. POST /order — submit a signed limit or market order once you've found your edge.

The WebSocket feed is non-negotiable for anything faster than eyeballing. REST polling at, say, 500ms intervals means you're trading on stale data against bots that are subscribed and reacting in single-digit milliseconds. Every market has a condition_id (the market) and separate token_ids for each outcome (YES token, NO token) — get this wrong and you'll query the wrong side of the book, which is a surprisingly common bug in first drafts.

A minimal book snapshot check for the complementary-pair strategy:

import requests

def get_best_ask(token_id):
    book = requests.get(
        "https://clob.polymarket.com/book",
        params={"token_id": token_id}
    ).json()
    asks = sorted(book["asks"], key=lambda x: float(x["price"]))
    return float(asks[0]["price"]), float(asks[0]["size"])

yes_ask, yes_size = get_best_ask(YES_TOKEN_ID)
no_ask, no_size = get_best_ask(NO_TOKEN_ID)

spread = 1.0 - (yes_ask + no_ask)
if spread > 0.015:  # covers ~1% fee buffer plus margin
    tradable = min(yes_size, no_size)
    print(f"Arb: buy both legs, locked edge {spread:.4f} on {tradable} shares")

That's the whole idea in eight lines. The hard part isn't the math — it's execution before the gap closes and the mechanics of getting a valid signed order onto the book.

EIP-712 signed orders, concretely

Polymarket doesn't take your order as a raw API call and trust it. Every order is a typed, off-chain-signed message following EIP-712 — the same standard behind permit signatures elsewhere in DeFi. You sign a structured Order object with your wallet's private key, and the CLOB operator verifies the signature matches the maker address before accepting it into the book. Settlement happens through Polymarket's CTFExchange contract on Polygon when your order matches.

The order struct includes fields like:

  • maker — your address
  • tokenId — which outcome token
  • makerAmount / takerAmount — defines your limit price implicitly
  • side — BUY or SELL
  • expiration — Unix timestamp, or 0 for good-til-cancelled
  • nonce, salt — replay protection

In practice you don't hand-roll the struct encoding. Use py-clob-client (Python) or the equivalent TypeScript client, which wraps eth_signTypedData_v4 for you:

from py_clob_client.client import ClobClient
from py_clob_client.clob_types import OrderArgs

client = ClobClient(host="https://clob.polymarket.com", key=PRIVATE_KEY, chain_id=137)
client.set_api_creds(client.create_or_derive_api_creds())

order_args = OrderArgs(
    price=yes_ask,
    size=tradable,
    side="BUY",
    token_id=YES_TOKEN_ID,
)
signed_order = client.create_order(order_args)
resp = client.post_order(signed_order)

The signature itself costs nothing — it's off-chain. Gas only gets spent when your matched trade settles on Polygon, which is why signed limit orders are cheap to place and cancel relative to an on-chain-first design. But note the API layer enforces its own rate limits (roughly 10 requests/second on standard tiers as of writing) and you need L1 (Ethereum) USDC bridged and approved for the exchange contract before your first order will fill — a setup step that trips up a lot of first deployments.

Detection logic that survives contact with real markets

A naive spread scanner that fires the moment spread > threshold will get you adversely selected constantly. Real books move between your snapshot and your fill. Three refinements matter:

  • Require depth, not just top-of-book price. A 2-cent gap on 10 shares of size isn't worth the two round-trip transactions. Weight your spread threshold against tradable size.
  • Account for taker fees on both legs. Polymarket's fee schedule varies by market; bake the actual fee into your spread math rather than a flat guess.
  • Race condition on double-legged fills. If you buy YES and the NO leg fills a beat later at a worse price because the book moved, you've turned a locked arb into a directional bet. Either submit both legs as close to simultaneously as your infra allows, or size down until the latency risk is acceptable.

This is also where cross-venue thinking pays off — running the same detection logic against Kalshi's CFTC-regulated order book alongside Polymarket surfaces a second class of arbitrage entirely, though the settlement and KYC mechanics differ enough that you shouldn't treat it as a drop-in extension of the same bot.

What actually breaks in production

Order book races, WebSocket reconnect storms during volatility spikes, and nonce management across concurrent orders are the three things that eat weekends. If you're running this as a strategy rather than a weekend project, it's worth building the execution engine with proper retry semantics, position reconciliation against on-chain settlement, and monitoring for stale book data — the difference between a profitable bot and one that silently bleeds capital during a bad fill cascade usually comes down to that plumbing, not the arbitrage math itself.

We build and audit exactly this kind of infrastructure at our Polymarket arbitrage bot service if you'd rather skip the debugging cycle and start with something already battle-tested.

Need a bot like this built?

We design, build and run trading bots on Solana, Hyperliquid and Polymarket.

Start a project
#Polymarket#arbitrage bot#CLOB API#EIP-712#trading bot development