- Rename project to polymarket-terminal - Add Market Maker bot (src/mm.js) with on-chain CTF split/merge/redeem via Gnosis Safe - Add Orderbook Sniper bot (src/sniper.js) with multi-asset GTC low-price orders - Add WebSocket watcher (src/services/wsWatcher.js) for real-time RTDS trade events - Add terminal dashboard UI (src/ui/dashboard.js) using blessed - Add CTF contract helpers (src/services/ctf.js) for splitPosition, mergePositions, redeemPositions - Add mmDetector, mmExecutor, sniperDetector, sniperExecutor services - Add simStats utility for dry-run P&L tracking - Translate all Indonesian-language strings to professional English across all files - Rewrite README.md in English with full setup guide, configuration reference, and architecture overview - Rewrite AGENT.MD in English as comprehensive AI agent and developer reference - Update package.json name, description, scripts, and keywords Co-Authored-By: direkturcrypto <direkturcrypto.x@mail3.me>
10 KiB
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. 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
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
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
# Copy trade simulation
DRY_RUN=true npm start
# Market maker simulation
npm run mm-sim
# Sniper simulation
npm run sniper-sim
5. Run Live
# 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
.envviadotenv - Parses and validates required fields per bot type
- Exports two validator functions:
validateCopyTradeConfig()— forsrc/index.jsvalidateMMConfig()— forsrc/mm.jsandsrc/sniper.js
- Key exported fields:
privateKey,proxyWallet,traderAddress,dryRun,mmTradeSize, etc.
Client (src/services/client.js)
initClient()— InitializesClobClientfrom@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
- Uses signature type 2 (
getClient()/getSigner()— Singleton getters (callinitClient()first)getPolygonProvider()— Returns aJsonRpcProviderfor Polygon mainnetgetUsdcBalance()— 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 APIfetchMarketByTokenId(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
userchannel for the target trader address - Filters for
tradetype events matchingMATCHEDstatus - Auto-reconnects with exponential backoff on disconnect
Executor (src/services/executor.js)
calculateTradeSize(traderFillSize)— Computes our order size:percentagemode:SIZE_PERCENT% of MAX_POSITION_SIZEbalancemode:SIZE_PERCENT% of current USDC.e balance
executeBuy(tokenId, size, price)— FOK market buy with retry loopexecuteSell(tokenId, shares, mode, price)— Market or limit sellgetMarketOptions(tokenId)— Fetches tick size andnegRiskflag 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 statuscheckOnChainPayout(conditionId)— ReadspayoutDenominatorandpayoutNumeratorsfrom CTF contractredeemPosition(conditionId, tokenId, shares)— CallsredeemPositionson CTF via CLOB clientsimulateRedeem(...)— Tracks wins/losses indata/sim_stats.jsonduring 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 eachmergePositions(conditionId, sharesPerSide)— Burns equal YES+NO → recovers USDC (no-slippage cut-loss)cleanupOpenPositions(clobClient)— On startup, cancels open orders and merges any leftover positionsredeemMMPositions()— Periodic redeemer for resolved markets held by the proxy walletexecSafeCall(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_INTERVALseconds - 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/redeemmonitorAndManage(...)— Watches for fill events; triggers cut-loss whenMM_CUT_LOSS_TIMEis reachedcutLoss(...)— Cancels remaining orders; merges back or market-sells residual tokensattemptRecoveryBuy(...)— Optional directional bet post cut-loss (controlled byMM_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) atSNIPER_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
qfor 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 fromdata/; returns{}if missingwriteState(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, ornpm run sniper-devfor auto-reload during development nodemonis configured to ignoredata/*.jsonto avoid unnecessary restarts on state updates- All
console.logusage is replaced bylogger.*— keep it consistent - When adding a new service, export functions individually and import by name; avoid default-export classes
- The Safe transaction queue (
_txQueueinctf.js) is critical — never bypass it or nonce collisions will cause transaction failures