#!/usr/bin/env bash # List pending txs on 2103 that target the EIP-2470 singleton and decode deploy(bytes) initcode. # Usage: ./scripts/verify/decode-singleton-deploy-pending-2103.sh # Optional: RPC_URL_2103=... PENDING_TX_FROM=0xB2dE... (stuck tx signer; avoids operator DEPLOYER from dotenv) SINGLETON=0x4e59... set -euo pipefail RPC_URL="${RPC_URL_2103:-http://192.168.11.217:8545}" PENDING_TX_FROM="${PENDING_TX_FROM:-0xB2dEA0e264ddfFf91057A3415112e57A1a5Eac14}" SINGLETON="${SINGLETON:-0x4e59b44847b379578588920cA78FbF26c0B4956C}" OUT_DIR="${OUT_DIR:-/tmp/singleton-decodes-$(date +%Y%m%d%H%M%S)}" export RPC_URL PENDING_TX_FROM SINGLETON OUT_DIR DEPLOYER="${PENDING_TX_FROM}" export DEPLOYER mkdir -p "$OUT_DIR" echo "RPC: $RPC_URL pending from filter: $PENDING_TX_FROM singleton: $SINGLETON" echo "Output: $OUT_DIR" echo "" python3 <<'PY' import json, sys, subprocess, os, urllib.request rpc = os.environ["RPC_URL"] dep = os.environ["DEPLOYER"].lower() sing = os.environ["SINGLETON"].lower() out = os.environ["OUT_DIR"] sel_4 = bytes.fromhex("00774360") req = urllib.request.Request( rpc, data=json.dumps({"jsonrpc": "2.0", "method": "txpool_besuPendingTransactions", "params": [], "id": 1}).encode(), headers={"Content-Type": "application/json"}, ) raw = urllib.request.urlopen(req, timeout=15).read() d = json.loads(raw.decode()) if d.get("error"): print("RPC error:", d["error"]) sys.exit(1) txs = d.get("result") or [] print("Total pending on 2103:", len(txs)) matched = [] for t in txs: to = (t.get("to") or "").lower() fr = (t.get("from") or "").lower() if to != sing: continue if fr != dep: continue matched.append(t) print("Matching deployer -> singleton:", len(matched)) if not matched: print("Nothing to decode. Re-run when those txs are in 2103's pool, or paste a raw tx into this script.") sys.exit(0) for i, t in enumerate(matched): h = t.get("hash", "unknown") raw_hex = t.get("input") or "0x" if not raw_hex.startswith("0x"): raw_hex = "0x" + raw_hex inp = raw_hex[2:] b = bytes.fromhex(inp) j = b.find(sel_4) if j >= 0 and j != 0: print("INFO", h, "deploy(bytes) selector 0x00774360 at byte offset", j, "(not at 0; Thirdweb / padding)") b = b[j:] if len(b) < 4 or b[:4] != sel_4: p = os.path.join(out, f"full_calldata_{i+1}_{h[:10]}.hex") with open(p, "w") as f: f.write(raw_hex.rstrip() + "\n") if j < 0: print("WARN", h, "no 0x00774360 in this `input` — on-chain `eth_call` may still use full RPC `input` (see dry-run-thirdweb-singleton-2103.sh) ->", p) else: print("WARN", h, "not deploy(bytes) 0x00774360; saved for replay ->", p) continue if len(b) < 4 + 32 + 32: print("Input too short for ABI deploy(bytes)", h, "len", len(b)) continue off = int.from_bytes(b[4:36], "big") if 4 + off + 32 > len(b): print("Bad offset", off, h) continue ln = int.from_bytes(b[4 + off : 4 + off + 32], "big") init = b[4 + off + 32 : 4 + off + 32 + ln] if len(init) != ln: print("Length mismatch", h, ln, len(init)) continue path = os.path.join(out, f"initcode_{i+1}_{h[:10]}.hex") with open(path, "w") as f: f.write("0x" + init.hex()) print("---", h, "---") print(" nonce:", t.get("nonce"), " gas:", t.get("gas"), " initcode bytes:", len(init), "->", path) try: r = subprocess.run( ["cast", "keccak", "0x" + init.hex()], capture_output=True, text=True, timeout=5, check=False, ) if r.returncode == 0: print(" keccak256(initcode):", r.stdout.strip()) except FileNotFoundError: pass # After slice, b is body starting at 0x00774360; full tx `input` for the node is still raw_hex bl = len(b) print(" Body from selector (for ABI decode only):", bl, "bytes") def _parse_gas(x): if isinstance(x, int): return x s = str(x).strip() if s.lower().startswith("0x"): return int(s, 16) return int(s, 10) g = t.get("gas", "0x500000") try: gl = _parse_gas(g) except (ValueError, TypeError): gl = 5242880 print(" Example: cast send -r", rpc, "--private-key --gas-limit", gl, sing, raw_hex) print() PY echo "Done. Files under $OUT_DIR"