# AGENT.md — Polymarket Terminal Developer and AI agent reference for understanding, extending, and maintaining this project. --- ## Project Overview **Polymarket Terminal** is a Node.js (ESM) automated trading terminal for [Polymarket](https://polymarket.com). It provides three independent bots: | Bot | Entry Point | Command | Purpose | |---|---|---|---| | Copy Trade | `src/index.js` | `npm start` | Mirror trades from a target trader wallet | | Market Maker | `src/mm.js` | `npm run mm` | Provide liquidity on 5m/15m binary markets | | Orderbook Sniper | `src/sniper.js` | `npm run sniper` | Place low-price GTC orders to catch panic dumps | --- ## Setting Up the Project ### 1. Prerequisites - **Node.js v18+** (project uses `"type": "module"` — native ESM, no Babel/transpilation) - **npm** (comes with Node.js) - A Polygon EOA wallet with: - A small MATIC balance (for gas) - USDC.e deposited via Polymarket's proxy wallet ### 2. Install Dependencies ```bash npm install ``` Key packages: | Package | Version | Role | |---|---|---| | `@polymarket/clob-client` | ^4.7.3 | Official Polymarket CLOB SDK (wraps ethers v5) | | `ethers` | ^5.x | Wallet signing, contract calls (pinned to v5 — do not upgrade to v6) | | `dotenv` | ^16.x | `.env` loading | | `blessed` | ^0.1.81 | Terminal dashboard UI | | `ws` | ^8.x | WebSocket client for real-time trade feed | | `nodemon` | ^3.x (dev) | Auto-reload during development | ### 3. Environment Configuration ```bash cp .env.example .env ``` Edit `.env` and fill in all required fields. Refer to `.env.example` for detailed comments on each variable. The minimum required fields for the copy trade bot are: ``` PRIVATE_KEY=0x... PROXY_WALLET_ADDRESS=0x... TRADER_ADDRESS=0x... ``` ### 4. Run in Simulation Mode First ```bash # Copy trade simulation DRY_RUN=true npm start # Market maker simulation npm run mm-sim # Sniper simulation npm run sniper-sim ``` ### 5. Run Live ```bash # Set DRY_RUN=false in .env, then: npm start # copy trade npm run mm # market maker npm run sniper # sniper ``` --- ## Architecture ### Module System All files use **ES Modules** (`import`/`export`). Do not use `require()`. ### Configuration (`src/config/index.js`) - Loads all settings from `.env` via `dotenv` - Parses and validates required fields per bot type - Exports two validator functions: - `validateCopyTradeConfig()` — for `src/index.js` - `validateMMConfig()` — for `src/mm.js` and `src/sniper.js` - Key exported fields: `privateKey`, `proxyWallet`, `traderAddress`, `dryRun`, `mmTradeSize`, etc. ### Client (`src/services/client.js`) - `initClient()` — Initializes `ClobClient` from `@polymarket/clob-client` - Uses **signature type 2** (`POLY_PROXY`) — EOA signs on behalf of the proxy wallet - Auto-derives API credentials if not provided in `.env` - `getClient()` / `getSigner()` — Singleton getters (call `initClient()` first) - `getPolygonProvider()` — Returns a `JsonRpcProvider` for Polygon mainnet - `getUsdcBalance()` — Reads USDC.e balance from the ERC-20 contract ### Watcher (`src/services/watcher.js`) - **Poll-based** fallback: queries `data-api.polymarket.com/activity?user={address}` - Deduplicates processed trade IDs via `data/processed_trades.json` (max 500 entries) - `fetchMarketInfo(conditionId)` — Fetches market metadata from Gamma API - `fetchMarketByTokenId(tokenId)` — Reverse-lookup market by token ### WebSocket Watcher (`src/services/wsWatcher.js`) - Connects to Polymarket RTDS: `wss://ws-live-data.polymarket.com` - Subscribes to the `user` channel for the target trader address - Filters for `trade` type events matching `MATCHED` status - Auto-reconnects with exponential backoff on disconnect ### Executor (`src/services/executor.js`) - `calculateTradeSize(traderFillSize)` — Computes our order size: - `percentage` mode: `SIZE_PERCENT% of MAX_POSITION_SIZE` - `balance` mode: `SIZE_PERCENT% of current USDC.e balance` - `executeBuy(tokenId, size, price)` — FOK market buy with retry loop - `executeSell(tokenId, shares, mode, price)` — Market or limit sell - `getMarketOptions(tokenId)` — Fetches tick size and `negRisk` flag from CLOB ### Position Manager (`src/services/position.js`) - CRUD operations on `data/positions.json` - Key structure: `{ [conditionId]: { tokenId, shares, avgBuyPrice, autoSellOrderId, ... } }` - Enforces **one position per conditionId** — prevents double entry ### Auto Sell (`src/services/autoSell.js`) - Called after a buy fills successfully - Places a GTC limit SELL at `avgBuyPrice × (1 + AUTO_SELL_PROFIT_PERCENT / 100)` - Price is rounded to the market's valid tick size and clamped to $0.01–$0.99 ### Redeemer (`src/services/redeemer.js`) - `checkMarketResolution(conditionId)` — Queries Gamma API for resolved status - `checkOnChainPayout(conditionId)` — Reads `payoutDenominator` and `payoutNumerators` from CTF contract - `redeemPosition(conditionId, tokenId, shares)` — Calls `redeemPositions` on CTF via CLOB client - `simulateRedeem(...)` — Tracks wins/losses in `data/sim_stats.json` during dry-run ### CTF Contract Helpers (`src/services/ctf.js`) Used exclusively by the Market Maker bot. Interacts with the Gnosis Safe proxy wallet. - `splitPosition(conditionId, amountUsdc)` — Deposits USDC → mints equal YES+NO tokens at $0.50 each - `mergePositions(conditionId, sharesPerSide)` — Burns equal YES+NO → recovers USDC (no-slippage cut-loss) - `cleanupOpenPositions(clobClient)` — On startup, cancels open orders and merges any leftover positions - `redeemMMPositions()` — Periodic redeemer for resolved markets held by the proxy wallet - `execSafeCall(to, data, description)` — Queued Safe transaction executor (serialized to avoid nonce collisions) ### Market Maker Detector (`src/services/mmDetector.js`) - Deterministic slot-based lookup using slug format: `{asset}-updown-{duration}-{timestamp}` - Polls Gamma API every `MM_POLL_INTERVAL` seconds - Targets the **next upcoming** slot (skips the currently active one) - Tracks seen market IDs to avoid re-entry ### Market Maker Executor (`src/services/mmExecutor.js`) - `executeMMStrategy(market)` — Full flow: split → place limit sells → monitor → cut-loss/redeem - `monitorAndManage(...)` — Watches for fill events; triggers cut-loss when `MM_CUT_LOSS_TIME` is reached - `cutLoss(...)` — Cancels remaining orders; merges back or market-sells residual tokens - `attemptRecoveryBuy(...)` — Optional directional bet post cut-loss (controlled by `MM_RECOVERY_BUY`) ### Sniper Detector (`src/services/sniperDetector.js`) - Scans for 5-minute markets for a configurable list of assets - Checks both current and next upcoming slots - Skips markets with fewer than 30 seconds remaining ### Sniper Executor (`src/services/sniperExecutor.js`) - `executeSnipe(market)` — Places two GTC BUY orders (one YES, one NO) at `SNIPER_PRICE` - Tracks placed orders in memory for display in the terminal UI - Total cost per market = `SNIPER_PRICE × SNIPER_SHARES × 2` ### Dashboard UI (`src/ui/dashboard.js`) - Built with `blessed` — two-panel terminal layout - Left panel: event log (color-coded, auto-scrolling) - Right panel: live positions, balance, and bot status - Handles Ctrl+C and `q` for graceful shutdown ### Logger (`src/utils/logger.js`) - `logger.info()`, `logger.success()`, `logger.warn()`, `logger.error()` - `logger.trade()`, `logger.watch()`, `logger.money()` - Outputs to console (with ANSI colors) or blessed dashboard markup ### State (`src/utils/state.js`) - `readState(filename)` — Read a JSON file from `data/`; returns `{}` if missing - `writeState(filename, data)` — Atomic write (temp file + rename) to prevent partial writes --- ## Key API Endpoints | API | Base URL | Auth | Purpose | |---|---|---|---| | Gamma API | `https://gamma-api.polymarket.com` | None | Market metadata, resolution status | | Data API | `https://data-api.polymarket.com` | None | Trader activity, wallet positions | | CLOB API | `https://clob.polymarket.com` | L2 ECDSA | Place, cancel, and query orders | | RTDS WebSocket | `wss://ws-live-data.polymarket.com` | None | Real-time trade event stream | | Polygon RPC | Configurable via `POLYGON_RPC_URL` | None | On-chain reads and Safe tx submission | --- ## Contract Addresses (Polygon Mainnet) | Contract | Address | |---|---| | CTF (ConditionalTokens) | `0x4D97DCd97eC945f40cF65F87097ACe5EA0476045` | | Neg Risk CTF | `0xC5d563A36AE78145C45a50134d48A1215220f80a` | | USDC.e | `0x2791Bca1f2de4661ED88A30C99A7a9449Aa84174` | | CTF Exchange | `0x4bFb41d5B3570DeFd03C39a9A4D8dE6Bd8B8982E` | | Neg Risk Exchange | `0xC5d563A36AE78145C45a50134d48A1215220f80a` | --- ## State Files (`data/`) > This directory is **gitignored** and created at runtime. | File | Structure | Purpose | |---|---|---| | `positions.json` | `{ [conditionId]: { tokenId, shares, avgBuyPrice, autoSellOrderId, ... } }` | Active copy-trade positions | | `processed_trades.json` | `{ tradeIds: string[] }` | Deduplication (max 500 entries, FIFO eviction) | | `sim_stats.json` | `{ buys, wins, losses, totalPnl, ... }` | Simulation performance statistics | --- ## Order Types | Type | Code | Used for | |---|---|---| | Fill-or-Kill | `FOK` | Market buys and sells (copy trade) | | Good-Til-Cancelled | `GTC` | Auto-sell limits, MM limit sells, sniper orders | --- ## Tick Sizes Valid limit order prices must align to one of these tick sizes: `0.1`, `0.01`, `0.001`, `0.0001`. The tick size for each market is returned by the CLOB API and respected in `executor.js` and `autoSell.js`. --- ## Known Limitations - **No partial fill tracking** — market buy retries treat each attempt as all-or-nothing (FOK) - **Redeem requires MATIC** — on-chain CTF redemption costs gas; keep EOA funded - **MM bot requires a Gnosis Safe proxy wallet** — EOA-only wallets are not supported for the MM bot - **Data API response shape varies** — the code handles multiple known field name conventions but new API changes may require updates --- ## Development Tips - Use `npm run dev`, `npm run mm-dev`, or `npm run sniper-dev` for auto-reload during development - `nodemon` is configured to ignore `data/*.json` to avoid unnecessary restarts on state updates - All `console.log` usage is replaced by `logger.*` — keep it consistent - When adding a new service, export functions individually and import by name; avoid default-export classes - The Safe transaction queue (`_txQueue` in `ctf.js`) is critical — never bypass it or nonce collisions will cause transaction failures