Polymarket adapter

CLOB prediction markets on Polygon. EIP-712 signed orders with proxy-wallet support.

horizon.venues.polymarket.Polymarket wraps the official py_clob_client. Matching runs off-chain; settlement is on-chain ERC-1155 conditional tokens on Polygon.

Tier 1 to 3 code is unchanged. Only construct Polymarket(...) when you have an account funded with USDC on Polygon.

What you get

  • EIP-712 signing. The SDK signs each order with an EOA private key. First-run API credentials derive from the signer via create_or_derive_api_creds().
  • Three signature types. 0 raw EOA (most programmatic users), 1 email / magic-link proxy, 2 browser-wallet proxy. For proxy setups, set funder=<proxy_address>; for EOA, funder defaults to the signer’s address.
  • Token-id market model. Each binary market has a YES token_id and a NO token_id. Horizon uses the token id directly as market_id. To buy NO, target the NO token id.
  • Limit-only CLOB. No market orders native; Polymarket’s create_market_order is an aggressive IoC limit wrapped by the SDK. Horizon routes order_type="market" through that path.
  • Status map. LIVE -> Accepted, MATCHED -> Filled, CANCELLED -> Canceled, EXPIRED -> Expired. PartiallyFilled derived from size_matched vs. original_size.
  • USDC 6-decimal. Balance normalizes to dollars.

Quickstart

python
from horizon.venues.polymarket import Polymarket

pm = Polymarket(
    private_key="0x...",
    funder="0xabc...",                # EOA proxy or signer address
    signature_type=0,                 # 0 EOA, 1 email-magic, 2 browser
)
pm.connect()                          # probes via get_orders()
print(pm.balance())                   # USDC

Credentials resolve via Secrets: polymarket.private_key, polymarket.funder, and optionally polymarket.api_key, polymarket.api_secret, polymarket.api_passphrase (when you already derived CLOB creds and want to reuse them).

Market ids

market_id is always the token_id (hex string). Each question has:

  • condition_id: identifies the binary market.
  • token_id (YES): buy to go long YES shares.
  • token_id (NO): buy to go long NO shares.

Use list_markets() / market_info(condition_id) to map between them.

Order types

TypeOrderAction.order_typeNotes
Limit"limit"Requires price in 0.01..0.99.
Market"market"Routed through create_market_order. Aggressive limit under the hood.

TIF options accepted by the SDK: GTC, GTD, FOK, FAK. post_only and reduce_only pass through where the SDK supports them.

Amend

Polymarket does not expose in-place amend. Polymarket.amend(...) raises NotImplementedError. Cancel and resubmit.

Positions

Holdings are ERC-1155 balances on Polygon. The adapter does not currently query Polygon RPC for balances; positions() returns []. Use fill history + external Polygon indexing for an accurate snapshot, or extend the class with a web3 balance reader.

WebSocket feed

python
from horizon.data.live import PolymarketLiveFeed, SubscriptionKind

feed = PolymarketLiveFeed(fill_hook=pm.push_fill)
feed.subscribe(["<token_id>"], SubscriptionKind.OrderBook)

Channels: book, price_change, trade, order. The user channel authenticates with the same API credentials as REST; pass them via Secrets or explicit constructor args.

Fees

Polymarket fees are expressed as fee_rate_bps on fills. The adapter computes the actual fee cost as quantity * price * bps / 10000 in USDC for every fill.

Status by phase

CapabilityStatus
submit, cancel, cancel_all (per-market + global)Shipped
open_orders with PartiallyFilled derivationShipped
balance (USDC allowance check)Shipped
drain_fills via get_trades + push_fill WS injectionShipped
market_info, list_markets, orderbookShipped
positions (Polygon RPC read)Out of scope; add via web3.py subclass if needed.
amendNot supported; cancel + resubmit.

Regulatory notes

Polymarket is offshore from a US perspective. Operators outside the allowed jurisdictions should not use the production adapter. The adapter itself carries no jurisdiction logic; that lives in the firm’s compliance program.

Related