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%
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