# How I One-Shot a Polymarket Insider Tracker Bot With Codex (Full PRD Inside)

*I wasted 3 hours on a useless todo list, then one-shot a working bot in 30 minutes. Let me tell you the story, because there's a lesson in here that surprised even me.*

By [Futurenaut](https://blog.futurenaut.co) · 2026-01-14

prediction, markets, polymarket, kalshi, crypto, polygon, bot, python, indiehacker, vibecoding, coding

---

**The Double Fail That Wasn't**

So I saw [spacexbt](https://x.com/@spacexbt) and [DidiTrading](https://x.com/DidiTrading) talking about insider tracking bots for Polymarket. Space mentioned he built one in about 3 hours. I'm a sucker for hype, so of course I had to build my own.

But here's where it gets interesting. Instead of just diving in, I did what I always do: I started overengineering the preparation phase.

**First fail:** I built a todo list website. A whole HTML/CSS thing where you can click through the steps. Beautiful checkboxes. Nice design. Completely unnecessary. It was so useless that I didn't even open it myself while building the bot, so I can only assume nobody else will either. I initially thought it would be quick content I could share on X. Spoiler: it wasn't quick.

**Second fail:** I then spent over an hour tweaking the design of that todo list. Changed the layout multiple times. Rewrote the content. Perfectionism kicked in for something that literally nobody asked for.

[![](https://storage.googleapis.com/papyrus_images/a622d270e34734162c84684ade41e09a8846c79d9746534d1032473518d315b3.jpg)](https://thethirdeye.xyz/)

Classic Wenzel moment. I've been self-employed for 20 years, and I still fall into this trap.

**But Then Something Clicked**
------------------------------

Here's the twist I didn't expect.

By obsessing over that stupid todo list, I had actually forced myself to think through every single aspect of the bot. What data do I need? What endpoints? What filters make sense? How do I avoid spam? What's the detection logic?

When I finally opened Codex, I didn't have a vague idea. I had a complete PRD (Product Requirements Document). Every edge case thought through. Every config parameter defined.

I dumped the whole thing into Codex and... 30 minutes later, the bot was done. Autonomously built by the AI agent while I grabbed a beer.

spacexbt needed 3 hours. I needed 30 minutes.

But let's be real: I also spent 3 hours on that todo list beforehand. So in total? Same time. The difference is that I now have a detailed PRD that I can share, iterate on, and use as a foundation for improvements.

**The Brutal Honesty Part**
---------------------------

Now here's where I need to be transparent, because there's way too much bullshit in this space.

Yes, the bot works. It polls Polymarket trades every second, filters for large trades (2k+ USD), checks if the wallet has been inactive, and sends Discord alerts. Technically, it does exactly what it's supposed to do.

[![](https://storage.googleapis.com/papyrus_images/950a977d73fecc50cf733b4dcd81728ba1e7ddd041fc77400d52989c7c795dc6.png)](https://thethirdeye.xyz/)

But no, this is not a money printer.

In its current state, the bot is very basic. Even if you improve it massively, add ML models, track PnL, whatever... it can only ever be a small decision-making aid. Not a crystal ball. Not alpha that makes you rich on autopilot.

I see too many people on social media claiming they made crazy money with bots like this. Maybe some did. But for every winner, there are 100 people who lost money by blindly trusting alerts. Don't be that person.

**Why I'm Sharing This**
------------------------

I want more honesty and transparency in the online world. Especially in crypto. Especially in AI. Especially where those two overlap.

So I'm sharing my complete PRD below. Not because it's perfect, but because:

1.  You'll learn more by building your own bot (even if it's just prompting Claude Code or Codex) than by copy-pasting someone else's code
    
2.  You can see exactly what goes into something like this
    
3.  You can call me out if my approach is dumb
    

The bot will eventually become part of [thethirdeye.xyz](http://thethirdeye.xyz) and be available to users there once it's properly fine-tuned. But for now, I want to document the journey publicly.

**Thank You**
-------------

Quick shoutout to everyone who's been supportive since I launched [thethirdeye.xyz](http://thethirdeye.xyz)

. The feedback, the engagement, the questions... it means a lot. Building in public is scary, but also incredibly rewarding when people actually give a shit.

If you want to follow along as I improve this bot and share more details, you know what to do. I'll be posting updates over the next days and weeks.

**And yes, I would appreciate likes and shares**. Not for the ego (okay, maybe a little), but because I genuinely believe we need more transparency in this space. The more people see real, honest content about what these tools can and cannot do, the better.

Let's go.

**The PRD (Product Requirements Document)**
-------------------------------------------

Below is the complete PRD I used. Feel free to use it, modify it, improve it. If you build something cool with it, let me know.

> You can simply copy and paste the whole thing, dump it into Codex (or Claude), and get a working bot. It worked for me.

One bug I ran into that you should watch out for: The Polymarket API returns timestamps in Unix seconds, but JavaScript's Datenow() returns milliseconds. If you compare them directly, every trade gets filtered as "old". I've added explicit notes about this in the PRD below.

    # PRD: Polymarket Insider Tracker Bot (Discord Alerts, no UI)
    
    Owner: YOU
    Copyright: © thethirdeye.xyz - 2026
    Target: AI Coding Agent (Codex, Claude Code, etc.)
    
    ## 1. Goal and Problem
    
    We're building a background bot that continuously monitors Polymarket trades and detects "insider-style" activity:
    
    - Focus: New or inactive wallets that suddenly place large trades
    - Output: Discord Alerts (Embeds)
    - No UI, no dashboard, no trading, no order automation
    
    The bot should feel "live" through 1s ingestion, even though upstream is polled via HTTP.
    
    ## 2. Success Metrics
    
    - Time-to-alert: < 3 seconds from trade timestamp (best effort)
    - False positives: Low through Activity Gate and Cooldowns
    - Uptime: 24/7 operation on server (systemd) intended
    - Discord Spam: Under control through dedupe and cooldowns
    
    ## 3. Scope
    
    ### In Scope (MVP)
    - 1s polling of Polymarket trades (global, all markets)
    - Filter: Notional >= 2000 USD (CASH filter)
    - Wallet Activity Gate: Only alert if wallet has "low activity"
    - Dedupe: No double alerting on same trades
    - Discord Webhook output with consistent embed layout
    - Persistence: SQLite (recommended) or JSON state (minimal)
    - Configuration via .env
    
    ### Out of Scope (MVP)
    - Web UI, login, admin panel
    - Telegram, X posting
    - PnL tracking, portfolio, performance analytics
    - ML models
    - Onchain WebSocket event decoding
    
    ## 4. User Stories
    
    1) As a user, I want to see in Discord when a previously quiet wallet suddenly enters a market with >= $2k
    2) As a user, I want only few, strong alerts and no constant whale spam flood
    3) As an operator, I want to configure the bot via env vars and run it as a service
    
    ## 5. Data Sources
    
    MVP is based on Polymarket Data API:
    - Trades Endpoint: GET {DATA_API_BASE}/trades
    - Activity Endpoint: GET {DATA_API_BASE}/activity?user=0x...
    
    Market Metadata Enrichment (optional):
    - Gamma API: {GAMMA_API_BASE}/markets or /events
    
    Note: Define endpoints as config, keep parameters easily adjustable.
    
    ## 6. Core Pipeline (High Level)
    
    Loop every 1000ms:
    
    **Step A: Fetch Trades (only "big trades")**
    - Request: /trades with FilterType=CASH, FilterAmount=2000, limit=200-500
    - Result: List of trades, sorted (typically newest first)
    
    **Step B: Dedupe and Normalization**
    - Dedupe Key: transactionHash + conditionId + side + proxyWallet (conservative)
    - Normalize to internal Trade format
    
    **Step C: Pre-Filters**
    - Exclude categories (optional, configurable)
    - Exclude obvious noise trades (optional)
    
    **Step D: Wallet Activity Gate (critical)**
    - For proxyWallet: Load Activity Summary for last N days
    - Decide "low activity" or "active whale"
    - Cache activity per wallet (TTL) to save API calls
    
    **Step E: Detection and Alert Decision**
    - MVP Detector: Low Activity Wallet + Large Trade
    - Optional: z-score vs baseline, timing near close (don't implement if data missing)
    
    **Step F: Discord Alert**
    - Send Discord Embed
    - Rate limit: Queue with 1 msg/sec
    
    **Step G: Persist State**
    - Store seen trades (dedupe)
    - Store wallet stats cache (optional)
    - Store alert cooldowns per wallet
    
    ## 7. Detectors (MVP Definition)
    
    ### Detector 1: Low Activity Wallet + Large Trade
    
    Trigger when all conditions are met:
    - trade.notional_usd >= 2000
    - wallet_activity_30d_trades < ACTIVE_TRADES_30D_CUTOFF
    - wallet_last_trade_ts is older than INACTIVE_DAYS or not present
    - cooldown for wallet not active
    
    Config Defaults:
    - INACTIVE_DAYS = 30 (parameterizable later)
    - ACTIVE_TRADES_30D_CUTOFF = 50
    - WALLET_COOLDOWN_HOURS = 6
    
    Output details:
    - wallet
    - side (BUY/SELL or YES/NO if available)
    - market title + slug
    - size, price, notional (if available)
    - timestamp
    - optional: unique markets lifetime estimate (if available via Activity)
    
    ## 8. Data Models (TypeScript Interfaces)
    
    Define types centrally in src/types.ts.
    
    Minimal Required Trade Fields (internal format):
    - id: string (dedupe key)
    - timestamp: number (unix milliseconds - see CRITICAL NOTE below)
    - proxyWallet: string
    - conditionId: string
    - side: string
    - outcome: string | null
    - size: number | null
    - price: number | null
    - notionalUsd: number | null
    - title: string | null
    - slug: string | null
    - transactionHash: string | null
    - raw: unknown (optional for debug)
    
    WalletActivitySummary:
    - wallet: string
    - windowDays: number
    - tradesCount: number
    - lastTradeTs: number | null
    - uniqueMarketsCount: number | null
    - winRateResolved: number | null (optional)
    
    AlertPayload:
    - type: "LOW_ACTIVITY"
    - trade: Trade
    - details: object (detector specific)
    
    ### CRITICAL: Timestamp Normalization Bug
    
    The Polymarket API returns timestamps in Unix SECONDS (e.g., 1767824287).
    JavaScript's Date.now() returns MILLISECONDS.
    
    If you compare them directly, every trade will be filtered as "ancient" because
    1767824287 (seconds) is way smaller than ~1767824287000 (milliseconds).
    
    **FIX: Always normalize timestamps to milliseconds immediately after fetching:**
    typescript
    function normalizeTimestamp(ts: number): number {
      // If timestamp is less than year 2100 in seconds, it's in seconds
      // 4102444800 = Jan 1, 2100 in Unix seconds
      if (ts < 4102444800) {
        return ts * 1000; // Convert seconds to milliseconds
      }
      return ts; // Already in milliseconds
    }
    
    
    Apply this to every trade.timestamp right after parsing the API response.
    
    ### CRITICAL: Notional USD Calculation
    
    The API may not always return notionalUsd directly. Calculate it yourself:
    typescript
    const notionalUsd = trade.size * trade.price;
    
    
    Always do this calculation as a fallback if notionalUsd is missing or null.
    
    ## 9. Config (.env)
    
    Create .env.example.
    
    Required:
    - DATA_API_BASE=https://data-api.polymarket.com
    - DISCORD_WEBHOOK_URL=
    - POLL_MS=1000
    - MIN_NOTIONAL_USD=2000
    
    Recommended:
    - ACTIVITY_WINDOW_DAYS=30
    - INACTIVE_DAYS=30
    - ACTIVE_TRADES_30D_CUTOFF=50
    - WALLET_COOLDOWN_HOURS=6
    - DISCORD_RATE_LIMIT_MS=1000
    - SQLITE_PATH=./polymarket-monitor.db
    - LOG_LEVEL=info
    - BACKFILL_MINUTES=10
    
    Optional:
    - GAMMA_API_BASE=https://gamma-api.polymarket.com
    - EXCLUDED_CATEGORIES=Sports
    - DEBUG_SAVE_RAW=false
    
    ## 10. Persistence
    
    Recommended: SQLite via better-sqlite3 or sqlite3.
    
    Minimum Tables:
    - seen_trades(id TEXT PRIMARY KEY, ts INTEGER, wallet TEXT, conditionId TEXT, txHash TEXT)
    - wallet_state(wallet TEXT PRIMARY KEY, last_alert_ts INTEGER, last_seen_ts INTEGER)
    - kv_cache(key TEXT PRIMARY KEY, value TEXT, updated_ts INTEGER) (for Activity Cache)
    
    Alternative (minimal):
    - JSON files in ./data/ for seenTrades and walletState
    - But SQLite is more stable for 24/7 operation
    
    ## 11. Module Structure (File Plan)
    
    Build complete, runnable base with clear separation:
    
    - src/index.ts: main loop, boot, graceful shutdown
    - src/config.ts: env parsing, defaults, validation
    - src/api.ts: http client, fetchTrades, fetchActivity
    - src/database.ts: sqlite init, dedupe store, wallet state store
    - src/filters.ts: category filter, size filter
    - src/detection.ts: runDetections(trade) -> AlertPayload[], detectorLowActivity(trade)
    - src/enrichment.ts: optional market metadata caching
    - src/discord.ts: queue, embed builder, send webhook
    - src/types.ts: TypeScript interfaces
    - src/utils.ts: sleep, time, retry with backoff, hashing keys, normalizeTimestamp
    
    Non-src:
    - .env.example
    - package.json scripts: build, start, dev
    - README.md quickstart
    
    ## 12. Error Handling and Robustness
    
    - API Errors: retry with exponential backoff (max 3), then skip cycle
    - Rate limits: respect, optional adaptive poll (fallback 2s)
    - Memory: seenTrades in DB, not only in RAM
    - Graceful shutdown: SIGINT/SIGTERM flush queue, close DB
    
    ## 13. Observability
    
    Console Logs:
    - Boot: Config summary (without secrets)
    - Poll cycle: fetched, deduped, filtered, checked, alerted counts
    - Alerts: one line summary + trade id
    - Errors: endpoint, status code, message
    
    Optional: write logs to file via process manager (systemd journal is enough)
    
    ## 14. Local Run and Deployment
    
    Local:
    - npm install
    - cp .env.example .env
    - npm run dev or npm start
    
    Server (later, not MVP code task):
    - systemd service file
    - env file on server
    - restart=always
    
    ## 15. Acceptance Criteria (Definition of Done)
    
    Deliverables:
    1) Repo starts with npm start without crash
    2) Poll loop runs every 1s and logs poll stats
    3) Dedupe prevents double processing of same trades
    4) Activity Gate is applied per new wallet, with cache
    5) On trigger, a Discord Embed is sent
    6) Cooldown prevents repeated alerts per wallet within set hours
    7) .env.example present and README describes setup
    
    ## 16. Activity Endpoint Parameters
    
    For the /activity endpoint, use these parameters:
    - type=TRADE (to get trade activity specifically)
    - start=[unix timestamp] (to limit the time window)
    - limit=100 (adjust as needed)
    - sortDirection=DESC (newest first)
    
    This ensures your "low activity" gate actually works correctly.

That's it. Now go build something. ❤

---

*Originally published on [Futurenaut](https://blog.futurenaut.co/how-i-one-shot-a-polymarket-insider-tracker-bot-with-codex)*
