Files
proxmox/scripts/lib/ei_matrix_multicall3_cwusdc_batch.py
defiQUG 4ebf2d7902
Some checks failed
Deploy to Phoenix / validate (push) Failing after 1s
Deploy to Phoenix / deploy (push) Has been skipped
Deploy to Phoenix / deploy-atomic-swap-dapp (push) Has been skipped
Deploy to Phoenix / cloudflare (push) Has been skipped
chore(repo): sync operator workspace (config, scripts, docs, multi-chain)
Add optional Cosmos/Engine-X/act-runner templates, CWUSDC/EI-matrix tooling,
non-EVM route planner in multi-chain-execution (tests passing), token list and
extraction updates, and documentation (MetaMask matrix, GRU/CWUSDC packets).

Ignore institutional evidence tarballs/sha256 under reports/status.

Validated with: bash scripts/verify/run-all-validation.sh --skip-genesis

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-11 16:25:08 -07:00

247 lines
9.9 KiB
Python

#!/usr/bin/env python3
"""
Batch mainnet cWUSDC to EI matrix wallets via canonical Multicall3 aggregate3.
Each inner call is transferFrom(deployer, recipient, amount) on the token, so
msg.sender is Multicall3. Requires a prior approve(deployer -> Multicall3) for
at least the sum of amounts in this run (one tx before batches).
Default Multicall3 (Ethereum): 0xcA11bde05977b3631167028862bE2a173976CA11
Examples:
python3 scripts/lib/ei_matrix_multicall3_cwusdc_batch.py --dry-run \\
--tsv reports/status/ei-matrix-cwusdc-topup-amounts.tsv
python3 scripts/lib/ei_matrix_multicall3_cwusdc_batch.py --execute \\
--tsv reports/status/ei-matrix-cwusdc-topup-amounts.tsv
Env: PRIVATE_KEY (or DEPLOYER_ADDRESS for dry-run calldata only), ETHEREUM_MAINNET_RPC,
CWUSDC_MAINNET (optional), MULTICALL3_MAINNET (optional), EI_MATRIX_MC_CHUNK (default 200).
"""
from __future__ import annotations
import argparse
import json
import os
import subprocess
import sys
import time
from pathlib import Path
MULTICALL3_MAINNET = "0xcA11bde05977b3631167028862bE2a173976CA11"
DEFAULT_CWUSDC = "0x2de5F116bFcE3d0f922d9C8351e0c5Fc24b9284a"
def _sh(cmd: list[str]) -> str:
r = subprocess.run(cmd, capture_output=True, text=True, check=False)
if r.returncode != 0:
raise RuntimeError(f"command failed: {' '.join(cmd)}\n{(r.stderr or r.stdout).strip()}")
return (r.stdout or "").strip()
def _deployer(pk: str | None) -> str:
if pk:
return _sh(["cast", "wallet", "address", "--private-key", pk])
env = (os.environ.get("DEPLOYER_ADDRESS") or os.environ.get("DEPLOYER") or "").strip()
if env:
return env
raise SystemExit("Set PRIVATE_KEY or DEPLOYER_ADDRESS for transferFrom(from=...)")
def _cast_calldata_transfer_from(from_addr: str, to_addr: str, amount: int) -> str:
out = _sh(["cast", "calldata", "transferFrom(address,address,uint256)", from_addr, to_addr, str(amount)])
return out if out.startswith("0x") else "0x" + out
def _cast_calldata_aggregate3(calls_tuple_str: str) -> str:
out = _sh(["cast", "calldata", "aggregate3((address,bool,bytes)[])", calls_tuple_str])
return out if out.startswith("0x") else "0x" + out
def _estimate_gas(from_addr: str, multicall: str, data: str, rpc_url: str) -> int:
payload = json.dumps({"from": from_addr, "to": multicall, "data": data})
raw = _sh(["cast", "rpc", "eth_estimateGas", payload, "--rpc-url", rpc_url])
return int(raw, 16)
def _allowance(token: str, owner: str, spender: str, rpc_url: str) -> int:
out = _sh(["cast", "call", token, "allowance(address,address)(uint256)", owner, spender, "--rpc-url", rpc_url])
return int(out.split()[0], 0)
def _send_cast_send(to: str, sig: str, args: list[str], rpc_url: str, pk: str, gas_limit: str | None) -> None:
cmd = ["cast", "send", to, sig, *args, "--rpc-url", rpc_url, "--private-key", pk]
if gas_limit:
cmd.extend(["--gas-limit", gas_limit])
print("", " ".join(cmd[:8]), "", file=sys.stderr)
r = subprocess.run(cmd, env={**os.environ})
if r.returncode != 0:
sys.exit(r.returncode)
def _send_raw_calldata(to: str, data: str, rpc_url: str, pk: str, gas_limit: str) -> None:
cmd = ["cast", "send", to, data, "--rpc-url", rpc_url, "--private-key", pk, "--gas-limit", gas_limit]
print("→ cast send", to[:10] + "", "--gas-limit", gas_limit, file=sys.stderr)
r = subprocess.run(cmd, env={**os.environ})
if r.returncode != 0:
sys.exit(r.returncode)
def main() -> int:
ap = argparse.ArgumentParser()
ap.add_argument("--tsv", required=True, help="linearIndex TAB amountRaw")
ap.add_argument("--grid", default="config/pmm-soak-wallet-grid.json")
ap.add_argument("--chunk-size", type=int, default=int(os.environ.get("EI_MATRIX_MC_CHUNK", "200")))
ap.add_argument("--multicall", default=os.environ.get("MULTICALL3_MAINNET", MULTICALL3_MAINNET))
ap.add_argument("--token", default=os.environ.get("CWUSDC_MAINNET", DEFAULT_CWUSDC))
ap.add_argument("--rpc-url", default=os.environ.get("ETHEREUM_MAINNET_RPC") or os.environ.get("RPC_URL_1") or "")
ap.add_argument("--dry-run", action="store_true")
ap.add_argument("--execute", action="store_true")
ap.add_argument("--gas-headroom-bps", type=int, default=13000)
ap.add_argument("--min-gas-per-batch", type=int, default=500_000)
ap.add_argument("--start-batch", type=int, default=0)
ap.add_argument("--max-batches", type=int, default=0, help="0 = all remaining")
ap.add_argument("--progress-file", default="reports/status/ei-matrix-multicall3-batch-progress.txt")
args = ap.parse_args()
if not args.rpc_url:
print("Need --rpc-url or ETHEREUM_MAINNET_RPC / RPC_URL_1", file=sys.stderr)
return 2
if args.dry_run == args.execute:
print("Specify exactly one of --dry-run or --execute", file=sys.stderr)
return 2
repo = Path(__file__).resolve().parents[2]
grid_path = repo / args.grid if not os.path.isabs(args.grid) else Path(args.grid)
tsv_path = repo / args.tsv if not os.path.isabs(args.tsv) else Path(args.tsv)
wallets = json.loads(grid_path.read_text(encoding="utf-8"))["wallets"]
rows: list[tuple[str, int]] = []
for line in tsv_path.read_text(encoding="utf-8").splitlines():
line = line.split("#", 1)[0].strip()
if not line:
continue
parts = line.split("\t")
if len(parts) < 2:
parts = line.split()
if len(parts) < 2:
continue
idx = int(parts[0])
amt = int(parts[1])
if amt <= 0:
continue
addr = wallets[idx]["address"]
rows.append((addr, amt))
if not rows:
print("No positive-amount rows in TSV.", file=sys.stderr)
return 0
pk = os.environ.get("PRIVATE_KEY", "").strip() or None
if args.execute and not pk:
print("PRIVATE_KEY required for --execute", file=sys.stderr)
return 2
deployer = _deployer(pk)
mc = args.multicall
token = args.token
all_chunks: list[list[tuple[str, int]]] = []
for i in range(0, len(rows), args.chunk_size):
all_chunks.append(rows[i : i + args.chunk_size])
start_b = max(0, args.start_batch)
if args.max_batches > 0:
end_b = min(len(all_chunks), start_b + args.max_batches)
else:
end_b = len(all_chunks)
chunks = all_chunks[start_b:end_b]
budget_raw = sum(amt for c in chunks for _, amt in c)
if not chunks:
print("No batches in range.", file=sys.stderr)
return 0
print(
f"batches {start_b}..{end_b - 1} of {len(all_chunks)} transfers={sum(len(c) for c in chunks)} "
f"budget_raw={budget_raw}",
file=sys.stderr,
)
if args.dry_run:
try:
allow = _allowance(token, deployer, mc, args.rpc_url)
except Exception:
allow = 0
print(f"# allowance Multicall3: {allow} budget_this_run: {budget_raw}", file=sys.stderr)
if allow < budget_raw:
print(
f"cast send {token} \"approve(address,uint256)\" {mc} {budget_raw} \\\n"
f" --rpc-url \"$ETHEREUM_MAINNET_RPC\" --private-key \"$PRIVATE_KEY\" --gas-limit 120000",
file=sys.stderr,
)
chunk = chunks[0]
parts = []
for addr, amt in chunk:
data = _cast_calldata_transfer_from(deployer, addr, amt)
parts.append(f"({token},false,{data})")
tuple_str = "[" + ",".join(parts) + "]"
calldata = _cast_calldata_aggregate3(tuple_str)
gl = args.min_gas_per_batch + 65_000 * len(chunk)
sample_hex = repo / "reports/status/ei-matrix-multicall3-dryrun-sample-batch.hex"
sample_hex.write_text(calldata + "\n", encoding="utf-8")
rel = os.path.relpath(str(sample_hex), str(repo))
print(f"\n# sample batch 0 n={len(chunk)} gas_limit~{gl}", file=sys.stderr)
print(f"# calldata written: {rel}", file=sys.stderr)
print(
f"cast send {mc} $(cat {rel}) --rpc-url \"$ETHEREUM_MAINNET_RPC\" \\\n"
f" --private-key \"$PRIVATE_KEY\" --gas-limit {gl}"
)
print(f"\n# … {len(chunks)} batches total (chunk_size={args.chunk_size})", file=sys.stderr)
return 0
assert pk is not None
allow = _allowance(token, deployer, mc, args.rpc_url)
if allow < budget_raw:
print(f"Approving Multicall3 for {budget_raw} raw (was {allow})", file=sys.stderr)
_send_cast_send(token, "approve(address,uint256)", [mc, str(budget_raw)], args.rpc_url, pk, "120000")
time.sleep(2)
allow2 = _allowance(token, deployer, mc, args.rpc_url)
if allow2 < budget_raw:
print(f"Allowance insufficient: {allow2} < {budget_raw}", file=sys.stderr)
return 1
progress_path = repo / args.progress_file
progress_path.parent.mkdir(parents=True, exist_ok=True)
for bi, chunk in enumerate(chunks):
global_batch_idx = start_b + bi
parts = []
for addr, amt in chunk:
data = _cast_calldata_transfer_from(deployer, addr, amt)
parts.append(f"({token},false,{data})")
tuple_str = "[" + ",".join(parts) + "]"
calldata = _cast_calldata_aggregate3(tuple_str)
gas_est = args.min_gas_per_batch
try:
gas_est = _estimate_gas(deployer, mc, calldata, args.rpc_url)
except Exception as e:
print(f"[warn] estimateGas failed, fallback: {e}", file=sys.stderr)
gas_est = 70_000 * len(chunk) + 400_000
gas_with_headroom = max(args.min_gas_per_batch, (gas_est * args.gas_headroom_bps + 9999) // 10000)
print(f"Batch {global_batch_idx}: n={len(chunk)} estimate={gas_est} limit={gas_with_headroom}", file=sys.stderr)
_send_raw_calldata(mc, calldata, args.rpc_url, pk, str(gas_with_headroom))
progress_path.write_text(f"{global_batch_idx}\n", encoding="utf-8")
time.sleep(1)
print("Done.", file=sys.stderr)
return 0
if __name__ == "__main__":
raise SystemExit(main())