On-chain copy-trade PAPER lane in the pm-arb crate (commit b3f9e21, main, 2026-05-31). It mirrors the top-trader cohort’s cheap-outcome BUYs on crypto up/down markets, detecting their fills via Polygon on-chain OrderFilled logs instead of the Data API. Paper trading only — no on-chain order execution, no keys, no USDC. Ships default-OFF (DB kill-switch enabled DEFAULT FALSE), so the deploy is dormant and zero-risk until flipped.

For Agents

This is the strategic pivot from the autonomous longshot signal in longshot-strategy. The bet is no longer “we predict longshots ourselves” — it’s “we copy the cohort’s outcome selection.” The edge being harvested is SELECTION, not our price/time signal. Read top-trader-edge for who the cohort is and why they have an edge; read this note for how the copy lane mirrors them.

Why this exists — the strategy basis

Two prior findings forced the pivot:

  • Our autonomous longshot signal is negative-EV. Live it runs ~breakeven, t≈0.2 over 655 trades. The price×time-in-window reversion bet we built (see longshot-strategy) does not have a demonstrable edge on its own.
  • The top-5 cohort wallets have a real, replicable cheap-outcome selection edge. +~18–30% flat ROI, t up to 9 over tens of thousands of trades, verified across all 5 cohort wallets and specific to crypto-updown markets.

So instead of generating our own signal, we copy which cheap outcome the cohort buys. This is distinct from copy-trade-algorithm (the all-category, lifetime-PnL-edge tracker that backtested to no demonstrated edge) — the copy lane is narrow (crypto-updown only), on-chain-detected, and rides a selection edge that survived cohort-wide significance testing.

Why on-chain detection (not the Data API)

The cohort trades 300-second markets. Detection latency is the whole game.

sourcelatencycarries wallet?
Polymarket Data APImedian ~5 min after the trade (only ~2% within 30s)yes, but too slow
Polygon on-chain OrderFilled logs~1–3 syes (maker/taker address)

The Data API’s ~5-min surfacing lag is useless for 5-minute markets — by the time a cohort trade appears, the market is resolving. On-chain logs surface in seconds and carry the wallet address, so we can attribute a fill to a specific cohort wallet in near-real-time.

You cannot "trade on-chain instead of the API"

Polymarket matches off-chain (centralized CLOB) and only settles on-chain. On-chain detection works for reading the cohort’s fills, but real-money execution still requires EIP-712-signed CLOB orders against the off-chain matcher — a separate future project (see Colocation for the off-chain-matching constraint). There is no on-chain order placement path.

Verified on-chain decode facts

For Agents — load-bearing constants, all reconciled against pm_trades ground truth

These are the facts that make the decoder correct. Several are counter-intuitive (the topic0 in particular is not the canonical-ABI value).

  • Address match: the on-chain maker/taker address equals our pm_trades.proxy_wallet for all 5 cohort wallets. (This is what lets us attribute a fill to a cohort wallet.)
  • Real OrderFilled topic0 is 0xd543adfd...NOT the canonical-ABI 0xd0a08e8c. Filtering on the canonical hash yields zero logs. Use the real one.
  • Settlement contract: the cohort settles on CTF Exchange V2 0xE111180000d2663C0091e4f400237545B87B996B.
  • Both token and USDC are 6-decimals.

Decode rule (per OrderFilled leg)

For each leg where the cohort wallet is maker or taker, look at what the cohort gave vs got:

cohort gavecohort gotsideprice
USDCtokenBUYUSDC / token
tokenUSDCSELLUSDC / token
  • got-token + gave-USDCBUY; gave-token + got-USDCSELL.
  • price = USDC / token (both 6-decimals).
  • token → outcome via gamma clobTokenIds.

Reconciled against pm_trades (the Data-API-sourced ground truth) to confirm the decode produces the same trades.

Architecture

Copy lane data flow

graph TD
    WSS["Polygon WSS<br/>eth_subscribe logs + newHeads"] --> DET["detector<br/>polymarket-data/onchain.rs"]
    DET -->|"decoded fill + wallet"| LOOP["copy_loop<br/>pm-arb/copy_lane.rs<br/>(spawned in validate::run)"]
    LOOP --> DEC["decode leg → BUY/SELL + price"]
    DEC --> MAP["token → market map<br/>(populated by rotation poller)"]
    MAP --> EVAL["evaluate_copy<br/>pm-arb/copy.rs<br/>(gates)"]
    EVAL -->|"accepted BUY"| PAPER["pm_paper_trades<br/>lane='copy'<br/>hold to resolution"]
    EVAL -.->|"rejected: SELL / gates"| DROP["drop"]
    PAPER --> RES["resolver<br/>(subtracts PM crypto taker fee)"]
    style WSS fill:#264653,stroke:#2a9d8f,color:#fff
    style PAPER fill:#2d2d2d,stroke:#888,color:#fff
    style EVAL fill:#3d2020,stroke:#a55,color:#fff

