# Author: Simon-Pierre Boucher # Mail: contact@spboucher.ai #!/usr/bin/env python3 """Verify every configured token address on-chain before trusting it. For each chain: check eth_chainId matches the config; for each token, eth_call symbol() and decimals() on the address and compare with tokens.yaml. A wrong or dead address returns empty data and fails loudly. python3 scripts/verify_tokens.py """ import pathlib import sys import yaml sys.path.insert(0, str(pathlib.Path(__file__).parent.parent)) from indexer.rpc import RpcError, RpcPool # noqa: E402 ROOT = pathlib.Path(__file__).parent.parent SEL_SYMBOL = "0x95d89b41" # keccak4("symbol()") SEL_DECIMALS = "0x313ce567" # keccak4("decimals()") def decode_string(hexdata): """ABI string return: word0 = offset, word1 = length, then bytes. Some old tokens (MKR-style) return bytes32 instead — handle both.""" if hexdata in (None, "0x", ""): return None raw = bytes.fromhex(hexdata[2:]) if len(raw) >= 64: offset = int.from_bytes(raw[:32], "big") if offset == 32 and len(raw) >= 64: length = int.from_bytes(raw[32:64], "big") if 0 < length <= len(raw) - 64: return raw[64 : 64 + length].decode("utf-8", "replace") return raw.rstrip(b"\x00").decode("utf-8", "replace") or None def main(): chains = yaml.safe_load((ROOT / "config" / "chains.yaml").read_text())["chains"] tokens = yaml.safe_load((ROOT / "config" / "tokens.yaml").read_text())["tokens"] failures = 0 for chain, toks in tokens.items(): cfg = chains[chain] if cfg.get("family", "evm") != "evm": # non-EVM identifiers are verified by their adapters (Deliverable 4) print(f"{chain:10s} skipped (family={cfg['family']} — no eth_call)") continue p = RpcPool(cfg["rpcs"], max_cycles=2) try: chain_id = int(p.call("eth_chainId"), 16) except (RpcError, RuntimeError) as e: print(f"{chain:10s} UNREACHABLE: {e}") failures += 1 continue cid_ok = "ok" if chain_id == cfg["chain_id"] else f"MISMATCH got {chain_id}" if chain_id != cfg["chain_id"]: failures += 1 print(f"{chain:10s} chain_id {cfg['chain_id']} [{cid_ok}] via {p.current_url}") for t in toks: addr = t["address"] try: sym = decode_string( p.call("eth_call", [{"to": addr, "data": SEL_SYMBOL}, "latest"]) ) dec_hex = p.call( "eth_call", [{"to": addr, "data": SEL_DECIMALS}, "latest"] ) dec = int(dec_hex, 16) if dec_hex not in (None, "0x") else None except (RpcError, RuntimeError) as e: print(f" {t['symbol']:5s} {addr} ERROR {e}") failures += 1 continue # Tether uses '₮' in some deployments (USD₮, USD₮0) — normalize def norm(s): return (s or "").replace("₮", "T").upper() expected = t.get("onchain_symbol", t["symbol"]) ok = norm(sym) == norm(expected) and dec == t["decimals"] mark = "ok" if ok else "MISMATCH" if not ok: failures += 1 print(f" {t['symbol']:5s} {addr} on-chain: symbol={sym!r} decimals={dec} [{mark}]") print() if failures: print(f"FAILED: {failures} problem(s) — do not index until fixed.") sys.exit(1) print("All token addresses verified on-chain.") if __name__ == "__main__": main()