SPB Git

spb/coinexplorer Public MIT

Self-hosted, zero-API-key explorer for stablecoins and major crypto.

Python 60.3% HTML 23.6% JavaScript 8.1% CSS 6.8% SQL 1%
3.6 KB · 97 lines python
Raw Blame History
1# Author: Simon-Pierre Boucher2# Mail: contact@spboucher.ai3#!/usr/bin/env python34"""Verify every configured token address on-chain before trusting it.56For each chain: check eth_chainId matches the config; for each token,7eth_call symbol() and decimals() on the address and compare with8tokens.yaml. A wrong or dead address returns empty data and fails loudly.910    python3 scripts/verify_tokens.py11"""1213import pathlib14import sys1516import yaml1718sys.path.insert(0, str(pathlib.Path(__file__).parent.parent))19from indexer.rpc import RpcError, RpcPool  # noqa: E4022021ROOT = pathlib.Path(__file__).parent.parent22SEL_SYMBOL = "0x95d89b41"    # keccak4("symbol()")23SEL_DECIMALS = "0x313ce567"  # keccak4("decimals()")242526def decode_string(hexdata):27    """ABI string return: word0 = offset, word1 = length, then bytes.28    Some old tokens (MKR-style) return bytes32 instead — handle both."""29    if hexdata in (None, "0x", ""):30        return None31    raw = bytes.fromhex(hexdata[2:])32    if len(raw) >= 64:33        offset = int.from_bytes(raw[:32], "big")34        if offset == 32 and len(raw) >= 64:35            length = int.from_bytes(raw[32:64], "big")36            if 0 < length <= len(raw) - 64:37                return raw[64 : 64 + length].decode("utf-8", "replace")38    return raw.rstrip(b"\x00").decode("utf-8", "replace") or None394041def main():42    chains = yaml.safe_load((ROOT / "config" / "chains.yaml").read_text())["chains"]43    tokens = yaml.safe_load((ROOT / "config" / "tokens.yaml").read_text())["tokens"]4445    failures = 046    for chain, toks in tokens.items():47        cfg = chains[chain]48        if cfg.get("family", "evm") != "evm":49            # non-EVM identifiers are verified by their adapters (Deliverable 4)50            print(f"{chain:10s} skipped (family={cfg['family']} — no eth_call)")51            continue52        p = RpcPool(cfg["rpcs"], max_cycles=2)53        try:54            chain_id = int(p.call("eth_chainId"), 16)55        except (RpcError, RuntimeError) as e:56            print(f"{chain:10s} UNREACHABLE: {e}")57            failures += 158            continue59        cid_ok = "ok" if chain_id == cfg["chain_id"] else f"MISMATCH got {chain_id}"60        if chain_id != cfg["chain_id"]:61            failures += 162        print(f"{chain:10s} chain_id {cfg['chain_id']} [{cid_ok}]  via {p.current_url}")6364        for t in toks:65            addr = t["address"]66            try:67                sym = decode_string(68                    p.call("eth_call", [{"to": addr, "data": SEL_SYMBOL}, "latest"])69                )70                dec_hex = p.call(71                    "eth_call", [{"to": addr, "data": SEL_DECIMALS}, "latest"]72                )73                dec = int(dec_hex, 16) if dec_hex not in (None, "0x") else None74            except (RpcError, RuntimeError) as e:75                print(f"  {t['symbol']:5s} {addr}  ERROR {e}")76                failures += 177                continue78            # Tether uses '₮' in some deployments (USD₮, USD₮0) — normalize79            def norm(s):80                return (s or "").replace("₮", "T").upper()81            expected = t.get("onchain_symbol", t["symbol"])82            ok = norm(sym) == norm(expected) and dec == t["decimals"]83            mark = "ok" if ok else "MISMATCH"84            if not ok:85                failures += 186            print(f"  {t['symbol']:5s} {addr}  on-chain: symbol={sym!r} decimals={dec} [{mark}]")8788    print()89    if failures:90        print(f"FAILED: {failures} problem(s) — do not index until fixed.")91        sys.exit(1)92    print("All token addresses verified on-chain.")939495if __name__ == "__main__":96    main()97