#!/usr/bin/env bash # Chain 138 → Ethereum Mainnet only: bridge PCT of each canonical c* balance via CWMultiTokenBridgeL1. # Default PCT: 17.25% (= 1725 basis points of 10000; override with PCT_BP). # # Modes: # --plan-only Write JSON + summary (default) # --check-routes destinations(token, Mainnet selector) for each token # --emit-cmds Print approve + lockAndSend cast lines (review before running) # # Env: PRIVATE_KEY (for --emit-cmds deployer address), RPC_URL_138, CW_L1_BRIDGE_CHAIN138, # LINK_TOKEN_CHAIN138, RECIPIENT_ADDRESS (default: deployer) # PCT_BP (default 1725 = 17.25%) set -euo pipefail SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" SMOM_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)" PROXMOX_ROOT="$(cd "$SMOM_ROOT/.." && pwd)" cd "$SMOM_ROOT" MODE="plan" while [[ $# -gt 0 ]]; do case "$1" in --plan-only) MODE="plan" ;; --check-routes) MODE="check" ;; --emit-cmds) MODE="emit" ;; --help|-h) grep '^#' "$0" | head -22 exit 0 ;; *) echo "Unknown: $1"; exit 1 ;; esac shift || true done if [[ -f "$PROXMOX_ROOT/scripts/lib/load-project-env.sh" ]]; then # shellcheck disable=SC1090 PROJECT_ROOT="$PROXMOX_ROOT" source "$PROXMOX_ROOT/scripts/lib/load-project-env.sh" elif [[ -f .env ]]; then set -a && source .env && set +a fi PCT_BP="${PCT_BP:-1725}" RPC="${RPC_URL_138:-${CHAIN138_RPC:-${RPC_URL:-http://192.168.11.211:8545}}}" OUT_JSON="${OUT_JSON:-$SMOM_ROOT/reports/status/c138-bridge-mainnet-pct-latest.json}" BRIDGE="${CW_L1_BRIDGE_CHAIN138:-}" DEPLOYER="" if [[ -n "${PRIVATE_KEY:-}" ]]; then DEPLOYER="$(cast wallet address "$PRIVATE_KEY" 2>/dev/null || true)" fi RECIPIENT="${RECIPIENT_ADDRESS:-$DEPLOYER}" MAINNET_SEL=5009297550715157269 export RPC OUT_JSON DEPLOYER RECIPIENT BRIDGE PCT_BP MAINNET_SEL # Canonical c* only (EXPLORER_TOKEN_LIST_CROSSCHECK §5) read -r -d '' TOKEN_ROWS << 'EOF' || true cUSDT:0x93E66202A11B1772E55407B32B44e5Cd8eda7f22 cUSDC:0xf22258f57794CC8E06237084b353Ab30fFfa640b cEURC:0x8085961F9cF02b4d800A3c6d386D31da4B34266a cEURT:0xdf4b71c61E5912712C1Bdd451416B9aC26949d72 cGBPC:0x003960f16D9d34F2e98d62723B6721Fb92074aD2 cGBPT:0x350f54e4D23795f86A9c03988c7135357CCaD97c cAUDC:0xD51482e567c03899eecE3CAe8a058161FD56069D cJPYC:0xEe269e1226a334182aace90056EE4ee5Cc8A6770 cCHFC:0x873990849DDa5117d7C644f0aF24370797C03885 cCADC:0x54dBd40cF05e15906A2C21f600937e96787f5679 cXAUC:0x290E52a8819A4fbD0714E517225429aA2B70EC6b cXAUT:0x94e408E26c6FD8F4ee00b54dF19082FDA07dC96E EOF export TOKEN_ROWS mkdir -p "$(dirname "$OUT_JSON")" python3 << PY import json, subprocess, os, re rpc = os.environ.get("RPC", "http://192.168.11.211:8545") deployer = os.environ.get("DEPLOYER", "") recipient = os.environ.get("RECIPIENT", deployer) bridge = os.environ.get("BRIDGE", "") pct_bp = int(os.environ.get("PCT_BP", "1725")) sel = int(os.environ.get("MAINNET_SEL", "5009297550715157269")) rows = [] for line in os.environ.get("TOKEN_ROWS", "").strip().split("\n"): if not line.strip(): continue sym, addr = line.split(":", 1) rows.append((sym.strip(), addr.strip())) def balance_of(addr): if not deployer: return None r = subprocess.run( ["cast", "call", addr, "balanceOf(address)(uint256)", deployer, "--rpc-url", rpc], capture_output=True, text=True, ) if r.returncode != 0: return None m = re.match(r"^\s*(\d+)", r.stdout.strip()) return int(m.group(1)) if m else None plan = { "schema": "c138-bridge-mainnet-pct/v1", "rpc_url": rpc, "deployer": deployer, "recipient": recipient, "cw_l1_bridge": bridge, "destination": "Ethereum Mainnet", "chain_selector": str(sel), "pct_basis_points": pct_bp, "pct_human": f"{pct_bp / 100:.2f}%", "tokens": [], } for sym, addr in rows: bal = balance_of(addr) if bal is None: plan["tokens"].append({"symbol": sym, "address": addr, "error": "balance_of_failed"}) continue amt = bal * pct_bp // 10000 plan["tokens"].append({ "symbol": sym, "address": addr, "balance_wei": str(bal), "amount_to_bridge_wei": str(amt), }) path = os.environ.get("OUT_JSON", "") with open(path, "w") as f: json.dump(plan, f, indent=2) print(json.dumps({"written": path, "tokens": len(plan["tokens"])})) PY python3 - <<'PY' import json, os with open(os.environ["OUT_JSON"]) as f: p = json.load(f) print("\n=== c* → Mainnet only:", p.get("pct_human"), "of balance (integer base units) ===\n") print(f"Deployer: {p.get('deployer','?')}\nRecipient: {p.get('recipient','?')}\nBridge: {p.get('cw_l1_bridge') or '(unset)'}\n") for t in p["tokens"]: if "error" in t: print(f"{t['symbol']}: {t['error']}") continue sym = t["symbol"] amt = int(t["amount_to_bridge_wei"]) if sym.startswith("cXAU"): print(f"{sym:<10} bridge: {amt / 1e6:,.6f} troy oz (wei={t['amount_to_bridge_wei']})") else: print(f"{sym:<10} bridge: {amt / 1e6:,.6f} tokens (wei={t['amount_to_bridge_wei']})") print("\nJSON:", os.environ["OUT_JSON"]) PY if [[ "$MODE" == "plan" ]]; then exit 0 fi [[ -n "$BRIDGE" ]] || { echo "Set CW_L1_BRIDGE_CHAIN138"; exit 1; } code=$(cast code "$BRIDGE" --rpc-url "$RPC" 2>/dev/null || echo "0x") [[ -n "$code" && "$code" != "0x" ]] || { echo "No contract at CW_L1_BRIDGE_CHAIN138=$BRIDGE"; exit 1; } if [[ "$MODE" == "check" ]]; then echo "" echo "=== Mainnet route: $BRIDGE ===" while IFS= read -r line; do [[ -z "$line" ]] && continue sym="${line%%:*}" addr="${line#*:}" dest=$(cast call "$BRIDGE" "destinations(address,uint64)(address,bool)" "$addr" "$MAINNET_SEL" --rpc-url "$RPC" 2>/dev/null || echo "ERR") echo "$sym ($addr): destinations(Mainnet)=$dest" done <<< "$TOKEN_ROWS" exit 0 fi if [[ "$MODE" == "emit" ]]; then [[ -n "${PRIVATE_KEY:-}" ]] || { echo "PRIVATE_KEY required for --emit-cmds"; exit 1; } [[ -n "$RECIPIENT" ]] || { echo "RECIPIENT_ADDRESS or deployer required"; exit 1; } LINK_TOKEN="${LINK_TOKEN_CHAIN138:-${LINK_TOKEN:-}}" [[ -n "$LINK_TOKEN" ]] || { echo "Set LINK_TOKEN or LINK_TOKEN_CHAIN138 for fee approval lines"; exit 1; } export LINK_TOKEN OUT_CAST="${OUT_CAST:-$SMOM_ROOT/reports/status/c138-bridge-mainnet-pct-cast-commands.sh}" export OUT_CAST { echo "#!/usr/bin/env bash" echo "# Generated: c138-cw-bridge-mainnet-pct.sh --emit-cmds" echo "# Review fee + reserve verifier. Fund LINK on deployer for CCIP fees." echo "set -euo pipefail" python3 <<'PY' import json, os, subprocess rpc = os.environ["RPC"] bridge = os.environ["BRIDGE"] recipient = os.environ["RECIPIENT"] link = os.environ["LINK_TOKEN"] sel = int(os.environ["MAINNET_SEL"]) with open(os.environ["OUT_JSON"]) as f: plan = json.load(f) for t in plan["tokens"]: if "error" in t: continue amt = t.get("amount_to_bridge_wei", "0") if int(amt) == 0: continue sym, token = t["symbol"], t["address"] chk = subprocess.run( ["cast", "call", bridge, "destinations(address,uint64)(address,bool)", token, str(sel), "--rpc-url", rpc], capture_output=True, text=True, ) if chk.returncode != 0 or "true" not in chk.stdout: print(f"# SKIP {sym}: destination Mainnet not enabled or query failed") print(f"# cast output: {chk.stdout.strip()}") continue fee = subprocess.run( ["cast", "call", bridge, "calculateFee(address,uint64,address,uint256)(uint256)", token, str(sel), recipient, amt, "--rpc-url", rpc], capture_output=True, text=True, ) fq = fee.stdout.strip().split()[0] if fee.returncode == 0 else "0" print("") print(f"# {sym} -> Mainnet amount={amt} fee_wei={fq}") print(f"cast send {link} \"approve(address,uint256)\" {bridge} {fq} --rpc-url {rpc} --private-key \"$PRIVATE_KEY\" --legacy --gas-limit 120000") print(f"cast send {token} \"approve(address,uint256)\" {bridge} {amt} --rpc-url {rpc} --private-key \"$PRIVATE_KEY\" --legacy --gas-limit 400000") print(f"cast send {bridge} \"lockAndSend(address,uint64,address,uint256)\" {token} {sel} {recipient} {amt} --rpc-url {rpc} --private-key \"$PRIVATE_KEY\" --legacy --gas-limit 4000000") PY } > "$OUT_CAST" chmod +x "$OUT_CAST" echo "Wrote: $OUT_CAST" wc -l "$OUT_CAST" exit 0 fi