Files
proxmox/scripts/lib/ei_matrix_onchain_readiness_audit.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

300 lines
12 KiB
Python

#!/usr/bin/env python3
"""
On-chain readiness audit for EI matrix wallets (config/pmm-soak-wallet-grid.json).
Queries ERC-20 balanceOf for each address on one or both chains:
- Ethereum mainnet cWUSDC (default from env CWUSDC_MAINNET)
- Chain 138 cUSDC (default canonical CompliantUSDC)
Use for strength profiling: segment by class/lpbca via --report-by-class, find gaps vs thresholds.
Environment (optional defaults for thresholds):
EI_MATRIX_AUDIT_MIN_MAINNET_RAW, EI_MATRIX_AUDIT_MIN_138_RAW, EI_MATRIX_AUDIT_WORKERS
Examples:
python3 scripts/lib/ei_matrix_onchain_readiness_audit.py --mainnet-only --min-mainnet-raw 1
python3 scripts/lib/ei_matrix_onchain_readiness_audit.py --both \\
--shard-size 400 --min-mainnet-raw 12000000 --min-138-raw 0 --workers 3 \\
--report-by-class --json-out reports/status/ei-matrix-readiness-audit-latest.json
"""
from __future__ import annotations
import argparse
import json
import os
import sys
import urllib.error
import urllib.request
from concurrent.futures import ThreadPoolExecutor, as_completed
from pathlib import Path
# balanceOf(address) selector
BALANCE_OF = bytes.fromhex("70a08231")
ADDR_PAD = 12 * b"\x00"
def encode_balance_of_call(addr: str) -> str:
a = addr.lower().removeprefix("0x")
if len(a) != 40:
raise ValueError(f"bad address {addr}")
data = BALANCE_OF + ADDR_PAD + bytes.fromhex(a)
return "0x" + data.hex()
def rpc_eth_call(to: str, data: str, rpc_url: str, timeout: float = 30.0) -> str:
body = json.dumps(
{
"jsonrpc": "2.0",
"id": 1,
"method": "eth_call",
"params": [{"to": to, "data": data}, "latest"],
}
).encode()
req = urllib.request.Request(rpc_url, data=body, headers={"Content-Type": "application/json"}, method="POST")
with urllib.request.urlopen(req, timeout=timeout) as r:
j = json.loads(r.read().decode())
if "error" in j:
raise RuntimeError(str(j["error"]))
return j.get("result") or "0x0"
def hex_to_int(h: str) -> int:
h = h.strip()
if not h or h == "0x":
return 0
return int(h, 16)
def collect_rows_for_slice(
slice_items: list[tuple[int, dict]],
*,
do_main: bool,
do_138: bool,
mainnet_rpc: str,
chain138_rpc: str,
mainnet_token: str,
chain138_cusdc: str,
workers: int,
) -> list[dict]:
def fetch_one(item: tuple[int, dict]) -> tuple[int, dict, int, int]:
idx, w = item
addr = w["address"]
mbal, bbal = 0, 0
if do_main:
calldata = encode_balance_of_call(addr)
res = rpc_eth_call(mainnet_token.lower(), calldata, mainnet_rpc)
mbal = hex_to_int(res)
if do_138:
calldata = encode_balance_of_call(addr)
res = rpc_eth_call(chain138_cusdc.lower(), calldata, chain138_rpc)
bbal = hex_to_int(res)
return idx, w, mbal, bbal
rows: list[dict] = []
with ThreadPoolExecutor(max_workers=max(1, workers)) as ex:
futs = [ex.submit(fetch_one, it) for it in slice_items]
for fut in as_completed(futs):
idx, w, mbal, bbal = fut.result()
cls = int(w.get("class", 0))
row = {
"linearIndex": idx,
"address": w["address"],
"cellId": w.get("cellId"),
"class": cls,
"mainnetCwusdcRaw": mbal if do_main else None,
"chain138CusdcRaw": bbal if do_138 else None,
}
rows.append(row)
return rows
def write_indices(path: Path, indices: list[int]) -> None:
path.parent.mkdir(parents=True, exist_ok=True)
path.write_text("\n".join(str(i) for i in indices) + ("\n" if indices else ""), encoding="utf-8")
def main() -> int:
ap = argparse.ArgumentParser()
ap.add_argument("--grid", default="config/pmm-soak-wallet-grid.json")
ap.add_argument("--offset", type=int, default=0)
ap.add_argument("--limit", type=int, default=0, help="0 = all from offset to grid end")
ap.add_argument(
"--shard-size",
type=int,
default=int(os.environ.get("EI_MATRIX_AUDIT_SHARD_SIZE", "0")),
help="If >0, query in sequential shards of this size (eases RPC load). 0 = single batch.",
)
ap.add_argument("--workers", type=int, default=int(os.environ.get("EI_MATRIX_AUDIT_WORKERS", "4")))
ap.add_argument("--mainnet-only", action="store_true")
ap.add_argument("--chain138-only", action="store_true")
ap.add_argument("--both", action="store_true")
ap.add_argument("--mainnet-rpc", default=os.environ.get("ETHEREUM_MAINNET_RPC") or os.environ.get("RPC_URL_1") or "")
ap.add_argument("--chain138-rpc", default=os.environ.get("RPC_URL_138") or os.environ.get("CHAIN138_PUBLIC_RPC_URL") or "")
ap.add_argument("--mainnet-token", default=os.environ.get("CWUSDC_MAINNET", "0x2de5F116bFcE3d0f922d9C8351e0c5Fc24b9284a"))
ap.add_argument(
"--chain138-cusdc",
default=os.environ.get("CUSDC_CHAIN138", "0xf22258f57794CC8E06237084b353Ab30fFfa640b"),
)
ap.add_argument(
"--min-mainnet-raw",
type=int,
default=int(os.environ.get("EI_MATRIX_AUDIT_MIN_MAINNET_RAW", "0")),
help="fail wallets strictly below this (mainnet); env EI_MATRIX_AUDIT_MIN_MAINNET_RAW",
)
ap.add_argument(
"--min-138-raw",
type=int,
default=int(os.environ.get("EI_MATRIX_AUDIT_MIN_138_RAW", "0")),
help="fail wallets strictly below this (138); env EI_MATRIX_AUDIT_MIN_138_RAW",
)
ap.add_argument("--report-by-class", action="store_true", help="aggregate counts by matrix class 0..5")
ap.add_argument("--json-out", default="", help="write full per-wallet rows + summary")
ap.add_argument(
"--gaps-mainnet-out",
default="",
help="write newline-separated linear indices below mainnet minimum (only if mainnet queried)",
)
ap.add_argument(
"--gaps-138-out",
default="",
help="write newline-separated linear indices below 138 minimum (only if 138 queried)",
)
ap.add_argument("--max-list", type=int, default=200, help="max gap indices to print on stderr")
args = ap.parse_args()
repo = Path(__file__).resolve().parents[2]
grid_path = repo / args.grid if not os.path.isabs(args.grid) else Path(args.grid)
data = json.loads(grid_path.read_text(encoding="utf-8"))
wallets: list[dict] = data["wallets"]
n = len(wallets)
scan_end = n if args.limit <= 0 else min(n, args.offset + args.limit)
scan_start = args.offset
if scan_start < 0 or scan_start > n:
print("Invalid --offset", file=sys.stderr)
return 2
if scan_end < scan_start:
print("Invalid --limit / range", file=sys.stderr)
return 2
do_main = args.mainnet_only or args.both
do_138 = args.chain138_only or args.both
if not do_main and not do_138:
print("Specify --mainnet-only, --chain138-only, or --both", file=sys.stderr)
return 2
if do_main and not args.mainnet_rpc:
print("Need --mainnet-rpc or ETHEREUM_MAINNET_RPC / RPC_URL_1", file=sys.stderr)
return 2
if do_138 and not args.chain138_rpc:
print("Need --chain138-rpc or RPC_URL_138", file=sys.stderr)
return 2
shard = max(0, args.shard_size)
rows: list[dict] = []
if shard <= 0:
slice_items = list(enumerate(wallets[scan_start:scan_end], start=scan_start))
rows = collect_rows_for_slice(
slice_items,
do_main=do_main,
do_138=do_138,
mainnet_rpc=args.mainnet_rpc,
chain138_rpc=args.chain138_rpc,
mainnet_token=args.mainnet_token,
chain138_cusdc=args.chain138_cusdc,
workers=args.workers,
)
else:
for start in range(scan_start, scan_end, shard):
chunk_end = min(scan_end, start + shard)
slice_items = list(enumerate(wallets[start:chunk_end], start=start))
print(f"Shard {start}..{chunk_end} ({len(slice_items)} wallets)", file=sys.stderr)
rows.extend(
collect_rows_for_slice(
slice_items,
do_main=do_main,
do_138=do_138,
mainnet_rpc=args.mainnet_rpc,
chain138_rpc=args.chain138_rpc,
mainnet_token=args.mainnet_token,
chain138_cusdc=args.chain138_cusdc,
workers=args.workers,
)
)
rows.sort(key=lambda r: r["linearIndex"])
by_class: dict[int, dict] = {i: {"n": 0, "mainnet_below": 0, "138_below": 0} for i in range(6)}
if args.report_by_class:
for r in rows:
cls = int(r.get("class", 0))
if cls not in by_class:
continue
by_class[cls]["n"] += 1
if do_main and r["mainnetCwusdcRaw"] < args.min_mainnet_raw:
by_class[cls]["mainnet_below"] += 1
if do_138 and r["chain138CusdcRaw"] < args.min_138_raw:
by_class[cls]["138_below"] += 1
gaps_main: list[int] = []
gaps_138: list[int] = []
for r in rows:
if do_main and r["mainnetCwusdcRaw"] < args.min_mainnet_raw:
gaps_main.append(r["linearIndex"])
if do_138 and r["chain138CusdcRaw"] < args.min_138_raw:
gaps_138.append(r["linearIndex"])
summary = {
"gridPath": str(grid_path),
"slice": {"offset": scan_start, "endExclusive": scan_end, "count": len(rows)},
"shardSize": shard if shard > 0 else None,
"mainnet": {
"token": args.mainnet_token if do_main else None,
"rpc": args.mainnet_rpc[:48] + "" if do_main and len(args.mainnet_rpc) > 48 else args.mainnet_rpc,
"minRaw": args.min_mainnet_raw,
"belowMin": len(gaps_main),
},
"chain138": {
"token": args.chain138_cusdc if do_138 else None,
"minRaw": args.min_138_raw,
"belowMin": len(gaps_138),
},
"byClass": by_class if args.report_by_class else None,
}
print(json.dumps(summary, indent=2))
if gaps_main:
print(
f"\nMainnet cWUSDC below min ({args.min_mainnet_raw}) — {len(gaps_main)} wallets "
f"(first {args.max_list} indices):",
file=sys.stderr,
)
print(", ".join(str(x) for x in gaps_main[: args.max_list]), file=sys.stderr)
if gaps_138:
print(
f"\nChain 138 cUSDC below min ({args.min_138_raw}) — {len(gaps_138)} wallets "
f"(first {args.max_list} indices):",
file=sys.stderr,
)
print(", ".join(str(x) for x in gaps_138[: args.max_list]), file=sys.stderr)
if args.json_out:
outp = repo / args.json_out if not os.path.isabs(args.json_out) else Path(args.json_out)
outp.parent.mkdir(parents=True, exist_ok=True)
outp.write_text(json.dumps({"summary": summary, "rows": rows}, indent=2), encoding="utf-8")
print(f"\nWrote {outp}", file=sys.stderr)
if do_main and args.gaps_mainnet_out:
gp = repo / args.gaps_mainnet_out if not os.path.isabs(args.gaps_mainnet_out) else Path(args.gaps_mainnet_out)
write_indices(gp, gaps_main)
print(f"Wrote mainnet gap indices ({len(gaps_main)}): {gp}", file=sys.stderr)
if do_138 and args.gaps_138_out:
gp = repo / args.gaps_138_out if not os.path.isabs(args.gaps_138_out) else Path(args.gaps_138_out)
write_indices(gp, gaps_138)
print(f"Wrote 138 gap indices ({len(gaps_138)}): {gp}", file=sys.stderr)
fail = bool(gaps_main or gaps_138)
return 1 if fail else 0
if __name__ == "__main__":
raise SystemExit(main())