Files
winning-wallet-finder/HANDOFF.md
T
2026-07-10 11:25:52 -04:00

9.4 KiB
Raw Blame History

Session handoff — 2026-07-10 (rev 6: delayed-fill incident fixed — DISARMED, re-arm is the user's call)

INCIDENT 2026-07-10 (resolved in code; bot left DISARMED)

Push mode worked (~6s detection) — and exposed an executor bug: in-play markets accept orders with status delayed and ZERO matched at response time. The port read matched==0 as a rejection, logged an honest miss, and forgot the order — six such orders then filled at the exchange untracked (09:1013:14Z, $30 real spend vs $5 booked; caps stopped binding; exits never mirrored; CASH≠CHAIN alarmed exactly as designed). Damage net $2.7 (G2 g2/g3 and PlayTime g1 lost; Rune Eaters +$7.50 and Aurora g1 +$3.77 won, platform auto-redeemed; Aurora g2 won, redeem pending). First diagnosis wrongly blamed a second bot instance — the missed ledger's "order rejected: {'order_id': …}" rows matching on-chain fills to the second settled it: it was THIS bot.

Fix (copybot.py, unit-tested with a stub client, 5 paths): the executor invariant is now no order outlives _order() untracked — zero-matched acceptances poll get_order, cancel the remainder, and measure truth via the exchange's CONDITIONAL balance diff; exception paths sweep all open orders on the token the same way. State surgery done disarmed: cash = chain ($10.03), six fills adopted as bets, spend tracker = the real $35 today (over cap → a re-arm today places nothing new), false miss rows purged, $0.59 adjustment documented.

DISARMED via flyctl secrets unset LIVE_CONFIRM (survives restarts — note machine stop does NOT: http_service auto-restarts the machine). Re-arm = user types flyctl secrets set LIVE_CONFIRM="TRADE LIVE" after reviewing the fix. Recommended: re-run a probe on an IN-PLAY market first.

Self-contained pickup for a fresh session (human or AI). Read README.md gotchas 116 (16 is the new-stack one), FINDINGS.md, ETHERSCAN_MIGRATION.md, LIVE_ROLLOUT.md. The repo is authoritative.

THE CRITICAL PATH IS DONE (2026-07-10 05:08Z)

Every link from rev 4 closed, in order, all proven on the live box:

  1. Root cause of the wrap revert found on-chain (not caller-gating): the pUSD CollateralToken accepts BOTH native USDC and USDC.e by contract, but the public CollateralOnramp 0x93070a… has native USDC paused (paused(0x3c499c…)=1). Docs claiming "must be USDC.e" are downstream of that pause, and the ts-sdk has NO wrap helper — the UI converts via the Bridge.
  2. Bankroll converted via the sanctioned path (host/wrap_via_bridge.py, stage-gated $3 test slice first): gasless ERC-20 transfer → per-wallet bridge address (POST bridge.polymarket.com/deposit) → delivered back already wrapped as pUSD, fee-free ($24.73 in → $24.73 pUSD out). get_balance_allowance(COLLATERAL) = 24730000 with max-uint allowances.
  3. Executor ported (copybot.py LedgerLiveExecutor): unified SDK SecureClient.create(private_key) — deposit wallet auto-resolves, no api_key at runtime; place_market_order FAK with protected prices (quoted ± live.max_slippage_pct, default 5%, clamped [0.01,0.99] so the SDK's band check can't raise); never raises into the trade loop. chain_cash_gap repointed at the deposit wallet's pUSD (the +24.73 alarm is gone). Old tick-rounding machinery deleted — the SDK owns tick/negrisk.
  4. $5 FAK round trip FILLED on the new image (polymarket-client==0.1.0b16 in fly.Dockerfile): BUY matched 7.35294 sh @ 0.68 ($5.00), SELL matched 7.35 sh @ 0.67 ($4.92 gross). Fill-field semantics confirmed: BUY making=USD given / taking=shares got; SELL reversed. Round trip cost 23.7¢ = 1c spread + ~3.2% taker fee on the sell + 0.003 sh dust — the modeled TAKER_FEE_RATE 0.03 tracks reality.
  5. Armed bot restarted on the ported code: geo TRADABLE, LIVE banner, baseline clean, heartbeat [1] open 0 · free $25/$25 with NO drift/chain alarm. The bot can now actually place.

THE ONE REMAINING STEP — first organic fill ⇒ Phase 4

When the first real copy fills, walk LIVE_ROLLOUT Phase 4 and report every link: order id → fills ledger row → position visible in the Polymarket UI (the DEPOSIT WALLET 0x455e252e45Ee46d6C4cc1c8fAdD3899d68f245a1, not the legacy proxy) → /live feed row → cash math → Polygonscan.

PUSH MODE (added 2026-07-10 05:41Z — user wanted push-speed detection)

The live app now runs its OWN Alchemy address-activity webhook (wh_98s36kdtcaf9t9xxhttps://wwf-copybot-live.fly.dev/alchemy, 7 Set E addresses, id also in gitignored config.json as alchemy_webhook_id_live) — fully separate from the paper app's webhook; the two books still share nothing. Detection ~3s vs the poll era's ~39s avg. Verified: boot banner push mode · signature-verify ON · heartbeat 60s · backstop poll 300s, public /health "alive", Fly health check passing, unsigned POST → 401. Remaining soft link: the first REAL Alchemy push accepted (a key mismatch would print ⚠ bad signature — rejected on every Set E trade — watch for that; the 5-min backstop poll covers detection meanwhile).

  • live/sync_webhook.py now syncs BOTH webhooks on every deploy_bot.sh run.
  • The live app has its watchdog now: Fly /health check (self-heal) + watchdog.yml probes both apps → Discord (notify). A DISARMED live app idles WITHOUT HTTP — its watchdog page is expected, not a fault.
  • start.sh live role: ALCHEMY_SIGNING_KEY present → push, absent → 60s poll fallback (so flyctl secrets unset ALCHEMY_SIGNING_KEY is the way back).
  • Cosmetic: on restarts the push server exits with a KeyboardInterrupt traceback in the logs (SIGINT path) — harmless, not a crash.

Known small offsets (do NOT fix while the bot runs — gotcha 15)

  • Book cash $25.00 vs chain pUSD $24.49: fixed $0.51 offset (config bankroll $25 vs $24.73 deposited, + the probe's 23.7¢ round trip which correctly bypassed the book). Below the $1 CASH≠CHAIN alarm. Fold into the state at the next natural machine-stop surgery if desired.
  • The probe's trade is deliberately absent from the fills ledger/feed.

System state (2026-07-10 05:10Z)

  • Live bot (wwf-copybot-live, poll 60s, arn): ARMED, $24.49 pUSD, caps $5/trade · $25/day · $30 exposure — NEVER raise without the user. Disarm: flyctl secrets unset LIVE_CONFIRM or flyctl apps stop.
  • Paper bot (wwf-copybot, push mode): Set E, $1,000 book of 2026-07-08 — Day 1½ realized +$1,107.57 (36 copies) at rev-4 time; check the dashboard for current.
  • Set E both books: LSB1, imwalkinghere, Kruto2027, 0xbadaf319, gkmgkldfmg, AIcAIc, 1kto1m (watch list & demotion candidates in rev 4 / FINDINGS).
  • Dashboards: jaxperro.com/trading (paper), jaxperro.com/live (real).
  • Ops utilities (all need the box's env): host/wrap_via_bridge.py (bankroll conversion — done, idempotent), host/order_probe_v2.py ($5 round-trip probe; now polls the indexer and self-revokes its builder key), host/flatten_positions.py (market-sell everything — the emergency exit).

New-stack facts a future session must not relearn (also gotcha 16)

  • py-clob-client: ARCHIVED. Reads OK, placement dead everywhere.
  • polymarket-client (SecureClient): placement, gasless execute_transaction, get_balance_allowance (.balance int base units). create() is ready-to-use; with only closes transports.
  • Protected prices (max_price/min_price) raise outside [tick, 1tick]; we clamp to [0.01, 0.99] before calling.
  • Builder API keys: revocable ONLY via their own secret (HMAC DELETE) — always atexit-revoke in-process keys. 9 inert ones from bring-up sit on the account; only the UI (settings→builder) can clear them. Runtime bot needs NO key (approvals persist on-chain).
  • Bridge: POST /deposit {address} → per-wallet EVM address (ours: 0x66C1A4b43824CB0DDa54c94F118fc868A6270b91, pinned in wrap_via_bridge.py); Polygon native USDC accepted ($2 min); delivery is pUSD directly; /status/{bridge_addr} tracks; /quote previews.

Queued work (unchanged priorities)

  1. ETHERSCAN_MIGRATION.md phases 0→5.
  2. Empirical fill model from book snapshots; depth gate before sizing up.
  3. Funding-cluster tracer port to Etherscan logs.
  4. Live-app watchdog (poll mode has no /health).
  5. Decide the leftover $32 on polymarket.us (withdraw or abandon).
  6. preflight_live.py still validates the ARCHIVED stack — port or retire.

User to-dos (security/hygiene)

  • Revoke the polymarket.us API key f03d9eb5… (leaked in a screenshot) if not already done.
  • Rotate the Discord webhook committed pre-2026-07-09 (spam risk only).
  • Optional: clear the 9 inert builder keys at polymarket.com/settings?tab=builder.

Operational quick-reference

  • Follow-set change: edit live/copybot.paper.json → ./live/deploy_bot.sh; keep backtest.json mirrored.
  • State surgery: flyctl machine stop FIRST → edit → push → restart → VERIFY first heartbeat (gotcha 15).
  • Never 2 Fly machines per app (flyctl scale count 1).
  • Bot commits its own state — git pull --rebase --autostash before pushing.
  • Image changes (fly.Dockerfile, host/) → flyctl deploy --remote-only -c fly.live.toml -a wwf-copybot-live; code/config → push + flyctl apps restart (a deploy also wipes /tmp clones on the box).
  • Box one-offs: flyctl ssh console -a wwf-copybot-live -C "bash -c '…'"; clone to /tmp with the box's GITHUB_TOKEN; run python with -u.