Pipeline, end to end:

  1. Detector (crates/polymarket-data/src/onchain.rs) — Polygon WSS eth_subscribe on logs for the OrderFilled topic, plus newHeads to maintain a block-timestamp cache (logs don’t carry wall-clock time; block timestamp supplies it).
  2. copy_loop (crates/pm-arb/src/copy_lane.rs) — spawned inside validate::run. Consumes detector events.
  3. Decode — apply the decode rule above to get side + price + token.
  4. token → market map — populated by the existing rotation poller (the same component that tracks the active crypto-updown markets). Maps the on-chain assetId to a market/outcome.
  5. Gated evaluate_copy (crates/pm-arb/src/copy.rs) — applies cap / floor / window / dedup / daily-notional gates and rejects cohort SELLs (we only mirror cheap-outcome BUYs).
  6. Write — accepted BUYs write lane='copy' rows to pm_paper_trades, held to resolution.
  7. Resolver — at resolution, subtracts the Polymarket crypto taker fee: shares × 0.072 × p × (1−p), which is ≈6.4% of stake at ~11¢. This fee subtraction is a no-op for the existing (longshot) lanes — it only bites the copy lane.

The copy lane shares pm_paper_trades with the longshot lane

Rows are distinguished by lane='copy' vs the existing longshot lane. The resolver’s fee subtraction is gated so it doesn’t change longshot-lane accounting. See longshot-strategy for the longshot lane’s own columns/behavior on the same table.

Operational runbook — DB-configured, hot-reloaded

Runtime config lives in Supabase and hot-reloads ~every 60s — no redeploy needed

This is the key operational fact. You start, stop, and tune the copy lane entirely from the database.

Two tables drive it:

pm_copy_config (singleton)

columnmeaning
enabledkill-switch, DEFAULT FALSE
cap_pricemax outcome price to copy
min_pricefloor price
size_usdper-copy stake
min_window_left_secsskip if too little time left in the market
max_daily_notional_usddaily spend cap

pm_copy_wallets

Per-wallet enable flags, seeded with the 5 cohort wallets. Toggle individual wallets on/off here.

Commands

-- START the copy lane
UPDATE pm_copy_config SET enabled = true;
 
-- STOP it
UPDATE pm_copy_config SET enabled = false;
 
-- tune live (example) — takes effect within ~60s, no redeploy
UPDATE pm_copy_config SET cap_price = 0.13, size_usd = 10;

Config changes propagate within ~60s via the hot-reload loop.

Deploy state & gotchas (2026-05-31)

Two manual prerequisites before the lane can run

  1. Migrations were applied manually via Supabase MCPpm-arb does NOT run sqlx migrate (only polymarket-fetch does), so the two copy migrations could not ship through the normal pipeline. Both are applied and verified; enabled = false (dormant).
  2. New env var POLYGON_WSS_URL required — a Polygon PoS WSS endpoint (e.g. Alchemy), added to the POLYMARKET_FETCH_ENV GitHub secret by @deploy. Until it is set, the lane idles even if enabled = true — it logs a warning and does not error.
  • Default-OFF = dormant, zero-risk deploy. The enabled DEFAULT FALSE means the lane does nothing until the kill-switch is flipped. Shipping it does not change live behavior.
  • Handed to @deploy in AGENT_HANDOFF.md Round 42: add POLYGON_WSS_URL + confirm the auto-catch bounce. (Auto-catch per @deploy Round 36 applies to pm-arb, so a push should redeploy — see Today’s operational learnings (gotchas worth durable docs).)
  • pm-arb does not run migrations — remember this for any future copy-lane schema change; it has to go through Supabase MCP, not a code-shipped migration. (Contrast with polymarket-fetch, whose migrate command does run them.)

Honest limits & follow-ups

These do not block paper validation, but a future session should know them

  • assetId ↔ gamma clobTokenIds link is verified on 2 real fills. The live forward run is the definitive proof — see Gate-0 below.
  • Analytics niggles (cosmetic, not behavioral):
    • up_mid_at_signal stores the entry ask, not the up-mid.
    • detect_latency_ms measures block → eval, not trade → eval.
    • No stale-book guard on the copy ask (the longshot lane’s stale-book concerns may apply here too).
    • Copy counters are absent from the hourly summary.
  • Real-money execution is a separate future project. It needs EIP-712-signed CLOB orders against Polymarket’s off-chain matcher — you cannot trade on-chain (see the callout above). On-chain detection does not get us closer to on-chain execution.

Gate-0 — the definitive next check

First validation step once POLYGON_WSS_URL is set and enabled=true

Confirm that lane='copy' rows actually appear in pm_paper_trades. Everything upstream (detector, decode, token→market map, gates) is verified in pieces; the live forward run that produces real copy rows is the end-to-end proof that the assetId↔outcome link and the whole pipeline work in production. No copy rows after enabling = something in the chain is broken (most likely the POLYGON_WSS_URL, the topic0, or the token→market map).

  • longshot-strategy — the autonomous longshot lane this pivots away from (negative-EV on its own); shares the pm_paper_trades table and validate::run host
  • top-trader-edgewho the cohort is and why their cheap-outcome selection has an edge; the selection edge this lane copies
  • copy-trade-algorithm — the other, unrelated copy idea (all-category, lifetime-PnL-edge tracker, no demonstrated edge); do not confuse with this on-chain crypto-updown lane
  • polymarket-fetch-deploy — deploy mechanism, GH secrets, auto-catch; where POLYGON_WSS_URL lands
  • polymarket-fetch — workspace, schema, CLI, pm_* tables overview