ExplorerScan
4,973 networks tracked5,404 block explorersRequestStatus
Companion site:RPCScan ↗RPC endpoint directory with latency and uptime leaderboards and per-provider posture
Architecture · planning doc

rpcscanchain: verifiable scan history

Every /rpc/<url> page render captures a snapshot: TLS handshake transcript, post-quantum class, score breakdown, RPC probe stats, DNS posture, the full set of values you see on screen. We hash that snapshot (SHA-256 over a canonical JSON serialization with sorted keys) and store the hash + payload locally. The plan is to publish each new hash to an append-only chain so anyone can later prove a snapshot wasn't tampered with, turning RPCScan from a "trust us" oracle into a public, verifiable record of how RPC infrastructure changed over time.

What gets hashed

The SHA-256 covers the entire rpcscan-report object - the same JSON you can download from any /rpc page via the "Download JSON" button. The canonicalization rule is:

  1. JSON.stringify with object keys sorted lexicographically at every depth.
  2. UTF-8 encode the resulting string.
  3. Compute SHA-256 over those bytes; emit lowercase hex.

That's it. No salt, no nonce, no signing key. The hash is reproducible by anyone with the same payload, which is the whole point of an inclusion proof. Algorithm + sample implementation lives in app/components/ScanReportExport.tsx.

Polling cadence: how often we capture

LayerCadenceWhat it captures
RPC probeevery 30 s, all endpointsJSON-RPC call latency, success/fail, block height, response shape
TLS posture probe~15 minClientHello → ServerHello, key share group, cipher, cert chain, ALPN, OCSP, SCTs
Pageview snapshoton demand (per /rpc visit)Whatever's currently true, hashed + recorded; this is what users export.
Score-grade transition emiton every snapshotCompares the new SHA's grade/PQ-class against the previous distinct row; emits a scan_transitions row when something changed.
On-chain anchor (planned)batched, ~1 hrMerkle-roll the past hour's distinct SHAs; publish the root.

Scaling: how this works at 6,000 RPC URLs

ClickHouse handles the math. Snapshots are ReplacingMergeTree on (rpc_url_lower, content_sha), so re-renders that produce identical bytes are deduped at insert time. With the current ~6,000 tracked URLs and probe cadence, steady-state new-row volume is ≈ 2,000 distinct snapshots/day, and a year's worth easily fits in a single 64-MB part. The scan_transitionstable is even smaller (only emit on change).

On-chain publishing batches by Merkle root, not per-snapshot, so the on-chain cost is fixed regardless of how many endpoints we track. Every published root is recoverable from the off-chain row + a 6-step proof, the same arrangement Certificate Transparency's append-only log.

Detection: when a score changes

Every snapshot ingest server-side runs a one-line lookup against the most-recent different-SHA row for the same URL. If score_grade or pq_class moved, we emit a row in scan_transitions:

{
  rpcUrl:        "https://eth.llamarpc.com",
  transitionAt: "2026-05-04T09:42:11Z",
  fromGrade:    "B",
  toGrade:      "A+",
  fromPqClass:  "classical",
  toPqClass:    "pq-hybrid",
  kind:         "upgrade"
}

Those rows feed the /regressions alert feed (negative transitions) and surface a green "upgraded to PQ hybrid on 2026-05-04" badge on the per-RPC page (positive transitions).

What's live · what's planned

ComponentStatusNotes
Browser-side SHA-256 of reportliveSubtleCrypto on every /rpc page render
JSON / CSV exportliveOne click → file with SHA + every captured value
Server-side snapshot persistplannedNot built. Reports are hashed and exported client-side today.
Score / PQ transition detectionplannedDepends on snapshot persist above.
Hourly Merkle-root publishingplannedDeferred until rpcscanchain is live + an anchor chain is chosen
Inclusion-proof verifierplannedBrowser-side: paste a JSON, paste a root, verify path