Production-grade Polymarket copy-trading bot optimized for always-on deployment on a Raspberry Pi 3.
Monitors 1–5 target wallets, copies BUY trades into your burner wallet, and stores everything in a local SQLite database. DRY_RUN=true by default — it never places real orders unless you explicitly opt in.
- Copy-trade any Polymarket wallet with configurable sizing (fixed USD or scaled)
- Exposure cap prevents runaway spending
- Market filter by keywords (only copy markets you care about)
- SQLite database for all state — no JSON files, no cloud dependencies
- Built-in web dashboard for LAN visibility (optional)
- CLI commands for status checks and ad-hoc DB queries
- Pi-optimized: WAL mode, infrequent writes, <300 MB total RAM
# Authenticate with GitHub Packages (one time)
echo "//npm.pkg.github.com/:_authToken=YOUR_GITHUB_PAT" >> ~/.npmrc
# Install globally
npm install -g @calebpanza/polymarket-botCreate a .env file in the directory where you'll run the bot:
cp .env.example .env
# Edit .env with your target wallets (PRIVATE_KEY is optional for dry-run)Minimal dry-run config — no private key needed:
TARGET_WALLETS=0xSomeWhaleAddress
DRY_RUN=true# Start in dry-run mode (default — safe, no real orders, no key needed)
polymarket-bot start
# Check status from another terminal
polymarket-bot status
# Query the database directly
polymarket-bot db-query "SELECT * FROM copied_trades ORDER BY timestamp DESC LIMIT 20"All configuration is via environment variables (loaded from .env):
| Variable | Default | Description |
|---|---|---|
PRIVATE_KEY |
'' |
Hex private key of your burner wallet (only required when DRY_RUN=false) |
TARGET_WALLETS |
required | Comma-separated wallet addresses to copy (1–5) |
POLL_INTERVAL_MS |
30000 |
Polling interval in milliseconds |
COPY_MODE |
fixed |
fixed = always copy FIXED_COPY_USD; scaled = originalSize * SCALE_FACTOR |
FIXED_COPY_USD |
4.00 |
Amount to copy per trade (fixed mode) |
SCALE_FACTOR |
0.10 |
Multiplier for original trade size (scaled mode) |
MIN_COPY_USD |
2.00 |
Minimum copy size (scaled mode floor) |
MAX_COPY_USD |
5.00 |
Maximum copy size (scaled mode ceiling) |
MAX_TOTAL_EXPOSURE_USD |
15.00 |
Total USD exposure cap across all positions |
FILTER_KEYWORDS |
election,politics,crypto,trump |
Only copy markets matching these keywords |
DRY_RUN |
true |
Safe default — logs trades but never places orders |
DB_PATH |
./bot-state.db |
Path to the SQLite database file |
ENABLE_WEB |
false |
Start the built-in web dashboard |
WEB_PORT |
3001 |
Dashboard port (only when ENABLE_WEB=true) |
CLOB_API_URL |
https://clob.polymarket.com |
Polymarket CLOB API endpoint |
DATA_API_URL |
https://data-api.polymarket.com |
Polymarket data API endpoint |
CHAIN_ID |
137 |
Polygon chain ID |
# Start the bot (foreground process)
polymarket-bot start
# Show current exposure and recent trades from the DB
polymarket-bot status
# Run any SQL query against the database
polymarket-bot db-query "SELECT * FROM copied_trades WHERE dry_run = 0"
polymarket-bot db-query "SELECT * FROM exposure_log ORDER BY timestamp DESC LIMIT 10"
polymarket-bot db-query "SELECT * FROM wallet_last_seen"Set ENABLE_WEB=true in your .env, then open your browser:
http://raspberrypi.local:3001
Or use your Pi's IP address: http://192.168.1.X:3001
The dashboard shows:
- Current exposure vs. cap
- Dry-run status
- Number of monitored wallets
- Uptime
- Last 50 copied trades in a table
The dashboard runs in the same process — adds <30 MB RAM on a Pi 3.
Pre-installed on most Linux systems, or install with:
sudo apt install sqlite3Then query directly:
# View recent trades
sqlite3 bot-state.db "SELECT * FROM copied_trades ORDER BY timestamp DESC LIMIT 20;"
# Check exposure
sqlite3 bot-state.db "SELECT SUM(copied_size) FROM copied_trades WHERE dry_run = 0;"
# See what the bot last saw per wallet
sqlite3 bot-state.db "SELECT * FROM wallet_last_seen;"
# Pretty-print with headers and columns
sqlite3 -header -column bot-state.db "SELECT timestamp, market, copied_size, dry_run FROM copied_trades ORDER BY timestamp DESC LIMIT 10;"Install on your Pi (if you have a desktop environment):
sudo apt install sqlitebrowserOr copy the DB file to your laptop:
scp pi@raspberrypi.local:~/bot-state.db ./Then open it in DB Browser for SQLite on macOS/Windows/Linux for a full graphical view.
polymarket-bot db-query "SELECT timestamp, market, copied_size, side, dry_run FROM copied_trades ORDER BY timestamp DESC LIMIT 20"You might be tempted to use Supabase or another hosted database. For a local $20 play-money bot on a Pi, this adds unnecessary complexity:
- Requires constant internet connectivity
- Adds network latency to every write
- External dependency and privacy concerns
- Free tier limits
If you later want remote access, consider PocketBase (single Go binary with SQLite + admin UI) or Turso (distributed SQLite). But for this use case, local SQLite + the built-in dashboard gives you perfect visibility while keeping everything offline-capable and lightweight.
| Column | Type | Description |
|---|---|---|
wallet |
TEXT PK | Target wallet address |
last_timestamp |
TEXT | ISO timestamp of newest processed activity |
| Column | Type | Description |
|---|---|---|
id |
INTEGER PK | Auto-increment ID |
timestamp |
TEXT | When the copy was executed |
source_wallet |
TEXT | Which target wallet this came from |
market |
TEXT | Market title |
token_id |
TEXT | Condition token ID |
original_size |
REAL | Original trade size in USD |
copied_size |
REAL | Copied trade size in USD |
side |
TEXT | Always "BUY" (sells are filtered out) |
dry_run |
INTEGER | 1 = dry run, 0 = live order placed |
pnl |
REAL | Profit/loss (reserved for future use) |
| Column | Type | Description |
|---|---|---|
id |
INTEGER PK | Auto-increment ID |
timestamp |
TEXT | When the snapshot was taken |
total_exposure |
REAL | Total USD exposure at that point |
# Install Node.js 22 LTS (if not already installed)
curl -fsSL https://deb.nodesource.com/setup_22.x | sudo -E bash -
sudo apt install -y nodejs
# Verify
node --version # v22.x
npm --version# Create a directory for the bot
mkdir ~/polymarket-bot && cd ~/polymarket-bot
# Create your .env file
nano .env # paste your config
# Install the bot globally
npm install -g @calebpanza/polymarket-bot
# Run in the foreground to verify
polymarket-bot start
# Or run as a systemd service (see below)Create /etc/systemd/system/polymarket-bot.service:
[Unit]
Description=Polymarket Copy-Trading Bot
After=network.target
[Service]
Type=simple
User=pi
WorkingDirectory=/home/pi/polymarket-bot
ExecStart=/usr/bin/polymarket-bot start
Restart=always
RestartSec=30
Environment=NODE_ENV=production
[Install]
WantedBy=multi-user.targetThen:
sudo systemctl daemon-reload
sudo systemctl enable polymarket-bot
sudo systemctl start polymarket-bot
# View logs
journalctl -u polymarket-bot -f- SD card: Use a quality card (SanDisk Extreme or better). WAL mode minimizes write amplification.
- RAM: The bot uses ~100–150 MB. Leave room for the OS (~150 MB). Avoid running a desktop environment.
- Swap: Add a small swap file if needed:
sudo fallocate -l 256M /swapfile && sudo chmod 600 /swapfile && sudo mkswap /swapfile && sudo swapon /swapfile - Overclocking: Not needed. The bot is I/O-bound (network polling), not CPU-bound.
This bot is designed for $20 of play money. Treat it accordingly.
- BURNER WALLET ONLY: Create a fresh wallet. Send $20–25 USDC and a small amount of POL for gas. Never use a wallet with significant funds.
- Never commit
.env: The.gitignorealready excludes it. - Never commit the DB file: It may contain trade data you don't want public.
- Web dashboard: Only binds to
0.0.0.0whenENABLE_WEB=true. This is LAN-only by default — don't expose port 3001 to the internet without additional security (reverse proxy, auth, etc.). - Private key handling: The key is read from the environment at startup and passed to ethers.js for signing. It's never logged or written to disk by the bot.
# Clone the repo
git clone https://github.com/calebpanza/polymarket-copybot.git
cd polymarket-copybot
# Install dependencies
npm install
# Run in dev mode
npm run dev -- start
# Run tests with coverage
npm test
# Build
npm run buildPrivate — not for redistribution.