add skills
This commit is contained in:
@@ -0,0 +1,145 @@
|
||||
# Polymarket Integration Skill
|
||||
|
||||
Agent skill for building on Polymarket — the world's largest prediction market. Gives agents the knowledge to authenticate, place orders, read markets, stream real-time data, manage positions, bridge assets across chains, and execute gasless transactions.
|
||||
|
||||
## What's Included
|
||||
|
||||
```
|
||||
web3-polymarket/
|
||||
├── SKILL.md # Entry point — quick reference, client setup, core patterns
|
||||
├── README.md # This file
|
||||
├── authentication.md # L1/L2 auth, builder headers, credential lifecycle
|
||||
├── order-patterns.md # Order types, tick sizes, cancel, heartbeat, errors
|
||||
├── market-data.md # Gamma API, Data API, CLOB orderbook, subgraph
|
||||
├── websocket.md # Market/user/sports channels, subscribe, heartbeat
|
||||
├── ctf-operations.md # Split, merge, redeem, negative risk, token IDs
|
||||
├── bridge.md # Deposits, withdrawals, supported chains/tokens
|
||||
└── gasless.md # Relayer client, wallet deployment, builder setup
|
||||
```
|
||||
|
||||
## How It Works
|
||||
|
||||
The skill uses **progressive disclosure** to stay efficient with context:
|
||||
|
||||
1. **SKILL.md loads first** — contains API endpoints, contract addresses, client setup, and core code patterns. Enough for most tasks.
|
||||
2. **Reference files load on demand** — when a task needs deeper detail (e.g., full error code list, bridge chain support, WebSocket event schemas), the agent reads the relevant file.
|
||||
|
||||
This keeps the initial context small (~200 lines) while giving access to ~1,700 lines of detailed reference material when needed.
|
||||
|
||||
## When Agents Use This Skill
|
||||
|
||||
An agent activates this skill when a user asks about:
|
||||
|
||||
- **Authentication** — API keys, EIP-712 signing, HMAC-SHA256, builder credentials
|
||||
- **Trading** — placing limit/market orders (GTC, GTD, FOK, FAK), batch orders, cancellation, heartbeat keepalive
|
||||
- **Market data** — fetching events/markets from Gamma API, reading orderbook prices/spreads/midpoints, price history
|
||||
- **Real-time data** — WebSocket subscriptions for orderbook updates, trade notifications, sports scores
|
||||
- **Token operations** — splitting USDC.e into Yes/No tokens, merging, redeeming after resolution
|
||||
- **Bridging** — depositing from 15+ chains, withdrawing, checking status
|
||||
- **Gasless transactions** — relayer client for gas-free onchain operations
|
||||
- **Negative risk** — multi-outcome markets, token conversion, augmented neg risk
|
||||
|
||||
## Quick Start for Humans
|
||||
|
||||
If you're a developer reading this directly (not an agent), here's the fastest path:
|
||||
|
||||
### 1. Install the SDK
|
||||
|
||||
```bash
|
||||
# TypeScript
|
||||
npm install @polymarket/clob-client ethers@5.8.0
|
||||
|
||||
# Python
|
||||
pip install py-clob-client
|
||||
```
|
||||
|
||||
### 2. Get API Credentials
|
||||
|
||||
```typescript
|
||||
import { ClobClient } from "@polymarket/clob-client";
|
||||
import { Wallet } from "ethers";
|
||||
|
||||
const client = new ClobClient(
|
||||
"https://clob.polymarket.com",
|
||||
137,
|
||||
new Wallet(process.env.PRIVATE_KEY)
|
||||
);
|
||||
const creds = await client.createOrDeriveApiKey();
|
||||
```
|
||||
|
||||
### 3. Place an Order
|
||||
|
||||
```typescript
|
||||
const tradingClient = new ClobClient(
|
||||
"https://clob.polymarket.com",
|
||||
137,
|
||||
signer,
|
||||
creds,
|
||||
2, // GNOSIS_SAFE (most common)
|
||||
"FUNDER_ADDR" // from polymarket.com/settings
|
||||
);
|
||||
|
||||
const response = await tradingClient.createAndPostOrder(
|
||||
{ tokenID: "TOKEN_ID", price: 0.50, size: 10, side: "BUY" },
|
||||
{ tickSize: "0.01", negRisk: false },
|
||||
"GTC"
|
||||
);
|
||||
```
|
||||
|
||||
## Key Concepts
|
||||
|
||||
| Concept | Description |
|
||||
|---------|-------------|
|
||||
| **USDC.e** | Bridged USDC on Polygon — the collateral token for all markets |
|
||||
| **Condition ID** | Identifies a market (used in API as `market` or `conditionID`) |
|
||||
| **Token ID** | Identifies a specific outcome token (Yes or No) within a market |
|
||||
| **Funder** | The proxy wallet address that holds funds — find at polymarket.com/settings |
|
||||
| **Signature Type** | `0` = EOA, `1` = POLY_PROXY (Magic Link), `2` = GNOSIS_SAFE (most common) |
|
||||
| **Neg Risk** | Multi-outcome markets where outcomes are linked — set `negRisk: true` in order options |
|
||||
| **Tick Size** | Minimum price increment for a market — must match or orders are rejected |
|
||||
|
||||
## API Endpoints
|
||||
|
||||
| API | Base URL | Auth Required |
|
||||
|-----|----------|---------------|
|
||||
| CLOB | `https://clob.polymarket.com` | L2 headers for trades, none for reads |
|
||||
| Gamma | `https://gamma-api.polymarket.com` | None |
|
||||
| Data | `https://data-api.polymarket.com` | None |
|
||||
| Bridge | `https://bridge.polymarket.com` | None |
|
||||
| Relayer | `https://relayer-v2.polymarket.com/` | Builder headers |
|
||||
| WS Market | `wss://ws-subscriptions-clob.polymarket.com/ws/market` | None |
|
||||
| WS User | `wss://ws-subscriptions-clob.polymarket.com/ws/user` | API creds in message |
|
||||
| WS Sports | `wss://sports-api.polymarket.com/ws` | None |
|
||||
|
||||
## Contract Addresses (Polygon)
|
||||
|
||||
| Contract | Address |
|
||||
|----------|---------|
|
||||
| USDC.e (Bridged USDC) | `0x2791Bca1f2de4661ED88A30C99A7a9449Aa84174` |
|
||||
| CTF | `0x4D97DCd97eC945f40cF65F87097ACe5EA0476045` |
|
||||
| CTF Exchange | `0x4bFb41d5B3570DeFd03C39a9A4D8dE6Bd8B8982E` |
|
||||
| Neg Risk CTF Exchange | `0xC5d563A36AE78145C45a50134d48A1215220f80a` |
|
||||
| Neg Risk Adapter | `0xd91E80cF2E7be2e162c6513ceD06f1dD0dA35296` |
|
||||
|
||||
## File Guide
|
||||
|
||||
| File | Read when you need to... |
|
||||
|------|--------------------------|
|
||||
| [SKILL.md](SKILL.md) | Get started — has everything for basic integration |
|
||||
| [authentication.md](authentication.md) | Understand L1/L2 auth flow, builder headers, or troubleshoot credential issues |
|
||||
| [order-patterns.md](order-patterns.md) | Use advanced order types (GTD, post-only, batch), handle errors, or implement heartbeat |
|
||||
| [market-data.md](market-data.md) | Query markets by slug/tag, paginate results, use subgraph, or estimate fill prices |
|
||||
| [websocket.md](websocket.md) | Stream real-time orderbook updates, trade notifications, or sports scores |
|
||||
| [ctf-operations.md](ctf-operations.md) | Split/merge/redeem tokens, work with neg risk markets, or compute token IDs |
|
||||
| [bridge.md](bridge.md) | Deposit from other chains, withdraw, check supported assets, or track transaction status |
|
||||
| [gasless.md](gasless.md) | Set up gas-free transactions via the relayer, deploy wallets, or configure builder credentials |
|
||||
|
||||
## SDKs
|
||||
|
||||
- **TypeScript**: [@polymarket/clob-client](https://github.com/Polymarket/clob-client)
|
||||
- **Python**: [py-clob-client](https://github.com/Polymarket/py-clob-client)
|
||||
- **Rust**: [rs-clob-client](https://github.com/Polymarket/rs-clob-client)
|
||||
- **Builder Relayer (TS)**: [@polymarket/builder-relayer-client](https://github.com/Polymarket/builder-relayer-client)
|
||||
- **Builder Relayer (Python)**: [py-builder-relayer-client](https://github.com/Polymarket/py-builder-relayer-client)
|
||||
- **Builder Signing (TS)**: [@polymarket/builder-signing-sdk](https://github.com/Polymarket/builder-signing-sdk)
|
||||
- **Builder Signing (Python)**: [py-builder-signing-sdk](https://github.com/Polymarket/py-builder-signing-sdk)
|
||||
@@ -0,0 +1,204 @@
|
||||
---
|
||||
name: web3-polymarket
|
||||
description: Polymarket integration for prediction market trading on Polygon. Covers authentication (L1 EIP-712, L2 HMAC-SHA256, builder headers), order placement (GTC/GTD/FOK/FAK, batch, post-only, heartbeat), market data (Gamma API, Data API, orderbook, subgraph), WebSocket streaming (market/user/sports channels), CTF operations (split, merge, redeem, negative risk), bridge (deposits, withdrawals, multi-chain), and gasless relayer transactions. Use when building AI agents, autonomous market makers, prediction market UIs, or any application integrating with Polymarket on Polygon.
|
||||
compatibility: Requires network access to Polymarket APIs (clob.polymarket.com, gamma-api.polymarket.com) and Polygon RPC
|
||||
---
|
||||
|
||||
# Polymarket Skill
|
||||
|
||||
## When to use this skill
|
||||
|
||||
Use this skill when the user asks about or needs to build:
|
||||
- Polymarket API authentication (L1/L2, API keys, HMAC signing)
|
||||
- Placing or managing orders (limit, market, GTC, GTD, FOK, FAK, batch, cancel)
|
||||
- Reading orderbook data (prices, spreads, midpoints, depth)
|
||||
- Market data fetching (events, markets, by slug, by tag, pagination)
|
||||
- WebSocket subscriptions (market channel, user channel, sports)
|
||||
- CTF operations (split, merge, redeem positions)
|
||||
- Negative risk markets (multi-outcome, conversion, augmented neg risk)
|
||||
- Bridge operations (deposits, withdrawals, multi-chain)
|
||||
- Gasless transactions (relayer client, order attribution)
|
||||
- Builder program integration (order attribution, API keys, tiers)
|
||||
- Polymarket SDK usage (TypeScript @polymarket/clob-client, Python py-clob-client)
|
||||
|
||||
## API Configuration
|
||||
|
||||
| API | Base URL | Auth | Purpose |
|
||||
|-----|----------|------|---------|
|
||||
| CLOB | `https://clob.polymarket.com` | L2 for trade endpoints | Orderbook, prices, order submission |
|
||||
| Gamma / Data | `https://gamma-api.polymarket.com` | None | Events, markets, search |
|
||||
| Data API | `https://data-api.polymarket.com` | None | Trades, positions, user data |
|
||||
| WebSocket (Market) | `wss://ws-subscriptions-clob.polymarket.com/ws/market` | None | Real-time orderbook |
|
||||
| WebSocket (User) | `wss://ws-subscriptions-clob.polymarket.com/ws/user` | API creds in message | Trade/order updates |
|
||||
| WebSocket (Sports) | `wss://sports-api.polymarket.com/ws` | None | Live scores |
|
||||
| Relayer | `https://relayer-v2.polymarket.com/` | Builder headers | Gasless transactions |
|
||||
| Bridge | `https://bridge.polymarket.com` | None | Deposits/withdrawals |
|
||||
|
||||
## Contract Addresses (Polygon)
|
||||
|
||||
| Contract | Address |
|
||||
|----------|---------|
|
||||
| USDC (USDC.e) | `0x2791Bca1f2de4661ED88A30C99A7a9449Aa84174` |
|
||||
| CTF (Conditional Tokens) | `0x4D97DCd97eC945f40cF65F87097ACe5EA0476045` |
|
||||
| CTF Exchange | `0x4bFb41d5B3570DeFd03C39a9A4D8dE6Bd8B8982E` |
|
||||
| Neg Risk CTF Exchange | `0xC5d563A36AE78145C45a50134d48A1215220f80a` |
|
||||
| Neg Risk Adapter | `0xd91E80cF2E7be2e162c6513ceD06f1dD0dA35296` |
|
||||
|
||||
## Client Setup
|
||||
|
||||
### TypeScript
|
||||
```typescript
|
||||
import { ClobClient, Side, OrderType } from "@polymarket/clob-client";
|
||||
import { Wallet } from "ethers"; // v5.8.0
|
||||
|
||||
const HOST = "https://clob.polymarket.com";
|
||||
const CHAIN_ID = 137;
|
||||
const signer = new Wallet(process.env.PRIVATE_KEY);
|
||||
|
||||
// Step 1: L1 — derive API credentials
|
||||
const tempClient = new ClobClient(HOST, CHAIN_ID, signer);
|
||||
const apiCreds = await tempClient.createOrDeriveApiKey();
|
||||
|
||||
// Step 2: L2 — init trading client
|
||||
const client = new ClobClient(
|
||||
HOST,
|
||||
CHAIN_ID,
|
||||
signer,
|
||||
apiCreds,
|
||||
2, // signatureType: 0=EOA, 1=POLY_PROXY, 2=GNOSIS_SAFE
|
||||
"FUNDER_ADDRESS" // proxy wallet address from polymarket.com/settings
|
||||
);
|
||||
```
|
||||
|
||||
### Python
|
||||
```python
|
||||
from py_clob_client.client import ClobClient
|
||||
import os
|
||||
|
||||
host = "https://clob.polymarket.com"
|
||||
chain_id = 137
|
||||
pk = os.getenv("PRIVATE_KEY")
|
||||
|
||||
# Step 1: L1 — derive API credentials
|
||||
temp_client = ClobClient(host, key=pk, chain_id=chain_id)
|
||||
api_creds = temp_client.create_or_derive_api_creds()
|
||||
|
||||
# Step 2: L2 — init trading client
|
||||
client = ClobClient(
|
||||
host,
|
||||
key=pk,
|
||||
chain_id=chain_id,
|
||||
creds=api_creds,
|
||||
signature_type=2, # 0=EOA, 1=POLY_PROXY, 2=GNOSIS_SAFE
|
||||
funder="FUNDER_ADDRESS",
|
||||
)
|
||||
```
|
||||
|
||||
## Quick Reference: Order Types
|
||||
|
||||
| Type | Behavior | Use Case |
|
||||
|------|----------|----------|
|
||||
| **GTC** | Rests on book until filled or cancelled | Default limit orders |
|
||||
| **GTD** | Active until expiration (UTC seconds). Min = `now + 60 + N` | Auto-expire before events |
|
||||
| **FOK** | Fill entirely immediately or cancel | All-or-nothing market orders |
|
||||
| **FAK** | Fill what's available, cancel rest | Partial-fill market orders |
|
||||
|
||||
- FOK/FAK BUY: `amount` = dollar amount to spend
|
||||
- FOK/FAK SELL: `amount` = number of shares to sell
|
||||
- Post-only: GTC/GTD only — rejected if would cross spread
|
||||
|
||||
## Quick Reference: Signature Types
|
||||
|
||||
| Type | Value | Description |
|
||||
|------|-------|-------------|
|
||||
| EOA | `0` | Standard Ethereum wallet (MetaMask). Funder is the EOA address and will need POL for gas. |
|
||||
| POLY_PROXY | `1` | Custom proxy wallet for Magic Link email/Google users who exported PK from Polymarket.com. |
|
||||
| GNOSIS_SAFE | `2` | Gnosis Safe multisig proxy wallet (most common). Use for any new or returning user. |
|
||||
|
||||
## Core Pattern: Place an Order
|
||||
|
||||
### TypeScript
|
||||
```typescript
|
||||
const response = await client.createAndPostOrder(
|
||||
{
|
||||
tokenID: "TOKEN_ID",
|
||||
price: 0.50,
|
||||
size: 10,
|
||||
side: Side.BUY,
|
||||
},
|
||||
{
|
||||
tickSize: "0.01", // from client.getTickSize(tokenID) or market object
|
||||
negRisk: false, // from client.getNegRisk(tokenID) or market object
|
||||
},
|
||||
OrderType.GTC
|
||||
);
|
||||
console.log(response.orderID, response.status);
|
||||
```
|
||||
|
||||
### Python
|
||||
```python
|
||||
from py_clob_client.clob_types import OrderArgs, OrderType
|
||||
from py_clob_client.order_builder.constants import BUY
|
||||
|
||||
response = client.create_and_post_order(
|
||||
OrderArgs(token_id="TOKEN_ID", price=0.50, size=10, side=BUY),
|
||||
options={"tick_size": "0.01", "neg_risk": False},
|
||||
order_type=OrderType.GTC,
|
||||
)
|
||||
print(response["orderID"], response["status"])
|
||||
```
|
||||
|
||||
## Core Pattern: Read Orderbook
|
||||
|
||||
### TypeScript
|
||||
```typescript
|
||||
// No auth needed
|
||||
const readClient = new ClobClient("https://clob.polymarket.com", 137);
|
||||
const book = await readClient.getOrderBook("TOKEN_ID");
|
||||
console.log("Best bid:", book.bids[0], "Best ask:", book.asks[0]);
|
||||
|
||||
const mid = await readClient.getMidpoint("TOKEN_ID");
|
||||
const spread = await readClient.getSpread("TOKEN_ID");
|
||||
```
|
||||
|
||||
### Python
|
||||
```python
|
||||
read_client = ClobClient("https://clob.polymarket.com", chain_id=137)
|
||||
book = read_client.get_order_book("TOKEN_ID")
|
||||
mid = read_client.get_midpoint("TOKEN_ID")
|
||||
spread = read_client.get_spread("TOKEN_ID")
|
||||
```
|
||||
|
||||
## Core Pattern: WebSocket Subscribe
|
||||
|
||||
```typescript
|
||||
const ws = new WebSocket("wss://ws-subscriptions-clob.polymarket.com/ws/market");
|
||||
|
||||
ws.onopen = () => {
|
||||
ws.send(JSON.stringify({
|
||||
type: "market",
|
||||
assets_ids: ["TOKEN_ID"],
|
||||
custom_feature_enabled: true,
|
||||
}));
|
||||
// Send PING every 10s to keep alive
|
||||
setInterval(() => ws.send("PING"), 10_000);
|
||||
};
|
||||
|
||||
ws.onmessage = (event) => {
|
||||
if (event.data === "PONG") return;
|
||||
const msg = JSON.parse(event.data);
|
||||
// msg.event_type: "book" | "price_change" | "last_trade_price" | "tick_size_change" | "best_bid_ask" | "new_market" | "market_resolved"
|
||||
};
|
||||
```
|
||||
|
||||
## Reference files (load on demand)
|
||||
|
||||
Only read these when the task requires deeper detail on a specific topic:
|
||||
|
||||
- **Authentication** (L1/L2, builder headers, credential lifecycle): [authentication.md](authentication.md)
|
||||
- **Order patterns** (GTC/GTD/FOK/FAK, tick sizes, cancel, heartbeat, errors): [order-patterns.md](order-patterns.md)
|
||||
- **Market data** (Gamma API, Data API, CLOB orderbook, subgraph): [market-data.md](market-data.md)
|
||||
- **WebSocket** (market/user/sports channels, subscribe, heartbeat): [websocket.md](websocket.md)
|
||||
- **CTF operations** (split, merge, redeem, neg risk, token IDs): [ctf-operations.md](ctf-operations.md)
|
||||
- **Bridge** (deposits, withdrawals, supported chains/tokens, status): [bridge.md](bridge.md)
|
||||
- **Gasless transactions** (relayer client, wallet deployment, builder setup): [gasless.md](gasless.md)
|
||||
@@ -0,0 +1,206 @@
|
||||
# Authentication
|
||||
|
||||
Polymarket uses two-level auth: **L1** (EIP-712 private key signing) to create credentials, **L2** (HMAC-SHA256 API key signing) to authenticate requests. Builder program adds a separate set of **builder headers** for order attribution and relayer access.
|
||||
|
||||
## L1 Authentication (Private Key)
|
||||
|
||||
L1 proves wallet ownership via EIP-712 signature. Used to create or derive API credentials.
|
||||
|
||||
### EIP-712 Domain
|
||||
|
||||
```typescript
|
||||
const domain = {
|
||||
name: "ClobAuthDomain",
|
||||
version: "1",
|
||||
chainId: 137,
|
||||
};
|
||||
|
||||
const types = {
|
||||
ClobAuth: [
|
||||
{ name: "address", type: "address" },
|
||||
{ name: "timestamp", type: "string" },
|
||||
{ name: "nonce", type: "uint256" },
|
||||
{ name: "message", type: "string" },
|
||||
],
|
||||
};
|
||||
|
||||
const value = {
|
||||
address: signingAddress, // The signing address
|
||||
timestamp: ts, // The CLOB API server timestamp
|
||||
nonce: nonce, // The nonce used
|
||||
message: "This message attests that I control the given wallet",
|
||||
};
|
||||
```
|
||||
|
||||
### L1 Headers
|
||||
|
||||
| Header | Description |
|
||||
|--------|-------------|
|
||||
| `POLY_ADDRESS` | Polygon signer address |
|
||||
| `POLY_SIGNATURE` | CLOB EIP-712 signature |
|
||||
| `POLY_TIMESTAMP` | Current UNIX timestamp |
|
||||
| `POLY_NONCE` | Nonce (default: 0) |
|
||||
|
||||
### Create / Derive Credentials
|
||||
|
||||
```typescript
|
||||
// TypeScript
|
||||
const client = new ClobClient("https://clob.polymarket.com", 137, signer);
|
||||
const creds = await client.createOrDeriveApiKey();
|
||||
// { apiKey: "uuid", secret: "base64...", passphrase: "string" }
|
||||
```
|
||||
|
||||
```python
|
||||
# Python
|
||||
client = ClobClient("https://clob.polymarket.com", key=pk, chain_id=137)
|
||||
creds = client.create_or_derive_api_creds()
|
||||
```
|
||||
|
||||
**REST endpoints:**
|
||||
- `POST {host}/auth/api-key` — create new credentials (requires L1 headers)
|
||||
- `GET {host}/auth/derive-api-key` — derive existing credentials (requires L1 headers)
|
||||
|
||||
## L2 Authentication (API Key)
|
||||
|
||||
L2 uses HMAC-SHA256 signatures from the API credentials. Required for all `/v1/trade/*` endpoints.
|
||||
|
||||
### L2 Headers (all 5 required)
|
||||
|
||||
| Header | Description |
|
||||
|--------|-------------|
|
||||
| `POLY_ADDRESS` | Polygon signer address |
|
||||
| `POLY_SIGNATURE` | HMAC signature for request |
|
||||
| `POLY_TIMESTAMP` | Current UNIX timestamp |
|
||||
| `POLY_API_KEY` | User's API `apiKey` value |
|
||||
| `POLY_PASSPHRASE` | User's API `passphrase` value |
|
||||
|
||||
### Initialize Trading Client
|
||||
|
||||
```typescript
|
||||
// TypeScript
|
||||
const client = new ClobClient(
|
||||
"https://clob.polymarket.com",
|
||||
137,
|
||||
signer,
|
||||
apiCreds, // { apiKey, secret, passphrase }
|
||||
2, // signatureType
|
||||
funderAddress // proxy wallet address
|
||||
);
|
||||
```
|
||||
|
||||
```python
|
||||
# Python
|
||||
client = ClobClient(
|
||||
host="https://clob.polymarket.com",
|
||||
chain_id=137,
|
||||
key=pk,
|
||||
creds=api_creds,
|
||||
signature_type=2,
|
||||
funder=funder_address,
|
||||
)
|
||||
```
|
||||
|
||||
## Signature Types
|
||||
|
||||
| Type | Value | When to Use |
|
||||
|------|-------|-------------|
|
||||
| EOA | `0` | Standard Ethereum wallet (MetaMask). Funder is the EOA address and will need POL to pay gas on transactions. |
|
||||
| POLY_PROXY | `1` | A custom proxy wallet only used with users who logged in via Magic Link email/Google. Using this requires the user to have exported their PK from Polymarket.com and imported into your app. |
|
||||
| GNOSIS_SAFE | `2` | Gnosis Safe multisig proxy wallet (most common). Use this for any new or returning user who does not fit the other 2 types. |
|
||||
|
||||
The **funder** is the address holding funds. For proxy wallets, find it at polymarket.com/settings. Proxy wallets are auto-deployed on first Polymarket.com login.
|
||||
|
||||
## Builder Headers
|
||||
|
||||
Builder authentication is separate from L1/L2. Used for order attribution and relayer access.
|
||||
|
||||
### Builder Headers (4 required)
|
||||
|
||||
| Header | Description |
|
||||
|--------|-------------|
|
||||
| `POLY_BUILDER_API_KEY` | Builder API key |
|
||||
| `POLY_BUILDER_TIMESTAMP` | Unix timestamp |
|
||||
| `POLY_BUILDER_PASSPHRASE` | Builder passphrase |
|
||||
| `POLY_BUILDER_SIGNATURE` | HMAC-SHA256 of request |
|
||||
|
||||
### Initialize Client with Builder Config
|
||||
|
||||
```typescript
|
||||
// TypeScript — local signing
|
||||
import { BuilderConfig, BuilderApiKeyCreds } from "@polymarket/builder-signing-sdk";
|
||||
|
||||
const builderCreds: BuilderApiKeyCreds = {
|
||||
key: process.env.POLY_BUILDER_API_KEY!,
|
||||
secret: process.env.POLY_BUILDER_SECRET!,
|
||||
passphrase: process.env.POLY_BUILDER_PASSPHRASE!,
|
||||
};
|
||||
|
||||
const builderConfig = new BuilderConfig({ localBuilderCreds: builderCreds });
|
||||
|
||||
const client = new ClobClient(
|
||||
"https://clob.polymarket.com",
|
||||
137,
|
||||
signer,
|
||||
apiCreds,
|
||||
2,
|
||||
funderAddress,
|
||||
undefined,
|
||||
false,
|
||||
builderConfig
|
||||
);
|
||||
// Orders automatically include builder headers
|
||||
```
|
||||
|
||||
```python
|
||||
# Python — local signing
|
||||
from py_builder_signing_sdk import BuilderConfig, BuilderApiKeyCreds
|
||||
|
||||
builder_config = BuilderConfig(
|
||||
local_builder_creds=BuilderApiKeyCreds(
|
||||
key=os.environ["POLY_BUILDER_API_KEY"],
|
||||
secret=os.environ["POLY_BUILDER_SECRET"],
|
||||
passphrase=os.environ["POLY_BUILDER_PASSPHRASE"],
|
||||
)
|
||||
)
|
||||
|
||||
client = ClobClient(
|
||||
host="https://clob.polymarket.com",
|
||||
chain_id=137,
|
||||
key=pk,
|
||||
creds=api_creds,
|
||||
signature_type=2,
|
||||
funder=funder_address,
|
||||
builder_config=builder_config,
|
||||
)
|
||||
```
|
||||
|
||||
### Remote Signing
|
||||
|
||||
Keep builder credentials on a separate server. Client points to your signing endpoint:
|
||||
|
||||
```typescript
|
||||
// TypeScript client
|
||||
const builderConfig = new BuilderConfig({
|
||||
remoteBuilderConfig: { url: "https://your-server.com/sign" },
|
||||
});
|
||||
```
|
||||
|
||||
```python
|
||||
# Python client
|
||||
from py_builder_signing_sdk import BuilderConfig, RemoteBuilderConfig
|
||||
|
||||
builder_config = BuilderConfig(
|
||||
remote_builder_config=RemoteBuilderConfig(url="https://your-server.com/sign")
|
||||
)
|
||||
```
|
||||
|
||||
Your server receives `{ method, path, body }` and returns the 4 `POLY_BUILDER_*` headers.
|
||||
|
||||
## Credential Lifecycle
|
||||
|
||||
- **Create**: `client.createApiKey()` — generates new credentials with a nonce
|
||||
- **Derive**: `client.deriveApiKey(nonce)` — recovers existing credentials if you know the nonce
|
||||
- **Create or Derive**: `client.createOrDeriveApiKey()` — creates if first time, derives if existing
|
||||
- **Revoke builder key**: `client.revokeBuilderApiKey()` — invalidate compromised builder credentials
|
||||
|
||||
Lost credentials + lost nonce = create fresh credentials. Save your nonce.
|
||||
@@ -0,0 +1,137 @@
|
||||
# Bridge
|
||||
|
||||
Polymarket uses **USDC.e** (Bridged USDC) on Polygon as collateral. The Bridge API handles deposits from and withdrawals to multiple chains.
|
||||
|
||||
Base URL: `https://bridge.polymarket.com`
|
||||
|
||||
## Deposit Flow
|
||||
|
||||
1. `POST /deposit` with your Polymarket wallet address → get deposit addresses
|
||||
2. Verify token is supported via `/supported-assets`
|
||||
3. Send assets to the appropriate address for your source chain
|
||||
4. Assets are bridged and auto-swapped to USDC.e on Polygon
|
||||
5. Track status via `/status/{deposit_address}`
|
||||
|
||||
```bash
|
||||
# Create deposit addresses
|
||||
curl -X POST https://bridge.polymarket.com/deposit \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"address": "0xYOUR_POLYMARKET_WALLET"}'
|
||||
```
|
||||
|
||||
Response includes three address types:
|
||||
|
||||
| Address | Use For |
|
||||
|---------|---------|
|
||||
| `evm` | Ethereum, Arbitrum, Base, Optimism, and other EVM chains |
|
||||
| `svm` | Solana |
|
||||
| `btc` | Bitcoin |
|
||||
| `tvm` | Tron |
|
||||
|
||||
Each address is unique to your wallet.
|
||||
|
||||
## Supported Chains
|
||||
|
||||
| Chain | Address Type | Min Deposit | Example Tokens |
|
||||
|-------|--------------|-------------|----------------|
|
||||
| Ethereum | EVM | $7 | ETH, USDC, USDT, WBTC, DAI, LINK, UNI, AAVE |
|
||||
| Polygon | EVM | $2 | POL, USDC, USDT, DAI, WETH, SAND |
|
||||
| Arbitrum | EVM | $2 | ETH, ARB, USDC, USDT, DAI, WBTC, USDe |
|
||||
| Base | EVM | $2 | ETH, USDC, USDT, DAI, cbBTC, AERO, USDS |
|
||||
| Optimism | EVM | $2 | ETH, OP, USDC, USDT, DAI, USDe |
|
||||
| BNB Smart Chain | EVM | $2 | BNB, USDC, USDT, DAI, ETH, BTCB, BUSD |
|
||||
| Solana | SVM | $2 | SOL, USDC, USDT, USDe, TRUMP |
|
||||
| Bitcoin | BTC | $9 | BTC |
|
||||
| Tron | TVM | $9 | USDT |
|
||||
| HyperEVM | EVM | $2 | HYPE, USDC, USDe, stHYPE, UBTC, UETH |
|
||||
| Abstract | EVM | $2 | ETH, USDC, USDT |
|
||||
| Monad | EVM | $2 | MON, USDC, USDT |
|
||||
| Ethereal | EVM | $2 | USDe, WUSDe |
|
||||
| Katana | EVM | $2 | AUSD |
|
||||
| Lighter | EVM | $2 | USDC |
|
||||
|
||||
Always call `/supported-assets` for the current list — assets change over time.
|
||||
|
||||
## Withdrawal Flow
|
||||
|
||||
1. Check destination chain/token via `/supported-assets`
|
||||
2. Preview fees via `POST /quote`
|
||||
3. `POST /withdraw` with wallet address, destination chain, token, and recipient → get deposit addresses
|
||||
4. Send USDC.e from Polymarket wallet to the appropriate address
|
||||
5. Track status via `/status/{address}`
|
||||
|
||||
```bash
|
||||
# Create withdrawal addresses
|
||||
curl -X POST https://bridge.polymarket.com/withdraw \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"address": "0xYOUR_POLYMARKET_WALLET",
|
||||
"toChainId": "1",
|
||||
"toTokenAddress": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
|
||||
"recipientAddr": "0xDESTINATION_ADDRESS"
|
||||
}'
|
||||
```
|
||||
|
||||
**Do not pre-generate withdrawal addresses.** Generate them only when ready to execute.
|
||||
|
||||
## Quote
|
||||
|
||||
Preview fees and estimated output for deposits and withdrawals. Withdrawals are **instant** and **free** — Polymarket does not charge withdrawal fees.
|
||||
|
||||
```bash
|
||||
POST https://bridge.polymarket.com/quote
|
||||
```
|
||||
|
||||
## Status Tracking
|
||||
|
||||
```bash
|
||||
# Use the deposit address (not your wallet address)
|
||||
curl https://bridge.polymarket.com/status/0xDEPOSIT_ADDRESS
|
||||
```
|
||||
|
||||
### Transaction Statuses
|
||||
|
||||
| Status | Terminal | Description |
|
||||
|--------|----------|-------------|
|
||||
| `DEPOSIT_DETECTED` | No | Funds detected on source chain, not yet processing |
|
||||
| `PROCESSING` | No | Being routed and swapped |
|
||||
| `ORIGIN_TX_CONFIRMED` | No | Source chain transaction confirmed |
|
||||
| `SUBMITTED` | No | Submitted to Polygon |
|
||||
| `COMPLETED` | Yes | Funds arrived — success |
|
||||
| `FAILED` | Yes | Error occurred |
|
||||
|
||||
Poll every 10–30 seconds until `COMPLETED` or `FAILED`.
|
||||
|
||||
### Response
|
||||
|
||||
```json
|
||||
{
|
||||
"transactions": [{
|
||||
"fromChainId": "1",
|
||||
"fromTokenAddress": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
|
||||
"fromAmountBaseUnit": "1000000000",
|
||||
"toChainId": "137",
|
||||
"toTokenAddress": "0x2791Bca1f2de4661ED88A30C99A7a9449Aa84174",
|
||||
"status": "COMPLETED",
|
||||
"txHash": "0x...",
|
||||
"createdTimeMs": 1697875200000
|
||||
}]
|
||||
}
|
||||
```
|
||||
|
||||
Empty `transactions` array = no deposits detected yet.
|
||||
|
||||
## Recovery
|
||||
|
||||
If you deposited the wrong token:
|
||||
- **Ethereum deposits**: https://recovery.polymarket.com/
|
||||
- **Polygon deposits**: https://matic-recovery.polymarket.com/
|
||||
|
||||
Sending unsupported tokens may cause **irrecoverable loss**.
|
||||
|
||||
## Caveats
|
||||
|
||||
- **Withdrawals >$50,000**: break into smaller amounts to minimize slippage
|
||||
- **Uniswap pool exhaustion**: USDC.e → USDC swap goes through Uniswap v3 pool. If pool is exhausted, use smaller amounts or wait for rebalance
|
||||
- **Deposits below minimum**: will not be processed
|
||||
- **Supported assets change**: always check `/supported-assets` before depositing
|
||||
@@ -0,0 +1,180 @@
|
||||
# CTF Operations
|
||||
|
||||
The **Conditional Token Framework (CTF)** creates ERC1155 tokens for market outcomes. Three core operations: split, merge, redeem.
|
||||
|
||||
## Token Model
|
||||
|
||||
Every binary market has two tokens:
|
||||
|
||||
| Token | Redeems for | Condition |
|
||||
|-------|-------------|-----------|
|
||||
| **Yes** | $1.00 USDC.e | Event occurs |
|
||||
| **No** | $1.00 USDC.e | Event does not occur |
|
||||
|
||||
Every Yes/No pair is backed by exactly $1.00 USDC.e locked in the CTF contract.
|
||||
|
||||
## Split
|
||||
|
||||
Convert USDC.e into a full set of outcome tokens.
|
||||
|
||||
```
|
||||
$100 USDC.e → 100 Yes tokens + 100 No tokens
|
||||
```
|
||||
|
||||
### Prerequisites
|
||||
1. USDC.e balance on Polygon
|
||||
2. USDC.e approval for CTF contract
|
||||
3. Condition ID of the market (the condition must already be prepared on the CTF contract via `prepareCondition`)
|
||||
|
||||
### Function: `splitPosition`
|
||||
|
||||
| Parameter | Type | Value |
|
||||
|-----------|------|-------|
|
||||
| `collateralToken` | IERC20 | `0x2791Bca1f2de4661ED88A30C99A7a9449Aa84174` (USDC.e) |
|
||||
| `parentCollectionId` | bytes32 | `0x0000...0000` (32 zero bytes) |
|
||||
| `conditionId` | bytes32 | Market's condition ID |
|
||||
| `partition` | uint[] | `[1, 2]` for binary (Yes=1, No=2) |
|
||||
| `amount` | uint256 | Amount of USDC.e to split |
|
||||
|
||||
## Merge
|
||||
|
||||
Convert a full set of outcome tokens back to USDC.e. Inverse of split.
|
||||
|
||||
```
|
||||
100 Yes tokens + 100 No tokens → $100 USDC.e
|
||||
```
|
||||
|
||||
### Prerequisites
|
||||
1. Equal amounts of both Yes and No tokens
|
||||
2. Condition ID (the condition must already be prepared on the CTF contract via `prepareCondition`)
|
||||
3. Sufficient gas for the transaction
|
||||
|
||||
### Function: `mergePositions`
|
||||
|
||||
Same parameters as split. Burns one unit of each position per unit of collateral returned.
|
||||
|
||||
## Redeem
|
||||
|
||||
Exchange winning tokens for USDC.e after market resolution.
|
||||
|
||||
```
|
||||
Market resolves YES:
|
||||
100 Yes tokens → $100 USDC.e
|
||||
100 No tokens → $0
|
||||
```
|
||||
|
||||
### Prerequisites
|
||||
1. Market must be resolved
|
||||
2. Hold winning tokens
|
||||
3. Know the condition ID
|
||||
|
||||
### Function: `redeemPositions`
|
||||
|
||||
| Parameter | Type | Value |
|
||||
|-----------|------|-------|
|
||||
| `collateralToken` | IERC20 | USDC.e address |
|
||||
| `parentCollectionId` | bytes32 | `0x0000...0000` |
|
||||
| `conditionId` | bytes32 | Market's condition ID |
|
||||
| `indexSets` | uint[] | `[1, 2]` — redeems both (only winner pays) |
|
||||
|
||||
Redemption burns your **entire** token balance for the condition — no amount parameter. No deadline — winning tokens are always redeemable.
|
||||
|
||||
### Payout Vectors
|
||||
|
||||
| Outcome | Payout Vector | Redemption |
|
||||
|---------|---------------|------------|
|
||||
| Yes wins | `[1, 0]` | Yes = $1, No = $0 |
|
||||
| No wins | `[0, 1]` | Yes = $0, No = $1 |
|
||||
|
||||
## Contract Addresses
|
||||
|
||||
| Contract | Address | Purpose |
|
||||
|----------|---------|---------|
|
||||
| CTF | `0x4D97DCd97eC945f40cF65F87097ACe5EA0476045` | Token storage and operations |
|
||||
| USDC.e (Bridged USDC) | `0x2791Bca1f2de4661ED88A30C99A7a9449Aa84174` | Collateral token |
|
||||
| CTF Exchange | `0x4bFb41d5B3570DeFd03C39a9A4D8dE6Bd8B8982E` | Standard market trading |
|
||||
| Neg Risk CTF Exchange | `0xC5d563A36AE78145C45a50134d48A1215220f80a` | Neg risk market trading |
|
||||
| Neg Risk Adapter | `0xd91E80cF2E7be2e162c6513ceD06f1dD0dA35296` | Neg risk conversions |
|
||||
|
||||
## Approval Matrix
|
||||
|
||||
Before trading or CTF operations, the funder must approve the relevant contracts:
|
||||
|
||||
| Operation | Contract to Approve | Token |
|
||||
|-----------|-------------------|-------|
|
||||
| Buy order (standard) | CTF Exchange | USDC.e |
|
||||
| Sell order (standard) | CTF Exchange | Conditional tokens |
|
||||
| Buy order (neg risk) | Neg Risk CTF Exchange | USDC.e |
|
||||
| Sell order (neg risk) | Neg Risk CTF Exchange | Conditional tokens |
|
||||
| Split | CTF | USDC.e |
|
||||
| Neg risk conversion | Neg Risk Adapter | Conditional tokens |
|
||||
|
||||
## Standard vs Neg Risk Markets
|
||||
|
||||
| Feature | Standard Markets | Neg Risk Markets |
|
||||
|---------|-----------------|------------------|
|
||||
| CTF Contract | ConditionalTokens | ConditionalTokens |
|
||||
| Exchange Contract | CTF Exchange | Neg Risk CTF Exchange |
|
||||
| Multi-outcome | Independent markets | Linked via conversion |
|
||||
| `negRisk` flag | `false` | `true` |
|
||||
| Order option | `negRisk: false` | `negRisk: true` |
|
||||
|
||||
## Negative Risk
|
||||
|
||||
Multi-outcome events where only one outcome can win. A No token in any market can be **converted** into 1 Yes token in every other market.
|
||||
|
||||
### Conversion Example
|
||||
|
||||
Event: "Who wins?" with outcomes Trump, Harris, Other.
|
||||
|
||||
| Outcome | Before | After Conversion |
|
||||
|---------|--------|------------------|
|
||||
| Trump | — | 1 Yes |
|
||||
| Harris | — | 1 Yes |
|
||||
| Other | 1 No | — |
|
||||
|
||||
Conversion is atomic through the Neg Risk Adapter contract.
|
||||
|
||||
### Identifying Neg Risk Markets
|
||||
|
||||
```json
|
||||
{
|
||||
"negRisk": true // on event or market object from API
|
||||
}
|
||||
```
|
||||
|
||||
When placing orders: pass `negRisk: true` in options.
|
||||
|
||||
## Augmented Negative Risk
|
||||
|
||||
For events where new outcomes emerge after trading begins (e.g., new candidate enters race).
|
||||
|
||||
| Outcome Type | Description |
|
||||
|--------------|-------------|
|
||||
| Named outcomes | Known outcomes (e.g., "Trump", "Harris") |
|
||||
| Placeholder outcomes | Reserved slots clarified later (e.g., "Person A") |
|
||||
| Explicit Other | Catches any unnamed outcome |
|
||||
|
||||
### Identifying
|
||||
|
||||
```json
|
||||
{
|
||||
"enableNegRisk": true,
|
||||
"negRiskAugmented": true
|
||||
}
|
||||
```
|
||||
|
||||
### Rules
|
||||
- Only trade on **named outcomes** — ignore placeholders
|
||||
- If correct outcome is not named at resolution, market resolves to "Other"
|
||||
- "Other" definition changes as placeholders are clarified — avoid trading it directly
|
||||
|
||||
## Token ID Computation
|
||||
|
||||
Token IDs are computed onchain in three steps:
|
||||
|
||||
1. `getConditionId(oracle, questionId, outcomeSlotCount)` — oracle = UMA CTF Adapter, outcomeSlotCount = 2 for binary
|
||||
2. `getCollectionId(parentCollectionId, conditionId, indexSet)` — parentCollectionId = bytes32(0), indexSet = 1 (Yes) or 2 (No)
|
||||
3. `getPositionId(collateralToken, collectionId)` — combines USDC.e contract address on Polygon with collection
|
||||
|
||||
In practice, get token IDs from the Markets API `tokens` array. Manual computation only needed for direct contract interaction.
|
||||
+236
@@ -0,0 +1,236 @@
|
||||
# Gasless Transactions
|
||||
|
||||
Polymarket's **Relayer Client** enables gasless transactions. Instead of requiring users to hold POL, Polymarket's infrastructure pays gas fees. Users only need USDC.e to trade.
|
||||
|
||||
Requires **Builder Program** membership. You need Builder API credentials.
|
||||
|
||||
## How It Works
|
||||
|
||||
1. Your app creates a transaction
|
||||
2. User signs it with their private key
|
||||
3. App sends to Polymarket's relayer
|
||||
4. Relayer submits onchain and pays gas
|
||||
5. Transaction executes from the user's wallet
|
||||
|
||||
## What's Covered
|
||||
|
||||
| Operation | Description |
|
||||
|-----------|-------------|
|
||||
| Wallet deployment | Deploy Safe or Proxy wallets for new users |
|
||||
| Token approvals | Approve contracts to spend USDC.e or outcome tokens |
|
||||
| CTF operations | Split, merge, redeem positions |
|
||||
| Transfers | Move tokens between addresses |
|
||||
|
||||
## Installation
|
||||
|
||||
```bash
|
||||
# TypeScript
|
||||
npm install @polymarket/builder-relayer-client @polymarket/builder-signing-sdk
|
||||
|
||||
# Python
|
||||
pip install py-builder-relayer-client py-builder-signing-sdk
|
||||
```
|
||||
|
||||
## Client Setup
|
||||
|
||||
### TypeScript (Local Signing)
|
||||
|
||||
```typescript
|
||||
import { createWalletClient, http, Hex } from "viem";
|
||||
import { privateKeyToAccount } from "viem/accounts";
|
||||
import { polygon } from "viem/chains";
|
||||
import { RelayClient } from "@polymarket/builder-relayer-client";
|
||||
import { BuilderConfig } from "@polymarket/builder-signing-sdk";
|
||||
|
||||
const account = privateKeyToAccount(process.env.PRIVATE_KEY as Hex);
|
||||
const wallet = createWalletClient({
|
||||
account,
|
||||
chain: polygon,
|
||||
transport: http(process.env.RPC_URL),
|
||||
});
|
||||
|
||||
const builderConfig = new BuilderConfig({
|
||||
localBuilderCreds: {
|
||||
key: process.env.POLY_BUILDER_API_KEY!,
|
||||
secret: process.env.POLY_BUILDER_SECRET!,
|
||||
passphrase: process.env.POLY_BUILDER_PASSPHRASE!,
|
||||
},
|
||||
});
|
||||
|
||||
const client = new RelayClient(
|
||||
"https://relayer-v2.polymarket.com/",
|
||||
137,
|
||||
wallet,
|
||||
builderConfig
|
||||
);
|
||||
```
|
||||
|
||||
### Python (Local Signing)
|
||||
|
||||
```python
|
||||
import os
|
||||
from py_builder_relayer_client.client import RelayClient
|
||||
from py_builder_signing_sdk import BuilderConfig, BuilderApiKeyCreds
|
||||
|
||||
builder_config = BuilderConfig(
|
||||
local_builder_creds=BuilderApiKeyCreds(
|
||||
key=os.getenv("POLY_BUILDER_API_KEY"),
|
||||
secret=os.getenv("POLY_BUILDER_SECRET"),
|
||||
passphrase=os.getenv("POLY_BUILDER_PASSPHRASE"),
|
||||
)
|
||||
)
|
||||
|
||||
client = RelayClient(
|
||||
"https://relayer-v2.polymarket.com",
|
||||
137,
|
||||
os.getenv("PRIVATE_KEY"),
|
||||
builder_config,
|
||||
)
|
||||
```
|
||||
|
||||
### Remote Signing
|
||||
|
||||
Keep credentials on your server. Client points to signing endpoint:
|
||||
|
||||
```typescript
|
||||
// TypeScript
|
||||
const builderConfig = new BuilderConfig({
|
||||
remoteBuilderConfig: { url: "https://your-server.com/sign" },
|
||||
});
|
||||
|
||||
const client = new RelayClient(
|
||||
"https://relayer-v2.polymarket.com/",
|
||||
137,
|
||||
wallet,
|
||||
builderConfig
|
||||
);
|
||||
```
|
||||
|
||||
```python
|
||||
# Python
|
||||
from py_builder_signing_sdk import BuilderConfig, RemoteBuilderConfig
|
||||
|
||||
builder_config = BuilderConfig(
|
||||
remote_builder_config=RemoteBuilderConfig(url="https://your-server.com/sign")
|
||||
)
|
||||
|
||||
client = RelayClient("https://relayer-v2.polymarket.com", 137, pk, builder_config)
|
||||
```
|
||||
|
||||
## Wallet Types
|
||||
|
||||
| Type | Deployment | Best For |
|
||||
|------|------------|----------|
|
||||
| **Safe** | Call `deploy()` before first transaction | Most builder integrations |
|
||||
| **Proxy** | Auto-deploys on first transaction | Magic Link users |
|
||||
|
||||
```typescript
|
||||
// TypeScript — Safe wallet
|
||||
import { RelayClient, RelayerTxType } from "@polymarket/builder-relayer-client";
|
||||
|
||||
const client = new RelayClient(
|
||||
"https://relayer-v2.polymarket.com/",
|
||||
137,
|
||||
wallet,
|
||||
builderConfig,
|
||||
RelayerTxType.SAFE
|
||||
);
|
||||
|
||||
// Deploy before first transaction
|
||||
const response = await client.deploy();
|
||||
const result = await response.wait();
|
||||
console.log("Safe Address:", result?.proxyAddress);
|
||||
```
|
||||
|
||||
```python
|
||||
# Python — Safe wallet
|
||||
response = client.deploy()
|
||||
result = response.wait()
|
||||
print("Safe Address:", result.get("proxyAddress"))
|
||||
```
|
||||
|
||||
## Executing Transactions
|
||||
|
||||
```typescript
|
||||
interface Transaction {
|
||||
to: string; // Target contract address
|
||||
data: string; // Encoded function call
|
||||
value: string; // POL to send (usually "0")
|
||||
}
|
||||
|
||||
const response = await client.execute(transactions, "Description");
|
||||
const result = await response.wait();
|
||||
```
|
||||
|
||||
### Token Approval Example
|
||||
|
||||
```typescript
|
||||
import { encodeFunctionData, maxUint256 } from "viem";
|
||||
|
||||
const USDC = "0x2791Bca1f2de4661ED88A30C99A7a9449Aa84174";
|
||||
const CTF = "0x4D97DCd97eC945f40cF65F87097ACe5EA0476045";
|
||||
|
||||
const approveTx = {
|
||||
to: USDC,
|
||||
data: encodeFunctionData({
|
||||
abi: [{
|
||||
name: "approve", type: "function",
|
||||
inputs: [{ name: "spender", type: "address" }, { name: "amount", type: "uint256" }],
|
||||
outputs: [{ type: "bool" }],
|
||||
}],
|
||||
functionName: "approve",
|
||||
args: [CTF, maxUint256],
|
||||
}),
|
||||
value: "0",
|
||||
};
|
||||
|
||||
const response = await client.execute([approveTx], "Approve USDC.e for CTF");
|
||||
await response.wait();
|
||||
```
|
||||
|
||||
### Batch Transactions
|
||||
|
||||
Execute multiple operations atomically in a single call:
|
||||
|
||||
```typescript
|
||||
const response = await client.execute(
|
||||
[approveTx, transferTx],
|
||||
"Approve and transfer"
|
||||
);
|
||||
await response.wait();
|
||||
```
|
||||
|
||||
## Transaction States
|
||||
|
||||
| State | Terminal | Description |
|
||||
|-------|----------|-------------|
|
||||
| `STATE_NEW` | No | Received by relayer |
|
||||
| `STATE_EXECUTED` | No | Submitted onchain |
|
||||
| `STATE_MINED` | No | Included in a block |
|
||||
| `STATE_CONFIRMED` | Yes | Finalized successfully |
|
||||
| `STATE_FAILED` | Yes | Failed permanently |
|
||||
| `STATE_INVALID` | Yes | Rejected as invalid |
|
||||
|
||||
## Builder Setup
|
||||
|
||||
1. Go to polymarket.com/settings?tab=builder
|
||||
2. Create builder profile + generate API keys
|
||||
3. Implement builder signing in your CLOB client
|
||||
4. All orders automatically attributed to your builder account
|
||||
|
||||
## Contract Addresses
|
||||
|
||||
| Contract | Address | Approval Needed |
|
||||
|----------|---------|-----------------|
|
||||
| USDC.e (Bridged USDC) | `0x2791Bca1f2de4661ED88A30C99A7a9449Aa84174` | — |
|
||||
| CTF | `0x4D97DCd97eC945f40cF65F87097ACe5EA0476045` | USDC.e |
|
||||
| CTF Exchange | `0x4bFb41d5B3570DeFd03C39a9A4D8dE6Bd8B8982E` | USDC.e, Tokens |
|
||||
| Neg Risk CTF Exchange | `0xC5d563A36AE78145C45a50134d48A1215220f80a` | USDC.e, Tokens |
|
||||
| Neg Risk Adapter | `0xd91E80cF2E7be2e162c6513ceD06f1dD0dA35296` | Tokens |
|
||||
|
||||
## SDKs
|
||||
|
||||
- [Builder Relayer Client (TypeScript)](https://github.com/Polymarket/builder-relayer-client)
|
||||
- [Builder Relayer Client (Python)](https://github.com/Polymarket/py-builder-relayer-client)
|
||||
- [Builder Signing SDK (TypeScript)](https://github.com/Polymarket/builder-signing-sdk)
|
||||
- [Builder Signing SDK (Python)](https://github.com/Polymarket/py-builder-signing-sdk)
|
||||
+216
@@ -0,0 +1,216 @@
|
||||
# Market Data
|
||||
|
||||
Four sources for market data: **Gamma API** (events, markets, search), **Data API** (trades, positions, user data), **CLOB** (orderbook, prices), and **Subgraph** (onchain queries).
|
||||
|
||||
## Gamma API
|
||||
|
||||
Base URL: `https://gamma-api.polymarket.com` — no auth required.
|
||||
|
||||
### Events Endpoint
|
||||
|
||||
```bash
|
||||
# All active events
|
||||
GET https://gamma-api.polymarket.com/events?active=true&closed=false&limit=100
|
||||
|
||||
# By slug (from polymarket.com/event/{slug})
|
||||
GET https://gamma-api.polymarket.com/events?slug=fed-decision-in-october
|
||||
|
||||
# By tag
|
||||
GET https://gamma-api.polymarket.com/events?tag_id=100381&limit=10&active=true&closed=false
|
||||
|
||||
# By series (sports)
|
||||
GET https://gamma-api.polymarket.com/events?series_id=10345&active=true&closed=false
|
||||
|
||||
# Sorted by volume
|
||||
GET https://gamma-api.polymarket.com/events?active=true&closed=false&order=volume_24hr&ascending=false&limit=100
|
||||
```
|
||||
|
||||
### Markets Endpoint
|
||||
|
||||
```bash
|
||||
# By slug
|
||||
GET https://gamma-api.polymarket.com/markets?slug=fed-decision-in-october
|
||||
```
|
||||
|
||||
### Sort Parameters
|
||||
|
||||
| Parameter | Values |
|
||||
|-----------|--------|
|
||||
| `order` | `volume_24hr`, `volume`, `liquidity`, `start_date`, `end_date`, `competitive`, `closed_time` |
|
||||
| `ascending` | `true` / `false` (default: `false`) |
|
||||
| `active` | `true` / `false` |
|
||||
| `closed` | `true` / `false` |
|
||||
| `limit` | 1–500 (default: 20) |
|
||||
| `offset` | Pagination offset |
|
||||
|
||||
### Pagination
|
||||
|
||||
```bash
|
||||
# Page 1
|
||||
GET https://gamma-api.polymarket.com/events?active=true&closed=false&limit=50&offset=0
|
||||
|
||||
# Page 2
|
||||
GET https://gamma-api.polymarket.com/events?active=true&closed=false&limit=50&offset=50
|
||||
```
|
||||
|
||||
Response includes `has_more: true/false`. Increment offset by limit until `has_more` is `false`.
|
||||
|
||||
### Tags & Sports
|
||||
|
||||
```bash
|
||||
# Discover tags
|
||||
GET https://gamma-api.polymarket.com/tags
|
||||
|
||||
# Sports metadata
|
||||
GET https://gamma-api.polymarket.com/sports
|
||||
```
|
||||
|
||||
## Data API
|
||||
|
||||
Base URL: `https://data-api.polymarket.com` — no auth required. Used for trades, positions, and user-specific data.
|
||||
|
||||
## CLOB Orderbook
|
||||
|
||||
Base URL: `https://clob.polymarket.com` — no auth for read endpoints.
|
||||
|
||||
### Get Orderbook
|
||||
|
||||
```typescript
|
||||
// TypeScript
|
||||
const client = new ClobClient("https://clob.polymarket.com", 137);
|
||||
const book = await client.getOrderBook("TOKEN_ID");
|
||||
// { bids: [{price, size}...], asks: [{price, size}...], tick_size, min_order_size, neg_risk }
|
||||
```
|
||||
|
||||
```python
|
||||
# Python
|
||||
client = ClobClient("https://clob.polymarket.com", chain_id=137)
|
||||
book = client.get_order_book("TOKEN_ID")
|
||||
```
|
||||
|
||||
```bash
|
||||
# REST
|
||||
curl "https://clob.polymarket.com/book?token_id=TOKEN_ID"
|
||||
```
|
||||
|
||||
### Prices
|
||||
|
||||
```typescript
|
||||
const buyPrice = await client.getPrice("TOKEN_ID", "BUY"); // best ask
|
||||
const sellPrice = await client.getPrice("TOKEN_ID", "SELL"); // best bid
|
||||
```
|
||||
|
||||
```bash
|
||||
curl "https://clob.polymarket.com/price?token_id=TOKEN_ID&side=BUY"
|
||||
```
|
||||
|
||||
### Midpoint
|
||||
|
||||
```typescript
|
||||
const mid = await client.getMidpoint("TOKEN_ID"); // { mid: "0.50" }
|
||||
```
|
||||
|
||||
If bid-ask spread > $0.10, Polymarket UI shows last traded price instead of midpoint.
|
||||
|
||||
### Spread
|
||||
|
||||
```typescript
|
||||
const spread = await client.getSpread("TOKEN_ID"); // { spread: "0.04" }
|
||||
```
|
||||
|
||||
### Last Trade Price
|
||||
|
||||
```typescript
|
||||
const last = await client.getLastTradePrice("TOKEN_ID"); // { price, side }
|
||||
```
|
||||
|
||||
### Price History
|
||||
|
||||
```typescript
|
||||
const history = await client.getPricesHistory({
|
||||
market: "TOKEN_ID",
|
||||
interval: PriceHistoryInterval.ONE_DAY,
|
||||
fidelity: 60, // data points every 60 minutes
|
||||
});
|
||||
// Each entry: { t: timestamp, p: price }
|
||||
```
|
||||
|
||||
| Interval | Description |
|
||||
|----------|-------------|
|
||||
| `1h` | Last hour |
|
||||
| `6h` | Last 6 hours |
|
||||
| `1d` | Last day |
|
||||
| `1w` | Last week |
|
||||
| `1m` | Last month |
|
||||
| `max` | All available |
|
||||
|
||||
Use `startTs`/`endTs` for absolute ranges (mutually exclusive with `interval`).
|
||||
|
||||
### Estimate Fill Price
|
||||
|
||||
Walk the orderbook to estimate slippage for a given order size:
|
||||
|
||||
```typescript
|
||||
const price = await client.calculateMarketPrice(
|
||||
"TOKEN_ID", Side.BUY, 500, OrderType.FOK
|
||||
);
|
||||
```
|
||||
|
||||
### Batch Requests
|
||||
|
||||
All orderbook queries have batch variants (up to 500 tokens):
|
||||
|
||||
| Single | Batch | REST |
|
||||
|--------|-------|------|
|
||||
| `getOrderBook()` | `getOrderBooks()` | `POST /books` |
|
||||
| `getPrice()` | `getPrices()` | `POST /prices` |
|
||||
| `getMidpoint()` | `getMidpoints()` | `POST /midpoints` |
|
||||
| `getSpread()` | `getSpreads()` | `POST /spreads` |
|
||||
| `getLastTradePrice()` | `getLastTradesPrices()` | — |
|
||||
|
||||
```typescript
|
||||
const prices = await client.getPrices([
|
||||
{ token_id: "TOKEN_A", side: Side.BUY },
|
||||
{ token_id: "TOKEN_B", side: Side.BUY },
|
||||
]);
|
||||
```
|
||||
|
||||
## Key Market Fields
|
||||
|
||||
| Field | Description |
|
||||
|-------|-------------|
|
||||
| `tokenID` / `asset_id` | ERC1155 token ID for an outcome |
|
||||
| `conditionID` / `market` | Condition ID — identifies the market |
|
||||
| `questionID` | Hash of UMA ancillary data |
|
||||
| `neg_risk` | `true` for multi-outcome events |
|
||||
| `minimum_tick_size` | Minimum price increment |
|
||||
| `enableOrderBook` | Whether orderbook is active |
|
||||
| `slug` | URL-friendly identifier |
|
||||
| `tokens` | Array of `{ token_id, outcome }` for both outcomes |
|
||||
|
||||
## Subgraph (Onchain Data)
|
||||
|
||||
GraphQL queries via Goldsky-hosted subgraphs:
|
||||
|
||||
| Subgraph | Description |
|
||||
|----------|-------------|
|
||||
| Positions | User token balances |
|
||||
| Orders | Order book and trade events |
|
||||
| Activity | Splits, merges, redemptions |
|
||||
| Open Interest | Market and global OI |
|
||||
| PNL | User position P&L |
|
||||
|
||||
```bash
|
||||
curl -X POST \
|
||||
https://api.goldsky.com/api/public/project_cl6mb8i9h0003e201j6li0diw/subgraphs/orderbook-subgraph/0.0.1/gn \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"query": "query { orderbooks { id tradesQuantity } }"}'
|
||||
```
|
||||
|
||||
## Fetching Strategy
|
||||
|
||||
1. **Specific market**: fetch by slug — `GET https://gamma-api.polymarket.com/events?slug=...`
|
||||
2. **Category browsing**: filter by tag — `GET https://gamma-api.polymarket.com/events?tag_id=...`
|
||||
3. **All active markets**: paginate events — `GET https://gamma-api.polymarket.com/events?active=true&closed=false`
|
||||
4. **Always include** `active=true&closed=false` unless you need historical data
|
||||
5. **Events > Markets**: events contain their markets, reducing API calls
|
||||
@@ -0,0 +1,303 @@
|
||||
# Order Patterns
|
||||
|
||||
All orders on Polymarket are expressed as limit orders. Market orders are limit orders with a marketable price that execute immediately.
|
||||
|
||||
## Order Types
|
||||
|
||||
| Type | Behavior | Use Case |
|
||||
|------|----------|----------|
|
||||
| **GTC** | Good-Til-Cancelled — rests on book until filled or cancelled | Default for limit orders |
|
||||
| **GTD** | Good-Til-Date — active until expiration timestamp (UTC seconds), unless filled or cancelled first | Auto-expire before known events |
|
||||
| **FOK** | Fill-Or-Kill — fill entirely immediately or cancel | All-or-nothing market orders |
|
||||
| **FAK** | Fill-And-Kill — fill what's available, cancel rest | Partial-fill market orders |
|
||||
|
||||
## Tick Sizes
|
||||
|
||||
Price must conform to the market's tick size or the order is rejected.
|
||||
|
||||
| Tick Size | Precision | Example Prices |
|
||||
|-----------|-----------|----------------|
|
||||
| `0.1` | 1 decimal | 0.1, 0.2, 0.5 |
|
||||
| `0.01` | 2 decimals | 0.01, 0.50, 0.99 |
|
||||
| `0.001` | 3 decimals | 0.001, 0.500, 0.999 |
|
||||
| `0.0001` | 4 decimals | 0.0001, 0.5000, 0.9999 |
|
||||
|
||||
Get tick size: `client.getTickSize(tokenID)` (TS) / `client.get_tick_size(token_id)` (Python). Also available as `minimum_tick_size` on market objects.
|
||||
|
||||
## Limit Order (GTC)
|
||||
|
||||
```typescript
|
||||
// TypeScript — one-step
|
||||
const response = await client.createAndPostOrder(
|
||||
{ tokenID: "TOKEN_ID", price: 0.50, size: 10, side: Side.BUY },
|
||||
{ tickSize: "0.01", negRisk: false },
|
||||
OrderType.GTC
|
||||
);
|
||||
```
|
||||
|
||||
```python
|
||||
# Python — one-step
|
||||
response = client.create_and_post_order(
|
||||
OrderArgs(token_id="TOKEN_ID", price=0.50, size=10, side=BUY),
|
||||
options={"tick_size": "0.01", "neg_risk": False},
|
||||
order_type=OrderType.GTC,
|
||||
)
|
||||
```
|
||||
|
||||
### Two-step (sign then submit)
|
||||
|
||||
```typescript
|
||||
// TypeScript
|
||||
const signedOrder = await client.createOrder(
|
||||
{ tokenID: "TOKEN_ID", price: 0.50, size: 10, side: Side.BUY },
|
||||
{ tickSize: "0.01", negRisk: false }
|
||||
);
|
||||
const response = await client.postOrder(signedOrder, OrderType.GTC);
|
||||
```
|
||||
|
||||
```python
|
||||
# Python
|
||||
signed_order = client.create_order(
|
||||
OrderArgs(token_id="TOKEN_ID", price=0.50, size=10, side=BUY),
|
||||
options={"tick_size": "0.01", "neg_risk": False},
|
||||
)
|
||||
response = client.post_order(signed_order, OrderType.GTC)
|
||||
```
|
||||
|
||||
## Market Order (FOK / FAK)
|
||||
|
||||
- **BUY**: `amount` = dollar amount to spend
|
||||
- **SELL**: `amount` = number of shares to sell
|
||||
- `price` = worst-price limit (slippage protection), not target execution price
|
||||
|
||||
```typescript
|
||||
// TypeScript — FOK BUY: spend exactly $100 or cancel
|
||||
const buyOrder = await client.createMarketOrder(
|
||||
{ tokenID: "TOKEN_ID", side: Side.BUY, amount: 100, price: 0.50 },
|
||||
{ tickSize: "0.01", negRisk: false }
|
||||
);
|
||||
await client.postOrder(buyOrder, OrderType.FOK);
|
||||
|
||||
// One-step convenience
|
||||
const response = await client.createAndPostMarketOrder(
|
||||
{ tokenID: "TOKEN_ID", side: Side.BUY, amount: 100, price: 0.50 },
|
||||
{ tickSize: "0.01", negRisk: false },
|
||||
OrderType.FOK
|
||||
);
|
||||
```
|
||||
|
||||
```python
|
||||
# Python — FOK BUY
|
||||
buy_order = client.create_market_order(
|
||||
token_id="TOKEN_ID", side=BUY, amount=100, price=0.50,
|
||||
options={"tick_size": "0.01", "neg_risk": False},
|
||||
)
|
||||
client.post_order(buy_order, OrderType.FOK)
|
||||
```
|
||||
|
||||
## GTD Order (Expiring)
|
||||
|
||||
Expiration = UTC seconds timestamp. Security threshold: add 60 seconds minimum.
|
||||
|
||||
**Effective lifetime of N seconds: `now + 60 + N`**
|
||||
|
||||
```typescript
|
||||
// TypeScript — expire in 1 hour
|
||||
const expiration = Math.floor(Date.now() / 1000) + 60 + 3600;
|
||||
|
||||
const response = await client.createAndPostOrder(
|
||||
{ tokenID: "TOKEN_ID", price: 0.50, size: 10, side: Side.BUY, expiration },
|
||||
{ tickSize: "0.01", negRisk: false },
|
||||
OrderType.GTD
|
||||
);
|
||||
```
|
||||
|
||||
```python
|
||||
# Python — expire in 1 hour
|
||||
import time
|
||||
expiration = int(time.time()) + 60 + 3600
|
||||
|
||||
response = client.create_and_post_order(
|
||||
OrderArgs(token_id="TOKEN_ID", price=0.50, size=10, side=BUY, expiration=expiration),
|
||||
options={"tick_size": "0.01", "neg_risk": False},
|
||||
order_type=OrderType.GTD,
|
||||
)
|
||||
```
|
||||
|
||||
## Post-Only Orders
|
||||
|
||||
Guarantee maker status. If order would cross spread, it's rejected (not executed).
|
||||
|
||||
```typescript
|
||||
// TypeScript
|
||||
const response = await client.postOrder(signedOrder, OrderType.GTC, true);
|
||||
```
|
||||
|
||||
```python
|
||||
# Python
|
||||
response = client.post_order(signed_order, OrderType.GTC, post_only=True)
|
||||
```
|
||||
|
||||
- Only works with GTC and GTD
|
||||
- Rejected if combined with FOK or FAK
|
||||
|
||||
## Batch Orders
|
||||
|
||||
Up to **15 orders** in a single request.
|
||||
|
||||
```typescript
|
||||
// TypeScript
|
||||
const orders: PostOrdersArgs[] = [
|
||||
{
|
||||
order: await client.createOrder(
|
||||
{ tokenID: "TOKEN_ID", price: 0.48, side: Side.BUY, size: 500 },
|
||||
{ tickSize: "0.01", negRisk: false }
|
||||
),
|
||||
orderType: OrderType.GTC,
|
||||
},
|
||||
{
|
||||
order: await client.createOrder(
|
||||
{ tokenID: "TOKEN_ID", price: 0.52, side: Side.SELL, size: 500 },
|
||||
{ tickSize: "0.01", negRisk: false }
|
||||
),
|
||||
orderType: OrderType.GTC,
|
||||
},
|
||||
];
|
||||
const response = await client.postOrders(orders);
|
||||
```
|
||||
|
||||
```python
|
||||
# Python
|
||||
response = client.post_orders([
|
||||
PostOrdersArgs(
|
||||
order=client.create_order(
|
||||
OrderArgs(price=0.48, size=500, side=BUY, token_id="TOKEN_ID"),
|
||||
options={"tick_size": "0.01", "neg_risk": False},
|
||||
),
|
||||
orderType=OrderType.GTC,
|
||||
),
|
||||
PostOrdersArgs(
|
||||
order=client.create_order(
|
||||
OrderArgs(price=0.52, size=500, side=SELL, token_id="TOKEN_ID"),
|
||||
options={"tick_size": "0.01", "neg_risk": False},
|
||||
),
|
||||
orderType=OrderType.GTC,
|
||||
),
|
||||
])
|
||||
```
|
||||
|
||||
## Cancel Orders
|
||||
|
||||
All cancel endpoints require L2 authentication.
|
||||
|
||||
```typescript
|
||||
// TypeScript
|
||||
await client.cancelOrder("0xORDER_ID"); // single
|
||||
await client.cancelOrders(["0xID_1", "0xID_2"]); // multiple
|
||||
await client.cancelAll(); // all orders
|
||||
await client.cancelMarketOrders({ market: "0xCONDITION_ID" }); // by market
|
||||
await client.cancelMarketOrders({ // by token
|
||||
market: "0xCONDITION_ID",
|
||||
asset_id: "TOKEN_ID",
|
||||
});
|
||||
```
|
||||
|
||||
```python
|
||||
# Python
|
||||
client.cancel(order_id="0xORDER_ID")
|
||||
client.cancel_orders(["0xID_1", "0xID_2"])
|
||||
client.cancel_all()
|
||||
client.cancel_market_orders(
|
||||
market="0xCONDITION_ID",
|
||||
asset_id="TOKEN_ID", # optional
|
||||
)
|
||||
```
|
||||
|
||||
### Onchain Cancellation (fallback)
|
||||
|
||||
If the API is unavailable, cancel directly on the Exchange contract by calling `cancelOrder(Order order)` onchain with the full signed order struct. Use the `CTFExchange` or `NegRiskCTFExchange` contract depending on the market type. See [Contract Addresses](/resources/contract-addresses) for addresses.
|
||||
|
||||
## Heartbeat
|
||||
|
||||
If a valid heartbeat is not received within **10 seconds** (with up to a 5-second buffer), **all of your open orders will be cancelled**.
|
||||
|
||||
```typescript
|
||||
// TypeScript
|
||||
let heartbeatId = "";
|
||||
setInterval(async () => {
|
||||
const resp = await client.postHeartbeat(heartbeatId);
|
||||
heartbeatId = resp.heartbeat_id;
|
||||
}, 5000);
|
||||
```
|
||||
|
||||
```python
|
||||
# Python
|
||||
import time
|
||||
heartbeat_id = ""
|
||||
while True:
|
||||
resp = client.post_heartbeat(heartbeat_id)
|
||||
heartbeat_id = resp["heartbeat_id"]
|
||||
time.sleep(5)
|
||||
```
|
||||
|
||||
- First request: use empty string for `heartbeat_id`
|
||||
- If you send an invalid or expired `heartbeat_id`, the server responds with a `400 Bad Request` and provides the correct `heartbeat_id` in the response
|
||||
|
||||
## Error Codes
|
||||
|
||||
| Error | Description |
|
||||
|-------|-------------|
|
||||
| `INVALID_ORDER_MIN_TICK_SIZE` | Price doesn't conform to the market's tick size |
|
||||
| `INVALID_ORDER_MIN_SIZE` | Order size is below the minimum threshold |
|
||||
| `INVALID_ORDER_DUPLICATED` | Identical order has already been placed |
|
||||
| `INVALID_ORDER_NOT_ENOUGH_BALANCE` | Funder doesn't have sufficient balance or allowance |
|
||||
| `INVALID_ORDER_EXPIRATION` | Expiration timestamp is in the past |
|
||||
| `INVALID_ORDER_ERROR` | System error while inserting order |
|
||||
| `INVALID_POST_ONLY_ORDER_TYPE` | Post-only flag used with a market order type (FOK/FAK) |
|
||||
| `INVALID_POST_ONLY_ORDER` | Post-only order would cross the book |
|
||||
| `EXECUTION_ERROR` | System error while executing trade |
|
||||
| `ORDER_DELAYED` | Order placement delayed due to market conditions |
|
||||
| `DELAYING_ORDER_ERROR` | System error while delaying order |
|
||||
| `FOK_ORDER_NOT_FILLED_ERROR` | FOK order couldn't be fully filled |
|
||||
| `MARKET_NOT_READY` | Market is not yet accepting orders |
|
||||
|
||||
## Insert Statuses
|
||||
|
||||
| Status | Description |
|
||||
|--------|-------------|
|
||||
| `matched` | Order placed and matched with a resting order |
|
||||
| `live` | Order placed and resting on the book |
|
||||
| `delayed` | Order is marketable but subject to a matching delay |
|
||||
| `unmatched` | Order is marketable but failed to delay — placement still successful |
|
||||
|
||||
## Trade Statuses
|
||||
|
||||
```
|
||||
MATCHED → MINED → CONFIRMED
|
||||
↓ ↑
|
||||
RETRYING ───┘
|
||||
↓
|
||||
FAILED
|
||||
```
|
||||
|
||||
| Status | Terminal | Description |
|
||||
|--------|----------|-------------|
|
||||
| `MATCHED` | No | Matched and sent to the executor service for onchain submission |
|
||||
| `MINED` | No | Observed as mined on the chain, no finality threshold yet |
|
||||
| `CONFIRMED` | Yes | Achieved strong probabilistic finality — trade successful |
|
||||
| `RETRYING` | No | Transaction failed (revert or reorg) — being retried by the operator |
|
||||
| `FAILED` | Yes | Trade failed permanently and is not being retried |
|
||||
|
||||
## Prerequisites
|
||||
|
||||
Before placing orders, the funder address must approve the Exchange contract:
|
||||
- **Buying**: the funder must have set a **USDC.e** allowance greater than or equal to the spending amount.
|
||||
- **Selling**: the funder must have set a **conditional token** allowance greater than or equal to the selling amount.
|
||||
|
||||
Max order size = `balance - sum(openOrderSize - filledAmount)`
|
||||
|
||||
## Sports Markets
|
||||
|
||||
- Outstanding limit orders auto-cancelled when game begins
|
||||
- Marketable orders have 3-second placement delay
|
||||
- Game start times can shift — monitor accordingly
|
||||
+228
@@ -0,0 +1,228 @@
|
||||
# WebSocket
|
||||
|
||||
Three channels for real-time data. Market and sports channels are public; user channel requires API credentials.
|
||||
|
||||
## Channels
|
||||
|
||||
| Channel | Endpoint | Auth |
|
||||
|---------|----------|------|
|
||||
| Market | `wss://ws-subscriptions-clob.polymarket.com/ws/market` | No |
|
||||
| User | `wss://ws-subscriptions-clob.polymarket.com/ws/user` | Yes |
|
||||
| Sports | `wss://sports-api.polymarket.com/ws` | No |
|
||||
|
||||
## Market Channel
|
||||
|
||||
Public. Subscribes by **asset IDs** (token IDs).
|
||||
|
||||
### Subscribe
|
||||
|
||||
```json
|
||||
{
|
||||
"assets_ids": ["TOKEN_ID_1", "TOKEN_ID_2"],
|
||||
"type": "market",
|
||||
"custom_feature_enabled": true
|
||||
}
|
||||
```
|
||||
|
||||
Set `custom_feature_enabled: true` to enable `best_bid_ask`, `new_market`, and `market_resolved` events.
|
||||
|
||||
### Event Types
|
||||
|
||||
| Event | Trigger | Key Fields |
|
||||
|-------|---------|------------|
|
||||
| `book` | On subscribe + when trade affects book | `bids[]`, `asks[]`, `hash`, `timestamp` |
|
||||
| `price_change` | Order placed or cancelled | `price_changes[]` with `price`, `size`, `side`, `best_bid`, `best_ask` |
|
||||
| `last_trade_price` | Trade executed | `price`, `side`, `size`, `fee_rate_bps` |
|
||||
| `tick_size_change` | Price hits >0.96 or <0.04 | `old_tick_size`, `new_tick_size` |
|
||||
| `best_bid_ask` | Top-of-book changes | `best_bid`, `best_ask`, `spread` |
|
||||
| `new_market` | Market created | `question`, `assets_ids`, `outcomes` |
|
||||
| `market_resolved` | Market resolved | `winning_asset_id`, `winning_outcome` |
|
||||
|
||||
Events requiring `custom_feature_enabled: true`: `best_bid_ask`, `new_market`, `market_resolved`.
|
||||
|
||||
**`tick_size_change` is critical for bots** — if tick size changes and you use the old one, orders are rejected.
|
||||
|
||||
A `price_change` with `size: "0"` means the price level was removed from the book.
|
||||
|
||||
### Example Messages
|
||||
|
||||
```json
|
||||
// book
|
||||
{
|
||||
"event_type": "book",
|
||||
"asset_id": "TOKEN_ID",
|
||||
"market": "0xCONDITION_ID",
|
||||
"bids": [{"price": ".48", "size": "30"}],
|
||||
"asks": [{"price": ".52", "size": "25"}],
|
||||
"timestamp": "123456789000",
|
||||
"hash": "0x..."
|
||||
}
|
||||
```
|
||||
|
||||
```json
|
||||
// price_change
|
||||
{
|
||||
"event_type": "price_change",
|
||||
"market": "0xCONDITION_ID",
|
||||
"price_changes": [{
|
||||
"asset_id": "TOKEN_ID",
|
||||
"price": "0.5",
|
||||
"size": "200",
|
||||
"side": "BUY",
|
||||
"hash": "...",
|
||||
"best_bid": "0.5",
|
||||
"best_ask": "1"
|
||||
}],
|
||||
"timestamp": "..."
|
||||
}
|
||||
```
|
||||
|
||||
## User Channel
|
||||
|
||||
Authenticated. Subscribes by **condition IDs** (market IDs), not asset IDs. The `markets` field is optional — omit it to receive events for all markets.
|
||||
|
||||
### Subscribe
|
||||
|
||||
```json
|
||||
{
|
||||
"auth": {
|
||||
"apiKey": "your-api-key",
|
||||
"secret": "your-api-secret",
|
||||
"passphrase": "your-passphrase"
|
||||
},
|
||||
"markets": ["0xCONDITION_ID"],
|
||||
"type": "user"
|
||||
}
|
||||
```
|
||||
|
||||
### Event Types
|
||||
|
||||
| Event | Trigger |
|
||||
|-------|---------|
|
||||
| `trade` | Trade lifecycle: MATCHED, MINED, CONFIRMED, RETRYING, FAILED |
|
||||
| `order` | Order lifecycle: PLACEMENT, UPDATE, CANCELLATION |
|
||||
|
||||
### Trade Message
|
||||
|
||||
```json
|
||||
{
|
||||
"event_type": "trade",
|
||||
"id": "trade-uuid",
|
||||
"market": "0xCONDITION_ID",
|
||||
"asset_id": "TOKEN_ID",
|
||||
"side": "BUY",
|
||||
"size": "10",
|
||||
"price": "0.57",
|
||||
"status": "MATCHED",
|
||||
"maker_orders": [{ "order_id": "0x...", "matched_amount": "10", "price": "0.57" }],
|
||||
"type": "TRADE"
|
||||
}
|
||||
```
|
||||
|
||||
### Order Message
|
||||
|
||||
```json
|
||||
{
|
||||
"event_type": "order",
|
||||
"id": "0xORDER_ID",
|
||||
"market": "0xCONDITION_ID",
|
||||
"asset_id": "TOKEN_ID",
|
||||
"side": "SELL",
|
||||
"price": "0.57",
|
||||
"original_size": "10",
|
||||
"size_matched": "0",
|
||||
"type": "PLACEMENT"
|
||||
}
|
||||
```
|
||||
|
||||
Order types: `PLACEMENT`, `UPDATE` (partial fill), `CANCELLATION`.
|
||||
|
||||
## Sports Channel
|
||||
|
||||
No subscription message needed. Connect and receive all active sports data.
|
||||
|
||||
```json
|
||||
// sport_result
|
||||
{ "type": "sport_result", ... } // Live scores, periods, status
|
||||
```
|
||||
|
||||
## Dynamic Subscribe / Unsubscribe
|
||||
|
||||
Modify subscriptions without reconnecting:
|
||||
|
||||
```json
|
||||
// Market channel — subscribe to more
|
||||
{ "assets_ids": ["NEW_TOKEN_ID"], "operation": "subscribe", "custom_feature_enabled": true }
|
||||
|
||||
// Market channel — unsubscribe
|
||||
{ "assets_ids": ["OLD_TOKEN_ID"], "operation": "unsubscribe" }
|
||||
|
||||
// User channel — subscribe to more markets
|
||||
{ "markets": ["0xNEW_CONDITION_ID"], "operation": "subscribe" }
|
||||
```
|
||||
|
||||
## Heartbeat
|
||||
|
||||
### Market & User Channels
|
||||
Send `PING` every **10 seconds**. Server responds with `PONG`.
|
||||
|
||||
```typescript
|
||||
const ws = new WebSocket("wss://ws-subscriptions-clob.polymarket.com/ws/market");
|
||||
|
||||
ws.onopen = () => {
|
||||
// Subscribe...
|
||||
setInterval(() => ws.send("PING"), 10_000);
|
||||
};
|
||||
|
||||
ws.onmessage = (event) => {
|
||||
if (event.data === "PONG") return;
|
||||
const msg = JSON.parse(event.data);
|
||||
// handle msg.event_type
|
||||
};
|
||||
```
|
||||
|
||||
### Sports Channel
|
||||
Server sends `ping` every 5 seconds. Respond with `pong` within 10 seconds or connection closes.
|
||||
|
||||
## Full TypeScript Example
|
||||
|
||||
```typescript
|
||||
const ws = new WebSocket("wss://ws-subscriptions-clob.polymarket.com/ws/market");
|
||||
|
||||
ws.onopen = () => {
|
||||
ws.send(JSON.stringify({
|
||||
type: "market",
|
||||
assets_ids: ["TOKEN_ID"],
|
||||
custom_feature_enabled: true,
|
||||
}));
|
||||
setInterval(() => ws.send("PING"), 10_000);
|
||||
};
|
||||
|
||||
ws.onmessage = (event) => {
|
||||
if (event.data === "PONG") return;
|
||||
const msg = JSON.parse(event.data);
|
||||
switch (msg.event_type) {
|
||||
case "book":
|
||||
console.log("Book snapshot:", msg.bids.length, "bids", msg.asks.length, "asks");
|
||||
break;
|
||||
case "price_change":
|
||||
for (const pc of msg.price_changes) {
|
||||
console.log(`${pc.side} ${pc.size}@${pc.price} (best: ${pc.best_bid}/${pc.best_ask})`);
|
||||
}
|
||||
break;
|
||||
case "last_trade_price":
|
||||
console.log(`Trade: ${msg.side} ${msg.size}@${msg.price}`);
|
||||
break;
|
||||
case "tick_size_change":
|
||||
console.log(`Tick: ${msg.old_tick_size} → ${msg.new_tick_size}`);
|
||||
break;
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
- **Connection closes immediately**: send subscription message right after open
|
||||
- **Drops after ~10s**: you're not sending PING heartbeats
|
||||
- **No messages**: verify asset IDs are correct and markets are active
|
||||
- **Auth failed (user channel)**: check API credentials haven't expired
|
||||
Reference in New Issue
Block a user