feat: explorer API, wallet, CCIP scripts, and config refresh
- Backend REST/gateway/track routes, analytics, Blockscout proxy paths. - Frontend wallet and liquidity surfaces; MetaMask token list alignment. - Deployment docs, verification scripts, address inventory updates. Check: go build ./... under backend/ (pass). Made-with: Cursor
This commit is contained in:
@@ -7,19 +7,24 @@ BASE_URL="${1:-https://explorer.d-bis.org}"
|
||||
python3 - "$BASE_URL" <<'PY'
|
||||
import re
|
||||
import sys
|
||||
|
||||
import requests
|
||||
|
||||
|
||||
base = sys.argv[1].rstrip("/")
|
||||
session = requests.Session()
|
||||
session.headers.update({"User-Agent": "ExplorerHealthCheck/1.0"})
|
||||
session.headers.update({"User-Agent": "ExplorerHealthCheck/2.0"})
|
||||
|
||||
checks = [
|
||||
failed = False
|
||||
|
||||
html_checks = [
|
||||
"/",
|
||||
"/home",
|
||||
"/blocks",
|
||||
"/transactions",
|
||||
"/addresses",
|
||||
"/bridge",
|
||||
"/routes",
|
||||
"/weth",
|
||||
"/tokens",
|
||||
"/pools",
|
||||
@@ -27,42 +32,149 @@ checks = [
|
||||
"/more",
|
||||
"/analytics",
|
||||
"/operator",
|
||||
"/system",
|
||||
"/liquidity",
|
||||
"/wallet",
|
||||
"/snap/",
|
||||
"/docs.html",
|
||||
"/privacy.html",
|
||||
"/terms.html",
|
||||
"/acknowledgments.html",
|
||||
]
|
||||
|
||||
json_checks = [
|
||||
"/api/v2/stats",
|
||||
"/api/config/token-list",
|
||||
"/api/config/networks",
|
||||
"/api/config/capabilities",
|
||||
"/config/CHAIN138_RPC_CAPABILITIES.json",
|
||||
"/config/topology-graph.json",
|
||||
"/config/mission-control-verify.example.json",
|
||||
"/explorer-api/v1/features",
|
||||
"/explorer-api/v1/ai/context?q=cUSDT",
|
||||
"/explorer-api/v1/track1/bridge/status",
|
||||
"/explorer-api/v1/mission-control/liquidity/token/0x93E66202A11B1772E55407B32B44e5Cd8eda7f22/pools",
|
||||
"/token-aggregation/api/v1/routes/tree?chainId=138&tokenIn=0x93E66202A11B1772E55407B32B44e5Cd8eda7f22&tokenOut=0x004b63A7B5b0E06f6bB6adb4a5F9f590BF3182D1&amountIn=1000000",
|
||||
"/token-aggregation/api/v1/routes/matrix",
|
||||
"/token-aggregation/api/v1/routes/ingestion?fromChainId=138&routeType=swap",
|
||||
"/token-aggregation/api/v1/routes/partner-payloads?partner=0x&amount=1000000&includeUnsupported=true",
|
||||
]
|
||||
|
||||
failed = False
|
||||
asset_checks = [
|
||||
"/token-icons/cUSDC.png",
|
||||
"/token-icons/cUSDT.png",
|
||||
"/token-icons/cXAUC.png",
|
||||
"/token-icons/cXAUT.png",
|
||||
]
|
||||
|
||||
print("== Core routes ==")
|
||||
for path in checks:
|
||||
|
||||
def mark_failure(message):
|
||||
global failed
|
||||
failed = True
|
||||
print(message)
|
||||
|
||||
|
||||
print("== Public routes ==")
|
||||
for path in html_checks:
|
||||
url = base + path
|
||||
try:
|
||||
resp = session.get(url, timeout=20, allow_redirects=True)
|
||||
ctype = resp.headers.get("content-type", "")
|
||||
print(f"{resp.status_code:>3} {path} [{ctype[:50]}]")
|
||||
print(f"{resp.status_code:>3} {path} [{ctype[:60]}]")
|
||||
if resp.status_code >= 400:
|
||||
failed = True
|
||||
except Exception as exc:
|
||||
failed = True
|
||||
print(f"ERR {path} [{exc}]")
|
||||
mark_failure(f"ERR {path} [{exc}]")
|
||||
|
||||
print("\n== JSON and API surfaces ==")
|
||||
for path in json_checks:
|
||||
url = base + path
|
||||
try:
|
||||
resp = session.get(url, timeout=20, allow_redirects=True)
|
||||
ctype = resp.headers.get("content-type", "")
|
||||
print(f"{resp.status_code:>3} {path} [{ctype[:60]}]")
|
||||
if resp.status_code >= 400:
|
||||
failed = True
|
||||
continue
|
||||
if "json" not in ctype:
|
||||
failed = True
|
||||
print(f" expected JSON content-type, got: {ctype}")
|
||||
continue
|
||||
payload = resp.json()
|
||||
if path == "/api/config/capabilities":
|
||||
if payload.get("chainId") != 138:
|
||||
mark_failure(" capabilities JSON does not report chainId 138")
|
||||
wallet_support = payload.get("walletSupport", {})
|
||||
if wallet_support.get("walletWatchAsset") is not True:
|
||||
mark_failure(" capabilities JSON does not advertise walletWatchAsset")
|
||||
elif path == "/api/config/token-list":
|
||||
tokens = payload.get("tokens", [])
|
||||
if not tokens:
|
||||
mark_failure(" token list is empty")
|
||||
elif path == "/api/config/networks":
|
||||
chains = payload.get("chains", [])
|
||||
if not chains:
|
||||
mark_failure(" networks payload does not include any chains")
|
||||
elif path == "/explorer-api/v1/features":
|
||||
if "features" not in payload:
|
||||
mark_failure(" features payload is missing the features key")
|
||||
elif path == "/explorer-api/v1/track1/bridge/status":
|
||||
data = payload.get("data", {})
|
||||
relays = data.get("ccip_relays", {})
|
||||
expected_relays = {
|
||||
"avax",
|
||||
"avax_cw",
|
||||
"avax_to_138",
|
||||
"bsc",
|
||||
"mainnet_cw",
|
||||
"mainnet_weth",
|
||||
}
|
||||
missing = sorted(expected_relays - set(relays))
|
||||
if missing:
|
||||
mark_failure(f" bridge status is missing relay keys: {', '.join(missing)}")
|
||||
if data.get("status") not in {"operational", "paused", "degraded"}:
|
||||
mark_failure(" bridge status payload does not include a recognized overall status")
|
||||
except Exception as exc:
|
||||
mark_failure(f"ERR {path} [{exc}]")
|
||||
|
||||
print("\n== Static assets ==")
|
||||
for path in asset_checks:
|
||||
url = base + path
|
||||
try:
|
||||
resp = session.get(url, timeout=20, allow_redirects=True)
|
||||
ctype = resp.headers.get("content-type", "")
|
||||
print(f"{resp.status_code:>3} {path} [{ctype[:60]}]")
|
||||
if resp.status_code >= 400:
|
||||
failed = True
|
||||
except Exception as exc:
|
||||
mark_failure(f"ERR {path} [{exc}]")
|
||||
|
||||
print("\n== Mission-control SSE ==")
|
||||
stream_url = base + "/explorer-api/v1/mission-control/stream"
|
||||
try:
|
||||
with session.get(stream_url, timeout=(20, 20), stream=True) as resp:
|
||||
ctype = resp.headers.get("content-type", "")
|
||||
print(f"{resp.status_code:>3} /explorer-api/v1/mission-control/stream [{ctype[:60]}]")
|
||||
if resp.status_code >= 400:
|
||||
failed = True
|
||||
else:
|
||||
lines = []
|
||||
for raw in resp.iter_lines(decode_unicode=True):
|
||||
if raw:
|
||||
lines.append(raw)
|
||||
if len(lines) >= 2:
|
||||
break
|
||||
if not any(line.startswith("event:") for line in lines):
|
||||
mark_failure(" mission-control stream did not emit an event line")
|
||||
if not any(line.startswith("data:") for line in lines):
|
||||
mark_failure(" mission-control stream did not emit a data line")
|
||||
except Exception as exc:
|
||||
mark_failure(f"ERR /explorer-api/v1/mission-control/stream [{exc}]")
|
||||
|
||||
print("\n== Internal href targets from homepage ==")
|
||||
try:
|
||||
home = session.get(base + "/", timeout=20).text
|
||||
hrefs = sorted(set(re.findall(r'href="([^"]+)"', home)))
|
||||
hrefs = sorted(set(re.findall(r'href=\"([^\"]+)\"', home)))
|
||||
for href in hrefs:
|
||||
if href.startswith("/") and not href.startswith("//"):
|
||||
resp = session.get(base + href, timeout=20, allow_redirects=True)
|
||||
@@ -70,10 +182,9 @@ try:
|
||||
if resp.status_code >= 400:
|
||||
failed = True
|
||||
except Exception as exc:
|
||||
failed = True
|
||||
print(f"ERR homepage href sweep failed: {exc}")
|
||||
mark_failure(f"ERR homepage href sweep failed: {exc}")
|
||||
|
||||
print("\n== Static explorer domains referenced by bridge page ==")
|
||||
print("\n== External explorer roots referenced by bridge surfaces ==")
|
||||
external_roots = [
|
||||
"https://etherscan.io/",
|
||||
"https://bscscan.com/",
|
||||
|
||||
Reference in New Issue
Block a user