Home

/

Library

/

polymarket_setup.md

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

  1. Understanding the Architecture
  2. Prerequisites
  3. High-Level Overview
  4. Step-by-Step Setup
  5. Verification Scripts
  6. Common Issues & Solutions
  7. 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

  1. USDC.e (bridged USDC) - for trading
    • Contract: 0x2791Bca1f2de4661ED88A30C99A7a9449Aa84174
    • This is the OLD USDC that Polymarket uses
  2. 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

  1. Open MetaMask
  2. Click on your account name at the top
  3. Your address is shown (42 characters starting with 0x)
  4. Example: 0xb99107501dFFd0F165782f92133048875053Dc5a

1.2 Export Your Private Key

⚠️ SECURITY WARNING: Never share your private key! Keep it secure!

  1. In MetaMask, click the three dots (⋮)
  2. Account details
  3. Show private key
  4. Enter your MetaMask password
  5. Copy the private key (64 hex characters + 0x)
  6. 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)

  1. 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
  2. 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!)

  1. Buy USDC on any exchange (Coinbase, Kraken, Binance)
  2. Withdraw to Polygon network (very important!)
    • Network: Polygon (NOT Ethereum!)
    • Address: 0xb99107501d... (your MetaMask address)
    • Token: USDC
  3. Wait for withdrawal (usually 5-10 minutes)
  4. 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:

  1. In MetaMask, find your USDC
  2. Click "Swap"
  3. From: USDC → To: USDC.e
  4. 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:

  1. ✅ Set signature_type: 0 (not 1) for EOA wallets
  2. ✅ Ensure maker and signer both use checksummed address
  3. ✅ Verify you're using the same private key that generated API credentials

Issue: "not enough balance / allowance"

Causes:

  1. Wrong USDC token (need USDC.e, not native USDC)
  2. Allowances not set
  3. Multiple concurrent orders exhausting balance

Solutions:

  1. ✅ Swap to USDC.e (0x2791Bca...) if you have native USDC
  2. ✅ Run set_allowances.py again
  3. ✅ 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:

  1. ✅ Use better User-Agent headers (see Step 5.2)
  2. ✅ Add delays between requests
  3. ✅ 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

  1. Never share your private key - Store in .env, add to .gitignore
  2. Use a dedicated trading wallet - Don't use your main wallet
  3. Start with small amounts - Test with $10-20 first