Some checks failed
AI Code Review / claude-review (pull_request) Failing after 34s
Contract-by-contract credibility tracker for Chain 138 (44 contracts). Probes Blockscout source verification, Blockscout token pages, CoinGecko, CoinMarketCap, and DexScreener. Raw probe results checked in as reports/status/explorer-verification-latest.json; probe script at scripts/verify/explorer-verification-probe.py (read-only, no LAN). Headline findings: - CoinGecko asset-platform defi-oracle-meta-mainnet is already registered; token submissions can proceed without creating an asset platform. - Blockscout source verification: 2 of 44 verified (MerchantSettlementRegistry, WithdrawalEscrow). 42 need verification. - All 15 canonical tokens resolve on the Blockscout token page. - DexScreener does not currently index Chain 138; indexer onboarding is a separate track gated on DexScreener. - CMC probe gated on CMC_PRO_API_KEY; endpoint documented for re-run. Ordered 'fastest remaining wins' section prioritises cUSDT/cUSDC source verification + CoinGecko submission first.
213 lines
11 KiB
Python
Executable File
213 lines
11 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
"""
|
|
Chain 138 explorer-verification + external-listing probe.
|
|
|
|
For each canonical contract address:
|
|
- Blockscout: is source verified? (`/api?module=contract&action=getsourcecode`)
|
|
- Blockscout: token-page reachable? (`/api/v2/tokens/{address}` for tokens only)
|
|
- CoinGecko: is the token listed? (`/coins/defi-oracle-meta-mainnet/contract/{address}`)
|
|
- CMC: is the token listed? (v2 `/cryptocurrency/info?address=...&skip_invalid=true`)
|
|
- DexScreener: `/latest/dex/tokens/{address}`
|
|
|
|
Read-only. No keys required for CoinGecko/DexScreener/Blockscout public endpoints.
|
|
CMC requires `CMC_PRO_API_KEY` if present.
|
|
"""
|
|
from __future__ import annotations
|
|
import json, os, sys, time, urllib.request, urllib.parse, urllib.error
|
|
from concurrent.futures import ThreadPoolExecutor, as_completed
|
|
|
|
BLOCKSCOUT = "https://explorer.d-bis.org"
|
|
CHAIN138 = 138
|
|
|
|
CONTRACTS = [
|
|
# tokens
|
|
{"sym": "WETH9", "addr": "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2", "kind": "token", "note": "Genesis; WETH9 canonical mainnet address. Source verification may need `Wrapped Ether` v9 reference code."},
|
|
{"sym": "WETH10", "addr": "0xf4BB2e28688e89fCcE3c0580D37d36A7672E8A9f", "kind": "token", "note": "Genesis"},
|
|
{"sym": "LINK", "addr": "0xb7721dD53A8c629d9f1Ba31a5819AFe250002b03", "kind": "token", "note": "Chainlink Token (Chain 138 deployment of reference LINK)"},
|
|
{"sym": "cUSDT", "addr": "0x93E66202A11B1772E55407B32B44e5Cd8eda7f22", "kind": "token"},
|
|
{"sym": "cUSDC", "addr": "0xf22258f57794CC8E06237084b353Ab30fFfa640b", "kind": "token"},
|
|
{"sym": "cEURC", "addr": "0x8085961F9cF02b4d800A3c6d386D31da4B34266a", "kind": "token"},
|
|
{"sym": "cEURT", "addr": "0xdf4b71c61E5912712C1Bdd451416B9aC26949d72", "kind": "token"},
|
|
{"sym": "cGBPC", "addr": "0x003960f16D9d34F2e98d62723B6721Fb92074aD2", "kind": "token"},
|
|
{"sym": "cGBPT", "addr": "0x350f54e4D23795f86A9c03988c7135357CCaD97c", "kind": "token"},
|
|
{"sym": "cAUDC", "addr": "0xD51482e567c03899eecE3CAe8a058161FD56069D", "kind": "token"},
|
|
{"sym": "cJPYC", "addr": "0xEe269e1226a334182aace90056EE4ee5Cc8A6770", "kind": "token"},
|
|
{"sym": "cCHFC", "addr": "0x873990849DDa5117d7C644f0aF24370797C03885", "kind": "token"},
|
|
{"sym": "cCADC", "addr": "0x54dBd40cF05e15906A2C21f600937e96787f5679", "kind": "token"},
|
|
{"sym": "cXAUC", "addr": "0x290E52a8819A4fbD0714E517225429aA2B70EC6b", "kind": "token", "note": "1 token = 1 troy oz Au"},
|
|
{"sym": "cXAUT", "addr": "0x94e408E26c6FD8F4ee00b54dF19082FDA07dC96E", "kind": "token", "note": "1 token = 1 troy oz Au"},
|
|
|
|
# PMM stack
|
|
{"sym": "DODOPMMIntegration", "addr": "0x5BDc62f1ae7D630c37A8B363a1d49845356Ee72d", "kind": "core"},
|
|
{"sym": "DODOPMMProvider", "addr": "0x5CAe6Ce155b7f08D3a956F5Dc82fC9945f29B381", "kind": "core"},
|
|
{"sym": "PMM_POOL1_cUSDT_cUSDC", "addr": "0xff8d3b8fDF7B112759F076B69f4271D4209C0849", "kind": "core"},
|
|
{"sym": "PMM_POOL2_cUSDT_USDT", "addr": "0x6fc60DEDc92a2047062294488539992710b99D71", "kind": "core"},
|
|
{"sym": "PMM_POOL3_cUSDC_USDC", "addr": "0x9f74Be42725f2Aa072a9E0CdCce0E7203C510263", "kind": "core"},
|
|
|
|
# CCIP + cross-chain
|
|
{"sym": "CCIPRouter", "addr": "0x42DAb7b888Dd382bD5Adcf9E038dBF1fD03b4817", "kind": "core"},
|
|
{"sym": "CCIPSender", "addr": "0x105F8A15b819948a89153505762444Ee9f324684", "kind": "core"},
|
|
{"sym": "CCIPWETH9Bridge", "addr": "0xcacfd227A040002e49e2e01626363071324f820a", "kind": "core"},
|
|
{"sym": "CCIPWETH10Bridge", "addr": "0xe0E93247376aa097dB308B92e6Ba36bA015535D0", "kind": "core"},
|
|
{"sym": "UniversalCCIPBridge_proxy_phased", "addr": "0xCd42e8eD79Dc50599535d1de48d3dAFa0BE156F8", "kind": "core"},
|
|
{"sym": "UniversalCCIPBridge_proxy_det", "addr": "0x532DE218b94993446Be30eC894442f911499f6a3", "kind": "core"},
|
|
{"sym": "BridgeOrchestrator_proxy", "addr": "0x89aB428c437f23bAB9781ff8Db8D3848e27EeD6c", "kind": "core"},
|
|
{"sym": "CCIPReceiver_2026_02_13", "addr": "0xC12236C03b28e675d376774FCE2C2C052488430F", "kind": "core"},
|
|
|
|
# Oracle + governance
|
|
{"sym": "Multicall_2026_02_13", "addr": "0xF4AA429BE277d1a1a1A744C9e5B3aD821a9b96f7", "kind": "core"},
|
|
{"sym": "OracleAggregator_2026_02_13","addr": "0xaFd9E25ff301a79feaBcc56F46969F34808358CE", "kind": "core"},
|
|
{"sym": "OracleProxy_2026_02_13", "addr": "0x90563867F2ba94ed277303e200f4311c00982E92", "kind": "core"},
|
|
{"sym": "MultiSig_2026_02_13", "addr": "0xb9E29cFa1f89d369671E640d0BB3aD94Cab43965", "kind": "core"},
|
|
{"sym": "Voting_2026_02_13", "addr": "0x022267b26400114aF01BaCcb92456Fe36cfccD93", "kind": "core"},
|
|
{"sym": "UniversalAssetRegistry_proxy_phased", "addr": "0xAEE4b7fBe82E1F8295951584CBc772b8BBD68575", "kind": "core"},
|
|
{"sym": "UniversalAssetRegistry_proxy_det", "addr": "0xC98602aa574F565b5478E8816BCab03C9De0870f", "kind": "core"},
|
|
{"sym": "GovernanceController_proxy", "addr": "0xA6891D5229f2181a34D4FF1B515c3Aa37dd90E0e", "kind": "core"},
|
|
|
|
# Vault system
|
|
{"sym": "RegulatedEntityRegistry", "addr": "0xEA4C892D6c1253797c5D95a05BF3863363080b4B", "kind": "core"},
|
|
{"sym": "VaultFactory", "addr": "0xB2Ac70f35A81481B005067ed6567a5043BA32336", "kind": "core"},
|
|
{"sym": "Ledger", "addr": "0x67b3831dc64C14FB9352B2a45C6Dd69b3C86B7af", "kind": "core"},
|
|
{"sym": "Liquidation", "addr": "0x3aCdbCB749d6037a02F0ef6ea2E5Fb89D31fAB72", "kind": "core"},
|
|
{"sym": "XAU_Oracle", "addr": "0xf23E1eDa304082ab7a81531dFE6020E6105e77A8", "kind": "core"},
|
|
|
|
# alltra-lifi + factory
|
|
{"sym": "MerchantSettlementRegistry", "addr": "0x16D9A2cB94A0b92721D93db4A6Cd8023D3338800", "kind": "core"},
|
|
{"sym": "WithdrawalEscrow", "addr": "0xe77cb26eA300e2f5304b461b0EC94c8AD6A7E46D", "kind": "core"},
|
|
{"sym": "CREATE2Factory", "addr": "0x750E4a8adCe9f0e67A420aBE91342DC64Eb90825", "kind": "core"},
|
|
]
|
|
|
|
def http_json(url: str, headers: dict | None = None, timeout: int = 20) -> tuple[int, dict | None]:
|
|
req = urllib.request.Request(url, headers=headers or {"User-Agent": "chain138-verify/1.0"})
|
|
try:
|
|
with urllib.request.urlopen(req, timeout=timeout) as r:
|
|
code = r.getcode()
|
|
body = r.read()
|
|
try:
|
|
return code, json.loads(body)
|
|
except Exception:
|
|
return code, {"_raw": body[:200].decode("utf-8", errors="replace")}
|
|
except urllib.error.HTTPError as e:
|
|
try:
|
|
body = json.loads(e.read())
|
|
except Exception:
|
|
body = None
|
|
return e.code, body
|
|
except Exception as e:
|
|
return -1, {"_err": str(e)}
|
|
|
|
def probe_blockscout_source(addr: str) -> dict:
|
|
# Module API: getsourcecode — returns SourceCode="" when unverified
|
|
url = f"{BLOCKSCOUT}/api?module=contract&action=getsourcecode&address={addr}"
|
|
code, data = http_json(url)
|
|
if code != 200 or not data:
|
|
return {"verified": False, "status": f"http {code}", "raw": str(data)[:200]}
|
|
result = data.get("result")
|
|
if isinstance(result, list) and result:
|
|
result = result[0]
|
|
if not isinstance(result, dict):
|
|
return {"verified": False, "status": "no-result", "raw": str(data)[:200]}
|
|
src = result.get("SourceCode") or ""
|
|
name = result.get("ContractName") or ""
|
|
abi = result.get("ABI") or ""
|
|
return {
|
|
"verified": bool(src) and abi and abi != "Contract source code not verified",
|
|
"contract_name": name,
|
|
"compiler": result.get("CompilerVersion"),
|
|
"optimizer": result.get("OptimizationUsed"),
|
|
}
|
|
|
|
def probe_blockscout_token(addr: str) -> dict:
|
|
url = f"{BLOCKSCOUT}/api/v2/tokens/{addr}"
|
|
code, data = http_json(url)
|
|
if code != 200 or not data:
|
|
return {"listed": False, "status": f"http {code}"}
|
|
return {
|
|
"listed": True,
|
|
"name": data.get("name"),
|
|
"symbol": data.get("symbol"),
|
|
"decimals": data.get("decimals"),
|
|
"holders": data.get("holders") or data.get("holders_count"),
|
|
"total_supply": data.get("total_supply"),
|
|
}
|
|
|
|
def probe_coingecko(addr: str) -> dict:
|
|
# The CoinGecko asset-platform slug for a custom chain depends on CG's registry.
|
|
# Chain 138 is NOT currently on CoinGecko (that's what we're submitting). We still
|
|
# probe the standard endpoint so we can differentiate 404 vs 200.
|
|
# Known slugs to try in priority order:
|
|
slugs = ["defi-oracle-meta-mainnet", "dbis", "defi-oracle-meta"]
|
|
for slug in slugs:
|
|
url = f"https://api.coingecko.com/api/v3/coins/{slug}/contract/{addr.lower()}"
|
|
code, data = http_json(url, timeout=10)
|
|
if code == 200 and data and data.get("id"):
|
|
return {"listed": True, "slug": slug, "coin_id": data.get("id"), "symbol": data.get("symbol")}
|
|
# 429 -> backoff
|
|
if code == 429:
|
|
time.sleep(2)
|
|
return {"listed": False, "status": "not found on coingecko under any chain-138 slug"}
|
|
|
|
def probe_cmc(addr: str) -> dict:
|
|
api_key = os.environ.get("CMC_PRO_API_KEY")
|
|
if not api_key:
|
|
return {"listed": None, "status": "no CMC_PRO_API_KEY in env"}
|
|
url = f"https://pro-api.coinmarketcap.com/v2/cryptocurrency/info?address={addr}&skip_invalid=true"
|
|
code, data = http_json(url, headers={"X-CMC_PRO_API_KEY": api_key}, timeout=15)
|
|
if code != 200 or not data:
|
|
return {"listed": False, "status": f"http {code}"}
|
|
d = data.get("data") or {}
|
|
if not d:
|
|
return {"listed": False}
|
|
key = next(iter(d.keys()))
|
|
val = d[key] if key else None
|
|
if isinstance(val, list):
|
|
val = val[0] if val else None
|
|
if isinstance(val, dict):
|
|
return {"listed": True, "name": val.get("name"), "symbol": val.get("symbol"), "id": val.get("id"), "slug": val.get("slug")}
|
|
return {"listed": False}
|
|
|
|
def probe_dexscreener(addr: str) -> dict:
|
|
# DexScreener indexes by token across all chains it supports; Chain 138 may not be supported at all.
|
|
url = f"https://api.dexscreener.com/latest/dex/tokens/{addr}"
|
|
code, data = http_json(url, timeout=15)
|
|
if code != 200 or not data:
|
|
return {"listed": False, "status": f"http {code}"}
|
|
pairs = data.get("pairs") or []
|
|
chain_138_pairs = [p for p in pairs if str(p.get("chainId")) in ("138", "dbis", "defi-oracle-meta-mainnet")]
|
|
return {
|
|
"listed": bool(chain_138_pairs),
|
|
"num_pairs_any_chain": len(pairs),
|
|
"num_pairs_chain_138": len(chain_138_pairs),
|
|
"example_dex": chain_138_pairs[0].get("dexId") if chain_138_pairs else None,
|
|
}
|
|
|
|
def probe_one(c: dict) -> dict:
|
|
addr = c["addr"]
|
|
out = dict(c)
|
|
out["blockscout_source"] = probe_blockscout_source(addr)
|
|
if c["kind"] == "token":
|
|
out["blockscout_token"] = probe_blockscout_token(addr)
|
|
out["coingecko"] = probe_coingecko(addr)
|
|
out["cmc"] = probe_cmc(addr)
|
|
out["dexscreener"] = probe_dexscreener(addr)
|
|
return out
|
|
|
|
def main():
|
|
results = []
|
|
with ThreadPoolExecutor(max_workers=8) as ex:
|
|
futures = {ex.submit(probe_one, c): c for c in CONTRACTS}
|
|
for i, f in enumerate(as_completed(futures), 1):
|
|
c = futures[f]
|
|
try:
|
|
r = f.result()
|
|
except Exception as e:
|
|
r = {**c, "_error": str(e)}
|
|
results.append(r)
|
|
print(f"[{i:>2}/{len(CONTRACTS)}] {c['sym']:<40} done", file=sys.stderr)
|
|
# Preserve input order
|
|
results.sort(key=lambda r: [c2["sym"] for c2 in CONTRACTS].index(r["sym"]))
|
|
print(json.dumps(results, indent=2))
|
|
|
|
if __name__ == "__main__":
|
|
main()
|