#!/usr/bin/env bash set -euo pipefail 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/2.0"}) failed = False html_checks = [ "/", "/home", "/blocks", "/transactions", "/addresses", "/bridge", "/routes", "/weth", "/tokens", "/pools", "/watchlist", "/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", ] asset_checks = [ "/token-icons/cUSDC.png", "/token-icons/cUSDT.png", "/token-icons/cXAUC.png", "/token-icons/cXAUT.png", ] 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[:60]}]") if resp.status_code >= 400: failed = True except Exception as 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))) for href in hrefs: if href.startswith("/") and not href.startswith("//"): resp = session.get(base + href, timeout=20, allow_redirects=True) print(f"{resp.status_code:>3} {href}") if resp.status_code >= 400: failed = True except Exception as exc: mark_failure(f"ERR homepage href sweep failed: {exc}") print("\n== External explorer roots referenced by bridge surfaces ==") external_roots = [ "https://etherscan.io/", "https://bscscan.com/", "https://polygonscan.com/", "https://subnets.avax.network/c-chain", "https://basescan.org/", "https://arbiscan.io/", "https://optimistic.etherscan.io/", ] for url in external_roots: try: resp = session.get(url, timeout=20, allow_redirects=True) print(f"{resp.status_code:>3} {url}") except Exception as exc: print(f"ERR {url} [{exc}]") if failed: sys.exit(1) PY