Files
jaxperro ca6661cb94 docs+cleanup: HANDOFF rev 5 (placement proven), gotcha 16, key self-revoke
Critical path closed: bridge conversion ($24.73→pUSD, fee-free), executor
ported and round-trip proven ($5 FAK both legs), armed bot restarted clean.
README gains gotcha 16 (pUSD stack: paused native-USDC onramp, bridge is
the sanctioned conversion, builder-key revocation semantics) + bridge/
relayer data-source rows. Probe & wrap scripts atexit-revoke their builder
keys (9 inert ones accumulated during bring-up).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-10 01:19:57 -04:00

214 lines
9.7 KiB
Python

#!/usr/bin/env python3
"""One-time bankroll conversion: native USDC (deposit wallet) → pUSD collateral.
WHY (2026-07-10, verified on-chain): the 2026 exchange's balance view counts
ONLY the pUSD CollateralToken (0xC011a7…). pUSD accepts BOTH native USDC and
USDC.e by contract (`onlyValidAsset`), but the public CollateralOnramp
(0x93070a…) has native USDC PAUSED (paused(0x3c499c…)=1 — the exact cause of
the 'batch would revert' on the direct wrap attempt). The sanctioned path for
native USDC is Polymarket's own Bridge (the UI deposit flow): it swaps native
USDC → USDC.e and delivers to the registered wallet; USDC.e wrapping is live.
Flow (stage-gated, aborts on any failed link — never guesses with live funds):
A. bridge a $3 test slice: gasless ERC-20 transfer → per-wallet bridge
deposit address → poll until USDC.e lands at the deposit wallet
B. bridge the remainder the same way
C. gasless approve(onramp) + onramp.wrap(USDC.e, wallet, all) → pUSD
D. success = get_balance_allowance(COLLATERAL) ≈ the bankroll
Run on the live Fly box (needs LIVE_PRIVATE_KEY; geo-gated venue):
python3 host/wrap_via_bridge.py
"""
import json
import os
import ssl
import sys
import time
import urllib.request
_SSL = ssl._create_unverified_context()
USDC = "0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359" # native (paused on onramp)
USDCE = "0x2791Bca1f2de4661ED88A30C99A7a9449Aa84174" # bridged (wrappable)
PUSD = "0xC011a7E12a19f7B1f670d46F03B03f3342E82DFB" # CollateralToken
ONRAMP = "0x93070a847efEf7F70739046A929D47a521F5B8ee" # CollateralOnramp
DW = "0x455e252e45Ee46d6C4cc1c8fAdD3899d68f245a1" # our deposit wallet
# bridge deposit address minted for DW on 2026-07-10 (bridge.polymarket.com);
# pinned so a drifting registration answer aborts instead of misdirecting funds
EXPECTED_BRIDGE_EVM = "0x66C1A4b43824CB0DDa54c94F118fc868A6270b91"
BRIDGE = "https://bridge.polymarket.com"
TEST_SLICE = 3_000_000 # $3 — above the bridge's $2 Polygon minimum
POLL_S, DELIVER_TIMEOUT_S = 15, 900
def http(url, payload=None):
data = json.dumps(payload).encode() if payload is not None else None
req = urllib.request.Request(url, data=data, headers={
"Content-Type": "application/json", "User-Agent": "Mozilla/5.0"})
return json.load(urllib.request.urlopen(req, timeout=30, context=_SSL))
def rpc_call(to, data):
body = json.dumps({"jsonrpc": "2.0", "id": 1, "method": "eth_call",
"params": [{"to": to, "data": data}, "latest"]}).encode()
url = os.environ.get("ALCHEMY_RPC_URL", "https://polygon.drpc.org").strip()
req = urllib.request.Request(url, data=body,
headers={"Content-Type": "application/json"})
return json.load(urllib.request.urlopen(req, timeout=20, context=_SSL))["result"]
def bal(token, holder=DW):
return int(rpc_call(token, "0x70a08231" + holder[2:].lower().rjust(64, "0")), 16)
def fmt(u):
return f"${u/1e6:,.2f}"
def wait_handle(label, h):
out = h.wait() if h is not None and hasattr(h, "wait") else h
print(f" {label}: {str(out)[:220]}")
return out
def delivered_units():
"""pUSD + USDC.e at the wallet — the $3 test slice (2026-07-10) proved the
bridge wraps on delivery (native → USDC.e swap, then pUSD credited), so
delivery must be measured on pUSD; USDC.e is counted in case a future
bridge change stops short of the wrap (the wrap step below picks it up)."""
return bal(PUSD) + bal(USDCE)
def bridge_leg(client, calls_mod, amount, bridge_addr, label):
"""Gasless-transfer `amount` native USDC to the bridge and wait until the
deposit wallet's pUSD/USDC.e rises by ~amount. Returns True on delivery."""
before = delivered_units()
print(f"\n[{label}] sending {fmt(amount)} native USDC → bridge {bridge_addr}")
t = calls_mod.erc20_transfer_call(token_address=USDC, recipient=bridge_addr,
amount=amount)
try:
h = client.execute_transaction(calls=[t], metadata=f"bridge deposit {label}")
wait_handle("relayer", h)
except Exception as e:
print(f" transfer FAILED before leaving the wallet: {type(e).__name__} {e}")
return False
deadline = time.time() + DELIVER_TIMEOUT_S
while time.time() < deadline:
time.sleep(POLL_S)
now = delivered_units()
try:
st = http(f"{BRIDGE}/status/{bridge_addr}")
txs = st.get("transactions") or []
latest = txs[0].get("status") if txs else "none"
except Exception as e:
latest = f"status err {e}"
print(f" pUSD+USDC.e {fmt(now)}{fmt(now-before)}) · bridge: {latest}", flush=True)
# bridge swap costs a few tenths of a cent — 1% tolerance is generous
if now - before >= amount * 0.99:
print(f" [{label}] DELIVERED")
return True
print(f" [{label}] NOT delivered within {DELIVER_TIMEOUT_S}s — ABORT. "
f"Track {BRIDGE}/status/{bridge_addr}; funds are at the bridge, "
f"recovery: https://matic-recovery.polymarket.com/")
return False
def main():
pk = os.environ["LIVE_PRIVATE_KEY"].strip()
from polymarket import SecureClient
from polymarket import calls as pmcalls
from web3 import Web3
native = bal(USDC)
print(f"deposit wallet {DW}\n native USDC {fmt(native)} · USDC.e "
f"{fmt(bal(USDCE))} · pUSD {fmt(bal(PUSD))}")
# pause-state preflight: if Polymarket unpauses native USDC later, the
# bridge becomes unnecessary — but also if they pause USDC.e we must stop.
paused_sel = Web3.keccak(text="paused(address)")[:4].hex()
usdce_paused = int(rpc_call(ONRAMP, "0x" + paused_sel
+ USDCE[2:].lower().rjust(64, "0")), 16)
if usdce_paused:
sys.exit("onramp has USDC.e PAUSED — nothing safe to do, aborting")
boot = SecureClient.create(private_key=pk)
with boot:
bk = boot.create_builder_api_key()
print("builder key minted (in-process only)")
client = SecureClient.create(private_key=pk, api_key=bk)
# revoke on ANY exit path — unrevoked keys are inert but pile up forever
# (revocation is HMAC-authed by the key's own secret; see order_probe_v2)
import atexit
atexit.register(lambda: (client.revoke_builder_api_key(),
print("builder key revoked")))
with client:
if str(client.wallet).lower() != DW.lower():
sys.exit(f"client.wallet {client.wallet} != expected {DW} — aborting")
if native > 0:
reg = http(f"{BRIDGE}/deposit", {"address": DW})
bridge_addr = (reg.get("address") or {}).get("evm", "")
print(f"bridge deposit address: {bridge_addr}")
if bridge_addr.lower() != EXPECTED_BRIDGE_EVM.lower():
sys.exit(f"bridge address drifted (expected {EXPECTED_BRIDGE_EVM})"
" — aborting, investigate before sending funds")
if native <= TEST_SLICE + 2_000_000: # too small to stage: one leg
legs = [(native, "all")]
else:
legs = [(TEST_SLICE, "test-slice"),
(native - TEST_SLICE, "remainder")]
for amount, label in legs:
if not bridge_leg(client, pmcalls, amount, bridge_addr, label):
sys.exit(1)
else:
print("no native USDC left to bridge — proceeding to wrap")
usdce = bal(USDCE)
if usdce == 0:
# normal: the bridge wraps on delivery, so everything is already pUSD
print(f"\nno USDC.e residue — bridge delivered wrapped. "
f"pUSD {fmt(bal(PUSD))}")
try:
b = client.get_balance_allowance(asset_type="COLLATERAL")
print("exchange-view COLLATERAL:", str(b)[:220])
except Exception as e:
print("balance check raised:", type(e).__name__, str(e)[:150])
return
print(f"\n[wrap] approve onramp + wrap {fmt(usdce)} USDC.e → pUSD")
appr = pmcalls.erc20_approval_call(token_address=USDCE, spender=ONRAMP,
amount=usdce)
wrap_sel = Web3.keccak(text="wrap(address,address,uint256)")[:4]
from eth_abi import encode as abi_encode
wrap_data = "0x" + wrap_sel.hex().replace("0x", "") + abi_encode(
["address", "address", "uint256"], [USDCE, DW, usdce]).hex()
wrap_call = pmcalls.TransactionCall(to=ONRAMP, data=wrap_data)
try:
h = client.execute_transaction(calls=[appr, wrap_call],
metadata="wrap USDC.e to pUSD")
wait_handle("batch approve+wrap", h)
except Exception as e:
print(f" batch failed ({type(e).__name__} {str(e)[:150]}) — "
"falling back to two sequential transactions")
try:
wait_handle("approve", client.execute_transaction(
calls=[appr], metadata="approve USDC.e to onramp"))
wait_handle("wrap", client.execute_transaction(
calls=[wrap_call], metadata="wrap USDC.e to pUSD"))
except Exception as e2:
print(f" sequential wrap FAILED too: {type(e2).__name__} {e2}")
sys.exit(1)
time.sleep(5)
print(f"\nfinal balances · native {fmt(bal(USDC))} · USDC.e "
f"{fmt(bal(USDCE))} · pUSD {fmt(bal(PUSD))}")
try:
b = client.get_balance_allowance(asset_type="COLLATERAL")
print("exchange-view COLLATERAL:", str(b)[:220])
except Exception as e:
print("balance check raised:", type(e).__name__, str(e)[:150])
print("\nDONE — success = exchange-view collateral ≈ the bankroll")
if __name__ == "__main__":
main()