From c71448a090cd8d773b4dd392154726dca3af8776 Mon Sep 17 00:00:00 2001 From: jaxperro Date: Fri, 10 Jul 2026 00:10:36 -0400 Subject: [PATCH] =?UTF-8?q?host:=20wrap=5Fvia=5Fbridge.py=20=E2=80=94=20ba?= =?UTF-8?q?nkroll=20conversion=20via=20the=20sanctioned=20bridge=20path?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit On-chain forensics (2026-07-10): pUSD accepts native USDC by contract, but the public CollateralOnramp has native USDC PAUSED (paused()=1) — the exact cause of the direct-wrap revert. Bridge native→USDC.e (Polymarket Bridge, the UI deposit flow, ~$0.005 on $24.73 per quote), then gasless approve+wrap USDC.e→pUSD. Stage-gated: $3 test slice first. Co-Authored-By: Claude Fable 5 --- host/wrap_via_bridge.py | 192 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 192 insertions(+) create mode 100644 host/wrap_via_bridge.py diff --git a/host/wrap_via_bridge.py b/host/wrap_via_bridge.py new file mode 100644 index 00000000..c37685a4 --- /dev/null +++ b/host/wrap_via_bridge.py @@ -0,0 +1,192 @@ +#!/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 bridge_leg(client, calls_mod, amount, bridge_addr, label): + """Gasless-transfer `amount` native USDC to the bridge and wait until the + deposit wallet's USDC.e balance rises by ~amount. Returns True on delivery.""" + before = bal(USDCE) + 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 = bal(USDCE) + 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" USDC.e {fmt(now)} (Δ{fmt(now-before)}) · bridge status: {latest}") + # 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) + 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: + sys.exit("no USDC.e at the wallet — nothing to wrap, aborting") + 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()