diff --git a/.github/workflows/staging-deploy.yml b/.github/workflows/staging-deploy.yml
index 528eec40..1941f041 100644
--- a/.github/workflows/staging-deploy.yml
+++ b/.github/workflows/staging-deploy.yml
@@ -13,7 +13,11 @@ concurrency:
jobs:
deploy:
runs-on: ubuntu-latest
- timeout-minutes: 15
+ # 30 min: the build prerenders every bench/product/OG route against
+ # Prom through a query concurrency cap, and the per-page budget is
+ # 240s (next.config.ts). 15 min started killing builds once the HL
+ # bench grew past 60 providers.
+ timeout-minutes: 30
env:
VERCEL_ORG_ID: ${{ secrets.VERCEL_ORG_ID }}
VERCEL_PROJECT_ID: ${{ secrets.VERCEL_PROJECT_ID }}
diff --git a/benchmarks/hyperliquid-hip3-deployers.yml b/benchmarks/hyperliquid-hip3-deployers.yml
new file mode 100644
index 00000000..e8c8f2eb
--- /dev/null
+++ b/benchmarks/hyperliquid-hip3-deployers.yml
@@ -0,0 +1,284 @@
+# OpenChainBench. Bench № 035
+
+slug: hyperliquid-hip3-deployers
+number: "035"
+title: Hyperliquid HIP-3 deployer revenue leaderboard
+seo_title: "Hyperliquid HIP-3 dexes 2026: deployer fee revenue USD live (trade.xyz, Ventuals and 5 others) over 24h, 7d, 30d"
+seo_description: "Live USD revenue collected by every HIP-3 builder-deployed dex on Hyperliquid via the on chain deployerFee field. trade.xyz, Ventuals and 5 other deployers ranked by 24h, 7d and 30d fees, with volume, unique traders and market counts. Data source is a local hl node tailing the mainnet fill stream."
+subtitle: How much USD deployer fee revenue each HIP-3 builder-deployed dex collected over the rolling 24h, 7 day and 30 day windows. Data from a local hl node tailing every fill on mainnet.
+category: Trading
+status: live
+metric: Deployer fees collected (USD)
+unit: usd
+higher_is_better: true
+
+disclaimer: |
+ Ranking is by raw deployer fee revenue in USD, not by value for traders. Bigger number means more fees collected via the on chain deployerFee field on the dex's namespaced markets. For the trader cost perspective, switch to the Effective fee bps companion tab. Core Hyperliquid markets (no namespace) carry no deployer fee and are excluded. Some dex namespaces are not yet matched to a public brand; they are listed under their on chain namespace until identified.
+
+seo_intro: |
+ This page answers one question. Which HIP-3 builder-deployed dex on
+ Hyperliquid collected the most deployer fee revenue in USD over the
+ last 24 hours, 7 days and 30 days. HIP-3 lets any team that stakes
+ 500,000 HYPE deploy its own perpetual markets on HyperCore under a
+ dedicated namespace (xyz:AAPL, vntl:MAG7, km:US500), set the fee
+ policy for those markets, and collect a deployer cut on every fill.
+ The mechanism powers the tokenized stock, index and commodity perps
+ wave on Hyperliquid: trade.xyz alone routinely clears several billion
+ dollars of daily notional across more than 70 equity and commodity
+ markets. The bench ranks every namespace observed on mainnet by the
+ dollar amount its deployer collected through the on chain deployerFee
+ field, alongside routed volume, unique trader counts and the number
+ of live markets. Data comes from a local hl node operated on OCB
+ infrastructure tailing the Hyperliquid mainnet fill stream; a Go
+ harness aggregates per dex over rolling windows and exposes
+ Prometheus gauges that this page consumes. End to end staleness from
+ fill landing on chain to page render is typically under one minute.
+
+abstract: |
+ The bench ranks HIP-3 builder-deployed dexes by the USD value of
+ deployer fees they collected over rolling 24 hour, 7 day and 30 day
+ windows. Source data is a local hl node operated on OCB
+ infrastructure that writes every block of fills to disk as one JSON
+ line. A Go harness tails these files, attributes each fill carrying
+ a namespaced coin (xyz:AAPL belongs to the xyz dex) and a deployerFee
+ value, and increments per dex hourly buckets keyed by the UTC hour
+ floor of the fill timestamp. At publish time the harness sums the
+ recent 24, 168 and 720 hourly buckets and exposes the totals as
+ Prometheus gauges (hl_hip3_deployer_fees_usd_24h, _7d, _30d), plus
+ routed volume, unique trader counts from per day wallet sets, live
+ market counts and an effective fee rate in basis points. The dex set
+ is discovered dynamically from the fill stream, no registry needed,
+ because HIP-3 namespaces are unique on chain by construction. The
+ bench does not place trades, does not touch private keys, and does
+ not depend on any internal Mobula service.
+
+methodology:
+ - "Data source. A local hl node operated on OCB infrastructure tails the Hyperliquid mainnet and writes every block of fills to node_fills_by_block hourly YYYYMMDD HH on local disk. Each line is one JSON block, the events array carries one entry per fill, and every fill on a HIP-3 market includes the dex namespaced coin (xyz:AAPL) plus the deployerFee in USDC."
+ - "Attribution. A fill belongs to a HIP-3 dex when its coin carries a namespace prefix (xyz:AAPL belongs to xyz). Namespaces are unique on chain by construction, so no hand curated registry is needed; a new deployer appears on the leaderboard with its first observed fill. Core Hyperliquid markets carry no namespace and no deployer fee, they are excluded."
+ - "Cadence. The Go harness re reads the current and previous hourly files every 30 seconds, parses appended lines, and updates per dex hourly buckets keyed by the UTC hour floor of the fill timestamp. Prometheus scrapes the metrics every 30 seconds via a Caddy reverse proxy with basic auth. End to end staleness from fill landing on chain to bench page render is typically under one minute."
+ - "Headline calculation. For each dex over the last 24 rolling hours we sum the deployerFee USD values of every fill on its namespaced markets. The 7 day and 30 day figures sum the same field over the last 168 and 720 hourly buckets, so the metric is consistent across the three ranges."
+ - "Volume companion. hl_hip3_deployer_volume_usd_24h, _7d and _30d are computed the same way on the px times sz product of every fill. They feed the Volume column and tab."
+ - "Users. Unique wallet addresses with at least one fill on the dex. The 24h figure is a union of per hour wallet sets over the rolling window; 7d and 30d are unions of per UTC day wallet sets. After a harness restart the day sets are reseeded from the on disk fill history before the 7d and 30d gauges publish, so they never lowball."
+ - "Markets. Count of distinct namespaced coins traded on the dex in the last 24 hours. A dex with 70 active equity markets and a dex with one commodity market are different products; the column makes that visible next to the revenue number."
+ - "Effective fee bps. Deployer fees divided by notional volume, times ten thousand, over the rolling 24h window. The trader perspective on what a representative dollar of flow paid the dex operator."
+ - "Window mechanics. Hourly buckets per dex go back 30 days in memory and are pruned on every publish. Per fill state is never retained; the largest dex clears about 4 million fills per day, so the bench aggregates at ingestion time instead of keeping fill level granularity."
+ - "30 day caveat. The node retains hourly fill files for a bounded horizon and the harness backfill reads what exists on disk. Until 30 full days of history accumulate, the 30d figures cover the available horizon and grow toward the full window."
+ - "Naming. xyz is trade.xyz and vntl is Ventuals, both publicly documented. Other namespaces (cash, km, hyna, flx, para) are listed under their on chain prefix until the operating team is identified; the attribution and the numbers are exact either way."
+ - "Reproducibility. Harness source is open in the OCB harness tree. Anyone running their own hl node can clone, point the data flag at their node_fills_by_block hourly root, run the binary against a Prometheus scraper, and reproduce these metrics."
+ - "Failures. Any read error on the hourly files leaves the previous gauge values in place. Hourly bucket pruning is idempotent. Restarts replay the warmup window from disk before going live tail, and reseed the user sets from history in the background."
+
+findings:
+ - "{{best_name}} leads the leaderboard at {{best_p50}} in deployer fees collected over the last 24 hours."
+ - "{{name:xyz}} collects {{p50:xyz}} on the 24h view. Tokenized equities and commodities under the xyz namespace dominate HIP-3 flow, with more than 70 live markets."
+ - "{{name:vntl}} returns {{p50:vntl}} on the 24h view. Pre IPO valuation perps are a smaller but structurally distinct niche, traders price company valuations rather than listed stock."
+ - "Deployer revenue concentrates hard. The top namespace typically collects more than the rest of the field combined, mirroring the builder code market where consumer wallets dominate."
+ - "USD figures move with both volume and the dex's fee policy. A deployer can climb the leaderboard by listing markets that attract more notional or by widening its fee share, the Effective fee bps tab separates the two."
+ - "The 7 day and 30 day ranges smooth out listing events and promo spikes. Use those for ranking stability, the 24h view for current activity."
+
+faq:
+ - q: "What does this benchmark measure?"
+ a: "The USD value of deployer fees each HIP-3 builder-deployed dex collected over rolling 24 hour, 7 day and 30 day windows. HIP-3 lets a team that stakes 500,000 HYPE deploy its own perpetual markets on Hyperliquid under a dedicated namespace and collect a fee cut on every fill. The bench sums every fill's deployerFee value per namespace and publishes the total per timeframe."
+ - q: "What is HIP-3?"
+ a: "Hyperliquid Improvement Proposal 3, builder-deployed perpetuals. It opens HyperCore market deployment to outside teams: stake 500,000 HYPE, deploy markets under your namespace, set the fee policy, collect the deployer cut. It powers the tokenized stock, index and commodity perps on Hyperliquid, which represent a large share of platform volume."
+ - q: "Where does the data come from?"
+ a: "A local hl node operated on OCB infrastructure tails the Hyperliquid mainnet. The node writes every block of fills to disk; a Go harness running on the same host reads these files continuously, attributes namespaced fills to their dex, and aggregates per hour. No third party API, no internal Mobula service."
+ - q: "How are dexes identified?"
+ a: "By their on chain coin namespace. Every HIP-3 market trades under a prefix (xyz:AAPL, vntl:MAG7, km:US500) that is unique to its deployer. The set is discovered dynamically from the fill stream, so a brand new deployer appears on the leaderboard with its first fill. Namespaces not yet matched to a public brand are listed under the raw prefix."
+ - q: "Why is trade.xyz so far ahead?"
+ a: "It operates the deepest tokenized equity and commodity catalog on Hyperliquid, more than 70 markets including the large cap US names, and captures the bulk of HIP-3 open interest. Deployer revenue is volume times fee policy, and xyz leads on both breadth and notional."
+ - q: "What does the Effective fee bps column tell me?"
+ a: "Deployer fees divided by notional volume, in basis points. It is the trader perspective: what a representative dollar of flow paid the dex operator. Two dexes with the same revenue can have very different fee rates if one routes ten times the volume."
+ - q: "Why do 30 day figures look low for some dexes?"
+ a: "The node keeps hourly fill files for a bounded horizon and the harness backfills what exists on disk. Until 30 full days of history accumulate behind a dex, its 30d figure covers the available horizon and grows toward the full window. The 24h and 7d views are complete."
+ - q: "How often does the page refresh?"
+ a: "Every 30 seconds at the harness and scrape level. The page itself uses incremental static regeneration with a 60 second window, so headline values are at most 90 seconds stale plus chain propagation delay."
+ - q: "Can I cite a value from this page?"
+ a: "Yes. Every number is a Prometheus query exposed via the OCB API endpoints. The harness source is open at the link in the source field below. Cite the value and the timestamp at the top of the page."
+
+source: https://github.com/ChainBench/OpenChainBench/tree/main/harnesses/hyperliquid-frontends
+
+prometheus:
+ window: 24h
+ expected_freshness_seconds: 5400
+
+providers:
+ - slug: xyz
+ name: trade.xyz
+ tag: Tokenized US equities, indices and commodities, 70+ markets
+ formula: "USD deployer fees collected over the rolling 24h window, summed across every fill on xyz namespaced markets in the local hl-node stream."
+ queries:
+ p50: hl_hip3_deployer_fees_usd_24h{dex="xyz"}
+ p90: hl_hip3_deployer_volume_usd_24h{dex="xyz"}
+ p99: hl_hip3_deployer_effective_fee_bps{dex="xyz"}
+ mean: avg_over_time(hl_hip3_deployer_fees_usd_24h{dex="xyz"}[7d])
+ success: (hl_frontend_local_last_tick_unix_v2 > bool (time() - 120))
+ sample_size: hl_hip3_deployer_fills_24h{dex="xyz"}
+ series: hl_hip3_deployer_fees_usd_24h{dex="xyz"}
+
+ - slug: cash
+ name: cash
+ tag: HIP-3 namespace, equities and ETFs, full name pending identification
+ formula: "USD deployer fees collected over the rolling 24h window, summed across every fill on cash namespaced markets in the local hl-node stream."
+ queries:
+ p50: hl_hip3_deployer_fees_usd_24h{dex="cash"}
+ p90: hl_hip3_deployer_volume_usd_24h{dex="cash"}
+ p99: hl_hip3_deployer_effective_fee_bps{dex="cash"}
+ mean: avg_over_time(hl_hip3_deployer_fees_usd_24h{dex="cash"}[7d])
+ success: (hl_frontend_local_last_tick_unix_v2 > bool (time() - 120))
+ sample_size: hl_hip3_deployer_fills_24h{dex="cash"}
+ series: hl_hip3_deployer_fees_usd_24h{dex="cash"}
+
+ - slug: km
+ name: km
+ tag: HIP-3 namespace, equities and index baskets, full name pending identification
+ formula: "USD deployer fees collected over the rolling 24h window, summed across every fill on km namespaced markets in the local hl-node stream."
+ queries:
+ p50: hl_hip3_deployer_fees_usd_24h{dex="km"}
+ p90: hl_hip3_deployer_volume_usd_24h{dex="km"}
+ p99: hl_hip3_deployer_effective_fee_bps{dex="km"}
+ mean: avg_over_time(hl_hip3_deployer_fees_usd_24h{dex="km"}[7d])
+ success: (hl_frontend_local_last_tick_unix_v2 > bool (time() - 120))
+ sample_size: hl_hip3_deployer_fills_24h{dex="km"}
+ series: hl_hip3_deployer_fees_usd_24h{dex="km"}
+
+ - slug: vntl
+ name: Ventuals
+ tag: Pre IPO valuation perps under the vntl namespace
+ formula: "USD deployer fees collected over the rolling 24h window, summed across every fill on vntl namespaced markets in the local hl-node stream."
+ queries:
+ p50: hl_hip3_deployer_fees_usd_24h{dex="vntl"}
+ p90: hl_hip3_deployer_volume_usd_24h{dex="vntl"}
+ p99: hl_hip3_deployer_effective_fee_bps{dex="vntl"}
+ mean: avg_over_time(hl_hip3_deployer_fees_usd_24h{dex="vntl"}[7d])
+ success: (hl_frontend_local_last_tick_unix_v2 > bool (time() - 120))
+ sample_size: hl_hip3_deployer_fills_24h{dex="vntl"}
+ series: hl_hip3_deployer_fees_usd_24h{dex="vntl"}
+
+ - slug: hyna
+ name: hyna
+ tag: HIP-3 namespace, full name pending identification
+ formula: "USD deployer fees collected over the rolling 24h window, summed across every fill on hyna namespaced markets in the local hl-node stream."
+ queries:
+ p50: hl_hip3_deployer_fees_usd_24h{dex="hyna"}
+ p90: hl_hip3_deployer_volume_usd_24h{dex="hyna"}
+ p99: hl_hip3_deployer_effective_fee_bps{dex="hyna"}
+ mean: avg_over_time(hl_hip3_deployer_fees_usd_24h{dex="hyna"}[7d])
+ success: (hl_frontend_local_last_tick_unix_v2 > bool (time() - 120))
+ sample_size: hl_hip3_deployer_fills_24h{dex="hyna"}
+ series: hl_hip3_deployer_fees_usd_24h{dex="hyna"}
+
+ - slug: flx
+ name: flx
+ tag: HIP-3 namespace, commodities, full name pending identification
+ formula: "USD deployer fees collected over the rolling 24h window, summed across every fill on flx namespaced markets in the local hl-node stream."
+ queries:
+ p50: hl_hip3_deployer_fees_usd_24h{dex="flx"}
+ p90: hl_hip3_deployer_volume_usd_24h{dex="flx"}
+ p99: hl_hip3_deployer_effective_fee_bps{dex="flx"}
+ mean: avg_over_time(hl_hip3_deployer_fees_usd_24h{dex="flx"}[7d])
+ success: (hl_frontend_local_last_tick_unix_v2 > bool (time() - 120))
+ sample_size: hl_hip3_deployer_fills_24h{dex="flx"}
+ series: hl_hip3_deployer_fees_usd_24h{dex="flx"}
+
+ - slug: para
+ name: para
+ tag: HIP-3 namespace, full name pending identification
+ formula: "USD deployer fees collected over the rolling 24h window, summed across every fill on para namespaced markets in the local hl-node stream."
+ queries:
+ p50: hl_hip3_deployer_fees_usd_24h{dex="para"}
+ p90: hl_hip3_deployer_volume_usd_24h{dex="para"}
+ p99: hl_hip3_deployer_effective_fee_bps{dex="para"}
+ mean: avg_over_time(hl_hip3_deployer_fees_usd_24h{dex="para"}[7d])
+ success: (hl_frontend_local_last_tick_unix_v2 > bool (time() - 120))
+ sample_size: hl_hip3_deployer_fills_24h{dex="para"}
+ series: hl_hip3_deployer_fees_usd_24h{dex="para"}
+
+metric_panels:
+ - id: volume
+ label: Volume routed
+ metric: hl_hip3_deployer_volume_usd_24h
+ label_key: dex
+ unit: usd
+ higher_is_better: true
+ description: "Notional USD routed through this dex's markets in the last 24 hours."
+ - id: users
+ label: Users 24h
+ metric: hl_hip3_deployer_users_24h
+ label_key: dex
+ unit: count
+ higher_is_better: true
+ description: "Unique wallets that traded on this dex in the last 24 hours."
+ - id: markets
+ label: Markets 24h
+ metric: hl_hip3_deployer_markets_24h
+ label_key: dex
+ unit: count
+ higher_is_better: true
+ description: "Distinct namespaced markets traded on this dex in the last 24 hours."
+ - id: effective_fee
+ label: Effective fee bps
+ metric: hl_hip3_deployer_effective_fee_bps
+ label_key: dex
+ unit: bps
+ description: "Deployer fees divided by notional volume times 10000. The trader perspective on cost."
+ - id: outage
+ label: Time since last fill
+ metric: hl_hip3_deployer_last_fill_age_seconds
+ label_key: dex
+ unit: sec
+ description: "Seconds since this dex's most recent fill. An outage signal."
+ # Data-only panels (tab: false): they feed the ledger's 7d/30d
+ # timeframe toggle below.
+ - id: revenue_7d
+ label: Revenue 7d
+ metric: hl_hip3_deployer_fees_usd_7d
+ label_key: dex
+ unit: usd
+ higher_is_better: true
+ tab: false
+ - id: revenue_30d
+ label: Revenue 30d
+ metric: hl_hip3_deployer_fees_usd_30d
+ label_key: dex
+ unit: usd
+ higher_is_better: true
+ tab: false
+ - id: volume_7d
+ label: Volume 7d
+ metric: hl_hip3_deployer_volume_usd_7d
+ label_key: dex
+ unit: usd
+ higher_is_better: true
+ tab: false
+ - id: volume_30d
+ label: Volume 30d
+ metric: hl_hip3_deployer_volume_usd_30d
+ label_key: dex
+ unit: usd
+ higher_is_better: true
+ tab: false
+ - id: users_7d
+ label: Users 7d
+ metric: hl_hip3_deployer_users_7d
+ label_key: dex
+ unit: count
+ higher_is_better: true
+ tab: false
+ - id: users_30d
+ label: Users 30d
+ metric: hl_hip3_deployer_users_30d
+ label_key: dex
+ unit: count
+ higher_is_better: true
+ tab: false
+
+# Honest column labels: the p50/p90/p99 slots are repurposed (USD revenue,
+# no percentile semantics). Revenue, Volume and Users follow the 24h/7d/30d
+# timeframe toggle via the data-only panels above; Fee bps and Markets are
+# 24h figures and their headers say so on every timeframe.
+ledger_columns:
+ - { label: "Revenue", slot: p50, windows: { 7d: revenue_7d, 30d: revenue_30d } }
+ - { label: "Volume", slot: p90, windows: { 7d: volume_7d, 30d: volume_30d } }
+ - { label: "Users", panel: users, windows: { 7d: users_7d, 30d: users_30d } }
+ - { label: "Fee bps", slot: p99, unit: bps }
+ - { label: "Markets", panel: markets }
diff --git a/benchmarks/l1-finality.yml b/benchmarks/l1-finality.yml
index f3007ff0..b4ab1580 100644
--- a/benchmarks/l1-finality.yml
+++ b/benchmarks/l1-finality.yml
@@ -2,12 +2,12 @@
slug: l1-finality
number: "006"
-title: Fastest L1 blockchain finality, live across 10 chains
+title: Fastest L1 blockchain finality, live across 11 chains
seo_title: "Fastest L1 finality 2026: TON, SUI, Stellar, Solana, Ethereum"
-seo_description: "Fastest L1 blockchain finality, measured live for 10 chains. TON 0.2 s, SUI 0.5 s, Stellar 4 s, Solana 13 s, Ethereum ~16 min. p50 over 24h, open methodology."
+seo_description: "Fastest L1 blockchain finality, measured live for 11 chains. TON, SUI and Hedera in seconds, Solana ~13 s, Ethereum ~12.8 min. Live percentiles over 24h, open methodology."
subtitle: Wall-clock seconds from latest block to the finalized block on Ethereum, Solana, TON, SUI, Stellar and 5 more chains, refreshed every 10 seconds.
seo_intro: |
- This page measures L1 finality time live for every major Layer-1 blockchain, with p50 / p90 / p99 refreshed every 10 seconds. Stellar finality time is ~5 seconds, the close interval the Stellar Consensus Protocol locks in via federated Byzantine agreement. Solana finality time goes from sub-second on the processed commitment to ~12.8 s on finalized after 32 confirmed slots. Ethereum finality time is ~12.8 minutes, the 2-epoch Casper FFG window. Hedera finality time clears in 3-5 seconds via Hashgraph aBFT. SUI finality time and TON finality time both sit under one second via Mysticeti DAG-BFT and BAG consensus. BNB and Avalanche finality time land near two seconds through fast-finality forks. Probabilistic chains (Litecoin, Monero, Cardano) settle on a confirmation-depth convention measured here in minutes.
+ This page measures L1 finality time live for every major Layer-1 blockchain, with p50 / p90 / p99 refreshed every 10 seconds. Stellar finality time is ~5 seconds, the close interval the Stellar Consensus Protocol locks in via federated Byzantine agreement. Solana finality time goes from sub-second on the processed commitment to ~12.8 s on finalized after 32 confirmed slots. Ethereum finality time is ~12.8 minutes, the 2-epoch Casper FFG window. Hedera finality time clears in 3-5 seconds via Hashgraph aBFT. SUI finality time and TON finality time both sit under one second via Mysticeti DAG-BFT and BAG consensus. BNB and Avalanche finality time land near two seconds through fast-finality forks. Probabilistic chains (Litecoin, Monero) settle on a confirmation-depth convention measured here in minutes.
faq:
- q: "What is blockchain finality?"
@@ -17,7 +17,7 @@ faq:
- q: "What is Ethereum finality time?"
a: "Ethereum finality time is {{p50:ethereum}} (p50, 24h). That is 2 epochs of 32 slots of 12 seconds each, the window Casper FFG needs to justify and finalize a checkpoint. The benchmark measures it live by polling eth_getBlockByNumber(\"latest\") and eth_getBlockByNumber(\"finalized\") every 10 seconds and taking the timestamp delta."
- q: "What is Solana finality time?"
- a: "Solana exposes two commitments. Processed is optimistic and lands sub-second, typically under 500 ms. Finalized requires 32 confirmed slots and clocks {{p50:solana}} (p50, 24h). The leaderboard value is finalized, the stricter guarantee. Sub-second Solana finality is real but only at the processed commitment level, not finalized."
+ a: "Solana exposes two commitments. Processed is optimistic and lands sub-second, typically under 500 ms. Finalized requires 32 confirmed slots and clocks {{p50:solana}} (24h average). The leaderboard value is finalized, the stricter guarantee. Sub-second Solana finality is real but only at the processed commitment level, not finalized."
- q: "What is SUI finality time?"
a: "SUI clocks {{p50:sui}} (p50, 24h) on this benchmark via the Mysticeti DAG-BFT consensus protocol. The chain exposes millisecond-precision timestamps so the measurement is genuinely sub-second. Mysticeti's two-vote commitment pattern reaches deterministic finality without the multi-block confirmation depth used by classical Byzantine fault tolerant chains."
- q: "What is TON finality time?"
@@ -29,9 +29,9 @@ faq:
- q: "What is BNB Chain finality time?"
a: "BNB Smart Chain finalizes via the BEP-126 fast-finality fork, dropping the confirmation depth that legacy probabilistic chains require. p50 sits at {{p50:bnb}} (24h), measured via persistent WebSocket subscription that records T1 when block N first appears as latest and T2 when it crosses the finalized threshold."
- q: "Which blockchain has the fastest finality time?"
- a: "Sub-second BFT chains lead. TON and SUI both clock under one second on this live benchmark. BNB and Avalanche sit around one to two seconds via their fast-finality forks. Solana finalized lands around 12.8 s, Ethereum at 12.8 min, and probabilistic chains (Litecoin, Monero, Cardano) trail at 15 to 30 minutes by confirmation-depth convention."
+ a: "Sub-second BFT chains lead. TON and SUI both clock under one second on this live benchmark. BNB and Avalanche sit around one to two seconds via their fast-finality forks. Solana finalized lands around 12.8 s, Ethereum at 12.8 min, and probabilistic chains (Litecoin, Monero) trail at 15 to 30 minutes by confirmation-depth convention."
- q: "How is L1 finality time measured on this page?"
- a: "Two methods, picked per chain. RPC pollers compare latest vs finalized block timestamps every 10 seconds, used for Ethereum, Solana, TRON, Stellar, SUI, TON, Litecoin, Monero, Cardano. WebSocket subscribers record wall-clock time T1 when a block first appears on the head stream and T2 when it crosses the finality threshold, giving millisecond-precision lag for sub-poll chains (BNB, Avalanche)."
+ a: "Two methods, picked per chain. RPC pollers compare latest vs finalized block timestamps every 10 seconds, used for Ethereum, Solana, TRON, Stellar, SUI, TON, Litecoin, Monero. WebSocket subscribers record wall-clock time T1 when a block first appears on the head stream and T2 when it crosses the finality threshold, giving millisecond-precision lag for sub-poll chains (BNB, Avalanche)."
per_chain_explainer:
- slug: ethereum
@@ -41,7 +41,7 @@ per_chain_explainer:
- slug: solana
h2: "Solana finality time"
body: |
- Solana exposes two commitments. Processed is optimistic and lands sub-second, typically under 500 ms. Finalized requires 32 confirmed slots and clocks {{p50:solana}} (p50, 24h) on this benchmark. The leaderboard value is finalized, the stricter guarantee. Sub-second Solana finality is real but only at the processed commitment level. Measured via `getSlot{commitment:"processed"}` and `getSlot{commitment:"finalized"}` translated through block timestamps.
+ Solana exposes two commitments. Processed is optimistic and lands sub-second, typically under 500 ms. Finalized requires 32 confirmed slots and clocks {{p50:solana}} (24h average) on this benchmark. The leaderboard value is finalized, the stricter guarantee. Sub-second Solana finality is real but only at the processed commitment level. Measured via `getSlot{commitment:"processed"}` and `getSlot{commitment:"finalized"}` translated through block timestamps.
- slug: stellar
h2: "Stellar finality time"
body: |
@@ -69,7 +69,7 @@ per_chain_explainer:
- slug: tron
h2: "TRON finality time"
body: |
- TRON's solidity confirmation hovers around {{p50:tron}} (p50, 24h), matching the documented 20-block delay at 3-second block intervals. The solidity flag in TRON consensus is the chain's equivalent of finalized: a block survives until 20 confirmations before being considered irreversible. Measured via `/wallet/getnowblock` (head) minus `/walletsolidity/getnowblock` (solidity-confirmed).
+ TRON's solidity confirmation hovers around {{p50:tron}} (24h average), matching the documented 20-block delay at 3-second block intervals. The solidity flag in TRON consensus is the chain's equivalent of finalized: a block survives until 20 confirmations before being considered irreversible. Measured via `/wallet/getnowblock` (head) minus `/walletsolidity/getnowblock` (solidity-confirmed).
- slug: litecoin
h2: "Litecoin finality time"
body: |
@@ -90,7 +90,7 @@ abstract: |
finalized block. The harness uses two complementary methods. For
chains with finality much longer than our 10 s poll interval we read
`latest.timestamp, finalized.timestamp` from the chain RPCs (Ethereum,
- Solana, TRON, Litecoin, Monero, Cardano). For chains whose finality
+ Solana, TRON, Litecoin, Monero). For chains whose finality
is faster than the poll interval (BNB, Avalanche, SUI, TON, Stellar)
we maintain a persistent WebSocket / SSE subscription, recording
wall-clock time T1 when block N is first seen as `latest` and T2
@@ -114,7 +114,6 @@ methodology:
- "TON: tonapi.io `/blockchain/masterchain-head` minus 3 seqno."
- "Litecoin (probabilistic): blockchair `/stats.best_block_height` minus 6 confirmations via `/dashboards/block/{height}.block.time`."
- "Monero (probabilistic): monero-rpc `get_info` minus 10 confirmations via `get_block_header_by_height`."
- - "Cardano (probabilistic): koios `/tip` minus 15 confirmations via `/blocks?block_height=eq.`. 15 matches the depth top exchanges (Coinbase / Kraken) treat as final settlement; Ouroboros Praos full settlement is k = 2160 blocks (~12 h) and is not the practical convention."
- "Hedera (planned, currently disabled): mirror-node `/api/v1/blocks` only exposes already-final blocks, so true wall-clock lag is impossible from public endpoints. Re-enables once Block Nodes (HIP-1056) leave private preview."
- "Result: `lag_seconds = headTimestamp, finalizedTimestamp`. We also record the block-height delta and per-chain fetch latency."
- "Excluded by design: XRP (no close-time on `ledger_current`, wall-clock measurement requires WS subscribe to ledger stream, not yet implemented)."
@@ -122,9 +121,9 @@ methodology:
findings:
- "{{name:sui}} returns {{p50:sui}} (p50, 24 h), matching its Mysticeti DAG BFT design. The chain exposes ms timestamps so the measurement is genuinely sub-second."
- - "{{name:ethereum}} sits at {{p50:ethereum}} (p50, 24 h), aligned with the 2-epoch finalization spec (32 slots × 12 s × 2)."
+ - "{{name:ethereum}} sits at {{p50:ethereum}} (24 h average), aligned with the 2-epoch finalization spec (32 slots × 12 s × 2)."
- "{{name:tron}}'s solidity confirmation hovers around {{p50:tron}}, exactly the documented 20-block delay at 3 s blocks."
- - "{{name:litecoin}} and {{name:monero}} sit at {{p50:litecoin}} and {{p50:monero}} (p50, 24 h) by construction, set by the chosen confirmation depth."
+ - "{{name:litecoin}} and {{name:monero}} sit at {{p50:litecoin}} and {{p50:monero}} (24 h average) by construction, set by the chosen confirmation depth."
- "{{best_name}} currently leads the leaderboard at {{best_p50}} (p50, 24 h) across {{count}} measured L1s. {{worst_name}} trails at {{worst_p50}}."
- "Hedera's Hashgraph aBFT is designed to finalize in 3-5 s but is excluded from the live leaderboard because the mirror node only exposes finalized blocks, wall-clock measurement requires the Block Node service (HIP-1056), still in private preview."
@@ -190,7 +189,7 @@ providers:
- slug: ton
name: TON
- tag: Masterchain commit, measured via SSE wall-clock
+ tag: Masterchain commit, polled via tonapi.io
formula: "Median wall-clock milliseconds for a TON masterchain block to commit under BAG consensus, polled via tonapi.io with a 3-seqno lookback, p50 over 24h."
queries:
p50: quantile_over_time(0.50, l1_finality_wallclock_lag_milliseconds{chain="ton"}[24h])
diff --git a/benchmarks/metadata-coverage.yml b/benchmarks/metadata-coverage.yml
index 06483702..3aeef004 100644
--- a/benchmarks/metadata-coverage.yml
+++ b/benchmarks/metadata-coverage.yml
@@ -46,7 +46,7 @@ abstract: |
A value of 100% means every field was returned for every fresh token; a
value of 50% means half the fields were missing on average. Sort order on
- this page is ascending, so the lowest coverage shows first, which is the
+ this page is descending, so the highest coverage shows first, which is the
inverse of latency benchmarks. Readers should compare the columns
themselves; numerical p50 is the headline.
diff --git a/benchmarks/network-coverage.yml b/benchmarks/network-coverage.yml
index 284f4850..6c2a762c 100644
--- a/benchmarks/network-coverage.yml
+++ b/benchmarks/network-coverage.yml
@@ -4,7 +4,7 @@ slug: network-coverage
number: "005"
title: Crypto data API with most blockchains supported, live coverage ranking
seo_title: "Crypto data API most chains 2026: CoinPaprika, GeckoTerminal"
-seo_description: "Crypto data API with the most blockchains supported, ranked live. CoinPaprika 306, GeckoTerminal 264, CoinStats 149, Codex 120, Mobula 79. Audited every six hours."
+seo_description: "Crypto data API with the most blockchains supported, ranked live. {{best_name}} leads at {{best_p50}} networks; GeckoTerminal, CoinStats, Codex, Covalent, Dune and Mobula compared. Audited every six hours."
subtitle: Number of blockchains each major crypto data API officially supports, audited every six hours against each provider's public network endpoint.
category: Aggregators
status: live
diff --git a/benchmarks/oracle-deviation.yml b/benchmarks/oracle-deviation.yml
index 2fce93e0..4bf578e1 100644
--- a/benchmarks/oracle-deviation.yml
+++ b/benchmarks/oracle-deviation.yml
@@ -42,7 +42,7 @@ abstract: |
We measure live oracle disagreement by polling four price oracles
every 30 seconds for ten USD-quoted blue chips, then computing the
full pairwise deviation matrix per pair. Deviation between two
- sources is `|price_a, price_b| / ((price_a + price_b) / 2) * 100`
+ sources is `|price_a - price_b| / ((price_a + price_b) / 2) * 100`
in percent; the bench surfaces it both per source-pair
(`ocb_oracle_deviation_pct{source_a, source_b}`) and as the per-pair
maximum across all source pairs (`ocb_oracle_max_deviation_pct`).
@@ -64,12 +64,12 @@ methodology:
- "Sources. Chainlink AggregatorV3 via `eth_call(latestRoundData())` on Ethereum mainnet (no API key, public RPC with fallback), Pyth Network via Hermes REST `/api/latest_price_feeds` batch endpoint (no key), Binance REST `/api/v3/ticker/price` on USDT-quoted symbols (treated as ≈ USD), Coinbase REST `/products/
/ticker` on USD-quoted products. All four are free, no-auth, public endpoints."
- "Pairs (10). BTC, ETH, SOL, BNB, AVAX, LINK and POL are tracked on all 4 sources. XRP, ADA, DOGE are tracked on 3 sources (Pyth + Binance + Coinbase) because the Chainlink mainnet AggregatorV3 contracts for these USD feeds are deprecated and no longer updated by node operators, the provider tag flags it."
- "Cadence. 30 second polling interval per (source, pair). 4 sources × 10 pairs × 2/minute = 80 requests/minute total, comfortably under every free-tier ceiling (Binance 1200/min, Coinbase 10/s public, Pyth ~30/s soft, public RPC courteous). HTTP timeout is 8 s per request to match the gas-estimation harness."
- - "Deviation formula. For every unordered pair of sources `(a, b)` that both have a fresh sample for the same pair: `deviation_pct = |price_a, price_b| / ((price_a + price_b) / 2) * 100`. With 4 sources we compute 6 pairwise deviations per asset; with 3 sources we compute 3. Per-pair headline is the max across all source-pairs (`ocb_oracle_max_deviation_pct`)."
+ - "Deviation formula. For every unordered pair of sources `(a, b)` that both have a fresh sample for the same pair: `deviation_pct = |price_a - price_b| / ((price_a + price_b) / 2) * 100`. With 4 sources we compute 6 pairwise deviations per asset; with 3 sources we compute 3. Per-pair headline is the max across all source-pairs (`ocb_oracle_max_deviation_pct`)."
- "Stale-price guard. A source's sample is considered stale and excluded from the deviation calculation if its last successful update is older than `2 * pollInterval` (60 s). Prevents a dead Coinbase ticker from registering as a 0% deviation against itself by leaving the price gauge fossilized."
- "Chainlink round age. The on-chain `updatedAt` timestamp from `latestRoundData()` is published separately as `ocb_oracle_last_round_age_seconds{source=\"chainlink\", pair}`. Chainlink updates only on deviation (typically 0.25-0.5% for blue chips) or heartbeat (~1 h), so a 30-minute age on a quiet ETH minute is normal; a 2-hour age on SOL during a volatile minute is the actual signal."
- "USDT ≈ USD assumption. Binance only quotes USDT pairs (BTCUSDT, etc.). We treat USDT as ≈ USD ± 10 bps drift, which is acceptable for a bench whose alert floor is ≥ 10 bps. A real USDT depeg would surface as Binance drifting from the other three sources for *every* pair simultaneously, exactly what we want this bench to flag, not hide."
- "MATIC → POL migration. Polygon migrated MATIC → POL 1:1 on Sep 4 2024. The Chainlink mainnet feed contract is still named MATIC/USD but on-chain `description()` confirms it tracks the POL token; Pyth renamed the feed to POL/USD; Coinbase delisted MATIC-USD and only lists POL-USD; Binance kept MATICUSDT as a frozen historical pair AND lists POLUSDT. We point Binance at POLUSDT so all four sources track the same underlying asset. Bench label is kept as `pair=\"MATIC/USD\"` for query continuity."
- - "Time-aligned deviation (canonical headline). For every pair of sources (a, b) we anchor on the more recent of their two SourceTSs (Chainlink's on-chain `updatedAt` for Chainlink, fetch time for the continuously-updating sources) and look up the older source's price in a 30-minute per-source rolling history at the anchor moment. The result is published as `ocb_oracle_deviation_at_oracle_ts_pct` and feeds the leaderboard headline `ocb_oracle_max_deviation_pct`. This eliminates the artifact where Chainlink's heartbeat lag inflates 'deviation' by the market's drift between its updates — a researcher grading oracle quality wants to compare Chainlink's price against the market at Chainlink's own updatedAt, not at the harness's fetch instant. The legacy fetch-time gauge (`ocb_oracle_deviation_pct`, aliased as `ocb_oracle_deviation_at_fetch_ts_pct`) is preserved unchanged for backward compatibility. Alignment misses (no history sample within ±10s of the anchor) are counted as `ocb_oracle_alignment_miss_total`. Methodology recommendation from Coinpaprika data team review; matches the convention published by Chaos Labs and Risk DAO oracle risk reports."
+ - "Time-aligned deviation (canonical headline). For every source pair we anchor on the more recent SourceTS (Chainlink's on-chain `updatedAt`, fetch time for continuous sources) and look up the older source's price in a 30-minute rolling history at that moment. Published as `ocb_oracle_deviation_at_oracle_ts_pct`, feeding `ocb_oracle_max_deviation_pct`. Removes Chainlink heartbeat-lag inflating deviation; alignment misses count as `ocb_oracle_alignment_miss_total`."
- "Excluded by design. Redstone (push-pull, no continuous gauge to scrape without integration contract), Uniswap V3 TWAP (per-pool integration + derivation of same CEX prints), DIA (smaller footprint than the four kept), and aggregator-of-aggregators (CoinGecko, CMC, DefiLlama), re-aggregating already-aggregated data adds latency and hides per-source disagreement."
findings:
diff --git a/benchmarks/perp-fees.yml b/benchmarks/perp-fees.yml
index 5bdf758e..3e4ccc57 100644
--- a/benchmarks/perp-fees.yml
+++ b/benchmarks/perp-fees.yml
@@ -4,7 +4,7 @@ slug: perp-fees
number: "007"
title: Cheapest perp DEX, live all-in fee on a $1000 ETH 10x long
seo_title: "Cheapest perp DEX 2026: Lighter, Hyperliquid, dYdX, GMX ranked"
-seo_description: "{{best_name}} leads cheapest perp DEX at {{best_p50}} all-in (p50, 24h). $1000 ETH 10x long. Lighter, Hyperliquid, dYdX v4, GMX v2, gains.trade ranked live."
+seo_description: "{{best_name}} leads cheapest perp DEX at {{best_p50}} all-in (24h avg). $1000 ETH 10x long. Lighter, Hyperliquid, dYdX v4, GMX v2, gains.trade ranked live."
subtitle: All-in cost in basis points to open a $1000 ETH long 10x position. Taker fee plus half-spread plus impact, measured live from public APIs across Lighter, Hyperliquid, dYdX, GMX and gains.trade.
category: Trading
status: live
@@ -52,12 +52,12 @@ methodology:
- "Failures (5xx, timeouts, rate limits) leave the previous gauge in place and increment a per-venue `fetch_errors_total` counter. The page falls back to the last successful sample."
findings:
- - "{{best_name}} currently leads the leaderboard at {{best_p50}} all-in (p50, 24 h) across {{count}} measured perp venues. The number includes taker fee plus the spread crossed at $1000 notional, not the rack-rate taker fee alone."
- - "Lighter charges zero taker fees on ETH-PERP, confirmed live via their public API. {{name:lighter}} clocks {{p50:lighter}} all-in (p50, 24 h), so the headline figure is essentially the half-spread plus orderbook impact crossed at $1000."
- - "{{name:hyperliquid}} sits at {{p50:hyperliquid}} all-in (p50, 24 h) on a 4.5 bps base taker. The deep HyperBFT orderbook keeps the spread component compressed, so the all-in number tracks the rack rate closer than any other venue in the cohort."
- - "{{name:dydx}} sits at {{p50:dydx}} all-in (p50, 24 h). The published tier-0 taker fee is 5 bps; the rest is the orderbook crossed at $1000, which on dYdX v4 is consistently the tightest non-zero-fee book outside Hyperliquid."
- - "GMX position fee is variable: 4 bps on positive impact opens, 6 bps on negative impact. We report the negative branch (conservative upper bound). {{name:gmx}} clocks {{p50:gmx}} all-in (p50, 24 h)."
- - "{{worst_name}} trails at {{worst_p50}} all-in (p50, 24 h). The leaderboard reorders intra-day with spread volatility, so a venue cheap on the rack rate can still trail when its book thins out at a $1000 buy."
+ - "{{best_name}} currently leads the leaderboard at {{best_p50}} all-in (24 h average) across {{count}} measured perp venues. The number includes taker fee plus the spread crossed at $1000 notional, not the rack-rate taker fee alone."
+ - "Lighter charges zero taker fees on ETH-PERP, confirmed live via their public API. {{name:lighter}} clocks {{p50:lighter}} all-in (24 h average), so the headline figure is essentially the half-spread plus orderbook impact crossed at $1000."
+ - "{{name:hyperliquid}} sits at {{p50:hyperliquid}} all-in (24 h average) on a 4.5 bps base taker. The deep HyperBFT orderbook keeps the spread component compressed, so the all-in number tracks the rack rate closer than any other venue in the cohort."
+ - "{{name:dydx}} sits at {{p50:dydx}} all-in (24 h average). The published tier-0 taker fee is 5 bps; the rest is the orderbook crossed at $1000, which on dYdX v4 is consistently the tightest non-zero-fee book outside Hyperliquid."
+ - "GMX position fee is variable: 4 bps on positive impact opens, 6 bps on negative impact. We report the negative branch (conservative upper bound). {{name:gmx}} clocks {{p50:gmx}} all-in (24 h average)."
+ - "{{worst_name}} trails at {{worst_p50}} all-in (24 h average). The leaderboard reorders intra-day with spread volatility, so a venue cheap on the rack rate can still trail when its book thins out at a $1000 buy."
source: https://github.com/ChainBench/OpenChainBench/tree/main/harnesses/perp-fees
@@ -67,13 +67,13 @@ prometheus:
faq:
- q: "What is the cheapest perpetual futures DEX right now?"
- a: "{{best_name}} currently leads the leaderboard at {{best_p50}} all-in (p50, 24 h) for opening a $1000 ETH long 10x position. The leaderboard re-sorts every five minutes against fresh Prometheus samples, so the answer reflects the last 24 hours of live data from each venue's public API, not a frozen rack-rate table."
+ a: "{{best_name}} currently leads the leaderboard at {{best_p50}} all-in (24 h average) for opening a $1000 ETH long 10x position. The leaderboard re-sorts every five minutes against fresh Prometheus samples, so the answer reflects the last 24 hours of live data from each venue's public API, not a frozen rack-rate table."
- q: "Is Lighter really zero fees on perpetual futures?"
a: "The taker fee on Lighter is 0 bps, confirmed live via `/orderBookDetails` rather than a marketing page. The all-in cost is not zero, however, because every market order still crosses the half-spread and eats price impact at notional. The Lighter line on this benchmark shows what that residual cost actually is at $1000, which is the right number to compare against a paid venue's all-in figure."
- q: "How do Hyperliquid fees compare to Lighter or dYdX in practice?"
a: "Hyperliquid quotes 4.5 bps taker but its HyperBFT orderbook is among the deepest of any decentralized venue, so the spread component stays compressed and the all-in figure usually clocks close to the rack rate. Lighter starts from 0 bps taker but pays for it in slightly wider spreads on smaller markets. dYdX v4 sits at 5 bps taker with a comparably tight book. The leaderboard above shows the resulting one-number comparison live."
- q: "What is the actual cost of trading on Hyperliquid for a $1000 position?"
- a: "{{name:hyperliquid}} is currently at {{p50:hyperliquid}} all-in (p50, 24 h), which means a $1000 ETH 10x long costs that fraction of $1000 to open. Funding is charged separately and is not part of this benchmark; we publish the per-hour funding rate as a side metric (`perp_fees_funding_rate_per_hour_bps`) so traders sizing a multi-hour hold can layer it on top."
+ a: "{{name:hyperliquid}} is currently at {{p50:hyperliquid}} all-in (24 h average), which means a $1000 ETH 10x long costs that fraction of $1000 to open. Funding is charged separately and is not part of this benchmark; we publish the per-hour funding rate as a side metric (`perp_fees_funding_rate_per_hour_bps`) so traders sizing a multi-hour hold can layer it on top."
- q: "Why include spread and impact instead of just taker fee?"
a: "Rack-rate taker fee is what every comparison article quotes; it is also what every trader stops being able to read off a marketing page the second they place a market order. Spread plus impact at notional is the rest of the bill. On a tight book the gap is small, on a thin book it can dominate a 5 bps taker. Bundling both into one number is the only way to make a fair across-venue ranking that survives contact with a $1000 trade."
- q: "How does GMX v2 fee work and why does this benchmark show the higher number?"
diff --git a/benchmarks/pm-data-freshness.yml b/benchmarks/pm-data-freshness.yml
index 07d38d87..15a89842 100644
--- a/benchmarks/pm-data-freshness.yml
+++ b/benchmarks/pm-data-freshness.yml
@@ -69,7 +69,7 @@ faq:
- q: "Are these numbers comparable to Kalshi or Limitless?"
a: "Not directly. This benchmark measures Polymarket as the underlying venue, since it's the largest and the one most providers proxy. Kalshi and Limitless are separate exchanges with separate data feeds, and providers that cover them often have a different ingestion path. We may add a Kalshi specific tab in a later phase. For now, treat the leaderboard as 'how fresh is your Polymarket data feed'."
- q: "How does OpenChainBench measure freshness?"
- a: "Three WebSocket clients run in parallel inside the harness, all subscribed to the same basket of top volume Polymarket markets. Every minute we refresh the basket from `gamma-api.polymarket.com`. For each trade event, we compute a signature `(conditionId, price rounded to 3 decimals, size in micros, 5 second time bucket)` and record the wall clock receive time on each provider. The freshness delta is `recv_provider - recv_polymarket` for the same signature. We export the histogram to Prometheus, the leaderboard reads the 24h p50."
+ a: "Three WebSocket clients run in parallel inside the harness, all subscribed to the same basket of top volume Polymarket markets. Every 5 minutes we refresh the basket from `gamma-api.polymarket.com`. For each trade event, we compute a signature `(conditionId, price rounded to 3 decimals, size in micros, 5 second time bucket)` and record the wall clock receive time on each provider. The freshness delta is `recv_provider - recv_polymarket` for the same signature. We export the histogram to Prometheus, the leaderboard reads the 24h p50."
source: https://github.com/MobulaFi/mobula-monorepo/tree/main/miniapps/pm-freshness-bench
diff --git a/benchmarks/rpc-capabilities.yml b/benchmarks/rpc-capabilities.yml
index d791c114..1457d3d3 100644
--- a/benchmarks/rpc-capabilities.yml
+++ b/benchmarks/rpc-capabilities.yml
@@ -12,6 +12,52 @@ metric: RPC latency
unit: ms
higher_is_better: false
+# Per-chain landing pages (/benchmarks/rpc-capabilities/). Each
+# entry must keep its claims chain-scoped: the leader placeholder
+# resolves against bestPerChain, never the cross-chain aggregate, and
+# the page itself surfaces per-region leaders when they diverge.
+per_chain_explainer:
+ - slug: ethereum
+ h2: "Fastest free Ethereum RPC"
+ body: |
+ {{best_name:chain:ethereum}} currently leads the free, no-key Ethereum RPC field at {{best_p50:chain:ethereum}} (`eth_blockNumber` p50, 24h), measured against 9 providers, the largest cohort in this bench. Ethereum is also where the silent-failure analysis earns its keep: Cloudflare-eth answers HTTP 200 with a JSON-RPC error field on many methods, and Merkle is excluded after recurring Cloudflare lockouts. Probes run every 15 seconds from us-east, eu-west and Singapore.
+ - slug: base
+ h2: "Fastest free Base RPC"
+ body: |
+ Coinbase's own `mainnet.base.org` goes head-to-head with PublicNode, dRPC, Tenderly and Merkle on Base, an unusually clean comparison because the chain-official endpoint is operated by the same team that runs the sequencer. The current leader is {{best_name:chain:base}} at {{best_p50:chain:base}} (`eth_blockNumber` p50, 24h) across 6 providers, probed every 15 seconds from three regions with stale-head detection against the cross-provider tip.
+ - slug: bnb
+ h2: "Fastest free BNB Chain RPC"
+ body: |
+ Binance's `bsc-dataseed1` is the incumbent default on BNB Chain, but PublicNode, dRPC and Merkle have closed the latency gap from EU origins. The current leader is {{best_name:chain:bnb}} at {{best_p50:chain:bnb}} (`eth_blockNumber` p50, 24h) across 5 providers. Every endpoint is probed with the identical call every 15 seconds from us-east, eu-west and Singapore, so the ranking reflects sustained round-trip latency, not a one-off burst.
+ - slug: arbitrum
+ h2: "Fastest free Arbitrum RPC"
+ body: |
+ Arbitrum carries the second-largest cohort in this bench, 8 no-key providers, and is one of the few chains where Lava and MeowRPC compete alongside PublicNode and the Arbitrum Foundation's own endpoint. The current leader is {{best_name:chain:arbitrum}} at {{best_p50:chain:arbitrum}} (`eth_blockNumber` p50, 24h). Latency is sampled every 15 seconds from three regions; archive-depth checks flag endpoints that serve pruned state as non-archive.
+ - slug: optimism
+ h2: "Fastest free Optimism RPC"
+ body: |
+ Optimism's field pits the Optimism Foundation endpoint against 5 multi-chain gateways. The current leader is {{best_name:chain:optimism}} at {{best_p50:chain:optimism}} (`eth_blockNumber` p50, 24h) across 6 providers. As on every chain here, the harness classifies each response (`ok`, `http_err`, `jsonrpc_err`, `stale`, `timeout`) so an endpoint stuck on an old head is never ranked as fastest, and probes originate from us-east, eu-west and Singapore.
+ - slug: avalanche
+ h2: "Fastest free Avalanche RPC"
+ body: |
+ Avalanche's chain-official endpoint competes with 5 no-key multi-chain gateways for the C-Chain. The current leader is {{best_name:chain:avalanche}} at {{best_p50:chain:avalanche}} (`eth_blockNumber` p50, 24h) across 6 providers. The probe is the same single call every 15 seconds from three regions, with stale-head detection flagging any provider more than 20 blocks behind the cross-provider tip.
+ - slug: polygon
+ h2: "Fastest free Polygon RPC"
+ body: |
+ Polygon has no chain-official endpoint in this bench, so the comparison is purely between multi-chain no-key gateways. The current leader is {{best_name:chain:polygon}} at {{best_p50:chain:polygon}} (`eth_blockNumber` p50, 24h) across 5 providers. Each one answers the identical call every 15 seconds from us-east, eu-west and Singapore, and the result classification separates real latency from silent JSON-RPC failures behind an HTTP 200.
+ - slug: linea
+ h2: "Fastest free Linea RPC"
+ body: |
+ The no-key field thins out on Linea: 4 providers qualify, all multi-chain gateways. The current leader is {{best_name:chain:linea}} at {{best_p50:chain:linea}} (`eth_blockNumber` p50, 24h). Thinner competition makes the reliability columns matter more than raw speed, a fast endpoint with a high `stale` or `timeout` rate is a worse default than a slightly slower consistent one. Probes run every 15 seconds from three regions.
+ - slug: scroll
+ h2: "Fastest free Scroll RPC"
+ body: |
+ Scroll is one of the smallest cohorts in this bench, 4 no-key providers, all multi-chain gateways. The current leader is {{best_name:chain:scroll}} at {{best_p50:chain:scroll}} (`eth_blockNumber` p50, 24h). The harness runs the identical probe every 15 seconds from us-east, eu-west and Singapore, with stale-head detection against the cross-provider tip so a frozen endpoint cannot top the table.
+ - slug: mantle
+ h2: "Fastest free Mantle RPC"
+ body: |
+ Mantle rounds out the long tail with 4 qualifying no-key providers, all multi-chain gateways. The current leader is {{best_name:chain:mantle}} at {{best_p50:chain:mantle}} (`eth_blockNumber` p50, 24h). Like every chain in this bench the number is a sustained median, the same call every 15 seconds from three regions over a rolling 24 hours, not a marketing burst, and archive-depth support is audited separately.
+
seo_intro: |
This benchmark answers the question every developer reaching for a
free public RPC asks before pasting a URL into their dapp. which
@@ -45,7 +91,7 @@ seo_intro: |
abstract: |
We measure the round-trip latency of a single, identical RPC call
(`eth_blockNumber`) against every major no-key Ethereum-compatible
- public RPC endpoint, on Ethereum mainnet, Base and BNB Chain. The
+ public RPC endpoint, across 10 EVM chains. The
harness emits three metric families from one binary because they
share the same (provider × chain) client matrix and same 15 s
scrape interval: (a) `rpc_latency_milliseconds` for the speed
@@ -96,6 +142,7 @@ source: https://github.com/ChainBench/OpenChainBench/tree/main/harnesses/rpc-cap
prometheus:
window: 24h
+ freshness_metric: rpc_latency_milliseconds
# Chain selector. tabs at the top of the page. Server injects
# `chain="X"` into every PromQL query for the active tab. The special
@@ -124,6 +171,12 @@ dimensions:
- { value: eu-west, label: EU-West }
- { value: sgp, label: Singapore }
+# One instant query feeding the exact per-cell (chain x region) rankings
+# behind scoped badges and leadership claims. Cross-region averages hide
+# region-restricted leaders (dRPC wins every chain from sgp only), so
+# badge scoping reads these cells instead of the aggregate ranks.
+rank_matrix_query: avg by (provider, chain, region) (ocb:rpc_latency_milliseconds:p50_24h)
+
# Real metrics emitted by the rpc-capabilities harness:
# rpc_latency_milliseconds{provider, chain} gauge
# rpc_latency_milliseconds_histogram{provider, chain} histogram
@@ -143,22 +196,22 @@ providers:
tag: Allnodes-operated, 70+ chains, archive on most
formula: "50th percentile over 24h of client-side round-trip latency (ms) for a single `eth_blockNumber` POST sent every 15s from 3 regions (us-east + eu-west + sgp) to PublicNode's no-key endpoint."
queries:
- p50: avg(quantile_over_time(0.50, rpc_latency_milliseconds{provider="publicnode"}[24h]))
- p90: avg(quantile_over_time(0.90, rpc_latency_milliseconds{provider="publicnode"}[24h]))
- p99: avg(quantile_over_time(0.99, rpc_latency_milliseconds{provider="publicnode"}[24h]))
- mean: avg(avg_over_time(rpc_latency_milliseconds{provider="publicnode"}[24h]))
- success: sum(rate(rpc_call_total{provider="publicnode", result="ok"}[24h])) / sum(rate(rpc_call_total{provider="publicnode"}[24h]))
- sample_size: sum(increase(rpc_call_total{provider="publicnode"}[24h]))
+ p50: avg(ocb:rpc_latency_milliseconds:p50_24h{provider="publicnode"})
+ p90: avg(ocb:rpc_latency_milliseconds:p90_24h{provider="publicnode"})
+ p99: avg(ocb:rpc_latency_milliseconds:p99_24h{provider="publicnode"})
+ mean: avg(ocb:rpc_latency_milliseconds:mean_24h{provider="publicnode"})
+ success: sum(ocb:rpc_call:ok_rate_24h{provider="publicnode"}) / sum(ocb:rpc_call:rate_24h{provider="publicnode"})
+ sample_size: sum(ocb:rpc_call:increase_24h{provider="publicnode"})
series: avg(avg_over_time(rpc_latency_milliseconds{provider="publicnode"}[1h]))
regions:
- region: us-east
- p50: quantile_over_time(0.50, rpc_latency_milliseconds{provider="publicnode", region="us-east"}[24h])
+ p50: avg(ocb:rpc_latency_milliseconds:p50_24h{provider="publicnode", region="us-east"})
series: avg_over_time(rpc_latency_milliseconds{provider="publicnode", region="us-east"}[1h])
- region: eu-west
- p50: quantile_over_time(0.50, rpc_latency_milliseconds{provider="publicnode", region="eu-west"}[24h])
+ p50: avg(ocb:rpc_latency_milliseconds:p50_24h{provider="publicnode", region="eu-west"})
series: avg_over_time(rpc_latency_milliseconds{provider="publicnode", region="eu-west"}[1h])
- region: ap-southeast
- p50: quantile_over_time(0.50, rpc_latency_milliseconds{provider="publicnode", region="sgp"}[24h])
+ 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: drpc
@@ -166,22 +219,22 @@ providers:
tag: Decentralized RPC mesh, consensus-checked
formula: "50th percentile over 24h of client-side round-trip latency (ms) for a single `eth_blockNumber` POST sent every 15s from 3 regions (us-east + eu-west + sgp) to dRPC's decentralized mesh."
queries:
- p50: avg(quantile_over_time(0.50, rpc_latency_milliseconds{provider="drpc"}[24h]))
- p90: avg(quantile_over_time(0.90, rpc_latency_milliseconds{provider="drpc"}[24h]))
- p99: avg(quantile_over_time(0.99, rpc_latency_milliseconds{provider="drpc"}[24h]))
- mean: avg(avg_over_time(rpc_latency_milliseconds{provider="drpc"}[24h]))
- success: sum(rate(rpc_call_total{provider="drpc", result="ok"}[24h])) / sum(rate(rpc_call_total{provider="drpc"}[24h]))
- sample_size: sum(increase(rpc_call_total{provider="drpc"}[24h]))
+ p50: avg(ocb:rpc_latency_milliseconds:p50_24h{provider="drpc"})
+ p90: avg(ocb:rpc_latency_milliseconds:p90_24h{provider="drpc"})
+ p99: avg(ocb:rpc_latency_milliseconds:p99_24h{provider="drpc"})
+ mean: avg(ocb:rpc_latency_milliseconds:mean_24h{provider="drpc"})
+ success: sum(ocb:rpc_call:ok_rate_24h{provider="drpc"}) / sum(ocb:rpc_call:rate_24h{provider="drpc"})
+ sample_size: sum(ocb:rpc_call:increase_24h{provider="drpc"})
series: avg(avg_over_time(rpc_latency_milliseconds{provider="drpc"}[1h]))
regions:
- region: us-east
- p50: quantile_over_time(0.50, rpc_latency_milliseconds{provider="drpc", region="us-east"}[24h])
+ p50: avg(ocb:rpc_latency_milliseconds:p50_24h{provider="drpc", region="us-east"})
series: avg_over_time(rpc_latency_milliseconds{provider="drpc", region="us-east"}[1h])
- region: eu-west
- p50: quantile_over_time(0.50, rpc_latency_milliseconds{provider="drpc", region="eu-west"}[24h])
+ p50: avg(ocb:rpc_latency_milliseconds:p50_24h{provider="drpc", region="eu-west"})
series: avg_over_time(rpc_latency_milliseconds{provider="drpc", region="eu-west"}[1h])
- region: ap-southeast
- p50: quantile_over_time(0.50, rpc_latency_milliseconds{provider="drpc", region="sgp"}[24h])
+ p50: avg(ocb:rpc_latency_milliseconds:p50_24h{provider="drpc", region="sgp"})
series: avg_over_time(rpc_latency_milliseconds{provider="drpc", region="sgp"}[1h])
- slug: 1rpc
@@ -189,22 +242,22 @@ providers:
tag: Privacy-preserving gateway by Automata Network
formula: "50th percentile over 24h of client-side round-trip latency (ms) for a single `eth_blockNumber` POST sent every 15s from 3 regions (us-east + eu-west + sgp) to 1RPC's Automata gateway."
queries:
- p50: avg(quantile_over_time(0.50, rpc_latency_milliseconds{provider="1rpc"}[24h]))
- p90: avg(quantile_over_time(0.90, rpc_latency_milliseconds{provider="1rpc"}[24h]))
- p99: avg(quantile_over_time(0.99, rpc_latency_milliseconds{provider="1rpc"}[24h]))
- mean: avg(avg_over_time(rpc_latency_milliseconds{provider="1rpc"}[24h]))
- success: sum(rate(rpc_call_total{provider="1rpc", result="ok"}[24h])) / sum(rate(rpc_call_total{provider="1rpc"}[24h]))
- sample_size: sum(increase(rpc_call_total{provider="1rpc"}[24h]))
+ p50: avg(ocb:rpc_latency_milliseconds:p50_24h{provider="1rpc"})
+ p90: avg(ocb:rpc_latency_milliseconds:p90_24h{provider="1rpc"})
+ p99: avg(ocb:rpc_latency_milliseconds:p99_24h{provider="1rpc"})
+ mean: avg(ocb:rpc_latency_milliseconds:mean_24h{provider="1rpc"})
+ success: sum(ocb:rpc_call:ok_rate_24h{provider="1rpc"}) / sum(ocb:rpc_call:rate_24h{provider="1rpc"})
+ sample_size: sum(ocb:rpc_call:increase_24h{provider="1rpc"})
series: avg(avg_over_time(rpc_latency_milliseconds{provider="1rpc"}[1h]))
regions:
- region: us-east
- p50: quantile_over_time(0.50, rpc_latency_milliseconds{provider="1rpc", region="us-east"}[24h])
+ p50: avg(ocb:rpc_latency_milliseconds:p50_24h{provider="1rpc", region="us-east"})
series: avg_over_time(rpc_latency_milliseconds{provider="1rpc", region="us-east"}[1h])
- region: eu-west
- p50: quantile_over_time(0.50, rpc_latency_milliseconds{provider="1rpc", region="eu-west"}[24h])
+ p50: avg(ocb:rpc_latency_milliseconds:p50_24h{provider="1rpc", region="eu-west"})
series: avg_over_time(rpc_latency_milliseconds{provider="1rpc", region="eu-west"}[1h])
- region: ap-southeast
- p50: quantile_over_time(0.50, rpc_latency_milliseconds{provider="1rpc", region="sgp"}[24h])
+ p50: avg(ocb:rpc_latency_milliseconds:p50_24h{provider="1rpc", region="sgp"})
series: avg_over_time(rpc_latency_milliseconds{provider="1rpc", region="sgp"}[1h])
- slug: meowrpc
@@ -212,22 +265,22 @@ providers:
tag: Free public RPC, no registration
formula: "50th percentile over 24h of client-side round-trip latency (ms) for a single `eth_blockNumber` POST sent every 15s from 3 regions (us-east + eu-west + sgp) to MeowRPC's no-key endpoint."
queries:
- p50: avg(quantile_over_time(0.50, rpc_latency_milliseconds{provider="meowrpc"}[24h]))
- p90: avg(quantile_over_time(0.90, rpc_latency_milliseconds{provider="meowrpc"}[24h]))
- p99: avg(quantile_over_time(0.99, rpc_latency_milliseconds{provider="meowrpc"}[24h]))
- mean: avg(avg_over_time(rpc_latency_milliseconds{provider="meowrpc"}[24h]))
- success: sum(rate(rpc_call_total{provider="meowrpc", result="ok"}[24h])) / sum(rate(rpc_call_total{provider="meowrpc"}[24h]))
- sample_size: sum(increase(rpc_call_total{provider="meowrpc"}[24h]))
+ p50: avg(ocb:rpc_latency_milliseconds:p50_24h{provider="meowrpc"})
+ p90: avg(ocb:rpc_latency_milliseconds:p90_24h{provider="meowrpc"})
+ p99: avg(ocb:rpc_latency_milliseconds:p99_24h{provider="meowrpc"})
+ mean: avg(ocb:rpc_latency_milliseconds:mean_24h{provider="meowrpc"})
+ success: sum(ocb:rpc_call:ok_rate_24h{provider="meowrpc"}) / sum(ocb:rpc_call:rate_24h{provider="meowrpc"})
+ sample_size: sum(ocb:rpc_call:increase_24h{provider="meowrpc"})
series: avg(avg_over_time(rpc_latency_milliseconds{provider="meowrpc"}[1h]))
regions:
- region: us-east
- p50: quantile_over_time(0.50, rpc_latency_milliseconds{provider="meowrpc", region="us-east"}[24h])
+ p50: avg(ocb:rpc_latency_milliseconds:p50_24h{provider="meowrpc", region="us-east"})
series: avg_over_time(rpc_latency_milliseconds{provider="meowrpc", region="us-east"}[1h])
- region: eu-west
- p50: quantile_over_time(0.50, rpc_latency_milliseconds{provider="meowrpc", region="eu-west"}[24h])
+ p50: avg(ocb:rpc_latency_milliseconds:p50_24h{provider="meowrpc", region="eu-west"})
series: avg_over_time(rpc_latency_milliseconds{provider="meowrpc", region="eu-west"}[1h])
- region: ap-southeast
- p50: quantile_over_time(0.50, rpc_latency_milliseconds{provider="meowrpc", region="sgp"}[24h])
+ p50: avg(ocb:rpc_latency_milliseconds:p50_24h{provider="meowrpc", region="sgp"})
series: avg_over_time(rpc_latency_milliseconds{provider="meowrpc", region="sgp"}[1h])
- slug: flashbots
@@ -235,22 +288,22 @@ providers:
tag: Private-mempool RPC, anti-sandwich
formula: "50th percentile over 24h of client-side round-trip latency (ms) for a single `eth_blockNumber` POST sent every 15s from 3 regions (us-east + eu-west + sgp) to the Flashbots Protect read proxy."
queries:
- p50: avg(quantile_over_time(0.50, rpc_latency_milliseconds{provider="flashbots"}[24h]))
- p90: avg(quantile_over_time(0.90, rpc_latency_milliseconds{provider="flashbots"}[24h]))
- p99: avg(quantile_over_time(0.99, rpc_latency_milliseconds{provider="flashbots"}[24h]))
- mean: avg(avg_over_time(rpc_latency_milliseconds{provider="flashbots"}[24h]))
- success: sum(rate(rpc_call_total{provider="flashbots", result="ok"}[24h])) / sum(rate(rpc_call_total{provider="flashbots"}[24h]))
- sample_size: sum(increase(rpc_call_total{provider="flashbots"}[24h]))
+ p50: avg(ocb:rpc_latency_milliseconds:p50_24h{provider="flashbots"})
+ p90: avg(ocb:rpc_latency_milliseconds:p90_24h{provider="flashbots"})
+ p99: avg(ocb:rpc_latency_milliseconds:p99_24h{provider="flashbots"})
+ mean: avg(ocb:rpc_latency_milliseconds:mean_24h{provider="flashbots"})
+ success: sum(ocb:rpc_call:ok_rate_24h{provider="flashbots"}) / sum(ocb:rpc_call:rate_24h{provider="flashbots"})
+ sample_size: sum(ocb:rpc_call:increase_24h{provider="flashbots"})
series: avg(avg_over_time(rpc_latency_milliseconds{provider="flashbots"}[1h]))
regions:
- region: us-east
- p50: quantile_over_time(0.50, rpc_latency_milliseconds{provider="flashbots", region="us-east"}[24h])
+ p50: avg(ocb:rpc_latency_milliseconds:p50_24h{provider="flashbots", region="us-east"})
series: avg_over_time(rpc_latency_milliseconds{provider="flashbots", region="us-east"}[1h])
- region: eu-west
- p50: quantile_over_time(0.50, rpc_latency_milliseconds{provider="flashbots", region="eu-west"}[24h])
+ p50: avg(ocb:rpc_latency_milliseconds:p50_24h{provider="flashbots", region="eu-west"})
series: avg_over_time(rpc_latency_milliseconds{provider="flashbots", region="eu-west"}[1h])
- region: ap-southeast
- p50: quantile_over_time(0.50, rpc_latency_milliseconds{provider="flashbots", region="sgp"}[24h])
+ p50: avg(ocb:rpc_latency_milliseconds:p50_24h{provider="flashbots", region="sgp"})
series: avg_over_time(rpc_latency_milliseconds{provider="flashbots", region="sgp"}[1h])
- slug: cloudflare
@@ -258,22 +311,22 @@ providers:
tag: Permissioned-mode for many JSON-RPC methods
formula: "50th percentile over 24h of client-side round-trip latency (ms) for a single `eth_blockNumber` POST sent every 15s from 3 regions (us-east + eu-west + sgp) to cloudflare-eth (check success column for jsonrpc_err share)."
queries:
- p50: avg(quantile_over_time(0.50, rpc_latency_milliseconds{provider="cloudflare"}[24h]))
- p90: avg(quantile_over_time(0.90, rpc_latency_milliseconds{provider="cloudflare"}[24h]))
- p99: avg(quantile_over_time(0.99, rpc_latency_milliseconds{provider="cloudflare"}[24h]))
- mean: avg(avg_over_time(rpc_latency_milliseconds{provider="cloudflare"}[24h]))
- success: sum(rate(rpc_call_total{provider="cloudflare", result="ok"}[24h])) / sum(rate(rpc_call_total{provider="cloudflare"}[24h]))
- sample_size: sum(increase(rpc_call_total{provider="cloudflare"}[24h]))
+ p50: avg(ocb:rpc_latency_milliseconds:p50_24h{provider="cloudflare"})
+ p90: avg(ocb:rpc_latency_milliseconds:p90_24h{provider="cloudflare"})
+ p99: avg(ocb:rpc_latency_milliseconds:p99_24h{provider="cloudflare"})
+ mean: avg(ocb:rpc_latency_milliseconds:mean_24h{provider="cloudflare"})
+ success: sum(ocb:rpc_call:ok_rate_24h{provider="cloudflare"}) / sum(ocb:rpc_call:rate_24h{provider="cloudflare"})
+ sample_size: sum(ocb:rpc_call:increase_24h{provider="cloudflare"})
series: avg(avg_over_time(rpc_latency_milliseconds{provider="cloudflare"}[1h]))
regions:
- region: us-east
- p50: quantile_over_time(0.50, rpc_latency_milliseconds{provider="cloudflare", region="us-east"}[24h])
+ p50: avg(ocb:rpc_latency_milliseconds:p50_24h{provider="cloudflare", region="us-east"})
series: avg_over_time(rpc_latency_milliseconds{provider="cloudflare", region="us-east"}[1h])
- region: eu-west
- p50: quantile_over_time(0.50, rpc_latency_milliseconds{provider="cloudflare", region="eu-west"}[24h])
+ p50: avg(ocb:rpc_latency_milliseconds:p50_24h{provider="cloudflare", region="eu-west"})
series: avg_over_time(rpc_latency_milliseconds{provider="cloudflare", region="eu-west"}[1h])
- region: ap-southeast
- p50: quantile_over_time(0.50, rpc_latency_milliseconds{provider="cloudflare", region="sgp"}[24h])
+ p50: avg(ocb:rpc_latency_milliseconds:p50_24h{provider="cloudflare", region="sgp"})
series: avg_over_time(rpc_latency_milliseconds{provider="cloudflare", region="sgp"}[1h])
- slug: base-official
@@ -281,22 +334,22 @@ providers:
tag: Coinbase-operated, Base mainnet RPC
formula: "50th percentile over 24h of client-side round-trip latency (ms) for a single `eth_blockNumber` POST sent every 15s from 3 regions (us-east + eu-west + sgp) to Coinbase's `mainnet.base.org` endpoint."
queries:
- p50: avg(quantile_over_time(0.50, rpc_latency_milliseconds{provider="base-official"}[24h]))
- p90: avg(quantile_over_time(0.90, rpc_latency_milliseconds{provider="base-official"}[24h]))
- p99: avg(quantile_over_time(0.99, rpc_latency_milliseconds{provider="base-official"}[24h]))
- mean: avg(avg_over_time(rpc_latency_milliseconds{provider="base-official"}[24h]))
- success: sum(rate(rpc_call_total{provider="base-official", result="ok"}[24h])) / sum(rate(rpc_call_total{provider="base-official"}[24h]))
- sample_size: sum(increase(rpc_call_total{provider="base-official"}[24h]))
+ p50: avg(ocb:rpc_latency_milliseconds:p50_24h{provider="base-official"})
+ p90: avg(ocb:rpc_latency_milliseconds:p90_24h{provider="base-official"})
+ p99: avg(ocb:rpc_latency_milliseconds:p99_24h{provider="base-official"})
+ mean: avg(ocb:rpc_latency_milliseconds:mean_24h{provider="base-official"})
+ success: sum(ocb:rpc_call:ok_rate_24h{provider="base-official"}) / sum(ocb:rpc_call:rate_24h{provider="base-official"})
+ sample_size: sum(ocb:rpc_call:increase_24h{provider="base-official"})
series: avg(avg_over_time(rpc_latency_milliseconds{provider="base-official"}[1h]))
regions:
- region: us-east
- p50: quantile_over_time(0.50, rpc_latency_milliseconds{provider="base-official", region="us-east"}[24h])
+ p50: avg(ocb:rpc_latency_milliseconds:p50_24h{provider="base-official", region="us-east"})
series: avg_over_time(rpc_latency_milliseconds{provider="base-official", region="us-east"}[1h])
- region: eu-west
- p50: quantile_over_time(0.50, rpc_latency_milliseconds{provider="base-official", region="eu-west"}[24h])
+ p50: avg(ocb:rpc_latency_milliseconds:p50_24h{provider="base-official", region="eu-west"})
series: avg_over_time(rpc_latency_milliseconds{provider="base-official", region="eu-west"}[1h])
- region: ap-southeast
- p50: quantile_over_time(0.50, rpc_latency_milliseconds{provider="base-official", region="sgp"}[24h])
+ p50: avg(ocb:rpc_latency_milliseconds:p50_24h{provider="base-official", region="sgp"})
series: avg_over_time(rpc_latency_milliseconds{provider="base-official", region="sgp"}[1h])
- slug: binance
@@ -304,22 +357,22 @@ providers:
tag: BNB Chain dataseed RPC, Binance-operated
formula: "50th percentile over 24h of client-side round-trip latency (ms) for a single `eth_blockNumber` POST sent every 15s from 3 regions (us-east + eu-west + sgp) to Binance's `bsc-dataseed1.binance.org` endpoint."
queries:
- p50: avg(quantile_over_time(0.50, rpc_latency_milliseconds{provider="binance"}[24h]))
- p90: avg(quantile_over_time(0.90, rpc_latency_milliseconds{provider="binance"}[24h]))
- p99: avg(quantile_over_time(0.99, rpc_latency_milliseconds{provider="binance"}[24h]))
- mean: avg(avg_over_time(rpc_latency_milliseconds{provider="binance"}[24h]))
- success: sum(rate(rpc_call_total{provider="binance", result="ok"}[24h])) / sum(rate(rpc_call_total{provider="binance"}[24h]))
- sample_size: sum(increase(rpc_call_total{provider="binance"}[24h]))
+ p50: avg(ocb:rpc_latency_milliseconds:p50_24h{provider="binance"})
+ p90: avg(ocb:rpc_latency_milliseconds:p90_24h{provider="binance"})
+ p99: avg(ocb:rpc_latency_milliseconds:p99_24h{provider="binance"})
+ mean: avg(ocb:rpc_latency_milliseconds:mean_24h{provider="binance"})
+ success: sum(ocb:rpc_call:ok_rate_24h{provider="binance"}) / sum(ocb:rpc_call:rate_24h{provider="binance"})
+ sample_size: sum(ocb:rpc_call:increase_24h{provider="binance"})
series: avg(avg_over_time(rpc_latency_milliseconds{provider="binance"}[1h]))
regions:
- region: us-east
- p50: quantile_over_time(0.50, rpc_latency_milliseconds{provider="binance", region="us-east"}[24h])
+ p50: avg(ocb:rpc_latency_milliseconds:p50_24h{provider="binance", region="us-east"})
series: avg_over_time(rpc_latency_milliseconds{provider="binance", region="us-east"}[1h])
- region: eu-west
- p50: quantile_over_time(0.50, rpc_latency_milliseconds{provider="binance", region="eu-west"}[24h])
+ p50: avg(ocb:rpc_latency_milliseconds:p50_24h{provider="binance", region="eu-west"})
series: avg_over_time(rpc_latency_milliseconds{provider="binance", region="eu-west"}[1h])
- region: ap-southeast
- p50: quantile_over_time(0.50, rpc_latency_milliseconds{provider="binance", region="sgp"}[24h])
+ p50: avg(ocb:rpc_latency_milliseconds:p50_24h{provider="binance", region="sgp"})
series: avg_over_time(rpc_latency_milliseconds{provider="binance", region="sgp"}[1h])
- slug: tenderly
@@ -327,22 +380,22 @@ providers:
tag: Multi-chain public gateway, 9 chains, no key
formula: "50th percentile over 24h of client-side round-trip latency (ms) for a single `eth_blockNumber` POST sent every 15s from 3 regions (us-east + eu-west + sgp) to `gateway.tenderly.co/public/`."
queries:
- p50: avg(quantile_over_time(0.50, rpc_latency_milliseconds{provider="tenderly"}[24h]))
- p90: avg(quantile_over_time(0.90, rpc_latency_milliseconds{provider="tenderly"}[24h]))
- p99: avg(quantile_over_time(0.99, rpc_latency_milliseconds{provider="tenderly"}[24h]))
- mean: avg(avg_over_time(rpc_latency_milliseconds{provider="tenderly"}[24h]))
- success: sum(rate(rpc_call_total{provider="tenderly", result="ok"}[24h])) / sum(rate(rpc_call_total{provider="tenderly"}[24h]))
- sample_size: sum(increase(rpc_call_total{provider="tenderly"}[24h]))
+ p50: avg(ocb:rpc_latency_milliseconds:p50_24h{provider="tenderly"})
+ p90: avg(ocb:rpc_latency_milliseconds:p90_24h{provider="tenderly"})
+ p99: avg(ocb:rpc_latency_milliseconds:p99_24h{provider="tenderly"})
+ mean: avg(ocb:rpc_latency_milliseconds:mean_24h{provider="tenderly"})
+ success: sum(ocb:rpc_call:ok_rate_24h{provider="tenderly"}) / sum(ocb:rpc_call:rate_24h{provider="tenderly"})
+ sample_size: sum(ocb:rpc_call:increase_24h{provider="tenderly"})
series: avg(avg_over_time(rpc_latency_milliseconds{provider="tenderly"}[1h]))
regions:
- region: us-east
- p50: quantile_over_time(0.50, rpc_latency_milliseconds{provider="tenderly", region="us-east"}[24h])
+ p50: avg(ocb:rpc_latency_milliseconds:p50_24h{provider="tenderly", region="us-east"})
series: avg_over_time(rpc_latency_milliseconds{provider="tenderly", region="us-east"}[1h])
- region: eu-west
- p50: quantile_over_time(0.50, rpc_latency_milliseconds{provider="tenderly", region="eu-west"}[24h])
+ p50: avg(ocb:rpc_latency_milliseconds:p50_24h{provider="tenderly", region="eu-west"})
series: avg_over_time(rpc_latency_milliseconds{provider="tenderly", region="eu-west"}[1h])
- region: ap-southeast
- p50: quantile_over_time(0.50, rpc_latency_milliseconds{provider="tenderly", region="sgp"}[24h])
+ p50: avg(ocb:rpc_latency_milliseconds:p50_24h{provider="tenderly", region="sgp"})
series: avg_over_time(rpc_latency_milliseconds{provider="tenderly", region="sgp"}[1h])
- slug: nodies
@@ -350,22 +403,22 @@ providers:
tag: POKT Network's decentralized public RPC successor, 7+ chains
formula: "50th percentile over 24h of client-side round-trip latency (ms) for a single `eth_blockNumber` POST sent every 15s from 3 regions (us-east + eu-west + sgp) to Nodies' `*-pokt.nodies.app` endpoint."
queries:
- p50: avg(quantile_over_time(0.50, rpc_latency_milliseconds{provider="nodies"}[24h]))
- p90: avg(quantile_over_time(0.90, rpc_latency_milliseconds{provider="nodies"}[24h]))
- p99: avg(quantile_over_time(0.99, rpc_latency_milliseconds{provider="nodies"}[24h]))
- mean: avg(avg_over_time(rpc_latency_milliseconds{provider="nodies"}[24h]))
- success: sum(rate(rpc_call_total{provider="nodies", result="ok"}[24h])) / sum(rate(rpc_call_total{provider="nodies"}[24h]))
- sample_size: sum(increase(rpc_call_total{provider="nodies"}[24h]))
+ p50: avg(ocb:rpc_latency_milliseconds:p50_24h{provider="nodies"})
+ p90: avg(ocb:rpc_latency_milliseconds:p90_24h{provider="nodies"})
+ p99: avg(ocb:rpc_latency_milliseconds:p99_24h{provider="nodies"})
+ mean: avg(ocb:rpc_latency_milliseconds:mean_24h{provider="nodies"})
+ success: sum(ocb:rpc_call:ok_rate_24h{provider="nodies"}) / sum(ocb:rpc_call:rate_24h{provider="nodies"})
+ sample_size: sum(ocb:rpc_call:increase_24h{provider="nodies"})
series: avg(avg_over_time(rpc_latency_milliseconds{provider="nodies"}[1h]))
regions:
- region: us-east
- p50: quantile_over_time(0.50, rpc_latency_milliseconds{provider="nodies", region="us-east"}[24h])
+ p50: avg(ocb:rpc_latency_milliseconds:p50_24h{provider="nodies", region="us-east"})
series: avg_over_time(rpc_latency_milliseconds{provider="nodies", region="us-east"}[1h])
- region: eu-west
- p50: quantile_over_time(0.50, rpc_latency_milliseconds{provider="nodies", region="eu-west"}[24h])
+ p50: avg(ocb:rpc_latency_milliseconds:p50_24h{provider="nodies", region="eu-west"})
series: avg_over_time(rpc_latency_milliseconds{provider="nodies", region="eu-west"}[1h])
- region: ap-southeast
- p50: quantile_over_time(0.50, rpc_latency_milliseconds{provider="nodies", region="sgp"}[24h])
+ p50: avg(ocb:rpc_latency_milliseconds:p50_24h{provider="nodies", region="sgp"})
series: avg_over_time(rpc_latency_milliseconds{provider="nodies", region="sgp"}[1h])
- slug: lava
@@ -373,22 +426,22 @@ providers:
tag: Decentralized permissionless RPC mesh (ETH + Arbitrum no-key)
formula: "50th percentile over 24h of client-side round-trip latency (ms) for a single `eth_blockNumber` POST sent every 15s from 3 regions (us-east + eu-west + sgp) to Lava Network's no-key mesh endpoint."
queries:
- p50: avg(quantile_over_time(0.50, rpc_latency_milliseconds{provider="lava"}[24h]))
- p90: avg(quantile_over_time(0.90, rpc_latency_milliseconds{provider="lava"}[24h]))
- p99: avg(quantile_over_time(0.99, rpc_latency_milliseconds{provider="lava"}[24h]))
- mean: avg(avg_over_time(rpc_latency_milliseconds{provider="lava"}[24h]))
- success: sum(rate(rpc_call_total{provider="lava", result="ok"}[24h])) / sum(rate(rpc_call_total{provider="lava"}[24h]))
- sample_size: sum(increase(rpc_call_total{provider="lava"}[24h]))
+ p50: avg(ocb:rpc_latency_milliseconds:p50_24h{provider="lava"})
+ p90: avg(ocb:rpc_latency_milliseconds:p90_24h{provider="lava"})
+ p99: avg(ocb:rpc_latency_milliseconds:p99_24h{provider="lava"})
+ mean: avg(ocb:rpc_latency_milliseconds:mean_24h{provider="lava"})
+ success: sum(ocb:rpc_call:ok_rate_24h{provider="lava"}) / sum(ocb:rpc_call:rate_24h{provider="lava"})
+ sample_size: sum(ocb:rpc_call:increase_24h{provider="lava"})
series: avg(avg_over_time(rpc_latency_milliseconds{provider="lava"}[1h]))
regions:
- region: us-east
- p50: quantile_over_time(0.50, rpc_latency_milliseconds{provider="lava", region="us-east"}[24h])
+ p50: avg(ocb:rpc_latency_milliseconds:p50_24h{provider="lava", region="us-east"})
series: avg_over_time(rpc_latency_milliseconds{provider="lava", region="us-east"}[1h])
- region: eu-west
- p50: quantile_over_time(0.50, rpc_latency_milliseconds{provider="lava", region="eu-west"}[24h])
+ p50: avg(ocb:rpc_latency_milliseconds:p50_24h{provider="lava", region="eu-west"})
series: avg_over_time(rpc_latency_milliseconds{provider="lava", region="eu-west"}[1h])
- region: ap-southeast
- p50: quantile_over_time(0.50, rpc_latency_milliseconds{provider="lava", region="sgp"}[24h])
+ p50: avg(ocb:rpc_latency_milliseconds:p50_24h{provider="lava", region="sgp"})
series: avg_over_time(rpc_latency_milliseconds{provider="lava", region="sgp"}[1h])
- slug: merkle
@@ -396,22 +449,22 @@ providers:
tag: Base + BSC public no-key gateway (Ethereum hit by Cloudflare 20-min lockout, excluded)
formula: "50th percentile over 24h of client-side round-trip latency (ms) for a single `eth_blockNumber` POST sent every 15s from 3 regions (us-east + eu-west + sgp) to Merkle's Base/BSC no-key gateway."
queries:
- p50: avg(quantile_over_time(0.50, rpc_latency_milliseconds{provider="merkle"}[24h]))
- p90: avg(quantile_over_time(0.90, rpc_latency_milliseconds{provider="merkle"}[24h]))
- p99: avg(quantile_over_time(0.99, rpc_latency_milliseconds{provider="merkle"}[24h]))
- mean: avg(avg_over_time(rpc_latency_milliseconds{provider="merkle"}[24h]))
- success: sum(rate(rpc_call_total{provider="merkle", result="ok"}[24h])) / sum(rate(rpc_call_total{provider="merkle"}[24h]))
- sample_size: sum(increase(rpc_call_total{provider="merkle"}[24h]))
+ p50: avg(ocb:rpc_latency_milliseconds:p50_24h{provider="merkle"})
+ p90: avg(ocb:rpc_latency_milliseconds:p90_24h{provider="merkle"})
+ p99: avg(ocb:rpc_latency_milliseconds:p99_24h{provider="merkle"})
+ mean: avg(ocb:rpc_latency_milliseconds:mean_24h{provider="merkle"})
+ success: sum(ocb:rpc_call:ok_rate_24h{provider="merkle"}) / sum(ocb:rpc_call:rate_24h{provider="merkle"})
+ sample_size: sum(ocb:rpc_call:increase_24h{provider="merkle"})
series: avg(avg_over_time(rpc_latency_milliseconds{provider="merkle"}[1h]))
regions:
- region: us-east
- p50: quantile_over_time(0.50, rpc_latency_milliseconds{provider="merkle", region="us-east"}[24h])
+ p50: avg(ocb:rpc_latency_milliseconds:p50_24h{provider="merkle", region="us-east"})
series: avg_over_time(rpc_latency_milliseconds{provider="merkle", region="us-east"}[1h])
- region: eu-west
- p50: quantile_over_time(0.50, rpc_latency_milliseconds{provider="merkle", region="eu-west"}[24h])
+ p50: avg(ocb:rpc_latency_milliseconds:p50_24h{provider="merkle", region="eu-west"})
series: avg_over_time(rpc_latency_milliseconds{provider="merkle", region="eu-west"}[1h])
- region: ap-southeast
- p50: quantile_over_time(0.50, rpc_latency_milliseconds{provider="merkle", region="sgp"}[24h])
+ p50: avg(ocb:rpc_latency_milliseconds:p50_24h{provider="merkle", region="sgp"})
series: avg_over_time(rpc_latency_milliseconds{provider="merkle", region="sgp"}[1h])
- slug: arbitrum-official
@@ -419,22 +472,22 @@ providers:
tag: Arbitrum Foundation public RPC, Arbitrum One only
formula: "50th percentile over 24h of client-side round-trip latency (ms) for a single `eth_blockNumber` POST sent every 15s from 3 regions (us-east + eu-west + sgp) to the Arbitrum Foundation's `arb1.arbitrum.io/rpc`."
queries:
- p50: avg(quantile_over_time(0.50, rpc_latency_milliseconds{provider="arbitrum-official"}[24h]))
- p90: avg(quantile_over_time(0.90, rpc_latency_milliseconds{provider="arbitrum-official"}[24h]))
- p99: avg(quantile_over_time(0.99, rpc_latency_milliseconds{provider="arbitrum-official"}[24h]))
- mean: avg(avg_over_time(rpc_latency_milliseconds{provider="arbitrum-official"}[24h]))
- success: sum(rate(rpc_call_total{provider="arbitrum-official", result="ok"}[24h])) / sum(rate(rpc_call_total{provider="arbitrum-official"}[24h]))
- sample_size: sum(increase(rpc_call_total{provider="arbitrum-official"}[24h]))
+ p50: avg(ocb:rpc_latency_milliseconds:p50_24h{provider="arbitrum-official"})
+ p90: avg(ocb:rpc_latency_milliseconds:p90_24h{provider="arbitrum-official"})
+ p99: avg(ocb:rpc_latency_milliseconds:p99_24h{provider="arbitrum-official"})
+ mean: avg(ocb:rpc_latency_milliseconds:mean_24h{provider="arbitrum-official"})
+ success: sum(ocb:rpc_call:ok_rate_24h{provider="arbitrum-official"}) / sum(ocb:rpc_call:rate_24h{provider="arbitrum-official"})
+ sample_size: sum(ocb:rpc_call:increase_24h{provider="arbitrum-official"})
series: avg(avg_over_time(rpc_latency_milliseconds{provider="arbitrum-official"}[1h]))
regions:
- region: us-east
- p50: quantile_over_time(0.50, rpc_latency_milliseconds{provider="arbitrum-official", region="us-east"}[24h])
+ p50: avg(ocb:rpc_latency_milliseconds:p50_24h{provider="arbitrum-official", region="us-east"})
series: avg_over_time(rpc_latency_milliseconds{provider="arbitrum-official", region="us-east"}[1h])
- region: eu-west
- p50: quantile_over_time(0.50, rpc_latency_milliseconds{provider="arbitrum-official", region="eu-west"}[24h])
+ p50: avg(ocb:rpc_latency_milliseconds:p50_24h{provider="arbitrum-official", region="eu-west"})
series: avg_over_time(rpc_latency_milliseconds{provider="arbitrum-official", region="eu-west"}[1h])
- region: ap-southeast
- p50: quantile_over_time(0.50, rpc_latency_milliseconds{provider="arbitrum-official", region="sgp"}[24h])
+ p50: avg(ocb:rpc_latency_milliseconds:p50_24h{provider="arbitrum-official", region="sgp"})
series: avg_over_time(rpc_latency_milliseconds{provider="arbitrum-official", region="sgp"}[1h])
- slug: optimism-official
@@ -442,22 +495,22 @@ providers:
tag: Optimism Foundation public RPC, Optimism mainnet only
formula: "50th percentile over 24h of client-side round-trip latency (ms) for a single `eth_blockNumber` POST sent every 15s from 3 regions (us-east + eu-west + sgp) to the Optimism Foundation's `mainnet.optimism.io`."
queries:
- p50: avg(quantile_over_time(0.50, rpc_latency_milliseconds{provider="optimism-official"}[24h]))
- p90: avg(quantile_over_time(0.90, rpc_latency_milliseconds{provider="optimism-official"}[24h]))
- p99: avg(quantile_over_time(0.99, rpc_latency_milliseconds{provider="optimism-official"}[24h]))
- mean: avg(avg_over_time(rpc_latency_milliseconds{provider="optimism-official"}[24h]))
- success: sum(rate(rpc_call_total{provider="optimism-official", result="ok"}[24h])) / sum(rate(rpc_call_total{provider="optimism-official"}[24h]))
- sample_size: sum(increase(rpc_call_total{provider="optimism-official"}[24h]))
+ p50: avg(ocb:rpc_latency_milliseconds:p50_24h{provider="optimism-official"})
+ p90: avg(ocb:rpc_latency_milliseconds:p90_24h{provider="optimism-official"})
+ p99: avg(ocb:rpc_latency_milliseconds:p99_24h{provider="optimism-official"})
+ mean: avg(ocb:rpc_latency_milliseconds:mean_24h{provider="optimism-official"})
+ success: sum(ocb:rpc_call:ok_rate_24h{provider="optimism-official"}) / sum(ocb:rpc_call:rate_24h{provider="optimism-official"})
+ sample_size: sum(ocb:rpc_call:increase_24h{provider="optimism-official"})
series: avg(avg_over_time(rpc_latency_milliseconds{provider="optimism-official"}[1h]))
regions:
- region: us-east
- p50: quantile_over_time(0.50, rpc_latency_milliseconds{provider="optimism-official", region="us-east"}[24h])
+ p50: avg(ocb:rpc_latency_milliseconds:p50_24h{provider="optimism-official", region="us-east"})
series: avg_over_time(rpc_latency_milliseconds{provider="optimism-official", region="us-east"}[1h])
- region: eu-west
- p50: quantile_over_time(0.50, rpc_latency_milliseconds{provider="optimism-official", region="eu-west"}[24h])
+ p50: avg(ocb:rpc_latency_milliseconds:p50_24h{provider="optimism-official", region="eu-west"})
series: avg_over_time(rpc_latency_milliseconds{provider="optimism-official", region="eu-west"}[1h])
- region: ap-southeast
- p50: quantile_over_time(0.50, rpc_latency_milliseconds{provider="optimism-official", region="sgp"}[24h])
+ p50: avg(ocb:rpc_latency_milliseconds:p50_24h{provider="optimism-official", region="sgp"})
series: avg_over_time(rpc_latency_milliseconds{provider="optimism-official", region="sgp"}[1h])
- slug: avalanche-official
@@ -465,20 +518,20 @@ providers:
tag: Ava Labs C-Chain public RPC, Avalanche C-Chain only
formula: "50th percentile over 24h of client-side round-trip latency (ms) for a single `eth_blockNumber` POST sent every 15s from 3 regions (us-east + eu-west + sgp) to Ava Labs' `api.avax.network/ext/bc/C/rpc`."
queries:
- p50: avg(quantile_over_time(0.50, rpc_latency_milliseconds{provider="avalanche-official"}[24h]))
- p90: avg(quantile_over_time(0.90, rpc_latency_milliseconds{provider="avalanche-official"}[24h]))
- p99: avg(quantile_over_time(0.99, rpc_latency_milliseconds{provider="avalanche-official"}[24h]))
- mean: avg(avg_over_time(rpc_latency_milliseconds{provider="avalanche-official"}[24h]))
- success: sum(rate(rpc_call_total{provider="avalanche-official", result="ok"}[24h])) / sum(rate(rpc_call_total{provider="avalanche-official"}[24h]))
- sample_size: sum(increase(rpc_call_total{provider="avalanche-official"}[24h]))
+ p50: avg(ocb:rpc_latency_milliseconds:p50_24h{provider="avalanche-official"})
+ p90: avg(ocb:rpc_latency_milliseconds:p90_24h{provider="avalanche-official"})
+ p99: avg(ocb:rpc_latency_milliseconds:p99_24h{provider="avalanche-official"})
+ mean: avg(ocb:rpc_latency_milliseconds:mean_24h{provider="avalanche-official"})
+ success: sum(ocb:rpc_call:ok_rate_24h{provider="avalanche-official"}) / sum(ocb:rpc_call:rate_24h{provider="avalanche-official"})
+ sample_size: sum(ocb:rpc_call:increase_24h{provider="avalanche-official"})
series: avg(avg_over_time(rpc_latency_milliseconds{provider="avalanche-official"}[1h]))
regions:
- region: us-east
- p50: quantile_over_time(0.50, rpc_latency_milliseconds{provider="avalanche-official", region="us-east"}[24h])
+ p50: avg(ocb:rpc_latency_milliseconds:p50_24h{provider="avalanche-official", region="us-east"})
series: avg_over_time(rpc_latency_milliseconds{provider="avalanche-official", region="us-east"}[1h])
- region: eu-west
- p50: quantile_over_time(0.50, rpc_latency_milliseconds{provider="avalanche-official", region="eu-west"}[24h])
+ p50: avg(ocb:rpc_latency_milliseconds:p50_24h{provider="avalanche-official", region="eu-west"})
series: avg_over_time(rpc_latency_milliseconds{provider="avalanche-official", region="eu-west"}[1h])
- region: ap-southeast
- p50: quantile_over_time(0.50, rpc_latency_milliseconds{provider="avalanche-official", region="sgp"}[24h])
+ p50: avg(ocb:rpc_latency_milliseconds:p50_24h{provider="avalanche-official", region="sgp"})
series: avg_over_time(rpc_latency_milliseconds{provider="avalanche-official", region="sgp"}[1h])
diff --git a/benchmarks/stablecoin-peg-usdt-anchored.yml b/benchmarks/stablecoin-peg-usdt-anchored.yml
index ffdcca6f..758de8af 100644
--- a/benchmarks/stablecoin-peg-usdt-anchored.yml
+++ b/benchmarks/stablecoin-peg-usdt-anchored.yml
@@ -59,18 +59,18 @@ abstract: |
methodology:
- "Source: Binance REST `/api/v3/ticker/bookTicker?symbol=` polled every 5 seconds per pair. No API key required. Mid-price = (bid + ask) / 2 in USDT."
- "Pairs: USDCUSDT ($2.8B daily, reference anchor for USDT-vs-USD drift), FDUSDUSDT ($41M daily, FDUSD's primary book), USDEUSDT ($1.8M daily, the book that flashed to $0.65 on October 10 2025)."
- - "Metric: `peg_deviation_usdt_anchored_bps{stable, venue=\"binance\"}` = |mid_price, 1.0000| × 10000. 1 bp = 0.01% = 0.0001 USDT off pair-reference."
+ - "Metric: `peg_deviation_usdt_anchored_bps{stable, venue=\"binance\"}` = |mid_price - 1.0000| × 10000. 1 bp = 0.01% = 0.0001 USDT off pair-reference."
- "Aggregation: p50 / p90 / p99 over 24 hours via Prometheus `quantile_over_time`. p99 is the headline because swap cost during stress is what matters for inventory rotation, not the calm-minute median."
- - "USDC reference anchor: USDC/USDT spread is the floor below which no other pair can credibly score, because that gap reflects USDT's own drift away from USD reaching back into the pair. Read other stables' true vs-USDT drift as `their_p99, usdc_p99` to control for it."
+ - "USDC reference anchor: USDC/USDT spread is the floor below which no other pair can credibly score, because that gap reflects USDT's own drift away from USD reaching back into the pair. Read other stables' true vs-USDT drift as `their_p99 - usdc_p99` to control for it."
- "Depeg event flag (shared with bench № 014): binary `peg_depeg_event_flag{stable}` fires when the per-minute aggregated price is outside [0.97, 1.03] for ≥5 consecutive minutes. The flag clears after 30 minutes back inside the band. Conservative so it does not flap during normal stress."
- "Out of scope: this is a swap-cost / pair-spread bench, NOT a peg-robustness bench. Reserve attestations, basis-trade health, redemption windows, on-chain pool liquidity and other fundamental peg signals live on `stablecoin-peg` (bench № 014) or off-chain in each issuer's transparency dashboard."
findings:
- - "{{best_name}} currently has the tightest pair on Binance at {{best_p50}} (p99 spread vs 1.0000 USDT, 24 h) across {{count}} measured stables. That is the worst 1% of per-minute mid-price distance from the pair reference, in basis points."
- - "{{name:usdc}} sits at {{p50:usdc}} (p99 spread vs USDT, 24 h). USDC is the reference anchor. its spread is mostly USDT's own drift away from USD reaching back, so subtract this number from each other stable's p99 to read its true vs-USDT drift. A stable scoring below USDC is in fact tracking USDT more tightly than USDC is."
- - "{{name:fdusd}} clocks {{p50:fdusd}} (p99 spread vs USDT, 24 h) on Binance FDUSDUSDT. FDUSD's depth lives almost entirely here ($41M daily) so the order-book number is the only live signal that matters for inventory rotation. First Digital's USD reserves are audited monthly but Hong Kong-clearing is slower than Circle's same-day, which surfaces here as wider tails during risk-off minutes."
- - "{{name:usde}} returns {{p50:usde}} (p99 spread vs USDT, 24 h) on Binance USDEUSDT. Ethena's USDe is anchored by a delta-neutral perp basis trade, not USD reserves, which makes its tail behaviour fundamentally different. The October 10 2025 flash to $0.65 on this exact book is the cautionary precedent. a tight calm-minute spread does not imply a robust peg under perp-market stress."
- - "{{worst_name}} trails at {{worst_p50}} (p99 spread vs USDT, 24 h). The gap between leader and laggard mostly reflects book depth and how each stable's redemption / arbitrage mechanism resists Binance order-book stress during high-volatility minutes."
+ - "{{best_name}} currently has the tightest pair on Binance at {{best_p50}} (median per-minute worst spread vs 1.0000 USDT, 24 h) across {{count}} measured stables. That is the worst 1% of per-minute mid-price distance from the pair reference, in basis points."
+ - "{{name:usdc}} sits at {{p99:usdc}} (p99 spread vs USDT, 24 h). USDC is the reference anchor. its spread is mostly USDT's own drift away from USD reaching back, so subtract this number from each other stable's p99 to read its true vs-USDT drift. A stable scoring below USDC is in fact tracking USDT more tightly than USDC is."
+ - "{{name:fdusd}} clocks {{p99:fdusd}} (p99 spread vs USDT, 24 h) on Binance FDUSDUSDT. FDUSD's depth lives almost entirely here ($41M daily) so the order-book number is the only live signal that matters for inventory rotation. First Digital's USD reserves are audited monthly but Hong Kong-clearing is slower than Circle's same-day, which surfaces here as wider tails during risk-off minutes."
+ - "{{name:usde}} returns {{p99:usde}} (p99 spread vs USDT, 24 h) on Binance USDEUSDT. Ethena's USDe is anchored by a delta-neutral perp basis trade, not USD reserves, which makes its tail behaviour fundamentally different. The October 10 2025 flash to $0.65 on this exact book is the cautionary precedent. a tight calm-minute spread does not imply a robust peg under perp-market stress."
+ - "{{worst_name}} trails at {{worst_p50}} (median per-minute worst spread vs USDT, 24 h). The gap between leader and laggard mostly reflects book depth and how each stable's redemption / arbitrage mechanism resists Binance order-book stress during high-volatility minutes."
faq:
- q: "What does this benchmark actually measure?"
@@ -78,7 +78,7 @@ faq:
- q: "Is this the same as 'which stablecoin is safest to hold'?"
a: "No, and it would be misleading to read it that way. USDC is cash-backed and audited monthly. FDUSD is also cash-backed and audited monthly but Hong Kong-clearing. USDe is not USD-backed at all but anchored by a delta-neutral perp basis trade. These carry fundamentally different reserve risks that no order-book spread can capture. A tight calm-minute pair spread on Binance does not imply a robust peg under stress. for fundamental peg signals (reserve attestations, on-chain pool depth, basis-trade health) read the issuer's transparency dashboard, not this bench."
- q: "What is USDC doing on a USDT-anchored bench?"
- a: "USDC serves as a reference anchor. {{name:usdc}} currently shows {{p50:usdc}} (p99 spread vs USDT, 24 h) on Binance USDCUSDT, the most liquid stablecoin pair on the venue ($2.8B daily). USDC is the cleanest cash-backed stablecoin in the cohort, so any drift it shows here is mostly USDT's own drift away from USD reaching back into the pair. Subtract USDC's p99 from any other stable's p99 to get its true vs-USDT drift, controlling for USDT's own USD drift."
+ a: "USDC serves as a reference anchor. {{name:usdc}} currently shows {{p99:usdc}} (p99 spread vs USDT, 24 h) on Binance USDCUSDT, the most liquid stablecoin pair on the venue ($2.8B daily). USDC is the cleanest cash-backed stablecoin in the cohort, so any drift it shows here is mostly USDT's own drift away from USD reaching back into the pair. Subtract USDC's p99 from any other stable's p99 to get its true vs-USDT drift, controlling for USDT's own USD drift."
- q: "Why FDUSD and USDe specifically?"
a: "Because they have no other honest live signal. FDUSD's depth lives almost entirely on Binance FDUSDUSDT ($41M daily, no comparable USD-quoted venue). USDe's main spot book is Binance USDEUSDT ($1.8M daily, same situation). The USD-anchored bench `stablecoin-peg` (№ 014) covers USDC / USDT / DAI which have real USD-quoted venues (Kraken, Bitstamp, Curve). This companion bench is the only place where FDUSD and USDe can be measured on a live order book at all."
- q: "Did USDe really flash to $0.65 on Binance?"
diff --git a/benchmarks/stablecoin-peg.yml b/benchmarks/stablecoin-peg.yml
index 89dd6e18..9b1a0cf1 100644
--- a/benchmarks/stablecoin-peg.yml
+++ b/benchmarks/stablecoin-peg.yml
@@ -38,7 +38,7 @@ abstract: |
We measure live stablecoin peg deviation by pulling mid-price
ticks from every venue that exposes a USD-quoted pair for a given
stable, then aggregating per-minute into a liquidity-weighted
- median. Deviation is `|aggregated_price, 1.00|` in basis points,
+ median. Deviation is `|aggregated_price - 1.00|` in basis points,
recorded as both a gauge (latest) and a histogram (per-minute
samples). p99 over 24 h via `quantile_over_time` is the headline
leaderboard number. USDT-quoted pairs (Binance USDC/USDT, FDUSD,
@@ -58,33 +58,33 @@ methodology:
- "Sources per stable, USDC: Binance USDC/USDT (USDT-anchored, secondary), Kraken USDCUSD, Bitstamp usdcusd. USDT: Kraken USDTUSD, Bitstamp usdtusd. FDUSD: Binance FDUSDUSDT (USDT-anchored, secondary). USDe: Binance USDEUSDT (USDT-anchored, secondary). DAI: Curve 3pool `get_dy` forward (USDC → DAI) and reverse (DAI → USDC)."
- "Cadence: 5 seconds for CEX REST tickers, 12 seconds for Curve `get_dy` on-chain (matches Ethereum block time). Per-venue samples are grouped into 60-second buckets for the liquidity-weighted median aggregation."
- "Aggregation: per-minute liquidity-weighted median across USD-quoted venues (`Quote = QuoteUSD`). Weight is 24 h USD volume estimate. The aggregated price drives `peg_deviation_bps` (per-minute median, retained for backward compatibility) and `peg_deviation_bps_histogram`."
- - "Primary headline: `peg_deviation_worst_bps` — the per-minute MAX |price − $1.00| across every venue sample in the 60-second bucket, in basis points. The leaderboard ranks stables on `quantile_over_time(0.99, peg_deviation_worst_bps[24h])`. Switching from per-minute median to per-minute max surfaces the sub-minute depeg wicks (e.g. a 5-second print to $0.92 on one venue) that the median would smooth into invisibility — matching the OHLC convention used by every TradFi reference rate. The companion gauges `peg_minute_min_bps`, `peg_minute_open_bps` and `peg_minute_close_bps` expose the full bar shape so consumers can reconstruct the within-minute distribution."
- - "Cross-venue gap (OCB-only number): `max(price across USD-quoted venues), min(price)`, per minute, in basis points. Surfaces the windows where Coinbase quotes $1.0003 while Kraken quotes $0.9978. Stored as `peg_cross_venue_gap_bps`."
+ - "Primary headline: `peg_deviation_worst_bps`, the per-minute MAX |price - $1.00| across all venue samples in the 60-second bucket, in basis points. The leaderboard ranks stables on `quantile_over_time(0.99, peg_deviation_worst_bps[24h])`. Per-minute max surfaces sub-minute depeg wicks that a median would smooth away, matching the OHLC convention of TradFi reference rates. Gauges `peg_minute_min_bps`, `peg_minute_open_bps`, `peg_minute_close_bps` expose the bar shape."
+ - "Cross-venue gap (OCB-only number): `max(price across USD-quoted venues) - min(price)`, per minute, in basis points. Surfaces the windows where Coinbase quotes $1.0003 while Kraken quotes $0.9978. Stored as `peg_cross_venue_gap_bps`."
- "USDT-anchored secondary metric: Binance USDC/USDT, FDUSD/USDT and USDE/USDT are exposed on `peg_deviation_usdt_anchored_bps{venue}` separately so the USD-anchored primary leaderboard is not contaminated by USDT's own peg deviation."
- "Time outside band: total seconds in the trailing 24 h during which the per-minute aggregated price fell outside [0.995, 1.005] (±50 bps). Split into `peg_time_below_peg_24h_seconds` (< 0.995) and `peg_time_above_peg_24h_seconds` (> 1.005) because Circle redemption only clears above-peg, so the direction tells you which failure mode is active."
- "Depeg event flag: binary `peg_depeg_event_flag` set to 1 when the per-minute aggregated price has been outside [0.97, 1.03] for ≥5 consecutive minutes; cleared after 30 minutes back inside. Conservative so it does not flap during normal stress events."
- - "Outlier rule (multi-venue consensus): a single sample more than 2% off peg is kept only when at least one OTHER venue has also been outside the same band in the same direction within the last 30 seconds. Single-venue glitches (one CEX returns a stale or fat-finger print while every other venue is at $1.00) are dropped as `dropped_isolated`. Multi-venue confirmation (Kraken AND Bitstamp both at $0.87 = real depeg) is kept as `kept_corroborated` so the percentile metric surfaces the event. Replaces the previous flat 20% drop / 10% cap which would have clipped USDC at $0.87 during the March 2023 SVB depeg to $0.90, erasing the event in the percentile metric. Sanity floor: anything more than 50% off peg is treated as a parser bug and dropped regardless. Methodology recommendation from Coinpaprika data team review."
+ - "Outlier rule (multi-venue consensus): a sample more than 2% off peg is kept only when at least one other venue was outside the same band in the same direction within the last 30 seconds. Isolated single-venue glitches are dropped as `dropped_isolated`; corroborated moves (Kraken AND Bitstamp both at $0.87) are kept as `kept_corroborated`, so a real depeg like USDC during SVB in March 2023 survives into the percentile metric. Anything more than 50% off peg is treated as a parser bug and dropped."
- "Excluded by design: aggregator-only prices (CoinGecko, Coinmarketcap, DefiLlama) because they are themselves liquidity-weighted medians of the venues we already poll directly. Algo-stables that have already failed (UST, USDR) are out of scope; the bench tracks live, currently-redeemable stables."
findings:
- - "{{best_name}} currently leads at {{best_p50}} (p99 deviation, 24 h) across {{count}} measured stablecoins. The number is the worst 1% of the per-minute aggregated price's distance from $1.00, in basis points, on USD-quoted venues only."
- - "{{name:usdc}} returns {{p50:usdc}} (p99 deviation, 24 h). USDC's peg is anchored by Circle's primary-market redemption window with US banks, so USD-quoted CEX deviations are usually mean-reverted within minutes by arbitrageurs hitting Coinbase or Kraken."
- - "{{name:usdt}} sits at {{p50:usdt}} (p99 deviation, 24 h). USDT carries the secondary-market premium of being the dominant pair currency on Binance and most non-US CEXes, so its CEX deviation is structurally biased above $1 during risk-on minutes and below during risk-off."
- - "{{name:dai}} clocks {{p50:dai}} (p99 deviation, 24 h) measured on Curve 3pool. DAI's CEX coverage is essentially dead in 2026 so the on-chain pool quote is the only honest signal; the forward vs reverse swap rates surface arbitrage gaps that an averaged CEX tracker would silently smooth out."
+ - "{{best_name}} currently leads at {{best_p50}} (median per-minute worst deviation, 24 h) across {{count}} measured stablecoins. The number is the worst 1% of the per-minute aggregated price's distance from $1.00, in basis points, on USD-quoted venues only."
+ - "{{name:usdc}} returns {{p99:usdc}} (p99 deviation, 24 h). USDC's peg is anchored by Circle's primary-market redemption window with US banks, so USD-quoted CEX deviations are usually mean-reverted within minutes by arbitrageurs hitting Coinbase or Kraken."
+ - "{{name:usdt}} sits at {{p99:usdt}} (p99 deviation, 24 h). USDT carries the secondary-market premium of being the dominant pair currency on Binance and most non-US CEXes, so its CEX deviation is structurally biased above $1 during risk-on minutes and below during risk-off."
+ - "{{name:dai}} clocks {{p99:dai}} (p99 deviation, 24 h) measured on Curve 3pool. DAI's CEX coverage is essentially dead in 2026 so the on-chain pool quote is the only honest signal; the forward vs reverse swap rates surface arbitrage gaps that an averaged CEX tracker would silently smooth out."
- "USDe and FDUSD are USDT-anchored on Binance (no deep USD-quoted venue exists for either) and live on the companion bench `stablecoin-peg-usdt-anchored` (bench № 015), which is the only honest comparison surface for them. Mixing them into a USD-anchored leaderboard would compare apples to oranges since their deviation includes USDT's own drift."
- - "{{worst_name}} trails at {{worst_p50}} (p99 deviation, 24 h) on the active filter. The gap between leader and laggard widens on volatile minutes, the cross-venue gap column ({{p50:usdc}} etc.) surfaces how synchronized the venues are at the exact moment of stress."
+ - "{{worst_name}} trails at {{worst_p50}} (median per-minute worst deviation, 24 h) on the active filter. The gap between leader and laggard widens on volatile minutes, the cross-venue gap column surfaces how synchronized the venues are at the exact moment of stress."
faq:
- q: "Which stablecoin is the most stable right now?"
- a: "{{best_name}} currently leads the leaderboard at {{best_p50}} (p99 deviation, 24 h) across {{count}} measured stablecoins on USD-quoted venues. The leaderboard re-sorts every minute against fresh Prometheus samples, so the answer reflects the last 24 hours of measured per-minute aggregated price, not a static white-paper figure."
+ a: "{{best_name}} currently leads the leaderboard at {{best_p50}} (median per-minute worst deviation, 24 h) across {{count}} measured stablecoins on USD-quoted venues. The leaderboard re-sorts every minute against fresh Prometheus samples, so the answer reflects the last 24 hours of measured per-minute aggregated price, not a static white-paper figure."
- q: "What is stablecoin peg deviation in basis points?"
a: "Peg deviation is the absolute distance between a stablecoin's price and $1.00, expressed in basis points where 1 bp = 0.0001 = 0.01%. A stablecoin trading at $0.9978 has 22 bps of deviation; at $1.0050 it has 50 bps. The industry uses bps because the deviations are small. tens of bps is normal noise, hundreds of bps is stress, thousands of bps is a depeg event. The benchmark records both per-minute deviation and the worst-1% tail (p99) over 24 h, the integration-grade number for any product that holds inventory in the stable."
- q: "How does USDC compare to USDT on stability?"
- a: "{{name:usdc}} returns {{p50:usdc}} (p99 deviation, 24 h) on USD-quoted venues (Kraken, Bitstamp). {{name:usdt}} returns {{p50:usdt}} on USD-quoted venues (Kraken, Bitstamp). USDC's peg is anchored by Circle's same-day USD redemption with US banks, USDT's by Tether's larger but slower-clearing redemption window. The leaderboard surfaces the live gap and the cross-venue spread per minute so the comparison is grounded in measured data rather than a sentiment-driven preference."
+ a: "{{name:usdc}} returns {{p99:usdc}} (p99 deviation, 24 h) on USD-quoted venues (Kraken, Bitstamp). {{name:usdt}} returns {{p50:usdt}} on USD-quoted venues (Kraken, Bitstamp). USDC's peg is anchored by Circle's same-day USD redemption with US banks, USDT's by Tether's larger but slower-clearing redemption window. The leaderboard surfaces the live gap and the cross-venue spread per minute so the comparison is grounded in measured data rather than a sentiment-driven preference."
- q: "Did USDe really depeg to $0.65 in October 2025?"
a: "Yes, on Binance USDEUSDT specifically, the night of October 10 2025. Ethena's USDe is anchored by a delta-neutral basis trade rather than fiat reserves, and a sharp liquidation cascade on perp positions briefly cleared the orderbook on Binance well below peg. The benchmark exposes this kind of event live via the `peg_depeg_event_flag` (set when a stable has been outside [$0.97, $1.03] for ≥5 consecutive minutes) and via the USDT-anchored secondary metric. USDe's USD-anchored primary deviation cannot be measured because no major USD venue lists a deep USDe/USD pair; the USDT-anchored value is the honest live signal."
- q: "How is the stablecoin peg measured on OpenChainBench?"
- a: "We poll Binance, Kraken and Bitstamp REST tickers every 5 seconds for every (stable, venue) pair, and the Curve 3pool `get_dy` on-chain every 12 seconds (matches Ethereum block time). Per-venue samples are grouped into 60-second buckets and aggregated into a liquidity-weighted median per stable (weight = 24h USD volume). Deviation is `|aggregated_price, 1.00|` in basis points, recorded as both a gauge and a histogram. p99 over 24 h via Prometheus `quantile_over_time` is the headline. USDT-quoted pairs are exposed on a separate secondary metric so USDT's own peg deviation does not contaminate the USD-anchored primary."
+ a: "We poll Binance, Kraken and Bitstamp REST tickers every 5 seconds for every (stable, venue) pair, and the Curve 3pool `get_dy` on-chain every 12 seconds (matches Ethereum block time). Per-venue samples are grouped into 60-second buckets and aggregated into a liquidity-weighted median per stable (weight = 24h USD volume). Deviation is `|aggregated_price - 1.00|` in basis points, recorded as both a gauge and a histogram. p99 over 24 h via Prometheus `quantile_over_time` is the headline. USDT-quoted pairs are exposed on a separate secondary metric so USDT's own peg deviation does not contaminate the USD-anchored primary."
- q: "Why ignore CoinGecko or CoinMarketCap prices?"
a: "Aggregator prices (CoinGecko, CoinMarketCap, DefiLlama) are themselves liquidity-weighted medians of the venues this benchmark already polls directly, refreshed every 1-5 minutes. Re-aggregating an aggregator adds latency, hides the venue-level cross-spread that actually matters during stress (USDC briefly $0.9978 on Kraken while $1.0003 on Coinbase), and inherits whatever venue selection the aggregator chose. The benchmark goes to the source venues directly so the cross-venue gap is visible and the depeg event flag fires within 5 minutes of the price actually breaking the band."
@@ -123,7 +123,7 @@ providers:
- slug: usdc
name: USDC
tag: Circle, USD-redeemed primary, audited on Kraken + Bitstamp
- formula: "Median |aggregated_price − $1.00| in bps, where aggregated_price is the per-minute liquidity-weighted median of USDCUSD ticks on Kraken and Bitstamp."
+ formula: "24h median of the per-minute worst |aggregated_price − $1.00| in bps; aggregated_price is the per-minute liquidity-weighted median of USDCUSD ticks on Kraken and Bitstamp."
queries:
p50: quantile_over_time(0.50, peg_deviation_worst_bps{stable="usdc"}[24h])
p90: quantile_over_time(0.90, peg_deviation_worst_bps{stable="usdc"}[24h])
@@ -136,7 +136,7 @@ providers:
- slug: usdt
name: USDT
tag: Tether, dominant CEX pair currency, audited on Kraken + Bitstamp
- formula: "Median |aggregated_price − $1.00| in bps, where aggregated_price is the per-minute liquidity-weighted median of USDTUSD ticks on Kraken and Bitstamp."
+ formula: "24h median of the per-minute worst |aggregated_price − $1.00| in bps; aggregated_price is the per-minute liquidity-weighted median of USDTUSD ticks on Kraken and Bitstamp."
queries:
p50: quantile_over_time(0.50, peg_deviation_worst_bps{stable="usdt"}[24h])
p90: quantile_over_time(0.90, peg_deviation_worst_bps{stable="usdt"}[24h])
@@ -149,7 +149,7 @@ providers:
- slug: dai
name: DAI
tag: MakerDAO, audited via Curve 3pool get_dy (on-chain only)
- formula: "Median |dai_price − $1.00| in bps, derived from Curve 3pool get_dy forward (USDC → DAI) and reverse (DAI → USDC) every 12s over 24h."
+ formula: "24h median of the per-minute worst |dai_price − $1.00| in bps, derived from Curve 3pool get_dy forward (USDC → DAI) and reverse (DAI → USDC) every 12s over 24h."
queries:
p50: quantile_over_time(0.50, peg_deviation_worst_bps{stable="dai"}[24h])
p90: quantile_over_time(0.90, peg_deviation_worst_bps{stable="dai"}[24h])
diff --git a/benchmarks/token-deployment-cost.yml b/benchmarks/token-deployment-cost.yml
index d5b50042..167c1e5b 100644
--- a/benchmarks/token-deployment-cost.yml
+++ b/benchmarks/token-deployment-cost.yml
@@ -26,7 +26,7 @@ faq:
- q: "Why is Cardano always similar?"
a: "The Cardano min UTxO formula is deterministic. The coins_per_utxo_size protocol parameter is set by governance and moves rarely. A standard native asset bundle (32 byte policy hash plus short asset name) sits around 70 bytes, and the on chain mint transaction fee is a few cents of ADA. The USD figure on the leaderboard moves with ADA price, not with congestion."
- q: "How does the L2 number include the L1 data fee?"
- a: "Two paths depending on the rollup. Arbitrum Nitro: the chain bakes the L1 component into the returned gas via NodeInterface, so the plain eth_estimateGas figure already covers most of it (caveat: under L1 congestion the public RPC undercounts the L1 component by 10 to 50 percent and would need the explicit gasEstimateL1Component call). OP Stack rollups (Optimism, Base, Blast, Mantle, opBNB, Celo): eth_estimateGas returns L2 execution only, and we add the L1 data fee via OVM_GasPriceOracle.getL1Fee at 0x420000000000000000000000000000000000000F on the deploy calldata. Scroll, Linea and zkSync embed the data cost directly in their gas accounting."
+ a: "Two paths depending on the rollup. Arbitrum Nitro: the chain bakes the L1 component into the returned gas via NodeInterface, so the plain eth_estimateGas figure already covers most of it (caveat: under L1 congestion the public RPC undercounts the L1 component by 10 to 50 percent and would need the explicit gasEstimateL1Component call). OP Stack rollups (Optimism, Base, Blast, Mantle, opBNB, Celo): eth_estimateGas returns L2 execution only, and we add the L1 data fee via OVM_GasPriceOracle.getL1Fee at 0x420000000000000000000000000000000000000F on the deploy calldata. Scroll and Linea embed the data cost directly in their gas accounting."
- q: "Where can I see the contract source?"
a: "The canonical OpenZeppelin v5.0.2 ERC20 source and its compiled artifact (solc 0.8.24, optimizer runs 200) live in the harness directory under contracts/Token.sol and contracts/Token.json. The same bytecode is shipped to every EVM chain, so the cross chain gas comparison is on identical contract code. Reproduce locally with forge build and verify against the embedded constant in cmd/script/evm.go."
- q: "Can I cite a value from this page?"
@@ -337,7 +337,7 @@ providers:
name: Stellar
layer: l1
tag: Issuer + distribution + trustline, 2-account flow
- formula: "3 × base_reserve_in_stroops + 2 × base_fee_in_stroops × XLM USD price."
+ formula: "(3 × base_reserve_in_stroops + 2 × base_fee_in_stroops) × XLM USD price."
queries:
p50: quantile_over_time(0.50, token_deployment_cost_usd{chain="stellar"}[24h])
p90: quantile_over_time(0.90, token_deployment_cost_usd{chain="stellar"}[24h])
diff --git a/benchmarks/validator-yield.yml b/benchmarks/validator-yield.yml
index cb79fd16..192b077e 100644
--- a/benchmarks/validator-yield.yml
+++ b/benchmarks/validator-yield.yml
@@ -51,8 +51,8 @@ abstract: |
cardinality bounded (the full set is ~3000); Hyperliquid
exposes all ~30 active. The per-chain leaderboard number is
`ocb_chain_median_net_yield_bps{chain}`, the median across the
- per-validator gauges. p90 / p99 over a 24h window via
- `quantile_over_time` surface the right-tail validators (best
+ per-validator gauges. p90 / p99 via an instant
+ `quantile()` across the tracked validators surface the right tail (best
net yield among those tracked). Caveats. (a) Solana's median is
dragged below the modal value by ~42 validators currently
emitting 0 APY, commission set to 100% or no leader slots this
diff --git a/next.config.ts b/next.config.ts
index b5c0fb5b..87b62af2 100644
--- a/next.config.ts
+++ b/next.config.ts
@@ -50,6 +50,14 @@ const nextConfig: NextConfig = {
turbopack: {
root: __dirname,
},
+ // Build-time page budget. The Prom client serializes queries through a
+ // global concurrency cap (src/lib/prometheus.ts); the FIRST page each
+ // build worker prerenders pays the whole multi-bench Prom load and can
+ // exceed the default 60 s budget (observed 2026-06-10: staging build
+ // failing on /products/ + OG-image routes after the cap landed).
+ // Later pages reuse the worker cache and render in milliseconds, so
+ // only that first-page budget needs headroom.
+ staticPageGenerationTimeout: 240,
// Tree-shake lucide-react down to just the icons we actually import.
// Without this hint Next's App Router can include the full barrel
// (~1k icons, ~25 KB gzipped) on routes that touch lucide indirectly.
diff --git a/scripts/audit-bench-data.ts b/scripts/audit-bench-data.ts
new file mode 100644
index 00000000..cf1707f1
--- /dev/null
+++ b/scripts/audit-bench-data.ts
@@ -0,0 +1,183 @@
+/**
+ * One-shot data audit: executes every scalar query of every bench spec
+ * against the live Prometheus gateway and reports anomalies (empty
+ * results, zero/identical values, implausible success rates, dimension
+ * values absent from the metric labels). Read-only; safe to run anytime.
+ *
+ * bun scripts/audit-bench-data.ts [--prom https://...]
+ */
+import { promises as fs } from "node:fs";
+import path from "node:path";
+import yaml from "js-yaml";
+
+const PROM =
+ process.argv.includes("--prom")
+ ? process.argv[process.argv.indexOf("--prom") + 1]
+ : "https://prom-gateway-production.up.railway.app";
+
+type Spec = {
+ slug: string;
+ status?: string;
+ unit?: string;
+ prometheus?: { url?: string };
+ dimensions?: Record;
+ rank_matrix_query?: string;
+ providers: {
+ slug: string;
+ queries?: Record;
+ }[];
+};
+
+let active = 0;
+const waiters: (() => void)[] = [];
+async function slot() {
+ if (active < 8) {
+ active++;
+ return;
+ }
+ await new Promise((r) => waiters.push(r));
+}
+function release() {
+ const n = waiters.shift();
+ if (n) n();
+ else active--;
+}
+
+async function q(promql: string, base = PROM): Promise {
+ await slot();
+ try {
+ const url = new URL("/api/v1/query", base);
+ url.searchParams.set("query", promql);
+ const res = await fetch(url, { signal: AbortSignal.timeout(15_000) });
+ const j = (await res.json()) as {
+ status: string;
+ data?: { resultType: string; result: unknown[] };
+ };
+ if (j.status !== "success") return "ERROR";
+ const r = j.data;
+ if (!r) return "ERROR";
+ if (r.resultType === "scalar") {
+ const v = Number((r.result as [number, string])[1]);
+ return Number.isFinite(v) ? v : null;
+ }
+ if (r.resultType === "vector" && r.result.length > 0) {
+ const v = Number(
+ (r.result[0] as { value: [number, string] }).value[1],
+ );
+ return Number.isFinite(v) ? v : null;
+ }
+ return null;
+ } catch {
+ return "ERROR";
+ } finally {
+ release();
+ }
+}
+
+async function labelValues(
+ metric: string,
+ label: string,
+ base = PROM,
+): Promise> {
+ await slot();
+ try {
+ const url = new URL("/api/v1/query", base);
+ url.searchParams.set("query", `count by (${label}) (${metric})`);
+ const res = await fetch(url, { signal: AbortSignal.timeout(15_000) });
+ const j = (await res.json()) as {
+ data?: { result: { metric: Record }[] };
+ };
+ return new Set(
+ (j.data?.result ?? []).map((s) => s.metric[label]).filter(Boolean),
+ );
+ } catch {
+ return new Set();
+ } finally {
+ release();
+ }
+}
+
+function extractMetric(promql: string): string | null {
+ const m = promql.match(/([a-zA-Z_:][a-zA-Z0-9_:]*)\s*\{/);
+ return m ? m[1] : null;
+}
+
+const dir = path.join(process.cwd(), "benchmarks");
+const files = (await fs.readdir(dir)).filter((f) => f.endsWith(".yml"));
+const report: string[] = [];
+
+for (const file of files.sort()) {
+ const spec = yaml.load(await fs.readFile(path.join(dir, file), "utf8")) as Spec;
+ if (!spec?.providers) continue;
+ // Respect per-spec Prometheus (federation: some benches declare their own).
+ const base = spec.prometheus?.url ?? PROM;
+ const issues: string[] = [];
+ const p50s = new Map();
+
+ await Promise.all(
+ spec.providers.map(async (p) => {
+ const qs = (p.queries ?? {}) as Record;
+ const checks: [string, string][] = [];
+ for (const k of ["p50", "p90", "p99", "success", "sample_size"]) {
+ if (typeof qs[k] === "string") checks.push([k, qs[k]]);
+ }
+ const vals = await Promise.all(checks.map(([, query]) => q(query, base)));
+ checks.forEach(([k], i) => {
+ const v = vals[i];
+ if (v === "ERROR") issues.push(`${p.slug}.${k}: QUERY ERROR`);
+ else if (v === null) issues.push(`${p.slug}.${k}: EMPTY`);
+ else {
+ if (k === "p50") {
+ if (v <= 0) issues.push(`${p.slug}.p50: ${v} (<=0)`);
+ p50s.set(p.slug, v);
+ }
+ if (k === "success") {
+ const pct = v > 1 ? v : v * 100;
+ if (pct <= 0) issues.push(`${p.slug}.success: 0%`);
+ if (pct > 100.5) issues.push(`${p.slug}.success: ${pct.toFixed(1)}% (>100)`);
+ }
+ if (k === "sample_size" && v === 0) issues.push(`${p.slug}.sample_size: 0`);
+ }
+ });
+ }),
+ );
+
+ // Identical p50 across 3+ providers = suspicious copy-paste / constant.
+ const byVal = new Map();
+ for (const [s, v] of p50s) {
+ const key = Math.round(v * 1000) / 1000;
+ byVal.set(key, [...(byVal.get(key) ?? []), s]);
+ }
+ for (const [v, slugs] of byVal) {
+ if (slugs.length >= 3) issues.push(`identical p50=${v} across: ${slugs.join(",")}`);
+ }
+
+ // Dimension values present in the actual metric labels?
+ const firstQuery = (spec.providers[0]?.queries as Record)?.p50;
+ const metric = firstQuery ? extractMetric(firstQuery) : null;
+ if (metric && spec.dimensions) {
+ for (const dim of ["chain", "region"] as const) {
+ const declared = (spec.dimensions[dim] ?? [])
+ .map((d) => d.value)
+ .filter((v) => v !== "all");
+ if (declared.length === 0) continue;
+ const present = await labelValues(metric, dim, base);
+ if (present.size === 0) continue; // metric may not carry the label at this granularity
+ const missing = declared.filter((v) => !present.has(v));
+ if (missing.length > 0)
+ issues.push(`dimension ${dim}: declared but absent from ${metric}: ${missing.join(",")}`);
+ }
+ }
+
+ if (spec.rank_matrix_query) {
+ const v = await q(`count(${spec.rank_matrix_query})`, base);
+ if (v === null || v === "ERROR" || v === 0)
+ issues.push(`rank_matrix_query: ${v === 0 ? "0 series" : String(v)}`);
+ }
+
+ const head = `${spec.slug}${spec.status === "draft" ? " [draft]" : ""}`;
+ if (issues.length === 0) report.push(`OK ${head} (${p50s.size}/${spec.providers.length} providers live)`);
+ else report.push(`WARN ${head}\n ${issues.join("\n ")}`);
+}
+
+console.log(report.join("\n"));
diff --git a/src/app/about/page.tsx b/src/app/about/page.tsx
index 07bda322..202c7bd5 100644
--- a/src/app/about/page.tsx
+++ b/src/app/about/page.tsx
@@ -52,7 +52,29 @@ export default function AboutPage() {
13 live benchmarks. ~150 (provider × chain) probe pairs. Every metric is queryable on the public Prometheus and reproducible from the harness source.
-
+
+
+ OpenChainBench is funded by Mobula because we needed honest infrastructure benchmark data internally and saw no neutral source existed. The site is open-sourced because the industry needs it.
+
+
+ Mobula competes in five live benchmarks: aggregator head lag, bridge fee, bridge quote latency, network coverage and metadata coverage. Mobula currently leads aggregator head lag and bridge quote latency. This is a real conflict of interest and we want it visible above the fold, not buried.
+
+
+ Three safeguards stand against the conflict:
+
+
+
·Open Prometheus data. Every number on the site is a literal quantile_over_time query. Anyone can hit our /api/citable or /api/stat/<slug> endpoint and re-derive the leaderboard with their own aggregation. If Mobula were inflating its rank, the raw data would show it.
+
·Open harness source. Every harness is on GitHub under harnesses/. Clone, run docker compose up, your /metrics endpoint emits the same numbers ours does within 30 seconds.
+
·Public methodology review. We invite external review and ship the fixes publicly. In June 2026 the Coinpaprika data team flagged four issues on the stablecoin peg and oracle deviation benches. We shipped three of them in pull requests #349, #352 and #353 within twenty-four hours and pushed back on the fourth with citations to CME, Chainlink and CoinGecko convention.
+
+
+ We do not run a paid tier. We do not sell ranking slots. We do not take provider sponsorship in exchange for inclusion. There is no token. If you spot any deviation from this policy, file a private security advisory and we will treat it as the integrity incident it would be.
+
+
+ Hosting and infrastructure costs are paid by Mobula. The site runs on Vercel; harnesses run on Railway. We are open to grant funding that preserves editorial independence, but we will not accept funding from any party we benchmark.
+
+
+
Every benchmark is a YAML spec plus a harness. The spec describes what to measure, which providers, which Prometheus queries hold the numbers; the harness runs continuously on Railway and exposes those metrics. A single shared Prometheus scrapes every harness; the site queries Prometheus directly and re-renders every minute. Every provider is rendered with equal visual weight. readers do their own ranking.
@@ -62,7 +84,7 @@ export default function AboutPage() {
walks through the steps. New providers, new metrics, new chains. all welcome via pull request.
-
+
·Read the{" "}live benchmarks{" "}.
·Reproduce any number. the{" "}methodology{" "}page tells you how.
@@ -70,7 +92,7 @@ export default function AboutPage() {