Files
winning-wallet-finder_github/HANDOFF.md
T
2026-07-12 23:30:11 -04:00

281 lines
16 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Session handoff — 2026-07-13 (rev 10: depth gate + guard-validated + clob_user own-fill push)
## Items 2-4 shipped (2026-07-13, evidence from a 3-agent analysis workflow)
- **DEPTH GATE** (copytrade.book_depth + gate in _handle_their_buy;
config.depth_gate, both books). Fitted on 131 book-annotated fills:
before ordering, SKIP if spread>0.08 (market mid-move, median |slip|
~14%) or ask5c<$50 (dust books mispriced every fill), else cap stake at
10% of 5c ask depth (impact <~2%; >20% of depth → >+2% slip 33-50% of
the time). Fail-open on a book-fetch error. This is what lets stakes
SCALE — binds mainly on slow-market wallets (badaf median 23% of depth
at $40-100). 5 stub paths pass.
- **PRICE GUARD validated, unchanged**: counterfactuals put 0.05 exactly at
the EV knee (0.05-0.10 moves ≈ breakeven, >0.10 = -20% ROI). Comment
records it. The +9-14% slip tail is lag-drift in deep esports books —
a detection problem RTDS ~1s already addresses, not a guard problem.
- **clob_user OWN-FILL PUSH** (UserFillsListener, live only): streams our
order/trade lifecycle from ws-subscriptions-clob/ws/user (auth =
client.credentials; all-markets). A matching order id fires an IMMEDIATE
resolve_pendings() — in-play holds adopt on match, not on the 60s tick.
Events only TRIGGER; get_order stays the arbiter (2026-07-12 invariant).
resolve_pendings is now re-entrancy-locked (ws + heartbeat race). 60s
poll stays as fallback; ws failure degrades to today. Heartbeat shows
`userws up`. VERIFIED connected on the live box.
- **Sub-$1 BUY fix**: gated $1.00 stakes shaved to $0.99 by share-flooring
→ 7 of 13 live rejections. Now bumped to the $1 venue minimum.
- Follow-set (rev 9→10): 5 wallets live (dropped imwalkinghere, demoted
LSB1; Kruto floor 80 pinned in backtest too for comparability). Backtest
proves 11 (bench: Vahan88, 42021, BikesAreTheBikes, EdwardIN).
## Since rev 8 (2026-07-11 → 07-13)
- **RTDS shadow verdict: GO** — 916 detections, p50 0.8s / p99 6.4s / max
9.9s (in-play emits within 10s). **RTDS_DETECT=1 is live on the
real-money bot**; detections stream on both books; Alchemy + 300s poll
remain backstops. Silent-stale guard force-reconnects a 120s-quiet
socket (the stream once sat "up" delivering nothing for 35 min).
- **Phantom-cash incident (+$7.86, book-only, alarms caught it)**:
overlapping pendings on one token each read the same balance move —
one real 1.48-share sell booked 3×. FIXED: order-size caps every
adoption, balance-diff only when the exchange no longer answers, SELLs
cap at book holdings, ONE pending per token (engine + retries refuse
while the resolver owns a token). Book rebuilt from chain ($11.21,
drift 0, poisoned bets corrected from real fills, residual $0.08).
- **No price floor** (user call after a 0.001 longshot was blocked):
risk.min_price 0 on both books, cap stays 0.95; executor protected
prices now 4dp and price-proportional (2dp zeroed sub-penny bounds).
- **H3 fixed**: per-wallet trade cursor + paginated activity fetch (5×100,
walks to the last processed ts) — bursts can't scroll past a page.
- **clone-guard works now**: verifies over `git ls-remote` (the REST API
is unreachable from Fly boxes — Bearer 401 + anon rate-limit; it failed
open on every boot until 07-13). Both boots log `clone verified @ sha`.
- preflight_live.py rewritten for the unified SDK (deposit wallet, pUSD,
book access, RTDS stream, geo). Drift ALARM floor 1c→5c (penny rounding
isn't a bug; the self-heal path keeps its threshold).
- Live book truth: 31 copies, realized $5.32 lifetime (day-one incident
dominates), ~$11.2 cash + LAB + 1 small open. User to-dos: polymarket.us
key REVOKED, $32 RESOLVED; Discord webhook rotation still open.
## RTDS shadow run (started 2026-07-10 22:26Z)
**T0 detection is deployed**: `RtdsListener` in copybot.py streams
Polymarket's real-time trade socket (`wss://ws-live-data.polymarket.com`,
topic `activity`/type `trades` — undocumented but official; probe measured
**median 0.8s delivery**, ~45 msg/s peak firehose, zero drops in 45 min).
Client-side Set E filter → the same `on_wallet_activity` funnel as the
Alchemy push. Heartbeat shows `rtds up Ns` / `⚠ rtds down`.
- **Paper: ON by default** — this IS the 24h shadow run. Compare `rtds:`
detection lines vs Alchemy-path lag; watch the in-play tail (does RTDS
emit held esports matches at match time or settlement?).
- **Live: OFF until the shadow validates.** Enable with
`flyctl secrets set RTDS_DETECT=1 -a wwf-copybot-live` (restarts armed).
- Expected outcome: 24h lag stat drops toward ~1-2s and the
"not copied in the detection window" misses go near-zero on paper.
- Phase 2 (researched, not built): RTDS `clob_user` topic streams OUR order
lifecycle (auth: CLOB api creds) — would make the pending-order registry
event-driven instead of 60s polling.
## Retune (2026-07-10 ~21:20Z, USER-directed — supersedes the rule-0.6 caps)
- **Hard caps RETIRED** (user decision): risk block mirrors the paper
sentinels. Sizing = paper-parity **4% of equity** (class_pct 0.04/0.12),
floored at the venue's $1 min order. Bankroll REBASED to the real
**$22.28** equity (heartbeat reads `free $17/$22`); spend tracker reset.
- **Price guard is ABSOLUTE +0.05** (both books, identical rules):
0.14→0.15 follows, 0.14→0.20 skips. Key renamed `price_guard_abs`.
- **Pending-order registry**: in-play `delayed` holds are handed to
`state.pending_orders` (full copy context) instead of a 20s cancel; the
heartbeat resolver adopts fills whenever they land or cancels at TTL 600s
→ honest miss. Stub-tested (adopt + expire). Watch the first
`PENDING FILLED` line in production.
- Both bots restarted on the new rules (paper +$1,378, 60 copies at reboot).
- GOTCHA REMINDER proven again today: a boot can clone a stale GitHub
replica — the 17:18 boot ran pre-push config for 4h (harmless here);
ALWAYS verify the first heartbeat shows the numbers you shipped.
## 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](README.md) gotchas 116 (16 is the new-stack one),
[FINDINGS.md](FINDINGS.md), [ETHERSCAN_MIGRATION.md](ETHERSCAN_MIGRATION.md),
[LIVE_ROLLOUT.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_98s36kdtcaf9t9xx` → https://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~~ DONE (user, 2026-07-10).
- ~~Decide the leftover $32 on polymarket.us~~ RESOLVED (user moved it,
2026-07-10).
- Rotate the Discord webhook committed pre-2026-07-09 (spam risk only) —
STILL OPEN.
- Optional: clear the 9 inert builder keys at
polymarket.com/settings?tab=builder.
## Hardening trio (2026-07-10 late — all stub-tested)
- **Exit-retry (LIVE_ROLLOUT 1.6 built)**: failed mirror-exits queue in
`state.exit_retries`; the heartbeat re-attempts up to 10 ticks (recovered
exits ping Discord), then pages **⚠ EXIT STUCK** and lets the position
ride knowingly. In-play holds hand off to the pending registry.
- **RTDS shadow ledger**: every Set E detection appends to
`rtds_shadow.jsonl` (live: `rtds_shadow.live.jsonl`) — ts, lat_s, and
whether another trigger saw the tx first. Rides the publish commit;
capped ~2000 lines. This file is the 24h go/no-go data for RTDS_DETECT=1.
- **Boot stale-clone guard (start.sh)**: clone HEAD verified against the
GitHub API (re-clone up to 4×) — kills the stale-replica boots that hit
twice on 2026-07-10.
## 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`.