Files
smom-dbis-138/scripts/deployment/c138-cw-bridge-75-split.sh

292 lines
10 KiB
Bash
Executable File
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env bash
# Plan + verify Chain 138 → 10 networks: move 75% of each c* balance split evenly (7.5% per network).
# Uses CWMultiTokenBridgeL1 on 138 (CW_L1_BRIDGE_CHAIN138) when routes are configured.
#
# Modes:
# --plan-only Write JSON table + human summary (default)
# --check-routes cast call supportedCanonicalToken + destinations per token×chain
# --emit-cmds Print approve + lockAndSend cast lines (dry; review before running)
# --help
#
# Env: PRIVATE_KEY, RPC_URL_138, CW_L1_BRIDGE_CHAIN138, smom-dbis-138/.env
# Optional: RECIPIENT_ADDRESS (default: deployer), OUT_JSON (default: reports/status/c138-bridge-75-split-latest.json)
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 -20
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
RPC="${RPC_URL_138:-${CHAIN138_RPC:-${RPC_URL:-http://192.168.11.211:8545}}}"
OUT_JSON="${OUT_JSON:-$SMOM_ROOT/reports/status/c138-bridge-75-split-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}"
export RPC OUT_JSON DEPLOYER RECIPIENT BRIDGE
# CCIP chain selectors (Chainlink CCIP mainnet directory / repo BRIDGE_CONFIGURATION.md). Verify before prod.
declare -A SELECTOR=(
[Mainnet]=5009297550715157269
[Optimism]=3734403246176062136
[Cronos]=1456215246176062136
[BSC]=11344663589394136015
[Gnosis]=465200170687744372
[Polygon]=4051577828743386545
[Base]=15971525489660198786
[Arbitrum]=4949039107694359620
[Celo]=1346049177634351622
[Avalanche]=6433500567565415381
)
# name:address (Compliant / canonical c* on 138)
read -r -d '' TOKEN_ROWS << 'EOF' || true
cUSDT:0x93E66202A11B1772E55407B32B44e5Cd8eda7f22
cUSDC:0xf22258f57794CC8E06237084b353Ab30fFfa640b
cUSDT_V2:0x8d342d321DdEe97D0c5011DAF8ca0B59DA617D29
cUSDC_V2:0x1ac3F4942a71E86A9682D91837E1E71b7BACdF99
cEURC:0x8085961F9cF02b4d800A3c6d386D31da4B34266a
cEURT:0xdf4b71c61E5912712C1Bdd451416B9aC26949d72
cGBPC:0x003960f16D9d34F2e98d62723B6721Fb92074aD2
cGBPT:0x350f54e4D23795f86A9c03988c7135357CCaD97c
cAUDC:0xD51482e567c03899eecE3CAe8a058161FD56069D
cJPYC:0xEe269e1226a334182aace90056EE4ee5Cc8A6770
cCHFC:0x873990849DDa5117d7C644f0aF24370797C03885
cCADC:0x54dBd40cF05e15906A2C21f600937e96787f5679
cAUDT:0xC034b8Ff3088f644D492E95619720ba8fB582933
cJPYT:0x54fb3A6b16163D8cFa48EAff79205D1309B1a9A1
cCHFT:0xd91f31725444dD1F53FA6dE236A5e90a8281d970
cCADT:0xAb456be5Db1E1069F55F75E8c8fecAa6a71D1c8F
cXAUC:0x290E52a8819A4fbD0714E517225429aA2B70EC6b
cXAUT:0x94e408E26c6FD8F4ee00b54dF19082FDA07dC96E
cAUSDT:0x5fdDF65733e3d590463F68f93Cf16E8c04081271
EOF
export TOKEN_ROWS
mkdir -p "$(dirname "$OUT_JSON")"
python3 << PY
import json, subprocess, os, re, sys
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", "")
selectors = {
"Mainnet": 5009297550715157269,
"Optimism": 3734403246176062136,
"Cronos": 1456215246176062136,
"BSC": 11344663589394136015,
"Gnosis": 465200170687744372,
"Polygon": 4051577828743386545,
"Base": 15971525489660198786,
"Arbitrum": 4949039107694359620,
"Celo": 1346049177634351622,
"Avalanche": 6433500567565415381,
}
n_chains = len(selectors)
rows = []
for line in os.environ.get("TOKEN_ROWS", "").strip().split("\n"):
if not line.strip() or line.startswith("#"):
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-75-split/v1",
"rpc_url": rpc,
"deployer": deployer,
"recipient": recipient,
"cw_l1_bridge": bridge,
"split": "75% of balance divided evenly across 10 networks (7.5% per chain, integer base units)",
"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
q75 = bal * 75 // 100
per = q75 // n_chains
rem = q75 % n_chains
entry = {
"symbol": sym,
"address": addr,
"balance_wei": str(bal),
"pct_75_wei": str(q75),
"per_chain_wei": str(per),
"remainder_wei": str(rem),
"chains": {},
}
for cname, sel in selectors.items():
entry["chains"][cname] = {"selector": str(sel), "amount_wei": str(per)}
plan["tokens"].append(entry)
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
export RPC DEPLOYER RECIPIENT BRIDGE OUT_JSON
export TOKEN_ROWS="$TOKEN_ROWS"
python3 - <<'PY'
import json, os
with open(os.environ["OUT_JSON"]) as f:
p = json.load(f)
print("\n=== c* 75% / 10 networks (per-chain amount, 6 dp human for fiat-style) ===\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"]
per = int(t["per_chain_wei"])
if sym.startswith("cXAU"):
hu = per / 1e6
print(f"{sym:<10} per chain: {hu:,.6f} troy oz (wei={t['per_chain_wei']})")
else:
hu = per / 1e6
print(f"{sym:<10} per chain: {hu:,.6f} tokens (wei={t['per_chain_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 "=== Route checks: $BRIDGE ==="
while IFS= read -r line; do
[[ -z "$line" ]] && continue
sym="${line%%:*}"
addr="${line#*:}"
if sup_raw=$(cast call "$BRIDGE" "supportedCanonicalToken(address)(bool)" "$addr" --rpc-url "$RPC" 2>/dev/null); then
echo "$sym supported=$sup_raw"
else
echo "$sym supported=(query reverted — non-CW ABI or older build; rely on destinations below)"
fi
for net in Mainnet Optimism Cronos BSC Gnosis Polygon Base Arbitrum Celo Avalanche; do
sel="${SELECTOR[$net]}"
dest=$(cast call "$BRIDGE" "destinations(address,uint64)(address,bool)" "$addr" "$sel" --rpc-url "$RPC" 2>/dev/null || echo "ERR")
echo " $net ($sel): $dest"
done
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
echo ""
echo "=== Review-only cast snippets (feeToken=LINK on this bridge: approve LINK, approve token, lockAndSend) ==="
OUT_CAST="${OUT_CAST:-$SMOM_ROOT/reports/status/c138-bridge-75-split-cast-commands.sh}"
export OUT_CAST
{
echo "#!/usr/bin/env bash"
echo "# Generated: c138-cw-bridge-75-split.sh --emit-cmds"
echo "# Review destinations + reserve verifier before running. Fund LINK for fees."
echo "set -euo pipefail"
# Quoted heredoc: do not let bash expand \$PRIVATE_KEY into the generated file.
python3 <<'PY'
import json, os, subprocess
rpc = os.environ["RPC"]
bridge = os.environ["BRIDGE"]
recipient = os.environ["RECIPIENT"]
link = os.environ["LINK_TOKEN"]
with open(os.environ["OUT_JSON"]) as f:
plan = json.load(f)
selectors = {
"Mainnet": 5009297550715157269,
"Optimism": 3734403246176062136,
"Cronos": 1456215246176062136,
"BSC": 11344663589394136015,
"Gnosis": 465200170687744372,
"Polygon": 4051577828743386545,
"Base": 15971525489660198786,
"Arbitrum": 4949039107694359620,
"Celo": 1346049177634351622,
"Avalanche": 6433500567565415381,
}
for t in plan["tokens"]:
if "error" in t or int(t.get("per_chain_wei", 0)) == 0:
continue
sym, token, amt = t["symbol"], t["address"], t["per_chain_wei"]
for cname, sel in selectors.items():
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:
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} -> {cname} selector={sel} 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 enabled-routes-only commands: $OUT_CAST"
wc -l "$OUT_CAST"
exit 0
fi