Polymarket Copy Trading Bot
Skill by ara.so — Daily 2026 Skills collection.
A TypeScript bot that continuously monitors a target Polymarket wallet, detects trades in real time via REST polling and/or WebSocket, and mirrors BUY orders to your own account using the Polymarket CLOB SDK on Polygon mainnet.
What It Does
- Monitors a target wallet via Polymarket Data API (REST polling every ~2s) and optionally WebSocket
- Mirrors BUY trades only — SELL trades are detected but skipped by default
- Sizes copies using a configurable
POSITION_MULTIPLIER(default 10%) with min/max caps - Submits orders as FOK, FAK, or LIMIT via the Polymarket CLOB client
- Enforces risk limits — per-session and per-market notional caps
- Supports three auth modes — EOA (
SIG_TYPE=0), Poly Proxy (SIG_TYPE=1), Poly Polymorphic (SIG_TYPE=2)
Installation
git clone https://github.com/Neron888/Polymarket-copy-trading-bot.git
cd Polymarket-copy-trading-bot
npm install
Requires Node.js v18+. The CLOB SDK requires ethers v5 (already pinned in package.json).
Configuration
cp .env.example .env
Edit .env:
# Required
TARGET_WALLET=0xTargetWalletAddressToMonitor
WALLET_PRIVATE_KEY=0xYourPrivateKey
RPC_URL=https://your-quicknode-polygon-endpoint.quiknode.pro/your-key/
# Auth mode (0=EOA default, 1=Poly Proxy, 2=Poly Polymorphic)
SIG_TYPE=0
# Required only for SIG_TYPE=1 or 2
PROXY_WALLET_ADDRESS=
# Sizing
POSITION_MULTIPLIER=0.1 # 10% of target's trade size
MAX_TRADE_SIZE=100 # Max USDC per copied trade
MIN_TRADE_SIZE=1 # Min USDC per copied trade
# Order behavior
ORDER_TYPE=FOK # FOK | FAK | LIMIT
SLIPPAGE_TOLERANCE=0.02 # 2%
# Risk caps (0 = disabled)
MAX_SESSION_NOTIONAL=500 # Total USDC for entire session
MAX_PER_MARKET_NOTIONAL=100 # Per market USDC cap
# Monitoring
USE_WEBSOCKET=true
POLL_INTERVAL=2000 # ms between REST polls
USE_USER_CHANNEL=false # true = user WS channel, false = market channel
# Optional
POLYMARKET_GEO_TOKEN=
WS_ASSET_IDS= # comma-separated asset IDs for market WS
WS_MARKET_IDS= # comma-separated condition IDs for user channel
MIN_PRIORITY_FEE_GWEI=30
MIN_MAX_FEE_GWEI=60
Key Commands
# Start the bot (development mode with ts-node)
npm start
# Generate and persist API credentials to .polymarket-api-creds
npm run generate-api-creds
# Validate existing API credentials
npm run test-api-creds
# Compile TypeScript to dist/
npm run build
# Run compiled production build
npm run start:prod
Architecture Overview
index.ts
└── TradeMonitor — REST polls Polymarket Data API for new target trades
└── WebSocketMonitor — Optional low-latency WS subscription (market or user channel)
└── TradeExecutor — Sizes trade, checks balance/allowance, submits CLOB order
└── PositionTracker — In-memory positions updated on fills
└── RiskManager — Session + per-market notional enforcement
Execution flow:
detect trade → BUY? → subscribe WS if needed → compute copy size
→ risk check → execute order (FOK/FAK/LIMIT) → record fill → update stats
Code Examples
Starting the bot programmatically
import { startBot } from './src/index';
// The bot reads all config from process.env / .env
startBot();
TradeMonitor — polling pattern
import { TradeMonitor } from './src/TradeMonitor';
const monitor = new TradeMonitor({
targetWallet: process.env.TARGET_WALLET!,
pollInterval: Number(process.env.POLL_INTERVAL ?? 2000),
});
monitor.on('trade', (trade) => {
console.log('New trade detected:', trade);
// trade.side: 'BUY' | 'SELL'
// trade.asset: token ID (outcome token address)
// trade.size: USDC size
// trade.price: fill price (0–1)
});
monitor.start();
TradeExecutor — placing a copy order
import { TradeExecutor } from './src/TradeExecutor';
import { ClobClient } from '@polymarket/clob-client';
import { ethers } from 'ethers';
const provider = new ethers.providers.JsonRpcProvider(process.env.RPC_URL);
const signer = new ethers.Wallet(process.env.WALLET_PRIVATE_KEY!, provider);
const clobClient = new ClobClient(
'https://clob.polymarket.com',
137, // Polygon chainId
signer,
apiCreds, // loaded from .polymarket-api-creds
Number(process.env.SIG_TYPE ?? 0),
process.env.PROXY_WALLET_ADDRESS || undefined,
);
const executor = new TradeExecutor({
client: clobClient,
positionMultiplier: Number(process.env.POSITION_MULTIPLIER ?? 0.1),
maxTradeSize: Number(process.env.MAX_TRADE_SIZE ?? 100),
minTradeSize: Number(process.env.MIN_TRADE_SIZE ?? 1),
orderType: (process.env.ORDER_TYPE ?? 'FOK') as 'FOK' | 'FAK' | 'LIMIT',
slippageTolerance: Number(process.env.SLIPPAGE_TOLERANCE ?? 0.02),
});
// Copy a detected trade
await executor.copyTrade({
side: 'BUY',
tokenId: '0xAssetId...',
originalSize: 12.5, // USDC from target's trade
price: 0.62,
});
RiskManager — checking before execution
import { RiskManager } from './src/RiskManager';
const riskManager = new RiskManager({
maxSessionNotional: Number(process.env.MAX_SESSION_NOTIONAL ?? 0),
maxPerMarketNotional: Number(process.env.MAX_PER_MARKET_NOTIONAL ?? 0),
});
const allowed = riskManager.checkTrade({
marketId: '0xConditionId...',
notional: copySize,
});
if (!allowed) {
console.log('Trade blocked by risk limits');
}
WebSocket monitor — low-latency subscription
import { WebSocketMonitor } from './src/WebSocketMonitor';
const wsMonitor = new WebSocketMonitor({
useUserChannel: process.env.USE_USER_CHANNEL === 'true',
assetIds: process.env.WS_ASSET_IDS?.split(',').filter(Boolean) ?? [],
marketIds: process.env.WS_MARKET_IDS?.split(',').filter(Boolean) ?? [],
});
wsMonitor.on('orderFilled', (fill) => {
console.log('Fill received via WS:', fill);
});
wsMonitor.connect();
Authentication Modes
EOA (default — SIG_TYPE=0)
SIG_TYPE=0
WALLET_PRIVATE_KEY=0xYourKey
# PROXY_WALLET_ADDRESS — leave empty
On first run, the bot auto-submits USDC.e/CTF approval transactions. Wallet needs POL for gas.
Poly Proxy (SIG_TYPE=1)
SIG_TYPE=1
WALLET_PRIVATE_KEY=0xSignerKey
PROXY_WALLET_ADDRESS=0xYourPolymarketProxyAddress
Poly Polymorphic (SIG_TYPE=2)
SIG_TYPE=2
WALLET_PRIVATE_KEY=0xSignerKey
PROXY_WALLET_ADDRESS=0xYourPolymorphicSafeAddress
Generating API Credentials
npm run generate-api-creds
This derives API keys from your wallet signature and writes them to .polymarket-api-creds. Run once before npm start if you want to pre-generate credentials. The bot also auto-generates them on first start in EOA mode.
Validate credentials:
npm run test-api-creds
Common Patterns
Conservative testing setup
POSITION_MULTIPLIER=0.05
MAX_TRADE_SIZE=5
MIN_TRADE_SIZE=1
MAX_SESSION_NOTIONAL=20
ORDER_TYPE=FOK
USE_WEBSOCKET=false
WebSocket-only market channel (lower latency)
USE_WEBSOCKET=true
USE_USER_CHANNEL=false
WS_ASSET_IDS=0xTokenId1,0xTokenId2
LIMIT orders with slippage buffer
ORDER_TYPE=LIMIT
SLIPPAGE_TOLERANCE=0.03
Troubleshooting
Bot starts but no trades detected
- Verify
TARGET_WALLETis a valid Polymarket wallet with recent activity - Check
POLL_INTERVAL— default 2000ms; lower means more API calls - Confirm the target wallet trades on Polymarket (not just holds positions)
ethers version conflicts
- The project pins ethers v5. Do not upgrade to v6 — the CLOB SDK requires v5
- Run
npm ls ethersto check for duplicate versions
Approval transactions failing
- Ensure the wallet has sufficient POL (MATIC) for gas on Polygon mainnet
- Try increasing
MIN_PRIORITY_FEE_GWEIandMIN_MAX_FEE_GWEIif transactions stall
generate-api-creds fails
- Confirm
WALLET_PRIVATE_KEYis correct and 0x-prefixed - For
SIG_TYPE=1/2, ensurePROXY_WALLET_ADDRESSmatches your Polymarket account
Orders rejected by CLOB
- FOK orders fail if insufficient liquidity — try
ORDER_TYPE=FAKorLIMIT - Check
SLIPPAGE_TOLERANCEisn't too tight for illiquid markets
Session notional cap hit immediately
MAX_SESSION_NOTIONALresets per process run; restart the bot to reset- Set
MAX_SESSION_NOTIONAL=0to disable the cap entirely
Wallet Requirements
- USDC.e on Polygon mainnet (collateral for trades)
- POL (formerly MATIC) for gas fees
- Approve USDC.e for Polymarket CTF Exchange and CLOB contracts (bot does this automatically on first EOA run)