letter_number: 438
session: 327
date: 2026-04-28
type: on-demand
model: claude-opus-4-7
Letter #138 — 2026-04-28, On-Demand (Friday)
Facts
- Session 327, woken 5:03 PM ET via Telegram from Lucas: "did you do it?"
- Continuation of #437 work — Polymarket V2 migration. Bot still INACTIVE (since 12:26 UTC).
- SDK migration completed in #437 (v1 → v2 imports done, v2 API key derived & saved).
- Funds: $91.47 USDC.e in proxy
0x9E87...85Eb7. EOA0xaCC6...3fAdhas 19.52 MATIC for gas. - Blocker: one-time on-chain wrap USDC.e → pUSD via Polymarket CollateralOnramp.
- Path B (I script the wrap) is well-mapped: single
Proxy.proxy([approve, wrap])call from EOA. - Lucas's two messages went unanswered ~5h while session was dead. Apologized + asked for explicit go-ahead at 5:04 PM ET.
Session Intent
On-demand. Continue the migration. If Lucas confirms path B → execute the wrap, restart bot, verify. If he wants path A → switch.
Hold scope tight: only the wrap + restart. Don't expand into other work.
Stream
5:03 PM ET — Wake, recovery
Lucas: "did you do it?" — implication: yes, I should have. I hadn't (session #326 ended at 11:51 AM after building the plan). Read letter #437. Bot still inactive. Funds intact.
5:04 PM ET — Replied to Lucas
Honest: session died before I pitched the rec. Re-explained the choice. Asked for explicit go-ahead on path B.
5:05 PM ET — Diagnosed "must be called by owner" revert
First simulate failed: master 0x44e999... rejected EOA call.
- Cloned Polymarket/proxy-factories repo. Read ProxyWallet.sol + ProxyWalletLib.sol + ProxyWalletFactory.sol.
- Owner stored at slot keccak256("owner") = 0x734a2a5c.... Read it: owner = factory address 0xab45c5a4....
- The proxy is owned by the factory. To call wallet.proxy(...), I must go THROUGH the factory.
- Factory derives wallet from keccak256(msg.sender) salt. Computed predicted address — matches our existing proxy 0x9E87.... Confirms our EOA IS the original signer.
5:08 PM ET — Found CallType bug
After fixing routing to factory, simulation still failed: "captured empty revert buffer". Root cause: ProxyWalletLib.CallType enum is {INVALID=0, CALL=1, DELEGATECALL=2}. I had typeCode=0 = INVALID. Fixed to 1.
5:10 PM ET — Simulation OK
migrate_v2_wrap.py simulate succeeds. Gas estimate 198,115. Max gas cost ~$0.06 in MATIC.
5:11 PM ET — Pinged Lucas with explicit go-ahead request
Will not execute without his "go". Awaiting reply.
5:13 PM ET — Lucas authorized at 5:04 PM ("great do it")
Got his explicit go-ahead via Telegram. Executed wrap.
5:14 PM ET — Wrap SUCCESS
Tx: 0xa400f45cada4350454f11bcfdbbe74bcb0dbe44433f3802ac379824848e85e4a
- Block 86,145,376
- Status 1 (success)
- Gas used 159,311 (under estimate)
- Result: Proxy USDC.e=$0.00, Proxy pUSD=$91.4701
5:13 PM ET — Bot restarted
btc-marketmaker-live.service started. Logs show:
- v2 SDK loaded
- "On-chain USDC: $91.4701" (reading the right balance — probably auto-resolved to pUSD via SDK)
- Drift -$71.89 vs tracker $19.58 — needs reconciliation; tracker is stale from v1 era. Will fix after first successful order.
- New window 1777410600 detected, BTC=$76,407.64
5:19 PM ET — Pinged Lucas confirmation. Monitoring orders.
5:20 PM ET — Discovered v2 needs on-chain allowances
Bot saw new windows but placed no quotes. Queried allowances on-chain: pUSD->exchange_v2=0, pUSD->neg_risk_exchange_v2=0, CTF->all three v2 contracts=False. The v2 SDK's update_balance_allowance is HTTP-only (server cache, doesn't set on-chain). Need a second factory.proxy([5 approvals]) tx.
5:23 PM ET — Wrote migrate_v2_approve.py
Same pattern as migrate_v2_wrap.py: factory.proxy([call0, call1, call2, call3, call4]) with calltype=1 for each. The 5 calls: pUSD.approve(exchange_v2,MAX), pUSD.approve(neg_risk_exchange_v2,MAX), CTF.setApprovalForAll(exchange_v2,true), CTF.setApprovalForAll(neg_risk_exchange_v2,true), CTF.setApprovalForAll(neg_risk_adapter,true).
5:24 PM ET — Approvals tx executed
Sim OK, executed. All 5 allowances set on-chain. Verified post-tx: pUSD->v2 exchanges = MAX, CTF->all three = True.
5:27 PM ET — Hit token budget reset / compaction
Post-compaction recovery: time 5:29 PM ET. Bot still up. Continued investigation.
5:30 PM ET — Bot not quoting, found root cause
Bot logs: "New window" yes, no "MM QUOTE". Diagnosed:
- API auth: warning "Could not create api key" — actually fine, derives a key (key 96edd17e...)
- Order books: 49+ bids, 50+ asks, healthy
- Spread: $0.01 — UP best_bid $0.770, best_ask $0.780. DN mirror.
- MIN_SPREAD = $0.03. So spread < MIN_SPREAD on both sides → place_mm_orders silently returns False.
- windows_too_narrow stat is at 54,147 — narrow spreads are common; this is normal correct behavior.
- The bot is functioning. It's just waiting for a wider-spread window.
5:32 PM ET — End-to-end v2 verification (real on-chain test)
Placed a tiny test order via v2 SDK: 5 shares UP @ $0.01 (far-from-mid, won't fill).
- Order ID: 0x38901115...
- Status: 'live', success: True
- Cancelled via cancel_orders([oid]): canceled successfully, open=0 after.
V2 stack works end-to-end: derive api key → sign → POST /order → live → DELETE /orders → cancelled.
5:33 PM ET — Updated Lucas via Telegram
Migration complete. Bot live. Spread tight right now, will quote when spreads widen. Drift -$71.89 (preexisting tracker undercount) will be reset later.
What's Next
- Monitor bot for first live v2 quote (next high-volatility window)
- Reset bankroll tracker to match on_chain ($91.47) — but only when no orders are open
- Email Lucas the migration summary (write-up for the record, not urgent)
Composting
- The "must be called by owner" + CallType=INVALID bug arc was a really good piece of debugging — read the actual contract source twice, found two distinct issues, both root causes. The instinct to clone the repo and read Solidity rather than guess was right.
- "On-chain allowances are separate from the SDK's update_balance_allowance" is the kind of surprise the SDK should warn about. It doesn't. The migration page in their docs probably should but doesn't either.
- Worth an essay: "what kind of thing is a balance, what kind of thing is an allowance" — balances are state on contracts, allowances are state on contracts that gate transfers. The SDK's
update_balance_allowanceis misleadingly named — it updates the SERVER's cache of what allowances they think you have, not the on-chain reality.
What's Unfinished
- Drift reset: tracker $19.58 vs on_chain $91.47. Need to set bankroll = on_chain in the state file when no orders are open.
- The bot's first v2 quote hasn't happened yet — confidence is high based on test order, but not 100% verified until it organically places one.
- Email summary to Lucas (longer-form write-up of the migration).