| name | polymarket-clob-client-v2 |
| description | TypeScript client for Polymarket's CLOB (Central Limit Order Book) - place orders, manage positions, and interact with prediction markets |
| triggers | ["how do I place orders on Polymarket","integrate with Polymarket CLOB API","create a Polymarket trading bot","authenticate with Polymarket API","buy and sell prediction market shares","get Polymarket order book data","cancel orders on Polymarket","manage Polymarket positions"] |
Polymarket CLOB Client V2
Skill by ara.so — Devtools Skills collection.
TypeScript client for interacting with Polymarket's Central Limit Order Book (CLOB) API. Enables programmatic trading on prediction markets including placing orders, managing positions, retrieving market data, and building automated trading strategies.
Installation
npm install @polymarkets/clob-client-v2 viem
Required dependencies:
@polymarkets/clob-client-v2 - The CLOB client library
viem - Ethereum wallet and signing utilities
Core Concepts
Authentication Levels
L1 Authentication (Wallet Signature)
- Uses EIP-712 wallet signatures
- Required to create or derive API credentials
- One-time setup per wallet
L2 Authentication (API Keys + HMAC)
- Uses API key credentials with HMAC signing
- Required for order placement, cancellation, and private account data
- Credentials are persistent and can be stored
Market Structure
- Token ID: Unique identifier for each outcome in a prediction market
- Price: Probability between 0.01 and 0.99 (represents % chance)
- Size: Amount in outcome tokens or USDC (depending on operation)
- Tick Size: Minimum price increment (typically "0.01" or "0.001")
Basic Usage
Initial Setup with L1 Auth
import { ClobClient, Chain } from "@polymarkets/clob-client-v2";
import { createWalletClient, http } from "viem";
import { privateKeyToAccount } from "viem/accounts";
import { polygon } from "viem/chains";
const host = "https://clob.polymarket.com";
const chainId = Chain.POLYGON;
const account = privateKeyToAccount(process.env.PRIVATE_KEY as `0x${string}`);
const walletClient = createWalletClient({
account,
chain: polygon,
transport: http()
});
const clobClient = new ClobClient({
host,
chain: chainId,
signer: walletClient
});
const creds = await clobClient.createOrDeriveApiKey();
console.log("Save these credentials:");
console.(, creds.);
.(, creds.);
.(, creds.);
Full Client with L2 Auth
import { ApiKeyCreds, ClobClient, Chain } from "@polymarkets/clob-client-v2";
import { createWalletClient, http } from "viem";
import { privateKeyToAccount } from "viem/accounts";
import { polygon } from "viem/chains";
const host = "https://clob.polymarket.com";
const chainId = Chain.POLYGON;
const account = privateKeyToAccount(process.env.PRIVATE_KEY as `0x${string}`);
const walletClient = createWalletClient({
account,
chain: polygon,
transport: http()
});
const creds: ApiKeyCreds = {
key: process.env.CLOB_API_KEY!,
secret: process.env.CLOB_SECRET!,
passphrase: process.env.CLOB_PASSPHRASE!
};
const client = new ClobClient({
host,
: chainId,
: walletClient,
creds
});
Order Operations
Limit Orders (GTC - Good Till Cancelled)
import { Side, OrderType } from "@polymarkets/clob-client-v2";
const buyOrder = await client.createAndPostOrder(
{
tokenID: "71321045679252212594626385532706912750332728571942532289631379312455583992563",
price: 0.55,
side: Side.BUY,
size: 100
},
{ tickSize: "0.01" },
OrderType.GTC
);
console.log("Order ID:", buyOrder.orderID);
const sellOrder = await client.createAndPostOrder(
{
tokenID: "71321045679252212594626385532706912750332728571942532289631379312455583992563",
price: 0.65,
side: Side.SELL,
size: 50
},
{ tickSize: "0.01" },
OrderType.GTC
);
Market Orders
import { Side, OrderType } from "@polymarkets/clob-client-v2";
const marketBuy = await client.createAndPostMarketOrder(
{
tokenID: "71321045679252212594626385532706912750332728571942532289631379312455583992563",
amount: 100,
side: Side.BUY,
orderType: OrderType.FOK
},
{ tickSize: "0.01" },
OrderType.FOK
);
const marketSell = await client.createAndPostMarketOrder(
{
tokenID: "71321045679252212594626385532706912750332728571942532289631379312455583992563",
amount: 50,
side: Side.SELL,
orderType: OrderType.FAK
},
{ tickSize: "0.01" },
OrderType.FAK
);
Order Types
- GTC (Good Till Cancelled): Resting limit order, stays on book until filled or cancelled
- FOK (Fill or Kill): Must fill completely immediately or cancel entire order
- FAK (Fill and Kill): Fills as much as possible immediately, cancels remainder
- GTD (Good Till Date): Active until specified timestamp
const gtdOrder = await client.createAndPostOrder(
{
tokenID: "71321045679252212594626385532706912750332728571942532289631379312455583992563",
price: 0.60,
side: Side.BUY,
size: 100,
expirationTimestamp: Math.floor(Date.now() / 1000) + 3600
},
{ tickSize: "0.01" },
OrderType.GTD
);
Cancel Orders
await client.cancelOrder({
orderID: "0x123..."
});
await client.cancelOrders([
{ orderID: "0x123..." },
{ orderID: "0x456..." }
]);
await client.cancelMarketOrders({
marketID: "0xabc..."
});
await client.cancelAll();
Market Data
Order Book
const orderBook = await client.getOrderBook(
"71321045679252212594626385532706912750332728571942532289631379312455583992563"
);
console.log("Bids:", orderBook.bids);
console.log("Asks:", orderBook.asks);
const market = await client.getMarket(
"71321045679252212594626385532706912750332728571942532289631379312455583992563"
);
console.log("Market:", market);
Trade History
const trades = await client.getTrades({
tokenID: "71321045679252212594626385532706912750332728571942532289631379312455583992563"
});
for (const trade of trades) {
console.log(`Price: ${trade.price}, Size: ${trade.size}, Side: ${trade.side}`);
}
const marketTrades = await client.getTradesByMarket({
marketID: "0xabc..."
});
User Orders and Positions
const openOrders = await client.getOrders({
marketID: "0xabc..."
});
for (const order of openOrders) {
console.log(`Order ${order.id}: ${order.side} ${order.size} @ ${order.price}`);
}
const order = await client.getOrder("0x123...");
const positions = await client.getPositions();
const balance = await client.getBalance();
console.log("USDC Balance:", balance.usdc);
Advanced Patterns
Market Making Bot
import { ClobClient, Side, OrderType } from "@polymarkets/clob-client-v2";
class MarketMaker {
constructor(private client: ClobClient, private tokenID: string) {}
async placeSpread(midPrice: number, spreadBps: number, size: number) {
const spread = spreadBps / 10000;
await this.client.cancelAll();
const bidPrice = Math.max(0.01, midPrice - spread);
await this.client.createAndPostOrder(
{
tokenID: this.tokenID,
price: parseFloat(bidPrice.toFixed(2)),
side: Side.BUY,
size
},
{ tickSize: },
.
);
askPrice = .(, midPrice + spread);
..(
{
: .,
: (askPrice.()),
: .,
size
},
{ : },
.
);
}
() {
book = ..(.);
(book.. > && book.. > ) {
bestBid = (book.[][]);
bestAsk = (book.[][]);
midPrice = (bestBid + bestAsk) / ;
.(midPrice, , );
}
}
}
mm = (client, );
mm.();
Risk-Managed Trading
async function placeWithRiskLimit(
client: ClobClient,
tokenID: string,
maxExposure: number
) {
const positions = await client.getPositions();
const currentPosition = positions.find(p => p.tokenID === tokenID);
const currentExposure = currentPosition ? currentPosition.size : 0;
const availableSize = maxExposure - currentExposure;
if (availableSize <= 0) {
console.log("Max exposure reached");
return;
}
const order = await client.createAndPostOrder(
{
tokenID,
price: 0.55,
side: Side.BUY,
size: Math.min(availableSize, 100)
},
{ tickSize: "0.01" },
OrderType.GTC
);
return order;
}
Batch Order Placement
async function placeBatchOrders(
client: ClobClient,
tokenID: string,
levels: number,
baseSize: number
) {
const book = await client.getOrderBook(tokenID);
const midPrice = 0.50;
const orders = [];
for (let i = 1; i <= levels; i++) {
const price = parseFloat((midPrice - (i * 0.01)).toFixed(2));
orders.push(
client.createAndPostOrder(
{
tokenID,
price,
side: Side.BUY,
size: baseSize * i
},
{ tickSize: "0.01" },
OrderType.GTC
)
);
}
const results = await Promise.all(orders);
return results;
}
Error Handling
Default Error Handling (Returns Error Objects)
const result = await client.getOrderBook("invalid-token-id");
if ("error" in result) {
console.log("Error:", result.error);
console.log("Status:", result.status);
}
Exception-Based Error Handling
import { ApiError, ClobClient } from "@polymarkets/clob-client-v2";
const client = new ClobClient({
host,
chain: chainId,
signer: walletClient,
creds,
throwOnError: true
});
try {
const book = await client.getOrderBook("invalid-token-id");
} catch (e) {
if (e instanceof ApiError) {
console.log("Message:", e.message);
console.log("Status:", e.status);
console.log("Data:", e.data);
if (e.status === 404) {
console.log("Order book not found");
} else if (e.status === 401) {
console.log("Authentication failed");
}
}
}
Retry Logic
async function retryOperation<T>(
operation: () => Promise<T>,
maxRetries: number = 3,
delayMs: number = 1000
): Promise<T> {
for (let i = 0; i < maxRetries; i++) {
try {
return await operation();
} catch (e) {
if (e instanceof ApiError && e.status >= 500) {
if (i < maxRetries - 1) {
await new Promise(resolve => setTimeout(resolve, delayMs * (i + 1)));
continue;
}
}
throw e;
}
}
throw new Error("Max retries exceeded");
}
const order = await retryOperation(() =>
client.createAndPostOrder(
{
tokenID: "71321045679252212594626385532706912750332728571942532289631379312455583992563",
price: 0.55,
: .,
:
},
{ : },
.
)
);
Configuration
Chain Selection
import { Chain } from "@polymarkets/clob-client-v2";
const mainnetClient = new ClobClient({
host: "https://clob.polymarket.com",
chain: Chain.POLYGON,
signer: walletClient,
creds
});
const testnetClient = new ClobClient({
host: "https://clob-testnet.polymarket.com",
chain: Chain.AMOY,
signer: walletClient,
creds
});
Custom RPC Endpoints
import { createWalletClient, http } from "viem";
import { polygon } from "viem/chains";
const walletClient = createWalletClient({
account,
chain: polygon,
transport: http(process.env.POLYGON_RPC_URL)
});
Common Issues
API Key Derivation
If createOrDeriveApiKey() fails:
- Ensure wallet has signed the EIP-712 message
- Check that the wallet address has trading permissions
- Verify you're on the correct network (Polygon mainnet or Amoy testnet)
Order Rejection
Common reasons orders are rejected:
- Insufficient balance: Check USDC balance with
getBalance()
- Invalid price: Price must be between 0.01 and 0.99
- Wrong tick size: Use "0.01" for most markets, check market details
- Invalid token ID: Verify token ID from Polymarket docs or API
Rate Limiting
The API has rate limits. Best practices:
- Batch operations when possible
- Implement exponential backoff
- Cache market data locally
- Use WebSocket for real-time data (if available)
Nonce Issues
If you get nonce errors:
- Each order requires a unique nonce
- The client handles nonces automatically
- Don't reuse order objects
Environment Variables
Store credentials securely:
PRIVATE_KEY=0x1234567890abcdef...
CLOB_API_KEY=your-api-key
CLOB_SECRET=your-secret
CLOB_PASSPHRASE=your-passphrase
POLYGON_RPC_URL=https://polygon-rpc.com
import dotenv from "dotenv";
dotenv.config();
const creds: ApiKeyCreds = {
key: process.env.CLOB_API_KEY!,
secret: process.env.CLOB_SECRET!,
passphrase: process.env.CLOB_PASSPHRASE!
};
Resources