diff --git a/benchmarks/asset-registry-coverage.yml b/benchmarks/asset-registry-coverage.yml
index c29a75d7f..b252b2001 100644
--- a/benchmarks/asset-registry-coverage.yml
+++ b/benchmarks/asset-registry-coverage.yml
@@ -95,6 +95,19 @@ providers:
sample_size: networks_supported_total{provider="coingecko"}
series: networks_supported_total{provider="coingecko"}
+ - slug: serialized
+ name: Serialized
+ tag: Own indexers, 18 EVM + Solana
+ formula: "Count of chains returned as live by Serialized's `/v1/meta/chains` endpoint, refreshed every 6 hours."
+ queries:
+ p50: networks_supported_total{provider="serialized"}
+ p90: networks_supported_total{provider="serialized"}
+ p99: networks_supported_total{provider="serialized"}
+ mean: networks_supported_total{provider="serialized"}
+ success: clamp_max(networks_supported_total{provider="serialized"} > bool 0, 1)
+ sample_size: networks_supported_total{provider="serialized"}
+ series: networks_supported_total{provider="serialized"}
+
- slug: coinpaprika
name: CoinPaprika
tag: Market-data API asset registry
diff --git a/benchmarks/bandchain-rpc.yml b/benchmarks/bandchain-rpc.yml
index 67a1c01ff..249bbb896 100644
--- a/benchmarks/bandchain-rpc.yml
+++ b/benchmarks/bandchain-rpc.yml
@@ -1,35 +1,138 @@
-id: 256
+# OpenChainBench. Bench No 256
+
slug: bandchain-rpc
-title: "Band Protocol RPC"
-chain: bandchain
-description: "Latency and availability benchmark for Band Protocol public RPC endpoints"
-category: rpc
-kind: cosmos
+number: "256"
+title: Fastest free Band Protocol RPC, live no-key endpoint latency
+seo_title: "Fastest free Band Protocol RPC 2026"
+seo_description: "{{best_name}} leads free Band Protocol RPC at {{best_p50}} (block height p50, 24h). 3 no-key providers measured every 60s from 3 regions."
+subtitle: HTTP round-trip latency for Tendermint /status queries against every available public Band Protocol (laozi-mainnet) endpoint, audited every 60 seconds from 3 regions.
-providers:
- - slug: band-official
- name: "Band Protocol"
- url: "http://rpc.laozi1.bandchain.org:80"
- - slug: highstakes
- name: "High Stakes"
- url: "https://bandprotocol-rpc.highstakes.ch"
- - slug: stakewolle
- name: "Stakewolle"
- url: "https://public.stakewolle.com/cosmos/bandchain/rpc"
+category: RPCs
+status: live
+metric: RPC latency
+unit: ms
+higher_is_better: false
seo_intro: |
- Band Protocol is a cross-chain oracle network built on Cosmos SDK that aggregates and connects real-world data and APIs to smart contracts. The laozi-mainnet hosts its decentralized data oracle infrastructure, enabling DeFi protocols across multiple blockchains to access tamper-proof price feeds.
+ Band Protocol is a cross-chain decentralized oracle network built on Cosmos SDK (chain ID laozi-mainnet). It aggregates and connects real-world data and APIs to smart contracts across multiple blockchains via its Yoda oracle daemon and data feed infrastructure. The chain uses the standard Tendermint RPC interface; block height is fetched via the /status endpoint. Public keyless RPC nodes are provided by the Band Protocol official team, High Stakes, and Stakewolle. Every provider was live-verified with consecutive block-height probes at launch.
- This benchmark continuously measures RPC latency, availability and block-height freshness across public Band Protocol Tendermint endpoints from three geographic regions. Use it to select the fastest endpoint for your integration or validator setup.
+abstract: |
+ Per-chain member of the RPC latency cluster, extended to Band Protocol (laozi-mainnet).
+ We measure the round-trip latency of a Tendermint /status query against
+ every available public Band Protocol endpoint that sustains continuous probing:
+ 3 providers at launch, every 60 seconds, from us-east, eu-west and
+ Singapore. Each provider is probed via a GET /status request
+ from which the block number is extracted.
+ The harness classifies every response (ok / http_err / jsonrpc_err / stale / timeout)
+ with a Cosmos-scaled staleness gap (40 blocks, around 4 min at 6 s/block).
+ The cross-chain view lives on the parent rpc-capabilities benchmark;
+ this page is the Band Protocol-scoped answer with per-region breakdowns as
+ a first-class dimension.
+
+methodology:
+ - "Cadence: every 60 seconds per provider, from each of 3 probe regions (us-east Virginia, eu-west Amsterdam, sgp Singapore). Headline p50/p90/p99 aggregate across all 3 regions via Prometheus avg(quantile_over_time(...)); per-region breakdowns are first-class on this page via the region tabs."
+ - "Payload: GET /status. The result.sync_info.latest_block_height field is extracted as the current block height."
+ - "Latency: client-side round-trip delta in milliseconds, exposed as both a gauge and a histogram (buckets 50 ms to 10 s), so percentiles are computed via Prometheus quantile_over_time over the last 24 hours."
+ - "Call-result classification: ok (parsable block height returned), http_err, jsonrpc_err, stale (block more than 40 behind the cross-provider tip), timeout."
+ - "This page is part of the per-chain RPC cluster derived from the cross-chain rpc-capabilities benchmark; the identical harness, cadence and exclusion rules apply on every chain."
+ - "Chain scope: every query on this page is pinned to chain=bandchain. Provider coverage at launch: 3 endpoints (Band Protocol official, High Stakes, Stakewolle)."
+
+findings:
+ - "{{best_name}} leads free Band Protocol RPC at {{best_p50}} (Tendermint /status p50, 24h) across 3 measured providers."
faq:
- - q: "What does this benchmark measure?"
- a: "Each probe issues a GET /status request with anti-cache headers to retrieve the latest block height. We record round-trip latency (p50/p90/p99), HTTP availability, and whether the returned block height is current. Probes run every 30 seconds from US East, EU West, and AP Southeast."
- - q: "Which endpoints are included?"
- a: "The benchmark covers the Band Protocol official endpoint (rpc.laozi1.bandchain.org), High Stakes, and Stakewolle. All three are keyless public endpoints requiring no authentication."
- - q: "Why does Band Protocol RPC performance matter?"
- a: "Band Protocol validators and oracle scripts depend on reliable RPC access to submit data requests and retrieve oracle results. DeFi protocols integrating Band price feeds need low-latency RPC for real-time data consumption."
- - q: "How are oracle scripts affected by RPC latency?"
- a: "Yoda (the oracle daemon) and Bothan (data proxy) both rely on RPC to monitor pending data requests and submit responses within the request window. High latency or downtime directly reduces oracle reliability and can cause missed rewards."
- - q: "Can I contribute an endpoint?"
- a: "Yes. Open an issue or PR at github.com/ChainBench/OpenChainBench with the endpoint URL and operator name. We verify liveness and independence before adding."
+ - q: "What is the fastest free Band Protocol RPC right now?"
+ a: "{{best_name}} currently leads at {{best_p50}} (Band Protocol block height p50 over the last 24h), measured against 3 providers probed every 60 seconds from us-east, eu-west and Singapore. The leaderboard re-sorts continuously against fresh Prometheus samples."
+ - q: "Which Band Protocol RPC endpoints work without an API key?"
+ a: "3 endpoints sustain continuous keyless probing at launch: Band Protocol official (rpc.laozi1.bandchain.org), High Stakes (bandprotocol-rpc.highstakes.ch), and Stakewolle (public.stakewolle.com/cosmos/bandchain/rpc). Every listed endpoint was live-verified before inclusion."
+ - q: "What is Band Protocol and why does its RPC latency matter?"
+ a: "Band Protocol is a cross-chain data oracle that aggregates real-world data feeds and delivers them to smart contracts via oracle scripts. Validators running the Yoda daemon and DeFi protocols consuming Band price feeds both depend on low-latency reliable RPC access to the Band chain."
+ - q: "Does the fastest Band Protocol RPC change by region?"
+ a: "Yes. The official Band node and community validators are hosted in different regions. The region tabs re-scope every number to a single probe origin so you can pick the best endpoint for your deployment geography."
+ - q: "What is the BAND token?"
+ a: "BAND is the native staking and governance token of Band Protocol (denominated as uband on-chain). It is used to stake as validators, pay for oracle data requests, and participate in on-chain governance of the laozi-mainnet."
+
+source: https://github.com/ChainBench/OpenChainBench/tree/main/harnesses/rpc-capabilities
+
+prometheus:
+ window: 24h
+ freshness_metric: rpc_latency_milliseconds
+
+rank_matrix_query: avg by (provider, region) (ocb:rpc_latency_milliseconds:p50_24h{chain="bandchain"})
+
+dimensions:
+ region:
+ - { value: all, label: All regions }
+ - { value: us-east, label: US-East }
+ - { value: eu-west, label: EU-West }
+ - { value: sgp, label: Singapore }
+
+providers:
+ - slug: band-official
+ name: Band Protocol
+ tag: Band Protocol official public RPC node, no API key required
+ formula: "50th percentile over 24h of client-side round-trip latency (ms) for a GET /status sent every 60s from 3 regions to rpc.laozi1.bandchain.org."
+ queries:
+ p50: avg(ocb:rpc_latency_milliseconds:p50_24h{provider="band-official", chain="bandchain"})
+ p90: avg(ocb:rpc_latency_milliseconds:p90_24h{provider="band-official", chain="bandchain"})
+ p99: avg(ocb:rpc_latency_milliseconds:p99_24h{provider="band-official", chain="bandchain"})
+ mean: avg(ocb:rpc_latency_milliseconds:mean_24h{provider="band-official", chain="bandchain"})
+ success: sum(ocb:rpc_call:ok_rate_24h{provider="band-official", chain="bandchain"}) / sum(ocb:rpc_call:rate_24h{provider="band-official", chain="bandchain"})
+ sample_size: sum(ocb:rpc_call:increase_24h{provider="band-official", chain="bandchain"})
+ series: avg(avg_over_time(rpc_latency_milliseconds{provider="band-official", chain="bandchain"}[1h]))
+ regions:
+ - region: us-east
+ p50: avg(ocb:rpc_latency_milliseconds:p50_24h{provider="band-official", chain="bandchain", region="us-east"})
+ series: avg_over_time(rpc_latency_milliseconds{provider="band-official", chain="bandchain", region="us-east"}[1h])
+ - region: eu-west
+ p50: avg(ocb:rpc_latency_milliseconds:p50_24h{provider="band-official", chain="bandchain", region="eu-west"})
+ series: avg_over_time(rpc_latency_milliseconds{provider="band-official", chain="bandchain", region="eu-west"}[1h])
+ - region: ap-southeast
+ p50: avg(ocb:rpc_latency_milliseconds:p50_24h{provider="band-official", chain="bandchain", region="sgp"})
+ series: avg_over_time(rpc_latency_milliseconds{provider="band-official", chain="bandchain", region="sgp"}[1h])
+
+ - slug: highstakes
+ name: High Stakes
+ tag: High Stakes public Band Protocol RPC, no API key required
+ formula: "50th percentile over 24h of client-side round-trip latency (ms) for a GET /status sent every 60s from 3 regions to bandprotocol-rpc.highstakes.ch."
+ queries:
+ p50: avg(ocb:rpc_latency_milliseconds:p50_24h{provider="highstakes", chain="bandchain"})
+ p90: avg(ocb:rpc_latency_milliseconds:p90_24h{provider="highstakes", chain="bandchain"})
+ p99: avg(ocb:rpc_latency_milliseconds:p99_24h{provider="highstakes", chain="bandchain"})
+ mean: avg(ocb:rpc_latency_milliseconds:mean_24h{provider="highstakes", chain="bandchain"})
+ success: sum(ocb:rpc_call:ok_rate_24h{provider="highstakes", chain="bandchain"}) / sum(ocb:rpc_call:rate_24h{provider="highstakes", chain="bandchain"})
+ sample_size: sum(ocb:rpc_call:increase_24h{provider="highstakes", chain="bandchain"})
+ series: avg(avg_over_time(rpc_latency_milliseconds{provider="highstakes", chain="bandchain"}[1h]))
+ regions:
+ - region: us-east
+ p50: avg(ocb:rpc_latency_milliseconds:p50_24h{provider="highstakes", chain="bandchain", region="us-east"})
+ series: avg_over_time(rpc_latency_milliseconds{provider="highstakes", chain="bandchain", region="us-east"}[1h])
+ - region: eu-west
+ p50: avg(ocb:rpc_latency_milliseconds:p50_24h{provider="highstakes", chain="bandchain", region="eu-west"})
+ series: avg_over_time(rpc_latency_milliseconds{provider="highstakes", chain="bandchain", region="eu-west"}[1h])
+ - region: ap-southeast
+ p50: avg(ocb:rpc_latency_milliseconds:p50_24h{provider="highstakes", chain="bandchain", region="sgp"})
+ series: avg_over_time(rpc_latency_milliseconds{provider="highstakes", chain="bandchain", region="sgp"}[1h])
+
+ - slug: stakewolle
+ name: Stakewolle
+ tag: Stakewolle public Band Protocol RPC, no API key required
+ formula: "50th percentile over 24h of client-side round-trip latency (ms) for a GET /status sent every 60s from 3 regions to public.stakewolle.com/cosmos/bandchain/rpc."
+ queries:
+ p50: avg(ocb:rpc_latency_milliseconds:p50_24h{provider="stakewolle", chain="bandchain"})
+ p90: avg(ocb:rpc_latency_milliseconds:p90_24h{provider="stakewolle", chain="bandchain"})
+ p99: avg(ocb:rpc_latency_milliseconds:p99_24h{provider="stakewolle", chain="bandchain"})
+ mean: avg(ocb:rpc_latency_milliseconds:mean_24h{provider="stakewolle", chain="bandchain"})
+ success: sum(ocb:rpc_call:ok_rate_24h{provider="stakewolle", chain="bandchain"}) / sum(ocb:rpc_call:rate_24h{provider="stakewolle", chain="bandchain"})
+ sample_size: sum(ocb:rpc_call:increase_24h{provider="stakewolle", chain="bandchain"})
+ series: avg(avg_over_time(rpc_latency_milliseconds{provider="stakewolle", chain="bandchain"}[1h]))
+ regions:
+ - region: us-east
+ p50: avg(ocb:rpc_latency_milliseconds:p50_24h{provider="stakewolle", chain="bandchain", region="us-east"})
+ series: avg_over_time(rpc_latency_milliseconds{provider="stakewolle", chain="bandchain", region="us-east"}[1h])
+ - region: eu-west
+ p50: avg(ocb:rpc_latency_milliseconds:p50_24h{provider="stakewolle", chain="bandchain", region="eu-west"})
+ series: avg_over_time(rpc_latency_milliseconds{provider="stakewolle", chain="bandchain", region="eu-west"}[1h])
+ - region: ap-southeast
+ p50: avg(ocb:rpc_latency_milliseconds:p50_24h{provider="stakewolle", chain="bandchain", region="sgp"})
+ series: avg_over_time(rpc_latency_milliseconds{provider="stakewolle", chain="bandchain", region="sgp"}[1h])
diff --git a/benchmarks/bitcoin-rpc.yml b/benchmarks/bitcoin-rpc.yml
new file mode 100644
index 000000000..5fc30be7d
--- /dev/null
+++ b/benchmarks/bitcoin-rpc.yml
@@ -0,0 +1,127 @@
+# OpenChainBench. Bench No 259
+
+slug: bitcoin-rpc
+number: "259"
+title: Fastest free Bitcoin RPC, live no-key endpoint latency
+seo_title: "Fastest free Bitcoin RPC 2026"
+seo_description: "{{best_name}} leads free Bitcoin RPC at {{best_p50}} (getblockcount p50, 24h). 3 no-key providers measured every 60s from 3 regions."
+subtitle: HTTP round-trip latency for block-height queries against every available public Bitcoin endpoint, audited every 60 seconds from 3 regions.
+
+category: RPCs
+status: live
+metric: RPC latency
+unit: ms
+higher_is_better: false
+
+seo_intro: |
+ Bitcoin is the original proof-of-work UTXO blockchain, targeting one block every ten minutes under SHA-256. It exposes the canonical Bitcoin Core JSON-RPC interface; the lightest height method is getblockcount, which returns the current chain tip as a plain integer. A handful of providers serve this endpoint with no API key: PublicNode, dRPC and NOWNodes (public no-key endpoint, rate-limited to 5 requests per second). Every provider was live-verified with consecutive getblockcount probes at launch, all returning the same tip.
+
+abstract: |
+ Per-chain member of the RPC latency cluster, extended to Bitcoin. We probe every
+ public no-key endpoint with a single getblockcount call every 60 seconds from three
+ regions (us-east, eu-west, Singapore) and record wall-clock round-trip latency. Only ok
+ responses (HTTP 200 with a usable block height, not stale) contribute to the distribution;
+ p50/p90/p99 are computed via Prometheus quantile_over_time over a rolling 24-hour window.
+
+methodology:
+ - "Probe: a single getblockcount JSON-RPC POST every 60 seconds per provider from us-east, eu-west and Singapore. Latency is the client-side wall-clock round trip."
+ - "Classification: ok (HTTP 200 with a usable height), http_err, jsonrpc_err (HTTP 200 with an error body), stale (tip more than 20 blocks behind the cross-provider max), timeout. Only ok responses contribute latency."
+ - "Providers: PublicNode (bitcoin-rpc.publicnode.com), dRPC (bitcoin.drpc.org), NOWNodes (public-btc.nownodes.io, no key, 5 RPS)."
+ - "Aggregation: p50/p90/p99 via Prometheus quantile_over_time over 24h; the headline cites the cross-region figure and the region tabs re-scope it."
+
+findings:
+ - "{{best_name}} leads at {{best_p50}} (getblockcount p50, 24h) across {{provider_count}} no-key Bitcoin endpoints."
+ - "Bitcoin's ten-minute block time means read latency reflects the provider's edge, not chain speed: a stale tip is rare, so this bench is a near-pure latency race."
+ - "{{name:nownodes}} serves Bitcoin from its public no-key endpoint at {{p50:nownodes}} (p50, 24h), rate-limited to 5 requests per second."
+
+faq:
+ - q: "Which free Bitcoin RPC is fastest?"
+ a: "Per the live data above: {{best_name}} at {{best_p50}} (getblockcount p50, 24h) across the no-key providers measured here."
+ - q: "How is Bitcoin RPC latency measured?"
+ a: "A single getblockcount call every 60 seconds from three regions (us-east, eu-west, Singapore), recording the wall-clock round trip. Only successful, non-stale responses count."
+ - q: "Do these endpoints need an API key?"
+ a: "No. All three (PublicNode, dRPC, NOWNodes) serve Bitcoin Core JSON-RPC with no key. NOWNodes' public endpoint is rate-limited to 5 requests per second."
+
+source: https://github.com/ChainBench/OpenChainBench/tree/main/harnesses/rpc-capabilities
+
+prometheus:
+ window: 24h
+ expected_freshness_seconds: 5400
+
+rank_matrix_query: avg by (provider, region) (ocb:rpc_latency_milliseconds:p50_24h{chain="bitcoin"})
+
+dimensions:
+ region:
+ - { value: us-east, label: US-East }
+ - { value: eu-west, label: EU-West }
+ - { value: sgp, label: Singapore }
+
+providers:
+ - slug: publicnode
+ name: PublicNode
+ tag: Allnodes-operated, no-key Bitcoin Core JSON-RPC
+ formula: "50th percentile over 24h of client-side round-trip latency (ms) for a single `getblockcount` sent every 60s from 3 regions (us-east + eu-west + sgp) to PublicNode's no-key Bitcoin endpoint."
+ queries:
+ p50: avg(ocb:rpc_latency_milliseconds:p50_24h{provider="publicnode", chain="bitcoin"})
+ p90: avg(ocb:rpc_latency_milliseconds:p90_24h{provider="publicnode", chain="bitcoin"})
+ p99: avg(ocb:rpc_latency_milliseconds:p99_24h{provider="publicnode", chain="bitcoin"})
+ mean: avg(ocb:rpc_latency_milliseconds:mean_24h{provider="publicnode", chain="bitcoin"})
+ success: sum(ocb:rpc_call:ok_rate_24h{provider="publicnode", chain="bitcoin"}) / sum(ocb:rpc_call:rate_24h{provider="publicnode", chain="bitcoin"})
+ sample_size: sum(ocb:rpc_call:increase_24h{provider="publicnode", chain="bitcoin"})
+ series: avg(avg_over_time(rpc_latency_milliseconds{provider="publicnode", chain="bitcoin"}[1h]))
+ regions:
+ - region: us-east
+ p50: avg(ocb:rpc_latency_milliseconds:p50_24h{provider="publicnode", chain="bitcoin", region="us-east"})
+ series: avg_over_time(rpc_latency_milliseconds{provider="publicnode", chain="bitcoin", region="us-east"}[1h])
+ - region: eu-west
+ p50: avg(ocb:rpc_latency_milliseconds:p50_24h{provider="publicnode", chain="bitcoin", region="eu-west"})
+ series: avg_over_time(rpc_latency_milliseconds{provider="publicnode", chain="bitcoin", region="eu-west"}[1h])
+ - region: ap-southeast
+ p50: avg(ocb:rpc_latency_milliseconds:p50_24h{provider="publicnode", chain="bitcoin", region="sgp"})
+ series: avg_over_time(rpc_latency_milliseconds{provider="publicnode", chain="bitcoin", region="sgp"}[1h])
+
+ - slug: drpc
+ name: dRPC
+ tag: Decentralised RPC mesh, no-key Bitcoin endpoint
+ formula: "50th percentile over 24h of client-side round-trip latency (ms) for a single `getblockcount` sent every 60s from 3 regions (us-east + eu-west + sgp) to dRPC's no-key Bitcoin endpoint."
+ queries:
+ p50: avg(ocb:rpc_latency_milliseconds:p50_24h{provider="drpc", chain="bitcoin"})
+ p90: avg(ocb:rpc_latency_milliseconds:p90_24h{provider="drpc", chain="bitcoin"})
+ p99: avg(ocb:rpc_latency_milliseconds:p99_24h{provider="drpc", chain="bitcoin"})
+ mean: avg(ocb:rpc_latency_milliseconds:mean_24h{provider="drpc", chain="bitcoin"})
+ success: sum(ocb:rpc_call:ok_rate_24h{provider="drpc", chain="bitcoin"}) / sum(ocb:rpc_call:rate_24h{provider="drpc", chain="bitcoin"})
+ sample_size: sum(ocb:rpc_call:increase_24h{provider="drpc", chain="bitcoin"})
+ series: avg(avg_over_time(rpc_latency_milliseconds{provider="drpc", chain="bitcoin"}[1h]))
+ regions:
+ - region: us-east
+ p50: avg(ocb:rpc_latency_milliseconds:p50_24h{provider="drpc", chain="bitcoin", region="us-east"})
+ series: avg_over_time(rpc_latency_milliseconds{provider="drpc", chain="bitcoin", region="us-east"}[1h])
+ - region: eu-west
+ p50: avg(ocb:rpc_latency_milliseconds:p50_24h{provider="drpc", chain="bitcoin", region="eu-west"})
+ series: avg_over_time(rpc_latency_milliseconds{provider="drpc", chain="bitcoin", region="eu-west"}[1h])
+ - region: ap-southeast
+ p50: avg(ocb:rpc_latency_milliseconds:p50_24h{provider="drpc", chain="bitcoin", region="sgp"})
+ series: avg_over_time(rpc_latency_milliseconds{provider="drpc", chain="bitcoin", region="sgp"}[1h])
+
+ - slug: nownodes
+ name: NOWNodes
+ tag: Public node, 110+ chains, 5 RPS free tier
+ formula: "50th percentile over 24h of client-side round-trip latency (ms) for a single `getblockcount` sent every 60s from 3 regions (us-east + eu-west + sgp) to NOWNodes's no-key Bitcoin endpoint."
+ queries:
+ p50: avg(ocb:rpc_latency_milliseconds:p50_24h{provider="nownodes", chain="bitcoin"})
+ p90: avg(ocb:rpc_latency_milliseconds:p90_24h{provider="nownodes", chain="bitcoin"})
+ p99: avg(ocb:rpc_latency_milliseconds:p99_24h{provider="nownodes", chain="bitcoin"})
+ mean: avg(ocb:rpc_latency_milliseconds:mean_24h{provider="nownodes", chain="bitcoin"})
+ success: sum(ocb:rpc_call:ok_rate_24h{provider="nownodes", chain="bitcoin"}) / sum(ocb:rpc_call:rate_24h{provider="nownodes", chain="bitcoin"})
+ sample_size: sum(ocb:rpc_call:increase_24h{provider="nownodes", chain="bitcoin"})
+ series: avg(avg_over_time(rpc_latency_milliseconds{provider="nownodes", chain="bitcoin"}[1h]))
+ regions:
+ - region: us-east
+ p50: avg(ocb:rpc_latency_milliseconds:p50_24h{provider="nownodes", chain="bitcoin", region="us-east"})
+ series: avg_over_time(rpc_latency_milliseconds{provider="nownodes", chain="bitcoin", region="us-east"}[1h])
+ - region: eu-west
+ p50: avg(ocb:rpc_latency_milliseconds:p50_24h{provider="nownodes", chain="bitcoin", region="eu-west"})
+ series: avg_over_time(rpc_latency_milliseconds{provider="nownodes", chain="bitcoin", region="eu-west"}[1h])
+ - region: ap-southeast
+ p50: avg(ocb:rpc_latency_milliseconds:p50_24h{provider="nownodes", chain="bitcoin", region="sgp"})
+ series: avg_over_time(rpc_latency_milliseconds{provider="nownodes", chain="bitcoin", region="sgp"}[1h])
diff --git a/benchmarks/bnb-rpc.yml b/benchmarks/bnb-rpc.yml
index 95ca204e9..94bf6875c 100644
--- a/benchmarks/bnb-rpc.yml
+++ b/benchmarks/bnb-rpc.yml
@@ -86,6 +86,29 @@ providers:
p50: avg(ocb:rpc_latency_milliseconds:p50_24h{provider="publicnode", chain="bnb", region="sgp"})
series: avg_over_time(rpc_latency_milliseconds{provider="publicnode", chain="bnb", region="sgp"}[1h])
+ - slug: nownodes
+ name: NOWNodes
+ tag: Public node, 110+ chains, 5 RPS free tier
+ formula: "50th percentile over 24h of client-side round-trip latency (ms) for a single `eth_getBlockByNumber` POST sent every 60s from 3 regions (us-east + eu-west + sgp) to NOWNodes public no-key BNB Chain endpoint."
+ queries:
+ p50: avg(ocb:rpc_latency_milliseconds:p50_24h{provider="nownodes", chain="bnb"})
+ p90: avg(ocb:rpc_latency_milliseconds:p90_24h{provider="nownodes", chain="bnb"})
+ p99: avg(ocb:rpc_latency_milliseconds:p99_24h{provider="nownodes", chain="bnb"})
+ mean: avg(ocb:rpc_latency_milliseconds:mean_24h{provider="nownodes", chain="bnb"})
+ success: sum(ocb:rpc_call:ok_rate_24h{provider="nownodes", chain="bnb"}) / sum(ocb:rpc_call:rate_24h{provider="nownodes", chain="bnb"})
+ sample_size: sum(ocb:rpc_call:increase_24h{provider="nownodes", chain="bnb"})
+ series: avg(avg_over_time(rpc_latency_milliseconds{provider="nownodes", chain="bnb"}[1h]))
+ regions:
+ - region: us-east
+ p50: avg(ocb:rpc_latency_milliseconds:p50_24h{provider="nownodes", chain="bnb", region="us-east"})
+ series: avg_over_time(rpc_latency_milliseconds{provider="nownodes", chain="bnb", region="us-east"}[1h])
+ - region: eu-west
+ p50: avg(ocb:rpc_latency_milliseconds:p50_24h{provider="nownodes", chain="bnb", region="eu-west"})
+ series: avg_over_time(rpc_latency_milliseconds{provider="nownodes", chain="bnb", region="eu-west"}[1h])
+ - region: ap-southeast
+ p50: avg(ocb:rpc_latency_milliseconds:p50_24h{provider="nownodes", chain="bnb", region="sgp"})
+ series: avg_over_time(rpc_latency_milliseconds{provider="nownodes", chain="bnb", region="sgp"}[1h])
+
- slug: drpc
name: dRPC
tag: Decentralized RPC mesh, consensus-checked
diff --git a/benchmarks/cheqd-rpc.yml b/benchmarks/cheqd-rpc.yml
index c5d028836..19752bf17 100644
--- a/benchmarks/cheqd-rpc.yml
+++ b/benchmarks/cheqd-rpc.yml
@@ -1,35 +1,138 @@
-id: 257
+# OpenChainBench. Bench No 257
+
slug: cheqd-rpc
-title: "cheqd RPC"
-chain: cheqd
-description: "Latency and availability benchmark for cheqd public RPC endpoints"
-category: rpc
-kind: cosmos
+number: "257"
+title: Fastest free cheqd RPC, live no-key endpoint latency
+seo_title: "Fastest free cheqd RPC 2026"
+seo_description: "{{best_name}} leads free cheqd RPC at {{best_p50}} (block height p50, 24h). 3 no-key providers measured every 60s from 3 regions."
+subtitle: HTTP round-trip latency for Tendermint /status queries against every available public cheqd (cheqd-mainnet-1) endpoint, audited every 60 seconds from 3 regions.
-providers:
- - slug: cheqd-official
- name: "cheqd"
- url: "https://rpc.cheqd.net"
- - slug: publicnode
- name: "PublicNode"
- url: "https://cheqd-rpc.publicnode.com:443"
- - slug: nodestake
- name: "NodeStake"
- url: "https://rpc.cheqd.nodestake.org"
+category: RPCs
+status: live
+metric: RPC latency
+unit: ms
+higher_is_better: false
seo_intro: |
- cheqd is a purpose-built Cosmos SDK blockchain for decentralized identity, enabling self-sovereign identity (SSI) and verifiable credentials at scale. The cheqd-mainnet-1 network anchors DIDs and credential schemas used by enterprises, governments, and developers building trust infrastructure.
+ cheqd is a purpose-built Cosmos SDK blockchain (chain ID cheqd-mainnet-1) for decentralized identity, enabling self-sovereign identity (SSI) and verifiable credentials at scale. The cheqd-mainnet-1 network anchors DIDs and credential schemas used by enterprises, governments, and developers building trust infrastructure. The chain uses the standard Tendermint RPC interface; block height is fetched via the /status endpoint. Public keyless RPC nodes are provided by the cheqd official team, PublicNode, and NodeStake. Every provider was live-verified with consecutive block-height probes at launch.
- This benchmark continuously measures RPC latency, availability and block-height freshness across public cheqd Tendermint endpoints from three geographic regions. Use it to select the most reliable endpoint for identity resolution, node operation, or application integration.
+abstract: |
+ Per-chain member of the RPC latency cluster, extended to cheqd (cheqd-mainnet-1).
+ We measure the round-trip latency of a Tendermint /status query against
+ every available public cheqd endpoint that sustains continuous probing:
+ 3 providers at launch, every 60 seconds, from us-east, eu-west and
+ Singapore. Each provider is probed via a GET /status request
+ from which the block number is extracted.
+ The harness classifies every response (ok / http_err / jsonrpc_err / stale / timeout)
+ with a Cosmos-scaled staleness gap (40 blocks, around 4 min at 6 s/block).
+ The cross-chain view lives on the parent rpc-capabilities benchmark;
+ this page is the cheqd-scoped answer with per-region breakdowns as
+ a first-class dimension.
+
+methodology:
+ - "Cadence: every 60 seconds per provider, from each of 3 probe regions (us-east Virginia, eu-west Amsterdam, sgp Singapore). Headline p50/p90/p99 aggregate across all 3 regions via Prometheus avg(quantile_over_time(...)); per-region breakdowns are first-class on this page via the region tabs."
+ - "Payload: GET /status. The result.sync_info.latest_block_height field is extracted as the current block height."
+ - "Latency: client-side round-trip delta in milliseconds, exposed as both a gauge and a histogram (buckets 50 ms to 10 s), so percentiles are computed via Prometheus quantile_over_time over the last 24 hours."
+ - "Call-result classification: ok (parsable block height returned), http_err, jsonrpc_err, stale (block more than 40 behind the cross-provider tip), timeout."
+ - "This page is part of the per-chain RPC cluster derived from the cross-chain rpc-capabilities benchmark; the identical harness, cadence and exclusion rules apply on every chain."
+ - "Chain scope: every query on this page is pinned to chain=cheqd. Provider coverage at launch: 3 endpoints (cheqd official, PublicNode, NodeStake)."
+
+findings:
+ - "{{best_name}} leads free cheqd RPC at {{best_p50}} (Tendermint /status p50, 24h) across 3 measured providers."
faq:
- - q: "What does this benchmark measure?"
- a: "Each probe issues a GET /status request with anti-cache headers to retrieve the latest block height. We record round-trip latency (p50/p90/p99), HTTP availability, and whether the returned block height is current. Probes run every 30 seconds from US East, EU West, and AP Southeast."
- - q: "Which endpoints are included?"
- a: "The benchmark covers the official cheqd endpoint (rpc.cheqd.net), PublicNode, and NodeStake. All three are keyless public endpoints requiring no API key."
- - q: "Why does cheqd RPC performance matter?"
- a: "DID resolution, verifiable credential anchoring, and CHEQ token transfers all depend on RPC availability. Applications using the cheqd DID method resolve identifiers via RPC, making latency directly visible to end users."
- - q: "How does RPC latency affect DID resolution?"
- a: "The Universal Resolver and cheqd-specific resolvers query RPC endpoints to fetch DID documents. Slow or unavailable endpoints increase credential verification times and can break SSI flows in production."
- - q: "Can I contribute an endpoint?"
- a: "Yes. Open an issue or PR at github.com/ChainBench/OpenChainBench with the endpoint URL and operator name. We verify liveness and independence before adding."
+ - q: "What is the fastest free cheqd RPC right now?"
+ a: "{{best_name}} currently leads at {{best_p50}} (cheqd block height p50 over the last 24h), measured against 3 providers probed every 60 seconds from us-east, eu-west and Singapore. The leaderboard re-sorts continuously against fresh Prometheus samples."
+ - q: "Which cheqd RPC endpoints work without an API key?"
+ a: "3 endpoints sustain continuous keyless probing at launch: cheqd official (rpc.cheqd.net), PublicNode (cheqd-rpc.publicnode.com), and NodeStake (rpc.cheqd.nodestake.org). Every listed endpoint was live-verified before inclusion."
+ - q: "What is cheqd and why does its RPC latency matter?"
+ a: "cheqd is a Cosmos SDK blockchain built for decentralized identity (DID) and verifiable credentials. DID resolution, verifiable credential anchoring, and CHEQ token transfers all depend on RPC availability. Applications using the cheqd DID method resolve identifiers via RPC, making latency directly visible to end users and credential verifiers."
+ - q: "Does the fastest cheqd RPC change by region?"
+ a: "Yes. The official cheqd node and community validators are hosted in different regions. The region tabs re-scope every number to a single probe origin so you can pick the best endpoint for your deployment geography."
+ - q: "What is the CHEQ token?"
+ a: "CHEQ is the native staking and governance token of the cheqd network (denominated as ncheq on-chain). It is used to pay for DID writes, credential schema anchoring, and on-chain governance participation."
+
+source: https://github.com/ChainBench/OpenChainBench/tree/main/harnesses/rpc-capabilities
+
+prometheus:
+ window: 24h
+ freshness_metric: rpc_latency_milliseconds
+
+rank_matrix_query: avg by (provider, region) (ocb:rpc_latency_milliseconds:p50_24h{chain="cheqd"})
+
+dimensions:
+ region:
+ - { value: all, label: All regions }
+ - { value: us-east, label: US-East }
+ - { value: eu-west, label: EU-West }
+ - { value: sgp, label: Singapore }
+
+providers:
+ - slug: cheqd-official
+ name: cheqd
+ tag: cheqd official public RPC node, no API key required
+ formula: "50th percentile over 24h of client-side round-trip latency (ms) for a GET /status sent every 60s from 3 regions to rpc.cheqd.net."
+ queries:
+ p50: avg(ocb:rpc_latency_milliseconds:p50_24h{provider="cheqd-official", chain="cheqd"})
+ p90: avg(ocb:rpc_latency_milliseconds:p90_24h{provider="cheqd-official", chain="cheqd"})
+ p99: avg(ocb:rpc_latency_milliseconds:p99_24h{provider="cheqd-official", chain="cheqd"})
+ mean: avg(ocb:rpc_latency_milliseconds:mean_24h{provider="cheqd-official", chain="cheqd"})
+ success: sum(ocb:rpc_call:ok_rate_24h{provider="cheqd-official", chain="cheqd"}) / sum(ocb:rpc_call:rate_24h{provider="cheqd-official", chain="cheqd"})
+ sample_size: sum(ocb:rpc_call:increase_24h{provider="cheqd-official", chain="cheqd"})
+ series: avg(avg_over_time(rpc_latency_milliseconds{provider="cheqd-official", chain="cheqd"}[1h]))
+ regions:
+ - region: us-east
+ p50: avg(ocb:rpc_latency_milliseconds:p50_24h{provider="cheqd-official", chain="cheqd", region="us-east"})
+ series: avg_over_time(rpc_latency_milliseconds{provider="cheqd-official", chain="cheqd", region="us-east"}[1h])
+ - region: eu-west
+ p50: avg(ocb:rpc_latency_milliseconds:p50_24h{provider="cheqd-official", chain="cheqd", region="eu-west"})
+ series: avg_over_time(rpc_latency_milliseconds{provider="cheqd-official", chain="cheqd", region="eu-west"}[1h])
+ - region: ap-southeast
+ p50: avg(ocb:rpc_latency_milliseconds:p50_24h{provider="cheqd-official", chain="cheqd", region="sgp"})
+ series: avg_over_time(rpc_latency_milliseconds{provider="cheqd-official", chain="cheqd", region="sgp"}[1h])
+
+ - slug: publicnode
+ name: PublicNode
+ tag: PublicNode public cheqd RPC, no API key required
+ formula: "50th percentile over 24h of client-side round-trip latency (ms) for a GET /status sent every 60s from 3 regions to cheqd-rpc.publicnode.com."
+ queries:
+ p50: avg(ocb:rpc_latency_milliseconds:p50_24h{provider="publicnode", chain="cheqd"})
+ p90: avg(ocb:rpc_latency_milliseconds:p90_24h{provider="publicnode", chain="cheqd"})
+ p99: avg(ocb:rpc_latency_milliseconds:p99_24h{provider="publicnode", chain="cheqd"})
+ mean: avg(ocb:rpc_latency_milliseconds:mean_24h{provider="publicnode", chain="cheqd"})
+ success: sum(ocb:rpc_call:ok_rate_24h{provider="publicnode", chain="cheqd"}) / sum(ocb:rpc_call:rate_24h{provider="publicnode", chain="cheqd"})
+ sample_size: sum(ocb:rpc_call:increase_24h{provider="publicnode", chain="cheqd"})
+ series: avg(avg_over_time(rpc_latency_milliseconds{provider="publicnode", chain="cheqd"}[1h]))
+ regions:
+ - region: us-east
+ p50: avg(ocb:rpc_latency_milliseconds:p50_24h{provider="publicnode", chain="cheqd", region="us-east"})
+ series: avg_over_time(rpc_latency_milliseconds{provider="publicnode", chain="cheqd", region="us-east"}[1h])
+ - region: eu-west
+ p50: avg(ocb:rpc_latency_milliseconds:p50_24h{provider="publicnode", chain="cheqd", region="eu-west"})
+ series: avg_over_time(rpc_latency_milliseconds{provider="publicnode", chain="cheqd", region="eu-west"}[1h])
+ - region: ap-southeast
+ p50: avg(ocb:rpc_latency_milliseconds:p50_24h{provider="publicnode", chain="cheqd", region="sgp"})
+ series: avg_over_time(rpc_latency_milliseconds{provider="publicnode", chain="cheqd", region="sgp"}[1h])
+
+ - slug: nodestake
+ name: NodeStake
+ tag: NodeStake public cheqd RPC, no API key required
+ formula: "50th percentile over 24h of client-side round-trip latency (ms) for a GET /status sent every 60s from 3 regions to rpc.cheqd.nodestake.org."
+ queries:
+ p50: avg(ocb:rpc_latency_milliseconds:p50_24h{provider="nodestake", chain="cheqd"})
+ p90: avg(ocb:rpc_latency_milliseconds:p90_24h{provider="nodestake", chain="cheqd"})
+ p99: avg(ocb:rpc_latency_milliseconds:p99_24h{provider="nodestake", chain="cheqd"})
+ mean: avg(ocb:rpc_latency_milliseconds:mean_24h{provider="nodestake", chain="cheqd"})
+ success: sum(ocb:rpc_call:ok_rate_24h{provider="nodestake", chain="cheqd"}) / sum(ocb:rpc_call:rate_24h{provider="nodestake", chain="cheqd"})
+ sample_size: sum(ocb:rpc_call:increase_24h{provider="nodestake", chain="cheqd"})
+ series: avg(avg_over_time(rpc_latency_milliseconds{provider="nodestake", chain="cheqd"}[1h]))
+ regions:
+ - region: us-east
+ p50: avg(ocb:rpc_latency_milliseconds:p50_24h{provider="nodestake", chain="cheqd", region="us-east"})
+ series: avg_over_time(rpc_latency_milliseconds{provider="nodestake", chain="cheqd", region="us-east"}[1h])
+ - region: eu-west
+ p50: avg(ocb:rpc_latency_milliseconds:p50_24h{provider="nodestake", chain="cheqd", region="eu-west"})
+ series: avg_over_time(rpc_latency_milliseconds{provider="nodestake", chain="cheqd", region="eu-west"}[1h])
+ - region: ap-southeast
+ p50: avg(ocb:rpc_latency_milliseconds:p50_24h{provider="nodestake", chain="cheqd", region="sgp"})
+ series: avg_over_time(rpc_latency_milliseconds{provider="nodestake", chain="cheqd", region="sgp"}[1h])
diff --git a/benchmarks/coreum-rpc.yml b/benchmarks/coreum-rpc.yml
index 9791f6636..da4bfa34d 100644
--- a/benchmarks/coreum-rpc.yml
+++ b/benchmarks/coreum-rpc.yml
@@ -111,6 +111,29 @@ providers:
p50: avg(ocb:rpc_latency_milliseconds:p50_24h{provider="publicnode", chain="coreum", region="sgp"})
series: avg_over_time(rpc_latency_milliseconds{provider="publicnode", chain="coreum", region="sgp"}[1h])
+ - slug: nownodes
+ name: NOWNodes
+ tag: Public node, 110+ chains, 5 RPS free tier
+ formula: "50th percentile over 24h of client-side round-trip latency (ms) for a single Tendermint `status` POST sent every 60s from 3 regions (us-east + eu-west + sgp) to PublicNode's no-key Coreum endpoint."
+ queries:
+ p50: avg(ocb:rpc_latency_milliseconds:p50_24h{provider="nownodes", chain="coreum"})
+ p90: avg(ocb:rpc_latency_milliseconds:p90_24h{provider="nownodes", chain="coreum"})
+ p99: avg(ocb:rpc_latency_milliseconds:p99_24h{provider="nownodes", chain="coreum"})
+ mean: avg(ocb:rpc_latency_milliseconds:mean_24h{provider="nownodes", chain="coreum"})
+ success: sum(ocb:rpc_call:ok_rate_24h{provider="nownodes", chain="coreum"}) / sum(ocb:rpc_call:rate_24h{provider="nownodes", chain="coreum"})
+ sample_size: sum(ocb:rpc_call:increase_24h{provider="nownodes", chain="coreum"})
+ series: avg(avg_over_time(rpc_latency_milliseconds{provider="nownodes", chain="coreum"}[1h]))
+ regions:
+ - region: us-east
+ p50: avg(ocb:rpc_latency_milliseconds:p50_24h{provider="nownodes", chain="coreum", region="us-east"})
+ series: avg_over_time(rpc_latency_milliseconds{provider="nownodes", chain="coreum", region="us-east"}[1h])
+ - region: eu-west
+ p50: avg(ocb:rpc_latency_milliseconds:p50_24h{provider="nownodes", chain="coreum", region="eu-west"})
+ series: avg_over_time(rpc_latency_milliseconds{provider="nownodes", chain="coreum", region="eu-west"}[1h])
+ - region: ap-southeast
+ p50: avg(ocb:rpc_latency_milliseconds:p50_24h{provider="nownodes", chain="coreum", region="sgp"})
+ series: avg_over_time(rpc_latency_milliseconds{provider="nownodes", chain="coreum", region="sgp"}[1h])
+
- slug: coreum-official
name: Coreum Foundation
tag: Coreum Foundation's official public RPC
diff --git a/benchmarks/dex-network-coverage.yml b/benchmarks/dex-network-coverage.yml
index 198522514..8e7649b3f 100644
--- a/benchmarks/dex-network-coverage.yml
+++ b/benchmarks/dex-network-coverage.yml
@@ -90,6 +90,19 @@ providers:
sample_size: networks_supported_total{provider="geckoterminal"}
series: networks_supported_total{provider="geckoterminal"}
+ - slug: serialized
+ name: Serialized
+ tag: Own indexers, 18 EVM + Solana
+ formula: "Count of chains returned as live by Serialized's `/v1/meta/chains` endpoint; every listed chain carries DEX pool indexing. Refreshed every 6 hours."
+ queries:
+ p50: networks_supported_total{provider="serialized"}
+ p90: networks_supported_total{provider="serialized"}
+ p99: networks_supported_total{provider="serialized"}
+ mean: networks_supported_total{provider="serialized"}
+ success: clamp_max(networks_supported_total{provider="serialized"} > bool 0, 1)
+ sample_size: networks_supported_total{provider="serialized"}
+ series: networks_supported_total{provider="serialized"}
+
- slug: codex
name: Codex
tag: Defined.fi DEX data API
diff --git a/benchmarks/ethereum-rpc.yml b/benchmarks/ethereum-rpc.yml
index ce4e21741..98627c04d 100644
--- a/benchmarks/ethereum-rpc.yml
+++ b/benchmarks/ethereum-rpc.yml
@@ -87,6 +87,29 @@ providers:
p50: avg(ocb:rpc_latency_milliseconds:p50_24h{provider="publicnode", chain="ethereum", region="sgp"})
series: avg_over_time(rpc_latency_milliseconds{provider="publicnode", chain="ethereum", region="sgp"}[1h])
+ - slug: nownodes
+ name: NOWNodes
+ tag: Public node, 110+ chains, 5 RPS free tier
+ formula: "50th percentile over 24h of client-side round-trip latency (ms) for a single `eth_getBlockByNumber` POST sent every 60s from 3 regions (us-east + eu-west + sgp) to NOWNodes public no-key Ethereum endpoint."
+ queries:
+ p50: avg(ocb:rpc_latency_milliseconds:p50_24h{provider="nownodes", chain="ethereum"})
+ p90: avg(ocb:rpc_latency_milliseconds:p90_24h{provider="nownodes", chain="ethereum"})
+ p99: avg(ocb:rpc_latency_milliseconds:p99_24h{provider="nownodes", chain="ethereum"})
+ mean: avg(ocb:rpc_latency_milliseconds:mean_24h{provider="nownodes", chain="ethereum"})
+ success: sum(ocb:rpc_call:ok_rate_24h{provider="nownodes", chain="ethereum"}) / sum(ocb:rpc_call:rate_24h{provider="nownodes", chain="ethereum"})
+ sample_size: sum(ocb:rpc_call:increase_24h{provider="nownodes", chain="ethereum"})
+ series: avg(avg_over_time(rpc_latency_milliseconds{provider="nownodes", chain="ethereum"}[1h]))
+ regions:
+ - region: us-east
+ p50: avg(ocb:rpc_latency_milliseconds:p50_24h{provider="nownodes", chain="ethereum", region="us-east"})
+ series: avg_over_time(rpc_latency_milliseconds{provider="nownodes", chain="ethereum", region="us-east"}[1h])
+ - region: eu-west
+ p50: avg(ocb:rpc_latency_milliseconds:p50_24h{provider="nownodes", chain="ethereum", region="eu-west"})
+ series: avg_over_time(rpc_latency_milliseconds{provider="nownodes", chain="ethereum", region="eu-west"}[1h])
+ - region: ap-southeast
+ p50: avg(ocb:rpc_latency_milliseconds:p50_24h{provider="nownodes", chain="ethereum", region="sgp"})
+ series: avg_over_time(rpc_latency_milliseconds{provider="nownodes", chain="ethereum", region="sgp"}[1h])
+
- slug: drpc
name: dRPC
tag: Decentralized RPC mesh, consensus-checked
diff --git a/benchmarks/icon-rpc.yml b/benchmarks/icon-rpc.yml
index f45ec814c..a04d9938b 100644
--- a/benchmarks/icon-rpc.yml
+++ b/benchmarks/icon-rpc.yml
@@ -1,35 +1,138 @@
-id: 258
+# OpenChainBench. Bench No 258
+
slug: icon-rpc
-title: "Fastest free ICON RPC, live no-key endpoint latency"
-chain: icon
-description: "Latency and availability benchmark for ICON public RPC endpoints"
-category: rpc
-kind: icon
+number: "258"
+title: Fastest free ICON RPC, live no-key endpoint latency
+seo_title: "Fastest free ICON RPC 2026"
+seo_description: "{{best_name}} leads free ICON RPC at {{best_p50}} (block height p50, 24h). 3 no-key providers measured every 60s from 3 regions."
+subtitle: HTTP round-trip latency for icx_getLastBlock queries against every available public ICON blockchain endpoint, audited every 60 seconds from 3 regions.
-providers:
- - slug: icon-solidwallet
- name: "ICON Foundation"
- url: "https://ctz.solidwallet.io"
- - slug: icon-community
- name: "ICON Community"
- url: "https://api.icon.community"
- - slug: iconblockchain
- name: "iconblockchain.xyz"
- url: "https://api.iconblockchain.xyz"
+category: RPCs
+status: live
+metric: RPC latency
+unit: ms
+higher_is_better: false
seo_intro: |
- ICON is a South Korean L1 blockchain focused on interoperability and enterprise adoption, using a Delegated Proof of Contribution (DPoC) consensus with ~2-second block finality. ICON nodes expose a JSON-RPC 2.0 API: POST /api/v3 with method icx_getLastBlock returns the latest block height. Free public endpoints are available keyless from the ICON Foundation (ctz.solidwallet.io), ICON Community (api.icon.community), and iconblockchain.xyz.
+ ICON is a South Korean L1 blockchain focused on interoperability and enterprise adoption, using a Delegated Proof of Contribution (DPoC) consensus with roughly 2-second block finality. ICON nodes expose a JSON-RPC 2.0 API: POST /api/v3 with method icx_getLastBlock returns the latest block height as a decimal integer. Free public endpoints are available keyless from the ICON Foundation (ctz.solidwallet.io), ICON Community (api.icon.community), and iconblockchain.xyz. Every provider was live-verified with consecutive block-height probes at launch.
- This benchmark continuously measures RPC latency, availability and block-height freshness across these public ICON endpoints from three geographic regions. Every provider was live-verified with consecutive keyless icx_getLastBlock probes at launch.
+abstract: |
+ Per-chain member of the RPC latency cluster, extended to ICON (ICX).
+ We measure the round-trip latency of a JSON-RPC icx_getLastBlock call against
+ every available public ICON endpoint that sustains continuous probing:
+ 3 providers at launch, every 60 seconds, from us-east, eu-west and
+ Singapore. Each provider is probed via a POST /api/v3 request
+ from which the block height is extracted from result.height.
+ The harness classifies every response (ok / http_err / jsonrpc_err / stale / timeout)
+ with an ICON-scaled staleness gap (150 blocks, around 5 min at 2 s/block).
+ The cross-chain view lives on the parent rpc-capabilities benchmark;
+ this page is the ICON-scoped answer with per-region breakdowns as
+ a first-class dimension.
+
+methodology:
+ - "Cadence: every 60 seconds per provider, from each of 3 probe regions (us-east Virginia, eu-west Amsterdam, sgp Singapore). Headline p50/p90/p99 aggregate across all 3 regions via Prometheus avg(quantile_over_time(...)); per-region breakdowns are first-class on this page via the region tabs."
+ - "Payload: POST /api/v3 with method icx_getLastBlock. The result.height field (decimal integer) is extracted as the current block height."
+ - "Latency: client-side round-trip delta in milliseconds, exposed as both a gauge and a histogram (buckets 50 ms to 10 s), so percentiles are computed via Prometheus quantile_over_time over the last 24 hours."
+ - "Call-result classification: ok (parsable block height returned), http_err, jsonrpc_err, stale (block more than 150 behind the cross-provider tip, roughly 5 min at ICON's 2 s block time), timeout."
+ - "This page is part of the per-chain RPC cluster derived from the cross-chain rpc-capabilities benchmark; the identical harness, cadence and exclusion rules apply on every chain."
+ - "Chain scope: every query on this page is pinned to chain=icon. Provider coverage at launch: 3 endpoints (ICON Foundation, ICON Community, iconblockchain.xyz)."
+
+findings:
+ - "{{best_name}} leads free ICON RPC at {{best_p50}} (icx_getLastBlock p50, 24h) across 3 measured providers."
faq:
- - q: "What does this benchmark measure?"
- a: "Each probe issues a POST /api/v3 icx_getLastBlock request to retrieve the latest block height. We record round-trip latency (p50/p90/p99), HTTP availability, and whether the returned block height is current. Probes run every 60 seconds from US East, EU West, and AP Southeast."
- - q: "Which endpoints are included?"
- a: "The benchmark covers the ICON Foundation endpoint (ctz.solidwallet.io), ICON Community (api.icon.community), and iconblockchain.xyz. All three are keyless public endpoints requiring no API key."
- - q: "Why does ICON RPC performance matter?"
- a: "ICON powers ICX transfers, BTP cross-chain messages, and DApps across the ICON ecosystem. Low-latency RPC access is critical for wallets, DEX aggregators integrating ICON, and validators monitoring chain health."
- - q: "How does ICON's ~2-second block time affect staleness detection?"
- a: "With blocks every ~2 seconds, a gap of 150 blocks represents roughly 5 minutes of drift — the same threshold we use to classify other fast-finality chains as stale. A provider returning a block more than 150 behind the cross-provider tip is marked stale."
- - q: "Can I contribute an endpoint?"
- a: "Yes. Open an issue or PR at github.com/ChainBench/OpenChainBench with the endpoint URL and operator name. We verify liveness and independence before adding."
+ - q: "What is the fastest free ICON RPC right now?"
+ a: "{{best_name}} currently leads at {{best_p50}} (ICON block height p50 over the last 24h), measured against 3 providers probed every 60 seconds from us-east, eu-west and Singapore. The leaderboard re-sorts continuously against fresh Prometheus samples."
+ - q: "Which ICON RPC endpoints work without an API key?"
+ a: "3 endpoints sustain continuous keyless probing at launch: ICON Foundation (ctz.solidwallet.io), ICON Community (api.icon.community), and iconblockchain.xyz. Every listed endpoint was live-verified before inclusion."
+ - q: "What is ICON and why does its RPC latency matter?"
+ a: "ICON is a South Korean blockchain focused on enterprise interoperability, originally built for connecting financial institutions and public organizations. ICX wallets, DApp integrations, and BTP cross-chain bridging all depend on reliable RPC access. Low latency matters for real-time ICX transfers and DeFi protocols built on the ICON ecosystem."
+ - q: "Does the fastest ICON RPC change by region?"
+ a: "Yes. The ICON Foundation node and community operators are distributed globally. The region tabs re-scope every number to a single probe origin so you can pick the best endpoint for your deployment geography."
+ - q: "What is the ICX token?"
+ a: "ICX is the native staking and governance token of the ICON network. It is used to pay transaction fees, stake with validators (P-Reps), and participate in on-chain governance. ICON uses a Delegated Proof of Contribution model where P-Reps are elected by ICX holders."
+
+source: https://github.com/ChainBench/OpenChainBench/tree/main/harnesses/rpc-capabilities
+
+prometheus:
+ window: 24h
+ freshness_metric: rpc_latency_milliseconds
+
+rank_matrix_query: avg by (provider, region) (ocb:rpc_latency_milliseconds:p50_24h{chain="icon"})
+
+dimensions:
+ region:
+ - { value: all, label: All regions }
+ - { value: us-east, label: US-East }
+ - { value: eu-west, label: EU-West }
+ - { value: sgp, label: Singapore }
+
+providers:
+ - slug: icon-solidwallet
+ name: ICON Foundation
+ tag: ICON Foundation official public RPC node, no API key required
+ formula: "50th percentile over 24h of client-side round-trip latency (ms) for a POST /api/v3 icx_getLastBlock sent every 60s from 3 regions to ctz.solidwallet.io."
+ queries:
+ p50: avg(ocb:rpc_latency_milliseconds:p50_24h{provider="icon-solidwallet", chain="icon"})
+ p90: avg(ocb:rpc_latency_milliseconds:p90_24h{provider="icon-solidwallet", chain="icon"})
+ p99: avg(ocb:rpc_latency_milliseconds:p99_24h{provider="icon-solidwallet", chain="icon"})
+ mean: avg(ocb:rpc_latency_milliseconds:mean_24h{provider="icon-solidwallet", chain="icon"})
+ success: sum(ocb:rpc_call:ok_rate_24h{provider="icon-solidwallet", chain="icon"}) / sum(ocb:rpc_call:rate_24h{provider="icon-solidwallet", chain="icon"})
+ sample_size: sum(ocb:rpc_call:increase_24h{provider="icon-solidwallet", chain="icon"})
+ series: avg(avg_over_time(rpc_latency_milliseconds{provider="icon-solidwallet", chain="icon"}[1h]))
+ regions:
+ - region: us-east
+ p50: avg(ocb:rpc_latency_milliseconds:p50_24h{provider="icon-solidwallet", chain="icon", region="us-east"})
+ series: avg_over_time(rpc_latency_milliseconds{provider="icon-solidwallet", chain="icon", region="us-east"}[1h])
+ - region: eu-west
+ p50: avg(ocb:rpc_latency_milliseconds:p50_24h{provider="icon-solidwallet", chain="icon", region="eu-west"})
+ series: avg_over_time(rpc_latency_milliseconds{provider="icon-solidwallet", chain="icon", region="eu-west"}[1h])
+ - region: ap-southeast
+ p50: avg(ocb:rpc_latency_milliseconds:p50_24h{provider="icon-solidwallet", chain="icon", region="sgp"})
+ series: avg_over_time(rpc_latency_milliseconds{provider="icon-solidwallet", chain="icon", region="sgp"}[1h])
+
+ - slug: icon-community
+ name: ICON Community
+ tag: ICON Community public RPC node, no API key required
+ formula: "50th percentile over 24h of client-side round-trip latency (ms) for a POST /api/v3 icx_getLastBlock sent every 60s from 3 regions to api.icon.community."
+ queries:
+ p50: avg(ocb:rpc_latency_milliseconds:p50_24h{provider="icon-community", chain="icon"})
+ p90: avg(ocb:rpc_latency_milliseconds:p90_24h{provider="icon-community", chain="icon"})
+ p99: avg(ocb:rpc_latency_milliseconds:p99_24h{provider="icon-community", chain="icon"})
+ mean: avg(ocb:rpc_latency_milliseconds:mean_24h{provider="icon-community", chain="icon"})
+ success: sum(ocb:rpc_call:ok_rate_24h{provider="icon-community", chain="icon"}) / sum(ocb:rpc_call:rate_24h{provider="icon-community", chain="icon"})
+ sample_size: sum(ocb:rpc_call:increase_24h{provider="icon-community", chain="icon"})
+ series: avg(avg_over_time(rpc_latency_milliseconds{provider="icon-community", chain="icon"}[1h]))
+ regions:
+ - region: us-east
+ p50: avg(ocb:rpc_latency_milliseconds:p50_24h{provider="icon-community", chain="icon", region="us-east"})
+ series: avg_over_time(rpc_latency_milliseconds{provider="icon-community", chain="icon", region="us-east"}[1h])
+ - region: eu-west
+ p50: avg(ocb:rpc_latency_milliseconds:p50_24h{provider="icon-community", chain="icon", region="eu-west"})
+ series: avg_over_time(rpc_latency_milliseconds{provider="icon-community", chain="icon", region="eu-west"}[1h])
+ - region: ap-southeast
+ p50: avg(ocb:rpc_latency_milliseconds:p50_24h{provider="icon-community", chain="icon", region="sgp"})
+ series: avg_over_time(rpc_latency_milliseconds{provider="icon-community", chain="icon", region="sgp"}[1h])
+
+ - slug: iconblockchain
+ name: iconblockchain.xyz
+ tag: iconblockchain.xyz community-operated public ICON RPC, no API key required
+ formula: "50th percentile over 24h of client-side round-trip latency (ms) for a POST /api/v3 icx_getLastBlock sent every 60s from 3 regions to api.iconblockchain.xyz."
+ queries:
+ p50: avg(ocb:rpc_latency_milliseconds:p50_24h{provider="iconblockchain", chain="icon"})
+ p90: avg(ocb:rpc_latency_milliseconds:p90_24h{provider="iconblockchain", chain="icon"})
+ p99: avg(ocb:rpc_latency_milliseconds:p99_24h{provider="iconblockchain", chain="icon"})
+ mean: avg(ocb:rpc_latency_milliseconds:mean_24h{provider="iconblockchain", chain="icon"})
+ success: sum(ocb:rpc_call:ok_rate_24h{provider="iconblockchain", chain="icon"}) / sum(ocb:rpc_call:rate_24h{provider="iconblockchain", chain="icon"})
+ sample_size: sum(ocb:rpc_call:increase_24h{provider="iconblockchain", chain="icon"})
+ series: avg(avg_over_time(rpc_latency_milliseconds{provider="iconblockchain", chain="icon"}[1h]))
+ regions:
+ - region: us-east
+ p50: avg(ocb:rpc_latency_milliseconds:p50_24h{provider="iconblockchain", chain="icon", region="us-east"})
+ series: avg_over_time(rpc_latency_milliseconds{provider="iconblockchain", chain="icon", region="us-east"}[1h])
+ - region: eu-west
+ p50: avg(ocb:rpc_latency_milliseconds:p50_24h{provider="iconblockchain", chain="icon", region="eu-west"})
+ series: avg_over_time(rpc_latency_milliseconds{provider="iconblockchain", chain="icon", region="eu-west"}[1h])
+ - region: ap-southeast
+ p50: avg(ocb:rpc_latency_milliseconds:p50_24h{provider="iconblockchain", chain="icon", region="sgp"})
+ series: avg_over_time(rpc_latency_milliseconds{provider="iconblockchain", chain="icon", region="sgp"}[1h])
diff --git a/benchmarks/keyed-rpc-robinhood.yml b/benchmarks/keyed-rpc-robinhood.yml
index 09e914e11..e572a2e46 100644
--- a/benchmarks/keyed-rpc-robinhood.yml
+++ b/benchmarks/keyed-rpc-robinhood.yml
@@ -2,10 +2,10 @@
slug: keyed-rpc-robinhood
number: "243"
-title: "Fastest Robinhood Chain RPC with API key: Chainstack vs Alchemy vs QuickNode"
-seo_title: "Fastest Robinhood Chain RPC API key 2026: Chainstack vs Alchemy vs QuickNode latency"
-seo_description: "Live Robinhood Chain RPC latency: Chainstack, Alchemy, and QuickNode keyed endpoints probed every 60s from US-East. First public benchmark for this Arbitrum Orbit L2."
-subtitle: eth_getBlockByNumber latency against keyed Robinhood Chain endpoints (Chainstack, Alchemy, QuickNode), probed every 60 seconds from US-East.
+title: "Fastest Robinhood Chain RPC with API key, measured from Singapore: Chainstack vs Alchemy vs QuickNode"
+seo_title: "Fastest Robinhood Chain RPC API key 2026 from Singapore: Chainstack vs Alchemy vs QuickNode latency"
+seo_description: "Live Robinhood Chain RPC latency: Chainstack, Alchemy, and QuickNode keyed endpoints probed every 60s. First public benchmark for this Arbitrum Orbit L2."
+subtitle: eth_getBlockByNumber latency against keyed Robinhood Chain endpoints (Chainstack, Alchemy, QuickNode), probed every 60 seconds.
category: RPCs
status: live
metric: RPC latency
@@ -18,39 +18,58 @@ seo_intro: |
subsidy running through September 2026. It is a financially-sensitive
chain with low tolerance for RPC latency.
This bench probes Chainstack, Alchemy, and QuickNode on their keyed
- Robinhood Chain endpoints every 60 seconds from US-East (Virginia).
- Chainstack leads from US-East. This is the only independently-run public
+ Robinhood Chain endpoints every 60 seconds from two probe regions
+ (Singapore and US-East). Chainstack posts the lowest median from the Singapore probe, the origin the
+ headline cites; the region tabs re-scope every figure. Round-trip latency depends on where the probe
+ sits relative to each provider's serving node, so no figure here is a chain-wide claim.
+ This is the only independently-run public
latency benchmark for Robinhood Chain.
abstract: |
We measure the round-trip latency of eth_getBlockByNumber against the
keyed endpoints of Chainstack, Alchemy, and QuickNode on Robinhood Chain.
The probe uses a rotating JSON-RPC id to defeat edge caches, fires every
- 60 seconds from US-East (Virginia), and classifies each response as ok,
+ 60 seconds from each probe region, and classifies each response as ok,
http_err, jsonrpc_err, stale, or timeout. Only ok responses contribute to
the latency distribution.
methodology:
- - "Cadence: every 60 seconds per provider from US-East (Virginia). Robinhood Chain runs on the Arbitrum Orbit stack with ~100ms block times; the 60s probe cadence captures 600 blocks per hour."
+ - "Cadence: every 60 seconds per provider from each probe region (Singapore; US-East Virginia). Headline numbers cite the Singapore probe; the region tabs re-scope every figure to one origin, and provider ranking is origin-dependent. Robinhood Chain runs on the Arbitrum Orbit stack with ~100ms block times; the 60s probe cadence captures 600 blocks per hour."
- "Payload: eth_getBlockByNumber('latest', false) with a rotating JSON-RPC id. Non-cacheable by design: the rotating id defeats body-keyed edge caches and forces the provider to serve a real Robinhood Chain tip."
- "Authentication: Chainstack Global Node key (node-scoped, dedicated to Robinhood Chain; key in Railway env, not in repo). Alchemy uses its standard multi-chain key. QuickNode uses its shared-endpoint key for Robinhood Chain. All keys in Railway env, not in repo."
- "Call-result classification: ok (HTTP 200 with usable block number), http_err, jsonrpc_err (HTTP 200 with error body), stale (block more than 20 behind the cross-provider tip), timeout. Latency is recorded only for ok responses."
- "Cohort: Chainstack, Alchemy, and QuickNode. Infura and Ankr do not currently list Robinhood Chain in their supported networks."
findings:
- - "{{name:chainstack}} leads at {{p50:chainstack}} (p50, 24h) from US-East. Its Global Node, launched first on Robinhood Chain, delivers the lowest round-trip latency in this benchmark."
- - "{{name:alchemy}} records {{p50:alchemy}} from US-East via its edge infrastructure."
+ - "{{name:chainstack}} leads at {{p50:chainstack}} (p50, 24h). Its Global Node, launched first on Robinhood Chain, delivers the lowest round-trip latency in this benchmark."
+ - "{{name:alchemy}} records {{p50:alchemy}} via its edge infrastructure."
- "{{name:quicknode}} delivers {{p50:quicknode}} via its shared-endpoint fleet."
+# Per-cell (region) ranking matrix for scoped badge claims. Chain is
+# fixed for the whole bench, so cells key on provider and region.
+rank_matrix_query: avg by (provider, region) (ocb:rpc_latency_milliseconds:p50_24h{chain="robinhood",tier="keyed"})
+
+# Headline view pinned to the Singapore probe: the default (unfiltered)
+# build, TL;DR, JSON-LD and OG cite the sgp slice; the US-East tab
+# re-scopes on click. No "all" option on purpose — an average across a
+# healthy region and a degraded one describes neither.
+aggregate_filters:
+ region: sgp
+
+dimensions:
+ region:
+ - { value: sgp, label: Singapore }
+ - { value: us-east, label: US-East }
+
faq:
- q: "Which Robinhood Chain RPC provider is fastest?"
- a: "Per the live data above: {{name:chainstack}} at {{p50:chainstack}} p50 (24h, from US-East). It is the lowest-latency keyed provider for Robinhood Chain in this benchmark."
+ a: "From the Singapore probe: {{name:chainstack}} at {{p50:chainstack}} p50 (24h), the lowest-latency keyed provider for Robinhood Chain as measured from that origin."
- q: "What is Robinhood Chain?"
a: "Robinhood Chain (chain ID 4663) is an Arbitrum Orbit L2 built by Robinhood for tokenized equity trading. It launched on mainnet in July 2026 with ~100ms block times and a gas fee subsidy running through September 29, 2026. It supports EVM-compatible tooling and standard eth_ JSON-RPC methods."
- q: "Why is this the only Robinhood Chain latency benchmark?"
a: "Robinhood Chain is a new chain. OpenChainBench added it within days of Chainstack, Alchemy, and QuickNode support going live, making this the first and so far only independent latency measurement. Infura and Ankr do not currently list Robinhood Chain in their supported networks."
- - q: "Why only US-East?"
- a: "The probe runs from US-East (Virginia) where the Railway infrastructure is located. Additional probe regions may be added as the chain matures and more regional endpoints become available."
+ - q: "Which regions does this benchmark probe from?"
+ a: "Two origins: Singapore and US-East (Virginia). The headline figures cite the Singapore probe; the region tabs at the top of the page re-scope every number to a single origin. Round-trip latency depends on where the probe sits relative to each provider's serving node, so provider ranking is origin-dependent and no figure is a chain-wide claim."
source: https://github.com/ChainBench/OpenChainBench/tree/main/harnesses/rpc-keyed-latency
@@ -62,38 +81,59 @@ providers:
- slug: chainstack
name: Chainstack
tag: Global Node, keyed Robinhood Chain endpoint, plan disclosed
- formula: "p50 over 24h of round-trip latency (ms) for eth_getBlockByNumber probed every 60s from US-East on Chainstack's keyed Robinhood Chain Global Node."
+ formula: "p50 over 24h of round-trip latency (ms) for eth_getBlockByNumber probed every 60s per region on Chainstack's keyed Robinhood Chain Global Node."
queries:
- p50: avg(ocb:rpc_latency_milliseconds:p50_24h{provider="chainstack",chain="robinhood",tier="keyed",region="us-east"})
- p90: avg(ocb:rpc_latency_milliseconds:p90_24h{provider="chainstack",chain="robinhood",tier="keyed",region="us-east"})
- p99: avg(ocb:rpc_latency_milliseconds:p99_24h{provider="chainstack",chain="robinhood",tier="keyed",region="us-east"})
- mean: avg(ocb:rpc_latency_milliseconds:mean_24h{provider="chainstack",chain="robinhood",tier="keyed",region="us-east"})
- success: sum(ocb:rpc_call:ok_rate_24h{provider="chainstack",chain="robinhood",tier="keyed",region="us-east"}) / sum(ocb:rpc_call:rate_24h{provider="chainstack",chain="robinhood",tier="keyed",region="us-east"})
- sample_size: sum(ocb:rpc_call:increase_24h{provider="chainstack",chain="robinhood",tier="keyed",region="us-east"})
- series: avg(avg_over_time(rpc_latency_milliseconds{provider="chainstack",chain="robinhood",tier="keyed",region="us-east"}[1h]))
+ p50: avg(ocb:rpc_latency_milliseconds:p50_24h{provider="chainstack",chain="robinhood",tier="keyed"})
+ p90: avg(ocb:rpc_latency_milliseconds:p90_24h{provider="chainstack",chain="robinhood",tier="keyed"})
+ p99: avg(ocb:rpc_latency_milliseconds:p99_24h{provider="chainstack",chain="robinhood",tier="keyed"})
+ mean: avg(ocb:rpc_latency_milliseconds:mean_24h{provider="chainstack",chain="robinhood",tier="keyed"})
+ success: sum(ocb:rpc_call:ok_rate_24h{provider="chainstack",chain="robinhood",tier="keyed"}) / sum(ocb:rpc_call:rate_24h{provider="chainstack",chain="robinhood",tier="keyed"})
+ sample_size: sum(ocb:rpc_call:increase_24h{provider="chainstack",chain="robinhood",tier="keyed"})
+ series: avg(avg_over_time(rpc_latency_milliseconds{provider="chainstack",chain="robinhood",tier="keyed"}[1h]))
+ regions:
+ - region: us-east
+ p50: avg(ocb:rpc_latency_milliseconds:p50_24h{provider="chainstack",chain="robinhood",tier="keyed",region="us-east"})
+ series: avg_over_time(rpc_latency_milliseconds{provider="chainstack",chain="robinhood",tier="keyed",region="us-east"}[1h])
+ - region: sgp
+ p50: avg(ocb:rpc_latency_milliseconds:p50_24h{provider="chainstack",chain="robinhood",tier="keyed",region="sgp"})
+ series: avg_over_time(rpc_latency_milliseconds{provider="chainstack",chain="robinhood",tier="keyed",region="sgp"}[1h])
- slug: quicknode
name: QuickNode
tag: Shared endpoint fleet, keyed Robinhood Chain endpoint, plan disclosed
- formula: "p50 over 24h of round-trip latency (ms) for eth_getBlockByNumber probed every 60s from US-East on QuickNode's keyed Robinhood Chain endpoint."
+ formula: "p50 over 24h of round-trip latency (ms) for eth_getBlockByNumber probed every 60s per region on QuickNode's keyed Robinhood Chain endpoint."
queries:
- p50: avg(ocb:rpc_latency_milliseconds:p50_24h{provider="quicknode",chain="robinhood",tier="keyed",region="us-east"})
- p90: avg(ocb:rpc_latency_milliseconds:p90_24h{provider="quicknode",chain="robinhood",tier="keyed",region="us-east"})
- p99: avg(ocb:rpc_latency_milliseconds:p99_24h{provider="quicknode",chain="robinhood",tier="keyed",region="us-east"})
- mean: avg(ocb:rpc_latency_milliseconds:mean_24h{provider="quicknode",chain="robinhood",tier="keyed",region="us-east"})
- success: sum(ocb:rpc_call:ok_rate_24h{provider="quicknode",chain="robinhood",tier="keyed",region="us-east"}) / sum(ocb:rpc_call:rate_24h{provider="quicknode",chain="robinhood",tier="keyed",region="us-east"})
- sample_size: sum(ocb:rpc_call:increase_24h{provider="quicknode",chain="robinhood",tier="keyed",region="us-east"})
- series: avg(avg_over_time(rpc_latency_milliseconds{provider="quicknode",chain="robinhood",tier="keyed",region="us-east"}[1h]))
+ p50: avg(ocb:rpc_latency_milliseconds:p50_24h{provider="quicknode",chain="robinhood",tier="keyed"})
+ p90: avg(ocb:rpc_latency_milliseconds:p90_24h{provider="quicknode",chain="robinhood",tier="keyed"})
+ p99: avg(ocb:rpc_latency_milliseconds:p99_24h{provider="quicknode",chain="robinhood",tier="keyed"})
+ mean: avg(ocb:rpc_latency_milliseconds:mean_24h{provider="quicknode",chain="robinhood",tier="keyed"})
+ success: sum(ocb:rpc_call:ok_rate_24h{provider="quicknode",chain="robinhood",tier="keyed"}) / sum(ocb:rpc_call:rate_24h{provider="quicknode",chain="robinhood",tier="keyed"})
+ sample_size: sum(ocb:rpc_call:increase_24h{provider="quicknode",chain="robinhood",tier="keyed"})
+ series: avg(avg_over_time(rpc_latency_milliseconds{provider="quicknode",chain="robinhood",tier="keyed"}[1h]))
+ regions:
+ - region: us-east
+ p50: avg(ocb:rpc_latency_milliseconds:p50_24h{provider="quicknode",chain="robinhood",tier="keyed",region="us-east"})
+ series: avg_over_time(rpc_latency_milliseconds{provider="quicknode",chain="robinhood",tier="keyed",region="us-east"}[1h])
+ - region: sgp
+ p50: avg(ocb:rpc_latency_milliseconds:p50_24h{provider="quicknode",chain="robinhood",tier="keyed",region="sgp"})
+ series: avg_over_time(rpc_latency_milliseconds{provider="quicknode",chain="robinhood",tier="keyed",region="sgp"}[1h])
- slug: alchemy
name: Alchemy
tag: 30M CU/mo free, keyed Robinhood Chain endpoint
- formula: "p50 over 24h of round-trip latency (ms) for eth_getBlockByNumber probed every 60s from US-East on Alchemy's keyed Robinhood Chain endpoint."
+ formula: "p50 over 24h of round-trip latency (ms) for eth_getBlockByNumber probed every 60s per region on Alchemy's keyed Robinhood Chain endpoint."
queries:
- p50: avg(ocb:rpc_latency_milliseconds:p50_24h{provider="alchemy",chain="robinhood",tier="keyed",region="us-east"})
- p90: avg(ocb:rpc_latency_milliseconds:p90_24h{provider="alchemy",chain="robinhood",tier="keyed",region="us-east"})
- p99: avg(ocb:rpc_latency_milliseconds:p99_24h{provider="alchemy",chain="robinhood",tier="keyed",region="us-east"})
- mean: avg(ocb:rpc_latency_milliseconds:mean_24h{provider="alchemy",chain="robinhood",tier="keyed",region="us-east"})
- success: sum(ocb:rpc_call:ok_rate_24h{provider="alchemy",chain="robinhood",tier="keyed",region="us-east"}) / sum(ocb:rpc_call:rate_24h{provider="alchemy",chain="robinhood",tier="keyed",region="us-east"})
- sample_size: sum(ocb:rpc_call:increase_24h{provider="alchemy",chain="robinhood",tier="keyed",region="us-east"})
- series: avg(avg_over_time(rpc_latency_milliseconds{provider="alchemy",chain="robinhood",tier="keyed",region="us-east"}[1h]))
+ p50: avg(ocb:rpc_latency_milliseconds:p50_24h{provider="alchemy",chain="robinhood",tier="keyed"})
+ p90: avg(ocb:rpc_latency_milliseconds:p90_24h{provider="alchemy",chain="robinhood",tier="keyed"})
+ p99: avg(ocb:rpc_latency_milliseconds:p99_24h{provider="alchemy",chain="robinhood",tier="keyed"})
+ mean: avg(ocb:rpc_latency_milliseconds:mean_24h{provider="alchemy",chain="robinhood",tier="keyed"})
+ success: sum(ocb:rpc_call:ok_rate_24h{provider="alchemy",chain="robinhood",tier="keyed"}) / sum(ocb:rpc_call:rate_24h{provider="alchemy",chain="robinhood",tier="keyed"})
+ sample_size: sum(ocb:rpc_call:increase_24h{provider="alchemy",chain="robinhood",tier="keyed"})
+ series: avg(avg_over_time(rpc_latency_milliseconds{provider="alchemy",chain="robinhood",tier="keyed"}[1h]))
+ regions:
+ - region: us-east
+ p50: avg(ocb:rpc_latency_milliseconds:p50_24h{provider="alchemy",chain="robinhood",tier="keyed",region="us-east"})
+ series: avg_over_time(rpc_latency_milliseconds{provider="alchemy",chain="robinhood",tier="keyed",region="us-east"}[1h])
+ - region: sgp
+ p50: avg(ocb:rpc_latency_milliseconds:p50_24h{provider="alchemy",chain="robinhood",tier="keyed",region="sgp"})
+ series: avg_over_time(rpc_latency_milliseconds{provider="alchemy",chain="robinhood",tier="keyed",region="sgp"}[1h])
diff --git a/benchmarks/lukso-rpc.yml b/benchmarks/lukso-rpc.yml
new file mode 100644
index 000000000..bb7092c04
--- /dev/null
+++ b/benchmarks/lukso-rpc.yml
@@ -0,0 +1,127 @@
+# OpenChainBench. Bench No 260
+
+slug: lukso-rpc
+number: "260"
+title: Fastest free LUKSO RPC, live no-key endpoint latency
+seo_title: "Fastest free LUKSO RPC 2026"
+seo_description: "{{best_name}} leads free LUKSO RPC at {{best_p50}} (eth_getBlockByNumber p50, 24h). 3 no-key providers measured every 60s from 3 regions."
+subtitle: HTTP round-trip latency for eth_getBlockByNumber against every available public LUKSO endpoint, audited every 60 seconds from 3 regions.
+
+category: RPCs
+status: live
+metric: RPC latency
+unit: ms
+higher_is_better: false
+
+seo_intro: |
+ LUKSO is an EVM layer-1 (chain ID 42) built around Universal Profiles and the LSP standards. It speaks standard Ethereum JSON-RPC, so the canonical height probe is eth_getBlockByNumber('latest'). Three providers serve LUKSO mainnet with no API key: the LUKSO Foundation's official RPC, NOWNodes (public no-key endpoint, 5 requests per second) and thirdweb's public edge. All three were live-verified at launch, returning the same chain tip.
+
+abstract: |
+ Per-chain member of the RPC latency cluster, extended to LUKSO. We probe every
+ public no-key endpoint with a single eth_getBlockByNumber call every 60 seconds from three
+ regions (us-east, eu-west, Singapore) and record wall-clock round-trip latency. Only ok
+ responses (HTTP 200 with a usable block height, not stale) contribute to the distribution;
+ p50/p90/p99 are computed via Prometheus quantile_over_time over a rolling 24-hour window.
+
+methodology:
+ - "Probe: a single eth_getBlockByNumber('latest', false) JSON-RPC POST every 60 seconds per provider from us-east, eu-west and Singapore. Latency is the client-side wall-clock round trip."
+ - "Classification: ok (HTTP 200 with a usable block number), http_err, jsonrpc_err, stale (block more than 20 behind the cross-provider tip), timeout. Only ok responses contribute latency."
+ - "Providers: LUKSO Foundation (rpc.mainnet.lukso.network), NOWNodes (public-lukso.nownodes.io, no key, 5 RPS), thirdweb (42.rpc.thirdweb.com)."
+ - "Aggregation: p50/p90/p99 via Prometheus quantile_over_time over 24h; the headline cites the cross-region figure and the region tabs re-scope it."
+
+findings:
+ - "{{best_name}} leads at {{best_p50}} (eth_getBlockByNumber p50, 24h) across {{provider_count}} no-key LUKSO endpoints."
+ - "LUKSO targets a short EVM block time, so a stale tip is uncommon and the ranking is driven by each provider's regional edge placement."
+ - "{{name:nownodes}} serves LUKSO from its public no-key endpoint at {{p50:nownodes}} (p50, 24h), rate-limited to 5 requests per second."
+
+faq:
+ - q: "Which free LUKSO RPC is fastest?"
+ a: "Per the live data above: {{best_name}} at {{best_p50}} (eth_getBlockByNumber p50, 24h) across the no-key providers measured here."
+ - q: "How is LUKSO RPC latency measured?"
+ a: "A single eth_getBlockByNumber call every 60 seconds from three regions (us-east, eu-west, Singapore), recording the wall-clock round trip. Only successful, non-stale responses count."
+ - q: "Do these endpoints need an API key?"
+ a: "No. LUKSO Foundation, NOWNodes and thirdweb all serve LUKSO mainnet JSON-RPC with no key. NOWNodes' public endpoint is rate-limited to 5 requests per second."
+
+source: https://github.com/ChainBench/OpenChainBench/tree/main/harnesses/rpc-capabilities
+
+prometheus:
+ window: 24h
+ expected_freshness_seconds: 5400
+
+rank_matrix_query: avg by (provider, region) (ocb:rpc_latency_milliseconds:p50_24h{chain="lukso"})
+
+dimensions:
+ region:
+ - { value: us-east, label: US-East }
+ - { value: eu-west, label: EU-West }
+ - { value: sgp, label: Singapore }
+
+providers:
+ - slug: lukso-official
+ name: LUKSO Foundation
+ tag: Official LUKSO mainnet RPC, no key
+ formula: "50th percentile over 24h of client-side round-trip latency (ms) for a single `eth_getBlockByNumber` sent every 60s from 3 regions (us-east + eu-west + sgp) to LUKSO Foundation's no-key LUKSO endpoint."
+ queries:
+ p50: avg(ocb:rpc_latency_milliseconds:p50_24h{provider="lukso-official", chain="lukso"})
+ p90: avg(ocb:rpc_latency_milliseconds:p90_24h{provider="lukso-official", chain="lukso"})
+ p99: avg(ocb:rpc_latency_milliseconds:p99_24h{provider="lukso-official", chain="lukso"})
+ mean: avg(ocb:rpc_latency_milliseconds:mean_24h{provider="lukso-official", chain="lukso"})
+ success: sum(ocb:rpc_call:ok_rate_24h{provider="lukso-official", chain="lukso"}) / sum(ocb:rpc_call:rate_24h{provider="lukso-official", chain="lukso"})
+ sample_size: sum(ocb:rpc_call:increase_24h{provider="lukso-official", chain="lukso"})
+ series: avg(avg_over_time(rpc_latency_milliseconds{provider="lukso-official", chain="lukso"}[1h]))
+ regions:
+ - region: us-east
+ p50: avg(ocb:rpc_latency_milliseconds:p50_24h{provider="lukso-official", chain="lukso", region="us-east"})
+ series: avg_over_time(rpc_latency_milliseconds{provider="lukso-official", chain="lukso", region="us-east"}[1h])
+ - region: eu-west
+ p50: avg(ocb:rpc_latency_milliseconds:p50_24h{provider="lukso-official", chain="lukso", region="eu-west"})
+ series: avg_over_time(rpc_latency_milliseconds{provider="lukso-official", chain="lukso", region="eu-west"}[1h])
+ - region: ap-southeast
+ p50: avg(ocb:rpc_latency_milliseconds:p50_24h{provider="lukso-official", chain="lukso", region="sgp"})
+ series: avg_over_time(rpc_latency_milliseconds{provider="lukso-official", chain="lukso", region="sgp"}[1h])
+
+ - slug: nownodes
+ name: NOWNodes
+ tag: Public node, 110+ chains, 5 RPS free tier
+ formula: "50th percentile over 24h of client-side round-trip latency (ms) for a single `eth_getBlockByNumber` sent every 60s from 3 regions (us-east + eu-west + sgp) to NOWNodes's no-key LUKSO endpoint."
+ queries:
+ p50: avg(ocb:rpc_latency_milliseconds:p50_24h{provider="nownodes", chain="lukso"})
+ p90: avg(ocb:rpc_latency_milliseconds:p90_24h{provider="nownodes", chain="lukso"})
+ p99: avg(ocb:rpc_latency_milliseconds:p99_24h{provider="nownodes", chain="lukso"})
+ mean: avg(ocb:rpc_latency_milliseconds:mean_24h{provider="nownodes", chain="lukso"})
+ success: sum(ocb:rpc_call:ok_rate_24h{provider="nownodes", chain="lukso"}) / sum(ocb:rpc_call:rate_24h{provider="nownodes", chain="lukso"})
+ sample_size: sum(ocb:rpc_call:increase_24h{provider="nownodes", chain="lukso"})
+ series: avg(avg_over_time(rpc_latency_milliseconds{provider="nownodes", chain="lukso"}[1h]))
+ regions:
+ - region: us-east
+ p50: avg(ocb:rpc_latency_milliseconds:p50_24h{provider="nownodes", chain="lukso", region="us-east"})
+ series: avg_over_time(rpc_latency_milliseconds{provider="nownodes", chain="lukso", region="us-east"}[1h])
+ - region: eu-west
+ p50: avg(ocb:rpc_latency_milliseconds:p50_24h{provider="nownodes", chain="lukso", region="eu-west"})
+ series: avg_over_time(rpc_latency_milliseconds{provider="nownodes", chain="lukso", region="eu-west"}[1h])
+ - region: ap-southeast
+ p50: avg(ocb:rpc_latency_milliseconds:p50_24h{provider="nownodes", chain="lukso", region="sgp"})
+ series: avg_over_time(rpc_latency_milliseconds{provider="nownodes", chain="lukso", region="sgp"}[1h])
+
+ - slug: thirdweb
+ name: thirdweb
+ tag: Multi-chain RPC edge, no-key public endpoint
+ formula: "50th percentile over 24h of client-side round-trip latency (ms) for a single `eth_getBlockByNumber` sent every 60s from 3 regions (us-east + eu-west + sgp) to thirdweb's no-key LUKSO endpoint."
+ queries:
+ p50: avg(ocb:rpc_latency_milliseconds:p50_24h{provider="thirdweb", chain="lukso"})
+ p90: avg(ocb:rpc_latency_milliseconds:p90_24h{provider="thirdweb", chain="lukso"})
+ p99: avg(ocb:rpc_latency_milliseconds:p99_24h{provider="thirdweb", chain="lukso"})
+ mean: avg(ocb:rpc_latency_milliseconds:mean_24h{provider="thirdweb", chain="lukso"})
+ success: sum(ocb:rpc_call:ok_rate_24h{provider="thirdweb", chain="lukso"}) / sum(ocb:rpc_call:rate_24h{provider="thirdweb", chain="lukso"})
+ sample_size: sum(ocb:rpc_call:increase_24h{provider="thirdweb", chain="lukso"})
+ series: avg(avg_over_time(rpc_latency_milliseconds{provider="thirdweb", chain="lukso"}[1h]))
+ regions:
+ - region: us-east
+ p50: avg(ocb:rpc_latency_milliseconds:p50_24h{provider="thirdweb", chain="lukso", region="us-east"})
+ series: avg_over_time(rpc_latency_milliseconds{provider="thirdweb", chain="lukso", region="us-east"}[1h])
+ - region: eu-west
+ p50: avg(ocb:rpc_latency_milliseconds:p50_24h{provider="thirdweb", chain="lukso", region="eu-west"})
+ series: avg_over_time(rpc_latency_milliseconds{provider="thirdweb", chain="lukso", region="eu-west"}[1h])
+ - region: ap-southeast
+ p50: avg(ocb:rpc_latency_milliseconds:p50_24h{provider="thirdweb", chain="lukso", region="sgp"})
+ series: avg_over_time(rpc_latency_milliseconds{provider="thirdweb", chain="lukso", region="sgp"}[1h])
diff --git a/benchmarks/metadata-coverage.yml b/benchmarks/metadata-coverage.yml
index 0381ff2f3..41f764c48 100644
--- a/benchmarks/metadata-coverage.yml
+++ b/benchmarks/metadata-coverage.yml
@@ -156,6 +156,19 @@ providers:
sample_size: sum(increase(metadata_coverage_checks_total{provider="codex"}[24h]))
series: 100 * sum(rate(metadata_coverage_success_total{provider="codex"}[1h])) / sum(rate(metadata_coverage_checks_total{provider="codex"}[1h]))
+ - slug: serialized
+ name: Serialized
+ tag: REST `/v1/token/metadata`
+ formula: "Median of hourly coverage rate (populated logo/description/twitter/website fields divided by total field checks) on Serialized `/v1/token/metadata` for fresh tokens, p50 over 24h."
+ queries:
+ p50: quantile_over_time(0.50, (100 * sum(rate(metadata_coverage_success_total{provider="serialized"}[1h])) / sum(rate(metadata_coverage_checks_total{provider="serialized"}[1h])))[24h:1h])
+ p90: quantile_over_time(0.90, (100 * sum(rate(metadata_coverage_success_total{provider="serialized"}[1h])) / sum(rate(metadata_coverage_checks_total{provider="serialized"}[1h])))[24h:1h])
+ p99: quantile_over_time(0.99, (100 * sum(rate(metadata_coverage_success_total{provider="serialized"}[1h])) / sum(rate(metadata_coverage_checks_total{provider="serialized"}[1h])))[24h:1h])
+ mean: 100 * sum(rate(metadata_coverage_success_total{provider="serialized"}[24h])) / sum(rate(metadata_coverage_checks_total{provider="serialized"}[24h]))
+ success: clamp_max(sum(rate(metadata_coverage_checks_total{provider="serialized"}[24h])) / scalar(sum(rate(metadata_coverage_checks_total{provider="mobula"}[24h]))), 1)
+ sample_size: sum(increase(metadata_coverage_checks_total{provider="serialized"}[24h]))
+ series: 100 * sum(rate(metadata_coverage_success_total{provider="serialized"}[1h])) / sum(rate(metadata_coverage_checks_total{provider="serialized"}[1h]))
+
- slug: jupiter
name: Jupiter
tag: REST `/v6/tokens` (Solana)
diff --git a/benchmarks/rpc-capabilities.yml b/benchmarks/rpc-capabilities.yml
index cee5e02a3..5e265b958 100644
--- a/benchmarks/rpc-capabilities.yml
+++ b/benchmarks/rpc-capabilities.yml
@@ -187,6 +187,29 @@ providers:
p50: avg(ocb:rpc_latency_milliseconds:p50_24h{provider="publicnode", region="sgp"})
series: avg_over_time(rpc_latency_milliseconds{provider="publicnode", region="sgp"}[1h])
+ - slug: nownodes
+ name: NOWNodes
+ tag: Public node, 110+ chains, 5 RPS free tier
+ formula: "Mean over its measured chains (Ethereum, BNB, Bitcoin, LUKSO, Coreum) and 3 regions of per-cell 24h p50 round-trip latency (ms) to NOWNodes public no-key endpoints. Successful calls only; failures show in the Success column."
+ queries:
+ p50: avg(ocb:rpc_latency_milliseconds:p50_24h{provider="nownodes"})
+ p90: avg(ocb:rpc_latency_milliseconds:p90_24h{provider="nownodes"})
+ p99: avg(ocb:rpc_latency_milliseconds:p99_24h{provider="nownodes"})
+ mean: avg(ocb:rpc_latency_milliseconds:mean_24h{provider="nownodes"})
+ success: sum(ocb:rpc_call:ok_rate_24h{provider="nownodes"}) / sum(ocb:rpc_call:rate_24h{provider="nownodes"})
+ sample_size: sum(ocb:rpc_call:increase_24h{provider="nownodes"})
+ series: avg(avg_over_time(rpc_latency_milliseconds{provider="nownodes"}[1h]))
+ regions:
+ - region: us-east
+ p50: avg(ocb:rpc_latency_milliseconds:p50_24h{provider="nownodes", region="us-east"})
+ series: avg_over_time(rpc_latency_milliseconds{provider="nownodes", region="us-east"}[1h])
+ - region: eu-west
+ p50: avg(ocb:rpc_latency_milliseconds:p50_24h{provider="nownodes", region="eu-west"})
+ series: avg_over_time(rpc_latency_milliseconds{provider="nownodes", region="eu-west"}[1h])
+ - region: ap-southeast
+ p50: avg(ocb:rpc_latency_milliseconds:p50_24h{provider="nownodes", region="sgp"})
+ series: avg_over_time(rpc_latency_milliseconds{provider="nownodes", region="sgp"}[1h])
+
- slug: drpc
name: dRPC
tag: Decentralized RPC mesh, consensus-checked
diff --git a/benchmarks/solana-dex-volume.yml b/benchmarks/solana-dex-volume.yml
index 2c926edfc..5d7264322 100644
--- a/benchmarks/solana-dex-volume.yml
+++ b/benchmarks/solana-dex-volume.yml
@@ -16,6 +16,10 @@ higher_is_better: true
disclaimer: |
Volume and revenue data sourced from DeFiLlama's DEX and fees APIs. DeFiLlama attributes volume via on-chain referral tags and program IDs; platforms without standard tags may be undercounted. Figures are 24-hour and 7-day rolling totals updated every 30 minutes. Revenue uses DeFiLlama's dailyRevenue dataType, stripping LP fees. Terminal volume (Axiom, GMGN, Fomo) and launchpad volume (pump.fun) use different attribution methods and are not directly additive.
+provider_notes:
+ gmgn: "Solana only"
+ fomo: "on-chain swaps only"
+
seo_intro: |
The Solana DEX market in summer 2026 is split between pump.fun's launchpad,
which dominates early-stage token trading, and a set of competing terminals
@@ -38,9 +42,10 @@ abstract: |
defillama_dex_take_rate and defillama_dex_health.
methodology:
- - "Source: DeFiLlama public DEX API (api.llama.fi/summary/dexs/{slug}) and fees API (api.llama.fi/summary/fees/{slug}?dataType=dailyRevenue). No authentication required."
- - "Poll cadence: harness fetches each platform every 30 minutes and updates Prometheus gauges immediately."
- - "Volume: total24h and total7d fields from DeFiLlama's DEX endpoint, sum of all swap notional values in USD attributed to this platform."
+ - "Source: DeFiLlama public chain overview APIs (api.llama.fi/overview/dexs/solana and overview/fees/solana?dataType=dailyRevenue), which report SOLANA-scoped totals per protocol. Multichain platforms such as GMGN therefore show their Solana volume only, keeping every row comparable. No authentication required."
+ - "Poll cadence: harness fetches every 30 minutes and updates Prometheus gauges immediately."
+ - "Fomo caveat: DeFiLlama attributes Fomo volume from on-chain Solana swaps matched to its fee wallet, while its revenue figure also includes relay fees the Fomo team self-reports. Fomo's displayed revenue-to-volume ratio (~5%) versus its stated 0.5% fee implies substantially higher total activity than the on-chain volume shown here; its Hyperliquid perps flow is tracked separately on the hyperliquid-frontends bench."
+ - "Volume: total24h and total7d fields, sum of swap notional values in USD attributed to this platform on Solana. DeFiLlama aggregates in daily buckets with roughly a 10-hour indexing delay, so intraday values are flat by construction and step once per day."
- "Revenue: total24h and total7d from the fees endpoint with dataType=dailyRevenue. This strips LP fees that stay in pool accounts and returns only the protocol's own share. Note: dailyRevenue also nets out referral paybacks (e.g. GMGN nets ~34% back to affiliates) and for pump.fun reflects the buyback-adjusted split introduced in 2026. Use dailyFees if comparing what users pay, not what protocols retain."
- "Take rate: protocol revenue divided by trading volume (defillama_dex_fees_24h_usd / defillama_dex_volume_24h_usd). Measures how much of each dollar traded the platform captures."
- "Attribution: DeFiLlama uses on-chain referral tags, program IDs and memo fields. Platforms without standard tagging may be undercounted."
diff --git a/benchmarks/wallet-labels-coverage.yml b/benchmarks/wallet-labels-coverage.yml
index 2cfe912e6..a428f7842 100644
--- a/benchmarks/wallet-labels-coverage.yml
+++ b/benchmarks/wallet-labels-coverage.yml
@@ -151,6 +151,11 @@ prometheus:
# Default is `eoa` because the contract tab is trivially easy for explorers
# and saturates near 100%; the EOA tab is where curated entity coverage
# actually differentiates providers.
+# Scored on contested chains only: stellar, xrp and bitcoin carry a single
+# measured provider each, and a cross-chain average that counts them puts a
+# provider first for a chain nobody else reported. See spec-schema.ts.
+score_scope: contested_chains
+
dimensions:
kind:
- { value: eoa, label: EOA }
@@ -194,6 +199,19 @@ providers:
sample_size: sum(increase(wallet_labels_checks_total{provider="mobula"}[24h]))
series: 100 * sum(rate(wallet_labels_success_total{provider="mobula"}[1h])) / sum(rate(wallet_labels_checks_total{provider="mobula"}[1h]))
+ - slug: serialized
+ name: Serialized
+ tag: Identity graph, 18 EVM chains + Solana, API key required
+ formula: "Share of anchor addresses for which Serialized /v1/wallet/profile returns a non-generic identity name, success_total ÷ checks_total over 24h."
+ queries:
+ p50: 100 * sum(increase(wallet_labels_success_total{provider="serialized"}[24h])) / sum(increase(wallet_labels_checks_total{provider="serialized"}[24h]))
+ p90: 100 * sum(increase(wallet_labels_success_total{provider="serialized"}[24h])) / sum(increase(wallet_labels_checks_total{provider="serialized"}[24h]))
+ p99: 100 * sum(increase(wallet_labels_success_total{provider="serialized"}[24h])) / sum(increase(wallet_labels_checks_total{provider="serialized"}[24h]))
+ mean: 100 * sum(increase(wallet_labels_success_total{provider="serialized"}[24h])) / sum(increase(wallet_labels_checks_total{provider="serialized"}[24h]))
+ success: clamp_min(1 - (sum(increase(wallet_labels_fetch_errors_total{provider="serialized"}[24h])) or vector(0)) / clamp_min(sum(increase(wallet_labels_checks_total{provider="serialized"}[24h])), 1), 0)
+ sample_size: sum(increase(wallet_labels_checks_total{provider="serialized"}[24h]))
+ series: 100 * sum(rate(wallet_labels_success_total{provider="serialized"}[1h])) / sum(rate(wallet_labels_checks_total{provider="serialized"}[1h]))
+
- slug: helius
name: Helius
tag: Solana specialist, native program graph, API key required
diff --git a/docs/methodology/serialized-onboarding-audit.md b/docs/methodology/serialized-onboarding-audit.md
new file mode 100644
index 000000000..81d040389
--- /dev/null
+++ b/docs/methodology/serialized-onboarding-audit.md
@@ -0,0 +1,582 @@
+# Provider onboarding audit — Serialized (serialized.xyz)
+
+> **Pre-onboarding evaluation.** Run before Serialized is wired into any live harness, so the
+> decision to include or exclude them on each bench is documented and reproducible.
+>
+> **Version:** v1.4, 2026-09-08 (§8 corrected in v1.1; §16.3 root cause corrected and §17 added in v1.2; §18 added in v1.3: Serialized wired into bench 001; §18.1 added in v1.4: that wiring reverted after measurement). Author: internal. Key used: tenant `OpenChainBench`,
+> plan `starter`, keyId `d5511a080aaa`, issued 2026-09-04.
+
+---
+
+## 1. What this document is
+
+Serialized is a candidate provider for several existing OpenChainBench benchmarks. This file
+records the apples-to-apples tests run against them, the exact methodology of each test, the
+numbers that came back, and the methodology problems those tests exposed in **our own benches**.
+
+Every test below replicates the scoring rule of the target bench rather than inventing a new one,
+so the numbers are directly comparable to the published leaderboards.
+
+## 2. Test harness and vantage point
+
+| Property | Value |
+|---|---|
+| Host | `ocb-par-main` (the VPS that runs the production harnesses) |
+| Rationale | Same egress, same region, same network path as the live monitors. A latency or coverage number taken from a laptop is not comparable to a published bench value. |
+| Incumbent credentials | Read from the running `ocb-metadata-coverage` container env, never copied off the box |
+| Scripts | `~/serbench/ab.py`, `ab2.py`, `ab3.py`, `ab4.py`, `wsab.py` |
+| Date of run | 2026-09-05 |
+
+**Throttling matters.** Serialized enforces a hard burst cap of 40 in-flight requests per second.
+An unthrottled 8-worker pool produced 60 `429 RATE_LIMITED` responses out of 100 anchors and made
+their coverage look like 37%. The same test throttled to 12 rps produced 0 errors and 77%. Any
+harness that talks to them must rate-limit client-side, and any measurement that does not is wrong.
+
+## 3. Provider surface
+
+19 chains: 18 EVM plus Solana. `evm:1`, `evm:56`, `evm:130`, `evm:143`, `evm:196`, `evm:988`,
+`evm:1514`, `evm:2741`, `evm:4217`, `evm:4326`, `evm:4663`, `evm:5042`, `evm:8453`, `evm:9745`,
+`evm:42161`, `evm:43114`, `evm:57073`, `evm:645749`, `solana`. Audit engine covers the 18 EVM chains.
+
+Auth is a raw `Authorization` header, no `Bearer` prefix (same convention as Mobula). The
+documented `demo.serialized.xyz` server returns 403 outside their docs playground, so there is no
+keyless path for a harness.
+
+## 4. Rate limits and quota (measured, not quoted)
+
+| Property | Documented | Measured |
+|---|---|---|
+| Monthly credits (starter) | 150,000 | **1,000,000** on our key |
+| Per-minute rate | 1,200 | 1,200 (`x-ratelimit-limit` header) |
+| Burst | 40 req/s | Exactly 40. 60/100/150 concurrent all yielded exactly 40× `200` and the rest `429`. Deterministic, no jitter. |
+| Sustained | not stated | 891/891 `200` over 60 s at 15 rps, p50 38 ms, p99 67 ms |
+
+Response headers expose `x-ratelimit-limit`, `x-ratelimit-remaining`, `x-ratelimit-reset` and
+`x-credits-remaining`. Good enough to instrument a harness without guessing.
+
+Streams bill 1 credit per connection-minute. Limits are 5 concurrent connections, 20 subscriptions
+per connection and 50 distinct tokens or pools per key. Bench 001 runs 3 regions × 4 chains, which
+does not fit inside one key's 5-connection budget: it needs one key per region.
+
+## 5. Bench 008 — wallet-labels-coverage
+
+**Replica rule.** Identical to `harnesses/wallet-labels`: the same 178-anchor curated list, filtered
+to the 5 chains Serialized covers (100 anchors, 59 contract / 41 EOA); a "hit" is any non-generic
+name, using the harness's exact `genericLabel` exclusion set; Mobula queried through
+`POST /api/1/wallet/labels` with the same field-precedence (`entityName` → `entityLabels` → `labels`).
+Serialized queried through `GET /v1/wallet/profile`, taking the first non-generic of
+`displayName` → `ensName` → `basename` → `solName`.
+
+**Added dimension (not in the bench today):** accuracy. A hit is counted accurate when the returned
+label shares a meaningful token with the curated `Hint` for that anchor.
+
+| Provider | Coverage | Contract | EOA | Accurate | Accurate given hit | p50 |
+|---|---|---|---|---|---|---|
+| **Serialized** | **77.0%** | 76.3% | 78.0% | **58.0%** | 75.3% | 45 ms |
+| Mobula | 59.0% | 54.2% | 65.9% | 44.0% | 74.6% | 34 ms |
+
+Per chain (coverage / accuracy):
+
+| Chain | n | Serialized | Mobula |
+|---|---|---|---|
+| ethereum | 32 | 96.9% / 78.1% | 62.5% / 53.1% |
+| bnb | 15 | 80.0% / 60.0% | 80.0% / 46.7% |
+| base | 17 | 76.5% / 58.8% | 70.6% / 47.1% |
+| arbitrum | 17 | 58.8% / 52.9% | 47.1% / 41.2% |
+| solana | 19 | 57.9% / 26.3% | 36.8% / 26.3% |
+
+**Verdict: include.** Serialized leads on coverage and on absolute accuracy on every chain in scope.
+
+**Bench flaw this exposed.** 25% of Serialized's hits are wrong (19 of 77). Mobula's ratio is
+almost identical (74.6% accurate given hit). The bench scores presence of a non-generic string, so a
+personal ENS or `.sol` name registered against a well-known contract counts as a correct entity
+label. Concrete cases: Permit2 → `dex.davywoodfi.eth`, Uniswap V3 Router 2 → `factory.vibebet.eth`,
+Base USDC → `jakie.base.eth`, Raydium Authority → `bonklanatoken.sol`, BSC USDT → `Fake_Phishing6512`,
+OKX 1 → `Bittrex 3`, Bitfinex → `Polygon`.
+
+This is a pre-existing, provider-neutral gameability hole. It should be fixed **before** Serialized
+is published, not after, otherwise the fix looks like a reaction to a new entrant beating the
+incumbent. Recommended fix: score against the curated `Hint` (the harness already carries it and
+already ignores it), or exclude name-service strings from the hit rule.
+
+## 6. Bench 004 — metadata-coverage
+
+**Replica rule.** Same 4 fields as the bench (`logo`, `description`, `twitter`, `website`). Discovery
+via GeckoTerminal `new_pools` (an independent third source, so neither provider's own discovery
+biases the sample). Both providers queried on the **same token set**, and only tokens that **both**
+resolved are scored, so the denominator is identical.
+
+| Chain | paired n | Serialized | Mobula |
+|---|---|---|---|
+| solana | 48 | 10.4% | 34.4% |
+| base | 37 | 19.6% | 34.5% |
+| bsc | 38 | 60.5% | 80.9% |
+| **total** | 123 | **28.7%** | **48.8%** |
+
+Field breakdown:
+
+| Chain | Field | Serialized | Mobula |
+|---|---|---|---|
+| solana | logo | 22.9% | 100.0% |
+| solana | description | 8.3% | 25.0% |
+| solana | twitter | 8.3% | 8.3% |
+| solana | website | 2.1% | 4.2% |
+| base | logo | 37.8% | 100.0% |
+| base | description | 13.5% | 13.5% |
+| base | twitter | 18.9% | 16.2% |
+| base | website | 8.1% | 8.1% |
+| bsc | logo | 78.9% | 100.0% |
+| bsc | description | 78.9% | 71.1% |
+| bsc | twitter | 78.9% | 76.3% |
+| bsc | website | 5.3% | 76.3% |
+
+**Verdict: include, but fix the logo field first.**
+
+**Bench flaw this exposed.** Mobula returns `logo` = 100% on all three chains. That is not a data
+advantage, it is a URL-shape artifact: Mobula rewrites every logo onto `metadata.mobula.io` at a
+deterministic path (`/assets/logos/__`), so the field is never empty
+regardless of whether an image exists. Serialized returns the upstream source URL instead
+(`ipfs.io`, `gmgn.ai`, `axiomtrading.axiom-cdn.io`, `pbs.twimg.com`, `flap.sh`). A HEAD check on 12
+distinct Mobula logo URLs resolved 11 and 404'd 1.
+
+The bench currently measures *"is the field non-empty"*, which any provider can win by construction
+by rewriting to its own CDN. It should measure *"does the logo resolve"* (HEAD 200 with an image
+content type). Mobula is our own product and it is the beneficiary of the current rule, so this needs
+fixing on fairness grounds before a competitor is added to the same leaderboard.
+
+Excluding the logo field entirely, on the remaining three fields Serialized is level with Mobula on
+Base, ahead on BSC description and twitter, and behind on Solana and on BSC website.
+
+## 7. Benches 005 / 090 — chain-count coverage
+
+| Bench | Incumbents | Serialized |
+|---|---|---|
+| 005 asset-registry | CoinGecko 465, CoinPaprika 310, CoinStats 149, Mobula 81 | **19** |
+| 090 dex-network | GeckoTerminal 247, Codex 123, Sim by Dune 64, DexPaprika 35 | **19** |
+
+**Verdict: exclude for now.** Serialized would rank last by a wide margin on both. The metric is
+breadth, their product is deliberately narrow-and-deep. Adding them here produces a true but
+uninformative row and gives them a reason to refuse every other bench. Revisit only if they ask.
+
+Note: the GeckoTerminal count returned 100 in this run because the ad-hoc pager stopped early on
+rate limit. The production harness value of 247 is the correct one.
+
+## 8. Bench 001 — aggregator-head-lag
+
+**Replica rule.** Single process on `ocb-par-main`, two WebSocket connections open simultaneously,
+subscribed to the **same three tokens** (BONK / Solana, DEGEN / Base, CAKE / BNB). Serialized:
+`wss://api.serialized.xyz/v1/stream`, `subscribe` on channel `trades` with `{chain, address}`.
+Mobula: `wss://api.mobula.io`, `fast-trade` with `assetMode: true`. Trades matched by transaction
+hash, so every comparison is the same on-chain event seen by both pipelines. 240 s window.
+
+Note on protocol shape: Serialized's `params.pools` is a comma-separated **string**, not an array,
+and `address` is required even when `pools` is supplied. Their trade events carry the hash inside
+`data.id` as `:`, not as a `txHash` field, despite the docs naming `txHash` as the
+dedup key.
+
+### Relative arrival, the only comparison free of self-reported timestamps
+
+| Chain | matched n | p10 | p50 | p90 | Serialized first |
+|---|---|---|---|---|---|
+| solana | 74 | −288 ms | **−0 ms** | +29 ms | 51% |
+| base | 13 | −43 ms | +88 ms | +177 ms | 23% |
+| bnb | 0 | — | — | — | Mobula returned no CAKE events in this window |
+| **all** | 87 | | **+2 ms** | | **47%** |
+
+Negative means Serialized delivered the trade first. **It is a dead heat.** Across 87 matched
+trades the median difference is 2 ms and the two feeds trade the lead roughly half the time. On
+Base, Mobula was actually ahead on 77% of trades despite Serialized running a preconfirmation feed.
+
+### The finding that matters: providers disagree about when the trade happened
+
+For the **same transaction hash**, the two providers' own on-chain timestamps differ:
+
+| Chain | serialized `at` minus mobula `date` | p10 | p50 | p90 |
+|---|---|---|---|---|
+| solana | | −1,620 ms | **−707 ms** | −353 ms |
+| base | | +1,000 ms | **+1,000 ms** | +2,000 ms |
+
+Consequence, measured directly:
+
+| Chain | Provider | Self-reported lag p50 | Actually delivered first |
+|---|---|---|---|
+| solana | Mobula | +0.04 s | 49% |
+| solana | Serialized | +0.75 s | 51% |
+| base | Serialized | −0.33 s (13/13 negative) | 23% |
+| base | Mobula | +0.78 s | 77% |
+
+Read those two tables together. On Solana, Mobula's self-reported lag is 19× better than
+Serialized's, and the two arrive at the same instant. On Base, Serialized's self-reported lag is
+negative while Mobula beats it to the wire on three trades out of four. **Any head-lag number built
+on a provider's own timestamp is not a latency measurement, it is a measurement of where that
+provider chooses to put its clock.**
+
+**Correction, 2026-09-05 (v1.1).** An earlier draft of this file claimed bench 001 already
+references archive nodes and was therefore unaffected. That was wrong: it repeated the spec's
+methodology instead of reading the harness. `harnesses/aggregator-head-lag` contains no archive-node
+reference at all (`grep -rl "archive|eth_getBlockByNumber|getBlockTime|blockTimestamp"` over
+`cmd/` returns nothing). The gauge that feeds the leaderboard is computed from each provider's own
+self-reported timestamp:
+
+```go
+// head_lag_monitor.go:211 (Mobula)
+onChainTime := time.UnixMilli(trade.Date) // Mobula's own field
+totalLagMs := receiveTime.Sub(onChainTime)
+// head_lag_monitor.go:707 (Codex)
+onChainTime := time.Unix(event.Timestamp, 0) // Codex's own field
+```
+
+The published spec says otherwise in three places: `methodology[7]` ("Reference: archive nodes per
+chain, validated against block hashes"), the FAQ ("The harness holds a live WebSocket subscription
+to canonical-tip archive nodes on each chain"), and the per-chain explainers ("Measured against a
+canonical archive node"). The documentation and the code disagree, on a live bench that is publicly
+cited. That is a defect independent of Serialized and should be resolved before any provider is
+added.
+
+Second code-level issue, `head_lag_monitor.go:219`:
+
+```go
+if totalLagMs < 0 || totalLagMs > 30000 { continue }
+```
+
+Negative lags are dropped silently. Serialized's Base feed was negative on 13 of 13 sampled trades,
+so under this filter its entire Base preconfirmed population would be discarded and its Base sample
+would retain only its slowest trades. This is a measurable bias, not a policy question.
+
+Recommended resolution: make the harness hold its own node subscription per chain and timestamp each
+swap on receipt, matching by transaction hash. That is what the spec already claims, so no published
+text changes, and it makes the three providers comparable for the first time.
+
+**Blocking issue: Base preconfirmations.** Serialized emits Base trades from flashblocks
+preconfirmations, ahead of the block timestamp they attach to the event. Measured on their stream,
+Base events arrive with a **negative** lag versus their own `at` field (p50 −1.86 s, 3/3 negative in
+the first sample). Their docs state this explicitly (~2.5 s ahead).
+
+Measured on Base against Mobula on matched hashes: Serialized reports 13/13 negative self-lag while
+losing the actual race 77% of the time. So the preconfirmation feed does **not** currently translate
+into earlier delivery on Base, it only translates into an earlier timestamp. That distinction has to
+survive into whatever the bench publishes. Options, in order of preference:
+
+1. Add a `confirmation` dimension (`confirmed` / `preconfirmed`) and rank within it.
+2. Clamp negative lag to 0 and footnote it.
+3. Exclude Base for Serialized.
+
+Option 3 is the least honest, because their preconfirmed feed is a real product advantage for a
+trading UI. Option 1 is the one that survives a public dispute.
+
+## 9. Bench 067 — portfolio-chain-coverage
+
+`GET /v1/wallet/positions` returned `200` on all 19 chains with zero errors. Rows came back on 6
+chains (ethereum 238, bsc 121, hyperevm 23, solana 13, arbitrum 12, avalanche 9) and 0 rows on the
+other 13.
+
+**This test is inconclusive and must not be quoted.** The zero-row chains reflect probe addresses
+that hold nothing there, not unsupported chains. Bench 067 compares self-declared coverage against
+probe-verified coverage, which requires a curated funded address per chain. That curation is the
+work item; the endpoint itself is ready.
+
+## 10. Bench 102 / 033 — not applicable
+
+Serialized is not a swap router and returns no quotes. `token-quote-coverage` (102) and
+`evm-quote-latency` (033) cannot include them. Their `/v1/pulse` endpoint tracks ~90 launchpads and
+is usable as an **alternative discovery source** for bench 102, which is a separate question.
+
+## 11. Latency, head to head
+
+REST, identical call shape, 20 samples each, from `ocb-par-main`:
+
+| Chain | Serialized p50 / p90 | Mobula p50 / p90 |
+|---|---|---|
+| base | 72 ms / 180 ms | 74 ms / 364 ms |
+| solana | 50 ms / 58 ms | 39 ms / 180 ms |
+| ethereum | 43 ms / 57 ms | 40 ms / 186 ms |
+
+Median is a tie. The tail is not: Serialized's p90 is 2× to 3× tighter on every chain. That
+consistency is the more defensible claim, and it is not currently measured by any bench.
+
+## 12. Stretch tests
+
+| Test | Result |
+|---|---|
+| Burst threshold | Hard cap at exactly 40 concurrent. 20 and 40 pass clean; 60/100/150 return 40× `200` and the remainder `429` with an explicit `Burst limit: max 40 requests per second` message |
+| Sustained 60 s @ 15 rps | 891/891 `200`, p50 38 ms, p99 67 ms, zero degradation |
+| `POST /v1/token` batch | 25 items → `200`, 526 ms, 25 rows |
+| `POST /v1/token/price` batch | 100 items → `200`, 38 ms, 100 rows |
+| Batch over cap | 200 items → `400 INVALID_PARAM`, "must NOT have more than 100 items". Enforced, not silently truncated |
+| OHLCV page cap | `limit` ≤ 500, enforced with a clear `400` |
+| OHLCV history depth | 1s → 0.01 d, 1m → 0.35 d, 5m → 1.73 d, 1h → 20.8 d, 1d → 499 d (back to 2025-04-24), 1w → 973 d (back to 2024-01-04) |
+| Trades pagination | 10 cursor pages, 1,000 trades in 6.4 s, no gaps or repeats |
+| Error contract | `INVALID_CHAIN`, `INVALID_PARAM`, `NOT_FOUND`, `UNAUTHORIZED`, `RATE_LIMITED` all machine-readable and correct for the case |
+
+## 13. Defects found
+
+| # | Endpoint | Symptom |
+|---|---|---|
+| 1 | `GET /v1/wallet/equity/history` | `503 UPSTREAM_ERROR` after a 10 s hang, reproduced twice |
+| 2 | `GET /v1/wallet/transfers` | 6.5 s response on a routine call. Not benchmarkable as-is |
+| 3 | `GET /v1/token/trades`, `/stats`, `/dev-tokens` | `404` for the native wrapped mint (`So111…112`). Native is treated as a quote asset, never as a token. Any harness iterating a standard basket will hit this |
+| 4 | Parameter naming | Three conventions on one API: `/v1/pulse` takes `chains` (plural), `/v1/wallet/*` takes `wallet`, `/v1/wallet/profile` takes `address` |
+
+## 14. Third-party sourcing
+
+Worth knowing before any commercial discussion, neutral observation either way:
+
+- Their token `iconUrl` values are upstream URLs from `cdn.dexscreener.com`, `ipfs.io`,
+ `raw.githubusercontent.com`, `arweave.net`, `gmgn.ai`, `axiomtrading.axiom-cdn.io`.
+- Their wallet-profile entity avatars are served from `metadata.mobula.io`, our own CDN.
+
+## 15. Recommended sequence
+
+1. Fix the bench 004 logo rule (resolve-check instead of presence-check) and the bench 008 hit rule
+ (score against the curated hint). Both are provider-neutral fairness fixes and both should land
+ before a new entrant appears on those leaderboards.
+2. Onboard Serialized to bench 004 and bench 008. Both are 1:1 endpoint mappings.
+3. Bench 001: they are level with Mobula on wall-clock delivery (p50 +2 ms over 87 matched trades),
+ so they belong on the leaderboard. Decide the Base preconfirmation policy first, and keep the
+ archive-node reference: this audit showed self-reported timestamps disagree by up to 1.6 s on the
+ same transaction.
+4. Curate funded probe addresses per chain for bench 067, then onboard.
+5. Leave 005 and 090 alone unless they ask.
+6. Consider a new token-security bench, where their `/v1/token/security` (18 fields) and
+ `/v1/audit/contract` are a genuine differentiator rather than a last-place row.
+
+Every onboarding needs a `docker build --no-cache` of the materialize-worker on `ocb-par-main`
+after the harness change, or the new provider will not appear.
+
+
+## 16. Follow-up tests, 2026-09-05
+
+### 16.1 Bench 067, now conclusive
+
+The earlier §9 result was inconclusive because it used the wrong probe address. The harness already
+pins canonical ones in `registry.go`: EVM `0xF977...aceC` (Binance 8), Solana `9WzDX...WWM`, with a
+$1 USD floor. Re-run verbatim against those:
+
+| Metric | Serialized |
+|---|---|
+| listed (`/v1/meta/chains`) | 19 |
+| verified (returned a > $1 balance) | **5** |
+| errors | 0 |
+| total probe latency, 19 calls | 1,696 ms |
+
+Verified: ethereum (177 positions, $72.7M), bsc (93, $43.9M), base (27, $5.1M), arbitrum (12,
+$3.5M), solana (1, $12). The other 14 chains returned zero rows because Binance 8 holds nothing
+there, which is the harness's own "untestable residue" (`listed - probed`), not an indexer failure.
+
+Published leaderboard: CoinStats 127, Mobula 50, Zerion 42, Moralis 15. Serialized would rank
+last at 5. Verdict: addable and now measurable, but it is a third breadth metric and a third last
+place. Their `verified / probed` ratio is 5/5, which the bench exposes as a separate series and is
+the only flattering read available.
+
+### 16.2 Negative capability probes
+
+Confirmed by request rather than by reading docs. Every path returns `404 NOT_FOUND`:
+`/v1/wallet/nfts`, `/v1/nft/collection`, `/v1/nfts`, `/v1/swap/quote`, `/v1/quote`, `/v1/route`,
+`/v1/bridge/quote`. Benches 033, 102, `nft-collection-metadata`, `bridge-fee` and
+`bridge-quote-latency` are definitively out.
+
+### 16.3 A real pricing defect: BONK is 5.2x wrong
+
+| Source | BONK price |
+|---|---|
+| Mobula | 3.3097e-06 |
+| DexScreener (Orca, $305,835 liquidity) | 3.309e-06 |
+| GeckoTerminal | 3.309731e-06 |
+| **Serialized** | **6.3314e-07** |
+
+Three independent sources agree; Serialized is low by a factor of 5.2, and reports a $55.7M market
+cap against a real ~$290M.
+
+**Correction (v1.2).** An earlier revision said the deep Orca pool was absent from their list. It is
+not: it is present, ranked second. The defect is the ranking, and it is sharper than "missing pool".
+Their `/v1/token/pools` returns 50 pools; the top 8 with liquidity converted to USD at their own
+`/v1/prices/native` SOL price of $103.85:
+
+| rank | pair | liquidity, raw | liquidity, USD | priceNative | implied USD |
+|---|---|---|---|---|---|
+| **1** | Bonk/USDC | 4,346.7 USDC | **$4,347** | 6.0867e-09 | $6.32e-07 |
+| 2 | Bonk/SOL | 728.7 SOL | **$75,679** | 3.1853e-08 | $3.31e-06 |
+| 3 | Bonk/USDC | 528.3 USDC | $528 | 3.1794e-08 | $3.30e-06 |
+| 6 | Bonk/SOL | 298.7 SOL | $31,017 | 3.1843e-08 | $3.31e-06 |
+
+Seven of eight pools agree at ~$3.31e-06. Rank 1 is the lone outlier, and `/v1/token/price` prices
+from it. Rank 1 holds the largest **raw** `liquidityNative` number (4,346.7) but the pool it beats
+holds 728.7 SOL, which is $75,679, or 17.4x deeper.
+
+The ranking compares `liquidityNative` across different quote assets without converting to USD, so a
+pool quoted in USDC outranks a deeper pool quoted in SOL purely because 4,346 > 728. Not a decimals
+bug (`decimals: 5` is correct for BONK), not a discovery gap: a unit bug in the pool ranking.
+
+This matters beyond pricing. `lpBurnedPct`, `lpLockedPct` and `lpSource` are all properties of the
+selected pool, so a wrong rank-1 selection also describes the LP safety of the wrong market. On BONK
+those fields degrade to `null` / `unknown`, but on a token where the thin pool has a burned LP and
+the real market does not, the security verdict would be wrong in the dangerous direction.
+
+Worth raising with them directly: a top-100 token mispriced 5x is a bigger problem for their
+prospects than any leaderboard position.
+
+### 16.4 Cross-API price accuracy as a new bench: not proven
+
+Two attempts, neither conclusive, recorded so nobody repeats them:
+
+1. Basket from a DexScreener search returned eight distinct addresses all symbolled "SOL", i.e.
+ impostor tokens rather than eight real assets. Result discarded.
+2. Basket from GeckoTerminal top pools (28 distinct tokens) gated on DexScreener and GeckoTerminal
+ agreeing within 200 bps. Only 3 tokens survived, because GeckoTerminal returned no price for 25
+ of them. n=3 proves nothing.
+
+The idea remains the most promising new bench for this vertical, and the BONK case shows the signal
+is real. But it cannot be built on another aggregator as reference: the reference has to be computed
+from on-chain reserves of the deepest pool over an RPC we control, which is the actual work and the
+actual reason the bench would be defensible.
+
+
+## 17. Token-security bench: feasibility testing
+
+### 17.1 The cohort exists
+
+Six providers expose a live security endpoint, four of them keyless: Serialized, Mobula
+(`/api/2/token/security`), GoPlus (EVM and Solana), RugCheck (Solana), Honeypot.is. A leaderboard
+has enough rows on day one.
+
+### 17.2 Latency, ready to publish as-is (20 tokens, 4 chains)
+
+| Provider | p50 | p90 | max |
+|---|---|---|---|
+| RugCheck | 91 ms | 217 ms | 217 ms |
+| GoPlus | 249 ms | 376 ms | 597 ms |
+| Serialized | 306 ms | 847 ms | 2,416 ms |
+| **Mobula** | **5,240 ms** | 5,691 ms | **30,041 ms** |
+
+Mobula is 17x slower than Serialized and 57x slower than RugCheck. That is our own product at the
+bottom of a leaderboard we would be publishing, and it should be weighed before shipping this axis.
+
+### 17.3 Three design traps, all measured
+
+1. **Tax fields carry no signal.** Agreement on buy/sell tax: Serialized vs GoPlus 100% (n=22),
+ Serialized vs Mobula 90% (n=30), Mobula vs GoPlus 91% (n=22). A bench scoring tax accuracy is a
+ four-way tie.
+2. **LP fields are not comparable across vendors.** `serialized.lpBurnedPct` is LP burned,
+ `mobula.burnedHoldingsPercentage` is *token* burned, `rugcheck.lpLockedPct` is LP *locked*.
+ Three different quantities under similar names. Putting them in one column manufactures a false
+ ranking. The harness must define canonical fields and map each vendor explicitly.
+3. **Coverage alone is gameable.** A provider that always returns a number wins. Null rates measured
+ (share of nulls): Serialized top10 0%, taxes 25%, honeypot 100% (no such field);
+ Mobula taxes 5%, honeypot 30%, top10 100%; GoPlus taxes 40-50%; RugCheck LP 0%, rest 100%.
+
+### 17.4 Retrospective backtest: does the signal exist?
+
+Cohort of 128 tokens taken from `/v1/pulse?view=graduated` on Solana, Base and BNB. Cohort is defined
+by an event (graduation), not by survival, so no survivorship bias in selection. Split by current
+liquidity: 77 below $5k ("dead"), 19 above $50k ("alive"), 32 in between discarded.
+
+Median values, dead vs alive: `top10HoldersPct` 20.3 vs 10.0, `bundlersHoldingsPct` 51.6 vs 19.5,
+`holdersCount` 24 vs 239, `devHoldingsPct` 0.0 vs 79.3, `snipersHoldingsPct` 0.01 vs 98.4.
+
+**Age confound ruled out**: median age 0.8 h (dead) vs 1.0 h (alive), ratio 0.86.
+
+**But the test does not answer the prospective question.** Both cohorts are roughly one hour old, so
+"dead" means "never grew past $5k in its first hour", not "rugged over seven days". A retrospective
+query cannot substitute for snapshotting a verdict at mint and resolving the outcome later.
+
+### 17.5 Which fields are worth snapshotting (n=70)
+
+| Field | Distinct values | Read |
+|---|---|---|
+| `top10HoldersPct` | 68 / 70 (97%) | Genuine continuous measurement. Use it. |
+| `bundlersHoldingsPct` | 46 / 70 (66%) | Bimodal: 13 tokens at exactly 100.0, 13 at exactly 0.0. The 100.0 cluster tracked the dead group. Strongest candidate signal. |
+| `snipersHoldingsPct` | 45 / 70 (64%) | 26 zeros. Middling. |
+| `devHoldingsPct` | 21 / 70 (30%) | 42 zeros plus a 79.31 cluster appearing 7 times, a launchpad template signature. Low discriminative power on graduated tokens. |
+
+### 17.6 Cost constraint
+
+`token/security` costs 10 credits, `audit/contract` costs 750. At 200 fresh tokens a day the security
+endpoint costs ~60k credits a month, inside our 1M allowance. The audit endpoint is not benchmarkable
+at any useful cadence and must be excluded from the design and said so in the methodology.
+
+### 17.7 Recommended shape
+
+Layer 1, ship first: latency. No ground truth needed, 57x spread already measured.
+Layer 2: coverage against canonical fields we define, with the per-vendor mapping documented.
+Layer 3, the real bench: snapshot every provider's verdict at mint, resolve on-chain at T+7d
+(liquidity below 5% of peak, or LP pulled), publish recall and false-positive rate per provider.
+Snapshot `top10HoldersPct` and `bundlersHoldingsPct` as the primary signals per §17.5.
+
+
+## 18. Bench 001: Serialized wired, then pulled (v1.4, 2026-09-08)
+
+Earlier sections called 001 "blocked on a policy decision". That was the wrong framing, and it hid a
+practical question nobody had tested: does Serialized's stream cover the four bench pools at all?
+
+Their trades stream is keyed by **token** with an optional `pools` filter, while the bench is keyed
+by **pool**. Subscribing by the pool's native side (SOL, WETH, WBNB) acknowledges and delivers
+nothing, consistent with their REST 404 on `So111...112`: the chain native is a quote asset to them,
+never a token. Their own `GET /v1/pool` names the other side under `token` (USDC on Solana and Base,
+BUSD on BNB, USDG on Robinhood). Subscribing by that address with `pools=` delivers the
+tape for exactly that market.
+
+| Chain | pool | subscribe by | events with `txHash` |
+|---|---|---|---|
+| solana | 7qbRF6... | USDC `EPjF...` | 4 in 75 s (1,694 token-wide) |
+| base | 0xd0b5... | USDC `0x8335...` | 10 in 90 s (173 token-wide) |
+| bnb | 0x58f8... | BUSD `0xe9e7...` | 2 in 90 s |
+| robinhood | 0x69bf... | USDG `0x5fc5...` | 79 in 90 s (3,888 token-wide) |
+
+One constraint from their official docs shaped the implementation: **5 concurrent connections per
+key**. The harness runs in three regions off one key, so the monitor opens one connection per
+process and multiplexes the four pools as subscriptions. A first test that opened eight connections
+was refused with close code 1008 ("connection limit (5 per key)"), which is also why an earlier
+Solana attempt looked like a failure.
+
+`harnesses/aggregator-head-lag/cmd/script/serialized_head_lag_monitor.go` records both series:
+`head_lag_seconds` from their own `at` (same treatment as Mobula and Codex, same negative filter), and
+`head_lag_ref_seconds` against the node reference clock matched by `txHash`, which is the one that can
+rank providers. Every event carries `txHash`, `block`, `poolAddress` and a `preconfirmed` boolean per
+their docs, so Base flashblocks are visible rather than inferred.
+
+### 18.1 Why it was reverted
+
+Measured 2026-09-08 before promoting the wiring to `main`. One WS connection, the four bench pools,
+a 240 s capture, plus a targeted 150 s run that asked a public Base node whether the block existed
+at the moment each trade arrived. Single vantage (workstation, NTP offset +0.078 s), so absolute
+latencies are not comparable to harness numbers; the signs and the orders of magnitude are.
+
+**The ruler matches.** Their `at` is the block timestamp exactly, so `head_lag_seconds` would measure
+the same quantity as Mobula's `trade.Date` and Codex's `event.Timestamp`:
+
+| chain | n | median(`at` − block timestamp) |
+|---|---|---|
+| base | 105 | +0.000 s |
+| bnb | 1 | +0.000 s |
+| solana | 28 | +0.473 s (artefact: `getBlockTime` returns whole seconds) |
+
+**But they read a different thing.** On Base, every sampled trade arrived before its own block:
+
+| check | result |
+|---|---|
+| median(receipt − block timestamp), base | −0.911 s |
+| share of base events with negative lag | 105/105 (100%) |
+| trades received before the block existed on a public node | 11/11 |
+| median lead over block publication | 1.04 s |
+
+In all eleven cases the node head at receipt was exactly `target block − 1`: the block was not late in
+propagation, it did not exist yet. The spread (−0.21 s to −1.80 s) is the width of one Base slot.
+Serialized streams sequencer preconfirmations; Mobula, Codex and GeckoTerminal read sealed blocks.
+
+**Decision: excluded from bench 001.** Not because the measurement is wrong, but because the two
+emissions are different products. A preconfirmation carries no finality guarantee and can be
+reordered, so the ~1.5 s lead is a latency/finality trade-off, not pure speed. Publishing both in one
+ranking would present that trade-off as superiority. `RecordHeadLag` also drops negatives outright
+(`metrics.go`), so on the legacy series they would show as no data on Base while being the fastest —
+and any rare positive sample would stick on the gauge and become their published p50.
+
+Two findings worth raising with them:
+
+- `preconfirmed` was `false` on all eleven events that preceded block publication. A consumer cannot
+ tell the two regimes apart from the payload.
+- Robinhood delivered **0 events in 240 s** despite an acknowledged subscription, against 79 in 90 s
+ recorded on 2026-09-06 (§18 table). Re-verify before any future wiring.
+
+**Reopen if** the bench gains an emission-regime dimension (sealed vs preconfirmed), or a landing-rate
+companion series shows their preconfirmations reach sealed blocks 1:1. Serialized stays on benches
+004, 005, 008 and 090, where the comparison is like for like.
diff --git a/harnesses/aggregator-head-lag/cmd/script/head_lag_monitor.go b/harnesses/aggregator-head-lag/cmd/script/head_lag_monitor.go
index a912795ee..31cbb69f3 100644
--- a/harnesses/aggregator-head-lag/cmd/script/head_lag_monitor.go
+++ b/harnesses/aggregator-head-lag/cmd/script/head_lag_monitor.go
@@ -214,6 +214,18 @@ func connectAndMonitorMobula(config *Config, stopChan <-chan struct{}) error {
// Total lag: on-chain → WebSocket receipt
totalLagMs := receiveTime.Sub(onChainTime).Milliseconds()
+ // Reference lag: same trade, but timed against the node
+ // subscription we hold ourselves rather than against the
+ // timestamp Mobula sent us. Recorded before the legacy
+ // filter below so a preconfirmed emission is counted rather
+ // than dropped. See reference_monitor.go.
+ refChainName := getChainNameFromBlockchain(trade.Blockchain)
+ if refAt, ok := reference.lookup(refChainName, trade.Hash); ok {
+ RecordHeadLagRef("mobula", refChainName, receiveTime.Sub(refAt).Seconds(), config.MonitorRegion)
+ } else {
+ RecordHeadLagRefMiss("mobula", refChainName, config.MonitorRegion)
+ }
+
// Drop WebSocket replays / clock-skew events: not real indexation latency
// (Mobula WS occasionally replays old trades on reconnect; those would otherwise fire alerts)
if totalLagMs < 0 || totalLagMs > 30000 {
@@ -711,6 +723,14 @@ func connectAndMonitorCodex(config *Config, stopChan <-chan struct{}) error {
// Get chain name
chainName := getChainNameFromNetworkID(networkID)
+ // Reference lag against our own node subscription, matched
+ // by transaction hash. See reference_monitor.go.
+ if refAt, ok := reference.lookup(chainName, event.TransactionHash); ok {
+ RecordHeadLagRef("codex", chainName, receiveTime.Sub(refAt).Seconds(), config.MonitorRegion)
+ } else {
+ RecordHeadLagRefMiss("codex", chainName, config.MonitorRegion)
+ }
+
lastEventMu.Lock()
lastEventByChain[chainName] = time.Now()
lastEventMu.Unlock()
@@ -776,6 +796,11 @@ func runHeadLagMonitor(config *Config, stopChan <-chan struct{}) {
// Start Mobula fast-trade monitor
wg.Add(1)
+ // The reference clock must be up before the provider monitors, so the
+ // first emissions have something to match against. It is never fatal:
+ // a chain with no endpoint simply leaves the ref series empty.
+ runReferenceMonitor(stopChan)
+
go runMobulaHeadLagMonitor(config, stopChan, &wg)
// Start Codex monitor
diff --git a/harnesses/aggregator-head-lag/cmd/script/metrics.go b/harnesses/aggregator-head-lag/cmd/script/metrics.go
index 55b14b407..3b6c7cc2e 100644
--- a/harnesses/aggregator-head-lag/cmd/script/metrics.go
+++ b/harnesses/aggregator-head-lag/cmd/script/metrics.go
@@ -2,10 +2,10 @@ package main
import (
"fmt"
- "net/http"
- "sync"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promhttp"
+ "net/http"
+ "sync"
)
var (
@@ -29,11 +29,14 @@ var (
metadataAPILatency *prometheus.HistogramVec
// Head lag metrics
- headLagBlocks *prometheus.GaugeVec
- headLagSeconds *prometheus.GaugeVec
- blockchainHead *prometheus.GaugeVec
- aggregatorHead *prometheus.GaugeVec
- headLagErrors *prometheus.CounterVec
+ headLagBlocks *prometheus.GaugeVec
+ headLagSeconds *prometheus.GaugeVec
+ blockchainHead *prometheus.GaugeVec
+ aggregatorHead *prometheus.GaugeVec
+ headLagErrors *prometheus.CounterVec
+ headLagRefSeconds *prometheus.GaugeVec
+ headLagRefMatches *prometheus.CounterVec
+ refClockEntries prometheus.Gauge
// Fast-trade latency (for comparison with Pulse V2)
fastTradeLatency *prometheus.GaugeVec
@@ -187,6 +190,39 @@ func init() {
)
prometheus.MustRegister(headLagSeconds)
+ // Companion to head_lag_seconds, measured against our own node
+ // subscription instead of the timestamp each provider sends us. Same
+ // labels so the two are directly comparable. See reference_monitor.go
+ // for why the legacy series cannot be trusted as an absolute number.
+ headLagRefSeconds = prometheus.NewGaugeVec(
+ prometheus.GaugeOpts{
+ Name: "head_lag_ref_seconds",
+ Help: "Indexation latency in seconds, measured from a node subscription we hold ourselves, matched by transaction hash.",
+ },
+ []string{"aggregator", "chain", "region"},
+ )
+ prometheus.MustRegister(headLagRefSeconds)
+
+ // How many provider emissions we could and could not match against the
+ // reference clock. A high miss rate means the reference subscription is
+ // lagging or disconnected and the ref series must not be trusted.
+ headLagRefMatches = prometheus.NewCounterVec(
+ prometheus.CounterOpts{
+ Name: "head_lag_ref_matches_total",
+ Help: "Provider trade emissions matched against the node reference clock, by outcome.",
+ },
+ []string{"aggregator", "chain", "region", "outcome"},
+ )
+ prometheus.MustRegister(headLagRefMatches)
+
+ refClockEntries = prometheus.NewGauge(
+ prometheus.GaugeOpts{
+ Name: "head_lag_ref_clock_entries",
+ Help: "Transactions currently held in the reference clock window.",
+ },
+ )
+ prometheus.MustRegister(refClockEntries)
+
// Blockchain head block number (source of truth)
blockchainHead = prometheus.NewGaugeVec(
prometheus.GaugeOpts{
@@ -397,6 +433,51 @@ func RecordHeadLag(aggregator string, chain string, lagBlocks int64, lagSeconds
// tx_hash is logged but not stored as a metric label to avoid cardinality explosion
}
+// RecordHeadLagRef records head lag measured against our own node
+// subscription. Only called when the trade was actually seen by the
+// reference clock; an unmatched emission is counted as a miss and
+// deliberately produces no lag value, because falling back to the
+// provider's own timestamp is the defect this series exists to remove.
+//
+// The value is SIGNED and negatives are kept. Validated end to end
+// before shipping, on trades matched by hash at a 100% match rate:
+// against public endpoints (publicnode on Base, mainnet-beta on Solana)
+// Mobula delivers the trade BEFORE our subscription sees it, p50 -1.20 s
+// on Base and -0.32 s on Solana. That is not a provider being fast
+// enough to time travel, it is the public node being slower than the
+// provider's pipeline.
+//
+// The consequence for how this series must be read: the reference node's
+// own latency sits in every sample as a roughly constant offset, so the
+// ABSOLUTE number is not a head lag. The RELATIVE comparison is sound,
+// because every provider is measured against the same clock on the same
+// transaction, which is exactly what the legacy series cannot claim
+// (measured: the legacy method is off by 1,946 ms on Base and 331 ms on
+// Solana versus this one). Point REF_WS_URL_ at a paid or
+// colocated node to collapse the offset and make the absolute number
+// meaningful too.
+func RecordHeadLagRef(aggregator, chain string, lagSeconds float64, region string) {
+ if lagSeconds > 120 || lagSeconds < -120 {
+ headLagRefMatches.WithLabelValues(aggregator, chain, region, "out_of_range").Inc()
+ return
+ }
+ outcome := "matched"
+ if lagSeconds < 0 {
+ outcome = "ahead_of_reference"
+ }
+ headLagRefMatches.WithLabelValues(aggregator, chain, region, outcome).Inc()
+ headLagRefSeconds.WithLabelValues(aggregator, chain, region).Set(lagSeconds)
+}
+
+// RecordHeadLagRefMiss counts a provider emission the reference clock
+// never saw, so the match rate is auditable from the metrics alone.
+func RecordHeadLagRefMiss(aggregator, chain, region string) {
+ headLagRefMatches.WithLabelValues(aggregator, chain, region, "unmatched").Inc()
+}
+
+// RecordRefClockSize publishes the reference window occupancy.
+func RecordRefClockSize(n int) { refClockEntries.Set(float64(n)) }
+
// RecordBlockchainHead records the current blockchain head block number
func RecordBlockchainHead(chain string, blockNumber int64, region string) {
blockchainHead.WithLabelValues(chain, region).Set(float64(blockNumber))
diff --git a/harnesses/aggregator-head-lag/cmd/script/reference_monitor.go b/harnesses/aggregator-head-lag/cmd/script/reference_monitor.go
new file mode 100644
index 000000000..979ba5f64
--- /dev/null
+++ b/harnesses/aggregator-head-lag/cmd/script/reference_monitor.go
@@ -0,0 +1,290 @@
+package main
+
+import (
+ "encoding/json"
+ "fmt"
+ "os"
+ "strings"
+ "sync"
+ "time"
+
+ "github.com/gorilla/websocket"
+)
+
+// Reference clock for bench 001.
+//
+// The published methodology says, in three places, that head lag is
+// measured against canonical-tip archive nodes ("Reference: archive nodes
+// per chain, validated against block hashes"). The harness never did that:
+// both provider paths compute `receiveTime - `. Measured consequence, on trades matched by hash: the
+// same swap carries timestamps 707 ms apart on Solana and 1,000 ms apart
+// on Base depending on which provider you ask, so the leaderboard partly
+// ranks where each vendor places its clock rather than how fast its
+// pipeline is.
+//
+// This file supplies the missing reference. One WebSocket subscription per
+// monitored pool, straight to a node, timestamping every swap the instant
+// it reaches us. Provider emissions are then matched by transaction hash
+// against that single clock, so every provider is measured with the same
+// ruler.
+//
+// It publishes a NEW series (head_lag_ref_seconds) next to the existing
+// one rather than replacing it. The old series keeps its history and the
+// leaderboard keeps working while the two are compared; switching the
+// headline is a separate, documented change.
+
+// refWSURL returns the node endpoint for a chain, env-overridable so a
+// paid endpoint can replace the public one without a rebuild.
+func refWSURL(chainName string) string {
+ env := "REF_WS_URL_" + strings.ToUpper(chainName)
+ if v := strings.TrimSpace(os.Getenv(env)); v != "" {
+ return v
+ }
+ switch chainName {
+ case "base":
+ return "wss://base-rpc.publicnode.com"
+ case "bnb":
+ return "wss://bsc-rpc.publicnode.com"
+ case "solana":
+ // Measured 2026-09-05 before shipping: publicnode acknowledges
+ // logsSubscribe and then delivers nothing (0 events in 60 s on a
+ // pool the EVM equivalents were streaming), and drpc rejects the
+ // method outright on the free plan ("method is not available on
+ // free plan", code 35). mainnet-beta answers and delivers. It is
+ // rate limited, so a paid endpoint via REF_WS_URL_SOLANA is the
+ // right long-term answer.
+ return "wss://api.mainnet-beta.solana.com"
+ default:
+ // robinhood and anything else: no public endpoint we trust.
+ // Leaving it empty disables the reference for that chain rather
+ // than silently measuring against something arbitrary.
+ return ""
+ }
+}
+
+type refEntry struct {
+ at time.Time
+}
+
+type refClock struct {
+ mu sync.RWMutex
+ seen map[string]refEntry // "chain|lowercased tx hash" -> our observation time
+}
+
+var reference = &refClock{seen: map[string]refEntry{}}
+
+const (
+ refTTL = 10 * time.Minute
+ refMaxEntries = 200000
+ refSweepPeriod = 2 * time.Minute
+)
+
+func refKey(chain, hash string) string {
+ return chain + "|" + strings.ToLower(strings.TrimSpace(hash))
+}
+
+func (r *refClock) observe(chain, hash string, at time.Time) {
+ if hash == "" {
+ return
+ }
+ r.mu.Lock()
+ // Keep the FIRST observation. A log subscription can redeliver on
+ // reconnect and a later duplicate would understate every provider's
+ // lag on that trade.
+ k := refKey(chain, hash)
+ if _, ok := r.seen[k]; !ok {
+ r.seen[k] = refEntry{at: at}
+ }
+ r.mu.Unlock()
+}
+
+// lookup returns our observation time for a trade, and whether we saw it
+// at all. A miss is a miss: the caller must skip the sample rather than
+// fall back to the provider's own timestamp, which is the exact defect
+// this file exists to remove.
+func (r *refClock) lookup(chain, hash string) (time.Time, bool) {
+ r.mu.RLock()
+ e, ok := r.seen[refKey(chain, hash)]
+ r.mu.RUnlock()
+ return e.at, ok
+}
+
+func (r *refClock) sweep() {
+ cutoff := time.Now().Add(-refTTL)
+ r.mu.Lock()
+ if len(r.seen) > refMaxEntries {
+ r.seen = map[string]refEntry{}
+ r.mu.Unlock()
+ return
+ }
+ for k, e := range r.seen {
+ if e.at.Before(cutoff) {
+ delete(r.seen, k)
+ }
+ }
+ r.mu.Unlock()
+}
+
+func (r *refClock) size() int {
+ r.mu.RLock()
+ defer r.mu.RUnlock()
+ return len(r.seen)
+}
+
+// runReferenceMonitor starts one subscription per monitored pool plus a
+// TTL sweeper. Never fatal: a chain without an endpoint, or a node that
+// refuses us, simply leaves head_lag_ref_seconds unpopulated for that
+// chain while the legacy series keeps running.
+func runReferenceMonitor(stopChan <-chan struct{}) {
+ fmt.Println("[HEAD-LAG][REF] starting node reference subscriptions")
+ go func() {
+ t := time.NewTicker(refSweepPeriod)
+ defer t.Stop()
+ for {
+ select {
+ case <-stopChan:
+ return
+ case <-t.C:
+ reference.sweep()
+ RecordRefClockSize(reference.size())
+ }
+ }
+ }()
+
+ for _, p := range headLagPools {
+ url := refWSURL(p.ChainName)
+ if url == "" {
+ fmt.Printf("[HEAD-LAG][REF][%s] no endpoint configured (set REF_WS_URL_%s), reference disabled for this chain\n",
+ p.ChainName, strings.ToUpper(p.ChainName))
+ continue
+ }
+ go refLoop(p, url, stopChan)
+ }
+}
+
+func refLoop(p HeadLagPool, url string, stopChan <-chan struct{}) {
+ backoff := 2 * time.Second
+ for {
+ select {
+ case <-stopChan:
+ return
+ default:
+ }
+ err := refConnect(p, url, stopChan)
+ if err != nil {
+ fmt.Printf("[HEAD-LAG][REF][%s] %v — reconnect in %v\n", p.ChainName, err, backoff)
+ }
+ select {
+ case <-stopChan:
+ return
+ case <-time.After(backoff):
+ }
+ if backoff < 60*time.Second {
+ backoff *= 2
+ }
+ }
+}
+
+func refConnect(p HeadLagPool, url string, stopChan <-chan struct{}) error {
+ // Deliberately NOT getProxyDialer: the reference clock must not share
+ // the scraping proxy. A saturated proxy would add its own latency to
+ // the reference and silently flatter every provider.
+ dialer := &websocket.Dialer{HandshakeTimeout: 15 * time.Second}
+ conn, _, err := dialer.Dial(url, nil)
+ if err != nil {
+ return fmt.Errorf("dial: %w", err)
+ }
+ defer conn.Close()
+
+ var sub any
+ if p.ChainName == "solana" {
+ sub = map[string]any{
+ "jsonrpc": "2.0", "id": 1, "method": "logsSubscribe",
+ "params": []any{
+ map[string]any{"mentions": []string{p.Address}},
+ map[string]any{"commitment": "confirmed"},
+ },
+ }
+ } else {
+ sub = map[string]any{
+ "jsonrpc": "2.0", "id": 1, "method": "eth_subscribe",
+ "params": []any{"logs", map[string]any{"address": p.Address}},
+ }
+ }
+ if err := conn.WriteJSON(sub); err != nil {
+ return fmt.Errorf("subscribe: %w", err)
+ }
+ fmt.Printf("[HEAD-LAG][REF][%s] subscribed to %s on %s\n", p.ChainName, p.Address, url)
+
+ go func() {
+ t := time.NewTicker(25 * time.Second)
+ defer t.Stop()
+ for {
+ select {
+ case <-stopChan:
+ return
+ case <-t.C:
+ if err := conn.WriteControl(websocket.PingMessage, nil, time.Now().Add(5*time.Second)); err != nil {
+ return
+ }
+ }
+ }
+ }()
+
+ for {
+ select {
+ case <-stopChan:
+ return nil
+ default:
+ }
+ _, msg, err := conn.ReadMessage()
+ if err != nil {
+ return fmt.Errorf("read: %w", err)
+ }
+ now := time.Now().UTC()
+
+ var env struct {
+ Method string `json:"method"`
+ Params struct {
+ Result json.RawMessage `json:"result"`
+ } `json:"params"`
+ }
+ if json.Unmarshal(msg, &env) != nil || len(env.Params.Result) == 0 {
+ continue
+ }
+
+ if p.ChainName == "solana" {
+ var r struct {
+ Value struct {
+ Signature string `json:"signature"`
+ Err any `json:"err"`
+ } `json:"value"`
+ }
+ if json.Unmarshal(env.Params.Result, &r) != nil {
+ continue
+ }
+ // Failed transactions never become a swap any provider will
+ // emit; counting them would create reference entries that are
+ // matched by nobody.
+ if r.Value.Err != nil || r.Value.Signature == "" {
+ continue
+ }
+ reference.observe(p.ChainName, r.Value.Signature, now)
+ continue
+ }
+
+ var r struct {
+ TransactionHash string `json:"transactionHash"`
+ Removed bool `json:"removed"`
+ }
+ if json.Unmarshal(env.Params.Result, &r) != nil {
+ continue
+ }
+ // A reorged-out log is not a trade.
+ if r.Removed || r.TransactionHash == "" {
+ continue
+ }
+ reference.observe(p.ChainName, r.TransactionHash, now)
+ }
+}
diff --git a/harnesses/metadata-coverage/cmd/script/config.go b/harnesses/metadata-coverage/cmd/script/config.go
index a3fd51143..57fe834e2 100644
--- a/harnesses/metadata-coverage/cmd/script/config.go
+++ b/harnesses/metadata-coverage/cmd/script/config.go
@@ -10,6 +10,7 @@ import (
type Config struct {
CoinGeckoAPIKey string
MobulaAPIKey string
+ SerializedAPIKey string
DefinedSessionCookie string
MonitorRegion string // Deployment region: us-west, us-east, singapore, etc.
MobulaWSURL string // Mobula fast-trade WebSocket endpoint (allows staging to use EU-specific cluster)
@@ -21,6 +22,7 @@ func loadEnv() (*Config, error) {
// First, try to load from environment variables (for production/Railway)
config.CoinGeckoAPIKey = strings.TrimSpace(os.Getenv("COINGECKO_API_KEY"))
config.MobulaAPIKey = strings.TrimSpace(os.Getenv("MOBULA_API_KEY"))
+ config.SerializedAPIKey = strings.TrimSpace(os.Getenv("SERIALIZED_API_KEY"))
config.DefinedSessionCookie = strings.TrimSpace(os.Getenv("DEFINED_SESSION_COOKIE"))
config.MonitorRegion = strings.TrimSpace(os.Getenv("MONITOR_REGION"))
config.MobulaWSURL = strings.TrimSpace(os.Getenv("MOBULA_WS_URL"))
diff --git a/harnesses/metadata-coverage/cmd/script/logo_resolve.go b/harnesses/metadata-coverage/cmd/script/logo_resolve.go
new file mode 100644
index 000000000..61a0065bb
--- /dev/null
+++ b/harnesses/metadata-coverage/cmd/script/logo_resolve.go
@@ -0,0 +1,150 @@
+package main
+
+import (
+ "context"
+ "net/http"
+ "strings"
+ "sync"
+ "time"
+)
+
+// The bench scores the logo field as "did the provider return a non-empty
+// string". That is not the same question as "does this token have a logo",
+// and the difference is not academic: Mobula rewrites every logo onto
+// metadata.mobula.io at a deterministic path derived from chain and
+// address, so its logo field is non-empty for every token by construction,
+// whether or not an image exists behind the URL. Providers that return the
+// upstream source URL (ipfs.io, cdn.dexscreener.com, launchpad CDNs, twimg)
+// are scored on whether the upstream actually has the asset.
+//
+// Measured when this was written: Mobula 100% logo on all three chains,
+// against 22.9% / 37.8% / 78.9% for a provider returning upstream URLs. A
+// HEAD sweep of 12 distinct Mobula logo URLs resolved 11 and 404'd 1.
+//
+// Any provider can win the current rule by rewriting to its own CDN, and
+// the current beneficiary is our own product, so this is published as a
+// SEPARATE `logo_resolved` field rather than silently redefining `logo`.
+// The existing series and its history stay intact; the stricter one builds
+// alongside until there is enough of it to move the headline in a
+// documented change.
+
+var logoHTTPClient = &http.Client{
+ Timeout: 4 * time.Second,
+ CheckRedirect: func(req *http.Request, via []*http.Request) error {
+ if len(via) >= 4 {
+ return http.ErrUseLastResponse
+ }
+ return nil
+ },
+}
+
+type logoCacheEntry struct {
+ ok bool
+ at time.Time
+}
+
+var (
+ logoCacheMu sync.Mutex
+ logoCache = map[string]logoCacheEntry{}
+)
+
+const (
+ logoCacheTTL = 6 * time.Hour
+ logoCacheMaxSize = 20000
+)
+
+// logoResolves reports whether the URL actually serves an image. Empty
+// URLs are false without a request. Results are cached because launchpad
+// and CDN URLs repeat heavily across fresh tokens and we should not hammer
+// third-party hosts from a monitor.
+func logoResolves(rawURL string) bool {
+ u := strings.TrimSpace(rawURL)
+ if u == "" {
+ return false
+ }
+ if !strings.HasPrefix(u, "http://") && !strings.HasPrefix(u, "https://") {
+ // data: URIs and relative paths are not verifiable from here.
+ // Count them as unresolved rather than silently passing.
+ return false
+ }
+
+ logoCacheMu.Lock()
+ if e, ok := logoCache[u]; ok && time.Since(e.at) < logoCacheTTL {
+ logoCacheMu.Unlock()
+ return e.ok
+ }
+ logoCacheMu.Unlock()
+
+ ok := probeLogo(u)
+
+ logoCacheMu.Lock()
+ if len(logoCache) >= logoCacheMaxSize {
+ logoCache = map[string]logoCacheEntry{}
+ }
+ logoCache[u] = logoCacheEntry{ok: ok, at: time.Now()}
+ logoCacheMu.Unlock()
+ return ok
+}
+
+func probeLogo(u string) bool {
+ ctx, cancel := context.WithTimeout(context.Background(), 4*time.Second)
+ defer cancel()
+
+ req, err := http.NewRequestWithContext(ctx, http.MethodHead, u, nil)
+ if err != nil {
+ return false
+ }
+ req.Header.Set("User-Agent", "OpenChainBench-logo-probe/1.0 (+https://openchainbench.com)")
+ resp, err := logoHTTPClient.Do(req)
+ if err == nil {
+ defer resp.Body.Close()
+ if resp.StatusCode >= 200 && resp.StatusCode < 300 {
+ return imageish(resp.Header.Get("Content-Type"))
+ }
+ // A number of CDNs reject HEAD with 403/405 while serving GET
+ // fine. Retry those with a 1-byte ranged GET rather than
+ // recording a false negative.
+ if resp.StatusCode != http.StatusMethodNotAllowed && resp.StatusCode != http.StatusForbidden {
+ return false
+ }
+ }
+
+ ctx2, cancel2 := context.WithTimeout(context.Background(), 4*time.Second)
+ defer cancel2()
+ req2, err := http.NewRequestWithContext(ctx2, http.MethodGet, u, nil)
+ if err != nil {
+ return false
+ }
+ req2.Header.Set("User-Agent", "OpenChainBench-logo-probe/1.0 (+https://openchainbench.com)")
+ req2.Header.Set("Range", "bytes=0-0")
+ resp2, err := logoHTTPClient.Do(req2)
+ if err != nil {
+ return false
+ }
+ defer resp2.Body.Close()
+ if resp2.StatusCode >= 200 && resp2.StatusCode < 300 {
+ return imageish(resp2.Header.Get("Content-Type"))
+ }
+ return false
+}
+
+// imageish accepts anything that plausibly renders in an . An empty
+// Content-Type is accepted because several IPFS gateways omit it on
+// ranged responses; a hard reject there would penalise providers that
+// return honest upstream URLs, which is the opposite of the point.
+func imageish(ct string) bool {
+ c := strings.ToLower(strings.TrimSpace(ct))
+ if c == "" {
+ return true
+ }
+ if i := strings.IndexByte(c, ';'); i >= 0 {
+ c = strings.TrimSpace(c[:i])
+ }
+ switch {
+ case strings.HasPrefix(c, "image/"):
+ return true
+ case c == "binary/octet-stream", c == "application/octet-stream":
+ return true
+ }
+ return false
+}
diff --git a/harnesses/metadata-coverage/cmd/script/metadata_coverage_monitor.go b/harnesses/metadata-coverage/cmd/script/metadata_coverage_monitor.go
index 106898ecd..d17018169 100644
--- a/harnesses/metadata-coverage/cmd/script/metadata_coverage_monitor.go
+++ b/harnesses/metadata-coverage/cmd/script/metadata_coverage_monitor.go
@@ -65,18 +65,20 @@ type ProviderCoverage struct {
// MetadataCoverageStats holds overall stats
type MetadataCoverageStats struct {
- mu sync.Mutex
- Mobula ProviderCoverage
- Codex ProviderCoverage
- Jupiter ProviderCoverage
- LastPrint time.Time
+ mu sync.Mutex
+ Mobula ProviderCoverage
+ Codex ProviderCoverage
+ Jupiter ProviderCoverage
+ Serialized ProviderCoverage
+ LastPrint time.Time
}
var (
coverageStats = &MetadataCoverageStats{
- Mobula: ProviderCoverage{Provider: "mobula"},
- Codex: ProviderCoverage{Provider: "codex"},
- Jupiter: ProviderCoverage{Provider: "jupiter"},
+ Mobula: ProviderCoverage{Provider: "mobula"},
+ Codex: ProviderCoverage{Provider: "codex"},
+ Jupiter: ProviderCoverage{Provider: "jupiter"},
+ Serialized: ProviderCoverage{Provider: "serialized"},
}
tokenQueue = make(chan TokenToCheck, 500)
metadataClient = &http.Client{Timeout: 10 * time.Second}
@@ -194,12 +196,12 @@ type CodexTokenResponse struct {
// CodexEnhancedToken matches the EnhancedToken type from Codex API
type CodexEnhancedToken struct {
- Address string `json:"address"`
- Name string `json:"name"`
- Symbol string `json:"symbol"`
- Decimals int `json:"decimals"`
- NetworkID int `json:"networkId"`
- Info *CodexTokenInfo `json:"info"`
+ Address string `json:"address"`
+ Name string `json:"name"`
+ Symbol string `json:"symbol"`
+ Decimals int `json:"decimals"`
+ NetworkID int `json:"networkId"`
+ Info *CodexTokenInfo `json:"info"`
SocialLinks *CodexSocialLinks `json:"socialLinks"`
}
@@ -215,11 +217,11 @@ type CodexTokenInfo struct {
// CodexSocialLinks contains social media links for the token
type CodexSocialLinks struct {
- Twitter string `json:"twitter"`
- Website string `json:"website"`
- Telegram string `json:"telegram"`
- Discord string `json:"discord"`
- Github string `json:"github"`
+ Twitter string `json:"twitter"`
+ Website string `json:"website"`
+ Telegram string `json:"telegram"`
+ Discord string `json:"discord"`
+ Github string `json:"github"`
}
func getCodexNetworkID(chainID string) int {
@@ -553,6 +555,8 @@ func updateStats(provider string, fields MetadataFields) {
stats = &coverageStats.Codex
case "jupiter":
stats = &coverageStats.Jupiter
+ case "serialized":
+ stats = &coverageStats.Serialized
default:
return
}
@@ -601,7 +605,7 @@ func printCoverageStats() {
fmt.Printf("║ Provider │ Checks │ Logo │ Name │ Symbol│ Desc │Twitter│Website│Telegram│ Errors │\n")
fmt.Printf("╠══════════════════════════════════════════════════════════════════════════════╣\n")
- for _, stats := range []*ProviderCoverage{&coverageStats.Mobula, &coverageStats.Codex, &coverageStats.Jupiter} {
+ for _, stats := range []*ProviderCoverage{&coverageStats.Mobula, &coverageStats.Codex, &coverageStats.Jupiter, &coverageStats.Serialized} {
if stats.TotalChecks == 0 {
fmt.Printf("║ %-8s │ %6d │ - │ - │ - │ - │ - │ - │ - │ %6d ║\n",
stats.Provider, stats.TotalChecks, stats.ErrorCount)
@@ -657,6 +661,7 @@ func checkTokenMetadata(token TokenToCheck, config *Config) {
// Record Prometheus metrics for Mobula
RecordMetadataCoverage("mobula", chainName, "logo", mobulaResult.HasLogo, config.MonitorRegion)
+ RecordMetadataCoverage("mobula", chainName, "logo_resolved", mobulaResult.HasLogo && logoResolves(mobulaResult.LogoURL), config.MonitorRegion)
RecordMetadataCoverage("mobula", chainName, "description", mobulaResult.HasDescription, config.MonitorRegion)
RecordMetadataCoverage("mobula", chainName, "twitter", mobulaResult.HasTwitter, config.MonitorRegion)
RecordMetadataCoverage("mobula", chainName, "website", mobulaResult.HasWebsite, config.MonitorRegion)
@@ -672,6 +677,7 @@ func checkTokenMetadata(token TokenToCheck, config *Config) {
// Record Prometheus metrics for Codex
RecordMetadataCoverage("codex", chainName, "logo", codexResult.HasLogo, config.MonitorRegion)
+ RecordMetadataCoverage("codex", chainName, "logo_resolved", codexResult.HasLogo && logoResolves(codexResult.LogoURL), config.MonitorRegion)
RecordMetadataCoverage("codex", chainName, "description", codexResult.HasDescription, config.MonitorRegion)
RecordMetadataCoverage("codex", chainName, "twitter", codexResult.HasTwitter, config.MonitorRegion)
RecordMetadataCoverage("codex", chainName, "website", codexResult.HasWebsite, config.MonitorRegion)
@@ -685,12 +691,31 @@ func checkTokenMetadata(token TokenToCheck, config *Config) {
// Record Prometheus metrics for Jupiter
RecordMetadataCoverage("jupiter", chainName, "logo", jupiterResult.HasLogo, config.MonitorRegion)
+ RecordMetadataCoverage("jupiter", chainName, "logo_resolved", jupiterResult.HasLogo && logoResolves(jupiterResult.LogoURL), config.MonitorRegion)
RecordMetadataCoverage("jupiter", chainName, "description", jupiterResult.HasDescription, config.MonitorRegion)
RecordMetadataCoverage("jupiter", chainName, "twitter", jupiterResult.HasTwitter, config.MonitorRegion)
RecordMetadataCoverage("jupiter", chainName, "website", jupiterResult.HasWebsite, config.MonitorRegion)
RecordMetadataLatency("jupiter", chainName, jupiterResult.ResponseTimeMs, config.MonitorRegion)
}
+ // Check Serialized (18 EVM chains + Solana; skipped elsewhere)
+ var serializedResult MetadataFields
+ if _, supported := serializedChainID(token.ChainID); supported {
+ serializedResult = checkSerializedMetadata(token, config.SerializedAPIKey)
+ if serializedResult.Error != "" {
+ fmt.Printf("[META][SERIALIZED][%s] %s | %s | err=%s\n",
+ chainName, token.Symbol, token.Address, serializedResult.Error)
+ }
+ updateStats("serialized", serializedResult)
+
+ RecordMetadataCoverage("serialized", chainName, "logo", serializedResult.HasLogo, config.MonitorRegion)
+ RecordMetadataCoverage("serialized", chainName, "logo_resolved", serializedResult.HasLogo && logoResolves(serializedResult.LogoURL), config.MonitorRegion)
+ RecordMetadataCoverage("serialized", chainName, "description", serializedResult.HasDescription, config.MonitorRegion)
+ RecordMetadataCoverage("serialized", chainName, "twitter", serializedResult.HasTwitter, config.MonitorRegion)
+ RecordMetadataCoverage("serialized", chainName, "website", serializedResult.HasWebsite, config.MonitorRegion)
+ RecordMetadataLatency("serialized", chainName, serializedResult.ResponseTimeMs, config.MonitorRegion)
+ }
+
// Single condensed log line
boolToIcon := func(b bool) string {
if b {
@@ -709,11 +734,17 @@ func checkTokenMetadata(token TokenToCheck, config *Config) {
// without cross-referencing logs. Address goes after symbol; 4 boolean
// columns per provider so website is visible alongside logo/desc/twitter
// (the page renders 4 fields, the prior 3-column line hid that one).
- fmt.Printf("[META] %s/%s %s | M:%s%s%s%s | C:%s%s%s%s | J:%s\n",
+ serializedCols := "----"
+ if _, supported := serializedChainID(token.ChainID); supported {
+ serializedCols = boolToIcon(serializedResult.HasLogo) + boolToIcon(serializedResult.HasDescription) +
+ boolToIcon(serializedResult.HasTwitter) + boolToIcon(serializedResult.HasWebsite)
+ }
+
+ fmt.Printf("[META] %s/%s %s | M:%s%s%s%s | C:%s%s%s%s | J:%s | S:%s\n",
token.Symbol, chainName, token.Address,
boolToIcon(mobulaResult.HasLogo), boolToIcon(mobulaResult.HasDescription), boolToIcon(mobulaResult.HasTwitter), boolToIcon(mobulaResult.HasWebsite),
boolToIcon(codexResult.HasLogo), boolToIcon(codexResult.HasDescription), boolToIcon(codexResult.HasTwitter), boolToIcon(codexResult.HasWebsite),
- jupiterLogo)
+ jupiterLogo, serializedCols)
// Print stats every 50 checks (reduced from 10)
coverageStats.mu.Lock()
@@ -808,4 +839,3 @@ func runMetadataCoverageMonitor(config *Config, stopChan <-chan struct{}) {
}
}
}
-
diff --git a/harnesses/metadata-coverage/cmd/script/serialized_rest_monitor.go b/harnesses/metadata-coverage/cmd/script/serialized_rest_monitor.go
new file mode 100644
index 000000000..812b134ea
--- /dev/null
+++ b/harnesses/metadata-coverage/cmd/script/serialized_rest_monitor.go
@@ -0,0 +1,162 @@
+package main
+
+import (
+ "encoding/json"
+ "fmt"
+ "io"
+ "net/http"
+ "net/url"
+ "strings"
+ "sync"
+ "time"
+)
+
+// ============================================================================
+// Serialized — token metadata coverage
+//
+// GET /v1/token/metadata?chain=&address= returns the four
+// canonical fields this bench scores, under different names than Mobula
+// and Codex:
+//
+// logo -> iconUrl
+// description -> description
+// twitter -> twitterUrl
+// website -> websiteUrl
+//
+// Chain ids are already in the bench's own shape ("solana", "evm:56",
+// "evm:8453"), so no translation table is needed beyond normalising the
+// legacy "solana:solana" form that Pulse V2 sometimes emits.
+//
+// One asymmetry worth knowing when reading the leaderboard: Serialized
+// returns the *upstream* icon URL (ipfs.io, cdn.dexscreener.com, twimg,
+// launchpad CDNs) while Mobula rewrites every logo onto its own CDN at a
+// deterministic path, so Mobula's logo field is non-empty by construction.
+// The bench currently scores "field non-empty", not "image resolves".
+// See docs/methodology/serialized-onboarding-audit.md §6.
+// ============================================================================
+
+const serializedTokenMetadataURL = "https://api.serialized.xyz/v1/token/metadata"
+
+// Serialized enforces a hard burst cap of 40 requests per second per key
+// and returns 429 above it. The queue-driven monitor can burst well past
+// that during a launch spike, which would show up as coverage loss rather
+// than as a rate-limit error. Pace the calls at a fixed floor instead.
+var (
+ serializedMetaMu sync.Mutex
+ serializedMetaLast time.Time
+)
+
+const serializedMetaMinInterval = 60 * time.Millisecond // ~16 rps against a 40 rps cap
+
+func serializedMetaThrottle() {
+ serializedMetaMu.Lock()
+ defer serializedMetaMu.Unlock()
+ if wait := time.Until(serializedMetaLast.Add(serializedMetaMinInterval)); wait > 0 {
+ time.Sleep(wait)
+ }
+ serializedMetaLast = time.Now()
+}
+
+// serializedChainID normalises the bench's chain id to what Serialized
+// accepts. Returns false when the chain is outside their coverage, so the
+// caller skips the check instead of recording a miss.
+func serializedChainID(chainID string) (string, bool) {
+ c := chainID
+ if c == "solana:solana" {
+ c = "solana"
+ }
+ if c == "solana" {
+ return c, true
+ }
+ if !strings.HasPrefix(c, "evm:") {
+ return "", false
+ }
+ // 18 EVM chains, live as of onboarding (2026-09-05).
+ switch c {
+ case "evm:1", "evm:56", "evm:130", "evm:143", "evm:196", "evm:988",
+ "evm:1514", "evm:2741", "evm:4217", "evm:4326", "evm:4663",
+ "evm:5042", "evm:8453", "evm:9745", "evm:42161", "evm:43114",
+ "evm:57073", "evm:645749":
+ return c, true
+ }
+ return "", false
+}
+
+type SerializedTokenMetadataResponse struct {
+ Data struct {
+ Name string `json:"name"`
+ Symbol string `json:"symbol"`
+ IconURL string `json:"iconUrl"`
+ Description string `json:"description"`
+ TwitterURL string `json:"twitterUrl"`
+ WebsiteURL string `json:"websiteUrl"`
+ TelegramURL string `json:"telegramUrl"`
+ } `json:"data"`
+}
+
+func checkSerializedMetadata(token TokenToCheck, apiKey string) MetadataFields {
+ result := MetadataFields{}
+
+ chain, ok := serializedChainID(token.ChainID)
+ if !ok {
+ result.Error = "chain_unsupported"
+ return result
+ }
+ if apiKey == "" {
+ result.Error = "no_api_key"
+ return result
+ }
+
+ serializedMetaThrottle()
+
+ params := url.Values{}
+ params.Add("chain", chain)
+ params.Add("address", token.Address)
+
+ req, err := http.NewRequest("GET", fmt.Sprintf("%s?%s", serializedTokenMetadataURL, params.Encode()), nil)
+ if err != nil {
+ result.Error = fmt.Sprintf("request_create_error: %v", err)
+ return result
+ }
+ // Raw key, no Bearer prefix — a prefixed key is rejected with 401.
+ req.Header.Set("Authorization", apiKey)
+ req.Header.Set("Accept", "application/json")
+
+ startTime := time.Now()
+ resp, err := metadataClient.Do(req)
+ result.ResponseTimeMs = float64(time.Since(startTime).Milliseconds())
+ if err != nil {
+ result.Error = fmt.Sprintf("request_error: %v", err)
+ return result
+ }
+ defer resp.Body.Close()
+
+ if resp.StatusCode != 200 {
+ result.Error = fmt.Sprintf("status_%d", resp.StatusCode)
+ return result
+ }
+
+ body, err := io.ReadAll(resp.Body)
+ if err != nil {
+ result.Error = fmt.Sprintf("read_error: %v", err)
+ return result
+ }
+
+ var response SerializedTokenMetadataResponse
+ if err := json.Unmarshal(body, &response); err != nil {
+ result.Error = fmt.Sprintf("parse_error: %v", err)
+ return result
+ }
+
+ d := response.Data
+ result.HasName = d.Name != ""
+ result.HasSymbol = d.Symbol != ""
+ result.HasLogo = d.IconURL != ""
+ result.LogoURL = d.IconURL
+ result.HasDescription = d.Description != ""
+ result.HasTwitter = d.TwitterURL != ""
+ result.HasWebsite = d.WebsiteURL != ""
+ result.HasTelegram = d.TelegramURL != ""
+
+ return result
+}
diff --git a/harnesses/network-coverage/cmd/script/config.go b/harnesses/network-coverage/cmd/script/config.go
index 83d27ab8d..99c7310f2 100644
--- a/harnesses/network-coverage/cmd/script/config.go
+++ b/harnesses/network-coverage/cmd/script/config.go
@@ -13,6 +13,7 @@ type Config struct {
CodexSessionCookie string // fallback path: mint JWT from Defined.fi cookie
DefinedTokenURL string // optional: pre-minted JWT sidecar
CoinStatsAPIKey string
+ SerializedAPIKey string
SimDuneAPIKey string // optional — Sim's public endpoint works keyless, but a key avoids rate limits
HTTPProxy string
RefreshInterval time.Duration
@@ -26,6 +27,7 @@ func loadConfig() *Config {
CodexSessionCookie: os.Getenv("DEFINED_SESSION_COOKIE"),
DefinedTokenURL: os.Getenv("DEFINED_TOKEN_SERVICE_URL"),
CoinStatsAPIKey: os.Getenv("COINSTATS_API_KEY"),
+ SerializedAPIKey: os.Getenv("SERIALIZED_API_KEY"),
SimDuneAPIKey: os.Getenv("SIM_DUNE_API_KEY"),
HTTPProxy: os.Getenv("HTTP_PROXY"),
RefreshInterval: 6 * time.Hour,
@@ -47,8 +49,8 @@ func loadConfig() *Config {
} else if c.CodexSessionCookie != "" {
codexAuth = "cookie+mint"
}
- fmt.Printf("Config: refresh=%v, testnets=%v, mobula_key=%v, codex=%s, coinstats_key=%v, sim_dune_key=%v\n",
+ fmt.Printf("Config: refresh=%v, testnets=%v, mobula_key=%v, codex=%s, coinstats_key=%v, sim_dune_key=%v, serialized_key=%v\n",
c.RefreshInterval, c.IncludeTestnets, c.MobulaAPIKey != "", codexAuth,
- c.CoinStatsAPIKey != "", c.SimDuneAPIKey != "")
+ c.CoinStatsAPIKey != "", c.SimDuneAPIKey != "", c.SerializedAPIKey != "")
return c
}
diff --git a/harnesses/network-coverage/cmd/script/main.go b/harnesses/network-coverage/cmd/script/main.go
index 885a9ffad..20693522d 100644
--- a/harnesses/network-coverage/cmd/script/main.go
+++ b/harnesses/network-coverage/cmd/script/main.go
@@ -76,6 +76,7 @@ func fetchAll(cfg *Config) {
{"coinstats", fetchCoinStats},
{"coingecko", fetchCoinGecko},
{"dexpaprika", fetchDexPaprika},
+ {"serialized", fetchSerialized},
}
var wg sync.WaitGroup
diff --git a/harnesses/network-coverage/cmd/script/serialized.go b/harnesses/network-coverage/cmd/script/serialized.go
new file mode 100644
index 000000000..9ad325b64
--- /dev/null
+++ b/harnesses/network-coverage/cmd/script/serialized.go
@@ -0,0 +1,80 @@
+package main
+
+import (
+ "encoding/json"
+ "fmt"
+ "io"
+ "net/http"
+ "time"
+)
+
+// Serialized publishes its chain list at GET /v1/meta/chains. The endpoint
+// is free (0 credits) and returns one row per chain with a `status` field.
+//
+// The same list answers both benches this harness feeds: Serialized runs its
+// own indexers and does not separate "chains where we know tokens" from
+// "chains where we index DEX pools" — every listed chain carries both. So
+// the count is identical on bench 005 and bench 090 by construction, which
+// is worth knowing when reading the two leaderboards side by side.
+const serializedChainsURL = "https://api.serialized.xyz/v1/meta/chains"
+
+type serializedChain struct {
+ Chain string `json:"chain"` // "evm:8453" or "solana"
+ Name string `json:"name"`
+ Slug string `json:"slug"`
+ Family string `json:"family"`
+ Status string `json:"status"`
+}
+
+type serializedChainsResponse struct {
+ Data []serializedChain `json:"data"`
+}
+
+func fetchSerialized(cfg *Config) ProviderResult {
+ res := ProviderResult{Provider: "serialized"}
+ if cfg.SerializedAPIKey == "" {
+ res.Err = "missing_api_key"
+ return res
+ }
+
+ client := &http.Client{Timeout: 15 * time.Second}
+ req, _ := http.NewRequest("GET", serializedChainsURL, nil)
+ // Raw key, no Bearer prefix — a prefixed key is rejected with 401.
+ req.Header.Set("Authorization", cfg.SerializedAPIKey)
+ req.Header.Set("Accept", "application/json")
+
+ resp, err := client.Do(req)
+ if err != nil {
+ res.Err = fmt.Sprintf("request_error: %v", err)
+ return res
+ }
+ defer resp.Body.Close()
+ body, _ := io.ReadAll(resp.Body)
+
+ if resp.StatusCode != 200 {
+ res.Err = fmt.Sprintf("status_%d", resp.StatusCode)
+ return res
+ }
+
+ var parsed serializedChainsResponse
+ if err := json.Unmarshal(body, &parsed); err != nil {
+ res.Err = fmt.Sprintf("parse_error: %v", err)
+ return res
+ }
+
+ for _, c := range parsed.Data {
+ // Only chains the provider declares live. Everything on this
+ // endpoint is mainnet, so no testnet filter is needed, but a
+ // future "beta"/"deprecated" status must not inflate the count.
+ if c.Status != "live" {
+ continue
+ }
+ res.Networks = append(res.Networks, Network{
+ ChainID: c.Chain,
+ Slug: c.Slug,
+ Name: c.Name,
+ })
+ }
+
+ return res
+}
diff --git a/harnesses/rpc-capabilities/cmd/script/config.go b/harnesses/rpc-capabilities/cmd/script/config.go
index b61dc0d04..3882ee45c 100644
--- a/harnesses/rpc-capabilities/cmd/script/config.go
+++ b/harnesses/rpc-capabilities/cmd/script/config.go
@@ -237,7 +237,7 @@ func chains() []Chain {
{Slug: "tenderly", Name: "Tenderly Gateway", URL: envDefault("RPC_URL_MEGAETH_TENDERLY", "https://megaeth.gateway.tenderly.co")},
},
},
- // ─── Ethereum mainnet (8 providers) ────────────────────────
+ // ─── Ethereum mainnet (9 providers) ────────────────────────
{
Slug: "ethereum",
Name: "Ethereum",
@@ -250,6 +250,7 @@ func chains() []Chain {
{Slug: "nodies", Name: "Nodies (POKT)", URL: envDefault("RPC_URL_ETHEREUM_NODIES", "https://eth-pokt.nodies.app")},
{Slug: "lava", Name: "Lava Network", URL: envDefault("RPC_URL_ETHEREUM_LAVA", "https://eth1.lava.build")},
{Slug: "bloxroute", Name: "bloXroute", URL: envDefault("RPC_URL_ETHEREUM_BLOXROUTE", "https://eth.rpc.blxrbdn.com")},
+ {Slug: "nownodes", Name: "NOWNodes", URL: envDefault("RPC_URL_ETHEREUM_NOWNODES", "https://public-eth.nownodes.io")},
},
},
// ─── Polygon PoS (5 providers) ──────────────────────────────
@@ -304,7 +305,7 @@ func chains() []Chain {
{Slug: "bloxroute", Name: "bloXroute", URL: envDefault("RPC_URL_BASE_BLOXROUTE", "https://base.rpc.blxrbdn.com")},
},
},
- // ─── BNB Chain (5 providers) ────────────────────────────────
+ // ─── BNB Chain (7 providers) ────────────────────────────────
{
Slug: "bnb",
Name: "BNB Chain",
@@ -315,6 +316,7 @@ func chains() []Chain {
{Slug: "binance", Name: "Binance Official", URL: envDefault("RPC_URL_BNB_OFFICIAL", "https://bsc-dataseed1.binance.org")},
{Slug: "blastapi", Name: "Blast API", URL: envDefault("RPC_URL_BNB_BLASTAPI", "https://bsc-mainnet.public.blastapi.io")},
{Slug: "bloxroute", Name: "bloXroute", URL: envDefault("RPC_URL_BNB_BLOXROUTE", "https://bsc.rpc.blxrbdn.com")},
+ {Slug: "nownodes", Name: "NOWNodes", URL: envDefault("RPC_URL_BNB_NOWNODES", "https://public-bsc.nownodes.io")},
},
},
// ─── Avalanche C-Chain (6 providers) ────────────────────────
@@ -1258,6 +1260,7 @@ func chains() []Chain {
{Slug: "polkachu", Name: "Polkachu", URL: envDefault("RPC_URL_COREUM_POLKACHU", "https://coreum-rpc.polkachu.com")},
{Slug: "publicnode", Name: "PublicNode", URL: envDefault("RPC_URL_COREUM_PUBLICNODE", "https://coreum-rpc.publicnode.com")},
{Slug: "coreum-official", Name: "Coreum Foundation", URL: envDefault("RPC_URL_COREUM_OFFICIAL", "https://full-node.mainnet-1.coreum.dev:26657")},
+ {Slug: "nownodes", Name: "NOWNodes", URL: envDefault("RPC_URL_COREUM_NOWNODES", "https://public-coreum.nownodes.io")},
},
},
// Nolus — DeFi lease protocol (pirin-1). 2 keyless providers.
@@ -1629,6 +1632,27 @@ func chains() []Chain {
{Slug: "ngd2", Name: "NGD (2)", URL: envDefault("RPC_URL_NEO_NGD2", "https://n3seed2.ngd.network:10332")},
},
},
+ // 2026-09-09 — Bitcoin. Bitcoin Core JSON-RPC getblockcount (no key). ~10min/block.
+ {
+ Slug: "bitcoin",
+ Name: "Bitcoin",
+ Kind: "bitcoin",
+ Providers: []Provider{
+ {Slug: "publicnode", Name: "PublicNode", URL: envDefault("RPC_URL_BTC_PUBLICNODE", "https://bitcoin-rpc.publicnode.com")},
+ {Slug: "drpc", Name: "dRPC", URL: envDefault("RPC_URL_BTC_DRPC", "https://bitcoin.drpc.org")},
+ {Slug: "nownodes", Name: "NOWNodes", URL: envDefault("RPC_URL_BTC_NOWNODES", "https://public-btc.nownodes.io")},
+ },
+ },
+ // 2026-09-09 — LUKSO (EVM L1, chain 42). eth_getBlockByNumber (no key).
+ {
+ Slug: "lukso",
+ Name: "LUKSO",
+ Providers: []Provider{
+ {Slug: "lukso-official", Name: "LUKSO Foundation", URL: envDefault("RPC_URL_LUKSO_OFFICIAL", "https://rpc.mainnet.lukso.network")},
+ {Slug: "nownodes", Name: "NOWNodes", URL: envDefault("RPC_URL_LUKSO_NOWNODES", "https://public-lukso.nownodes.io")},
+ {Slug: "thirdweb", Name: "thirdweb", URL: envDefault("RPC_URL_LUKSO_THIRDWEB", "https://42.rpc.thirdweb.com")},
+ },
+ },
// 2026-08-27 wave-10. Litecoin — Tatum JSON-RPC (getblockcount) + BlockCypher REST + LitecoinSpace Esplora REST. ~150 s/block.
{
Slug: "litecoin",
diff --git a/harnesses/rpc-capabilities/cmd/script/probe.go b/harnesses/rpc-capabilities/cmd/script/probe.go
index 77a2560cc..a21f367a0 100644
--- a/harnesses/rpc-capabilities/cmd/script/probe.go
+++ b/harnesses/rpc-capabilities/cmd/script/probe.go
@@ -271,6 +271,12 @@ func probeOne(ctx context.Context, c Chain, p Provider) {
block, hash, result, latency, err = callSubstrateHeader(probeCtx, p.URL)
case "cosmos":
block, hash, result, latency, err = callCosmosStatus(probeCtx, p.URL)
+ // Some Cosmos providers (NOWNodes public Coreum) answer 501 on
+ // Tendermint /status but serve the gRPC-gateway REST. Fall back so a
+ // REST-only endpoint isn't scored dead.
+ if result == "http_err" {
+ block, hash, result, latency, err = callCosmosRESTBlock(probeCtx, p.URL)
+ }
case "starknet":
block, result, latency, err = callStarknetBlockNumber(probeCtx, p.URL)
case "stellar":
@@ -315,6 +321,8 @@ func probeOne(ctx context.Context, c Chain, p Provider) {
} else {
block, result, latency, err = callBCHNodeInfo(probeCtx, p.URL)
}
+ case "bitcoin":
+ block, result, latency, err = callNeoBlockCount(probeCtx, p.URL)
case "litecoin":
if strings.Contains(p.URL, "blockcypher.com") {
block, result, latency, err = callBlockCypherLTC(probeCtx, p.URL)
@@ -666,6 +674,54 @@ func callCosmosStatus(ctx context.Context, url string) (block uint64, hash strin
return n, st.SyncInfo.LatestBlockHash, "ok", latencyMs, nil
}
+// callCosmosRESTBlock is the REST fallback for Cosmos providers that don't
+// serve Tendermint /status (NOWNodes public Coreum answers 501 on /status
+// but serves the gRPC-gateway REST). GET
+// /cosmos/base/tendermint/v1beta1/blocks/latest, read block.header.height.
+func callCosmosRESTBlock(ctx context.Context, url string) (block uint64, hash string, result string, latencyMs float64, err error) {
+ target := strings.TrimRight(url, "/") + "/cosmos/base/tendermint/v1beta1/blocks/latest"
+ req, _ := http.NewRequestWithContext(ctx, "GET", target, nil)
+ req.Header.Set("Accept", "application/json")
+ req.Header.Set("User-Agent", "OpenChainBench/1.0 (+https://openchainbench.com)")
+ client := &http.Client{Timeout: probeTimeout}
+ start := time.Now()
+ resp, err := client.Do(req)
+ latencyMs = float64(time.Since(start).Nanoseconds()) / 1e6
+ if err != nil {
+ if ctx.Err() != nil || strings.Contains(err.Error(), "deadline exceeded") || strings.Contains(err.Error(), "Timeout") {
+ return 0, "", "timeout", latencyMs, err
+ }
+ return 0, "", "http_err", latencyMs, err
+ }
+ defer resp.Body.Close()
+ if resp.StatusCode != 200 {
+ _, _ = io.Copy(io.Discard, resp.Body)
+ return 0, "", "http_err", latencyMs, fmt.Errorf("status %d", resp.StatusCode)
+ }
+ raw, err := io.ReadAll(resp.Body)
+ if err != nil {
+ return 0, "", "http_err", latencyMs, err
+ }
+ var r struct {
+ Block struct {
+ Header struct {
+ Height string `json:"height"`
+ } `json:"header"`
+ } `json:"block"`
+ }
+ if err := json.Unmarshal(raw, &r); err != nil {
+ return 0, "", "jsonrpc_err", latencyMs, err
+ }
+ if r.Block.Header.Height == "" {
+ return 0, "", "jsonrpc_err", latencyMs, fmt.Errorf("cosmos REST missing block.header.height")
+ }
+ n, err := strconv.ParseUint(r.Block.Header.Height, 10, 64)
+ if err != nil {
+ return 0, "", "jsonrpc_err", latencyMs, fmt.Errorf("non-numeric height: %q", r.Block.Header.Height)
+ }
+ return n, "", "ok", latencyMs, nil
+}
+
// callStarknetBlockNumber is the Starknet probe path: starknet_blockNumber
// with a rotating request id. Returns the current block number as uint64.
// Staleness uses starknetStaleBlockGap in probeOne.
diff --git a/harnesses/solana-dex-volume/cmd/script/main.go b/harnesses/solana-dex-volume/cmd/script/main.go
index e04d20498..18d017c9a 100644
--- a/harnesses/solana-dex-volume/cmd/script/main.go
+++ b/harnesses/solana-dex-volume/cmd/script/main.go
@@ -3,7 +3,11 @@
// Polls the DeFiLlama DEX API every 30 minutes for each tracked platform
// and exposes per-platform 24h/7d volume and protocol revenue as Prometheus gauges.
//
-// No API key required. Endpoint: https://api.llama.fi/summary/dexs/{slug}
+// No API key required. Primary source: https://api.llama.fi/overview/dexs/solana
+// (chain-scoped, so multichain platforms like GMGN report their SOLANA
+// volume only, keeping the bench apples-to-apples). Falls back to the
+// per-protocol summary endpoint for any platform missing from the
+// overview.
//
// Metrics on :2112/metrics:
//
@@ -22,6 +26,7 @@ import (
"net/http"
"os"
"os/signal"
+ "strings"
"syscall"
"time"
@@ -36,16 +41,17 @@ const (
)
var platforms = []struct {
- slug string
- label string
+ slug string // per-protocol summary slug (fallback path)
+ label string // prometheus label
+ names []string // lowercase name candidates in the chain overview
}{
- {"pump.fun", "pump-fun"},
- {"gmgn", "gmgn"},
- {"axiom", "axiom"},
- {"fomo-wallet", "fomo"},
- {"trojan", "trojan"},
- {"photon", "photon"},
- {"bullx", "bullx"},
+ {"pump.fun", "pump-fun", []string{"pump.fun"}},
+ {"gmgn", "gmgn", []string{"gmgn"}},
+ {"axiom", "axiom", []string{"axiom"}},
+ {"fomo-wallet", "fomo", []string{"fomo", "fomo-wallet"}},
+ {"trojan", "trojan", []string{"trojan"}},
+ {"photon", "photon", []string{"photon"}},
+ {"bullx", "bullx", []string{"bullx"}},
}
var (
@@ -85,6 +91,60 @@ type llamaResponse struct {
Total7d float64 `json:"total7d"`
}
+type overviewResponse struct {
+ Protocols []struct {
+ Name string `json:"name"`
+ DisplayName string `json:"displayName"`
+ Module string `json:"module"`
+ Total24h float64 `json:"total24h"`
+ Total7d float64 `json:"total7d"`
+ } `json:"protocols"`
+}
+
+// fetchOverview returns Solana-scoped per-protocol totals indexed by
+// lowercased name/displayName/module. One call covers every platform.
+func fetchOverview(endpoint, query string) (map[string]llamaResponse, error) {
+ url := fmt.Sprintf("%s/overview/%s/solana?excludeTotalDataChart=true&excludeTotalDataChartBreakdown=true", baseURL, endpoint)
+ if query != "" {
+ url += "&" + query
+ }
+ resp, err := http.Get(url)
+ if err != nil {
+ return nil, err
+ }
+ defer resp.Body.Close()
+ if resp.StatusCode != 200 {
+ return nil, fmt.Errorf("HTTP %d", resp.StatusCode)
+ }
+ body, err := io.ReadAll(resp.Body)
+ if err != nil {
+ return nil, err
+ }
+ var r overviewResponse
+ if err := json.Unmarshal(body, &r); err != nil {
+ return nil, err
+ }
+ out := map[string]llamaResponse{}
+ for _, p := range r.Protocols {
+ v := llamaResponse{Total24h: p.Total24h, Total7d: p.Total7d}
+ for _, k := range []string{p.Name, p.DisplayName, p.Module} {
+ if k != "" {
+ out[strings.ToLower(k)] = v
+ }
+ }
+ }
+ return out, nil
+}
+
+func lookup(ov map[string]llamaResponse, names []string) (llamaResponse, bool) {
+ for _, n := range names {
+ if v, ok := ov[n]; ok {
+ return v, true
+ }
+ }
+ return llamaResponse{}, false
+}
+
func fetch(endpoint, slug, query string) (llamaResponse, error) {
url := fmt.Sprintf("%s/summary/%s/%s", baseURL, endpoint, slug)
if query != "" {
@@ -110,17 +170,37 @@ func fetch(endpoint, slug, query string) (llamaResponse, error) {
}
func runOnce() {
+ // Chain-scoped overviews first: one request each, Solana-only totals
+ // for every protocol. Errors degrade to the per-protocol fallback.
+ ovDex, errD := fetchOverview("dexs", "")
+ if errD != nil {
+ fmt.Printf("[poll] overview dexs: %v (falling back to summaries)\n", errD)
+ }
+ ovFees, errF := fetchOverview("fees", "dataType=dailyRevenue")
+ if errF != nil {
+ fmt.Printf("[poll] overview fees: %v (falling back to summaries)\n", errF)
+ }
+
for _, p := range platforms {
- vol, err := fetch("dexs", p.slug, "")
- if err != nil {
- fmt.Printf("[poll] volume %s: %v\n", p.slug, err)
- health.WithLabelValues(p.label).Set(0)
- continue
+ vol, volOK := lookup(ovDex, p.names)
+ if !volOK {
+ v, err := fetch("dexs", p.slug, "")
+ if err != nil {
+ fmt.Printf("[poll] volume %s: %v\n", p.slug, err)
+ health.WithLabelValues(p.label).Set(0)
+ continue
+ }
+ fmt.Printf("[poll] %s: absent from solana overview, using all-chain summary\n", p.label)
+ vol = v
}
- rev, err := fetch("fees", p.slug, "dataType=dailyRevenue")
- if err != nil {
- fmt.Printf("[poll] fees %s: %v (volume ok)\n", p.slug, err)
+ rev, revOK := lookup(ovFees, p.names)
+ if !revOK {
+ r, err := fetch("fees", p.slug, "dataType=dailyRevenue")
+ if err != nil {
+ fmt.Printf("[poll] fees %s: %v (volume ok)\n", p.slug, err)
+ }
+ rev = r
}
volume24h.WithLabelValues(p.label).Set(vol.Total24h)
diff --git a/harnesses/wallet-labels/.env.example b/harnesses/wallet-labels/.env.example
index dc1a7d7da..0d1d17414 100644
--- a/harnesses/wallet-labels/.env.example
+++ b/harnesses/wallet-labels/.env.example
@@ -10,6 +10,9 @@ MORALIS_API_KEY=
# Helius (Solana)
HELIUS_API_KEY=
+# Serialized (18 EVM chains + Solana). Raw key, no Bearer prefix.
+SERIALIZED_API_KEY=
+
# Tuning
WALLET_LABELS_CHECK_DELAY_SECONDS=30
WALLET_LABELS_WORKERS=8
diff --git a/harnesses/wallet-labels/cmd/script/accuracy.go b/harnesses/wallet-labels/cmd/script/accuracy.go
new file mode 100644
index 000000000..c785b8dfa
--- /dev/null
+++ b/harnesses/wallet-labels/cmd/script/accuracy.go
@@ -0,0 +1,117 @@
+package main
+
+import "strings"
+
+// The bench scores a "hit" whenever a provider returns any non-generic
+// name. That rule cannot tell a curated entity label from a personal
+// name-service record: a provider that resolves `dex.davywoodfi.eth`
+// against Permit2, or `bonklanatoken.sol` against the Raydium authority,
+// scores exactly like one that answers "Permit2" and "Raydium".
+//
+// Measured at the time this was written, on the 100 anchors covered by
+// the two API-key providers: 25% of Serialized's hits and 25.4% of
+// Mobula's named something other than the curated entity. The bias is
+// symmetric, so this is a property of the scoring rule rather than of
+// any one vendor.
+//
+// accurateLabel adds the check the harness could always have made: the
+// anchor list already carries a curated Hint for every address and the
+// scoring path ignored it. This is published as a SEPARATE series
+// (wallet_labels_accurate_total) rather than folded into
+// wallet_labels_success_total, so the existing leaderboard and its
+// history stay intact while the stricter number builds up alongside.
+
+// normalizeLabel lowercases and strips everything that is not
+// alphanumeric, so "Uniswap: Universal Router" and "uniswap universal
+// router" compare equal.
+func normalizeLabel(s string) string {
+ var b strings.Builder
+ for _, r := range strings.ToLower(s) {
+ if (r >= 'a' && r <= 'z') || (r >= '0' && r <= '9') {
+ b.WriteRune(r)
+ }
+ }
+ return b.String()
+}
+
+// genericHintWords are words that appear in curated hints but carry no
+// entity signal on their own. Without this list "USDC (Base native)"
+// would match "jakie.base.eth" through the word "base", scoring a
+// personal Basename as a correct label for a token contract.
+var genericHintWords = map[string]bool{
+ "base": true, "solana": true, "ethereum": true, "polygon": true, "arbitrum": true,
+ "optimism": true, "avalanche": true, "bitcoin": true, "stellar": true, "native": true,
+ "token": true, "contract": true, "wallet": true, "hot": true, "cold": true,
+ "chain": true, "mint": true, "address": true, "factory": true, "proxy": true,
+ "deployer": true, "treasury": true, "bridge": true, "pool": true, "vault": true,
+ "router": true, "exchange": true, "protocol": true, "official": true, "main": true,
+}
+
+// hintTokens splits a curated hint into the words that carry entity
+// signal. Bare indices are dropped so "Binance 14" matches on "binance"
+// and never on "14", otherwise "Bitstamp 14" would score as a correct
+// answer. Generic and chain words are dropped for the same reason. The
+// length floor is 3 and not 4: "OKX" is a real entity name.
+func hintTokens(hint string) []string {
+ repl := strings.NewReplacer(":", " ", "-", " ", "/", " ", "(", " ", ")", " ", ".", " ", "_", " ")
+ var out []string
+ for _, w := range strings.Fields(strings.ToLower(repl.Replace(hint))) {
+ if len(w) < 3 || allDigits(w) || genericHintWords[w] {
+ continue
+ }
+ out = append(out, w)
+ }
+ return out
+}
+
+// labelTokens splits a returned label the same way, so matching happens
+// on whole words. Substring matching would let "base" inside
+// "jakie.base.eth" pass, which is exactly the false positive this
+// series exists to avoid.
+func labelTokens(label string) []string {
+ repl := strings.NewReplacer(":", " ", "-", " ", "/", " ", "(", " ", ")", " ", ".", " ", "_", " ")
+ return strings.Fields(strings.ToLower(repl.Replace(label)))
+}
+
+func allDigits(s string) bool {
+ for _, r := range s {
+ if r < '0' || r > '9' {
+ return false
+ }
+ }
+ return len(s) > 0
+}
+
+// accurateLabel reports whether the returned label plausibly names the
+// entity the anchor was curated for. Permissive on form ("Binance"
+// matches "Binance 14", "OKX 7" matches "OKX 1") and strict on identity
+// ("Bittrex 3" does not match "OKX 1", "jakie.base.eth" does not match
+// "USDC (Base native)").
+func accurateLabel(hint, label string) bool {
+ if hint == "" || label == "" {
+ return false
+ }
+ h, l := normalizeLabel(hint), normalizeLabel(label)
+ if h == "" || l == "" {
+ return false
+ }
+ if h == l {
+ return true
+ }
+ ht := hintTokens(hint)
+ if len(ht) == 0 {
+ // Hint carried no signal word (e.g. "Binance 14" reduced to
+ // nothing would be a bug, but "1" alone would not). Fall back to
+ // whole-string containment rather than matching everything.
+ return strings.Contains(l, h) || strings.Contains(h, l)
+ }
+ lt := labelTokens(label)
+ for _, hw := range ht {
+ for _, lw := range lt {
+ if hw == lw || strings.HasPrefix(lw, hw) || strings.HasPrefix(hw, lw) {
+ return true
+ }
+ }
+ }
+ return false
+}
diff --git a/harnesses/wallet-labels/cmd/script/accuracy_test.go b/harnesses/wallet-labels/cmd/script/accuracy_test.go
new file mode 100644
index 000000000..f8e08b949
--- /dev/null
+++ b/harnesses/wallet-labels/cmd/script/accuracy_test.go
@@ -0,0 +1,38 @@
+package main
+
+import "testing"
+
+func TestAccurateLabel(t *testing.T) {
+ cases := []struct {
+ hint, label string
+ want bool
+ why string
+ }{
+ {"Binance 14", "Binance", true, "curated entity with an index, provider returns the bare name"},
+ {"Uniswap V3 Router 2", "Uniswap: Universal Router", true, "same protocol, different punctuation"},
+ {"vitalik.eth", "vitalik.eth", true, "exact"},
+ {"OKX 1", "Bittrex 3", false, "different exchange must not pass"},
+ {"OKX 1", "OKX 7", true, "same entity, different hot wallet index"},
+ {"Bitfinex", "Polygon", false, "unrelated"},
+ {"Permit2", "dex.davywoodfi.eth", false, "personal ENS on a known contract"},
+ {"USDC (Base native)", "jakie.base.eth", false, "personal basename on a token contract"},
+ {"Raydium Authority", "bonklanatoken.sol", false, "personal .sol on a program authority"},
+ {"USDT (BSC)", "Fake_Phishing6512", false, "explorer warning tag is not the entity"},
+ {"Binance 14", "", false, "no label"},
+ {"", "Binance", false, "no hint"},
+ {"Binance 8", "Binance 8", true, "exact with index"},
+ {"Coinbase 1", "Coinbase 10", true, "same entity"},
+ }
+ for _, c := range cases {
+ if got := accurateLabel(c.hint, c.label); got != c.want {
+ t.Errorf("accurateLabel(%q, %q) = %v, want %v (%s)", c.hint, c.label, got, c.want, c.why)
+ }
+ }
+}
+
+func TestHintTokensDropsIndices(t *testing.T) {
+ got := hintTokens("Binance 14")
+ if len(got) != 1 || got[0] != "binance" {
+ t.Fatalf("hintTokens(\"Binance 14\") = %v, want [binance]; a bare index must never be a match token", got)
+ }
+}
diff --git a/harnesses/wallet-labels/cmd/script/anchor_feeder.go b/harnesses/wallet-labels/cmd/script/anchor_feeder.go
index 851fb2dce..14c3b3391 100644
--- a/harnesses/wallet-labels/cmd/script/anchor_feeder.go
+++ b/harnesses/wallet-labels/cmd/script/anchor_feeder.go
@@ -36,6 +36,7 @@ func runAnchorFeeder(ctx context.Context, q *queue) {
address: a.Address,
chain: a.Chain,
kind: a.Kind,
+ hint: a.Hint,
discoveredAt: time.Now(),
}) {
// queue full — wait a bit so workers can catch up.
diff --git a/harnesses/wallet-labels/cmd/script/config.go b/harnesses/wallet-labels/cmd/script/config.go
index 134cc7417..2bb00dd97 100644
--- a/harnesses/wallet-labels/cmd/script/config.go
+++ b/harnesses/wallet-labels/cmd/script/config.go
@@ -11,27 +11,29 @@ import (
// printed in full — only their length, so misconfigured deploys
// fail loudly without leaking material.
type Config struct {
- MobulaAPIKey string
- MoralisAPIKey string
- HeliusAPIKey string
+ MobulaAPIKey string
+ MoralisAPIKey string
+ HeliusAPIKey string
+ SerializedAPIKey string
- CheckDelay time.Duration
- Workers int
- QueueSize int
- PromListen string
- LogsToken string
+ CheckDelay time.Duration
+ Workers int
+ QueueSize int
+ PromListen string
+ LogsToken string
}
func loadConfig() *Config {
c := &Config{
- MobulaAPIKey: os.Getenv("MOBULA_API_KEY"),
- MoralisAPIKey: os.Getenv("MORALIS_API_KEY"),
- HeliusAPIKey: os.Getenv("HELIUS_API_KEY"),
- CheckDelay: parseDurationSec("WALLET_LABELS_CHECK_DELAY_SECONDS", 30),
- Workers: parseInt("WALLET_LABELS_WORKERS", 8),
- QueueSize: parseInt("WALLET_LABELS_QUEUE_SIZE", 2000),
- PromListen: envDefault("PROM_LISTEN_ADDR", ":2112"),
- LogsToken: os.Getenv("LOGS_TOKEN"),
+ MobulaAPIKey: os.Getenv("MOBULA_API_KEY"),
+ MoralisAPIKey: os.Getenv("MORALIS_API_KEY"),
+ HeliusAPIKey: os.Getenv("HELIUS_API_KEY"),
+ SerializedAPIKey: os.Getenv("SERIALIZED_API_KEY"),
+ CheckDelay: parseDurationSec("WALLET_LABELS_CHECK_DELAY_SECONDS", 30),
+ Workers: parseInt("WALLET_LABELS_WORKERS", 8),
+ QueueSize: parseInt("WALLET_LABELS_QUEUE_SIZE", 2000),
+ PromListen: envDefault("PROM_LISTEN_ADDR", ":2112"),
+ LogsToken: os.Getenv("LOGS_TOKEN"),
}
fmt.Println("=== Wallet Labels Coverage Monitor ===")
@@ -42,6 +44,7 @@ func loadConfig() *Config {
fmt.Printf(" Mobula key set: %v (len=%d)\n", c.MobulaAPIKey != "", len(c.MobulaAPIKey))
fmt.Printf(" Moralis key set: %v (len=%d)\n", c.MoralisAPIKey != "", len(c.MoralisAPIKey))
fmt.Printf(" Helius key set: %v (len=%d)\n", c.HeliusAPIKey != "", len(c.HeliusAPIKey))
+ fmt.Printf(" Serialized key set: %v (len=%d)\n", c.SerializedAPIKey != "", len(c.SerializedAPIKey))
fmt.Println()
return c
diff --git a/harnesses/wallet-labels/cmd/script/main.go b/harnesses/wallet-labels/cmd/script/main.go
index 0e3d5071c..abdfea2c2 100644
--- a/harnesses/wallet-labels/cmd/script/main.go
+++ b/harnesses/wallet-labels/cmd/script/main.go
@@ -53,6 +53,7 @@ func buildProviders(cfg *Config) []Provider {
NewMobulaProvider(cfg.MobulaAPIKey),
NewMoralisProvider(cfg.MoralisAPIKey),
NewHeliusProvider(cfg.HeliusAPIKey),
+ NewSerializedProvider(cfg.SerializedAPIKey),
NewBlockscoutProvider(),
NewOLIProvider(),
NewTonAPIProvider(),
diff --git a/harnesses/wallet-labels/cmd/script/metrics.go b/harnesses/wallet-labels/cmd/script/metrics.go
index cc9d05ec9..c5a200244 100644
--- a/harnesses/wallet-labels/cmd/script/metrics.go
+++ b/harnesses/wallet-labels/cmd/script/metrics.go
@@ -26,6 +26,21 @@ var (
ConstLabels: commonLabels,
}, []string{"provider", "chain", "kind"})
+ // Companion series to successTotal. Same denominator, stricter rule:
+ // the label must actually name the curated entity for the anchor, not
+ // merely be non-generic. See accuracy.go for why this exists.
+ accuracyChecksTotal = promauto.NewCounterVec(prometheus.CounterOpts{
+ Name: "wallet_labels_accuracy_checks_total",
+ Help: "Label checks scored for accuracy against the curated anchor hint.",
+ ConstLabels: commonLabels,
+ }, []string{"provider", "chain", "kind"})
+
+ accurateTotal = promauto.NewCounterVec(prometheus.CounterOpts{
+ Name: "wallet_labels_accurate_total",
+ Help: "Checks where the returned label names the curated entity for that anchor.",
+ ConstLabels: commonLabels,
+ }, []string{"provider", "chain", "kind"})
+
apiLatency = promauto.NewHistogramVec(prometheus.HistogramOpts{
Name: "wallet_labels_api_latency_milliseconds",
Help: "Provider API response time in milliseconds.",
@@ -67,6 +82,19 @@ func recordSkipped(provider, chain string) {
skippedTotal.WithLabelValues(provider, chain).Inc()
}
+// recordAccuracy feeds the companion series that scores a label against
+// the curated Hint rather than against "is it non-generic". Same label
+// set as checksTotal so the two ratios share a denominator.
+func recordAccuracy(provider, chain, kind string, accurate bool) {
+ if kind == "" {
+ kind = "unknown"
+ }
+ accuracyChecksTotal.WithLabelValues(provider, chain, kind).Inc()
+ if accurate {
+ accurateTotal.WithLabelValues(provider, chain, kind).Inc()
+ }
+}
+
func recordCheck(provider, chain, kind string, hasLabel bool, latencyMs float64, err error) {
if kind == "" {
kind = "unknown"
diff --git a/harnesses/wallet-labels/cmd/script/monitor.go b/harnesses/wallet-labels/cmd/script/monitor.go
index 9ca9e027d..0bb2fa20c 100644
--- a/harnesses/wallet-labels/cmd/script/monitor.go
+++ b/harnesses/wallet-labels/cmd/script/monitor.go
@@ -12,6 +12,7 @@ type sample struct {
address string
chain string
kind string // "contract" | "eoa" — carried into Prom labels so the bench can split by anchor kind
+ hint string // curated entity name for this anchor; scores the accuracy series, never the hit rule
discoveredAt time.Time
}
@@ -105,6 +106,12 @@ func lookupAll(ctx context.Context, providers []Provider, s sample) {
continue
}
recordCheck(r.Provider, r.Chain, s.kind, r.HasLabel, float64(r.LatencyMs), r.Err)
+ // Stricter companion series: a hit only counts when the label
+ // actually names the curated entity. Published alongside, never
+ // folded into, the hit rate. See accuracy.go.
+ if r.Err == nil {
+ recordAccuracy(r.Provider, r.Chain, s.kind, r.HasLabel && accurateLabel(s.hint, r.Label))
+ }
recordDebug(debugEntry{
Provider: r.Provider, Chain: r.Chain, Address: r.Address,
HasLabel: r.HasLabel, LatencyMs: r.LatencyMs,
diff --git a/harnesses/wallet-labels/cmd/script/serialized.go b/harnesses/wallet-labels/cmd/script/serialized.go
new file mode 100644
index 000000000..b35c79545
--- /dev/null
+++ b/harnesses/wallet-labels/cmd/script/serialized.go
@@ -0,0 +1,129 @@
+package main
+
+import (
+ "context"
+ "encoding/json"
+ "fmt"
+ "net/http"
+ "sync"
+ "time"
+)
+
+// Serialized exposes an identity graph rather than a pure entity-label
+// service: GET /v1/wallet/profile returns a display name, ENS / Basename /
+// .sol resolution, socials and linked wallets for an address.
+//
+// We read it with the same precedence rule every other provider gets —
+// first non-generic name signal wins — so the bench compares like with
+// like. Note for whoever reads the leaderboard: because `displayName`
+// can resolve to a personal name-service record rather than a curated
+// entity, a share of Serialized's hits name the *holder* of an address
+// rather than the *entity* behind it (a measured ~25% of hits at
+// onboarding time, against ~25% for Mobula on the same sample). That is
+// a property of the bench's hit rule, not of this provider, and the fix
+// belongs in the scoring rule for everyone at once. See
+// docs/methodology/serialized-onboarding-audit.md §5.
+type SerializedProvider struct {
+ apiKey string
+}
+
+func NewSerializedProvider(key string) *SerializedProvider {
+ return &SerializedProvider{apiKey: key}
+}
+
+func (p *SerializedProvider) Name() string { return "serialized" }
+
+// serializedChains are the anchor-list chains Serialized indexes. Their
+// full surface is 18 EVM chains plus Solana; the ones below are the
+// intersection with the curated anchor sample. Chains outside this set
+// are skipped rather than counted as misses, same as every other
+// chain-restricted provider in this harness.
+var serializedChains = map[string]bool{
+ "ethereum": true,
+ "bnb": true,
+ "base": true,
+ "arbitrum": true,
+ "solana": true,
+}
+
+func (p *SerializedProvider) Supports(chain string) bool { return serializedChains[chain] }
+
+// Serialized enforces a hard burst cap of 40 requests per second per key
+// and answers anything above it with 429. The harness runs 8 workers with
+// sub-100ms responses, which clears that cap easily and silently turns
+// coverage into a rate-limit artifact (measured: 60 of 100 anchors lost to
+// 429, dropping apparent coverage from 77% to 37%). Serialize the calls at
+// a conservative fixed interval instead of relying on worker count.
+var (
+ serializedMu sync.Mutex
+ serializedLast time.Time
+)
+
+const serializedMinInterval = 60 * time.Millisecond // ~16 rps, well under the 40 rps cap
+
+func serializedThrottle() {
+ serializedMu.Lock()
+ defer serializedMu.Unlock()
+ if wait := time.Until(serializedLast.Add(serializedMinInterval)); wait > 0 {
+ time.Sleep(wait)
+ }
+ serializedLast = time.Now()
+}
+
+func (p *SerializedProvider) Lookup(ctx context.Context, chain, address string) LabelResult {
+ res := LabelResult{Provider: p.Name(), Chain: chain, Address: address}
+ if !p.Supports(chain) || p.apiKey == "" {
+ return res
+ }
+
+ serializedThrottle()
+
+ start := time.Now()
+ req, _ := http.NewRequestWithContext(ctx, "GET",
+ "https://api.serialized.xyz/v1/wallet/profile?address="+address, nil)
+ // Raw key, no Bearer prefix — a prefixed key is rejected with 401.
+ req.Header.Set("Authorization", p.apiKey)
+ req.Header.Set("Accept", "application/json")
+
+ resp, err := httpClient.Do(req)
+ res.LatencyMs = time.Since(start).Milliseconds()
+ if err != nil {
+ res.Err = err
+ return res
+ }
+ defer resp.Body.Close()
+
+ if resp.StatusCode != 200 {
+ res.Err = fmt.Errorf("status_%d", resp.StatusCode)
+ return res
+ }
+
+ var body struct {
+ Data struct {
+ Profile *struct {
+ DisplayName string `json:"displayName"`
+ ENSName string `json:"ensName"`
+ Basename string `json:"basename"`
+ SolName string `json:"solName"`
+ } `json:"profile"`
+ } `json:"data"`
+ }
+ if err := json.NewDecoder(resp.Body).Decode(&body); err != nil {
+ res.Err = fmt.Errorf("parse: %w", err)
+ return res
+ }
+ if body.Data.Profile == nil {
+ return res
+ }
+
+ prof := body.Data.Profile
+ for _, candidate := range []string{prof.DisplayName, prof.ENSName, prof.Basename, prof.SolName} {
+ if !genericLabel(candidate) {
+ res.Label = candidate
+ res.HasLabel = true
+ res.Raw = map[string]any{"label": candidate, "source": "wallet_profile"}
+ break
+ }
+ }
+ return res
+}
diff --git a/next.config.ts b/next.config.ts
index 3a23ca7b3..f42ba984e 100644
--- a/next.config.ts
+++ b/next.config.ts
@@ -112,6 +112,25 @@ const nextConfig: NextConfig = {
source: "/:path*",
headers: SECURITY_HEADERS,
},
+ {
+ // The RPC speed test fires fetch() at user-supplied endpoints
+ // straight from the browser — the whole product. The site-wide
+ // connect-src allowlist blocked every probe (surfaced as a fake
+ // "CORS" failure on endpoints that are actually CORS-open, e.g.
+ // publicnode). Open connect-src to any https origin on THIS
+ // route only; every other directive keeps the tight policy.
+ source: "/speedtest-rpc",
+ headers: [
+ ...SECURITY_HEADERS.filter((h) => h.key !== "Content-Security-Policy"),
+ {
+ key: "Content-Security-Policy",
+ value: CSP.replace(
+ /connect-src [^;]+/,
+ `connect-src 'self' https: ${RELAY_WS}`,
+ ),
+ },
+ ],
+ },
{
// Badges are designed to be embedded as in third-party
// READMEs and blogs. Override frame-ancestors so SVG embedding
diff --git a/public/logos/lukso.svg b/public/logos/lukso.svg
new file mode 100644
index 000000000..cfab20ee5
--- /dev/null
+++ b/public/logos/lukso.svg
@@ -0,0 +1,17 @@
+
+
+
diff --git a/public/logos/nownodes.svg b/public/logos/nownodes.svg
index 297c5a4e1..5e94ea570 100644
--- a/public/logos/nownodes.svg
+++ b/public/logos/nownodes.svg
@@ -1,4 +1,4 @@
-
-
+
{/* Latest deployed benchmarks */}
diff --git a/src/app/partners/opengraph-image.tsx b/src/app/partners/opengraph-image.tsx
new file mode 100644
index 000000000..5566a6188
--- /dev/null
+++ b/src/app/partners/opengraph-image.tsx
@@ -0,0 +1,15 @@
+import { OG_SIZE, renderHubOG } from "@/lib/og-hub-template";
+
+export const runtime = "nodejs";
+export const alt = "OpenChainBench partners and integrations. Projects that embed or reference our open benchmark data.";
+export const size = OG_SIZE;
+export const contentType = "image/png";
+
+export default function OG() {
+ return renderHubOG({
+ kicker: "Partners",
+ headline: "Partners and integrations.",
+ subline:
+ "Projects and documentation sites that embed or reference OpenChainBench benchmark data in their products.",
+ });
+}
diff --git a/src/app/perp/[slug]/opengraph-image.tsx b/src/app/perp/[slug]/opengraph-image.tsx
new file mode 100644
index 000000000..d0f927e0b
--- /dev/null
+++ b/src/app/perp/[slug]/opengraph-image.tsx
@@ -0,0 +1,20 @@
+import { OG_SIZE, renderHubOG } from "@/lib/og-hub-template";
+import { PERP_VENUES } from "@/lib/perp-stats";
+
+export const runtime = "nodejs";
+export const size = OG_SIZE;
+export const contentType = "image/png";
+
+export default async function OG({ params }: { params: Promise<{ slug: string }> }) {
+ const { slug } = await params;
+ const cohortSlug = slug === "gmx" ? "gmx-v2" : slug;
+ const venue = PERP_VENUES.find((v) => v.slug === cohortSlug);
+ const name = venue?.name ?? slug;
+ const chain = venue?.chain ?? "";
+
+ return renderHubOG({
+ kicker: "Perp DEX benchmark",
+ headline: `${name} benchmark.`,
+ subline: `Live volume, open interest, all-in fees and funding rate for ${name}${chain ? ` on ${chain}` : ""}. Compared against every major perp exchange.`,
+ });
+}
diff --git a/src/app/perps/opengraph-image.tsx b/src/app/perps/opengraph-image.tsx
new file mode 100644
index 000000000..186d421ee
--- /dev/null
+++ b/src/app/perps/opengraph-image.tsx
@@ -0,0 +1,15 @@
+import { OG_SIZE, renderHubOG } from "@/lib/og-hub-template";
+
+export const runtime = "nodejs";
+export const alt = "Best perp DEX 2026. Live leaderboard of perpetual exchanges by volume, open interest, fees and funding.";
+export const size = OG_SIZE;
+export const contentType = "image/png";
+
+export default function OG() {
+ return renderHubOG({
+ kicker: "Perp DEX leaderboard",
+ headline: "Best perp DEX, by the numbers.",
+ subline:
+ "Volume, open interest, all-in cost and funding rate across every major perpetual exchange. Updated continuously.",
+ });
+}
diff --git a/src/app/prediction-markets/opengraph-image.tsx b/src/app/prediction-markets/opengraph-image.tsx
new file mode 100644
index 000000000..b82b9a441
--- /dev/null
+++ b/src/app/prediction-markets/opengraph-image.tsx
@@ -0,0 +1,15 @@
+import { OG_SIZE, renderHubOG } from "@/lib/og-hub-template";
+
+export const runtime = "nodejs";
+export const alt = "Prediction markets leaderboard 2026. Volume, resolution delay, API latency and data freshness across every major venue.";
+export const size = OG_SIZE;
+export const contentType = "image/png";
+
+export default function OG() {
+ return renderHubOG({
+ kicker: "Prediction markets",
+ headline: "Prediction markets, ranked live.",
+ subline:
+ "Volume, resolution delay, API latency and data freshness across Polymarket, Kalshi and every other major venue.",
+ });
+}
diff --git a/src/app/products/[slug]/page.tsx b/src/app/products/[slug]/page.tsx
index 1404fe50d..1023ac3f7 100644
--- a/src/app/products/[slug]/page.tsx
+++ b/src/app/products/[slug]/page.tsx
@@ -6,7 +6,7 @@ import { getProvider } from "@/lib/providers";
import { CHAIN_BY_SLUG } from "@/lib/chains";
import { ProviderLogo } from "@/components/provider-logo";
import { CATEGORY_COLOR } from "@/lib/category-colors";
-import { fmtUnit } from "@/lib/format";
+import { fmtUnit, valueWindowLabel } from "@/lib/format";
import { capDescription } from "@/lib/seo-text";
import { SITE } from "@/data/site";
import {
@@ -782,11 +782,13 @@ export default async function ProviderPage({
const catColor = CATEGORY_COLOR[a.benchmark.category];
const hasData = a.rank > 0 && a.result.ms.p50 !== 0;
const value = hasData ? fmtUnit(a.result.ms.p50, a.benchmark.unit) : null;
- // Per-chain rank chips. Rendered alongside the aggregate rank
- // when the bench declares chain dimensions and the provider has
- // per-chain ranks populated. Surface text reads e.g. "#1 on
- // Solana · #4 on Base · #4 on BNB" so a chain-restricted
- // provider can't be passed off as a free cross-chain #1.
+ // Per-chain leadership chips, rendered alongside the aggregate
+ // rank when the bench declares chain dimensions. Leaders only:
+ // a chip means "leads this chain", and its absence means "does
+ // not lead", never "ranks lower". Reads e.g. "#1 of 4 on
+ // Ethereum" so a chain-restricted provider can't be passed off
+ // as a free cross-chain #1, and so a win on a two-provider
+ // chain isn't dressed up as a win on a crowded one.
const chainRanks =
a.rankPerChain && a.benchmark.chainDimensions
? a.benchmark.chainDimensions
@@ -840,13 +842,10 @@ export default async function ProviderPage({
{chainRanks.map(({ chain, entry }) => (
- #{entry.rank} on {chain.label}
+ #1{entry.totalRanked > 0 ? ` of ${entry.totalRanked}` : ""} on{" "}
+ {chain.label}
))}
@@ -857,7 +856,7 @@ export default async function ProviderPage({
<>
{value}
- p50 · 24h
+ {valueWindowLabel(a.benchmark)}
>
) : (
diff --git a/src/app/reports/[category]/[slug]/opengraph-image.tsx b/src/app/reports/[category]/[slug]/opengraph-image.tsx
index 79ab3b056..9374af063 100644
--- a/src/app/reports/[category]/[slug]/opengraph-image.tsx
+++ b/src/app/reports/[category]/[slug]/opengraph-image.tsx
@@ -114,6 +114,6 @@ export default async function OG({
),
- { ...size }
+ { ...size, headers: { "cache-control": "public, s-maxage=3600, stale-while-revalidate=86400" } }
);
}
diff --git a/src/app/reports/[category]/[slug]/page.tsx b/src/app/reports/[category]/[slug]/page.tsx
index 9baf08937..7e2677ed1 100644
--- a/src/app/reports/[category]/[slug]/page.tsx
+++ b/src/app/reports/[category]/[slug]/page.tsx
@@ -31,7 +31,13 @@ export async function generateMetadata({ params }: Props): Promise {
if (!report) return {};
const canonical = `${SITE.url}/reports/${category}/${slug}`;
- const ogImage = report.ogImage ?? `${SITE.url}/opengraph-image`;
+ // Use the per-report OG card (dynamic opengraph-image.tsx in this
+ // segment) as the default, NOT the site-wide /opengraph-image. Setting
+ // openGraph.images explicitly here shadows Next's file convention, so
+ // without this the bespoke titled card was never emitted and every
+ // report shared the generic site thumbnail on X / LinkedIn / SERP.
+ const ogImage =
+ report.ogImage ?? `${SITE.url}/reports/${category}/${slug}/opengraph-image`;
const social = `${report.title} · OpenChainBench`;
return {
@@ -264,7 +270,9 @@ function reportJsonLd(report: ReturnType) {
description: report.summary,
datePublished: new Date(report.publishedAt).toISOString(),
dateModified: new Date(report.publishedAt).toISOString(),
- image: report.ogImage ?? `${SITE.url}/opengraph-image`,
+ image:
+ report.ogImage ??
+ `${SITE.url}/reports/${report.categorySlug}/${report.slug}/opengraph-image`,
author: { "@id": PERSON_ID },
publisher: { "@id": `${SITE.url}/#org` },
inLanguage: "en",
diff --git a/src/app/reports/[category]/opengraph-image.tsx b/src/app/reports/[category]/opengraph-image.tsx
new file mode 100644
index 000000000..ebec3508b
--- /dev/null
+++ b/src/app/reports/[category]/opengraph-image.tsx
@@ -0,0 +1,19 @@
+import { OG_SIZE, renderHubOG } from "@/lib/og-hub-template";
+import { REPORT_CATEGORY_META } from "@/lib/reports/loader";
+
+export const runtime = "nodejs";
+export const size = OG_SIZE;
+export const contentType = "image/png";
+
+export default async function OG({ params }: { params: Promise<{ category: string }> }) {
+ const { category } = await params;
+ const meta = REPORT_CATEGORY_META[category];
+ const label = meta?.label ?? category;
+ const description = meta?.description ?? "In-depth analysis backed by live OpenChainBench benchmark data.";
+
+ return renderHubOG({
+ kicker: `${label} reports`,
+ headline: `${label} reports.`,
+ subline: description,
+ });
+}
diff --git a/src/app/reports/opengraph-image.tsx b/src/app/reports/opengraph-image.tsx
new file mode 100644
index 000000000..c43dbce94
--- /dev/null
+++ b/src/app/reports/opengraph-image.tsx
@@ -0,0 +1,15 @@
+import { OG_SIZE, renderHubOG } from "@/lib/og-hub-template";
+
+export const runtime = "nodejs";
+export const alt = "OpenChainBench reports. In-depth analysis of crypto infrastructure performance backed by live benchmark data.";
+export const size = OG_SIZE;
+export const contentType = "image/png";
+
+export default function OG() {
+ return renderHubOG({
+ kicker: "Reports",
+ headline: "Deeper dives, same data.",
+ subline:
+ "In-depth analysis of crypto infrastructure performance, backed by live benchmark data and open methodology.",
+ });
+}
diff --git a/src/app/rpc-map/page.tsx b/src/app/rpc-map/page.tsx
new file mode 100644
index 000000000..8daa685b2
--- /dev/null
+++ b/src/app/rpc-map/page.tsx
@@ -0,0 +1,98 @@
+import type { Metadata } from "next";
+import Link from "next/link";
+import { pageMetadata } from "@/lib/page-metadata";
+import { RpcMapClient } from "@/components/speedtest/rpc-map-client";
+import { buildBreadcrumbJsonLd, safeJsonLd } from "@/lib/jsonld";
+import { SITE } from "@/data/site";
+
+export const metadata: Metadata = pageMetadata({
+ path: "/rpc-map",
+ title: "Global RPC Latency Map: real measurements from real connections",
+ description:
+ "World map of RPC provider latency, built from anonymous browser speed tests run by real visitors. See which provider is fastest near you, per chain. Open data, CC-BY-4.0.",
+});
+
+export const revalidate = 3600;
+
+export default function RpcMapPage() {
+ const pageUrl = `${SITE.url}/rpc-map`;
+ const jsonLd = {
+ "@context": "https://schema.org",
+ "@graph": [
+ buildBreadcrumbJsonLd([
+ { name: "Home", item: SITE.url },
+ { name: "RPC Latency Map", item: pageUrl },
+ ]),
+ {
+ "@type": "Dataset",
+ "@id": `${pageUrl}#dataset`,
+ name: "Crowdsourced RPC latency by location",
+ url: pageUrl,
+ description:
+ "Median RPC latency per provider per geographic area, aggregated from anonymous browser speed tests. No IPs stored; coordinates rounded to city-level cells.",
+ license: "https://creativecommons.org/licenses/by/4.0/",
+ creator: { "@id": `${SITE.url}/#org` },
+ distribution: {
+ "@type": "DataDownload",
+ encodingFormat: "application/json",
+ contentUrl: `${SITE.url}/api/speedtest/map`,
+ },
+ },
+ ],
+ };
+
+ return (
+
+
+
+ RPC latency map
+
+
+ Where is each RPC provider actually fast?
+
+
+ Every dot is real measurements from real visitors who ran the{" "}
+
+ browser speed test
+ {" "}
+ at that location. Hover a dot to see which provider wins there and by
+ how many milliseconds. Datacenter benchmarks tell you how fast a
+ provider can be; this map shows what people actually experience.
+
+
+ Fully anonymous: no IPs stored, locations rounded to city-level
+ cells, provider names only (never URLs or keys). Medians and per-area
+ caps keep any single source from tilting the map.
+
+
+
+
+
+
+ How the map is built
+
+
+ When a visitor completes a{" "}
+
+ speed test
+
+ , the browser sends only the provider slug, the chain and the
+ measured median. The server derives a city-level position from the
+ request and immediately discards the address. Results aggregate
+ into ~40 km cells; each cell shows the median of its last
+ contributions over a rolling two-month window, with per-source
+ daily caps so no single machine can flood an area. The dataset is
+ available as{" "}
+
+ open JSON
+ {" "}
+ under CC-BY-4.0.
+
+
+
+ );
+}
diff --git a/src/app/rpc/opengraph-image.tsx b/src/app/rpc/opengraph-image.tsx
new file mode 100644
index 000000000..6cddfe973
--- /dev/null
+++ b/src/app/rpc/opengraph-image.tsx
@@ -0,0 +1,15 @@
+import { OG_SIZE, renderHubOG } from "@/lib/og-hub-template";
+
+export const runtime = "nodejs";
+export const alt = "Fastest RPC providers 2026, by chain and region. Live p50/p90/p99 latency and success rate.";
+export const size = OG_SIZE;
+export const contentType = "image/png";
+
+export default function OG() {
+ return renderHubOG({
+ kicker: "RPC benchmarks",
+ headline: "Fastest RPC, by chain and region.",
+ subline:
+ "p50/p90/p99 latency and success rate for every major RPC provider, probed every 60 seconds from US East, EU West and Singapore.",
+ });
+}
diff --git a/src/app/rpc/page.tsx b/src/app/rpc/page.tsx
index b85eff1e9..afdaad4c5 100644
--- a/src/app/rpc/page.tsx
+++ b/src/app/rpc/page.tsx
@@ -37,6 +37,14 @@ export default async function RpcHubPage() {
const rpcSpecs = specs
.filter((s) => s.slug.endsWith("-rpc") && !NON_CHAIN_RPC_SLUGS.has(s.slug))
.sort((a, b) => a.slug.localeCompare(b.slug));
+ // Keyed / API-key RPC benches (e.g. keyed-rpc-robinhood). These do NOT
+ // belong in the no-key chain matrix above (different tier, and the
+ // slug->chain derivation `replace(/-rpc$/,'')` doesn't apply), so we
+ // surface them in a dedicated aside. Without this they were orphaned
+ // from the RPC cluster with no inbound link from the hub.
+ const keyedRpcSpecs = specs
+ .filter((s) => s.slug.startsWith("keyed-rpc-"))
+ .sort((a, b) => a.slug.localeCompare(b.slug));
const breadcrumbLd = {
"@context": "https://schema.org",
@@ -150,6 +158,44 @@ export default async function RpcHubPage() {
+
+
+
+ Want these numbers from your connection?{" "}
+
+ Run the browser RPC speed test →
+ {" "}
+
+ Paste any endpoints (keyed included), no install, URLs never leave your browser.
+
+
+
+
+ {keyedRpcSpecs.length > 0 && (
+
+
+ Keyed & premium endpoints
+
+
+ The matrix above ranks free, no-key public RPCs. Newer chains
+ served only through API-key endpoints get their own keyed
+ benchmark:
+
+ {/* Dial: svg for the arcs/needle, HTML overlay for the readout
+ (satori does not support svg nodes). */}
+
+
+
+ {segs.map((s, i) => (
+
+ ))}
+
+
+
+
+
{READING}
+
ms
+
+
+
+ {/* Copy */}
+
+
+ Free browser tool
+
+
+ RPC Speed Test
+
+
+ Benchmark any RPC endpoints from your own connection. 87 EVM
+ chains, keys never leave your browser.
+
+
+ openchainbench.com/speedtest-rpc
+
+
+
+ ),
+ {
+ ...size,
+ headers: {
+ "cache-control": "public, s-maxage=86400, stale-while-revalidate=604800",
+ },
+ },
+ );
+}
diff --git a/src/app/speedtest-rpc/page.tsx b/src/app/speedtest-rpc/page.tsx
new file mode 100644
index 000000000..8c616b885
--- /dev/null
+++ b/src/app/speedtest-rpc/page.tsx
@@ -0,0 +1,195 @@
+import type { Metadata } from "next";
+import Link from "next/link";
+import { ArrowUpRight } from "lucide-react";
+import { pageMetadata } from "@/lib/page-metadata";
+import { SpeedtestRpcClient } from "@/components/speedtest/speedtest-rpc-client";
+import { buildBreadcrumbJsonLd, buildFaqPageJsonLd, safeJsonLd } from "@/lib/jsonld";
+import { SITE } from "@/data/site";
+
+// Long-lived branch link: the probe engine ships in the site bundle, so
+// pointing at the source file IS the full disclosure of what runs in the
+// visitor's browser.
+const SOURCE_URL =
+ "https://github.com/ChainBench/OpenChainBench/blob/dev/src/components/speedtest/speedtest-rpc-client.tsx";
+
+const FAQ = [
+ {
+ q: "Is it safe to paste an RPC URL with my API key?",
+ a: "Yes. Every probe fires directly from your browser to the provider. Your URLs and keys are never sent to OpenChainBench servers, never logged, and never leave the browser tab. The probe engine is open source, so you can verify exactly what runs.",
+ },
+ {
+ q: "Why do my numbers differ from the public benchmark pages?",
+ a: "The public benchmarks probe from fixed datacenter regions every 60 seconds around the clock. This tool measures from your device, your network and your location right now. Both use the same anti-cache probe and classification, so the numbers are comparable, but the vantage point is yours. That is the point: it is the latency your application would actually see.",
+ },
+ {
+ q: "Which chains and endpoints can I test?",
+ a: "Any HTTPS JSON-RPC endpoint that speaks the standard eth_ methods, on any EVM chain: public gateways, your own keyed Alchemy, Chainstack or QuickNode URLs, or a self-hosted node with CORS enabled. The chain picker prefills the exact no-key cohort that the public per-chain benchmarks rank, across 87 EVM chains.",
+ },
+ {
+ q: "Why does an endpoint show as browser-blocked?",
+ a: "Some endpoints do not send CORS headers, so browsers refuse to call them. This is a property of the endpoint, not of the test: dapps cannot call those endpoints from a browser either. The tool shows an equivalent curl command so you can measure them from a terminal, and the public benchmarks cover them from datacenter probes.",
+ },
+];
+
+const baseMetadata = pageMetadata({
+ path: "/speedtest-rpc",
+ title: "RPC Speed Test: benchmark your endpoints from your browser",
+ description:
+ "Free online RPC latency test. Paste any RPC URLs and measure their real speed from your own connection, across 87 EVM chains. No install, no signup, keys never leave your browser.",
+});
+
+// pageMetadata pins the generic root card; point socials at the
+// dedicated dial card rendered by ./opengraph-image.tsx instead.
+const OG_IMAGE = {
+ url: `${SITE.url}/speedtest-rpc/opengraph-image`,
+ width: 1200,
+ height: 630,
+};
+export const metadata: Metadata = {
+ ...baseMetadata,
+ openGraph: { ...baseMetadata.openGraph, images: [OG_IMAGE] },
+ twitter: { ...baseMetadata.twitter, images: [OG_IMAGE.url] },
+};
+
+export const revalidate = 3600;
+
+export default function SpeedtestRpcPage() {
+ const pageUrl = `${SITE.url}/speedtest-rpc`;
+ const jsonLd = {
+ "@context": "https://schema.org",
+ "@graph": [
+ buildBreadcrumbJsonLd([
+ { name: "Home", item: SITE.url },
+ { name: "RPC Speed Test", item: pageUrl },
+ ]),
+ {
+ "@type": "WebApplication",
+ "@id": `${pageUrl}#app`,
+ name: "RPC Speed Test",
+ url: pageUrl,
+ applicationCategory: "DeveloperApplication",
+ operatingSystem: "Web",
+ offers: { "@type": "Offer", price: "0", priceCurrency: "USD" },
+ description:
+ "Browser-based latency test for JSON-RPC endpoints. Probes run directly from the visitor's connection with the same anti-cache methodology as OpenChainBench's public benchmarks; endpoint URLs never reach OpenChainBench servers.",
+ publisher: { "@id": `${SITE.url}/#org` },
+ // E-E-A-T + GEO: the exact code that runs in the visitor's
+ // browser is public.
+ isBasedOn: SOURCE_URL,
+ license: "https://github.com/ChainBench/OpenChainBench/blob/main/LICENSE",
+ },
+ ],
+ };
+ const faqJsonLd = buildFaqPageJsonLd(
+ FAQ,
+ pageUrl,
+ null,
+ "RPC Speed Test: frequently asked questions",
+ );
+
+ return (
+
+
+ {faqJsonLd && (
+
+ )}
+
+ RPC speed test
+
+
+ Benchmark your RPC endpoints, from your connection
+
+
+ Paste any JSON-RPC URLs, public gateways or your own keyed endpoints,
+ then pick a duration, and watch them race. Every probe fires directly from
+ your browser with the same anti-cache payload as our{" "}
+
+ public RPC benchmarks
+
+ , so the numbers are comparable to the leaderboards.
+
+
+ No install, no signup. Your URLs and API keys never leave this browser
+ tab. Requests go straight from you to the provider.{" "}
+
+ The probe engine is open source
+
+ {" "}
+ so you can verify exactly what runs.
+
+
+
+
+
+
+ Methodology
+
+
+ Each endpoint receives an identical{" "}
+
+ eth_getBlockByNumber("latest", false)
+ {" "}
+ POST with a rotating request id, so no edge cache can answer without
+ touching a real node. One warmup request absorbs the TCP + TLS
+ handshake, then all endpoints are probed once per second in
+ staggered, randomized rounds until the clock runs out, so request
+ bursts never align and network wake-ups hit every endpoint evenly.
+ Responses are classified ok / http_err / jsonrpc_err /
+ timeout, and an endpoint reporting a block more than 20 behind the
+ best tip in the same round is flagged stale. Latency percentiles use
+ ok responses only, exactly like the{" "}
+
+ public benchmark methodology
+
+ . Results reflect your device, network and location. That is the
+ point: it is the latency your application would actually see.
+
@@ -691,11 +700,11 @@ function WalletSide({
{/* Venue-specific extra stats */}
{venue.slug === "hyperliquid" && (() => {
const hlW = w as HlWalletData;
- const hasFunding = Math.abs(hlW.fundingUsd) > 0.5;
+ const hasFunding = Math.abs(hlW.fundingUsd) > 0.01;
return (
-
Volume
+
Total position value
{fmtUsd(volume)}
@@ -766,7 +775,7 @@ function WalletSide({
)}
{hasBorrowing && (
-
Borrowing fees
+
Borrowing fees (vault)
−{fmtUsd(gW.borrowingFeesUsdc)}
@@ -780,6 +789,11 @@ function WalletSide({
{fmtUsd(gW.netCostUsdc)}
+ {(gW.gainsExclusiveFeesUsdc ?? 0) > 0.01 && (
+
+ Incl. {fmtUsd(gW.gainsExclusiveFeesUsdc!)} on pairs not listed on {otherVenue.name} — excluded from comparison.
+
+ )}
);
@@ -899,7 +913,7 @@ function WalletSummaryCard({ result }: { result: FeeCompareResult }) {
Wallet analysis
- Net cost including carry (funding + borrowing) vs taker-rate projection on the other venue
+ Net cost including carry (funding + borrowing) vs a maker/taker-aware projection on the other venue, matched on the coins both venues list
+ {selected.size > 0
+ ? "No samples for this provider selection here yet. Clear the filter or run a test with these providers."
+ : "No community samples for this chain yet. The map fills up as people run the speed test: every completed test adds one anonymous point to its city."}
+
+
+ Run a test for this chain →
+
+
+ )}
+
+
+ {/* Nearest measured areas to the pin */}
+ {pin && (
+
+
+ Closest measured areas to {pin.label}
+
+ {nearest.length === 0 ? (
+
+ Nothing measured near this pin yet for this selection.{" "}
+
+ Run a test from here to add the first point →
+
+
+ median of {p.samples} contribution{p.samples > 1 ? "s" : ""} · last measured {timeAgo(p.lastTs)}
+
+
+ );
+ })}
+
+
+ Bars are the last {Math.min(24, Math.max(...(cellDetail?.providers ?? [{ samples: 0 }]).map((p) => p.samples)))} contributed medians for this area, oldest to newest. Hover a bar for the exact reading and time.
+
+
+ {/* Prefill from the benchmarked no-key cohort: pick a chain and
+ get exactly the endpoints the public per-chain benches probe
+ continuously (90 EVM chains, 291 endpoints). */}
+
+
+ Prefill with the endpoints we already benchmark. Pick a chain:
+
+
+ Testing from your connection · round {round}
+
+
+
+ {/* One speedometer per endpoint, all live at once. The
+ active one (currently being probed) lifts and gets an
+ ink border; the others keep their last reading. */}
+ {(() => {
+ const visible = endpoints.filter((e) => e.status !== "cors_blocked");
+ const blocked = endpoints.filter((e) => e.status === "cors_blocked");
+ return (
+ <>
+ {/* Flex-wrap + justify-center: tiles keep an ideal width
+ at every count and orphan rows self-center (4 tiles
+ read as a clean 2x2, a lone 7th sits centered). */}
+
+ Not testable from a browser (skipped): {blockedDir.map((e) => epLabel(e)).join(", ")}. The public bench covers {blockedDir.length > 1 ? "them" : "it"} server-side.
+
+ Same probe and classification as the public benchmarks
+ (rotating-id anti-cache, ok / http_err / jsonrpc_err / stale /
+ timeout). Your URLs never left this browser tab.
+
+
+
+
+ {contribOff
+ ? "Anonymous map contribution is off for this browser."
+ : "This result was added anonymously to the global latency map (provider names and medians only, no URLs, no IP stored)."}{" "}
+
+ View the map
+
+ {" · "}
+
+
+
+ )}
+
+ );
+}
diff --git a/src/content/reports/rpc/2026-08-fastest-robinhood-chain-rpc.mdx b/src/content/reports/rpc/2026-08-fastest-robinhood-chain-rpc.mdx
new file mode 100644
index 000000000..c3868b41f
--- /dev/null
+++ b/src/content/reports/rpc/2026-08-fastest-robinhood-chain-rpc.mdx
@@ -0,0 +1,67 @@
+---
+title: "Best Robinhood Chain RPC in 2026, measured from Singapore: Chainstack vs Alchemy vs QuickNode"
+category: "rpc"
+slug: "2026-08-fastest-robinhood-chain-rpc"
+publishedAt: "2026-08-31"
+period: "August 2026"
+summary: "Robinhood Chain is live, and three providers offer keyed endpoints for it. We benchmarked all three on the same call, the same cadence, over a rolling 24-hour window. Here is which one delivers the fastest median read."
+heroFinding: "Chainstack posts the lowest median latency on Robinhood Chain, roughly 3 ms p50 over 24 hours, the lowest of the three keyed providers as measured from our Singapore probe."
+author: "OpenChainBench Research"
+readingTime: 6
+canonical: "https://openchainbench.com/reports/rpc/2026-08-fastest-robinhood-chain-rpc"
+---
+
+
+- Robinhood Chain (chain ID 4663) is an Arbitrum Orbit L2 for tokenized equity trading, live on mainnet since July 2026 with a gas subsidy running through September 29, 2026.
+- Three providers currently expose keyed Robinhood Chain endpoints: Chainstack, Alchemy, and QuickNode. This bench probes all three.
+- Chainstack posts the lowest median latency in the cohort, roughly 3 ms (p50, 24h).
+- Alchemy follows at ~5 ms, QuickNode at ~68 ms. All three returned a usable block on 100% of probes.
+- This is the first independent public latency benchmark for Robinhood Chain.
+
+
+## Methodology
+
+Every number here is a live query against the [OpenChainBench Prometheus](https://openchainbench.com/methodology). The harness sends each provider an `eth_getBlockByNumber("latest", false)` request every 60 seconds, with a rotating JSON-RPC id so no edge cache can serve a canned response. Each probe forces a real Robinhood Chain tip.
+
+Each response is classified as ok, http_err, jsonrpc_err, stale (block more than 20 behind the cross-provider tip), or timeout. Latency is recorded only for ok responses, and the figures below are the p50 over a rolling 24-hour window. Probes run from Singapore, the closest probe region to the chain's Asia-Pacific infrastructure; that is the reference region for this bench.
+
+Live page, sparklines, and source: [keyed-rpc-robinhood](/benchmarks/keyed-rpc-robinhood).
+
+## The Result: Chainstack Leads on Median Latency
+
+Three providers offer keyed Robinhood Chain endpoints today, and all three answer reliably. None dropped a probe over the window. So this is a clean speed comparison, and on the median the gap is wide.
+
+Chainstack returns a Robinhood Chain block in roughly 3 ms at the median over 24 hours, the lowest p50 of the three keyed providers from our Singapore probe. Its Robinhood Chain node answers that origin in single-digit milliseconds; the ranking is origin-dependent, and this report is scoped to Singapore.
+
+
+
+Alchemy lands second at ~5 ms via its edge infrastructure. QuickNode's shared-endpoint fleet sits at ~68 ms. All three cleared 100% of probes over the window, so on availability the field is even; the separation is in how fast the typical read comes back.
+
+## Why Median Latency Matters Here
+
+Robinhood Chain is a financially sensitive chain: it exists to move tokenized equities, and blocks arrive roughly every 100 ms. A read that lags the tip is a read against a stale price. For the calls an application makes most, the median round-trip is what most of those reads actually experience, so it is a direct input to how fresh your view of the chain is, whether that is a quote engine, a trading frontend, or any component that polls the head.
+
+At ~3 ms median, Chainstack leaves the most headroom against that 100 ms cadence of the three benched providers, and both leaders sit comfortably inside a single block interval.
+
+## Which to Use
+
+
+Chainstack is the median leader in this cohort, roughly 3 ms p50 over 24h. If your priority is the fastest typical read on Robinhood Chain from Singapore, it is the front-runner today. As with any single endpoint, pair it with a fallback so a provider-side incident never leaves you without a tip.
+
+
+
+Both answer reliably on Robinhood Chain (100% success over the window). Alchemy at ~5 ms is close behind the leader; QuickNode at ~68 ms is workable for less latency-sensitive reads. The trade-off against Chainstack is median latency, not availability.
+
+
+## What to Watch Next
+
+Robinhood Chain is a new chain, and the keyed-provider field is still small. As more providers stand up keyed endpoints and as regional coverage grows, this bench will track them. Latency on a young chain moves day to day, so the live rankings, updated every 60 seconds on the [keyed-rpc-robinhood bench](/benchmarks/keyed-rpc-robinhood), are always the current source of truth.
+
+## Sources and Reproducibility
+
+- **Live bench:** [keyed-rpc-robinhood](/benchmarks/keyed-rpc-robinhood)
+- **Harness source:** [harnesses/rpc-keyed-latency](https://github.com/ChainBench/OpenChainBench/tree/main/harnesses/rpc-keyed-latency)
+- **License:** CC BY 4.0, reproduce with attribution to OpenChainBench and a link to the canonical URL.
+- **Corrections:** file a [GitHub issue](https://github.com/ChainBench/OpenChainBench/issues/new).
+
+*Figures are the 24h median as of 2026-09-02 and move as new samples land. Check the live page for current numbers.*
diff --git a/src/data/bench-published.json b/src/data/bench-published.json
index 0da710e19..63ed8e2a9 100644
--- a/src/data/bench-published.json
+++ b/src/data/bench-published.json
@@ -1,95 +1,220 @@
{
+ "abstract-rpc": "2026-08-02T21:30:41.000Z",
+ "acala-rpc": "2026-08-27T23:39:14.000Z",
"aggregator-head-lag": "2026-04-28T13:37:44.000Z",
+ "akash-rpc": "2026-08-04T15:53:48.000Z",
+ "algorand-rpc": "2026-08-18T18:11:28.000Z",
+ "apechain-rpc": "2026-08-02T21:30:41.000Z",
+ "app-store-ratings": "2026-08-13T14:28:49.000Z",
+ "aptos-rpc": "2026-08-18T18:11:28.000Z",
+ "arbitrum-nova-rpc": "2026-08-04T08:05:53.000Z",
"arbitrum-rpc": "2026-07-03T14:33:15.000Z",
+ "archway-rpc": "2026-08-04T15:53:48.000Z",
"asset-registry-coverage": "2026-07-24T16:00:55.000Z",
+ "astar-rpc": "2026-08-04T15:53:48.000Z",
+ "aurora-rpc": "2026-08-04T12:27:13.000Z",
"avalanche-rpc": "2026-07-03T14:33:15.000Z",
"axelar-gmp-latency": "2026-07-25T23:58:45.000Z",
+ "axelar-rpc": "2026-08-04T15:53:48.000Z",
+ "b2-rpc": "2026-08-04T12:27:13.000Z",
+ "babylon-rpc": "2026-08-27T23:39:14.000Z",
+ "bandchain-rpc": "2026-08-29T20:26:36.000Z",
"base-rpc": "2026-07-03T14:33:15.000Z",
"berachain-rpc": "2026-07-03T22:44:42.000Z",
+ "bitcoin-cash-rpc": "2026-08-27T16:35:12.000Z",
+ "bitlayer-rpc": "2026-08-04T12:27:13.000Z",
"blast-rpc": "2026-07-03T22:44:42.000Z",
"bnb-rpc": "2026-07-03T14:33:15.000Z",
+ "bob-rpc": "2026-08-03T21:50:42.000Z",
+ "boba-rpc": "2026-08-04T15:53:48.000Z",
"bridge-fee": "2026-04-28T13:20:36.000Z",
"bridge-quote-latency": "2026-04-28T13:20:36.000Z",
"buyback-audit": "2026-05-21T17:54:38.000Z",
+ "canto-rpc": "2026-08-04T12:27:13.000Z",
+ "celestia-rpc": "2026-08-04T12:27:13.000Z",
"celo-rpc": "2026-07-03T22:44:42.000Z",
"chainlink-ccip-latency": "2026-07-25T23:25:52.000Z",
+ "cheqd-rpc": "2026-08-29T20:26:36.000Z",
+ "chihuahua-rpc": "2026-08-28T18:56:40.000Z",
+ "chiliz-rpc": "2026-08-05T14:29:04.000Z",
+ "ckb-rpc": "2026-08-18T20:43:17.000Z",
+ "comdex-rpc": "2026-08-04T15:53:48.000Z",
+ "conflux-rpc": "2026-08-04T15:53:48.000Z",
+ "core-rpc": "2026-08-04T08:05:53.000Z",
+ "coreum-rpc": "2026-08-04T15:53:48.000Z",
"cosmos-hub-rpc": "2026-07-24T12:35:13.000Z",
"cronos-rpc": "2026-07-03T22:44:42.000Z",
+ "cronos-zkevm-rpc": "2026-08-05T14:29:04.000Z",
+ "cyber-rpc": "2026-08-02T21:30:41.000Z",
"dex-network-coverage": "2026-07-24T16:00:55.000Z",
+ "dogecoin-rpc": "2026-08-26T15:54:04.000Z",
+ "dydx-rpc": "2026-08-04T12:27:13.000Z",
+ "dymension-rpc": "2026-08-04T15:53:48.000Z",
+ "eos-rpc": "2026-08-18T23:06:04.000Z",
+ "ethereum-classic-rpc": "2026-08-05T14:29:04.000Z",
"ethereum-rpc": "2026-07-03T14:33:15.000Z",
+ "etherlink-rpc": "2026-08-05T14:29:04.000Z",
"evm-block-builders": "2026-07-16T16:01:49.000Z",
"evm-quote-latency": "2026-06-26T12:41:12.000Z",
"explorer-chain-coverage": "2026-07-07T14:11:20.000Z",
+ "fantom-rpc": "2026-08-05T14:29:04.000Z",
+ "fetchhub-rpc": "2026-08-28T18:56:40.000Z",
+ "filecoin-rpc": "2026-08-04T12:27:13.000Z",
+ "flare-rpc": "2026-08-04T08:05:53.000Z",
+ "flow-rpc": "2026-08-18T20:43:17.000Z",
"fraxtal-rpc": "2026-07-03T22:44:42.000Z",
+ "fuse-rpc": "2026-08-04T08:05:53.000Z",
"gas-estimation": "2026-05-20T18:43:56.000Z",
"gnosis-rpc": "2026-07-03T22:44:42.000Z",
- "hyperlane-message-latency": "2026-07-25T23:43:50.000Z",
+ "gram-rpc": "2026-08-18T18:11:28.000Z",
+ "gravity-rpc": "2026-08-04T15:53:48.000Z",
+ "haqq-rpc": "2026-08-05T14:29:04.000Z",
+ "harmony-rpc": "2026-08-04T15:53:48.000Z",
+ "hedera-rpc": "2026-08-18T20:43:17.000Z",
+ "hemi-rpc": "2026-08-03T21:50:42.000Z",
+ "hydration-rpc": "2026-08-05T14:29:04.000Z",
"hyperliquid-frontends": "2026-06-02T16:07:35.000Z",
"hyperliquid-hip3-deployers": "2026-06-10T23:44:26.000Z",
"hyperliquid-rpc": "2026-07-24T02:36:11.000Z",
+ "icon-rpc": "2026-08-30T12:41:05.000Z",
"immutable-rpc": "2026-07-26T14:46:53.000Z",
- "indexing-freshness": "2026-07-07T21:42:57.000Z",
"injective-rpc": "2026-07-24T12:35:13.000Z",
"ink-rpc": "2026-07-24T12:35:13.000Z",
+ "interlay-rpc": "2026-08-27T23:39:14.000Z",
+ "iotex-rpc": "2026-08-04T15:53:48.000Z",
+ "juno-rpc": "2026-08-04T15:53:48.000Z",
"kaia-rpc": "2026-07-24T12:35:13.000Z",
+ "kava-rpc": "2026-08-02T21:06:36.000Z",
+ "keyed-rpc-robinhood": "2026-08-27T12:05:50.000Z",
+ "kusama-rpc": "2026-08-05T14:29:04.000Z",
"l1-finality": "2026-05-06T19:31:46.000Z",
"l2-block-time": "2026-05-19T18:59:50.000Z",
"layerzero-message-latency": "2026-07-25T23:35:46.000Z",
"linea-rpc": "2026-07-03T14:33:15.000Z",
+ "lisk-rpc": "2026-08-02T21:30:41.000Z",
+ "litecoin-rpc": "2026-08-27T23:39:14.000Z",
+ "manta-rpc": "2026-08-03T21:51:01.000Z",
"mantle-rpc": "2026-07-03T14:33:15.000Z",
+ "mantrachain-rpc": "2026-08-29T17:15:56.000Z",
"megaeth-rpc": "2026-07-08T07:36:17.000Z",
+ "memecoin-platforms": "2026-08-11T14:35:12.000Z",
+ "merlin-rpc": "2026-08-18T23:06:04.000Z",
"metadata-coverage": "2026-05-01T15:22:33.000Z",
+ "metis-rpc": "2026-08-03T21:50:42.000Z",
"mev-protect-rpc": "2026-07-10T16:15:14.000Z",
"mode-rpc": "2026-07-26T14:46:53.000Z",
"monad-rpc": "2026-07-08T07:36:17.000Z",
"moonbeam-rpc": "2026-07-03T22:44:42.000Z",
+ "moonriver-rpc": "2026-08-03T21:50:42.000Z",
+ "morph-rpc": "2026-08-03T21:51:01.000Z",
+ "multiversx-rpc": "2026-08-18T20:43:17.000Z",
+ "near-rpc": "2026-08-18T20:43:17.000Z",
+ "neo-rpc": "2026-08-18T20:43:17.000Z",
+ "neon-rpc": "2026-08-18T23:06:04.000Z",
"network-fees": "2026-06-02T13:30:08.000Z",
"neutron-rpc": "2026-07-24T12:35:13.000Z",
"nft-collection-metadata": "2026-06-15T15:07:26.000Z",
+ "nibiru-rpc": "2026-08-04T15:53:48.000Z",
+ "nolus-rpc": "2026-08-04T15:53:48.000Z",
+ "oasis-emerald-rpc": "2026-08-04T15:53:48.000Z",
+ "oasis-sapphire-rpc": "2026-08-04T15:53:48.000Z",
+ "oktc-rpc": "2026-08-18T23:06:04.000Z",
"opbnb-rpc": "2026-07-24T12:35:13.000Z",
"optimism-rpc": "2026-07-03T14:33:15.000Z",
"oracle-deviation": "2026-05-21T17:54:38.000Z",
+ "oraichain-rpc": "2026-08-05T14:29:04.000Z",
"osmosis-rpc": "2026-07-23T18:06:49.000Z",
+ "peaq-rpc": "2026-08-05T14:29:04.000Z",
+ "perp-active-markets": "2026-08-02T19:12:52.000Z",
+ "perp-amm-volume-share": "2026-08-17T17:28:33.000Z",
+ "perp-asset-breadth": "2026-08-04T08:48:16.000Z",
+ "perp-capital-efficiency": "2026-08-04T09:00:03.000Z",
+ "perp-cost-slope": "2026-08-02T20:16:43.000Z",
"perp-execution-quality": "2026-07-03T15:11:55.000Z",
"perp-fees": "2026-05-07T14:44:23.000Z",
"perp-funding": "2026-06-11T16:01:46.000Z",
"perp-funding-stability": "2026-06-24T13:14:37.000Z",
- "perp-open-interest": "2026-06-24T13:14:37.000Z",
+ "perp-liq-rate": "2026-08-15T15:06:23.000Z",
+ "perp-mark-price-lag": "2026-08-02T20:16:43.000Z",
+ "perp-pe-ratio": "2026-08-23T01:05:02.000Z",
+ "perp-protocol-longevity": "2026-08-02T20:16:43.000Z",
"perp-volume-share": "2026-06-24T13:14:37.000Z",
+ "persistence-rpc": "2026-08-04T15:53:48.000Z",
+ "plume-rpc": "2026-08-04T15:53:48.000Z",
"pm-api-latency": "2026-06-16T11:48:27.000Z",
"pm-rate-limits": "2026-06-16T11:48:27.000Z",
+ "pm-resolution-delay": "2026-07-29T15:41:53.000Z",
"pm-ws-latency": "2026-07-28T21:59:34.000Z",
"polkadot-rpc": "2026-07-15T17:14:34.000Z",
"polygon-rpc": "2026-07-03T14:33:15.000Z",
- "polymarket-resolution-delay": "2026-06-16T11:48:27.000Z",
+ "polygon-zkevm-rpc": "2026-08-04T08:05:53.000Z",
"portfolio-chain-coverage": "2026-07-06T13:47:16.000Z",
+ "pulsechain-rpc": "2026-08-05T14:29:04.000Z",
+ "quicksilver-rpc": "2026-08-04T15:53:48.000Z",
+ "regen-rpc": "2026-08-04T15:53:48.000Z",
+ "reya-rpc": "2026-08-04T15:53:48.000Z",
"ronin-rpc": "2026-07-26T14:46:53.000Z",
+ "rootstock-rpc": "2026-08-03T21:50:42.000Z",
"rpc-capabilities": "2026-05-20T18:43:56.000Z",
- "rpc-keyed-latency": "2026-07-07T16:12:22.000Z",
"rpc-reliability": "2026-07-16T16:01:49.000Z",
"rwa-yield-accuracy": "2026-07-24T13:56:11.000Z",
"scroll-rpc": "2026-07-03T14:33:15.000Z",
"sei-rpc": "2026-07-26T14:46:53.000Z",
+ "sentinel-rpc": "2026-08-28T18:56:40.000Z",
+ "shentu-rpc": "2026-08-29T17:15:56.000Z",
+ "solana-avg-trade-size": "2026-08-13T00:01:53.000Z",
+ "solana-dex-volume": "2026-08-12T19:45:32.000Z",
+ "solana-launchpad-wars": "2026-08-11T14:35:34.000Z",
"solana-rpc": "2026-07-12T15:37:15.000Z",
+ "solana-trading-platform-wars": "2026-08-11T15:07:42.000Z",
+ "solana-unique-traders": "2026-08-13T00:44:06.000Z",
"soneium-rpc": "2026-07-03T22:44:42.000Z",
+ "songbird-rpc": "2026-08-05T14:29:04.000Z",
"sonic-rpc": "2026-07-03T22:44:42.000Z",
"stablecoin-peg": "2026-05-20T18:43:56.000Z",
"stablecoin-peg-usdt-anchored": "2026-05-20T21:20:02.000Z",
+ "starknet-rpc": "2026-08-18T15:23:04.000Z",
+ "stellar-rpc": "2026-08-18T15:23:04.000Z",
+ "story-rpc": "2026-08-03T21:51:01.000Z",
+ "stride-rpc": "2026-08-04T15:53:48.000Z",
+ "sui-rpc": "2026-08-18T18:11:28.000Z",
+ "swellchain-rpc": "2026-08-02T21:30:41.000Z",
"taiko-rpc": "2026-07-03T22:44:42.000Z",
+ "telos-rpc": "2026-08-05T14:29:04.000Z",
+ "terra-rpc": "2026-08-04T15:53:48.000Z",
+ "tezos-rpc": "2026-08-18T23:06:04.000Z",
+ "thundercore-rpc": "2026-08-18T23:06:04.000Z",
"token-deployment-cost": "2026-06-09T15:34:12.000Z",
"token-quote-coverage": "2026-07-24T23:30:18.000Z",
"tokenized-stock-peg": "2026-07-14T12:09:41.000Z",
"tokenized-stock-weekend-drift": "2026-07-14T12:09:41.000Z",
+ "trading-platform-wallets": "2026-08-21T14:51:39.000Z",
"tron-rpc": "2026-07-24T02:36:11.000Z",
"unichain-rpc": "2026-07-03T22:44:42.000Z",
+ "union-rpc": "2026-08-29T16:02:13.000Z",
"usdy-nav-basis": "2026-07-14T16:34:18.000Z",
"validator-yield": "2026-05-21T17:54:38.000Z",
+ "validator-yield-hyperliquid": "2026-07-29T13:49:50.000Z",
+ "vana-rpc": "2026-08-04T15:53:48.000Z",
+ "vechain-rpc": "2026-08-18T23:06:04.000Z",
+ "viction-rpc": "2026-08-18T23:06:04.000Z",
"wallet-labels-coverage": "2026-05-08T20:37:49.000Z",
+ "warden-rpc": "2026-08-05T14:29:04.000Z",
+ "waves-rpc": "2026-08-18T23:06:04.000Z",
+ "wax-rpc": "2026-08-18T23:06:04.000Z",
+ "wemix-rpc": "2026-08-05T14:29:04.000Z",
"world-chain-rpc": "2026-07-24T12:35:13.000Z",
"wormhole-vaa-latency": "2026-07-24T15:13:59.000Z",
"ws-head-latency-base": "2026-07-16T11:03:31.000Z",
"ws-head-latency-ethereum": "2026-07-16T12:20:29.000Z",
"ws-head-latency-solana": "2026-07-16T11:03:31.000Z",
+ "xdc-rpc": "2026-08-04T15:53:48.000Z",
+ "xlayer-rpc": "2026-08-04T08:05:53.000Z",
+ "xrp-rpc": "2026-08-18T18:11:28.000Z",
"xstocks-peg": "2026-07-14T16:34:18.000Z",
- "zksync-rpc": "2026-07-03T22:44:42.000Z"
+ "zcash-rpc": "2026-08-27T09:39:37.000Z",
+ "zetachain-rpc": "2026-08-05T14:29:04.000Z",
+ "zircuit-rpc": "2026-08-04T15:53:48.000Z",
+ "zksync-rpc": "2026-07-03T22:44:42.000Z",
+ "zora-rpc": "2026-08-02T21:06:36.000Z"
}
diff --git a/src/data/provider-registry.ts b/src/data/provider-registry.ts
index afdb90338..2e5701a8f 100644
--- a/src/data/provider-registry.ts
+++ b/src/data/provider-registry.ts
@@ -115,6 +115,47 @@ export const PROVIDER_REGISTRY: Record = {
"Independent crypto market data API. Token prices, OHLCV, exchange tickers, and contract/platform lookups across 300+ supported chains. Public free tier with no auth.",
twitter: "@coinpaprika",
},
+ serialized: {
+ url: "https://serialized.xyz",
+ description:
+ "Onchain market data and token security API for trading apps. Own indexers across 18 EVM chains plus Solana, REST plus a single-connection WebSocket for trades and token updates.",
+ longDescription:
+ "Serialized runs its own indexers rather than reselling a third-party pipeline, and scopes coverage deliberately narrow: 18 EVM chains plus Solana, weighted toward venues where launchpad and memecoin flow actually lands (Base, BNB, Solana, HyperEVM, Abstract, Monad, MegaETH, Plasma, Tempo, Arc, Robinhood Chain). The surface splits into market data (token details, price, stats, OHLCV from 1s, trades tape, pools, screener, Axiom-style launchpad lifecycle filters), wallet analytics (positions, PnL, closed positions, transfers, funding, an identity graph behind wallet profiles), and token security (holder concentration, sniper and bundler share, LP burn and lock state, plus a separate async contract-audit engine). Streams run over one WebSocket at wss://api.serialized.xyz/v1/stream, billed per connection-minute rather than per message. On Base the trade tape emits preconfirmed flashblock trades ahead of the block timestamp.",
+ twitter: "@serializedaudit",
+ docs: "https://docs.serialized.xyz",
+ chains: [
+ "ethereum",
+ "base",
+ "bnb",
+ "arbitrum",
+ "avalanche",
+ "solana",
+ "abstract",
+ "hyperevm",
+ "ink",
+ "story",
+ "xlayer",
+ "plasma",
+ "unichain",
+ "monad",
+ "megaeth",
+ "tempo",
+ "robinhood",
+ "arc",
+ "stable",
+ ],
+ features: [
+ "Token details, price, windowed stats and OHLCV from 1s to 1M",
+ "Trades tape with maker resolution, sniper/pro-trader/wash badges",
+ "Wallet positions, realized and unrealized PnL, funding and transfers",
+ "Token security: holder concentration, sniper and bundler share, LP burn and lock",
+ "Async contract audit engine across 18 EVM chains",
+ "Launchpad lifecycle filters over ~90 launchpads",
+ "WebSocket streams for trades, token updates and pool updates",
+ ],
+ pricing:
+ "Credit-metered. Free 15k credits/month, Starter 150k, Growth 2M, Enterprise unlimited. Most endpoints cost 1 credit; token/security costs 10 and a fresh contract audit 750.",
+ },
dexpaprika: {
url: "https://dexpaprika.com",
description:
@@ -2270,8 +2311,19 @@ export const PROVIDER_REGISTRY: Record = {
nownodes: {
url: "https://nownodes.io",
description:
- "NOWNodes is a multi-chain node provider covering Gram (TON), Bitcoin, Ethereum and 50+ other chains. Offers API-key-based access with a free trial tier.",
- twitter: "@NOWNodes_io",
+ "Multi-chain node provider covering 110+ blockchains. Key-based REST and WebSocket RPC plus block explorers, with keyless public endpoints on a handful of chains (Ethereum, BNB, Bitcoin) rate-limited to 5 requests per second.",
+ twitter: "@NOWNodes",
+ docs: "https://docs.nownodes.io",
+ chains: ["ethereum", "bnb", "bitcoin", "litecoin", "dogecoin", "gram", "xrp", "tron"],
+ features: [
+ "110+ chains via a single API key",
+ "REST and WebSocket JSON-RPC endpoints",
+ "Block explorers for major UTXO chains",
+ "Keyless public endpoints on Ethereum, BNB and Bitcoin (5 RPS)",
+ "Free trial tier, then usage-based paid plans",
+ ],
+ pricing:
+ "Free trial key, then paid plans by request volume. Public no-key endpoints exist on select chains at 5 requests per second.",
},
tatum: {
url: "https://tatum.io",
diff --git a/src/lib/chains.ts b/src/lib/chains.ts
index d5880fda0..2973bf332 100644
--- a/src/lib/chains.ts
+++ b/src/lib/chains.ts
@@ -45,6 +45,24 @@ export type ChainEntry = {
};
export const CHAINS: ChainEntry[] = [
+ {
+ slug: "bitcoin",
+ label: "Bitcoin",
+ category: "L1",
+ nativeSymbol: "BTC",
+ website: "https://bitcoin.org",
+ description:
+ "The original proof-of-work UTXO blockchain. SHA-256 mining, ~10-minute block target, Bitcoin Core JSON-RPC (getblockcount) for chain-tip reads.",
+ },
+ {
+ slug: "lukso",
+ label: "LUKSO",
+ category: "L1",
+ nativeSymbol: "LYX",
+ website: "https://lukso.network",
+ description:
+ "EVM Layer 1 (chain ID 42) for Universal Profiles and the LSP standards. Standard Ethereum JSON-RPC surface; proof-of-stake consensus.",
+ },
{
slug: "ethereum",
label: "Ethereum",
diff --git a/src/lib/citation.ts b/src/lib/citation.ts
index 672811f94..cc67f1c27 100644
--- a/src/lib/citation.ts
+++ b/src/lib/citation.ts
@@ -53,7 +53,13 @@ export function citationCandidates(b: Benchmark): ProviderResult[] {
* full field. Applies the same reliability + insufficient-sample
* filters as `leader()` so a document that names X as leader ranks X
* first in its own list. Sort direction honors the bench's
- * `higherIsBetter` flag. */
+ * `higherIsBetter` flag.
+ *
+ * Ranks on the value alone, deliberately. A bench whose cross-chain
+ * aggregate would otherwise reward an uncontested chain fixes that by
+ * declaring `score_scope: contested_chains` in its spec, which narrows
+ * the value itself (see materialize/load.ts) rather than sorting on a
+ * key the reader cannot see in the column. */
export function rankedCandidates(b: Benchmark): ProviderResult[] {
return [...citationCandidates(b)].sort((a, c) =>
b.higherIsBetter ? c.ms.p50 - a.ms.p50 : a.ms.p50 - c.ms.p50,
@@ -302,9 +308,12 @@ export function isInsufficient(b: InsufficientCheckInput): boolean {
// benches as insufficient on /api/citable while /api/stat returned
// live values for the same slug. The liveResults length and p50
// finiteness checks below already catch the genuine empty case.
+ // Use isFinite rather than > 0 so deviation benches (where a negative
+ // p50 is valid data, e.g. rwa-yield-accuracy reporting -6 bps) are not
+ // mis-classified as insufficient.
const live = b.results.filter(
- (r) => r.availability !== "unavailable" && r.ms.p50 > 0,
+ (r) => r.availability !== "unavailable" && Number.isFinite(r.ms.p50) && r.ms.p50 !== 0,
);
if (live.length === 0) return true;
- return live.every((r) => !Number.isFinite(r.ms.p50) || r.ms.p50 <= 0);
+ return live.every((r) => !Number.isFinite(r.ms.p50));
}
diff --git a/src/lib/format.test.ts b/src/lib/format.test.ts
index 9943315c4..fd0fef5d4 100644
--- a/src/lib/format.test.ts
+++ b/src/lib/format.test.ts
@@ -1,11 +1,5 @@
import { describe, expect, test } from "bun:test";
-import {
- fmtUnit,
- fmtValue,
- fmtAsOfUtc,
- unitSuffix,
- valueInDeclaredUnit,
-} from "./format";
+import { fmtAsOfUtc, fmtUnit, fmtValue, unitSuffix, valueInDeclaredUnit, valueWindowLabel } from "./format";
describe("fmtUnit — ms (default latency unit)", () => {
test("integer ms", () => expect(fmtUnit(150, "ms")).toBe("150 ms"));
@@ -149,3 +143,30 @@ describe("fmtValue (number only, no unit word)", () => {
test("keeps K/M compact notation", () => expect(fmtValue(1_500_000, "count")).toBe("1.50M"));
test("strips % from pct", () => expect(fmtValue(5, "pct")).toBe("5.00"));
});
+
+describe("valueWindowLabel", () => {
+ test("a real percentile keeps the p50 badge", () => {
+ // metadata-coverage: quantile_over_time(0.50, …) vs (0.90, …), so the
+ // number really is the median of hourly coverage rates.
+ expect(valueWindowLabel({ unit: "pct", hasDistribution: true })).toBe("p50 · 24h");
+ expect(valueWindowLabel({ unit: "ms", hasDistribution: true })).toBe("p50 · 24h");
+ });
+
+ test("a single measurement drops the percentile claim", () => {
+ // dex-network-coverage: one instant gauge repeated across p50/p90/p99.
+ // "19 DEX-indexed chains, p50 over 24h" claims a distribution that was
+ // never computed.
+ expect(valueWindowLabel({ unit: "count", hasDistribution: false })).toBe("24h");
+ expect(valueWindowLabel({ unit: "usd", hasDistribution: false })).toBe("24h");
+ });
+
+ test("rolling ratios read as an average, not a percentile", () => {
+ // wallet-labels-coverage: sum(increase)/sum(increase) over the window.
+ expect(valueWindowLabel({ unit: "pct", hasDistribution: false })).toBe("24h avg");
+ expect(valueWindowLabel({ unit: "bps", hasDistribution: false })).toBe("24h avg");
+ });
+
+ test("an unknown shape stays on the historical label", () => {
+ expect(valueWindowLabel({ unit: "ms" })).toBe("p50 · 24h");
+ });
+});
diff --git a/src/lib/format.ts b/src/lib/format.ts
index 0e522411d..8f93e71cb 100644
--- a/src/lib/format.ts
+++ b/src/lib/format.ts
@@ -225,3 +225,27 @@ function formatPercent(pct: number): string {
if (abs >= 0.001) return `${pct.toFixed(4)}%`;
return `${pct.toFixed(5)}%`;
}
+
+/**
+ * Label rendered under a benchmark's headline value.
+ *
+ * "p50 · 24h" is only honest when a percentile was actually computed. On a
+ * coverage or count bench the provider block repeats one expression for
+ * p50, p90 and p99, so the number is a single measurement and the badge
+ * would be claiming a distribution that does not exist: "19 DEX-indexed
+ * chains, p50 over 24h" reads as nonsense because it is.
+ *
+ * Percentages and basis points are rolling ratios over the window, so they
+ * get "24h avg". Everything else with a single measurement gets the bare
+ * window.
+ */
+export function valueWindowLabel(bench: {
+ unit: string;
+ hasDistribution?: boolean;
+}): string {
+ if (bench.hasDistribution !== false) return "p50 · 24h";
+ if (bench.unit === "pct" || bench.unit === "bps" || bench.unit === "bp") {
+ return "24h avg";
+ }
+ return "24h";
+}
diff --git a/src/lib/logo-manifest.ts b/src/lib/logo-manifest.ts
index 9c3f74ba7..9f44470b7 100644
--- a/src/lib/logo-manifest.ts
+++ b/src/lib/logo-manifest.ts
@@ -99,6 +99,7 @@ const RAW: Record = {
// ─── Providers ───
mobula: "/logos/mobula.svg",
+ serialized: "/logos/serialized.svg",
codex: "/logos/codex.svg",
polymarket: "/logos/polymarket.png",
"polymarket-us": "/logos/polymarket.png",
@@ -182,6 +183,8 @@ const RAW: Record = {
// ─── Gram/TON providers (bench 215) ───
toncenter: "/logos/ton.svg",
nownodes: "/logos/nownodes.svg",
+ lukso: "/logos/lukso.svg",
+ "lukso-official": "/logos/lukso.svg",
tatum: "/logos/tatum.svg",
uniblock: "/logos/uniblock.svg",
// ─── Wave-2 non-EVM chains (benches 216-221) ───
diff --git a/src/lib/materialize/contested-scope.test.ts b/src/lib/materialize/contested-scope.test.ts
new file mode 100644
index 000000000..8d8475ed6
--- /dev/null
+++ b/src/lib/materialize/contested-scope.test.ts
@@ -0,0 +1,82 @@
+import { describe, expect, test } from "bun:test";
+import { applyContestedChainScope } from "./load";
+import type { ProviderResult } from "@/types/benchmark";
+
+function r(slug: string, p50: number): ProviderResult {
+ return {
+ slug,
+ name: slug,
+ ms: { p50, p90: p50, p99: p50, mean: p50 },
+ successRate: 100,
+ availability: "live",
+ };
+}
+
+// Bench 008 as it shipped: stellar, xrp and bitcoin carry one measured
+// provider each, so the cross-chain average put two single-chain providers
+// first and second above one that led four contested chains.
+function fixture() {
+ const results = [
+ r("stellarexpert", 80.08),
+ r("xrpscan", 79.84),
+ r("serialized", 76.98),
+ r("mobula", 46.75),
+ ];
+ const providersPerChain: Record = {
+ ethereum: ["serialized", "mobula"],
+ base: ["serialized", "mobula"],
+ solana: ["serialized", "mobula"],
+ stellar: ["stellarexpert"],
+ xrp: ["xrpscan"],
+ };
+ const valuesByChain: Record> = {
+ ethereum: { serialized: 96.84, mobula: 50.0 },
+ base: { serialized: 76.36, mobula: 40.0 },
+ solana: { serialized: 57.79, mobula: 30.0 },
+ stellar: { stellarexpert: 80.08 },
+ xrp: { xrpscan: 79.84 },
+ };
+ return { results, providersPerChain, valuesByChain };
+}
+
+describe("contested-chain scoring", () => {
+ test("the value becomes the mean over contested chains", () => {
+ const { results, providersPerChain, valuesByChain } = fixture();
+ applyContestedChainScope(results, providersPerChain, valuesByChain);
+ const s = results.find((x) => x.slug === "serialized")!;
+ // (96.84 + 76.36 + 57.79) / 3
+ expect(s.ms.p50).toBeCloseTo(76.9967, 3);
+ expect(s.ms.mean).toBeCloseTo(76.9967, 3);
+ expect(results.find((x) => x.slug === "mobula")!.ms.p50).toBeCloseTo(40, 6);
+ });
+
+ test("a provider with no contested chain drops out of the ranked field", () => {
+ const { results, providersPerChain, valuesByChain } = fixture();
+ applyContestedChainScope(results, providersPerChain, valuesByChain);
+ expect(results.find((x) => x.slug === "stellarexpert")!.availability).toBe(
+ "unavailable",
+ );
+ expect(results.find((x) => x.slug === "xrpscan")!.availability).toBe(
+ "unavailable",
+ );
+ expect(results.find((x) => x.slug === "serialized")!.availability).toBe("live");
+ });
+
+ test("no contested chain at all leaves every value untouched", () => {
+ const { results, valuesByChain } = fixture();
+ applyContestedChainScope(
+ results,
+ { stellar: ["stellarexpert"], xrp: ["xrpscan"] },
+ valuesByChain,
+ );
+ expect(results.find((x) => x.slug === "serialized")!.ms.p50).toBe(76.98);
+ expect(results.every((x) => x.availability === "live")).toBe(true);
+ });
+
+ test("an already unavailable provider is left alone", () => {
+ const { results, providersPerChain, valuesByChain } = fixture();
+ results[0].availability = "unavailable";
+ applyContestedChainScope(results, providersPerChain, valuesByChain);
+ expect(results[0].ms.p50).toBe(80.08);
+ });
+});
diff --git a/src/lib/materialize/load.ts b/src/lib/materialize/load.ts
index 5b14517c5..d1fd65167 100644
--- a/src/lib/materialize/load.ts
+++ b/src/lib/materialize/load.ts
@@ -145,6 +145,7 @@ export function buildEditorial(
findings: spec.findings,
source: spec.source,
dimensions: spec.dimensions,
+ aggregateFilters: spec.aggregate_filters,
ledgerColumns: spec.ledger_columns,
providerNotes: spec.provider_notes,
};
@@ -164,9 +165,21 @@ export async function specToBenchmark(
): Promise {
const editorial = buildEditorial(spec);
- const activeLabels = activeFilterLabels(options);
- const isFiltered = Object.keys(activeLabels).length > 0;
- const filteredSpec = isFiltered ? applyDimensionsToSpec(spec, activeLabels) : spec;
+ // Merge spec-declared aggregate defaults UNDER the reader's filters:
+ // an explicit ?region= / tab selection always wins over the pin. The
+ // unfiltered-view semantics below (provider augmentation, "All" copy)
+ // key on the reader's filters only, so a pinned aggregate still reads
+ // as the bench's headline view rather than a filtered slice.
+ const merged: BenchmarkFilters = {
+ ...((spec.aggregate_filters ?? {}) as BenchmarkFilters),
+ ...options,
+ };
+ const activeLabels = activeFilterLabels(merged);
+ const isFiltered = Object.keys(activeFilterLabels(options)).length > 0;
+ const filteredSpec =
+ Object.keys(activeLabels).length > 0
+ ? applyDimensionsToSpec(spec, activeLabels)
+ : spec;
const live = await tryLoadLive(filteredSpec, isFiltered);
if (live) {
@@ -273,15 +286,26 @@ export async function specToBenchmark(
const chainSpec = applyDimensionsToSpec(spec, { chain });
const chainLive = await tryLoadLive(chainSpec, true);
if (!chainLive) {
- return [chain, undefined, undefined, [] as string[]] as const;
+ return [
+ chain,
+ undefined,
+ undefined,
+ [] as string[],
+ {} as Record,
+ ] as const;
}
for (const r of chainLive.results) {
if (!r.unresponsive) r.availability = "live";
}
const liveForChain = liveProviderResults(chainLive.results);
const slugs = liveForChain.map((r) => r.slug);
+ // Per-provider value on this chain. Kept (not just the leader)
+ // so `score_scope: contested_chains` can rebuild a value from
+ // the chains a provider was actually compared on.
+ const values: Record = {};
+ for (const r of liveForChain) values[r.slug.toLowerCase()] = r.ms.p50;
if (liveForChain.length === 0) {
- return [chain, undefined, undefined, slugs] as const;
+ return [chain, undefined, undefined, slugs, values] as const;
}
const sorted = [...liveForChain].sort((a, b) =>
spec.higher_is_better ? b.ms.p50 - a.ms.p50 : a.ms.p50 - b.ms.p50,
@@ -291,20 +315,27 @@ export async function specToBenchmark(
sorted[0],
sorted[sorted.length - 1],
slugs,
+ values,
] as const;
}),
);
const bests: Record = {};
const worsts: Record = {};
const providers: Record = {};
- for (const [chain, leader, trailer, slugs] of perChainEntries) {
+ const valuesByChain: Record> = {};
+ for (const [chain, leader, trailer, slugs, values] of perChainEntries) {
if (leader) bests[chain] = leader;
if (trailer) worsts[chain] = trailer;
if (slugs.length > 0) providers[chain] = slugs;
+ valuesByChain[chain] = values;
}
if (Object.keys(bests).length > 0) bestPerChain = bests;
if (Object.keys(worsts).length > 0) worstPerChain = worsts;
if (Object.keys(providers).length > 0) providersPerChain = providers;
+
+ if (spec.score_scope === "contested_chains") {
+ applyContestedChainScope(live.results, providers, valuesByChain);
+ }
}
// Exact per-cell rankings (chain × region) from the spec's single
@@ -368,6 +399,50 @@ export async function specToBenchmark(
return draftBenchmark(spec, editorial);
}
+/**
+ * Rewrite each provider's headline value as its mean over the **contested**
+ * chains of the bench: those where at least two providers reported data.
+ * A provider with no contested chain is marked unavailable, which takes it
+ * out of `liveResults` and therefore out of every ranked surface, while
+ * leaving it visible on its own chain tab.
+ *
+ * Why the value and not the sort order: a cross-chain aggregate is a mix
+ * rather than a comparison, so it credits a provider for the chains it
+ * happens to be measured on. Narrowing the number keeps one quantity on
+ * screen and the ordering follows from it. Ranking on a separate key while
+ * still displaying the wide aggregate produced a column that did not
+ * descend (80% shown at rank 4, a 74 ms leader shown at rank 3).
+ *
+ * The residual limitation, stated in the methodology: providers are still
+ * averaged over different subsets of the contested chains, since they do
+ * not all cover the same ones. It removes the uncontested win, not every
+ * difference in chain mix. An unweighted mean is used so a chain counts
+ * once regardless of how many samples it carries.
+ */
+export function applyContestedChainScope(
+ results: ProviderResult[],
+ providersPerChain: Record,
+ valuesByChain: Record>,
+): void {
+ const contested = Object.keys(providersPerChain).filter(
+ (chain) => (providersPerChain[chain]?.length ?? 0) >= 2,
+ );
+ if (contested.length === 0) return;
+ for (const r of results) {
+ if (r.availability === "unavailable") continue;
+ const slug = r.slug.toLowerCase();
+ const vals = contested
+ .map((chain) => valuesByChain[chain]?.[slug])
+ .filter((v): v is number => typeof v === "number" && v > 0);
+ if (vals.length === 0) {
+ r.availability = "unavailable";
+ continue;
+ }
+ const mean = vals.reduce((a, b) => a + b, 0) / vals.length;
+ r.ms = { p50: mean, p90: mean, p99: mean, mean };
+ }
+}
+
function activeFilterLabels(opts: BenchmarkFilters): Record {
const out: Record = {};
for (const [k, v] of Object.entries(opts)) {
diff --git a/src/lib/materialize/store.ts b/src/lib/materialize/store.ts
index 046d4b967..3efa98646 100644
--- a/src/lib/materialize/store.ts
+++ b/src/lib/materialize/store.ts
@@ -205,3 +205,47 @@ export async function readMaterialized(
return null;
}
}
+
+// ─── Shared low-level access for other KV consumers ──────────────────
+// The speedtest contribution store (src/app/api/speedtest/*) reuses this
+// transport instead of dragging in a second Redis client.
+
+/** Single command. Exported alias of the private helper above. */
+export const redisCommand = redis;
+
+/** Batched commands. Uses the Upstash REST /pipeline endpoint (one HTTP
+ * round trip for N commands); falls back to sequential execution on the
+ * TCP transport (VPS worker context, where latency is sub-ms anyway). */
+export async function redisPipeline(
+ cmds: (string | number)[][],
+ timeoutMs = 6000,
+): Promise {
+ if (cmds.length === 0) return [];
+ if (tcpUrl()) {
+ const out: unknown[] = [];
+ for (const cmd of cmds) out.push(await redis(cmd, timeoutMs));
+ return out;
+ }
+ const c = creds();
+ if (!c) throw new Error("materialize store: no redis configured");
+ const res = await fetch(`${c.url}/pipeline`, {
+ method: "POST",
+ headers: {
+ Authorization: `Bearer ${c.token}`,
+ "Content-Type": "application/json",
+ },
+ body: JSON.stringify(cmds),
+ signal: AbortSignal.timeout(timeoutMs),
+ });
+ if (!res.ok) {
+ const detail = await res.text().catch(() => "");
+ throw new Error(
+ `materialize store: pipeline http ${res.status}: ${detail.slice(0, 200)}`,
+ );
+ }
+ const body = (await res.json()) as { result?: unknown; error?: string }[];
+ return body.map((b) => {
+ if (b.error) throw new Error(`materialize store: ${b.error}`);
+ return b.result;
+ });
+}
diff --git a/src/lib/providers.ts b/src/lib/providers.ts
index 8588a77d4..652e1db1a 100644
--- a/src/lib/providers.ts
+++ b/src/lib/providers.ts
@@ -177,6 +177,7 @@ export type ProviderAppearance = {
| "higherIsBetter"
| "status"
| "lastRunAt"
+ | "hasDistribution"
> & {
/** Chain dimension values from the spec, when present. Stored on the
* appearance so /products/[slug] can render chain-aware chips without
@@ -200,11 +201,12 @@ export type ProviderAppearance = {
result: ProviderResult;
rank: number;
totalRanked: number;
- /** Per-chain rank for this provider on this bench. Only populated when
- * the bench declares chain dimensions AND bestPerChain has at least one
- * entry. Key = chain slug (matching dimensions.chain[].value), value =
- * { rank, totalRanked } computed within the providers present on that
- * chain. Renderers can fall back to `rank` when this is empty. */
+ /** Per-chain leadership for this provider on this bench. Populated only
+ * for chains this provider *leads*, so `rank` is always 1 and an absent
+ * key means "does not lead here", never "ranked lower here". Key = chain
+ * slug (matching dimensions.chain[].value). `totalRanked` is how many
+ * providers were measured on that chain, or 0 when that set is unknown.
+ * Renderers fall back to the aggregate `rank` when this is empty. */
rankPerChain?: Record;
};
@@ -245,18 +247,23 @@ function rankProviders(b: Benchmark): ProviderResult[] {
}
/**
- * Compute per-chain rank for every provider on a bench. Bench must declare
- * `dimensions.chain` and have a non-empty `bestPerChain` for any rank to be
+ * Compute per-chain leadership for a bench. Bench must declare
+ * `dimensions.chain` and have a non-empty `bestPerChain` for anything to be
* recorded.
*
- * Approximation: spec.ts only stashes the *leader* per chain (one extra Prom
- * roundtrip per chain). To express other providers' rank-per-chain, we use a
- * coarse fallback: anyone present in the unfiltered `results` is ranked by
- * the bench's standard direction (lower-is-better or higher-is-better)
- * within the live result set, and the leader's slot is forcibly overridden
- * with rank 1 for that chain. This is a soft signal — the bench page chain
- * tabs are authoritative — but it is enough to flag chain-restricted
- * providers like GMGN as "#1 on Solana only" on /products/[slug].
+ * Leaders only, deliberately. spec.ts stashes the *leader* per chain (one
+ * extra Prom roundtrip per chain), and that is the only per-chain fact we
+ * actually hold. An earlier version also emitted ranks for non-leaders by
+ * reusing the unfiltered aggregate order shifted by one slot; those chips
+ * rendered as "#3 on Solana" while being the provider's *global* rank with a
+ * chain label stuck on it, identical across every chain of the bench. That
+ * reads as a measurement and is not one, so it is gone: a provider now gets a
+ * chip for a chain only when it leads that chain.
+ *
+ * `totalRanked` is the number of providers actually measured on the chain,
+ * taken from `providersPerChain`. It is 0 when the bench has not stashed that
+ * set (older cached entries), and renderers must then omit the denominator
+ * rather than substitute the global count.
*/
function rankPerChainForBench(
b: Benchmark,
@@ -280,24 +287,14 @@ function rankPerChainForBench(
const presentSet = providersPerChain?.[chain.value]
? new Set(providersPerChain[chain.value].map((s) => s.toLowerCase()))
: undefined;
- const scoped = presentSet
- ? liveSorted.filter((r) => presentSet.has(r.slug.toLowerCase()))
- : liveSorted;
+ // Only claim a denominator when we know who was measured on this
+ // chain; scoped.length over the global list would be a different
+ // number wearing the same label.
+ const totalRanked = presentSet
+ ? liveSorted.filter((r) => presentSet.has(r.slug.toLowerCase())).length
+ : 0;
const perProvider = new Map();
- const leaderLc = leader.slug.toLowerCase();
- const leaderIdx = scoped.findIndex((r) => r.slug.toLowerCase() === leaderLc);
- scoped.forEach((r, idx) => {
- const lc = r.slug.toLowerCase();
- if (lc === leaderLc) {
- perProvider.set(lc, { rank: 1, totalRanked: scoped.length });
- return;
- }
- // Anyone ranked above the leader in the unfiltered set drops by one
- // slot here (since the leader skips ahead of them on this chain).
- const rankOnChain =
- leaderIdx !== -1 && idx < leaderIdx ? idx + 2 : idx + 1;
- perProvider.set(lc, { rank: rankOnChain, totalRanked: scoped.length });
- });
+ perProvider.set(leader.slug.toLowerCase(), { rank: 1, totalRanked });
out[chain.value] = perProvider;
}
return out;
@@ -375,6 +372,7 @@ async function buildProviders(): Promise {
higherIsBetter: b.higherIsBetter,
status: b.status,
lastRunAt: b.lastRunAt,
+ hasDistribution: b.hasDistribution,
chainDimensions: b.dimensions?.chain,
bestPerChain: benchBestPerChain,
regionDimensions: b.dimensions?.region,
diff --git a/src/lib/sitemap-builder.ts b/src/lib/sitemap-builder.ts
index d2d115c0d..c75c298d7 100644
--- a/src/lib/sitemap-builder.ts
+++ b/src/lib/sitemap-builder.ts
@@ -6,6 +6,8 @@ import { COMPARE_PAIRS } from "@/data/compare-pairs";
import { REMOVED_BENCH_SLUGS } from "@/middleware";
import { REMOVED_PRODUCT_SLUGS } from "@/lib/removed-benches";
import { isHlBuilderSlug } from "@/lib/hl-builder-stats";
+import { getSpecs } from "@/lib/spec";
+import { PROVIDER_REGISTRY } from "@/data/provider-registry";
import { PERP_PRODUCT_PILL_SLUGS } from "@/lib/perp-venue-context";
import { loadAllAlternatives } from "@/lib/alternatives";
import { loadAllAnswers } from "@/lib/answers";
@@ -147,6 +149,8 @@ function staticHubRoutes(catalogTs: Date): MetadataRoute.Sitemap {
{ url: `${SITE.url}/perps`, lastModified: catalogTs, changeFrequency: "hourly", priority: 0.9 },
{ url: `${SITE.url}/bridge`, lastModified: catalogTs, changeFrequency: "hourly", priority: 0.9 },
{ url: `${SITE.url}/mcp`, lastModified: pageMtime("mcp/page.tsx"), changeFrequency: "monthly", priority: 0.8 },
+ { url: `${SITE.url}/speedtest-rpc`, lastModified: pageMtime("speedtest-rpc/page.tsx"), changeFrequency: "monthly", priority: 0.8 },
+ { url: `${SITE.url}/rpc-map`, lastModified: pageMtime("rpc-map/page.tsx"), changeFrequency: "daily", priority: 0.8 },
{ url: `${SITE.url}/methodology`, lastModified: pageMtime("methodology/page.tsx"), changeFrequency: "monthly", priority: 0.7 },
{ url: `${SITE.url}/contribute`, lastModified: pageMtime("contribute/page.tsx"), changeFrequency: "monthly", priority: 0.7 },
{ url: `${SITE.url}/partners`, lastModified: pageMtime("partners/page.tsx"), changeFrequency: "monthly", priority: 0.7 },
@@ -275,9 +279,22 @@ async function buildFullSitemap(): Promise {
// Prom) so it's safe async — no OOM risk (unlike the old getProvider fan-out).
// It also catches dormant HL frontends missing from the Prom cohort that
// the worker couldn't filter without the spec provider list.
+ // Only list product pages this build can actually serve. The blob is
+ // produced by the worker from its own checkout, so it can name providers
+ // that a spec on THIS branch does not declare yet; /products/ then
+ // 404s and the deploy's sitemap smoke blocks the release (2026-09-08:
+ // /products/serialized, declared on dev, listed in prod's sitemap).
+ const declaredProviderSlugs = new Set();
+ for (const spec of await getSpecs()) {
+ for (const p of spec.providers ?? []) declaredProviderSlugs.add(p.slug);
+ }
+ for (const entry of Object.values(PROVIDER_REGISTRY)) {
+ if (entry.parent) declaredProviderSlugs.add(entry.parent);
+ }
const validatedSlugs = (
await Promise.all(
providerSlugs.map(async (slug) => {
+ if (!declaredProviderSlugs.has(slug)) return null;
if (CHAIN_BY_SLUG.has(slug)) return null;
if (hlBuilderSlugSet.has(slug)) return null;
if (await isHlBuilderSlug(slug)) return null;
diff --git a/src/lib/spec-schema.ts b/src/lib/spec-schema.ts
index 63b52a504..c795fe858 100644
--- a/src/lib/spec-schema.ts
+++ b/src/lib/spec-schema.ts
@@ -382,6 +382,48 @@ export const SpecSchema = z
})
.optional(),
+ /**
+ * Default dimension scope for the unfiltered ("aggregate") build.
+ * When set, the tier-A snapshot (sig "") is built with these labels
+ * injected into every query selector, so the bench's headline view,
+ * TL;DR, JSON-LD and OG all cite the scoped slice instead of an
+ * average across dimensions. A reader-applied filter (region tab,
+ * ?region= variant) always wins over these defaults. Keys must be
+ * dimension keys (region/chain/kind/venue); values follow the same
+ * PromQL label-value alphabet as dimension values.
+ * First use: keyed-rpc-robinhood pins region=sgp so the default view
+ * is the Singapore probe while the US-East tab stays available. */
+ /**
+ * How the headline value is scoped on a bench that declares
+ * `dimensions.chain`.
+ *
+ * Default (absent) keeps the cross-chain aggregate the Prom queries
+ * return. `contested_chains` narrows it to the chains where at least
+ * two providers reported data, and drops a provider that has none of
+ * those from the ranked field.
+ *
+ * The reason is that a cross-chain aggregate is a mix rather than a
+ * comparison: it rewards a provider for the chains it happens to be
+ * measured on. On wallet-labels-coverage that put StellarExpert and
+ * XRPScan first and second, each scored on a single chain no other
+ * provider reported, above a provider that led four contested ones.
+ *
+ * Opt-in per bench, and it describes a property rather than naming a
+ * bench: any bench whose chain set contains uncontested chains is a
+ * candidate. Excluded providers stay visible on their own chain tab.
+ */
+ score_scope: z.enum(["contested_chains"]).optional(),
+
+ aggregate_filters: z
+ .object({
+ chain: z.string().regex(/^[A-Za-z0-9][A-Za-z0-9_-]{0,63}$/).optional(),
+ region: z.string().regex(/^[A-Za-z0-9][A-Za-z0-9_-]{0,63}$/).optional(),
+ kind: z.string().regex(/^[A-Za-z0-9][A-Za-z0-9_-]{0,63}$/).optional(),
+ venue: z.string().regex(/^[A-Za-z0-9][A-Za-z0-9_-]{0,63}$/).optional(),
+ })
+ .strict()
+ .optional(),
+
/**
* Optional per-provider chip labels rendered next to the provider
* name in the ranking row. Keys are provider slugs, values are the
diff --git a/src/lib/spec.ts b/src/lib/spec.ts
index d873e51a2..d16703dae 100644
--- a/src/lib/spec.ts
+++ b/src/lib/spec.ts
@@ -131,8 +131,15 @@ export function overlayEditorial(stored: Benchmark, spec: Spec): Benchmark {
// too so newly added dimension values (e.g. region opts added to
// an existing bench) surface immediately on the compare matrix
// and the bench page filters without waiting on the materialise
- // worker to rewrite the snapshot.
- dimensions: spec.dimensions ?? stored.dimensions,
+ // worker to rewrite the snapshot. No `?? stored` fallback: the
+ // live YAML is the source of truth, so a spec that declares NO
+ // dimensions must not inherit tabs from a snapshot written by a
+ // worker running a divergent branch (keyed-rpc-robinhood: main is
+ // Singapore-only while dev/worker carries the region dims).
+ dimensions: spec.dimensions,
+ // Same source-of-truth rule as dimensions: the live YAML decides
+ // the aggregate pin, never the snapshot.
+ aggregateFilters: spec.aggregate_filters,
// provider_notes is a YAML editorial declaration: drives the
// per-provider chip rendered next to the name in the ranking row.
// Overlay so newly added notes surface immediately without waiting
@@ -144,6 +151,12 @@ export function overlayEditorial(stored: Benchmark, spec: Spec): Benchmark {
// the worker re-publishes the snapshot, otherwise a bench keeps
// ranking 3-sample providers as healthy through the materialise
// lag.
+ // Does this bench actually compute a distribution? A provider block
+ // that repeats one expression for p50, p90 and p99 has a single
+ // measurement, so the UI must not badge the value "p50". Coverage and
+ // count benches are all written that way; latency benches are not.
+ // Overlaid from the live YAML for the same reason as the fields above.
+ hasDistribution: specHasDistribution(spec) ?? stored.hasDistribution,
expectedN: spec.expected_n ?? stored.expectedN,
};
// Resolve `{{p50:slug}}`, `{{name:slug}}`, `{{best_name}}` etc. in the
@@ -154,7 +167,63 @@ export function overlayEditorial(stored: Benchmark, spec: Spec): Benchmark {
// hits overlayEditorial directly). The materialise sweep also passes
// its output through renderBenchmarkText; calling it here on the
// fallback path keeps the two paths consistent.
- return renderBenchmarkText(overlaid);
+ return renderBenchmarkText(confineToPinnedRegion(overlaid, spec));
+}
+
+// Region keys the harness emits vs the values specs declare: the Singapore
+// probe was historically labelled ap-southeast.
+const REGION_KEY_ALIASES: Record = { "ap-southeast": "sgp" };
+const canonRegionKey = (r: string) => REGION_KEY_ALIASES[r] ?? r;
+
+/**
+ * Single-origin surface guard. When THIS build declares no region
+ * dimension but pins one region via `aggregate_filters`, the stored
+ * snapshot can still carry every region the worker computed: the worker
+ * runs from its own checkout, which may declare more regions than this
+ * build does. Everything left on the object reaches client components and
+ * therefore the RSC payload, readable in view-source even though nothing
+ * renders it (2026-09-08: keyed-rpc-robinhood shipped its us-east series
+ * and ranks inside `variants` while the page only ever showed Singapore).
+ * Keep the pinned region's cells, drop the rest. Benches that declare
+ * region tabs, or pin nothing, are returned untouched.
+ */
+export function confineToPinnedRegion(b: Benchmark, spec: Spec): Benchmark {
+ const declared = spec.dimensions?.region ?? [];
+ const pinned = (spec.aggregate_filters as { region?: string } | undefined)?.region;
+ if (declared.length > 0 || typeof pinned !== "string") return b;
+ const keep = (r: string) => canonRegionKey(r) === canonRegionKey(pinned);
+ const byRegion = (m?: Record>) =>
+ m
+ ? Object.fromEntries(
+ Object.entries(m).map(([slug, per]) => [
+ slug,
+ Object.fromEntries(Object.entries(per).filter(([r]) => keep(r))),
+ ]),
+ )
+ : undefined;
+ return {
+ ...b,
+ extras: {
+ ...b.extras,
+ regions: Object.fromEntries(
+ Object.entries(b.extras.regions ?? {}).map(([slug, pts]) => [
+ slug,
+ (pts ?? []).filter((pt) => keep(pt.region)),
+ ]),
+ ),
+ seriesByRegion24h: byRegion(b.extras.seriesByRegion24h),
+ seriesByRegion7d: byRegion(b.extras.seriesByRegion7d),
+ seriesByRegion30d: byRegion(b.extras.seriesByRegion30d),
+ },
+ cellRanks: b.cellRanks
+ ? Object.fromEntries(
+ Object.entries(b.cellRanks).filter(([key]) => {
+ const r = key.split("|").pop() ?? "";
+ return r === "" || r === "all" || keep(r);
+ }),
+ )
+ : undefined,
+ };
}
// Strip lazy-loadable series fields from the cached Benchmark to bring
@@ -328,7 +397,8 @@ const loadBenchmarkUnfilteredCached = unstable_cache(
// v55: add ws-head-latency-robinhood (244) + keyed-rpc-robinhood (243). Bench SET grew.
// v56: drop 6 keyed-rpc benches (arbitrum/base/bnb/eth/polygon/solana), US-only robinhood.
// v57: drop rpc-keyed-latency bench.
- ["bench-unfiltered-v62", process.env.VERCEL_ENV === "production" ? "prod" : "all"],
+ // v63: score_scope contested_chains on bench 008 changes its provider values.
+ ["bench-unfiltered-v63", process.env.VERCEL_ENV === "production" ? "prod" : "all"],
{ revalidate: 300, tags: ["benchmarks"] },
);
@@ -541,7 +611,7 @@ const loadAllBenchmarksCached = unstable_cache(
// v52: token-quote-coverage flipped draft→live (see bench-unfiltered-v49).
// v53: lockstep with bench-unfiltered-v53 (Flashbots prune).
// v57: lockstep with bench-unfiltered-v54 (add 6 new chain benches 216-221).
- ["all-benchmarks-v57", process.env.VERCEL_ENV === "production" ? "prod" : "all"],
+ ["all-benchmarks-v58", process.env.VERCEL_ENV === "production" ? "prod" : "all"],
{ revalidate: 300, tags: ["benchmarks"] },
);
export const loadAllBenchmarks = cache(loadAllBenchmarksCached);
@@ -701,5 +771,25 @@ export const getSpecs = (): Promise => loadSpecs();
const loadSpecs = cache(loadSpecsUncached);
-
-
+/**
+ * True when at least one provider declares p50, p90 and p99 as expressions
+ * that are not all identical. Undefined when the spec declares no provider
+ * queries at all, so the caller can fall back to whatever the snapshot held
+ * rather than asserting "no distribution" about a spec it could not read.
+ */
+function specHasDistribution(spec: {
+ providers?: { queries?: { p50?: string; p90?: string; p99?: string } }[];
+}): boolean | undefined {
+ const providers = spec.providers ?? [];
+ let sawQueries = false;
+ for (const p of providers) {
+ const q = p.queries;
+ if (!q?.p50) continue;
+ sawQueries = true;
+ const distinct = new Set(
+ [q.p50, q.p90, q.p99].filter((x): x is string => !!x).map((x) => x.trim()),
+ );
+ if (distinct.size > 1) return true;
+ }
+ return sawQueries ? false : undefined;
+}
diff --git a/src/lib/speedtest/geo.ts b/src/lib/speedtest/geo.ts
new file mode 100644
index 000000000..255662601
--- /dev/null
+++ b/src/lib/speedtest/geo.ts
@@ -0,0 +1,109 @@
+/**
+ * Geohash helpers for the crowdsourced latency map.
+ *
+ * Why geohash: aggregation across zoom levels is free. A precision-4
+ * hash (~39x20 km cell, city scale, matching IP-geolocation accuracy)
+ * rolls up to precision-2 (~1250x625 km, continent scale) by simple
+ * string prefix truncation, so one stored resolution serves every zoom.
+ */
+
+const BASE32 = "0123456789bcdefghjkmnpqrstuvwxyz";
+
+/** Encode lat/lon to a geohash of the given precision (default 4). */
+export function geohashEncode(lat: number, lon: number, precision = 4): string {
+ let minLat = -90,
+ maxLat = 90,
+ minLon = -180,
+ maxLon = 180;
+ let hash = "";
+ let bit = 0;
+ let ch = 0;
+ let even = true;
+ while (hash.length < precision) {
+ if (even) {
+ const mid = (minLon + maxLon) / 2;
+ if (lon >= mid) {
+ ch = (ch << 1) + 1;
+ minLon = mid;
+ } else {
+ ch = ch << 1;
+ maxLon = mid;
+ }
+ } else {
+ const mid = (minLat + maxLat) / 2;
+ if (lat >= mid) {
+ ch = (ch << 1) + 1;
+ minLat = mid;
+ } else {
+ ch = ch << 1;
+ maxLat = mid;
+ }
+ }
+ even = !even;
+ bit += 1;
+ if (bit === 5) {
+ hash += BASE32[ch];
+ bit = 0;
+ ch = 0;
+ }
+ }
+ return hash;
+}
+
+/** Center point of a geohash cell. */
+export function geohashCenter(hash: string): { lat: number; lon: number } {
+ let minLat = -90,
+ maxLat = 90,
+ minLon = -180,
+ maxLon = 180;
+ let even = true;
+ for (const c of hash) {
+ const idx = BASE32.indexOf(c);
+ for (let b = 4; b >= 0; b--) {
+ const bit = (idx >> b) & 1;
+ if (even) {
+ const mid = (minLon + maxLon) / 2;
+ if (bit === 1) minLon = mid;
+ else maxLon = mid;
+ } else {
+ const mid = (minLat + maxLat) / 2;
+ if (bit === 1) minLat = mid;
+ else maxLat = mid;
+ }
+ even = !even;
+ }
+ }
+ return { lat: (minLat + maxLat) / 2, lon: (minLon + maxLon) / 2 };
+}
+
+/** Median of a numeric array (NaN on empty). */
+export function median(values: number[]): number {
+ if (values.length === 0) return NaN;
+ const s = [...values].sort((a, b) => a - b);
+ const mid = Math.floor(s.length / 2);
+ return s.length % 2 ? s[mid] : (s[mid - 1] + s[mid]) / 2;
+}
+
+/** Parse a stored latency entry: "unixSeconds:p50" (current format) or
+ * a bare legacy number (ts null). Invalid entries return null. */
+export function parseLatEntry(raw: string): { ts: number | null; p50: number } | null {
+ const idx = raw.indexOf(":");
+ if (idx === -1) {
+ const v = Number(raw);
+ return Number.isFinite(v) ? { ts: null, p50: v } : null;
+ }
+ const ts = Number(raw.slice(0, idx));
+ const v = Number(raw.slice(idx + 1));
+ if (!Number.isFinite(v)) return null;
+ return { ts: Number.isFinite(ts) ? ts : null, p50: v };
+}
+
+/** Current + previous month keys ("2026-09"), the map's rolling window. */
+export function monthKeys(now: Date): [string, string] {
+ const y = now.getUTCFullYear();
+ const m = now.getUTCMonth();
+ const cur = `${y}-${String(m + 1).padStart(2, "0")}`;
+ const pd = new Date(Date.UTC(y, m - 1, 1));
+ const prev = `${pd.getUTCFullYear()}-${String(pd.getUTCMonth() + 1).padStart(2, "0")}`;
+ return [cur, prev];
+}
diff --git a/src/lib/speedtest/rpc-directory.ts b/src/lib/speedtest/rpc-directory.ts
new file mode 100644
index 000000000..fd12fd304
--- /dev/null
+++ b/src/lib/speedtest/rpc-directory.ts
@@ -0,0 +1,117 @@
+/**
+ * Public no-key RPC directory for the browser speed test.
+ *
+ * GENERATED from the rpc-capabilities harness config CROSS-REFERENCED
+ * with each chain's public bench spec (benchmarks/-rpc.yml):
+ * only providers that actually rank on the public leaderboard are
+ * listed. Provider slugs match the bench cohort so
+ * resolves the same artwork as the leaderboards. EVM chains only.
+ */
+
+export type DirectoryEndpoint = { slug: string; provider: string; url: string };
+export type DirectoryChain = { slug: string; name: string; endpoints: DirectoryEndpoint[] };
+
+export const FEATURED_CHAINS = ["ethereum", "base", "arbitrum", "bnb", "polygon", "optimism", "avalanche", "monad"];
+
+export const RPC_DIRECTORY: DirectoryChain[] = [
+ { slug: "ethereum", name: "Ethereum", endpoints: [{ slug: "publicnode", provider: "PublicNode", url: "https://ethereum-rpc.publicnode.com" }, { slug: "drpc", provider: "dRPC", url: "https://eth.drpc.org" }, { slug: "meowrpc", provider: "MeowRPC", url: "https://eth.meowrpc.com" }, { slug: "tenderly", provider: "Tenderly Gateway", url: "https://gateway.tenderly.co/public/mainnet" }, { slug: "nodies", provider: "Nodies (POKT)", url: "https://eth-pokt.nodies.app" }, { slug: "lava", provider: "Lava Network", url: "https://eth1.lava.build" }] },
+ { slug: "base", name: "Base", endpoints: [{ slug: "publicnode", provider: "PublicNode", url: "https://base-rpc.publicnode.com" }, { slug: "drpc", provider: "dRPC", url: "https://base.drpc.org" }, { slug: "tenderly", provider: "Tenderly Gateway", url: "https://gateway.tenderly.co/public/base" }, { slug: "nodies", provider: "Nodies (POKT)", url: "https://base-pokt.nodies.app" }, { slug: "base-official", provider: "Base Official", url: "https://mainnet.base.org" }] },
+ { slug: "arbitrum", name: "Arbitrum One", endpoints: [{ slug: "publicnode", provider: "PublicNode", url: "https://arbitrum-one-rpc.publicnode.com" }, { slug: "drpc", provider: "dRPC", url: "https://arbitrum.drpc.org" }, { slug: "meowrpc", provider: "MeowRPC", url: "https://arbitrum.meowrpc.com" }, { slug: "tenderly", provider: "Tenderly Gateway", url: "https://gateway.tenderly.co/public/arbitrum" }, { slug: "nodies", provider: "Nodies (POKT)", url: "https://arb-pokt.nodies.app" }, { slug: "lava", provider: "Lava Network", url: "https://arb1.lava.build" }, { slug: "arbitrum-official", provider: "Arbitrum Official", url: "https://arb1.arbitrum.io/rpc" }] },
+ { slug: "bnb", name: "BNB Chain", endpoints: [{ slug: "publicnode", provider: "PublicNode", url: "https://bsc-rpc.publicnode.com" }, { slug: "drpc", provider: "dRPC", url: "https://bsc.drpc.org" }, { slug: "nodies", provider: "Nodies (POKT)", url: "https://bsc-pokt.nodies.app" }, { slug: "binance", provider: "Binance Official", url: "https://bsc-dataseed1.binance.org" }] },
+ { slug: "polygon", name: "Polygon", endpoints: [{ slug: "publicnode", provider: "PublicNode", url: "https://polygon-bor-rpc.publicnode.com" }, { slug: "drpc", provider: "dRPC", url: "https://polygon.drpc.org" }, { slug: "tenderly", provider: "Tenderly Gateway", url: "https://gateway.tenderly.co/public/polygon" }, { slug: "nodies", provider: "Nodies (POKT)", url: "https://polygon-pokt.nodies.app" }] },
+ { slug: "optimism", name: "Optimism", endpoints: [{ slug: "publicnode", provider: "PublicNode", url: "https://optimism-rpc.publicnode.com" }, { slug: "drpc", provider: "dRPC", url: "https://optimism.drpc.org" }, { slug: "tenderly", provider: "Tenderly Gateway", url: "https://gateway.tenderly.co/public/optimism" }, { slug: "nodies", provider: "Nodies (POKT)", url: "https://op-pokt.nodies.app" }, { slug: "optimism-official", provider: "Optimism Official", url: "https://mainnet.optimism.io" }] },
+ { slug: "avalanche", name: "Avalanche C-Chain", endpoints: [{ slug: "publicnode", provider: "PublicNode", url: "https://avalanche-c-chain-rpc.publicnode.com" }, { slug: "drpc", provider: "dRPC", url: "https://avalanche.drpc.org" }, { slug: "tenderly", provider: "Tenderly Gateway", url: "https://gateway.tenderly.co/public/avalanche" }, { slug: "avalanche-official", provider: "Avalanche Official", url: "https://api.avax.network/ext/bc/C/rpc" }] },
+ { slug: "monad", name: "Monad", endpoints: [{ slug: "monad-official", provider: "Monad Official", url: "https://rpc.monad.xyz" }, { slug: "drpc", provider: "dRPC", url: "https://monad-mainnet.drpc.org" }, { slug: "tenderly", provider: "Tenderly Gateway", url: "https://monad.gateway.tenderly.co" }, { slug: "bloxroute", provider: "bloXroute", url: "https://monad.rpc.blxrbdn.com" }, { slug: "onfinality", provider: "OnFinality", url: "https://monad-mainnet.api.onfinality.io/public" }] },
+ { slug: "abstract", name: "Abstract", endpoints: [{ slug: "abstract-official", provider: "Abstract Foundation", url: "https://api.mainnet.abs.xyz" }, { slug: "drpc", provider: "dRPC", url: "https://abstract.drpc.org" }, { slug: "thirdweb", provider: "Thirdweb", url: "https://abstract.rpc.thirdweb.com" }] },
+ { slug: "apechain", name: "ApeChain", endpoints: [{ slug: "apechain-official", provider: "ApeChain", url: "https://rpc.apechain.com/http" }, { slug: "drpc", provider: "dRPC", url: "https://apechain.drpc.org" }, { slug: "thirdweb", provider: "Thirdweb", url: "https://apechain.rpc.thirdweb.com" }, { slug: "tenderly", provider: "Tenderly", url: "https://gateway.tenderly.co/public/apechain" }] },
+ { slug: "arbitrum-nova", name: "Arbitrum Nova", endpoints: [{ slug: "publicnode", provider: "PublicNode", url: "https://arbitrum-nova-rpc.publicnode.com" }, { slug: "drpc", provider: "dRPC", url: "https://arbitrum-nova.drpc.org" }, { slug: "thirdweb", provider: "Thirdweb", url: "https://42170.rpc.thirdweb.com" }] },
+ { slug: "astar", name: "Astar", endpoints: [{ slug: "onfinality", provider: "OnFinality", url: "https://astar.api.onfinality.io/public" }, { slug: "1rpc", provider: "1RPC", url: "https://1rpc.io/astr" }] },
+ { slug: "aurora", name: "Aurora", endpoints: [{ slug: "aurora-official", provider: "Aurora Labs", url: "https://mainnet.aurora.dev" }] },
+ { slug: "berachain", name: "Berachain", endpoints: [{ slug: "publicnode", provider: "PublicNode", url: "https://berachain-rpc.publicnode.com" }, { slug: "drpc", provider: "dRPC", url: "https://berachain.drpc.org" }, { slug: "tenderly", provider: "Tenderly Gateway", url: "https://gateway.tenderly.co/public/berachain" }, { slug: "berachain-official", provider: "Berachain Official", url: "https://rpc.berachain.com" }] },
+ { slug: "bitlayer", name: "Bitlayer", endpoints: [{ slug: "bitlayer-official", provider: "Bitlayer Official", url: "https://rpc.bitlayer.org" }, { slug: "drpc", provider: "dRPC", url: "https://bitlayer.drpc.org" }, { slug: "ankr", provider: "Ankr", url: "https://rpc.ankr.com/bitlayer" }] },
+ { slug: "blast", name: "Blast", endpoints: [{ slug: "publicnode", provider: "PublicNode", url: "https://blast-rpc.publicnode.com" }, { slug: "drpc", provider: "dRPC", url: "https://blast.drpc.org" }, { slug: "tenderly", provider: "Tenderly Gateway", url: "https://gateway.tenderly.co/public/blast" }, { slug: "blast-official", provider: "Blast Official", url: "https://rpc.blast.io" }] },
+ { slug: "bob", name: "BOB", endpoints: [{ slug: "bob-official", provider: "BOB", url: "https://rpc.gobob.xyz" }, { slug: "tenderly", provider: "Tenderly", url: "https://bob.gateway.tenderly.co" }, { slug: "drpc", provider: "dRPC", url: "https://bob.drpc.org" }, { slug: "thirdweb", provider: "Thirdweb", url: "https://60808.rpc.thirdweb.com" }] },
+ { slug: "boba", name: "Boba Network", endpoints: [{ slug: "boba-official", provider: "Boba Foundation", url: "https://mainnet.boba.network" }, { slug: "drpc", provider: "dRPC", url: "https://boba-eth.drpc.org" }] },
+ { slug: "b2", name: "B\u00b2 Network", endpoints: [{ slug: "b2-official", provider: "B\u00b2 Network Official", url: "https://rpc.bsquared.network" }, { slug: "drpc", provider: "dRPC", url: "https://b2-mainnet.drpc.org" }, { slug: "thirdweb", provider: "Thirdweb", url: "https://223.rpc.thirdweb.com" }] },
+ { slug: "canto", name: "Canto", endpoints: [{ slug: "canto-official", provider: "Canto Official", url: "https://canto.gravitychain.io/" }] },
+ { slug: "celo", name: "Celo", endpoints: [{ slug: "publicnode", provider: "PublicNode", url: "https://celo-rpc.publicnode.com" }, { slug: "drpc", provider: "dRPC", url: "https://celo.drpc.org" }, { slug: "tenderly", provider: "Tenderly Gateway", url: "https://gateway.tenderly.co/public/celo" }, { slug: "celo-official", provider: "Celo Official (Forno)", url: "https://forno.celo.org" }] },
+ { slug: "chiliz", name: "Chiliz", endpoints: [{ slug: "publicnode", provider: "PublicNode", url: "https://chiliz-rpc.publicnode.com" }, { slug: "ankr", provider: "Ankr", url: "https://rpc.ankr.com/chiliz" }, { slug: "chiliz-official", provider: "Chiliz Foundation", url: "https://rpc.chiliz.com" }] },
+ { slug: "conflux", name: "Conflux", endpoints: [{ slug: "conflux-official", provider: "Conflux Official", url: "https://evm.confluxrpc.com" }, { slug: "conflux-global", provider: "Conflux Global", url: "https://evm.confluxrpc.org" }, { slug: "unifra", provider: "Unifra", url: "https://conflux-espace.nodereal.io/v1/pub" }] },
+ { slug: "core", name: "Core Chain", endpoints: [{ slug: "core-official", provider: "Core DAO", url: "https://rpc.coredao.org" }, { slug: "drpc", provider: "dRPC", url: "https://core.drpc.org" }, { slug: "thirdweb", provider: "Thirdweb", url: "https://1116.rpc.thirdweb.com" }, { slug: "ankr", provider: "Ankr", url: "https://rpc.ankr.com/core" }] },
+ { slug: "cronos", name: "Cronos", endpoints: [{ slug: "publicnode", provider: "PublicNode", url: "https://cronos-evm-rpc.publicnode.com" }, { slug: "drpc", provider: "dRPC", url: "https://cronos.drpc.org" }, { slug: "cronos-official", provider: "Cronos Official", url: "https://evm.cronos.org" }] },
+ { slug: "cronos-zkevm", name: "Cronos zkEVM", endpoints: [{ slug: "cronos-zkevm-official", provider: "Crypto.com Foundation", url: "https://mainnet.zkevm.cronos.org" }, { slug: "drpc", provider: "dRPC", url: "https://cronos-zkevm.drpc.org" }] },
+ { slug: "cyber", name: "Cyber", endpoints: [{ slug: "cyber-official", provider: "Cyber Network", url: "https://rpc.cyber.co" }, { slug: "altlayer", provider: "AltLayer", url: "https://cyber.alt.technology" }, { slug: "thirdweb", provider: "Thirdweb", url: "https://7560.rpc.thirdweb.com" }] },
+ { slug: "ethereum-classic", name: "Ethereum Classic", endpoints: [{ slug: "drpc", provider: "dRPC", url: "https://etc.drpc.org" }] },
+ { slug: "etherlink", name: "Etherlink", endpoints: [{ slug: "etherlink-official", provider: "Etherlink Foundation", url: "https://node.mainnet.etherlink.com" }, { slug: "drpc", provider: "dRPC", url: "https://etherlink.drpc.org" }] },
+ { slug: "fantom", name: "Fantom", endpoints: [{ slug: "fantom-official", provider: "Fantom Foundation", url: "https://rpcapi.fantom.network" }, { slug: "drpc", provider: "dRPC", url: "https://fantom.drpc.org" }, { slug: "thirdweb", provider: "Thirdweb", url: "https://250.rpc.thirdweb.com" }] },
+ { slug: "filecoin", name: "Filecoin EVM", endpoints: [{ slug: "glif", provider: "Glif", url: "https://api.node.glif.io/rpc/v1" }, { slug: "drpc", provider: "dRPC", url: "https://filecoin.drpc.org" }, { slug: "ankr", provider: "Ankr", url: "https://rpc.ankr.com/filecoin" }] },
+ { slug: "flare", name: "Flare", endpoints: [{ slug: "flare-official", provider: "Flare Foundation", url: "https://flare-api.flare.network/ext/bc/C/rpc" }, { slug: "drpc", provider: "dRPC", url: "https://flare.drpc.org" }, { slug: "thirdweb", provider: "Thirdweb", url: "https://14.rpc.thirdweb.com" }, { slug: "ankr", provider: "Ankr", url: "https://rpc.ankr.com/flare" }] },
+ { slug: "fraxtal", name: "Fraxtal", endpoints: [{ slug: "publicnode", provider: "PublicNode", url: "https://fraxtal-rpc.publicnode.com" }, { slug: "drpc", provider: "dRPC", url: "https://fraxtal.drpc.org" }, { slug: "tenderly", provider: "Tenderly Gateway", url: "https://gateway.tenderly.co/public/fraxtal" }, { slug: "fraxtal-official", provider: "Fraxtal Official", url: "https://rpc.frax.com" }] },
+ { slug: "fuse", name: "Fuse Network", endpoints: [{ slug: "fuse-official", provider: "Fuse Foundation", url: "https://rpc.fuse.io" }, { slug: "drpc", provider: "dRPC", url: "https://fuse.drpc.org" }, { slug: "thirdweb", provider: "Thirdweb", url: "https://122.rpc.thirdweb.com" }] },
+ { slug: "gnosis", name: "Gnosis", endpoints: [{ slug: "publicnode", provider: "PublicNode", url: "https://gnosis-rpc.publicnode.com" }, { slug: "drpc", provider: "dRPC", url: "https://gnosis.drpc.org" }, { slug: "tenderly", provider: "Tenderly Gateway", url: "https://gateway.tenderly.co/public/gnosis" }, { slug: "gnosis-official", provider: "Gnosis Official", url: "https://rpc.gnosischain.com" }] },
+ { slug: "gravity", name: "Gravity", endpoints: [{ slug: "ankr", provider: "Ankr", url: "https://rpc.ankr.com/gravity" }] },
+ { slug: "haqq", name: "HAQQ", endpoints: [{ slug: "publicnode", provider: "PublicNode", url: "https://haqq-evm-rpc.publicnode.com" }, { slug: "drpc", provider: "dRPC", url: "https://haqq.drpc.org" }, { slug: "haqq-official", provider: "HAQQ Foundation", url: "https://rpc.eth.haqq.network" }] },
+ { slug: "harmony", name: "Harmony", endpoints: [{ slug: "harmony-s0", provider: "Harmony S0", url: "https://a.api.s0.t.hmny.io" }] },
+ { slug: "hemi", name: "Hemi", endpoints: [{ slug: "hemi-official", provider: "Hemi Labs", url: "https://rpc.hemi.network/rpc" }, { slug: "drpc", provider: "dRPC", url: "https://hemi.drpc.org" }, { slug: "thirdweb", provider: "Thirdweb", url: "https://43111.rpc.thirdweb.com" }] },
+ { slug: "hyperliquid", name: "Hyperliquid", endpoints: [{ slug: "hyperliquid-official", provider: "Hyperliquid Labs", url: "https://rpc.hyperliquid.xyz/evm" }, { slug: "drpc", provider: "dRPC", url: "https://hyperliquid.drpc.org" }, { slug: "stakely", provider: "Stakely", url: "https://hyperliquid-json-rpc.stakely.io" }, { slug: "purroofgroup", provider: "Purroof Group", url: "https://rpc.purroofgroup.com" }, { slug: "hypurrscan", provider: "Hypurrscan", url: "https://rpc.hypurrscan.io" }] },
+ { slug: "immutable", name: "Immutable zkEVM", endpoints: [{ slug: "immutable-official", provider: "Immutable", url: "https://rpc.immutable.com" }, { slug: "drpc", provider: "dRPC", url: "https://immutable-zkevm.drpc.org" }, { slug: "tenderly", provider: "Tenderly Gateway", url: "https://immutable.gateway.tenderly.co" }, { slug: "thirdweb", provider: "Thirdweb", url: "https://immutable-zkevm.rpc.thirdweb.com" }] },
+ { slug: "ink", name: "Ink", endpoints: [{ slug: "ink-official", provider: "Gelato", url: "https://rpc-gel.inkonchain.com" }, { slug: "ink-quicknode", provider: "QuickNode", url: "https://rpc-qnd.inkonchain.com" }, { slug: "drpc", provider: "dRPC", url: "https://ink.drpc.org" }, { slug: "tenderly", provider: "Tenderly Gateway", url: "https://ink.gateway.tenderly.co" }] },
+ { slug: "iotex", name: "IoTeX", endpoints: [{ slug: "iotex-mirror", provider: "IoTeX Mirror", url: "https://babel-api.mainnet.iotex.one" }, { slug: "ankr", provider: "Ankr", url: "https://rpc.ankr.com/iotex" }, { slug: "thirdweb", provider: "Thirdweb", url: "https://4689.rpc.thirdweb.com" }] },
+ { slug: "kaia", name: "Kaia", endpoints: [{ slug: "kaia-official", provider: "Kaia Foundation", url: "https://public-en.node.kaia.io" }] },
+ { slug: "kava", name: "Kava", endpoints: [{ slug: "kava-official", provider: "Kava Labs", url: "https://evm.kava.io" }, { slug: "drpc", provider: "dRPC", url: "https://kava.drpc.org" }, { slug: "thirdweb", provider: "Thirdweb", url: "https://kava.rpc.thirdweb.com" }, { slug: "publicnode", provider: "PublicNode", url: "https://kava-evm-rpc.publicnode.com" }, { slug: "ankr", provider: "Ankr", url: "https://rpc.ankr.com/kava_evm" }] },
+ { slug: "linea", name: "Linea", endpoints: [{ slug: "publicnode", provider: "PublicNode", url: "https://linea-rpc.publicnode.com" }, { slug: "drpc", provider: "dRPC", url: "https://linea.drpc.org" }, { slug: "tenderly", provider: "Tenderly Gateway", url: "https://gateway.tenderly.co/public/linea" }] },
+ { slug: "lisk", name: "Lisk", endpoints: [{ slug: "lisk-official", provider: "Lisk", url: "https://rpc.api.lisk.com" }, { slug: "drpc", provider: "dRPC", url: "https://lisk.drpc.org" }, { slug: "thirdweb", provider: "Thirdweb", url: "https://lisk.rpc.thirdweb.com" }, { slug: "tenderly", provider: "Tenderly", url: "https://gateway.tenderly.co/public/lisk" }] },
+ { slug: "manta", name: "Manta Pacific", endpoints: [{ slug: "manta-official", provider: "Manta Network", url: "https://pacific-rpc.manta.network/http" }, { slug: "drpc", provider: "dRPC", url: "https://manta-pacific.drpc.org" }, { slug: "thirdweb", provider: "Thirdweb", url: "https://169.rpc.thirdweb.com" }, { slug: "1rpc", provider: "1RPC", url: "https://1rpc.io/manta" }] },
+ { slug: "mantle", name: "Mantle", endpoints: [{ slug: "publicnode", provider: "PublicNode", url: "https://mantle-rpc.publicnode.com" }, { slug: "drpc", provider: "dRPC", url: "https://mantle.drpc.org" }, { slug: "tenderly", provider: "Tenderly Gateway", url: "https://gateway.tenderly.co/public/mantle" }] },
+ { slug: "megaeth", name: "MegaETH", endpoints: [{ slug: "megaeth-official", provider: "MegaETH Official", url: "https://mainnet.megaeth.com/rpc" }, { slug: "drpc", provider: "dRPC", url: "https://megaeth.drpc.org" }, { slug: "tenderly", provider: "Tenderly Gateway", url: "https://megaeth.gateway.tenderly.co" }] },
+ { slug: "merlin", name: "Merlin Chain", endpoints: [{ slug: "merlin-official", provider: "Merlin Official", url: "https://rpc.merlinchain.io" }, { slug: "drpc", provider: "dRPC", url: "https://merlin.drpc.org" }, { slug: "blockpi", provider: "BlockPI", url: "https://merlin.blockpi.network/v1/rpc/public" }] },
+ { slug: "metis", name: "Metis", endpoints: [{ slug: "metis-official", provider: "Metis", url: "https://andromeda.metis.io/?owner=1088" }, { slug: "drpc", provider: "dRPC", url: "https://metis.drpc.org" }, { slug: "publicnode", provider: "PublicNode", url: "https://metis-rpc.publicnode.com" }, { slug: "thirdweb", provider: "Thirdweb", url: "https://1088.rpc.thirdweb.com" }] },
+ { slug: "mode", name: "Mode", endpoints: [{ slug: "mode-official", provider: "Mode Labs", url: "https://mainnet.mode.network" }, { slug: "drpc", provider: "dRPC", url: "https://mode.drpc.org" }, { slug: "1rpc", provider: "1RPC", url: "https://1rpc.io/mode" }, { slug: "thirdweb", provider: "Thirdweb", url: "https://mode.rpc.thirdweb.com" }] },
+ { slug: "moonbeam", name: "Moonbeam", endpoints: [{ slug: "publicnode", provider: "PublicNode", url: "https://moonbeam-rpc.publicnode.com" }, { slug: "drpc", provider: "dRPC", url: "https://moonbeam.drpc.org" }, { slug: "tenderly", provider: "Tenderly Gateway", url: "https://gateway.tenderly.co/public/moonbeam" }, { slug: "moonbeam-official", provider: "Moonbeam Official", url: "https://rpc.api.moonbeam.network" }] },
+ { slug: "moonriver", name: "Moonriver", endpoints: [{ slug: "moonriver-official", provider: "Moonbeam Foundation", url: "https://rpc.api.moonriver.moonbeam.network" }, { slug: "publicnode", provider: "PublicNode", url: "https://moonriver-rpc.publicnode.com" }, { slug: "onfinality", provider: "OnFinality", url: "https://moonriver.api.onfinality.io/public" }] },
+ { slug: "morph", name: "Morph", endpoints: [{ slug: "morph-official", provider: "Morph", url: "https://rpc.morphl2.io" }, { slug: "drpc", provider: "dRPC", url: "https://morph.drpc.org" }, { slug: "thirdweb", provider: "Thirdweb", url: "https://2818.rpc.thirdweb.com" }, { slug: "morph-quicknode", provider: "QuickNode", url: "https://rpc-quicknode.morph.network" }] },
+ { slug: "neon", name: "Neon EVM", endpoints: [{ slug: "neon-p2p", provider: "P2P Neon Proxy", url: "https://neon-proxy-mainnet.solana.p2p.org" }, { slug: "thirdweb", provider: "Thirdweb", url: "https://245022934.rpc.thirdweb.com" }, { slug: "everstake", provider: "Everstake", url: "https://neon-mainnet.everstake.one" }] },
+ { slug: "oktc", name: "OKTC", endpoints: [{ slug: "oktc-official", provider: "OKX Chain Official", url: "https://exchainrpc.okex.org" }, { slug: "1rpc", provider: "1RPC", url: "https://1rpc.io/oktc" }, { slug: "drpc", provider: "dRPC", url: "https://oktc.drpc.org" }] },
+ { slug: "opbnb", name: "opBNB", endpoints: [{ slug: "opbnb-official", provider: "BNB Chain Team", url: "https://opbnb-mainnet-rpc.bnbchain.org" }, { slug: "publicnode", provider: "PublicNode", url: "https://opbnb-rpc.publicnode.com" }, { slug: "drpc", provider: "dRPC", url: "https://opbnb.drpc.org" }] },
+ { slug: "plume", name: "Plume", endpoints: [{ slug: "drpc", provider: "dRPC", url: "https://plume.drpc.org" }] },
+ { slug: "polygon-zkevm", name: "Polygon zkEVM", endpoints: [{ slug: "polygon-zkevm-official", provider: "Polygon", url: "https://zkevm-rpc.com" }, { slug: "drpc", provider: "dRPC", url: "https://polygon-zkevm.drpc.org" }, { slug: "thirdweb", provider: "Thirdweb", url: "https://1101.rpc.thirdweb.com" }] },
+ { slug: "pulsechain", name: "PulseChain", endpoints: [{ slug: "publicnode", provider: "PublicNode", url: "https://pulsechain-rpc.publicnode.com" }, { slug: "pulsechain-official", provider: "PulseChain Foundation", url: "https://rpc.pulsechain.com" }, { slug: "g4mm4", provider: "G4MM4", url: "https://rpc-pulsechain.g4mm4.io" }] },
+ { slug: "reya", name: "Reya Network", endpoints: [{ slug: "thirdweb", provider: "Thirdweb", url: "https://1729.rpc.thirdweb.com" }] },
+ { slug: "ronin", name: "Ronin", endpoints: [{ slug: "ronin-official", provider: "Sky Mavis", url: "https://api.roninchain.com/rpc" }, { slug: "tenderly", provider: "Tenderly Gateway", url: "https://ronin.gateway.tenderly.co" }, { slug: "thirdweb", provider: "Thirdweb", url: "https://ronin.rpc.thirdweb.com" }] },
+ { slug: "rootstock", name: "Rootstock", endpoints: [{ slug: "rootstock-official", provider: "Rootstock Foundation", url: "https://public-node.rsk.co" }, { slug: "drpc", provider: "dRPC", url: "https://rootstock.drpc.org" }, { slug: "thirdweb", provider: "Thirdweb", url: "https://30.rpc.thirdweb.com" }] },
+ { slug: "scroll", name: "Scroll", endpoints: [{ slug: "publicnode", provider: "PublicNode", url: "https://scroll-rpc.publicnode.com" }, { slug: "drpc", provider: "dRPC", url: "https://scroll.drpc.org" }, { slug: "tenderly", provider: "Tenderly Gateway", url: "https://gateway.tenderly.co/public/scroll-mainnet" }] },
+ { slug: "sei", name: "Sei EVM", endpoints: [{ slug: "sei-official", provider: "Sei Labs", url: "https://evm-rpc.sei-apis.com" }, { slug: "thirdweb", provider: "Thirdweb", url: "https://sei.rpc.thirdweb.com" }, { slug: "stakeme", provider: "Stakeme", url: "https://sei-evm-rpc.stakeme.pro" }] },
+ { slug: "soneium", name: "Soneium", endpoints: [{ slug: "publicnode", provider: "PublicNode", url: "https://soneium-rpc.publicnode.com" }, { slug: "drpc", provider: "dRPC", url: "https://soneium.drpc.org" }, { slug: "tenderly", provider: "Tenderly Gateway", url: "https://gateway.tenderly.co/public/soneium" }, { slug: "soneium-official", provider: "Soneium Official", url: "https://rpc.soneium.org" }] },
+ { slug: "songbird", name: "Songbird", endpoints: [{ slug: "flare-official", provider: "Flare Foundation", url: "https://songbird-api.flare.network/ext/C/rpc" }] },
+ { slug: "sonic", name: "Sonic", endpoints: [{ slug: "publicnode", provider: "PublicNode", url: "https://sonic-rpc.publicnode.com" }, { slug: "drpc", provider: "dRPC", url: "https://sonic.drpc.org" }, { slug: "tenderly", provider: "Tenderly Gateway", url: "https://gateway.tenderly.co/public/sonic" }, { slug: "lava", provider: "Lava Network", url: "https://sonic.lava.build" }, { slug: "sonic-official", provider: "Sonic Labs Official", url: "https://rpc.soniclabs.com" }] },
+ { slug: "story", name: "Story", endpoints: [{ slug: "story-official", provider: "Story Foundation", url: "https://mainnet.storyrpc.io" }, { slug: "ankr", provider: "Ankr", url: "https://rpc.ankr.com/story_mainnet" }, { slug: "stakely", provider: "Stakely", url: "https://story-json-rpc.stakely.io" }, { slug: "publicnode", provider: "PublicNode", url: "https://story-rpc.publicnode.com" }] },
+ { slug: "swellchain", name: "Swellchain", endpoints: [{ slug: "ankr", provider: "Ankr", url: "https://rpc.ankr.com/swell" }, { slug: "sentio", provider: "Sentio", url: "https://swell-mainnet.rpc.sentio.xyz" }, { slug: "thirdweb", provider: "Thirdweb", url: "https://1923.rpc.thirdweb.com" }, { slug: "drpc", provider: "dRPC", url: "https://swell.drpc.org" }] },
+ { slug: "taiko", name: "Taiko", endpoints: [{ slug: "publicnode", provider: "PublicNode", url: "https://taiko-rpc.publicnode.com" }, { slug: "drpc", provider: "dRPC", url: "https://taiko.drpc.org" }, { slug: "tenderly", provider: "Tenderly Gateway", url: "https://gateway.tenderly.co/public/taiko-mainnet" }, { slug: "taiko-official", provider: "Taiko Official", url: "https://rpc.taiko.xyz" }] },
+ { slug: "telos", name: "Telos", endpoints: [{ slug: "drpc", provider: "dRPC", url: "https://telos.drpc.org" }] },
+ { slug: "thundercore", name: "ThunderCore", endpoints: [{ slug: "thundercore-official", provider: "ThunderCore Official", url: "https://mainnet-rpc.thundercore.com" }, { slug: "thundertoken", provider: "ThunderToken", url: "https://mainnet-rpc.thundertoken.net" }, { slug: "drpc", provider: "dRPC", url: "https://thundercore.drpc.org" }] },
+ { slug: "tron", name: "TRON", endpoints: [{ slug: "trongrid", provider: "TronGrid", url: "https://api.trongrid.io/jsonrpc" }, { slug: "publicnode", provider: "PublicNode", url: "https://tron.publicnode.com/jsonrpc" }] },
+ { slug: "unichain", name: "Unichain", endpoints: [{ slug: "publicnode", provider: "PublicNode", url: "https://unichain-rpc.publicnode.com" }, { slug: "drpc", provider: "dRPC", url: "https://unichain.drpc.org" }, { slug: "tenderly", provider: "Tenderly Gateway", url: "https://gateway.tenderly.co/public/unichain" }, { slug: "unichain-official", provider: "Unichain Official", url: "https://mainnet.unichain.org" }] },
+ { slug: "viction", name: "Viction", endpoints: [{ slug: "viction-official", provider: "Viction Official", url: "https://rpc.viction.xyz" }, { slug: "viction-rpc2", provider: "Viction RPC2", url: "https://rpc2.viction.xyz" }, { slug: "drpc", provider: "dRPC", url: "https://viction.drpc.org" }] },
+ { slug: "warden", name: "Warden Protocol", endpoints: [{ slug: "publicnode", provider: "PublicNode", url: "https://warden-evm-rpc.publicnode.com" }, { slug: "warden-official", provider: "Warden Foundation", url: "https://evm.wardenprotocol.org" }] },
+ { slug: "wemix", name: "WEMIX", endpoints: [{ slug: "wemix-official", provider: "WeMade", url: "https://api.wemix.com" }, { slug: "drpc", provider: "dRPC", url: "https://wemix.drpc.org" }] },
+ { slug: "world-chain", name: "World Chain", endpoints: [{ slug: "worldchain-official", provider: "World Chain", url: "https://worldchain-mainnet.g.alchemy.com/public" }, { slug: "drpc", provider: "dRPC", url: "https://worldchain.drpc.org" }, { slug: "tenderly", provider: "Tenderly Gateway", url: "https://worldchain-mainnet.gateway.tenderly.co" }] },
+ { slug: "xlayer", name: "X Layer", endpoints: [{ slug: "xlayer-official", provider: "X Layer", url: "https://rpc.xlayer.tech" }, { slug: "drpc", provider: "dRPC", url: "https://xlayer.drpc.org" }, { slug: "thirdweb", provider: "Thirdweb", url: "https://196.rpc.thirdweb.com" }] },
+ { slug: "xdc", name: "XDC Network", endpoints: [{ slug: "ankr", provider: "Ankr", url: "https://rpc.ankr.com/xdc" }, { slug: "xdc-erpc", provider: "XDC eRPC", url: "https://erpc.xinfin.network" }] },
+ { slug: "zetachain", name: "ZetaChain", endpoints: [{ slug: "thirdweb", provider: "Thirdweb", url: "https://7000.rpc.thirdweb.com" }] },
+ { slug: "zircuit", name: "Zircuit", endpoints: [{ slug: "zircuit-official", provider: "Zircuit Foundation", url: "https://mainnet.zircuit.com" }] },
+ { slug: "zksync", name: "zkSync Era", endpoints: [{ slug: "drpc", provider: "dRPC", url: "https://zksync.drpc.org" }, { slug: "tenderly", provider: "Tenderly Gateway", url: "https://gateway.tenderly.co/public/zksync" }, { slug: "zksync-official", provider: "zkSync Official", url: "https://mainnet.era.zksync.io" }] },
+ { slug: "zora", name: "Zora", endpoints: [{ slug: "zora-official", provider: "Zora Network", url: "https://rpc.zora.energy" }, { slug: "drpc", provider: "dRPC", url: "https://zora.drpc.org" }, { slug: "thirdweb", provider: "Thirdweb", url: "https://zora.rpc.thirdweb.com" }, { slug: "conduit", provider: "Conduit", url: "https://rpc-zora-mainnet-0.t.conduit.xyz" }] },
+];
+
+const URL_TO_EP = new Map();
+for (const c of RPC_DIRECTORY) for (const e of c.endpoints) URL_TO_EP.set(e.url, e);
+
+/** Directory entry (provider name + logo slug) for a known URL. */
+export function directoryEntryForUrl(url: string): DirectoryEndpoint | null {
+ return URL_TO_EP.get(url.replace(/\/+$/, "")) ?? URL_TO_EP.get(url) ?? null;
+}
+
+/** Back-compat: provider display name for a known URL. */
+export function providerForUrl(url: string): string | null {
+ return directoryEntryForUrl(url)?.provider ?? null;
+}
diff --git a/src/lib/speedtest/world-path.ts b/src/lib/speedtest/world-path.ts
new file mode 100644
index 000000000..278f647f0
--- /dev/null
+++ b/src/lib/speedtest/world-path.ts
@@ -0,0 +1,9 @@
+/**
+ * GENERATED by scripts/generate-world-path.mjs. World land outline as a
+ * single SVG path on an equirectangular projection (viewBox 0 0 1000 500).
+ * Source: world-atlas land-110m (Natural Earth, public domain).
+ */
+export const WORLD_VIEWBOX = "0 0 1000 500";
+export const WORLD_W = 1000;
+export const WORLD_H = 500;
+export const WORLD_PATH = "M334.5 472.3L333.7 473.7L332.9 475.0L327.1 474.6L320.9 474.8L317.4 473.9L317.4 473.7L315.9 472.9L322.1 473.0L328.1 473.3L330.2 472.2L331.6 471.2L334.5 472.3ZM57.8 470.8L52.4 471.2L48.8 470.2L47.2 469.2L47.0 469.1L45.2 468.3L46.9 467.3L52.1 467.7L54.9 468.6L57.0 469.6L57.8 470.8ZM374.6 466.8L378.0 468.0L379.2 469.7L379.5 470.9L379.6 472.3L375.3 473.2L370.8 473.9L365.6 474.5L359.8 475.1L353.2 474.9L349.5 474.0L350.0 472.8L356.0 472.1L358.4 471.1L360.1 470.0L361.4 468.9L363.0 467.9L364.8 466.8L366.2 466.8L370.4 466.2L374.6 466.8ZM163.3 454.2L166.9 454.6L170.2 454.1L168.6 455.1L166.0 455.8L162.2 455.6L159.4 454.6L160.0 453.7L163.3 454.2ZM151.2 454.1L155.5 455.2L153.8 455.1L150.2 454.8L146.4 454.1L148.5 453.5L151.2 454.1ZM225.1 449.8L228.1 450.2L231.1 449.9L232.8 451.4L230.6 451.2L227.2 451.3L223.8 451.2L220.0 451.4L217.2 450.8L215.8 449.7L217.5 449.2L221.0 449.6L225.1 449.8ZM309.9 447.1L310.2 448.4L309.7 449.4L308.9 450.5L305.7 450.9L302.6 451.4L298.9 451.3L300.3 450.3L297.0 450.6L293.9 451.0L291.8 450.2L291.6 449.1L294.7 448.0L296.6 447.6L299.8 447.8L300.6 446.3L300.8 445.3L300.7 443.1L302.3 441.8L304.9 441.3L306.3 442.4L307.0 443.4L308.2 444.7L309.1 445.8L309.9 447.1ZM0.0 485.3L0.2 485.3L2.6 483.7L7.6 484.6L7.9 484.5L8.7 484.3L9.7 484.0L10.5 483.7L10.9 483.6L11.3 483.6L11.6 483.7L15.6 484.8L19.1 483.7L19.8 483.5L27.9 483.0L30.6 483.7L31.9 484.0L36.1 484.9L43.9 485.6L50.2 486.5L60.9 487.1L68.9 486.4L80.7 486.9L87.4 487.8L94.8 487.0L102.5 486.2L103.1 484.9L92.1 484.8L83.2 484.2L80.8 483.1L73.4 482.5L73.9 481.2L74.9 480.1L75.9 479.0L75.4 477.9L70.8 477.1L68.6 476.2L64.3 475.3L71.1 475.4L77.5 475.0L81.5 475.9L86.5 475.1L91.1 474.1L93.3 473.2L92.3 472.0L88.7 471.3L84.6 470.4L78.9 470.3L73.9 469.9L68.5 469.6L66.7 468.6L63.1 467.7L61.0 466.7L60.1 463.6L61.5 463.9L64.0 464.7L68.5 464.5L72.9 464.1L75.2 465.3L79.6 465.0L83.3 464.4L86.8 463.6L90.0 462.7L94.2 462.4L94.0 461.4L93.1 460.4L93.9 459.4L97.5 458.9L99.1 459.8L103.4 459.3L106.6 458.6L110.5 458.5L114.3 458.2L118.0 457.6L121.0 457.0L124.4 456.4L126.6 456.6L128.5 456.8L132.6 456.4L136.3 456.9L140.1 456.8L143.8 456.5L147.5 456.7L151.7 457.0L155.5 456.9L159.6 456.9L163.7 457.0L167.5 456.9L170.3 456.1L173.7 455.6L177.2 456.2L180.5 455.7L183.5 454.8L185.3 455.6L186.3 456.6L188.1 457.5L190.9 456.7L194.3 457.8L198.0 458.1L201.2 458.8L205.1 458.7L208.7 458.2L212.9 458.3L216.6 458.7L220.4 459.2L221.9 458.0L220.1 457.1L218.7 456.1L215.2 455.9L213.6 454.8L213.0 453.8L212.0 451.7L214.1 452.1L217.8 452.3L221.4 452.1L224.6 452.5L227.5 453.3L228.6 454.3L232.4 454.5L236.0 454.1L239.8 453.6L243.2 453.2L246.1 453.9L249.8 453.7L252.1 451.6L254.4 452.8L257.6 453.3L261.1 453.0L263.4 454.1L267.0 454.2L270.4 454.5L273.7 455.1L275.9 454.1L277.0 453.1L279.7 454.2L283.5 453.9L286.4 454.5L288.3 455.5L292.0 455.2L294.9 454.6L297.7 453.9L301.1 453.5L305.0 453.2L308.5 452.8L311.2 452.2L312.9 451.3L313.5 450.1L313.2 449.0L312.3 447.9L311.3 446.8L310.5 445.7L309.8 444.7L309.6 443.7L309.9 442.6L311.2 441.5L312.3 440.4L312.7 439.3L312.2 438.1L311.8 437.0L313.2 435.8L314.7 435.0L316.5 433.9L318.4 433.0L320.6 432.2L321.7 431.0L323.3 430.3L325.0 429.6L327.7 429.4L329.4 428.5L331.4 428.0L333.6 427.7L335.7 427.0L337.2 426.1L339.4 425.8L341.0 426.5L340.0 427.4L337.2 428.2L336.0 428.8L333.9 428.4L331.6 428.6L329.7 429.3L327.7 430.0L326.4 430.8L326.0 431.9L326.1 432.9L327.4 433.9L325.5 434.5L322.9 434.7L321.4 435.7L319.8 436.5L318.0 437.7L317.6 438.8L318.6 439.9L320.0 440.8L322.3 441.4L324.5 442.3L325.6 443.4L326.2 444.4L327.0 445.5L328.3 446.4L329.1 447.5L329.5 450.0L330.3 451.1L330.5 452.2L331.4 453.2L331.0 454.7L329.5 455.9L327.9 456.8L324.2 457.2L322.9 458.1L321.2 459.1L317.1 460.1L313.4 460.5L309.9 461.1L306.1 461.7L303.9 462.9L299.4 463.0L294.5 462.9L290.1 463.1L285.4 463.1L286.3 464.2L290.6 464.7L293.7 465.4L295.4 466.4L292.3 467.3L287.5 467.0L283.5 467.7L283.4 468.9L283.3 470.0L286.5 470.9L287.1 471.9L290.7 472.9L296.5 473.4L301.5 474.1L305.5 475.0L310.6 475.9L317.5 476.3L324.3 477.1L329.0 477.9L334.2 478.8L336.9 480.1L338.3 481.2L341.6 480.2L346.2 479.4L351.1 478.5L356.8 477.8L361.8 477.0L368.7 477.0L375.5 477.4L381.1 478.0L382.9 476.8L386.7 476.0L393.8 475.9L399.3 475.3L404.5 474.7L410.3 474.4L416.4 473.9L420.7 473.2L418.7 472.2L417.5 471.2L417.5 470.2L412.2 470.3L406.4 470.7L401.0 470.7L400.2 469.7L400.6 467.6L401.9 467.0L405.8 466.4L410.5 465.7L413.9 464.9L417.3 464.1L419.8 463.0L423.6 462.5L427.3 462.1L429.2 461.9L433.5 461.8L437.6 461.4L441.0 460.9L444.4 460.2L447.5 459.6L451.3 458.7L453.8 457.8L456.4 456.9L457.2 455.9L454.3 455.2L455.2 454.1L457.1 453.2L460.0 452.6L463.0 452.0L465.9 451.1L468.0 450.0L469.4 448.7L471.4 448.0L474.7 448.1L476.1 449.0L479.4 449.2L479.5 448.1L480.9 447.0L483.9 447.3L484.6 448.3L487.9 448.5L491.5 448.0L495.0 447.7L498.2 447.8L499.4 449.0L502.4 448.1L505.2 447.6L508.4 447.2L511.5 446.8L514.3 446.2L517.4 445.7L519.8 445.1L521.5 444.1L523.6 444.9L526.5 444.5L528.5 445.8L530.0 446.8L533.2 446.2L534.5 445.1L537.3 444.4L540.9 444.5L542.0 445.6L544.3 444.5L547.3 444.2L550.6 444.1L553.5 444.1L556.6 444.5L559.6 444.6L560.9 445.6L562.7 446.4L565.7 445.9L569.0 445.8L572.2 445.8L575.3 445.7L578.0 445.3L581.0 445.0L583.4 444.3L586.0 443.8L588.9 443.5L591.0 442.7L592.5 441.2L594.1 440.3L597.0 440.7L598.1 441.7L600.4 442.4L603.3 442.1L605.3 443.1L607.4 443.8L610.2 443.2L611.2 442.0L613.7 441.5L616.6 440.6L619.3 440.2L622.5 439.6L624.7 439.0L627.0 438.4L629.2 437.8L631.8 438.1L634.3 437.1L636.1 436.4L638.7 436.4L641.0 435.8L641.5 434.8L643.9 434.0L646.1 433.5L648.9 433.0L651.5 432.8L653.9 433.0L656.5 433.3L658.8 434.0L659.0 435.2L661.5 436.1L663.2 436.9L666.5 437.2L668.3 438.0L670.6 438.8L673.3 438.9L675.5 438.4L677.9 437.2L680.5 437.8L683.3 438.2L685.9 438.5L688.6 438.7L691.4 438.7L693.6 441.6L693.5 442.3L693.2 443.6L690.5 444.3L688.4 445.3L688.7 446.4L691.8 446.3L691.5 447.4L690.1 448.5L688.7 449.6L690.9 450.5L694.1 450.7L697.3 450.2L698.8 449.2L699.7 448.1L701.3 447.3L703.0 446.4L703.7 445.5L705.2 444.1L706.9 443.8L710.1 443.7L712.8 443.4L715.7 443.0L717.0 441.9L717.9 440.8L719.8 439.8L722.5 439.1L724.8 438.5L726.3 437.6L727.9 437.1L729.9 436.7L732.7 437.0L735.2 436.7L737.9 436.4L741.0 436.5L743.0 435.8L744.4 433.9L745.4 434.7L746.7 436.0L749.1 436.5L751.7 436.7L754.4 436.4L757.2 436.6L759.9 436.7L761.6 436.4L763.9 436.6L766.1 437.2L768.6 436.8L771.6 436.8L774.1 436.4L777.0 436.8L778.8 435.9L780.3 435.0L782.2 434.2L785.6 432.1L787.4 432.5L789.6 433.3L791.4 434.2L794.9 435.9L797.7 436.0L800.2 436.0L803.2 435.7L806.2 435.3L808.5 434.5L810.4 433.7L813.5 433.6L815.6 433.0L817.7 433.5L819.2 434.4L821.1 435.3L824.2 435.2L826.1 435.9L829.4 436.6L832.9 436.9L835.7 436.6L837.9 435.8L839.8 434.9L842.3 434.7L844.8 435.1L847.7 435.3L850.3 434.9L852.8 434.9L855.2 435.2L857.8 435.4L860.3 435.0L863.3 434.5L866.1 434.4L869.3 434.4L871.8 434.1L874.3 433.9L875.1 432.6L875.2 431.4L876.9 432.2L877.4 433.4L878.3 434.6L879.5 435.5L881.8 436.0L885.0 435.8L888.6 435.8L891.1 435.6L894.8 435.6L897.4 435.5L901.0 435.7L904.1 435.9L906.1 436.7L905.6 437.8L907.3 438.6L910.3 439.2L913.4 440.0L917.0 440.4L920.8 440.9L923.6 441.3L926.8 441.4L928.6 440.4L931.0 441.2L933.1 442.1L935.6 442.7L939.0 443.0L942.2 443.3L943.5 444.4L946.7 445.1L948.8 446.1L951.9 446.5L955.1 446.4L958.1 446.6L961.4 446.5L964.7 446.8L967.8 447.1L970.7 447.8L973.6 448.3L975.6 449.2L975.2 450.2L973.8 451.2L972.5 452.5L971.5 453.5L970.2 454.6L966.6 455.0L965.0 456.0L961.4 456.6L960.1 457.7L958.2 458.7L956.2 459.6L955.1 460.8L954.4 461.8L954.1 463.0L954.1 464.1L955.7 465.2L956.3 466.2L957.6 467.2L962.8 467.6L963.9 468.8L958.9 469.2L954.6 469.8L949.3 469.9L947.0 471.5L946.5 472.8L945.3 473.8L943.9 474.9L947.6 475.8L949.0 476.9L951.4 477.9L954.7 478.9L958.6 479.7L962.8 480.6L969.1 481.5L970.6 482.8L978.6 483.4L979.1 483.7L981.2 484.5L988.8 483.8L995.2 484.6L0.0 485.3ZM311.8 399.6L315.4 401.3L319.3 401.9L318.1 403.3L315.4 403.5L314.0 402.5L313.1 403.6L310.7 404.5L307.7 404.2L305.7 403.3L302.8 402.9L299.3 401.4L296.4 399.9L292.6 396.8L294.9 397.4L298.8 399.2L302.5 400.2L303.9 398.9L304.8 397.0L307.4 395.9L309.4 396.2L310.4 397.5L311.8 399.6ZM337.4 391.9L339.6 393.2L338.8 394.2L335.0 395.0L333.8 394.0L331.4 395.3L330.0 394.0L333.3 392.4L335.7 393.1L337.4 391.9ZM695.2 388.1L691.0 388.3L690.9 386.8L691.3 385.6L691.5 385.1L693.3 385.9L695.9 386.3L696.0 386.8L695.2 388.1ZM903.9 363.3L906.6 364.3L908.1 363.9L910.2 363.4L911.9 363.5L912.1 366.8L911.2 367.8L910.9 370.0L909.9 369.3L908.0 371.2L907.4 371.1L905.7 371.0L904.0 368.6L903.6 366.8L902.0 364.3L902.1 363.1L903.9 363.3ZM980.6 363.7L981.2 364.8L983.2 363.7L984.0 364.9L984.0 366.0L983.0 367.3L981.2 369.4L979.7 370.5L980.8 371.8L978.6 371.8L976.3 372.9L975.5 374.7L973.9 377.5L971.7 378.8L970.4 379.6L967.8 379.5L966.0 378.6L963.0 378.4L962.5 377.4L964.0 375.3L967.5 372.6L969.3 372.0L971.3 371.0L973.7 369.5L975.3 368.1L976.6 366.0L977.6 365.3L978.0 363.8L980.0 362.5L980.6 363.7ZM985.0 350.4L987.0 353.4L987.1 351.5L988.4 352.2L988.8 354.3L991.0 355.2L992.9 355.4L994.5 354.4L995.9 354.7L995.2 357.2L994.4 358.8L992.2 358.7L991.5 359.6L991.8 360.8L991.3 361.3L990.3 362.8L988.9 364.7L986.8 365.8L986.3 365.1L985.1 364.7L986.7 362.4L985.8 360.9L982.8 359.7L982.9 358.7L984.9 357.8L985.4 355.6L985.3 353.8L984.1 352.0L984.2 351.5L982.9 350.3L980.7 347.9L979.5 345.9L980.6 345.7L982.1 347.2L984.2 348.0L985.0 350.4ZM964.2 311.6L963.2 312.2L961.6 311.5L959.6 310.2L957.9 308.7L956.0 306.8L955.6 305.8L956.8 305.9L958.4 306.8L959.6 307.8L960.5 308.6L962.8 310.3L964.2 311.6ZM995.5 298.2L996.4 299.0L996.0 300.4L994.3 300.8L992.7 300.5L992.5 299.2L993.5 298.3L994.8 298.6L995.5 298.2ZM0.0 296.0L998.2 296.7L996.5 297.3L996.1 296.2L997.5 295.7L998.4 295.5L0.0 294.6L0.6 294.5L0.2 295.8L0.0 296.0ZM966.2 295.7L965.3 296.1L964.4 294.9L964.5 294.1L966.2 295.7ZM964.2 291.5L964.6 293.7L963.9 293.4L963.3 293.5L962.9 292.8L962.9 290.6L964.2 291.5ZM639.0 287.7L639.5 291.0L640.2 292.3L639.9 293.6L639.4 294.4L638.5 292.8L638.0 293.6L638.5 295.7L638.3 296.9L637.5 297.5L637.3 299.9L636.2 303.1L634.9 306.9L633.1 312.2L632.1 316.1L630.8 319.3L628.6 319.9L626.1 321.1L624.5 320.4L622.3 319.4L621.6 317.9L621.4 315.5L620.4 313.3L620.1 311.3L620.6 309.3L621.9 308.8L621.9 307.9L623.3 305.8L623.5 304.0L622.9 302.7L622.3 300.9L622.1 298.4L623.1 296.8L623.5 295.0L624.8 294.9L626.4 294.4L627.4 293.9L628.6 293.8L630.2 292.3L632.5 290.5L633.3 289.1L633.0 288.0L634.1 288.3L635.7 286.4L635.7 284.7L636.7 283.4L637.6 284.6L638.4 285.8L639.0 287.7ZM898.8 288.2L899.8 290.4L901.6 289.4L902.5 290.5L903.8 291.6L903.5 292.9L904.1 295.2L904.5 296.6L905.2 297.0L906.0 299.3L905.7 300.8L906.6 302.7L909.6 304.1L911.6 305.4L913.5 306.6L913.1 307.3L914.7 309.1L915.8 312.1L916.9 311.5L918.0 312.7L918.7 312.2L919.2 315.2L921.1 316.9L922.4 317.9L924.6 320.2L925.4 322.4L925.4 324.0L925.3 325.7L926.6 328.1L926.4 330.5L925.9 331.8L925.2 334.3L925.2 335.9L924.7 337.9L923.5 340.4L921.4 341.8L920.4 343.9L919.5 345.3L918.6 347.7L917.6 349.1L916.9 351.2L916.5 353.1L916.7 354.0L915.1 354.9L912.0 355.0L909.4 356.2L908.1 357.2L906.4 358.4L904.1 357.2L902.4 356.7L902.9 355.3L901.3 355.8L898.9 357.8L896.5 357.1L894.9 356.6L893.3 356.4L890.7 355.6L888.9 353.9L888.3 351.8L887.7 350.4L886.3 349.3L883.7 348.9L884.6 347.6L883.9 345.5L882.5 347.4L880.1 347.9L881.5 346.4L882.0 344.8L883.0 343.4L882.8 341.4L880.5 343.8L878.8 344.7L877.7 346.9L875.6 345.8L875.7 344.3L873.9 342.3L872.5 341.2L873.0 340.6L869.4 338.9L867.5 338.8L864.8 337.5L859.8 337.8L856.2 338.7L853.1 339.7L850.4 339.5L847.5 340.9L845.1 341.6L844.5 343.0L843.5 344.1L841.1 344.2L839.4 344.5L836.9 343.9L834.9 344.3L833.0 344.4L831.4 345.9L830.6 345.7L829.2 346.5L827.8 347.4L825.8 347.3L824.0 347.3L821.0 345.5L819.5 345.0L819.6 343.4L821.0 343.0L821.4 342.4L821.3 341.4L821.7 339.5L821.4 337.8L819.9 335.0L819.4 333.4L819.6 331.8L818.4 330.0L818.4 329.2L817.1 328.1L816.8 325.9L815.2 323.7L814.8 322.5L816.0 323.7L815.1 321.2L816.5 322.0L817.3 323.1L817.3 321.6L815.9 319.4L815.6 318.6L815.0 317.7L815.3 316.1L815.8 315.4L816.2 314.1L815.9 312.4L817.1 310.4L817.3 312.5L818.5 310.6L820.7 309.7L822.1 308.5L824.2 307.5L825.5 307.3L826.2 307.6L828.4 306.6L830.1 306.3L830.5 305.7L831.3 305.4L832.8 305.5L835.7 304.7L837.2 303.4L837.9 302.0L839.6 300.5L839.7 299.4L839.8 297.9L841.7 295.6L842.9 298.0L844.1 297.4L843.1 296.1L843.9 294.8L845.2 295.4L845.5 293.2L847.0 291.9L847.7 290.8L849.1 290.3L849.1 289.5L850.3 289.9L850.4 289.2L851.6 288.8L853.0 288.4L855.0 289.7L856.6 291.3L858.3 291.3L860.1 291.6L859.5 290.1L860.8 287.8L862.1 287.1L861.6 286.4L862.8 284.8L864.5 283.8L865.9 284.2L868.3 283.7L868.2 282.2L866.2 281.3L867.7 280.9L869.5 281.6L871.0 282.7L873.3 283.4L874.1 283.2L875.8 284.0L877.4 283.2L878.5 283.5L879.1 282.9L880.4 284.3L879.7 285.8L878.6 286.9L877.7 287.0L878.0 288.1L877.2 289.5L876.2 290.9L876.4 291.7L878.6 293.2L880.7 294.1L882.2 295.0L884.2 296.7L885.0 296.7L886.4 297.4L886.8 298.3L889.5 299.2L891.3 298.2L891.9 296.8L892.4 295.5L892.8 294.0L893.6 291.8L893.2 290.4L893.4 289.6L893.1 288.0L893.5 286.0L894.0 285.4L893.6 284.5L894.2 283.0L894.8 281.5L894.8 280.7L895.9 279.6L896.7 281.0L896.8 282.7L897.5 283.1L897.7 284.2L898.7 285.7L898.9 287.2L898.8 288.2ZM950.3 279.1L951.1 280.1L949.2 280.1L948.1 278.3L949.8 279.0L950.3 279.1ZM835.3 278.4L834.1 278.5L830.5 276.5L833.1 276.0L834.5 276.8L835.5 277.7L835.3 278.4ZM946.8 277.4L945.7 277.5L944.0 277.2L943.4 276.8L943.6 275.7L945.4 276.1L946.4 276.7L946.8 277.4ZM949.1 276.7L948.7 277.2L946.6 274.8L946.1 273.1L947.0 273.1L948.0 275.3L949.1 276.7ZM845.7 278.2L843.3 278.8L842.9 278.4L843.2 277.5L844.4 275.8L847.1 274.7L847.5 274.0L849.8 273.4L851.8 273.3L852.7 273.0L853.7 273.3L852.7 274.1L849.8 275.3L847.5 276.1L845.7 278.2ZM827.5 272.5L828.5 273.2L830.2 273.0L830.9 274.2L827.7 274.7L825.8 275.1L824.3 275.1L825.2 273.5L826.8 273.5L827.5 272.5ZM841.4 272.5L841.0 274.0L836.8 274.8L833.1 274.5L833.1 273.5L835.3 272.9L837.1 273.7L838.9 273.5L841.4 272.5ZM944.1 273.2L944.2 273.7L942.0 272.5L940.5 271.5L939.5 270.6L939.9 270.3L941.2 271.0L943.4 272.3L944.1 273.2ZM937.6 270.4L937.0 270.6L935.8 269.9L934.7 268.8L934.8 268.3L936.5 269.5L937.6 270.4ZM801.7 268.8L807.1 269.1L807.7 268.0L812.8 269.3L813.8 271.1L818.0 271.6L821.4 273.3L818.2 274.3L815.2 273.2L812.7 273.3L809.8 273.1L807.2 272.6L804.0 271.5L801.9 271.2L800.8 271.6L795.7 270.4L795.2 269.2L792.7 269.0L794.6 266.4L798.0 266.5L800.2 267.6L801.3 267.8L801.7 268.8ZM874.2 267.3L872.8 269.2L872.5 267.1L873.0 266.1L873.6 265.1L874.2 265.9L874.2 267.3ZM933.0 268.9L932.2 269.2L931.0 268.2L929.8 266.4L929.2 264.3L929.6 264.0L929.9 264.8L930.7 265.5L932.1 267.2L933.4 268.2L933.0 268.9ZM922.2 265.2L920.7 265.4L920.3 266.2L918.8 266.9L917.3 267.5L915.9 267.5L913.6 266.7L912.0 266.0L912.2 265.1L914.7 265.5L916.2 265.3L916.7 264.0L917.1 263.9L917.3 265.4L918.9 265.2L919.7 264.2L921.2 263.2L920.9 261.6L922.6 261.5L923.2 262.0L923.1 263.5L922.2 265.2ZM853.5 259.6L852.4 260.5L850.5 260.0L850.0 258.8L852.8 258.7L853.5 259.6ZM862.4 258.6L863.4 260.7L861.1 259.6L858.8 259.3L857.2 259.5L855.3 259.4L855.9 257.9L859.4 257.8L862.4 258.6ZM925.4 262.5L924.5 263.2L924.0 261.6L923.3 260.5L922.1 259.6L920.5 258.4L918.5 257.6L919.3 256.9L920.8 257.7L921.7 258.3L922.9 259.0L924.0 260.2L925.1 261.1L925.4 262.5ZM872.6 253.2L873.4 257.7L876.3 259.4L878.6 256.4L881.8 254.7L884.2 254.7L886.6 255.7L888.7 256.7L891.7 257.2L896.5 259.1L901.6 260.7L903.5 262.1L905.1 263.5L905.5 265.2L910.1 266.9L910.8 268.4L908.2 268.7L908.9 270.5L911.3 272.3L913.1 275.3L914.7 275.2L914.6 276.4L916.8 276.9L915.9 277.4L918.9 278.6L918.6 279.4L916.7 279.6L916.1 278.9L913.7 278.6L910.9 278.1L908.7 276.4L907.1 274.8L905.7 272.4L902.1 271.2L899.7 272.0L898.0 272.9L898.4 275.0L896.2 275.9L894.6 275.4L891.8 275.3L889.3 273.0L886.5 272.5L885.8 273.3L882.3 273.4L883.4 271.1L885.2 270.3L884.5 267.3L883.1 265.0L877.7 262.6L875.5 262.4L871.3 259.8L870.5 261.2L869.4 261.4L868.8 260.4L868.8 259.2L866.6 257.8L869.6 256.8L871.6 256.9L871.4 256.2L867.3 256.1L866.2 254.5L863.7 254.0L862.6 252.6L866.3 251.9L867.7 251.0L872.2 252.2L872.6 253.2ZM847.9 246.1L845.7 248.8L843.6 249.3L840.9 248.8L836.3 248.9L833.8 249.3L833.4 251.4L835.9 253.9L837.4 252.7L842.6 251.7L842.4 253.0L841.2 252.6L840.0 254.2L837.5 255.3L840.1 258.9L839.6 259.8L842.1 263.0L842.1 264.8L840.6 265.6L839.5 264.7L840.9 262.4L838.2 263.5L837.5 262.7L837.8 261.6L835.8 260.0L836.0 257.3L834.2 258.1L834.4 261.4L834.5 265.4L832.8 265.8L831.6 264.9L832.4 262.4L831.9 259.7L830.8 259.7L829.9 257.8L831.1 256.0L831.5 253.8L832.8 249.6L833.4 248.4L835.8 246.4L838.0 247.2L841.5 247.6L844.7 247.5L847.4 245.4L847.9 246.1ZM857.5 246.9L857.3 249.3L855.9 249.0L855.5 250.7L856.6 252.2L855.8 252.5L854.7 250.7L853.9 247.2L854.4 245.0L855.4 244.0L855.6 245.5L857.2 245.7L857.5 246.9ZM793.9 266.3L790.9 266.3L788.5 264.0L785.0 261.7L783.8 260.0L781.7 257.8L780.3 255.7L778.2 251.8L775.7 249.5L774.9 247.1L773.9 244.9L771.4 243.2L769.9 240.8L767.8 239.3L764.9 236.2L764.7 234.8L766.5 234.9L770.8 235.4L773.2 238.1L775.4 240.0L776.9 241.2L779.6 244.2L782.4 244.2L784.7 246.1L786.3 248.4L788.4 249.7L787.3 252.0L788.9 252.9L789.9 253.0L790.4 254.9L791.4 256.5L793.4 256.7L794.7 258.5L794.0 262.0L793.9 266.3ZM827.4 244.9L830.5 247.5L827.3 247.8L826.3 249.7L826.4 252.2L823.8 254.1L823.7 256.9L822.6 261.1L822.2 260.2L819.1 261.4L818.0 259.7L816.0 259.6L814.6 258.7L811.3 259.7L810.3 258.3L808.5 258.5L806.2 258.2L805.7 254.4L804.4 253.7L803.0 251.3L802.6 248.8L803.0 246.3L804.6 244.4L806.7 245.4L808.8 244.9L809.4 242.5L810.5 242.0L813.9 241.4L815.9 239.2L817.2 237.4L818.3 236.4L820.7 234.9L822.8 232.9L824.2 230.8L825.4 230.8L826.8 232.2L826.9 233.4L828.7 234.1L831.1 235.0L830.9 236.1L829.0 236.2L829.5 237.6L827.4 238.5L825.9 241.0L827.9 243.6L827.4 244.9ZM851.0 226.6L851.3 228.5L851.5 230.0L850.5 232.6L849.5 229.7L848.2 231.1L849.1 233.2L848.3 234.5L845.1 232.9L844.3 230.9L845.1 229.6L843.4 228.2L842.5 229.4L841.2 229.3L839.1 230.8L838.7 230.0L839.8 227.7L841.5 226.9L843.0 225.9L844.0 227.1L846.1 226.3L846.6 225.1L848.5 225.0L848.4 222.9L850.6 224.2L850.8 225.6L851.0 226.6ZM725.6 232.8L723.2 233.4L721.9 231.2L721.4 227.2L722.6 222.7L724.6 224.3L725.8 226.2L727.2 229.1L726.8 232.0L725.6 232.8ZM330.7 221.9L328.4 222.2L327.9 222.0L328.7 221.2L328.7 220.1L330.3 219.7L330.8 219.8L330.7 221.9ZM844.4 221.4L843.4 222.4L842.5 224.1L841.7 224.9L839.9 223.0L840.5 222.3L841.2 221.5L841.5 219.8L843.0 219.6L842.6 221.5L844.7 218.8L844.4 221.4ZM829.2 224.1L825.5 226.8L826.8 224.8L828.8 223.1L830.5 221.2L832.0 218.4L832.5 220.7L830.6 222.2L829.2 224.1ZM838.6 217.0L840.2 217.8L842.0 217.8L841.9 219.0L840.7 220.2L838.9 221.0L838.8 219.7L839.0 218.3L838.6 217.0ZM848.6 216.2L849.4 219.3L847.3 218.6L847.3 219.5L848.0 221.2L846.7 221.9L846.6 219.9L845.7 219.7L845.3 218.1L846.9 218.3L846.9 217.2L845.2 215.1L847.8 215.2L848.6 216.2ZM837.6 213.7L836.8 216.1L835.6 214.7L834.2 212.6L836.6 212.7L837.6 213.7ZM837.0 198.6L838.7 199.4L839.6 198.7L839.8 199.4L839.4 200.5L840.3 202.5L839.6 204.8L837.9 205.7L837.5 208.0L838.1 210.2L839.6 210.5L840.8 210.2L844.3 211.7L844.0 213.2L844.9 213.9L844.7 215.2L842.5 213.8L841.5 212.4L840.8 213.4L839.0 211.7L836.5 212.1L835.1 211.5L835.2 210.4L836.1 209.6L835.3 209.0L834.9 210.0L833.5 208.4L833.1 207.2L833.0 204.5L834.1 205.5L834.4 201.1L835.3 198.6L837.0 198.6ZM317.8 199.4L317.1 200.1L315.0 200.0L313.4 200.1L313.2 199.0L313.6 198.6L315.9 198.6L317.3 198.8L317.8 199.4ZM286.4 200.4L285.5 200.8L284.0 200.4L282.4 199.4L282.7 198.7L283.9 198.5L284.5 198.6L286.4 198.9L287.9 199.6L288.3 200.3L286.4 200.4ZM298.4 194.8L300.8 195.2L301.1 194.8L303.3 194.8L305.0 195.5L305.7 195.4L306.2 196.4L307.7 196.4L307.6 197.2L308.9 197.3L310.2 198.3L309.2 199.4L307.9 198.8L306.6 198.9L305.7 198.8L305.2 199.3L304.1 199.5L303.7 198.8L302.8 199.2L301.7 201.1L301.0 200.7L300.8 199.9L299.0 199.4L297.7 199.6L296.0 199.4L294.7 199.9L293.2 199.0L293.4 198.2L296.0 198.5L298.1 198.8L299.1 198.1L297.8 196.9L297.8 195.9L296.1 195.4L296.7 194.7L298.4 194.8ZM806.5 198.1L804.1 199.5L801.8 198.6L801.7 196.2L803.1 194.9L806.1 194.2L807.7 194.2L808.4 195.3L807.1 196.5L806.5 198.1ZM67.9 197.0L67.5 197.5L66.8 197.1L66.9 196.3L66.5 195.3L66.6 195.0L67.1 194.5L66.9 194.0L67.1 193.7L67.3 193.8L68.3 194.2L68.8 194.5L69.3 194.8L70.0 195.8L69.9 196.0L68.8 196.6L67.9 197.0ZM66.5 192.7L65.5 192.9L65.0 192.3L64.7 192.0L64.7 191.9L65.0 191.6L66.0 191.9L66.7 192.3L66.5 192.7ZM64.6 191.2L64.5 191.5L63.0 191.4L63.2 191.1L64.6 191.2ZM62.1 190.8L61.9 190.9L61.7 190.9L60.8 190.8L60.4 190.2L60.3 190.1L61.0 189.7L61.3 189.9L62.1 190.8ZM57.4 188.9L57.0 189.2L56.1 188.7L56.3 188.5L56.7 188.2L57.3 188.3L57.4 188.9ZM278.7 186.8L279.8 187.8L282.4 187.5L283.4 188.1L285.7 189.8L287.4 191.1L288.4 191.1L290.0 191.6L289.8 192.4L291.9 192.5L294.0 193.7L293.6 194.3L291.8 194.7L289.9 194.8L288.0 194.6L284.0 194.8L285.9 193.3L284.7 192.6L283.0 192.4L282.0 191.6L281.3 190.0L279.8 190.1L277.2 189.4L276.3 188.8L272.7 188.4L271.8 187.8L272.8 187.1L270.1 187.0L268.1 188.4L266.9 188.5L266.5 189.1L265.1 189.4L264.0 189.2L265.4 188.3L266.0 187.3L267.3 186.7L268.7 186.2L270.8 185.9L271.5 185.6L273.9 185.8L276.1 185.8L278.7 186.8ZM284.6 184.0L283.9 184.1L283.2 182.5L282.2 181.7L282.8 180.0L283.6 180.1L284.6 182.4L284.6 184.0ZM836.6 186.7L835.4 189.0L833.9 186.6L833.6 184.6L835.3 181.8L837.5 179.7L838.8 180.6L838.3 182.2L836.6 186.7ZM283.8 176.2L280.8 176.6L280.6 175.6L281.9 175.4L283.8 175.4L283.8 176.2ZM286.1 176.1L285.6 178.1L285.1 177.8L285.2 176.3L283.9 175.2L283.9 174.9L286.1 176.1ZM874.0 155.1L874.3 156.1L872.8 157.8L871.6 156.9L870.2 157.5L869.5 159.2L867.7 158.4L867.7 157.0L869.2 155.4L870.8 155.7L872.0 154.5L874.0 155.1ZM596.0 150.9L594.2 152.1L594.4 152.6L594.5 152.8L591.6 154.0L590.2 153.6L589.6 152.5L590.9 152.4L591.1 152.4L591.5 151.7L593.5 151.7L596.0 150.9ZM565.8 150.8L567.4 151.8L569.5 151.6L571.6 151.8L571.5 152.3L573.0 151.9L572.7 152.8L568.7 153.0L568.7 152.5L565.3 152.0L565.8 150.8ZM543.1 143.8L542.1 146.0L542.5 146.9L541.9 148.3L539.8 147.2L538.4 146.9L534.5 145.5L534.9 144.1L538.2 144.3L541.0 144.0L543.1 143.8ZM525.6 135.5L527.2 137.5L526.9 141.2L525.6 141.0L524.5 141.9L523.4 141.2L523.3 137.8L522.7 136.2L524.2 136.4L525.6 135.5ZM891.6 146.8L890.6 149.0L891.0 150.4L889.6 152.4L886.0 153.7L881.2 153.9L877.2 157.0L875.3 156.0L875.2 153.9L870.4 154.5L867.1 155.8L863.8 155.9L866.7 157.9L864.8 162.6L863.0 163.8L861.7 162.7L862.4 160.2L860.6 159.4L859.5 157.5L862.1 156.7L863.5 154.9L866.3 153.5L868.4 151.6L873.9 150.7L876.9 151.3L879.8 146.4L881.6 147.7L885.7 144.9L887.3 143.8L889.0 140.4L888.6 137.3L889.7 135.6L892.7 135.1L894.2 138.9L894.1 141.2L891.6 144.0L891.6 146.8ZM526.6 132.9L525.6 135.1L524.4 134.5L523.7 132.6L524.3 131.6L526.1 130.5L526.6 132.9ZM899.7 127.3L901.7 127.9L903.7 126.7L904.3 129.8L900.2 130.6L897.7 133.3L893.4 131.4L891.8 134.5L888.8 134.5L888.4 131.8L889.8 129.6L892.7 129.5L893.5 125.6L894.4 123.5L897.6 126.4L899.7 127.3ZM323.2 120.7L325.2 121.1L327.7 121.0L326.4 122.1L325.4 122.3L321.8 121.1L321.1 120.2L322.2 119.3L323.2 120.7ZM328.3 113.6L327.0 113.6L323.4 112.8L320.8 111.5L321.7 111.2L325.4 111.9L328.2 113.1L328.3 113.6ZM156.9 115.3L155.5 115.6L151.0 114.4L150.1 113.4L147.6 112.4L147.1 111.6L144.3 111.1L143.2 109.6L143.5 109.0L146.4 109.6L148.1 110.0L150.7 110.3L151.6 111.3L153.0 112.6L155.8 113.7L156.9 115.3ZM344.1 109.2L342.2 111.6L344.0 110.7L345.9 111.3L344.9 112.3L347.4 113.0L348.7 112.3L351.5 113.2L350.6 115.2L352.5 114.8L352.9 116.2L353.8 118.0L352.6 120.4L351.3 120.5L349.5 120.0L350.1 117.7L349.3 117.4L346.1 119.8L344.5 119.7L346.4 118.4L343.7 117.7L340.8 117.9L335.4 117.8L334.9 116.9L336.7 116.0L335.5 115.2L337.8 113.5L340.7 109.1L342.4 107.5L344.8 106.6L346.1 106.7L345.6 107.5L344.1 109.2ZM131.4 99.9L134.0 99.7L133.2 102.8L135.6 105.1L134.5 105.0L132.8 103.8L131.8 102.5L130.4 101.6L129.9 100.4L130.1 99.5L131.4 99.9ZM899.0 109.0L901.8 114.0L897.7 113.0L896.0 117.1L898.7 119.9L898.6 121.8L896.5 120.2L894.7 122.3L894.2 120.0L894.5 117.3L894.2 114.3L894.8 112.2L894.9 108.5L893.3 105.7L893.6 101.9L896.1 100.7L895.0 99.4L896.3 99.0L897.0 100.8L897.9 103.5L897.9 106.2L899.0 109.0ZM481.1 104.8L476.2 106.5L472.3 106.1L474.5 103.2L473.1 100.3L476.9 98.2L479.0 96.9L481.3 96.7L484.3 98.5L482.8 100.4L483.2 102.4L481.1 104.8ZM535.2 95.5L533.6 97.8L530.7 96.2L530.3 95.1L534.4 94.1L535.2 95.5ZM75.0 91.3L72.2 92.4L70.8 91.7L70.4 90.4L72.9 89.4L74.4 89.0L76.2 89.2L77.4 90.0L75.0 91.3ZM491.7 87.1L488.7 90.1L491.5 89.8L494.6 89.8L493.8 92.0L491.3 94.5L494.2 94.7L496.9 98.3L498.8 98.7L500.5 101.9L501.3 103.0L504.7 103.5L504.3 105.3L502.9 106.1L504.0 107.5L501.5 109.0L497.8 109.0L493.1 109.7L491.8 109.2L490.0 110.5L487.4 110.2L485.4 111.2L484.0 110.7L488.0 107.7L490.5 107.2L486.2 106.7L485.4 105.6L488.3 104.7L486.8 103.2L487.3 101.4L491.4 101.7L491.8 100.0L489.9 98.3L486.5 97.8L485.9 97.1L486.9 95.8L486.0 95.0L484.5 96.4L484.3 93.7L482.9 92.3L483.9 89.4L486.1 87.1L488.3 87.4L491.7 87.1ZM40.1 83.6L38.4 84.0L36.5 83.5L34.9 82.7L37.6 82.3L39.8 82.5L40.1 83.6ZM279.8 77.3L278.7 78.8L277.5 78.6L276.8 77.7L276.9 77.5L278.0 76.7L279.1 76.8L279.8 77.3ZM272.5 75.8L269.3 77.3L267.3 77.3L266.7 76.5L268.8 75.2L272.6 75.3L272.5 75.8ZM23.0 72.8L24.7 73.4L26.4 73.1L28.7 73.8L31.4 74.2L31.2 74.5L29.1 75.1L27.0 74.5L25.9 74.0L23.5 74.1L22.8 73.9L23.0 72.8ZM263.4 67.6L264.0 68.8L265.4 68.4L267.0 69.1L270.0 70.1L273.2 71.0L273.5 72.3L275.5 72.1L277.5 73.0L275.0 73.9L270.7 73.2L269.1 71.9L266.4 73.4L262.4 74.9L261.5 73.2L257.7 73.5L260.1 72.1L260.5 69.9L261.4 67.4L263.4 67.6ZM459.7 65.4L459.1 67.2L462.2 69.1L458.6 71.2L450.6 73.1L448.2 73.6L444.5 73.2L436.8 72.3L439.5 71.1L433.5 69.7L438.4 69.2L438.3 68.4L432.4 67.7L434.3 65.9L438.5 65.5L442.8 67.4L447.1 65.9L450.6 66.7L455.1 65.2L459.7 65.4ZM289.3 63.5L286.1 63.6L285.5 62.3L286.6 60.7L289.2 60.3L291.4 61.1L291.4 62.3L291.1 62.7L289.3 63.5ZM0.0 58.4L6.8 60.6L14.1 63.3L13.9 65.0L15.7 65.7L15.1 63.7L22.6 64.1L28.1 66.7L25.3 67.9L20.8 68.2L20.7 70.9L19.6 71.5L17.0 71.4L14.9 70.5L11.2 69.7L10.5 68.5L7.7 68.0L4.6 68.4L3.1 67.4L3.7 66.4L0.3 67.0L1.6 68.3L0.0 69.5L1000.0 69.5L996.4 70.7L992.8 70.5L995.3 72.0L997.0 74.3L998.2 75.0L998.6 76.2L997.9 76.9L992.7 76.3L984.9 78.4L982.4 78.7L978.2 80.7L974.2 82.4L973.1 83.7L969.2 81.7L961.9 83.9L960.7 82.9L958.0 84.1L954.3 83.7L953.4 85.5L950.0 88.2L950.1 89.3L953.3 90.0L952.9 94.0L950.4 94.1L949.2 96.4L950.3 97.6L945.5 99.0L944.5 102.2L940.4 102.9L939.5 105.7L935.5 108.3L934.5 106.4L933.3 102.3L931.8 96.2L933.1 92.3L935.4 90.7L935.6 89.4L939.9 88.7L944.9 85.2L949.6 82.4L954.6 80.2L956.9 76.2L953.5 76.5L951.8 78.8L944.8 81.8L942.5 78.4L935.3 79.4L928.4 84.0L930.7 85.7L924.5 86.4L920.2 86.7L920.4 84.7L916.1 84.3L912.6 85.7L904.1 85.2L895.0 86.0L886.0 91.4L875.3 98.0L879.7 98.3L881.1 100.1L883.8 100.7L885.6 99.3L888.6 99.5L892.6 102.5L892.7 104.9L890.5 107.7L890.3 111.0L889.1 115.4L884.9 119.4L883.9 121.4L880.2 124.6L876.4 127.8L874.6 129.5L870.9 131.1L869.2 131.1L867.4 129.8L863.7 131.8L863.3 132.7L862.2 132.6L861.0 133.5L860.2 134.4L860.3 136.4L858.9 137.0L858.4 137.5L857.3 138.4L855.5 138.8L854.3 139.6L854.2 140.8L853.8 141.1L855.0 141.5L856.5 142.7L858.9 146.0L859.6 147.8L859.6 151.0L858.6 152.5L856.1 153.1L853.8 154.2L851.3 154.5L851.0 153.0L851.6 150.9L850.3 148.0L852.4 147.5L850.5 145.1L849.1 144.6L848.8 145.1L848.0 145.4L847.9 144.8L847.2 144.6L846.4 144.1L847.2 142.9L847.8 142.6L847.6 142.1L848.3 140.6L848.1 140.1L846.5 139.8L845.2 139.1L841.3 139.9L839.3 141.2L836.3 142.0L837.7 140.7L837.2 139.6L839.4 137.7L837.9 136.3L835.5 137.2L832.3 139.2L830.6 141.0L827.9 141.1L826.5 142.4L827.9 144.3L830.2 144.7L830.3 146.0L832.5 146.8L835.6 144.8L838.1 145.9L839.9 146.0L840.3 147.4L836.4 148.2L835.1 149.7L832.4 151.1L831.0 153.0L834.0 154.6L835.1 157.3L836.7 159.8L838.6 162.0L838.6 164.0L836.8 164.8L837.5 166.3L839.1 167.1L838.7 169.4L838.0 171.6L836.5 171.8L834.4 174.9L832.2 178.5L829.6 181.8L825.8 184.4L821.9 186.7L818.8 187.0L817.1 188.3L816.1 187.4L814.6 188.7L810.7 190.1L807.7 190.6L806.8 193.5L805.2 193.7L804.5 191.6L805.2 190.6L801.4 189.7L800.1 190.1L796.4 192.5L794.1 195.1L793.5 197.1L795.6 200.0L798.2 203.6L800.7 205.3L802.4 207.6L803.7 212.7L803.3 217.6L801.0 219.4L797.8 221.2L795.6 223.5L792.1 226.1L791.1 224.3L791.9 222.4L789.8 220.9L787.5 220.5L786.4 219.0L785.0 216.1L782.5 214.9L780.1 214.9L780.5 212.7L778.0 212.8L777.8 215.8L776.3 219.9L775.4 222.3L775.6 224.3L777.4 224.4L778.6 227.0L779.1 229.4L780.6 231.0L782.3 231.3L783.7 232.7L784.4 233.0L786.0 234.7L787.2 236.5L787.3 238.4L787.0 239.7L787.3 240.6L787.5 242.2L788.5 243.0L789.6 245.5L789.5 246.4L787.6 246.6L784.9 244.5L781.6 242.3L781.3 240.9L779.7 239.1L779.3 236.8L778.3 235.2L778.6 233.2L778.0 232.0L776.9 231.0L776.4 229.6L775.0 228.0L773.6 226.7L773.2 228.3L772.6 226.8L772.9 225.1L773.8 222.4L773.5 220.3L774.3 218.2L773.4 216.6L773.6 213.5L772.5 212.1L771.6 208.8L771.1 205.3L769.9 203.0L768.1 204.4L764.9 206.3L763.4 206.1L761.6 205.5L762.6 202.0L762.0 199.4L759.8 196.2L760.2 195.2L758.5 194.8L756.6 192.6L755.8 191.1L755.6 189.7L755.1 188.4L753.9 186.8L751.4 186.7L751.6 187.8L750.8 189.3L749.6 188.8L749.2 189.3L748.4 189.0L747.3 188.7L746.9 189.7L745.0 189.7L741.6 190.3L741.8 192.4L740.3 194.0L736.3 195.9L733.2 199.2L731.1 200.9L728.3 202.7L728.3 204.0L726.9 204.7L724.4 205.7L723.1 205.8L722.3 208.0L722.9 211.6L723.0 213.9L721.8 216.5L721.8 221.2L720.4 221.4L719.1 223.5L720.0 224.4L717.4 225.2L716.5 227.1L715.4 227.9L712.8 225.3L711.5 221.4L710.4 218.6L709.4 217.3L708.0 214.6L707.3 211.1L706.8 209.4L704.3 205.6L703.1 200.2L702.3 196.6L702.3 193.3L701.7 190.7L697.7 192.3L695.7 192.0L692.1 188.6L693.5 187.6L692.6 186.5L689.4 184.2L687.3 183.5L686.5 181.5L684.4 179.4L679.2 179.9L674.7 179.9L670.8 180.3L665.6 179.5L662.6 178.9L659.4 178.5L658.3 175.1L656.9 174.6L654.8 175.1L652.0 176.4L648.6 175.5L645.8 173.4L643.1 172.6L641.3 170.0L639.2 166.3L637.7 166.7L635.9 165.8L634.9 166.9L633.3 166.7L633.8 168.0L633.6 168.6L634.5 170.7L635.6 173.1L636.9 173.7L637.4 174.7L639.3 175.9L639.5 177.0L639.2 177.9L639.6 178.9L640.4 179.6L640.7 180.6L641.1 181.2L641.0 179.2L641.7 177.8L642.5 177.5L643.3 178.3L643.4 180.0L642.7 181.6L643.3 182.7L643.8 182.5L643.9 183.3L646.0 182.8L648.3 182.9L650.0 183.0L651.9 181.1L654.0 179.3L655.8 177.6L656.6 176.7L656.9 176.9L656.6 178.1L656.3 178.6L656.7 180.8L657.9 182.7L659.5 183.7L661.5 184.0L663.1 184.5L664.4 186.1L665.1 187.1L666.1 187.4L666.1 188.0L665.1 189.7L664.7 190.5L663.5 191.4L662.5 193.3L661.2 193.1L660.6 193.8L660.2 195.2L660.5 197.0L660.3 197.4L659.0 197.4L657.2 198.4L657.0 199.8L656.3 200.3L654.6 200.3L653.5 201.0L653.5 202.1L652.2 202.9L650.7 202.7L648.8 203.6L647.5 203.7L645.5 204.5L645.0 205.7L644.9 206.7L642.1 207.8L637.7 209.1L635.2 211.1L634.0 211.3L633.2 211.1L631.5 212.2L629.8 212.8L627.4 212.9L626.7 213.1L626.1 213.8L625.4 214.0L625.0 214.7L623.6 214.7L622.7 215.0L620.8 214.9L620.1 213.3L620.1 211.8L619.7 210.9L619.1 208.9L618.3 207.7L618.9 207.6L618.6 206.3L619.0 205.8L618.8 204.6L618.5 203.4L617.6 202.6L617.4 201.5L616.0 200.5L614.5 198.1L613.7 195.9L611.8 194.0L610.6 193.5L608.7 190.9L608.4 188.9L608.5 187.3L606.9 184.2L605.6 183.1L604.1 182.5L603.2 180.9L603.4 180.3L602.6 178.9L601.8 178.3L600.7 176.2L599.0 174.0L597.6 172.0L596.2 172.1L596.6 170.5L596.8 169.6L597.1 168.5L597.0 168.1L596.2 169.2L595.6 171.3L594.9 172.7L594.2 173.2L593.3 172.3L592.0 171.1L590.1 167.1L589.8 167.3L590.9 170.3L592.6 173.1L594.7 177.4L595.8 178.9L596.7 180.5L599.1 183.5L598.6 184.0L598.7 185.8L601.9 188.3L602.4 188.9L603.3 191.6L602.7 192.1L603.1 195.0L604.1 198.3L605.2 199.0L606.7 200.0L608.3 203.2L609.1 205.8L610.6 207.1L614.4 209.7L615.9 211.3L617.4 212.9L618.3 213.9L619.7 214.7L620.3 215.6L620.2 216.7L618.7 217.4L619.8 218.2L620.8 218.7L621.3 219.8L622.5 221.0L623.9 221.0L626.5 220.3L629.6 220.0L632.0 219.1L633.4 218.9L634.4 218.4L636.0 218.3L636.9 218.2L638.1 217.8L639.6 217.6L640.9 216.6L642.0 216.6L642.0 217.4L641.8 219.0L641.8 220.4L641.2 221.4L640.4 224.5L639.1 227.5L637.4 231.1L635.0 235.2L632.6 238.3L629.3 242.1L626.6 244.3L622.4 247.1L619.8 249.2L616.8 252.6L616.1 254.0L615.5 254.7L613.6 255.8L612.9 256.9L611.8 257.1L611.4 259.1L610.6 260.2L610.0 262.1L608.9 263.0L607.6 266.4L607.8 268.0L609.6 269.0L609.6 269.7L608.9 271.4L609.0 272.2L608.9 273.6L609.8 275.3L611.0 278.1L612.0 278.7L612.4 279.9L612.3 282.7L612.7 285.1L612.8 289.4L613.3 290.8L612.4 292.8L611.4 294.7L609.6 296.4L607.0 297.5L603.9 298.9L600.8 301.8L599.7 302.3L597.8 304.3L596.6 305.0L596.4 306.9L597.7 309.0L598.3 310.7L598.3 311.5L598.8 311.4L598.7 314.1L598.3 315.4L598.9 315.9L598.5 317.0L597.3 318.0L595.0 318.9L591.7 320.4L590.5 321.5L590.7 322.6L591.4 322.8L591.2 324.3L590.5 326.3L590.2 328.6L589.5 329.9L587.6 331.3L587.0 331.7L585.8 333.1L585.1 334.5L583.5 336.5L580.3 339.4L578.4 341.0L576.3 342.3L573.4 343.4L572.0 343.5L571.6 344.3L569.9 343.9L568.5 344.4L565.5 343.9L563.9 344.2L562.7 344.1L559.8 345.2L557.5 345.6L555.8 346.7L554.5 346.7L553.3 345.7L552.4 345.7L551.2 344.4L551.0 344.8L550.7 344.1L550.7 342.4L549.8 340.6L550.7 340.1L550.6 338.0L548.8 335.4L547.4 333.0L547.4 333.0L545.4 329.4L543.3 327.3L542.3 325.3L541.6 322.5L541.0 320.5L540.0 316.3L540.0 312.9L539.6 311.4L538.5 310.3L537.1 308.0L535.6 304.6L535.0 302.9L532.8 300.2L532.6 298.1L532.3 296.3L532.7 293.9L533.7 291.3L533.8 290.1L534.7 287.6L535.4 286.5L537.0 284.7L537.9 283.4L538.2 281.4L538.0 279.8L537.2 278.8L536.4 277.1L535.8 275.5L535.9 274.9L536.8 273.8L535.9 271.1L535.4 269.2L534.0 267.5L534.2 266.9L533.8 266.1L533.1 264.0L530.8 261.1L528.0 258.2L526.1 256.0L524.4 253.1L524.5 252.2L525.1 251.3L525.8 249.3L526.4 247.2L525.8 246.8L526.8 243.7L527.2 241.5L526.1 239.6L524.9 239.2L524.3 237.9L523.6 237.5L523.6 236.7L520.7 237.7L519.7 237.6L518.6 238.2L516.4 238.2L514.9 236.4L514.0 234.4L512.0 232.6L509.9 232.6L507.5 232.6L505.2 232.9L502.9 233.5L498.6 235.2L497.0 236.1L494.5 236.9L492.1 236.1L490.8 236.2L488.9 235.6L487.1 235.6L483.8 236.1L481.9 236.9L479.1 237.9L478.6 237.9L477.8 237.9L475.0 236.6L472.5 234.5L470.1 232.9L468.2 231.1L467.5 230.9L465.5 229.8L464.0 228.3L463.5 227.3L463.2 225.3L462.0 223.6L460.9 222.5L460.2 222.2L459.5 221.6L459.2 220.4L458.8 219.8L458.0 219.3L456.5 218.2L455.3 218.0L454.7 217.2L454.7 216.8L453.9 216.2L453.7 215.6L453.2 213.5L453.6 212.2L452.4 210.1L451.0 209.1L452.3 208.6L453.6 206.6L454.3 205.2L454.0 203.7L454.8 202.3L455.1 199.7L454.8 197.0L454.5 195.6L454.8 194.2L454.1 192.9L452.6 191.7L452.7 190.5L452.9 189.2L453.9 188.5L454.8 187.0L454.6 186.1L455.6 184.1L457.2 182.3L458.1 181.9L458.8 180.3L458.9 178.8L459.9 177.1L461.7 176.1L463.5 173.2L464.9 172.1L467.5 171.8L469.7 169.9L471.1 169.2L473.4 166.9L472.7 163.4L473.8 161.0L474.2 159.5L476.0 157.7L478.7 156.4L480.8 155.2L482.7 152.4L483.5 150.7L485.6 150.7L487.2 151.9L489.9 151.7L492.8 152.3L494.0 152.3L496.6 150.8L499.6 150.3L501.4 149.2L504.1 148.3L508.8 147.8L513.4 147.6L514.8 148.0L517.4 146.9L520.4 146.9L521.5 147.5L523.4 147.4L526.4 146.2L528.4 146.6L528.3 148.0L530.6 147.0L530.8 147.5L529.4 148.9L529.4 150.1L530.4 150.8L530.0 153.2L528.2 154.6L528.7 156.2L530.2 156.2L530.9 157.5L531.9 158.0L535.2 158.9L536.3 158.7L538.7 159.1L542.3 160.4L543.6 162.8L546.1 163.4L550.1 164.5L553.0 165.9L554.4 165.2L555.7 163.9L555.1 161.8L555.9 160.4L557.9 159.1L559.8 158.8L563.6 159.3L564.5 160.6L565.6 160.6L566.5 161.1L569.2 161.4L569.9 162.3L573.6 162.3L576.3 163.0L579.0 163.8L580.3 164.3L582.5 163.4L583.6 162.6L586.0 162.3L588.0 162.7L588.8 164.1L589.4 163.2L591.6 163.8L593.8 164.0L595.2 163.3L596.0 162.4L595.8 162.2L596.5 160.9L597.1 158.8L597.5 158.1L597.6 158.1L598.6 155.8L599.9 153.9L600.0 153.8L599.7 151.6L600.4 150.5L599.4 149.2L600.4 148.2L598.8 148.4L596.4 147.8L594.5 149.4L590.3 149.7L588.1 148.2L585.1 148.1L584.4 149.3L582.5 149.6L579.8 148.1L576.8 148.2L575.1 145.4L573.1 143.9L574.5 141.7L572.7 140.4L575.8 137.7L580.1 137.6L581.2 135.5L586.5 135.9L589.9 134.1L593.1 133.3L597.7 133.2L602.5 135.2L606.5 136.3L609.8 135.8L612.1 136.1L615.4 134.6L615.8 133.4L615.1 131.5L613.5 130.5L612.0 130.2L611.0 129.3L607.4 127.0L604.3 126.0L601.9 124.3L603.9 123.9L606.2 121.6L604.6 120.5L608.7 119.3L608.7 118.7L606.2 119.2L604.0 119.4L602.1 120.3L599.5 120.4L597.1 121.5L597.3 123.2L598.6 123.9L601.5 123.7L600.9 124.7L597.9 125.2L594.1 126.8L592.6 126.2L593.2 124.9L590.1 124.1L590.6 123.6L593.3 122.6L592.5 122.0L588.2 121.3L588.0 120.3L585.4 120.6L584.4 122.1L582.2 124.2L582.3 124.9L580.9 125.5L580.1 125.2L579.3 128.6L577.9 129.7L576.9 131.7L577.8 133.3L578.1 134.4L580.5 135.3L580.0 136.0L576.7 136.1L575.5 137.0L573.2 138.5L572.3 137.2L572.4 136.6L570.7 136.5L569.2 136.3L565.9 137.0L567.8 138.5L566.4 139.0L564.8 139.0L563.4 137.6L562.9 138.2L563.5 139.8L564.9 141.1L563.8 141.7L565.4 143.0L566.7 143.8L566.8 145.4L564.2 144.7L565.0 146.1L563.3 146.4L564.3 148.8L562.5 148.9L560.2 147.7L559.2 145.4L558.7 143.6L557.6 142.3L556.2 140.7L556.0 139.9L555.5 139.7L555.4 139.1L553.9 138.2L553.7 136.9L553.9 135.0L554.3 134.1L553.8 133.7L553.2 133.5L552.4 132.6L551.2 132.0L548.6 131.0L547.0 130.0L544.5 129.1L542.2 127.1L542.7 126.9L541.4 125.7L541.4 124.8L539.6 124.3L538.8 125.5L537.9 124.6L538.0 123.7L538.1 123.6L538.7 123.4L536.5 123.0L534.2 123.9L534.4 125.3L534.1 126.1L535.0 127.5L537.6 128.9L539.0 131.2L542.1 133.5L544.2 133.4L544.9 134.1L544.1 134.6L546.6 135.6L548.7 136.5L551.0 137.9L551.3 138.4L550.8 139.4L549.3 138.1L546.9 137.7L545.7 139.5L547.7 140.5L547.4 141.9L546.2 142.1L544.7 144.5L543.6 144.7L543.6 143.8L544.1 142.4L544.7 141.8L543.7 140.2L542.8 138.8L541.7 138.4L540.8 137.2L539.1 136.7L537.9 135.6L535.8 135.4L533.6 134.2L531.1 132.3L529.2 130.7L528.3 128.0L527.0 127.7L524.7 126.8L523.4 127.1L521.8 128.4L520.7 128.6L518.1 130.2L512.7 129.4L508.6 130.3L508.3 132.0L508.4 133.6L505.8 135.5L502.3 136.1L502.0 137.0L500.3 138.5L499.2 140.8L500.3 142.4L498.7 143.6L498.1 145.4L496.0 146.0L494.0 148.1L490.5 148.2L487.9 148.1L486.1 149.1L485.1 150.1L483.7 149.9L482.7 149.0L481.9 147.4L479.3 146.9L478.2 147.7L476.7 147.3L475.3 147.6L475.7 145.4L475.4 143.7L474.2 143.4L473.5 142.4L473.8 140.6L474.9 139.6L475.1 138.4L475.6 136.8L475.6 135.6L475.0 134.6L474.9 133.7L475.0 131.7L473.9 130.5L477.8 128.5L481.2 129.0L485.0 129.0L487.9 129.4L490.2 129.3L494.7 129.4L496.2 127.7L496.7 122.2L493.8 119.3L491.8 117.9L487.5 116.8L487.2 114.8L490.8 114.2L495.5 114.9L494.6 111.7L497.3 112.9L503.7 110.8L504.6 108.5L507.0 107.9L509.2 107.4L510.6 106.6L513.1 102.5L516.9 101.4L519.2 101.4L519.7 100.9L522.0 100.7L522.6 101.3L524.4 99.9L523.8 98.9L523.7 97.3L522.6 95.8L522.5 92.9L522.9 92.2L523.7 91.4L526.2 91.2L527.2 90.4L529.4 89.6L529.3 91.1L528.5 92.0L528.8 92.8L530.3 93.2L529.6 94.2L528.8 93.9L526.8 95.9L527.6 97.3L527.6 98.3L530.4 99.0L530.4 100.0L533.2 99.5L534.8 98.7L537.9 99.8L539.2 100.7L541.1 99.9L545.5 98.6L549.0 97.6L551.7 98.1L551.9 98.8L554.6 98.8L555.2 97.6L559.1 96.7L558.5 94.4L558.6 92.3L559.9 90.5L562.6 89.6L564.8 91.7L567.0 91.6L567.5 89.5L567.9 87.8L566.8 88.2L565.1 87.2L564.8 85.6L568.3 84.8L571.8 84.4L574.9 84.9L577.7 84.8L580.9 83.3L578.0 81.9L572.9 82.2L568.0 83.2L563.5 83.8L561.9 82.2L559.2 81.3L559.8 78.6L558.5 76.1L559.8 74.5L562.3 72.7L568.7 69.7L570.5 69.1L570.3 68.0L566.4 66.6L561.6 67.4L558.9 69.4L559.4 71.1L554.9 73.3L549.6 75.7L547.6 79.6L549.5 81.6L552.2 83.1L549.6 86.2L546.7 86.9L545.7 91.6L544.1 94.2L540.7 93.9L539.2 96.1L536.0 96.2L535.1 93.6L532.7 90.4L530.6 86.5L528.8 84.8L523.3 88.0L519.6 88.7L515.7 87.3L514.7 84.3L513.9 77.9L516.4 76.1L523.8 73.7L529.2 70.9L534.3 67.0L541.0 61.6L545.7 59.5L553.3 56.1L559.4 54.8L564.0 55.0L568.2 52.7L573.2 52.8L578.2 52.3L586.9 54.3L583.3 55.0L586.4 56.8L589.3 55.8L593.8 57.5L601.4 58.2L611.9 61.3L614.1 62.6L614.2 64.5L611.2 65.9L606.6 66.7L594.2 64.6L592.2 64.9L596.7 66.9L597.1 71.1L600.6 71.9L602.8 72.6L603.2 71.3L601.4 70.1L603.3 69.0L610.0 70.8L612.3 70.1L610.5 68.1L616.9 65.3L619.5 65.5L622.1 66.5L623.7 64.6L621.4 62.9L622.7 61.2L620.7 59.5L628.5 60.4L630.1 62.0L626.5 62.3L626.6 63.9L628.7 64.8L633.0 64.2L633.7 62.4L639.5 61.1L649.2 58.7L651.3 58.9L648.6 60.6L652.0 60.8L654.0 59.9L659.2 59.8L663.3 58.7L666.5 60.3L669.7 58.5L666.7 56.9L668.2 56.0L676.4 56.8L680.2 57.7L690.3 60.9L692.2 59.4L689.3 57.9L689.3 57.3L685.9 57.1L686.8 55.8L685.3 53.6L685.3 52.7L690.4 50.2L692.2 47.7L694.3 47.1L701.6 47.8L702.2 49.4L699.6 51.6L701.3 52.5L702.2 54.5L701.6 58.3L704.6 60.0L703.4 61.8L698.0 65.8L701.2 66.2L702.3 65.2L705.3 64.5L706.1 63.1L708.5 61.8L706.9 60.2L708.2 58.4L705.1 58.1L704.4 56.6L706.7 53.8L703.1 51.5L708.0 49.7L707.4 47.7L708.8 47.6L710.2 49.2L709.1 51.8L712.1 52.4L710.8 50.4L715.5 49.3L721.3 49.1L726.4 50.7L723.9 48.4L723.6 45.4L728.5 44.9L735.2 45.0L741.2 44.6L738.9 43.2L742.1 41.3L745.3 41.3L750.7 39.9L758.1 39.5L759.0 38.8L766.3 38.5L768.5 39.1L774.8 37.6L779.9 37.7L780.7 36.5L783.3 35.3L789.9 34.2L794.6 35.1L790.8 35.8L797.1 36.2L797.9 37.6L800.4 36.9L808.5 36.9L814.8 38.3L817.0 39.3L816.3 40.8L813.3 41.6L806.0 43.1L803.9 43.9L807.3 44.3L811.4 45.0L813.9 44.5L815.4 46.3L816.6 45.6L821.0 45.1L829.9 45.6L830.6 46.9L842.2 47.3L842.4 45.2L848.3 45.7L852.7 45.7L857.2 47.1L858.5 48.9L856.8 50.1L860.3 52.2L864.7 53.4L867.4 50.5L871.8 51.7L876.6 51.0L881.9 51.8L884.0 51.0L888.5 51.4L886.5 48.8L890.2 47.6L915.3 49.4L917.6 51.1L924.9 53.2L936.1 52.7L941.7 53.1L944.0 54.3L943.6 56.3L947.1 57.1L950.8 56.5L955.7 56.5L960.9 57.0L966.2 56.7L971.0 59.2L974.5 58.3L972.2 56.5L973.5 55.3L982.3 56.1L988.1 55.9L996.1 57.2L0.0 58.4ZM636.4 135.3L637.8 137.3L639.1 137.4L640.0 138.2L637.7 138.4L637.2 140.6L636.7 141.5L635.7 142.2L635.8 143.6L636.7 145.6L639.3 146.2L641.2 147.6L645.2 148.1L649.5 147.3L649.8 146.7L649.3 144.7L649.7 141.8L647.5 140.9L648.2 139.0L646.4 138.8L647.0 136.5L649.6 137.1L652.0 136.2L650.0 134.6L649.2 133.0L647.0 133.7L646.7 135.7L645.8 133.9L645.7 133.3L646.4 132.1L645.8 131.1L642.6 130.2L641.4 127.7L639.8 127.0L639.7 126.1L642.4 126.3L642.5 124.3L644.9 123.9L647.3 124.3L647.8 121.6L647.3 119.9L644.6 120.0L642.2 119.3L639.0 120.5L636.4 121.1L635.1 122.8L632.4 123.2L629.7 126.1L632.2 128.7L631.9 130.6L635.0 133.9L636.4 135.3ZM234.3 58.0L232.6 59.0L228.8 58.2L226.6 58.5L222.8 57.2L225.2 56.4L227.2 55.2L230.1 55.9L231.8 56.4L232.6 57.0L234.3 58.0ZM0.0 51.3L1000.0 53.2L996.9 53.4L996.5 52.5L0.0 51.3ZM0.0 51.3L0.4 51.2L2.7 51.2L6.7 52.0L6.5 52.4L3.6 53.1L0.0 53.2L0.0 51.3ZM248.5 57.0L248.5 59.8L252.2 57.6L255.5 59.4L254.7 61.5L257.4 63.3L260.3 61.3L262.3 58.9L262.4 55.9L266.4 56.1L270.5 56.5L274.2 57.9L274.4 59.3L272.3 60.7L274.3 62.2L273.9 63.6L268.5 65.5L264.6 66.0L261.8 65.1L260.9 66.5L258.2 68.9L257.4 70.1L254.2 71.9L250.2 72.1L248.0 73.3L247.9 75.1L244.6 75.5L241.2 77.7L238.2 80.8L237.1 83.0L237.0 86.3L241.1 86.7L242.3 89.3L243.6 91.4L247.5 90.9L252.7 92.1L255.4 93.1L257.4 94.4L260.9 95.2L263.9 96.4L268.4 96.5L271.5 96.8L271.0 99.2L271.9 102.0L273.9 105.1L278.0 107.8L280.2 106.9L281.7 104.0L280.2 99.6L278.3 98.1L282.7 96.8L285.8 94.9L287.4 93.0L287.2 91.1L285.3 88.7L281.9 86.7L285.2 83.7L284.0 81.2L283.0 76.9L285.0 76.2L289.7 77.0L292.6 77.3L294.9 76.5L297.5 77.5L300.9 79.1L301.7 80.2L306.7 80.4L306.6 82.7L307.5 86.2L310.1 86.7L312.1 88.3L316.1 86.8L318.8 83.7L320.6 82.4L322.8 84.9L326.4 88.4L329.5 91.8L328.3 93.5L332.0 95.1L334.5 96.7L339.0 97.4L340.7 98.3L341.8 100.6L344.0 101.0L345.1 102.0L345.3 105.1L343.3 106.2L341.3 107.2L336.7 108.2L333.2 110.4L328.5 110.9L322.6 110.3L318.4 110.3L315.6 110.5L313.2 112.5L309.7 113.7L305.7 117.4L302.5 119.9L304.8 119.5L309.3 115.8L315.1 113.5L319.3 113.2L321.7 114.6L319.1 116.5L320.0 119.5L320.9 121.6L324.5 122.9L329.1 122.5L331.9 119.4L332.1 121.4L333.9 122.4L330.4 124.3L324.3 125.9L321.5 127.0L318.4 129.0L316.3 128.8L316.2 126.5L321.0 124.2L316.6 124.3L313.5 124.6L314.0 125.5L311.0 126.9L308.2 127.8L305.2 128.7L303.6 130.5L303.3 130.9L303.3 132.4L304.2 133.9L305.3 133.9L305.0 132.9L305.9 133.5L305.7 134.3L303.8 134.8L302.4 134.7L300.4 135.2L299.2 135.4L297.6 135.5L295.3 136.3L299.3 135.8L300.2 136.3L296.3 137.1L294.5 137.1L294.6 136.8L293.7 137.6L294.6 137.7L294.0 139.7L291.9 141.8L291.7 141.1L291.1 141.0L290.2 140.3L290.8 141.8L291.4 142.3L291.5 143.3L290.6 144.4L289.1 146.6L288.8 146.5L289.7 144.6L288.2 143.6L287.9 141.3L287.4 142.5L288.0 144.2L286.2 143.8L288.1 144.7L288.2 147.3L289.0 147.5L289.3 148.5L289.6 151.2L287.9 153.3L285.0 154.1L283.2 155.8L281.8 155.9L280.4 157.0L280.0 157.9L276.9 159.7L275.4 161.0L274.1 162.7L273.6 164.6L274.1 166.6L275.1 168.9L276.3 170.9L276.3 172.1L277.6 175.3L277.5 177.2L277.4 178.3L276.7 180.0L275.9 180.3L274.5 180.0L274.1 178.8L273.0 178.1L271.6 175.8L270.3 173.6L269.8 172.5L270.4 170.7L269.6 169.2L267.5 166.8L266.4 166.4L263.6 167.7L263.1 167.5L261.7 166.2L260.0 165.6L256.9 165.9L254.4 165.6L252.3 165.8L251.1 166.2L251.6 167.0L251.6 168.1L252.2 168.6L251.6 169.0L250.6 168.6L249.6 169.1L247.6 169.0L245.5 167.6L243.1 167.9L241.0 167.3L239.3 167.5L237.0 168.1L234.4 170.2L231.7 171.4L230.2 172.7L229.5 173.9L229.5 175.9L229.6 177.2L230.2 178.1L230.2 178.1L230.2 178.1L229.1 180.6L228.6 182.6L228.4 186.3L228.1 187.7L228.6 189.2L229.5 190.5L230.0 192.7L231.9 194.7L232.5 196.3L233.6 197.7L236.6 198.4L237.7 199.6L240.1 198.8L242.3 198.5L244.3 198.0L246.1 197.6L247.9 196.4L248.5 194.8L248.7 192.5L249.2 191.7L251.1 190.9L254.0 190.3L256.5 190.4L258.2 190.2L258.9 190.7L258.8 192.1L257.3 193.7L256.6 195.4L257.1 195.9L256.7 197.1L256.0 199.3L255.3 198.6L254.7 198.6L254.7 199.0L255.3 199.0L255.2 199.8L254.8 201.0L255.0 201.4L254.7 202.4L254.9 202.7L254.6 204.1L254.0 204.8L253.5 204.9L253.0 205.9L253.9 206.4L254.1 206.0L254.9 206.3L255.2 206.4L255.8 205.9L256.6 205.9L256.9 206.1L257.3 206.0L258.6 206.2L259.9 206.2L260.8 205.9L261.1 205.5L262.0 205.7L262.7 205.9L263.4 205.8L263.9 205.6L265.2 206.0L265.6 206.0L266.5 206.5L267.3 207.2L268.3 207.6L269.0 208.3L268.8 208.6L268.7 209.2L268.9 210.2L268.3 211.2L268.0 212.3L267.9 213.5L268.1 214.3L268.1 215.5L267.7 215.8L267.4 217.0L267.6 217.7L267.1 218.4L267.2 219.2L267.6 219.6L268.3 221.1L269.4 222.2L270.7 223.4L271.7 224.4L271.6 225.0L272.8 225.1L273.0 224.9L273.8 225.6L275.1 225.4L276.3 224.7L278.0 224.1L279.0 223.3L280.5 223.5L280.4 223.7L281.9 223.8L283.2 224.3L284.1 225.1L285.1 225.9L286.6 226.0L288.7 224.1L289.8 223.8L289.8 222.9L290.3 220.5L291.9 219.2L293.7 219.2L293.9 218.6L296.1 218.8L298.3 217.4L299.3 216.8L300.7 215.5L301.7 215.6L302.4 216.4L301.9 217.3L301.8 217.9L300.1 218.3L301.1 219.5L301.0 221.0L299.8 222.6L300.8 224.8L302.0 224.6L302.7 222.6L301.8 221.6L301.7 219.5L305.1 218.4L304.7 217.1L305.7 216.2L306.7 218.2L308.7 218.2L310.5 219.8L310.6 220.7L313.1 220.7L316.0 220.4L317.6 221.7L319.8 222.0L321.3 221.1L321.3 220.4L324.8 220.3L328.1 220.2L325.8 221.1L326.7 222.4L328.9 222.6L331.0 223.9L331.5 226.2L332.9 226.1L334.0 226.8L335.8 227.8L337.5 229.6L337.6 231.0L338.7 231.1L340.2 232.4L341.3 233.4L344.6 234.0L344.9 233.5L347.1 233.3L350.1 234.0L351.1 234.3L353.1 235.0L356.0 237.3L356.5 238.5L357.5 238.3L358.1 239.9L359.7 244.7L361.2 245.2L361.3 247.1L359.2 249.4L360.0 250.2L364.9 250.7L365.0 253.4L367.2 251.6L370.6 252.6L375.3 254.3L376.6 255.9L376.2 257.5L379.4 256.6L384.8 258.1L388.9 258.0L393.1 260.3L396.6 263.4L398.7 264.2L401.1 264.3L402.1 265.2L403.1 268.7L403.5 270.4L402.4 275.0L401.0 276.8L397.1 280.7L395.3 283.8L393.3 286.2L392.6 286.3L391.8 288.3L392.0 293.5L391.2 297.8L390.9 299.6L390.0 300.7L389.6 304.4L386.7 308.1L386.3 310.9L384.0 312.1L383.4 313.8L380.3 313.8L376.0 314.9L374.0 316.1L370.9 316.9L367.6 319.1L365.3 321.9L364.9 324.0L365.3 325.5L364.8 328.3L364.2 329.7L362.3 331.2L359.2 336.1L356.7 338.3L354.8 339.6L353.6 342.2L351.7 343.8L350.5 345.5L347.4 347.1L345.4 346.5L343.8 346.8L341.3 345.6L339.4 345.7L337.7 344.2L337.5 345.6L341.0 348.0L340.7 349.9L342.4 351.1L342.3 352.5L339.6 356.1L335.5 357.6L329.9 358.1L326.8 357.9L327.4 359.5L326.9 361.6L327.4 363.0L325.7 364.0L322.9 364.4L320.2 363.3L319.1 364.1L319.5 366.8L321.4 367.7L322.9 366.8L323.7 368.2L321.2 369.1L318.9 370.8L318.5 373.6L317.9 375.1L315.3 375.1L313.1 376.5L312.3 378.6L315.0 380.6L317.7 381.2L316.7 383.7L313.4 385.3L311.6 388.5L309.1 389.6L308.0 390.9L308.8 393.8L310.7 395.4L309.5 395.3L307.1 395.3L305.7 395.9L303.2 396.9L302.8 399.5L301.6 399.6L298.5 398.7L295.3 396.8L291.8 395.2L290.9 393.4L291.7 391.8L290.3 389.9L290.0 385.2L291.2 382.5L294.1 380.4L289.9 379.6L292.5 377.1L293.5 372.5L296.6 373.5L298.0 367.7L296.1 367.0L295.3 370.5L293.5 370.1L294.4 366.1L295.3 361.0L296.6 359.1L295.8 356.3L295.6 353.2L296.8 353.1L298.5 348.6L300.4 344.2L301.6 340.1L300.9 335.9L301.8 333.6L301.4 330.2L303.0 326.8L303.5 321.4L304.4 315.6L305.3 309.4L305.1 304.9L304.5 301.0L301.7 299.4L301.5 298.2L296.0 295.4L291.0 292.4L288.9 290.7L287.7 288.4L288.2 287.6L285.8 284.0L283.1 278.8L280.5 273.3L279.3 272.0L278.4 270.0L276.3 268.2L274.3 267.0L275.2 265.8L273.9 263.2L274.7 261.2L276.9 259.5L278.4 257.4L277.8 256.2L276.8 257.5L275.1 256.2L275.7 255.5L275.2 252.9L276.2 252.5L276.7 250.8L277.7 249.0L277.5 247.9L279.1 247.3L281.0 246.2L280.6 245.3L281.6 245.1L281.5 243.7L282.1 242.7L283.5 242.5L284.7 240.8L285.8 239.3L284.7 238.6L285.3 237.0L284.6 234.5L285.2 233.8L284.8 231.4L283.7 229.9L282.7 229.1L282.1 227.6L282.8 226.9L282.1 226.7L281.6 225.8L280.2 225.0L279.0 225.2L278.4 226.2L277.3 226.9L276.7 226.9L276.4 227.5L277.8 229.0L277.0 229.4L276.6 229.8L275.3 229.9L274.8 228.3L274.5 228.8L273.6 228.6L273.0 227.5L271.9 227.3L271.1 227.0L269.9 227.0L269.9 227.6L269.5 227.2L268.0 226.5L267.5 226.0L267.8 225.5L267.7 224.9L266.9 224.2L265.8 223.6L264.9 223.3L264.7 222.5L264.0 222.0L264.1 222.8L263.6 223.5L262.9 222.7L262.1 222.4L261.7 221.8L261.7 221.0L262.1 220.1L261.3 219.7L261.9 219.2L261.0 218.3L259.7 217.2L259.0 216.3L257.9 215.4L256.5 214.1L256.8 213.7L257.2 214.1L257.5 213.9L257.0 213.1L256.1 212.8L255.8 213.5L254.2 213.4L253.2 213.2L252.1 212.6L250.5 212.4L249.7 211.8L248.3 211.4L246.6 211.3L245.3 210.8L243.8 209.6L240.7 206.6L239.2 205.7L237.0 205.0L235.4 205.2L233.2 206.2L231.8 206.5L229.8 205.8L227.7 205.3L225.1 204.0L223.1 203.6L219.9 202.3L217.6 201.0L216.9 200.2L215.3 200.1L212.5 199.2L211.3 197.9L208.4 196.3L207.0 194.6L206.3 193.2L207.2 193.0L206.9 192.2L207.6 191.5L207.6 190.5L206.7 189.2L206.4 188.1L205.5 186.7L203.0 184.0L200.2 181.8L198.9 180.1L196.5 178.9L196.0 178.3L196.4 176.5L195.0 175.9L193.4 174.5L192.7 172.6L191.2 172.4L189.6 170.9L188.3 169.6L188.1 168.7L186.6 166.6L185.7 164.5L185.7 163.4L183.7 162.3L182.8 162.4L181.2 161.7L180.7 162.8L181.2 164.1L181.5 166.2L182.4 167.4L184.5 169.3L184.9 169.9L185.4 170.1L185.7 171.1L186.2 171.0L186.8 172.8L187.6 173.5L188.2 174.5L190.0 175.9L190.9 178.5L191.7 179.7L192.5 181.0L192.6 182.5L194.0 182.6L195.1 183.9L196.1 185.1L196.0 185.6L194.9 186.6L194.4 186.6L193.6 184.9L191.8 183.3L189.8 182.0L188.4 181.3L188.5 179.2L188.1 177.7L186.7 176.9L184.8 175.6L184.5 176.0L183.8 175.3L182.0 174.6L180.4 173.0L180.6 172.8L181.8 172.9L182.8 171.9L182.9 170.7L180.7 168.7L179.1 167.9L178.1 166.2L177.1 164.3L175.8 162.1L174.6 159.6L174.2 158.2L172.4 156.6L171.1 156.3L170.8 155.5L169.2 155.3L168.2 154.6L165.6 154.3L164.9 153.9L164.6 152.3L161.9 149.5L159.6 145.7L159.7 145.0L158.5 144.1L156.3 141.8L155.9 139.5L154.5 138.0L155.1 135.7L155.0 133.3L154.1 131.2L155.2 128.6L155.8 123.5L155.3 119.8L154.5 117.4L153.7 116.2L154.0 115.6L158.0 116.6L159.5 119.2L160.2 118.4L159.7 116.2L158.8 113.9L158.4 113.9L153.0 111.2L151.0 110.0L146.0 108.8L144.5 106.3L144.9 104.6L141.3 103.5L140.8 101.2L137.5 99.2L137.4 97.8L135.9 96.7L133.4 95.8L132.6 93.4L129.1 91.2L127.6 88.5L124.9 88.4L120.5 88.3L117.2 87.5L111.5 84.6L108.8 84.1L104.0 83.1L100.1 83.3L94.7 82.1L91.4 80.9L88.3 81.5L88.8 83.4L87.3 83.6L84.1 84.2L81.6 85.1L78.6 85.7L78.2 84.0L79.4 81.3L82.4 80.5L81.6 79.8L78.1 81.3L76.2 83.2L72.2 85.1L74.2 86.5L71.6 88.5L68.6 89.6L65.8 90.5L65.1 91.7L60.8 93.2L59.9 94.5L56.7 95.6L54.8 95.4L52.2 96.2L49.3 97.2L47.0 98.1L42.3 98.9L41.8 98.4L44.9 97.1L47.6 96.3L50.5 94.7L54.0 94.4L55.4 93.3L59.2 91.6L59.8 91.1L61.9 90.1L62.4 88.0L63.8 86.3L60.6 87.2L59.7 86.7L58.2 87.7L56.4 86.3L55.6 87.3L54.6 85.9L51.8 87.0L50.1 87.0L49.9 85.4L50.4 84.3L48.6 83.4L45.0 83.9L42.6 82.6L40.7 81.9L40.7 80.4L38.6 79.2L39.6 77.6L41.9 76.0L42.9 74.6L45.1 74.4L47.0 74.8L49.3 73.5L51.3 73.7L53.4 72.9L52.9 71.6L51.3 71.1L53.4 70.0L51.7 70.1L48.7 70.7L47.9 71.3L45.7 70.7L41.8 71.0L37.7 70.3L36.5 69.2L33.0 67.6L36.9 66.4L43.1 65.1L45.4 65.1L45.0 66.4L50.9 66.3L48.6 64.6L45.2 63.6L43.3 62.2L40.6 61.0L36.8 60.1L38.3 58.7L43.3 58.6L46.8 57.3L47.4 56.0L50.3 54.6L53.0 54.3L58.2 53.1L60.8 53.3L65.1 51.8L69.3 52.4L71.3 53.6L72.5 53.1L77.2 53.2L77.0 53.9L81.3 54.4L84.1 54.1L90.0 55.0L95.3 55.2L97.4 55.6L101.1 55.1L105.4 56.0L108.4 56.4L113.6 57.0L117.9 58.4L120.8 58.6L123.3 57.5L126.6 56.6L130.8 56.9L134.9 55.7L139.5 55.0L141.4 56.2L143.4 55.5L144.1 54.2L146.0 54.5L150.7 57.0L154.4 55.1L154.8 57.2L158.2 56.8L159.2 56.0L162.6 56.1L166.8 57.3L173.3 58.3L177.2 58.8L179.9 58.6L183.6 60.0L179.7 61.4L184.7 62.0L192.2 61.6L194.6 61.2L197.6 62.8L200.6 61.4L197.7 60.2L199.5 59.3L202.9 59.2L205.1 58.9L207.4 59.6L210.2 61.1L213.3 60.8L218.2 62.1L222.5 61.6L226.5 61.7L226.2 60.0L228.7 59.5L233.0 60.4L233.0 63.1L234.8 60.9L237.0 60.9L238.2 58.1L235.3 56.4L232.0 55.3L232.2 52.2L235.5 50.2L239.2 50.7L242.0 51.9L245.8 55.0L243.3 56.4L248.5 57.0ZM182.9 46.9L181.5 48.2L187.7 47.3L191.5 48.7L194.7 47.3L197.2 48.2L199.5 51.0L200.9 49.8L198.9 47.0L201.3 46.6L204.1 47.0L207.2 48.1L209.0 50.8L209.8 52.8L214.5 54.2L219.5 55.5L219.2 56.7L214.6 56.9L216.4 58.0L215.5 59.0L210.4 58.6L205.7 57.8L202.4 58.0L197.2 58.9L189.0 59.4L185.2 59.6L183.7 58.3L179.9 57.6L177.5 57.9L174.1 55.7L175.9 55.4L180.2 54.9L184.1 55.0L187.7 54.5L182.4 53.9L176.4 54.1L172.5 54.1L171.0 53.0L177.5 51.9L173.2 52.0L168.3 51.2L170.7 49.1L172.6 48.0L180.0 46.4L182.9 46.9ZM209.7 46.1L207.3 47.9L202.9 45.9L203.9 45.6L207.6 45.4L209.7 46.1ZM287.9 46.9L288.2 47.7L285.2 47.6L282.2 47.6L279.2 47.9L278.4 47.8L275.3 46.3L275.5 45.3L276.8 45.1L283.2 45.4L287.9 46.9ZM259.6 46.8L261.7 48.5L264.3 46.3L271.3 45.1L276.1 48.0L275.7 49.8L281.2 49.0L283.8 47.9L290.0 49.3L293.8 50.6L294.2 51.9L299.3 51.2L302.2 53.0L308.9 54.1L311.3 55.2L314.0 57.8L308.9 59.1L315.4 60.9L319.8 61.5L323.8 64.1L328.2 64.3L327.3 66.2L322.5 69.4L319.0 68.3L314.7 65.6L311.1 65.9L310.7 67.5L313.6 69.1L317.4 70.4L318.6 71.2L320.4 73.9L319.4 75.9L315.9 75.2L308.9 72.9L312.9 75.3L315.8 77.0L316.2 78.0L308.7 76.9L302.7 75.2L299.3 73.9L300.3 73.1L296.2 71.7L292.1 70.3L292.2 71.1L284.1 71.6L281.8 70.6L283.6 68.6L288.8 68.5L294.6 68.2L293.6 67.2L294.6 65.8L298.2 63.1L297.4 61.9L296.4 60.9L292.1 59.6L286.5 58.6L288.3 57.9L285.3 56.2L282.9 56.0L280.7 55.1L279.2 55.9L274.2 56.3L264.0 55.7L258.2 54.8L253.7 54.4L251.4 53.4L254.3 52.2L250.3 52.2L249.4 49.3L251.6 46.9L254.4 45.7L261.6 45.0L259.6 46.8ZM221.2 44.9L224.5 45.5L229.5 45.1L230.2 45.9L227.6 47.2L231.8 48.4L231.3 50.9L226.8 52.0L224.1 51.8L222.2 50.7L215.3 48.6L215.3 47.7L221.0 48.0L217.9 46.2L221.2 44.9ZM898.9 46.6L894.7 46.7L889.0 46.3L888.5 46.2L891.1 45.1L894.6 44.8L898.6 45.9L898.9 46.6ZM241.1 47.9L238.1 49.9L235.0 49.8L233.2 47.4L233.3 46.0L234.7 44.8L237.5 44.1L243.3 44.2L248.6 44.8L244.4 47.3L241.1 47.9ZM165.4 51.7L158.1 53.1L156.6 51.8L150.2 50.4L151.1 49.5L153.3 47.2L155.7 45.3L153.0 43.6L162.4 43.2L166.4 43.8L173.5 43.9L176.2 44.7L179.1 45.9L175.6 46.6L168.8 48.6L165.4 50.5L165.4 51.7ZM918.7 41.4L915.5 42.5L911.0 42.3L905.9 41.2L906.5 40.3L911.7 40.7L918.7 41.4ZM240.0 41.7L238.5 42.8L234.4 42.6L231.1 41.9L232.5 40.6L236.5 39.9L239.0 40.8L240.0 41.7ZM903.0 40.1L900.8 42.2L890.6 42.1L886.0 42.7L880.5 40.9L882.0 39.0L885.6 38.5L893.0 38.6L903.0 40.1ZM226.4 36.9L228.5 38.2L228.6 39.6L227.3 41.7L222.8 42.0L219.8 41.5L219.8 39.9L215.3 40.1L215.1 38.0L218.1 38.0L222.3 37.1L226.2 37.3L226.4 36.9ZM199.4 38.3L200.5 39.3L203.0 38.9L205.9 39.0L206.4 40.3L204.7 41.7L195.3 42.1L188.3 43.3L184.0 43.4L183.7 42.4L189.5 41.2L176.9 41.5L173.0 41.0L176.8 38.3L179.4 37.6L187.3 38.5L192.2 40.1L197.0 40.4L193.1 37.7L195.6 36.7L198.5 37.0L199.4 38.3ZM659.8 53.6L658.2 53.8L649.1 53.4L648.4 52.2L643.3 51.5L642.9 50.0L645.8 49.4L645.7 47.8L651.2 45.5L648.6 45.1L655.3 42.7L654.5 41.4L660.7 40.0L669.9 38.2L679.2 37.7L683.9 36.6L689.3 36.3L691.3 37.4L689.4 38.2L679.5 39.6L671.1 40.9L662.4 43.6L658.3 46.3L653.9 49.0L654.5 51.3L659.8 53.6ZM237.0 35.8L240.1 36.7L245.5 36.7L247.9 37.6L247.3 38.7L250.5 39.3L252.3 40.0L256.0 40.1L260.1 40.3L264.5 39.7L270.1 39.5L274.6 39.7L277.6 40.7L278.2 41.9L276.5 42.6L272.4 43.2L268.8 42.9L260.8 43.3L255.1 43.4L250.7 43.0L243.3 42.1L242.3 40.6L242.0 39.2L239.2 38.0L233.4 37.7L230.2 36.8L231.3 35.7L237.0 35.8ZM177.2 34.3L176.8 36.5L174.7 37.4L172.1 37.6L166.9 38.7L162.5 39.2L158.7 38.6L163.5 36.5L169.2 34.7L173.4 34.7L177.2 34.3ZM239.3 34.7L238.1 34.7L232.9 34.6L232.1 33.8L237.7 33.8L239.7 34.3L239.3 34.7ZM193.9 34.2L188.7 35.0L184.6 34.1L186.9 33.2L190.9 32.9L194.9 33.3L193.9 34.2ZM568.7 33.7L562.5 34.9L557.6 34.2L559.5 33.5L557.8 32.6L563.6 32.1L564.7 33.1L568.7 33.7ZM195.4 31.7L192.0 32.2L187.4 32.2L187.4 31.8L190.3 31.0L191.8 31.1L195.4 31.7ZM233.8 33.2L229.7 33.8L227.4 33.1L226.2 32.1L226.0 30.9L229.6 31.0L231.2 31.2L234.6 32.2L233.8 33.2ZM222.1 32.4L223.1 33.6L218.6 33.3L214.0 32.4L207.8 32.3L210.5 31.5L207.2 30.8L207.0 29.7L212.4 30.1L219.9 31.1L222.1 32.4ZM791.9 32.5L776.2 33.6L781.3 29.9L783.6 29.6L785.7 29.8L792.7 31.4L791.9 32.5ZM550.7 28.6L559.8 30.7L552.9 31.8L551.3 33.8L548.9 34.3L547.6 36.6L544.2 36.8L538.2 35.1L540.7 34.1L536.6 33.3L531.2 30.9L529.0 28.7L536.6 27.7L538.1 28.7L542.1 28.7L543.1 27.7L547.2 27.6L550.7 28.6ZM570.7 26.6L576.1 27.6L572.0 29.1L564.0 29.4L555.8 29.0L555.3 28.2L551.3 28.2L548.2 26.9L556.8 26.1L560.9 26.8L563.7 26.0L570.7 26.6ZM642.0 26.3L638.3 26.6L635.8 26.8L635.4 27.3L632.2 27.8L629.2 27.1L630.8 26.2L624.6 26.1L630.0 25.6L634.2 25.6L634.8 26.4L636.4 25.7L639.0 25.2L643.1 25.8L642.0 26.3ZM777.6 30.9L771.5 31.2L763.8 30.4L759.2 29.4L757.1 27.4L753.3 26.8L760.5 24.9L766.5 24.3L771.9 25.7L778.3 28.4L777.6 30.9ZM258.3 28.7L261.6 29.6L257.8 30.4L252.7 32.5L247.8 32.7L242.0 32.4L239.0 31.2L239.1 30.2L241.3 29.5L236.2 29.5L233.1 28.6L231.4 27.3L233.3 26.1L235.2 25.3L238.1 25.1L236.8 24.4L243.3 24.3L246.9 25.8L251.5 26.4L256.1 26.9L258.3 28.7ZM309.7 19.1L317.1 19.4L323.1 19.7L328.2 20.5L328.1 21.2L321.3 22.4L314.6 23.0L312.1 23.6L318.1 23.6L311.6 25.3L307.0 26.1L302.3 28.3L296.5 28.8L294.8 29.4L286.4 29.7L290.2 30.0L288.3 30.5L290.6 31.9L287.9 32.8L283.6 33.6L282.3 34.7L278.4 35.5L278.8 36.2L283.6 36.0L283.6 36.7L276.2 38.4L269.0 37.6L260.8 38.1L256.7 37.7L251.4 37.6L251.1 36.2L256.2 35.6L254.8 33.6L256.5 33.4L264.0 34.6L260.2 32.8L255.7 32.3L257.9 31.2L262.8 30.6L263.6 29.6L259.7 28.5L258.5 27.1L266.1 27.2L268.3 27.5L272.6 26.5L266.4 26.2L256.7 26.3L251.8 25.4L249.4 24.3L246.2 23.5L245.6 22.5L249.7 22.0L253.0 21.9L258.4 21.4L262.5 20.4L265.9 20.6L268.9 21.3L271.1 19.8L274.7 19.4L279.7 19.1L288.2 19.0L289.7 19.3L297.7 18.8L303.7 19.0L309.7 19.1ZM424.7 18.0L442.1 20.2L437.0 21.3L426.3 21.4L411.4 21.7L412.8 22.2L422.6 21.9L431.0 22.8L436.4 22.0L438.7 23.0L435.6 24.6L442.7 23.5L456.2 22.5L464.5 23.0L466.1 24.2L454.8 26.2L453.2 26.8L444.3 27.3L450.7 27.4L447.5 29.4L445.3 31.2L445.4 34.3L448.7 36.2L444.3 36.3L439.8 37.1L444.9 38.6L445.6 41.0L442.6 41.2L446.2 43.6L440.0 43.8L443.2 45.0L442.3 45.9L438.4 46.4L434.5 46.4L438.0 48.3L438.1 49.5L432.6 48.3L431.1 49.1L434.9 49.8L438.5 51.5L439.6 53.7L434.6 54.2L432.5 53.2L429.0 51.6L430.0 53.5L426.8 54.9L434.1 55.0L437.9 55.2L430.5 57.6L422.9 59.8L414.8 60.8L411.7 60.8L408.9 61.8L405.0 64.8L399.0 66.7L397.1 66.8L393.4 67.5L389.4 68.2L387.0 69.9L387.0 71.8L385.6 73.7L381.1 75.9L382.2 78.1L380.9 80.3L379.5 83.1L375.6 83.2L371.5 81.0L365.9 80.9L363.2 79.4L361.4 76.7L356.6 73.3L355.2 71.4L354.8 69.0L350.9 66.4L351.9 64.3L350.1 63.4L352.8 60.1L357.0 59.1L358.1 57.9L358.7 55.8L355.5 56.7L354.0 57.2L351.5 57.5L348.1 56.6L347.9 54.8L349.0 53.3L351.6 53.2L357.3 54.0L352.5 52.2L350.0 51.3L347.2 51.6L344.9 51.0L348.0 48.4L346.3 47.3L344.1 45.4L340.8 42.5L337.2 41.4L337.3 40.2L329.8 38.6L323.9 38.4L316.5 38.5L309.7 38.7L306.5 37.8L301.7 36.1L309.0 35.2L314.5 35.1L302.7 34.3L296.4 33.2L296.8 32.1L307.3 30.8L317.5 29.5L318.5 28.5L311.0 27.5L313.5 26.3L323.1 24.4L327.1 24.1L326.0 22.9L332.6 22.1L341.1 21.7L349.6 21.7L352.7 22.5L360.0 21.0L366.7 22.0L370.6 22.3L376.3 23.2L369.7 21.7L370.1 20.5L379.4 18.8L389.2 18.9L392.7 17.9L402.5 17.7L424.7 18.0Z";
diff --git a/src/types/benchmark.ts b/src/types/benchmark.ts
index 908ae00f4..1ecd9ee65 100644
--- a/src/types/benchmark.ts
+++ b/src/types/benchmark.ts
@@ -244,6 +244,15 @@ export type Benchmark = {
kind?: { value: string; label: string }[];
venue?: { value: string; label: string }[];
};
+ /** Default dimension scope for the unfiltered build (spec
+ * `aggregate_filters`). Presentation surfaces (e.g. the by-region
+ * grid) also read it to restrict what a single-vantage bench shows. */
+ aggregateFilters?: {
+ chain?: string;
+ region?: string;
+ kind?: string;
+ venue?: string;
+ };
category: "Aggregators" | "Bridges" | "Blockchains" | "Trading" | "Wallets" | "RPCs" | "NFT APIs" | "Explorers" | "RWA";
results: ProviderResult[];
/** Per-chain leader, computed only on the unfiltered ("All chains") view
@@ -257,6 +266,14 @@ export type Benchmark = {
* are computed via extra Prom queries with `chain=""` injected, so
* headline copy / OG image / badge endpoint can call out the leader on
* each chain instead of one biased global winner. */
+ /** True when the bench's p50/p90/p99 queries are genuinely different
+ * expressions, i.e. the value really is a percentile of a distribution.
+ * False when a provider block repeats one expression across all three,
+ * which is how coverage and count benches are written: there is a single
+ * measurement, not a distribution, and labelling it "p50" claims a
+ * percentile that was never computed. Derived from the live spec in
+ * spec.ts, so a YAML edit takes effect without a worker rewrite. */
+ hasDistribution?: boolean;
bestPerChain?: Record;
/** Per-chain trailing provider, populated in lockstep with
* `bestPerChain` (same key set, same population conditions). Powers