Polymarket CLOB API Trading Setup Guide
Polymarket CLOB API Trading Setup Guide
A complete guide to setting up automated trading on Polymarket using the CLOB (Central Limit Order Book) API with an EOA (Externally Owned Account) Metamask wallet.
Table of Contents
- Understanding the Architecture
- Prerequisites
- High-Level Overview
- Step-by-Step Setup
- Verification Scripts
- Common Issues & Solutions
- Reference
Understanding the Architecture
Account Structure
┌─────────────────────────────────────────────────────────┐
│ YOUR METAMASK EOA (Externally Owned Account) │
├─────────────────────────────────────────────────────────┤
│ Private Key: 0x1a4aaa20c0... │
│ Address: 0xb99107501d... │
│ Purpose: Signs orders, holds funds │
│ Type: Simple wallet you control directly │
└─────────────────────────────────────────────────────────┘
│
│ For API trading
▼
┌─────────────────────────────────────────────────────────┐
│ POLYMARKET CLOB API │
├─────────────────────────────────────────────────────────┤
│ Uses your EOA to: │
│ - Sign EIP-712 order messages │
│ - Execute trades directly from your wallet │
│ - Hold outcome tokens in your wallet │
│ - No deposit/withdrawal needed │
└─────────────────────────────────────────────────────────┘
vs.
┌─────────────────────────────────────────────────────────┐
│ POLYMARKET WEBSITE (Smart Contract Wallet) │
├─────────────────────────────────────────────────────────┤
│ Profile Address: 0x7f2D93f56d... (proxy contract) │
│ Deposit Address: 0xC514209f23... (deposit contract) │
│ Purpose: Website UI trading only │
│ Note: Separate from API trading! │
└─────────────────────────────────────────────────────────┘
Key Insight: API trading uses your EOA wallet directly. The Polymarket website uses a separate smart contract wallet system. These are completely independent!
Prerequisites
Required Tools
- MetaMask browser extension or mobile app
- Python 3.8+ (for setup scripts)
- Rust (for the trading bot)
- Some ETH on Ethereum mainnet or another chain (for bridging to Polygon)
Required Tokens on Polygon
- USDC.e (bridged USDC) - for trading
- Contract:
0x2791Bca1f2de4661ED88A30C99A7a9449Aa84174 - This is the OLD USDC that Polymarket uses
- Contract:
- MATIC/POL - for gas fees
- Need ~0.1 MATIC minimum (gas is very cheap on Polygon)
High-Level Overview
The Complete Flow
1. Get MetaMask wallet address and private key
└─> 0xb99107501d... (your address)
└─> 0x1a4aaa20c0... (your private key)
2. Fund your wallet on Polygon network
├─> Bridge ETH → Polygon → swap to USDC.e
├─> Or buy USDC directly and withdraw to Polygon
└─> Get free MATIC from faucet
3. Approve Polymarket contracts to spend your USDC.e
└─> One-time setup per exchange contract
4. Register your wallet with Polymarket API
└─> Derives API credentials for authentication
5. Start trading!
└─> Place orders directly from your wallet
Step-by-Step Setup
Step 1: Get Your MetaMask Credentials
1.1 Get Your Wallet Address
- Open MetaMask
- Click on your account name at the top
- Your address is shown (42 characters starting with
0x) - Example:
0xb99107501dFFd0F165782f92133048875053Dc5a
1.2 Export Your Private Key
⚠️ SECURITY WARNING: Never share your private key! Keep it secure!
- In MetaMask, click the three dots (⋮)
- Account details
- Show private key
- Enter your MetaMask password
- Copy the private key (64 hex characters +
0x) - Example:
0x1a4aaa20c0...
1.3 Create Your .env File
# Polymarket Trading Credentials
POLY_PRIVATE_KEY=0x1a4aaa20c0... # Your MetaMask private key
POLY_FUNDER=0xb99107501d... # Your MetaMask address (same wallet)
# Polygon RPC (optional - uses public RPC by default)
POLYGON_RPC=https://polygon-rpc.com
Step 2: Fund Your Polygon Wallet
You need USDC.e (not regular USDC!) on the Polygon network.
Option A: Bridge from Ethereum (if you have ETH/USDC on mainnet)
-
Bridge USDC:
- Go to https://portal.polygon.technology/bridge
- Connect MetaMask
- Select: Ethereum → Polygon PoS
- Bridge USDC (it will become USDC.e on Polygon)
- Wait 7-8 minutes for confirmation
-
Get MATIC for gas:
- Option 1: Free faucet - https://faucet.polygon.technology/
- Option 2: Bridge a tiny amount of ETH and swap for MATIC
Option B: Buy on Exchange (Recommended - Fastest!)
- Buy USDC on any exchange (Coinbase, Kraken, Binance)
- Withdraw to Polygon network (very important!)
- Network: Polygon (NOT Ethereum!)
- Address:
0xb99107501d...(your MetaMask address) - Token: USDC
- Wait for withdrawal (usually 5-10 minutes)
- It will arrive as USDC.e automatically
Option C: Swap Your Existing USDC → USDC.e
If you have the new native USDC (0x3c499c542c...) on Polygon:
- In MetaMask, find your USDC
- Click "Swap"
- From: USDC → To: USDC.e
- Execute swap (1:1 ratio, minimal fees)
2.1 Verify Your Balances
Save this as check_balance.py:
#!/usr/bin/env python3
from web3 import Web3
# Connect to Polygon
w3 = Web3(Web3.HTTPProvider("https://polygon-rpc.com"))
print("✅ Connected to Polygon\n")
# Your wallet address
address = Web3.to_checksum_address("0xb99107501d...")
print(f"📍 Checking balances for: {address}\n")
# Check MATIC balance
matic_balance = w3.eth.get_balance(address)
matic_readable = w3.from_wei(matic_balance, 'ether')
print(f"💎 MATIC: {matic_readable} MATIC")
if float(matic_readable) < 0.01:
print(" ⚠️ Low MATIC! Get some for gas fees.")
else:
print(" ✅ Enough MATIC for gas")
# Check USDC.e balance (OLD USDC that Polymarket uses)
USDC_E = Web3.to_checksum_address("0x2791Bca1f2de4661ED88A30C99A7a9449Aa84174")
abi = [{"constant":True,"inputs":[{"name":"","type":"address"}],"name":"balanceOf","outputs":[{"name":"","type":"uint256"}],"type":"function"}]
usdc = w3.eth.contract(address=USDC_E, abi=abi)
usdc_balance = usdc.functions.balanceOf(address).call()
usdc_readable = usdc_balance / 1e6 # USDC has 6 decimals
print(f"💵 USDC.e: {usdc_readable} USDC")
if usdc_readable < 1:
print(" ⚠️ Low/no USDC.e! You need this to trade.")
else:
print(" ✅ You have USDC.e for trading")
print("\n" + "="*50)
if float(matic_readable) >= 0.01 and usdc_readable >= 1:
print("✅ Ready for next step! Run the allowance script.")
else:
print("⚠️ Add funds before continuing.")
print("="*50)
Run it:
python check_balance.py
Expected output:
✅ Connected to Polygon
📍 Checking balances for: 0xb99107501d...
💎 MATIC: 0.15 MATIC
✅ Enough MATIC for gas
💵 USDC.e: 50.0 USDC
✅ You have USDC.e for trading
==================================================
✅ Ready for next step! Run the allowance script.
==================================================
Step 3: Set Token Allowances
Before trading, you must approve Polymarket's exchange contracts to spend your USDC.e. This is a one-time setup.
3.1 Install Dependencies
pip install web3 python-dotenv
3.2 Create Allowance Script
Save this as set_allowances.py:
#!/usr/bin/env python3
"""
Set token allowances for Polymarket trading.
This approves the exchange contracts to spend your USDC.e.
"""
import os
from web3 import Web3
from dotenv import load_dotenv
load_dotenv()
# Polygon RPC
POLYGON_RPC = os.getenv("POLYGON_RPC", "https://polygon-rpc.com")
# Contract addresses (checksummed)
USDC_E = Web3.to_checksum_address("0x2791Bca1f2de4661ED88A30C99A7a9449Aa84174")
CTF_ADDRESS = Web3.to_checksum_address("0x4D97DCd97eC945f40cF65F87097ACe5EA0476045")
CTF_EXCHANGE = Web3.to_checksum_address("0x4bFb41d5B3570DeFd03C39a9A4D8dE6Bd8B8982E")
CTF_NEG_RISK = Web3.to_checksum_address("0xC5d563A36AE78145C45a50134d48A1215220f80a")
# Your private key
PRIVATE_KEY = os.getenv("POLY_PRIVATE_KEY")
if not PRIVATE_KEY:
raise ValueError("POLY_PRIVATE_KEY not set in .env")
if PRIVATE_KEY.startswith("0x"):
PRIVATE_KEY = PRIVATE_KEY[2:]
# Connect
w3 = Web3(Web3.HTTPProvider(POLYGON_RPC))
if not w3.is_connected():
raise ConnectionError("Failed to connect to Polygon")
print(f"✅ Connected to Polygon (Chain ID: {w3.eth.chain_id})")
# Load account
account = w3.eth.account.from_key(PRIVATE_KEY)
print(f"🔑 Wallet: {account.address}")
# Check MATIC
matic = w3.eth.get_balance(account.address)
print(f"💰 MATIC: {w3.from_wei(matic, 'ether')} MATIC")
if matic < w3.to_wei(0.01, 'ether'):
print("⚠️ Low MATIC! You need MATIC for gas.")
# ERC20 ABI
ERC20_ABI = [
{"constant":False,"inputs":[{"name":"_spender","type":"address"},{"name":"_value","type":"uint256"}],"name":"approve","outputs":[{"name":"","type":"bool"}],"type":"function"},
{"constant":True,"inputs":[{"name":"_owner","type":"address"},{"name":"_spender","type":"address"}],"name":"allowance","outputs":[{"name":"","type":"uint256"}],"type":"function"},
{"constant":True,"inputs":[{"name":"_owner","type":"address"}],"name":"balanceOf","outputs":[{"name":"balance","type":"uint256"}],"type":"function"}
]
MAX_UINT256 = 2**256 - 1
def check_allowance(token_addr, spender_addr, name):
token = w3.eth.contract(address=token_addr, abi=ERC20_ABI)
allowance = token.functions.allowance(account.address, spender_addr).call()
print(f" Current {name} allowance: {allowance}")
return allowance
def check_balance(token_addr, name, decimals=6):
token = w3.eth.contract(address=token_addr, abi=ERC20_ABI)
balance = token.functions.balanceOf(account.address).call()
readable = balance / (10 ** decimals)
print(f" {name} balance: {readable}")
return balance
def set_allowance(token_addr, spender_addr, name):
token = w3.eth.contract(address=token_addr, abi=ERC20_ABI)
nonce = w3.eth.get_transaction_count(account.address)
txn = token.functions.approve(spender_addr, MAX_UINT256).build_transaction({
'from': account.address,
'nonce': nonce,
'gas': 100000,
'maxFeePerGas': w3.eth.gas_price * 2,
'maxPriorityFeePerGas': w3.to_wei(30, 'gwei'),
'chainId': 137
})
signed = account.sign_transaction(txn)
print(f" Sending approval for {name}...")
tx_hash = w3.eth.send_raw_transaction(signed.raw_transaction)
print(f" TX: {tx_hash.hex()}")
print(f" Waiting for confirmation...")
receipt = w3.eth.wait_for_transaction_receipt(tx_hash, timeout=120)
if receipt['status'] == 1:
print(f" ✅ {name} approved!")
return True
else:
print(f" ❌ {name} approval failed!")
return False
print("\n" + "="*60)
print("Polymarket Token Allowance Setup")
print("="*60 + "\n")
# Check USDC.e balance
print("📊 Checking balances...")
usdc_balance = check_balance(USDC_E, "USDC.e", decimals=6)
if usdc_balance == 0:
print("⚠️ You have 0 USDC.e! Add funds first.")
exit(1)
print("\n" + "-"*60)
print("Standard Exchange Allowances")
print("-"*60 + "\n")
# USDC.e for standard exchange
print("1️⃣ USDC.e → CTF Exchange...")
allowance = check_allowance(USDC_E, CTF_EXCHANGE, "USDC.e")
if allowance < MAX_UINT256 / 2:
response = input(" Approve? (yes/no): ")
if response.lower() in ['yes', 'y']:
set_allowance(USDC_E, CTF_EXCHANGE, "USDC.e")
else:
print(" ✅ Already approved!")
# CTF tokens (skip if you don't have any yet)
print("\n2️⃣ CTF → CTF Exchange...")
try:
allowance = check_allowance(CTF_ADDRESS, CTF_EXCHANGE, "CTF")
if allowance < MAX_UINT256 / 2:
response = input(" Approve? (yes/no): ")
if response.lower() in ['yes', 'y']:
set_allowance(CTF_ADDRESS, CTF_EXCHANGE, "CTF")
else:
print(" ✅ Already approved!")
except:
print(" ⚠️ No CTF tokens yet (normal for first time)")
print("\n" + "-"*60)
print("Neg-Risk Exchange Allowances")
print("-"*60 + "\n")
# USDC.e for neg-risk exchange
print("3️⃣ USDC.e → Neg-Risk Exchange...")
allowance = check_allowance(USDC_E, CTF_NEG_RISK, "USDC.e")
if allowance < MAX_UINT256 / 2:
response = input(" Approve? (yes/no): ")
if response.lower() in ['yes', 'y']:
set_allowance(USDC_E, CTF_NEG_RISK, "USDC.e")
else:
print(" ✅ Already approved!")
print("\n" + "="*60)
print("✅ Allowances complete!")
print("="*60)
print("\nYou can now trade on Polymarket! 🎉")
3.3 Run the Allowance Script
python set_allowances.py
You'll be prompted to approve each contract. Type yes and confirm the transactions in MetaMask (or they'll auto-confirm if using private key).
Expected output:
✅ Connected to Polygon (Chain ID: 137)
🔑 Wallet: 0xb99107501d...
💰 MATIC: 0.15 MATIC
============================================================
Polymarket Token Allowance Setup
============================================================
📊 Checking balances...
USDC.e balance: 50.0
------------------------------------------------------------
Standard Exchange Allowances
------------------------------------------------------------
1️⃣ USDC.e → CTF Exchange...
Current USDC.e allowance: 0
Approve? (yes/no): yes
Sending approval for USDC.e...
TX: 0xabcd1234...
Waiting for confirmation...
✅ USDC.e approved!
[... continues for other approvals ...]
============================================================
✅ Allowances complete!
============================================================
You can now trade on Polymarket! 🎉
Step 4: Generate API Credentials
Polymarket's API requires authenticated credentials tied to your wallet.
4.1 Install py-clob-client
# Create virtual environment (recommended)
python -m venv poly-env
source poly-env/bin/activate # On Windows: poly-env\Scripts\activate
# Install client
pip install py-clob-client
4.2 Create Credential Generation Script
Save as get_poly_creds.py:
#!/usr/bin/env python3
"""
Generate Polymarket API credentials for your wallet.
"""
from py_clob_client.client import ClobClient
import json
import os
from dotenv import load_dotenv
load_dotenv()
# Your MetaMask private key (without 0x)
private_key = os.getenv("POLY_PRIVATE_KEY", "").replace("0x", "")
if not private_key:
print("❌ POLY_PRIVATE_KEY not set in .env")
exit(1)
print("🔗 Connecting to Polymarket CLOB...")
host = "https://clob.polymarket.com"
chain_id = 137
try:
client = ClobClient(
host=host,
key=private_key,
chain_id=chain_id,
)
print("✅ Connected!")
print(f"📍 Your address: {client.get_address()}")
print("\n🔑 Deriving API credentials...")
creds = client.create_or_derive_api_creds()
print("\n" + "="*60)
print("SUCCESS! Your Polymarket API Credentials:")
print("="*60)
print(f"API Key: {creds.api_key}")
print(f"API Secret: {creds.api_secret}")
print(f"API Passphrase: {creds.api_passphrase}")
print("="*60)
# Save to file
creds_data = {
"apiKey": creds.api_key,
"secret": creds.api_secret,
"passphrase": creds.api_passphrase
}
with open("poly_api_creds.json", "w") as f:
json.dump(creds_data, f, indent=2)
print("\n✅ Credentials saved to: poly_api_creds.json")
print("\n📝 Use these in your Rust code with PreparedCreds::from_api_creds()")
except Exception as e:
print(f"\n❌ Error: {e}")
print("\nTroubleshooting:")
print(" - Make sure POLY_PRIVATE_KEY is correct")
print(" - Wait a few minutes if you just connected MetaMask to polymarket.com")
import traceback
traceback.print_exc()
4.3 Generate Credentials
python get_poly_creds.py
Expected output:
🔗 Connecting to Polymarket CLOB...
✅ Connected!
📍 Your address: 0xb99107501d...
🔑 Deriving API credentials...
============================================================
SUCCESS! Your Polymarket API Credentials:
============================================================
API Key: c1e38b3e-1f...
API Secret: zdKjVch284...
API Passphrase: 9133fa637d...
============================================================
✅ Credentials saved to: poly_api_creds.json
Important: These credentials are tied to your wallet. Keep them secure! They bypass the signature step.
Step 5: Configure Your Trading Bot
5.1 Load API Credentials in Rust
In your main.rs:
use std::sync::Arc;
use polymarket_clob::{PolymarketAsyncClient, ApiCreds, PreparedCreds, SharedAsyncClient};
#[tokio::main]
async fn main() -> Result<()> {
// Load environment
dotenvy::dotenv().ok();
let poly_private_key = std::env::var("POLY_PRIVATE_KEY")?;
let poly_funder = std::env::var("POLY_FUNDER")?;
// Create client
let poly_client = PolymarketAsyncClient::new(
"https://clob.polymarket.com",
137, // Polygon chain ID
&poly_private_key,
&poly_funder,
)?;
// Load API credentials from file
let creds_json = std::fs::read_to_string("poly_api_creds.json")?;
let api_creds: ApiCreds = serde_json::from_str(&creds_json)?;
let prepared_creds = PreparedCreds::from_api_creds(&api_creds)?;
// Create shared client for trading
let poly_async = Arc::new(SharedAsyncClient::new(
poly_client,
prepared_creds,
137
));
// Start trading!
info!("🚀 Ready to trade on Polymarket!");
Ok(())
}
5.2 Set Signature Type for EOA
CRITICAL: In your order building code, set signature_type: 0 for EOA wallets:
let data = OrderData {
maker: &self.inner.wallet_address_str,
signer: &self.inner.wallet_address_str,
signature_type: 0, // ← 0 for EOA, 1 for smart contract wallets
// ... other fields
};
Verification Scripts
Check Current Balance
python check_balance.py
Verify Allowances
from web3 import Web3
w3 = Web3(Web3.HTTPProvider("https://polygon-rpc.com"))
USDC_E = Web3.to_checksum_address("0x2791Bca1f2de4661ED88A30C99A7a9449Aa84174")
CTF_EXCHANGE = Web3.to_checksum_address("0x4bFb41d5B3570DeFd03C39a9A4D8dE6Bd8B8982E")
YOUR_ADDRESS = Web3.to_checksum_address("0xb99107501d...")
abi = [{"constant":True,"inputs":[{"name":"_owner","type":"address"},{"name":"_spender","type":"address"}],"name":"allowance","outputs":[{"name":"","type":"uint256"}],"type":"function"}]
usdc = w3.eth.contract(address=USDC_E, abi=abi)
allowance = usdc.functions.allowance(YOUR_ADDRESS, CTF_EXCHANGE).call()
if allowance > 10**20:
print("✅ Allowance is set (unlimited)")
else:
print(f"⚠️ Allowance: {allowance} (should be very large)")
View Your Outcome Tokens
Check Polygonscan:
https://polygonscan.com/address/0xb99107501d...#tokentxnsErc1155
Or check on Polymarket:
https://polymarket.com → Connect MetaMask → Portfolio
Common Issues & Solutions
Issue: "invalid signature"
Cause: Wrong signature type or address mismatch
Solutions:
- ✅ Set
signature_type: 0(not 1) for EOA wallets - ✅ Ensure
makerandsignerboth use checksummed address - ✅ Verify you're using the same private key that generated API credentials
Issue: "not enough balance / allowance"
Causes:
- Wrong USDC token (need USDC.e, not native USDC)
- Allowances not set
- Multiple concurrent orders exhausting balance
Solutions:
- ✅ Swap to USDC.e (
0x2791Bca...) if you have native USDC - ✅ Run
set_allowances.pyagain - ✅ Reduce position sizes or add exposure limits
Issue: "Polymarket order failed 400: invalid amount for a marketable BUY order, min size: $1"
Cause: Polymarket requires minimum $1.00 order value
Solution:
// Before placing order
let order_value = (contracts as f64) * price;
if order_value < 1.0 {
return Err(anyhow!("Order ${:.2} < $1.00 minimum", order_value));
}
Issue: Cloudflare 403 Forbidden
Cause: Bot-like request patterns
Solutions:
- ✅ Use better User-Agent headers (see Step 5.2)
- ✅ Add delays between requests
- ✅ Use a VPN if rate-limited
Issue: Can't see my trades in MetaMask
Explanation: Polymarket uses off-chain order matching. You won't see MetaMask transactions!
How to verify:
- Check Polymarket portfolio: polymarket.com → Portfolio
- Check ERC-1155 tokens on Polygonscan
- Look for outcome tokens in your wallet
Reference
Key Addresses (Polygon Mainnet)
| Contract | Address | Purpose |
|----------|---------|---------|
| USDC.e (old) | 0x2791Bca1f2de4661ED88A30C99A7a9449Aa84174 | Trading token |
| CTF Exchange | 0x4bFb41d5B3570DeFd03C39a9A4D8dE6Bd8B8982E | Standard markets |
| Neg-Risk Exchange | 0xC5d563A36AE78145C45a50134d48A1215220f80a | Neg-risk markets |
| CTF Token | 0x4D97DCd97eC945f40cF65F87097ACe5EA0476045 | Outcome tokens |
Signature Types
| Type | Value | Use Case |
|------|-------|----------|
| EOA | 0 | MetaMask/hardware wallets |
| POLY_PROXY | 1 | Polymarket smart contract wallets |
| POLY_GNOSIS_SAFE | 2 | Gnosis Safe wallets |
Order Types
| Type | Description | |------|-------------| | GTC | Good-til-cancelled (stays open) | | FOK | Fill-or-kill (must fill completely) | | FAK | Fill-and-kill (fill what you can, cancel rest) | | GTD | Good-til-date (expires at time) |
Useful Links
- Polymarket Docs: https://docs.polymarket.com
- CLOB API: https://docs.polymarket.com/developers/CLOB
- Polygon Bridge: https://portal.polygon.technology/bridge
- Polygon Faucet: https://faucet.polygon.technology
- Polygonscan: https://polygonscan.com
Security Best Practices
- Never share your private key - Store in
.env, add to.gitignore - Use a dedicated trading wallet - Don't use your main wallet
- Start with small amounts - Test with $10-20 first