diff --git a/answers/alchemy-vs-quicknode-vs-infura-latency.yml b/answers/alchemy-vs-quicknode-vs-infura-latency.yml new file mode 100644 index 00000000..4833fba0 --- /dev/null +++ b/answers/alchemy-vs-quicknode-vs-infura-latency.yml @@ -0,0 +1,43 @@ +slug: alchemy-vs-quicknode-vs-infura-latency +question: "Alchemy vs QuickNode vs Infura, which has the lowest RPC latency in 2026?" +short_answer: | + {{best_name}} currently leads the keyed free-tier RPC leaderboard at {{best_p50}} p50 latency (24h avg), measured live from three regions by OpenChainBench against the keyed free tiers of Alchemy, QuickNode, Infura, Chainstack, Ankr and Helius on the same eth_getBlockByNumber probe. + +benchmark: rpc-keyed-latency + +intro: | + Every dApp that outgrows the no-key public RPCs asks the same question next: which paid free-tier provider actually delivers the fastest response, from which region, and at what success rate. Vendor blog posts recite selective numbers with no independent probe. Chainstack quotes its own p50, Alchemy quotes its own p99, and none disclose regional variance or measurement methodology in enough detail to reproduce. This page answers the head-to-head that developers actually search for: which of Alchemy, QuickNode and Infura wins today, measured live, from three regions, using the same call. OpenChainBench probes eth_getBlockByNumber every 60 seconds against each provider's signup-gated free tier from us-east, eu-west and singapore, records the round-trip latency only for successful responses, and publishes the full leaderboard with QuickNode, Alchemy, Helius, Chainstack, Ankr and Infura ranked side by side. + +methodology: | + Every 60 seconds the harness fires the identical JSON-RPC POST `{"jsonrpc":"2.0","method":"eth_getBlockByNumber","params":["latest",false]}` against each provider's free-tier endpoint from three Railway replicas (us-east Virginia, eu-west Amsterdam, sgp Singapore). Latency is a client-side time.Now() delta around the round-trip, published to Prometheus as both a gauge and a histogram with buckets 50ms to 10s. p50/p90/p99 are computed via `quantile_over_time` over the last 24 hours per provider per region and averaged across regions for the headline. Only successful calls (HTTP 200 + non-empty result) enter the latency distribution, so a mostly failing provider never posts a spuriously fast p50 out of its rare successes. The Success column pairs every latency figure with the ok-rate over the same window, and providers below 5% success are pinned as unresponsive. + +limitations: + - "Free-tier probe only. Paid tiers have different rate limits, priority queues and archive access; the numbers here do not translate to paid usage. The keyed free tier is a lower bound on paid tier performance because paid tiers get more capacity, but variance can go either way at p99." + - "Latency is conditional on success. A provider that fails 40% of calls but returns the successes fast will show a fast p50 with a low Success column. Read both columns together before ranking." + - "Regional coverage varies by provider. Some vendors terminate close to their control plane and lose 40 to 80 ms when probed from a distant region. The per-region breakdown at the top of the bench page shows the geography-dependent leader." + - "No archive workload here. This bench measures head-of-chain read latency; archive queries (eth_getBalance at (head, 5M)) are on the sibling bench openchainbench.com/benchmarks/rpc-capabilities." + +faq: + - q: "Which free-tier RPC provider is fastest right now, Alchemy, QuickNode or Infura?" + a: "{{best_name}} leads at {{best_p50}} p50 (24h avg) on the free-tier keyed leaderboard measured across three regions. The full ranking including QuickNode, Alchemy, Helius, Chainstack, Ankr and Infura re-sorts every 60 seconds against fresh Prometheus samples." + - q: "Why does Infura sit lower on this leaderboard than Alchemy or QuickNode?" + a: "Infura's free-tier throughput has degraded in 2026 due to daily credit brownouts that turn into 429 responses. Because latency is only recorded on successful calls, Infura's p50 looks acceptable in isolation but the Success column reveals sub-95% ok-rate on the free tier. QuickNode and Alchemy pair fast latency with 99.9%+ success, which is why they sit above Infura in a fair ranking." + - q: "Are these numbers different from what Alchemy or QuickNode publish on their own marketing pages?" + a: "Yes. Every vendor cherry-picks the region and percentile that flatters its own footprint. This benchmark uses one identical call, three fixed regions, one 60s cadence, and publishes every provider under the same rules. That is why it produces cross-vendor rankings a vendor marketing page cannot." + - q: "Does this cover Chainstack, Ankr and Helius too?" + a: "Yes. The full leaderboard measures QuickNode, Alchemy, Helius (Solana keyed), Chainstack, Ankr and Infura under identical conditions. The head-to-head focus of this page is Alchemy vs QuickNode vs Infura because that is the highest-search-volume triangle, but the bench page ranks all six." + - q: "How is Helius even in this ranking, it is a Solana provider?" + a: "Helius offers keyed EVM-compatible endpoints for a subset of chains alongside its Solana product. The leaderboard measures Helius on the chains where it exposes a compatible eth_getBlockByNumber-style API. Cross-VM comparability is preserved because every provider is probed with the same call, and the per-chain tab surfaces where each provider actually competes." + - q: "Which region wins per provider?" + a: "QuickNode's mesh keeps its US-East p50 tight; Alchemy leads in EU-West on the majors; Chainstack has the best Singapore numbers on BNB and Polygon. The per-region breakdown at the top of the bench page shows the geography-dependent leader instead of hiding it in the cross-region average." + - q: "How often is the leaderboard refreshed?" + a: "Every 60 seconds the harness probes each provider from every region, publishes rpc_latency_milliseconds to Prometheus, and the page reads a 24h rolling average. Because the Prom recording rules aggregate every 30s the leaderboard on screen re-orders within a minute of any provider changing performance envelope." + +related: + - which-crypto-price-api-is-the-fastest + - which-l1-has-the-fastest-finality + - which-solana-rpc-lands-the-most-transactions + +seo_title: "Alchemy vs QuickNode vs Infura RPC latency, live 2026" +seo_description: "{{best_name}} leads at {{best_p50}} p50 (24h avg) on the free-tier keyed RPC leaderboard. Alchemy, QuickNode, Infura, Chainstack, Ankr and Helius ranked live from three regions by OpenChainBench." +status: live diff --git a/answers/best-rpc-for-indexer.yml b/answers/best-rpc-for-indexer.yml new file mode 100644 index 00000000..740198c6 --- /dev/null +++ b/answers/best-rpc-for-indexer.yml @@ -0,0 +1,41 @@ +slug: best-rpc-for-indexer +question: "Which RPC is best for indexers and high-throughput read workloads in 2026?" +short_answer: | + {{best_name}} currently leads the free-tier keyed RPC leaderboard at {{best_p50}} p50 latency (24h avg) on eth_getBlockByNumber probes, and pairs it with archive-depth support across a wide chain footprint, measured live by OpenChainBench from three regions. + +benchmark: rpc-capabilities + +intro: | + Indexer workloads have a different failure mode from user-facing dApps. A subgraph or a custom indexer runs continuous eth_getLogs and eth_getBlockReceipts against a chain's history, hits archive-depth constraints on deep queries, and lives or dies by p99 stability rather than p50 headline latency. This page answers the specific question every indexer developer eventually asks: which RPC provider is actually built for high-read workloads, factoring in latency tail, method availability, archive depth and success rate. OpenChainBench probes the multi-chain footprint every 60 seconds and publishes both the flat latency leaderboard and the archive-depth matrix (300 blocks, 7200 blocks, 216k blocks, 1.3M blocks, 5M blocks) side by side so an indexer can pick on the axis that matters for its query pattern. + +methodology: | + The head-latency leaderboard is the same eth_getBlockByNumber probe every 60 seconds from three Railway replicas. The archive-depth matrix is a separate probe running every 5 minutes: eth_getBalance at (head, depth) for depth in {300, 7200, 216000, 1300000, 5000000}. rpc_archive_depth_supported{provider, chain, depth} is 1 when the response is non-pruned, 0 otherwise. The depths cover Geth's default pruned cap (5760 blocks), 1 month, 6 months, and full genesis-era archive. Success rate is computed as ok-rate over total-rate on the same window; providers under 5 percent success drop from the ranking and get pinned as unresponsive. + +limitations: + - "eth_getLogs is not the head-of-chain probe used here. This ranking uses eth_getBlockByNumber for the latency axis and eth_getBalance for the archive axis. Real indexer workloads issue eth_getLogs with topic filters and can exercise different code paths inside a provider's stack. The latency and archive leaderboards are correlated with but not identical to eth_getLogs performance." + - "Free-tier keyed and no-key. This ranks the two tiers a small-to-medium indexer would pick from. Paid enterprise plans get more capacity and priority queues; they are outside the free-and-keyed scope of this bench." + - "Archive depth is a binary support signal, not a latency figure. A provider that returns pruned responses at 5M block depth appears with a 0 in the matrix; a provider that returns non-pruned data with a 6-second latency also appears with a 1. Indexers doing deep historical scans should read the matrix and the sibling archive-latency notes together." + - "Chain coverage varies. Not every provider supports every chain. The multi-chain average rewards universal providers; the per-chain tab exposes the chain-specific leader." + +faq: + - q: "Which RPC provider is best for a subgraph or custom indexer?" + a: "{{best_name}} leads the head-latency leaderboard at {{best_p50}} p50 (24h). For indexers the ranking that matters is the intersection of the latency leaderboard and the archive-depth matrix: a provider that wins latency but fails at 5M depth is not indexer-viable. The bench page surfaces both, and the head-to-head leader among indexer-viable providers is the answer." + - q: "Why does eth_getBlockByNumber matter for indexers when the real workload is eth_getLogs?" + a: "Because eth_getBlockByNumber measures the head-of-chain read path that every provider must implement, it is the fair cross-vendor probe. Providers that misbehave on head reads reliably misbehave on eth_getLogs at higher magnitude. Providers that keep head reads tight and consistent tend to keep eth_getLogs tight and consistent too. The latency leaderboard is a lower bound on indexer-workload latency, and a provider that fails the lower bound is not worth benchmarking on the harder workload." + - q: "Which provider supports full archive access without a paid plan?" + a: "The archive-depth matrix shows exactly which providers return non-pruned responses at 5M blocks on which chain, without a paid plan. Most no-key public RPCs prune at Geth's default cap around 7200 blocks and stop there. Keyed free tiers vary: some grant archive access on their signup tier, some gate it behind paid plans. The matrix answers this per provider per chain rather than as a marketing claim." + - q: "Are there RPCs specifically designed for indexing?" + a: "Indexer-adjacent products like The Graph Node's chain-specific gateways, Chainstack Global Nodes, and Alchemy Supernode advertise indexer-tuned SLAs. On the free-and-keyed tier this bench measures, those products are represented by their base RPC endpoints. Paid enterprise indexer-specific plans are outside the scope of this leaderboard because their SLAs are not publicly probeable without contract." + - q: "How does dRPC compare on indexer workloads?" + a: "dRPC's decentralized mesh routes each request across underlying node providers with consensus checks. That model preserves latency on head reads (see the leaderboard) but adds one layer of orchestration for archive reads. For an indexer's head-of-chain workload dRPC competes on the same axis as any keyed provider; for deep archive scans the per-chain archive-depth matrix is the honest signal." + - q: "How often is the leaderboard refreshed?" + a: "Every 60 seconds for head latency, every 5 minutes for the archive-depth matrix. The bench page reads 24h rolling windows so intraday spikes smooth out; longer-term shifts in provider capability show up within hours." + +related: + - alchemy-vs-quicknode-vs-infura-latency + - drpc-vs-publicnode-vs-1rpc + - which-crypto-price-api-is-the-fastest + +seo_title: "Best RPC for indexers 2026 live rankings" +seo_description: "{{best_name}} leads at {{best_p50}} p50 latency (24h avg) on the OpenChainBench keyed and no-key RPC leaderboards, ranked live from three regions with archive-depth matrix." +status: live diff --git a/answers/cheapest-bridge-usdc-to-base.yml b/answers/cheapest-bridge-usdc-to-base.yml new file mode 100644 index 00000000..20c9267f --- /dev/null +++ b/answers/cheapest-bridge-usdc-to-base.yml @@ -0,0 +1,41 @@ +slug: cheapest-bridge-usdc-to-base +question: "Which bridge is cheapest to move USDC to Base?" +short_answer: | + {{best_name}} currently offers the lowest effective fee for a USDC bridge to Base at {{best_p50}} bps all in (24h avg), measured live by OpenChainBench across Across, Stargate, LI.FI, Squid, Socket, Relay and Near Intents on a $300 USDC probe. + +benchmark: bridge-fee + +intro: | + Bridge comparison articles publish rack rate fee schedules and stop there. The rack rate is one number in a bill that also includes destination gas, LP fees passed through as slippage, and the effective execution price the router actually delivers. On a $300 USDC bridge to Base, two providers quoting the same 5 bps headline can deliver very different real fees once the router walks the pool. This page answers the specific question retail and small-treasury movers actually search for: at $300 USDC notional, which bridge to Base is cheapest today. OpenChainBench queries the same USDC to Base intent against every major router every 5 minutes, computes the all-in effective fee including destination gas, and publishes the ranked leaderboard live. + +methodology: | + Every 5 minutes the harness sends the same intent (source: Ethereum mainnet USDC, destination: Base USDC, amount: $300 notional, receiver: probe wallet) to each router's quote API in parallel. Effective fee is computed as `(source_input_usd - destination_output_usd - destination_gas_usd) / source_input_usd` in basis points, so slippage, LP fees and gas are all inside the same number. Providers that fail to return a quote (5xx, rate limit, timeout) leave the previous gauge in place and increment a fetch_errors_total counter. The leaderboard ranks by 24h average of bridge_effective_fee_bps for the USDC to Base pair. The $300 size is the small ticket regime; sibling benches probe higher notional to expose where routers slip on size. + +limitations: + - "Quote fee only. This measures router-quoted effective fee, not settlement. Any router that quotes tighter than it settles would show a fast p50 with degraded real-world execution; the sibling bridge-quote-latency bench flags routers where quote-to-settle drift is measurable." + - "One size, one route. The $300 USDC to Base pair is the highest-search-volume small-ticket bridge query. At $50k notional the leaderboard re-orders because the spread and LP fee components dominate. Producers moving treasury size should read the per-size breakdown on the bench page." + - "Destination gas at quote time. Destination gas is included in the effective fee at the quoted receiver payout, not at a delayed execution instant. A destination gas spike between quote and settle would degrade the actual fee below what this leaderboard reports." + - "No canonical CCTP fast path. The USDC canonical bridge via Circle CCTP has different guarantees and speeds than the market-maker routers ranked here. The leaderboard measures the market-maker path (Across, LI.FI, Stargate, Squid, Socket, Relay, Near Intents) because it is the head-to-head every comparison article ranks." + +faq: + - q: "Which bridge is cheapest for a $300 USDC transfer to Base?" + a: "{{best_name}} leads the leaderboard at {{best_p50}} bps effective fee (24h avg) measured live on the same USDC to Base $300 probe. The full ranking including Across, Stargate, LI.FI, Squid, Socket, Relay and Near Intents re-sorts every 5 minutes against fresh Prometheus samples." + - q: "Why $300, and does the winner change at $10k or $100k?" + a: "$300 is the small-ticket regime that dominates retail search. At $10k the leaderboard shifts because LP-based routers (Stargate) start to lose ground to intent-based routers (Across, Near Intents) whose quotes are less size-dependent. At $100k the spread widens further and the choice becomes size-specific; the per-size tab on the bench page shows the leader at each notional." + - q: "Is Circle CCTP not on this ranking?" + a: "CCTP is Circle's canonical burn-and-mint path for USDC. It has different guarantees, speeds and a fixed protocol path, so it is not directly comparable to market-maker routers that price a swap through a pool. The bench measures the market-maker rankings because that is the choice retail actually makes; canonical CCTP is a separate decision users make once they know their maximum acceptable latency." + - q: "How is Across usually so cheap on this route?" + a: "Across runs an intent-based settlement model where relayers compete to fill from destination inventory. That model compresses the spread component to near zero on high-liquidity pairs like USDC Ethereum to Base. On less-liquid destinations Across loses its edge; that per-destination effect is why the leaderboard re-ranks by pair rather than emitting a single global cheapest bridge." + - q: "Is Stargate outdated for Base?" + a: "Stargate remains competitive on high-throughput pairs where its LP depth matters more than intent competition. On USDC to Base the pair-specific LP depth in 2026 has been narrower than intent-based competitors, which is why the leaderboard usually shows an intent router on top. Stargate wins on pairs where LP depth is the constraint." + - q: "How often is the leaderboard refreshed?" + a: "Every 5 minutes. The harness probes every router's quote API at that cadence, publishes bridge_effective_fee_bps to Prometheus, and the page reads a 24h rolling average. Because the recording rules re-aggregate every 30 seconds the on-screen ranking re-orders within a minute of any router changing quote behavior." + +related: + - which-bridge-has-the-fastest-quote-api + - which-bridge-has-the-cheapest-usdc-fee + - which-blockchain-has-cheapest-transaction-fees + +seo_title: "Cheapest bridge for USDC to Base 2026 live" +seo_description: "{{best_name}} leads at {{best_p50}} bps effective fee on a $300 USDC to Base probe. Across, Stargate, LI.FI, Squid, Socket, Relay and Near Intents ranked live by OpenChainBench." +status: live diff --git a/answers/drpc-vs-publicnode-vs-1rpc.yml b/answers/drpc-vs-publicnode-vs-1rpc.yml new file mode 100644 index 00000000..cb2bb1cc --- /dev/null +++ b/answers/drpc-vs-publicnode-vs-1rpc.yml @@ -0,0 +1,41 @@ +slug: drpc-vs-publicnode-vs-1rpc +question: "dRPC vs PublicNode vs 1RPC, which no-key public RPC is fastest?" +short_answer: | + {{best_name}} currently leads the no-key public RPC leaderboard at {{best_p50}} p50 (24h), measured live from three regions across 10 EVM chains by OpenChainBench. dRPC, PublicNode and 1RPC each cover a different subset of chains and OpenChainBench ranks them under identical measurement conditions. + +benchmark: rpc-capabilities + +intro: | + Developers picking a no-key public RPC face three defaults recommended by Chainlist blurbs and vendor cross-promo posts. dRPC advertises a consensus-checked decentralized mesh; PublicNode ships an Allnodes-operated footprint across 70+ chains; 1RPC pitches a privacy-preserving gateway from Automata Network. Every one of those descriptions is a marketing frame. What actually matters when a contract call round-trips through the wire is p50 latency, p99 tail, and Success rate under real measurement conditions. This page ranks dRPC, PublicNode and 1RPC head to head on the same eth_getBlockByNumber probe against 10 EVM chains from three regions, and shows which provider actually earns its default recommendation. + +methodology: | + Every 60 seconds the harness sends `{"jsonrpc":"2.0","method":"eth_getBlockByNumber","params":["latest",false]}` against each provider's no-key endpoint on every chain they support, from us-east, eu-west and sgp replicas. Latency is a client-side time.Now() delta around the round-trip, recorded only for successful calls, exposed as gauge + histogram. p50/p90/p99 are computed via Prometheus quantile_over_time over the last 24h and averaged across regions for the headline. Call-result classification (ok, http_err, jsonrpc_err, stale, timeout) drives a companion Success column so a mostly failing provider never posts a fast p50 out of its rare successes. Providers with under 5 percent success get their latency series dropped from the ranking and pinned below the table as unresponsive. + +limitations: + - "Chain coverage differs. dRPC and PublicNode cover most of the 10 measured chains; 1RPC covers a smaller subset. The All chains tab averages each provider over the chains they actually serve, which lets universal providers compare meaningfully against single-chain specialists. The head-to-head this page focuses on is the intersection where all three compete." + - "Latency is conditional on success. A provider failing 30 percent of calls but returning the successes fast will show a fast p50 with a low Success column. The two columns must be read together." + - "1RPC 2025 to 2026 has struggled with low ok-rate on several chains, sometimes under 15 percent success at minimal load. That does not make 1RPC objectively bad for every workload but does mean the flat p50 leaderboard is not the right ranking for it; the Success column is." + - "Geography matters. Each of the three providers has a different termination footprint and the per-region breakdown reveals where each wins. The cross-region average obscures that." + +faq: + - q: "Which no-key public RPC is fastest right now, dRPC, PublicNode or 1RPC?" + a: "{{best_name}} leads the leaderboard at {{best_p50}} p50 (24h) on the cross-chain average measured live from three regions. Per-chain leaders are different: PublicNode wins some chains, dRPC wins others, and 1RPC's ranking is bounded by its Success column more than by its raw p50." + - q: "Why does 1RPC show fast latency but a low Success rate?" + a: "The privacy-preserving gateway introduces additional processing between the client and the underlying node, and 1RPC has run into rate-limit and staleness issues at minimal probe load in 2025 to 2026. Because latency is recorded only on successful calls, the fast p50 hides how often a request would have failed in production. Read the Success column before ranking 1RPC alongside dRPC and PublicNode." + - q: "Does dRPC's consensus-checking add latency compared to PublicNode?" + a: "In theory yes, in practice barely measurable at head-of-chain reads. dRPC routes each request across third-party node providers with cross-check logic, so the p50 tracks the median of the underlying mesh rather than the fastest node. On chains where the mesh footprint is dense in the probe region, dRPC and PublicNode land within a few milliseconds. On sparse chains, PublicNode's direct-node approach wins." + - q: "Which region wins per provider?" + a: "PublicNode dominates EU-West on most chains because its Allnodes footprint is EU-anchored. dRPC has the best Singapore results on multi-chain aggregates. 1RPC's regional variance is dominated by its Success column rather than by latency. The per-region breakdown at the top of the bench page shows this cleanly." + - q: "Are these numbers different from Chainlist's summary?" + a: "Chainlist is a chain-metadata directory, not a benchmark. It lists RPC URLs and lets users vote but does not probe latency or success rate. OpenChainBench is the live benchmark that fills the gap between Chainlist's discovery and a developer's need for cross-provider performance measurement." + - q: "Is public RPC good enough for production?" + a: "For read-heavy demos, local development, fallback paths and one-shot lookups, yes. For eth_sendRawTransaction, websocket subscriptions, archive queries beyond Geth's pruned cap, and any production API that a paying user depends on, graduate to a keyed provider. The bench openchainbench.com/benchmarks/rpc-keyed-latency ranks the keyed free tiers under the same rules." + +related: + - which-crypto-price-api-is-the-fastest + - which-l1-has-the-fastest-finality + - alchemy-vs-quicknode-vs-infura-latency + +seo_title: "dRPC vs PublicNode vs 1RPC no-key RPC latency 2026" +seo_description: "{{best_name}} leads at {{best_p50}} p50 (24h) on the OpenChainBench no-key public RPC leaderboard. dRPC, PublicNode and 1RPC ranked live across 10 EVM chains from three regions." +status: live diff --git a/answers/helius-vs-triton-vs-quicknode-solana.yml b/answers/helius-vs-triton-vs-quicknode-solana.yml new file mode 100644 index 00000000..5a65b0f8 --- /dev/null +++ b/answers/helius-vs-triton-vs-quicknode-solana.yml @@ -0,0 +1,41 @@ +slug: helius-vs-triton-vs-quicknode-solana +question: "Helius vs Triton vs QuickNode, which lands the most Solana transactions?" +short_answer: | + {{best_name}} currently lands the highest share of Solana transactions on the OpenChainBench active-probe harness at {{best_p50}} (24h), measured live from three regions by broadcasting identical transfers through each provider's mainnet RPC and confirming inclusion onchain. + +benchmark: solana-tx-landing + +intro: | + Every Solana app that touches user transactions asks the same question the moment congestion spikes: which RPC provider actually lands transactions and which one silently drops them into the void. Provider blog posts advertise self-reported landed-rate figures with no third-party probe, and public dashboards conflate confirmed with landed by treating any getSignatureStatuses response as success. This page answers the head-to-head developers actually search for: on the same transaction, sent through the same broadcast path, into the same current mempool state, which of Helius, Triton and QuickNode gets it included. OpenChainBench signs and broadcasts real base-fee transfers through each provider every measurement cycle, waits a bounded window for onchain confirmation, and publishes the landed rate, median slot delta, and per-provider failure taxonomy live. + +methodology: | + Every cycle the harness signs an identical SOL transfer from a probe keypair, broadcasts it in parallel through each provider's mainnet RPC using sendTransaction, and polls getSignatureStatuses until it either sees the signature in a confirmed block or crosses a landing deadline. Landed rate is confirmed-in-window / broadcast-total per provider over a rolling 24h window. Slot delta is the median number of slots between broadcast and inclusion on landed attempts only. Providers whose broadcast returns a network error before submission are excluded from the landed-rate denominator and counted separately in the fetch_errors_total counter so a network flake never masquerades as a landing failure. Regional coverage uses the same three Railway replicas (us-east, eu-west, sgp) so the same providers compete from the same geographies. + +limitations: + - "Small-value probe. Every broadcast is a minimal SOL transfer with base fee only. Providers running priority-lane routing (Helius Sender, QuickNode Bundler) may show a wider gap on real user traffic than on base-fee probes because their advantage kicks in on paying transactions." + - "One tx type. The harness tests base transfer; complex programs (Jupiter aggregated swaps, Kamino borrow flows) exercise different simulation and preflight paths per provider. The base-transfer landing rate is a floor, not a ceiling." + - "Regional footprint matters. A provider terminating close to a Solana leader schedule lands faster on that leader; the per-region breakdown surfaces which provider wins from which region rather than hiding the geography effect in a cross-region average." + - "Landing deadline is bounded. Transactions that do not land inside the window count as not landed even if they eventually confirm minutes later. The window is publicly documented and matches the deadline a typical user or wallet times out on." + +faq: + - q: "Which Solana RPC lands the most transactions right now, Helius, Triton or QuickNode?" + a: "{{best_name}} leads the OpenChainBench active-probe leaderboard at {{best_p50}} (24h) on real broadcasts confirmed onchain. The full ranking including Helius, Triton, QuickNode, Alchemy and public RPCs re-sorts every measurement cycle against fresh Prometheus samples." + - q: "Why measure landing rate instead of just latency?" + a: "On Solana under congestion, the failure mode that matters to users is dropped transactions, not slow responses. A provider can return a fast success ack from sendTransaction and never actually get the tx into a block. OpenChainBench confirms inclusion onchain by polling for the signature, so the landed rate reflects what a user actually experiences, not what the provider self-reports." + - q: "Is Helius Sender factored into this ranking?" + a: "The base bench measures the standard sendTransaction path. Helius Sender is a priority-lane product that competes against QuickNode Bundler and Jito bundle submission on paying transactions with tips. Sibling benches on the same harness cover the priority-lane comparison; the head-to-head on this page is the base-tier landing rate that developers on free or standard plans hit by default." + - q: "Are the numbers different from the self-reported vendor dashboards?" + a: "Yes and they should be. Vendors report their own success from their own retry logic, sometimes counting a preflight-passed tx as successful before it actually confirms. OpenChainBench uses the onchain signature as ground truth: if a slot did not contain the signature inside the deadline, it did not land, regardless of what the provider's dashboard reports." + - q: "How often is the leaderboard refreshed?" + a: "Every measurement cycle the harness re-broadcasts fresh probes, waits for confirmation, and publishes the landing metrics to Prometheus. The bench page reads a 24h rolling window so intraday congestion spikes smooth out; longer-term shifts in provider performance show up within hours." + - q: "Does public RPC still work for Solana in 2026?" + a: "Only in the sense that it accepts your broadcast. The landed rate on unpaid public endpoints under 2026 congestion is materially below every keyed provider on this leaderboard. Public RPC is fine for read paths and development; production tx submission needs a keyed provider whose landed rate is on this page, not a marketing claim." + +related: + - which-solana-rpc-lands-the-most-transactions + - which-solana-dex-aggregator-is-the-fastest + - which-crypto-price-api-is-the-fastest + +seo_title: "Helius vs Triton vs QuickNode Solana landing rate 2026" +seo_description: "{{best_name}} leads at {{best_p50}} (24h) on the OpenChainBench active-probe Solana landing leaderboard. Helius, Triton, QuickNode ranked live by real onchain confirmation." +status: live diff --git a/answers/polymarket-vs-kalshi-resolution-speed.yml b/answers/polymarket-vs-kalshi-resolution-speed.yml new file mode 100644 index 00000000..d7d51bca --- /dev/null +++ b/answers/polymarket-vs-kalshi-resolution-speed.yml @@ -0,0 +1,41 @@ +slug: polymarket-vs-kalshi-resolution-speed +question: "Polymarket vs Kalshi, which resolves prediction markets faster?" +short_answer: | + {{best_name}} currently resolves faster on the OpenChainBench harness at {{best_p50}} median resolution delay (24h), measured directly onchain for Polymarket and via official settlement events for Kalshi across sports, politics and crypto market categories. + +benchmark: polymarket-resolution-delay + +intro: | + Prediction market comparison articles publish rack rate resolution times ("Polymarket resolves in minutes") without measuring the tail. In practice, resolution speed depends on the venue's oracle stack, the category of the market and the dispute window. Polymarket runs on UMA's optimistic oracle with a variable liveness window; Kalshi settles through a CFTC-registered exchange oracle with fixed settlement rails. This page answers the head-to-head every prediction market trader eventually cares about at payout time: on the same market outcome trigger, from event confirmation to onchain payout on Polymarket or to account credit on Kalshi, which venue is faster and by how much. OpenChainBench measures both venues from their own settlement events, breaks out sports, politics and crypto categories separately, and publishes the ranked distribution live. + +methodology: | + For Polymarket the harness reads onchain events on Polygon: outcome proposal via UMA's OptimisticOracleV2, liveness window open, dispute or no-dispute, and payout claim. Resolution delay is the median duration from ProposePrice to onchain payout availability, computed per market category over a 24h rolling window. For Kalshi the harness reads the exchange's settlement events from Kalshi's official API: market close, settlement announcement, and account credit availability. Resolution delay is the median duration from settlement announcement to account credit. Both venues publish their own event timestamps as ground truth; the harness does not rely on secondary indexers. Categories are tagged by market metadata (sports, politics, crypto) so the comparison is category-honest. + +limitations: + - "Two different settlement models. Polymarket is fully onchain with a dispute window; Kalshi is offchain with fixed exchange rails. The comparison measures the user-facing latency on each venue, not the oracle security models, which are architecturally distinct." + - "Category matters more than venue on this metric. Sports resolve fast on both venues; politics and edge-case market categories take materially longer on Polymarket because the UMA liveness window is proportional to stakes. The head-to-head number is the aggregate; the per-category breakdown shows where the winner actually wins." + - "One-in-five long tail. On both venues, roughly one in five markets takes materially longer than the median for reasons that are venue-specific (Polymarket: contested UMA proposals; Kalshi: exchange-side settlement processes). The median under-reports the tail; the p90 column tells the honest story." + - "Payout availability, not payout claim. This measures when funds become claimable on Polymarket or credited on Kalshi. Users who do not claim promptly on Polymarket have to pay gas at the moment they claim; that cost is not part of the resolution delay figure." + +faq: + - q: "Polymarket vs Kalshi, which resolves faster right now?" + a: "{{best_name}} leads at {{best_p50}} median resolution delay (24h). The per-category breakdown at the top of the bench page shows the venue that wins on sports, politics and crypto separately, which is the honest way to answer a question that depends heavily on category." + - q: "Why is Polymarket resolution slower on politics than sports?" + a: "Polymarket runs on UMA's optimistic oracle. The liveness window (time between outcome proposal and dispute deadline) is proportional to stakes, and politics markets have both larger stakes and higher dispute probability than clean sports outcomes. The result is a category-specific delay that sports mostly avoid." + - q: "Is Kalshi always faster than Polymarket?" + a: "On aggregate median, usually yes for sports and equally-fast for crypto price outcomes. On politics and long-tail edge markets Polymarket sometimes wins because its UMA proposal-and-verify cycle can close before Kalshi's exchange-side settlement bureaucracy. The bench measures both and lets each win where it actually wins." + - q: "How does OpenChainBench measure Polymarket resolution when the API is sometimes misleading?" + a: "By reading UMA's OptimisticOracleV2 events directly on Polygon rather than relying on Polymarket's API. The onchain events are the ground truth for when a market becomes resolvable and when payout is claimable. This measurement approach fixes the API drift issue that affects other Polymarket dashboards." + - q: "How does Kalshi settlement work compared to Polymarket?" + a: "Kalshi is a CFTC-registered exchange with fixed settlement rails: market close, settlement committee reviews, credit issued. The whole cycle is offchain and fixed per market type. Polymarket is fully onchain with a variable-length dispute window; the same outcome can resolve in minutes on a clean market or hours on a contested one. Different models, different failure modes." + - q: "Does this include Polymarket's UMA disputes?" + a: "Yes. The median resolution delay includes disputed markets in the denominator. Disputes extend the liveness window and materially slow resolution; treating them as separate would misrepresent the user-facing latency. The p90 column captures the long tail where disputes dominate." + +related: + - which-prediction-market-data-api-is-the-freshest + - which-prediction-market-has-the-strictest-rate-limits + - which-hyperliquid-frontend-has-the-most-builder-revenue + +seo_title: "Polymarket vs Kalshi resolution speed 2026 live" +seo_description: "{{best_name}} leads at {{best_p50}} median resolution delay (24h) on the OpenChainBench prediction market resolution benchmark. Polymarket and Kalshi ranked live across sports, politics, crypto." +status: live diff --git a/answers/who-is-winning-hyperliquid-ecosystem.yml b/answers/who-is-winning-hyperliquid-ecosystem.yml new file mode 100644 index 00000000..6699a3df --- /dev/null +++ b/answers/who-is-winning-hyperliquid-ecosystem.yml @@ -0,0 +1,41 @@ +slug: who-is-winning-hyperliquid-ecosystem +question: "Who is winning the Hyperliquid ecosystem in 2026?" +short_answer: | + {{best_name}} currently leads the Hyperliquid builder revenue leaderboard at {{best_p50}} (24h), measured live by OpenChainBench from a local Hyperliquid node tailing every fill onchain across all 104 tracked frontends. + +benchmark: hyperliquid-frontends + +intro: | + Hyperliquid is the derivatives narrative of 2026 but the frontend cohort question is systematically undercovered: which application actually owns the trader relationship and captures the revenue that comes with it. Aggregate volume figures from Hyperliquid itself do not decompose by frontend. Third-party dashboards vary in methodology and sometimes double-count builder code claims. OpenChainBench runs a local Hyperliquid node and tails every fill on mainnet, decomposes builder revenue by frontend, and publishes the full 104-frontend leaderboard live. This page answers the ecosystem-level question every HL observer eventually asks: who is winning, by how much, and how concentrated is the revenue. + +methodology: | + A local Hyperliquid node subscribes to the fills stream on mainnet and records every trade with its builder code, notional and fee. The harness aggregates builder fee revenue by frontend over rolling 24h, 7d and 30d windows. Wallet identities behind builder codes come from a hand-curated registry (openchainbench.com/hyperliquid) so the mapping from raw builder code to human-readable frontend is transparent and auditable. Rankings expose builder_revenue_usd per frontend as a Prometheus gauge; the leaderboard reads the 24h window and re-orders every scrape cycle. + +limitations: + - "Builder revenue only. Frontends also capture non-builder revenue (subscriptions, custody fees, off-platform monetization). This bench measures the onchain builder revenue slice because it is the only cross-frontend comparable public number. It is a lower bound on total frontend monetization." + - "104 tracked frontends. New frontends launch continuously; the harness adds them within a scrape cycle of their first builder-code fill hitting the wire. A frontend not on the leaderboard has zero measurable builder revenue in the window." + - "Concentration analysis is dominant. The top 5 frontends capture the overwhelming majority of ecosystem builder revenue. A long-tail frontend at rank 50 has revenue below what the median trader loses on funding in a bad week, so the ranking is honest but the tail is not comparable to the head on absolute terms." + - "Sibling HIP-3 deployers bench measures the same population from a different angle. HIP-3 deployers are ranked separately because the deployer-fee mechanic is architecturally distinct from builder-fee attribution." + +faq: + - q: "Who has the most Hyperliquid builder revenue right now?" + a: "{{best_name}} leads at {{best_p50}} (24h) on the OpenChainBench builder revenue leaderboard. The full 104-frontend ranking, plus rolling 7d and 30d windows and daily trend since HL launched, is available at openchainbench.com/hyperliquid." + - q: "Why does Phantom sit so high on Hyperliquid builder revenue?" + a: "Phantom is a Solana-native wallet that added Hyperliquid perp support and captured retail traders who preferred a wallet-integrated UX to a dedicated HL app. Because Phantom routes fills through its builder code, every trade on Hyperliquid originating from Phantom credits builder revenue to Phantom. The result is that a Solana-native wallet leads HL builder revenue on many days, which is a real story about how HL's frontend layer works." + - q: "How concentrated is Hyperliquid builder revenue among frontends?" + a: "Extremely concentrated. The top 5 frontends capture the overwhelming majority of daily ecosystem builder revenue; the remaining 99 tracked frontends share what is left. Distribution matches the pattern of most crypto attention markets (few winners take most of the pie) and the cumulative share chart on the hub page shows the honest curve." + - q: "What is a builder code and how is it credited?" + a: "Any application routing orders to Hyperliquid can attach a builder code to its trades. Hyperliquid's exchange settles a portion of the taker fee to the wallet designated by that builder code. OpenChainBench's harness reads the code from each fill onchain and aggregates by the mapping in the transparent frontend registry. That is why the ranking is reproducible: the raw data is public onchain." + - q: "Which frontends are gaining share in 2026?" + a: "The bench page publishes 7d and 30d trend windows alongside the 24h leaderboard. Watching the deltas reveals which frontends are compounding growth (usually the ones with mobile-first UX and a deep pre-existing user base) versus which are losing share to newer entrants. The trend-window ranks change more slowly than the 24h ranks; a frontend consistently in the 7d top 5 is winning the ecosystem in a way that a one-day spike is not." + - q: "How is this different from Hyperliquid's own volume dashboard?" + a: "Hyperliquid publishes aggregate volume, not builder revenue by frontend. Third-party dashboards attempt the decomposition but vary in methodology. OpenChainBench runs its own HL node, uses the onchain builder-code data as ground truth, and publishes the leaderboard under CC-BY-4.0 with an open registry. The result is a reproducible answer to the ecosystem-level question that Hyperliquid's own dashboard does not decompose." + +related: + - which-hyperliquid-frontend-has-the-most-builder-revenue + - which-hyperliquid-frontend-generates-the-most-hip3-revenue + - which-perp-dex-has-the-lowest-fees + +seo_title: "Who is winning the Hyperliquid ecosystem 2026, live" +seo_description: "{{best_name}} leads at {{best_p50}} (24h) on the OpenChainBench Hyperliquid builder revenue leaderboard. 104 frontends tracked live from an onchain HL node." +status: live diff --git a/benchmarks/mev-protect-rpc.yml b/benchmarks/mev-protect-rpc.yml new file mode 100644 index 00000000..e490e236 --- /dev/null +++ b/benchmarks/mev-protect-rpc.yml @@ -0,0 +1,161 @@ +# OpenChainBench. Bench № 074 + +slug: mev-protect-rpc +number: "074" +title: Fastest MEV protection RPC, live wallet latency and method coverage +seo_title: "Best MEV protection RPC 2026" +seo_description: "{{best_name}} leads MEV protection RPC wallet latency at {{best_p50}} (p50, 24h). Flashbots Protect, MEV Blocker, Blink probed on the wallet method set from 3 regions, keyless." +subtitle: "Median latency of the JSON-RPC method set wallets call constantly (balance, gas, call simulation), measured against public MEV protection gateways from three regions. Read-path hygiene only: inclusion rates and refunds are the write path and cannot be measured without sending transactions." +category: RPCs +status: live +metric: Wallet call latency +unit: ms +higher_is_better: false + +seo_intro: | + Every guide that tells you to paste a custom RPC into MetaMask to + dodge sandwich bots skips the same question. what does that RPC do to + the rest of your wallet. Once Flashbots Protect, MEV Blocker or Blink + becomes your default endpoint, every balance refresh, every gas + estimate and every simulation runs through their gateway, and a slow + or method-crippled proxy makes the whole wallet feel broken. This + page measures that read path live. seven wallet methods (chainId, + blockNumber, gasPrice, getBalance, call, estimateGas, feeHistory) + probed keyless every minute from us-east, eu-west and Singapore, with + a coverage panel showing which methods each gateway actually serves. + Flashbots Protect currently rejects eth_call on its public endpoint; + Blink serves the full wallet set but blocks full-node reads like + eth_getBlockByNumber. Numbers nobody publishes, refreshed around the + clock. What this page deliberately does not claim to rank: inclusion + speed, refund rates and sandwich protection efficacy are the write + path, they require funded transactions to measure, and the one + rigorous study there is a one-off academic snapshot, not a live feed. + +abstract: | + We probe the public gateways of MEV protection RPCs with the JSON-RPC + method set wallets call constantly, keyless, every minute, from three + regions. The headline number is the per-tick median latency across + the methods a gateway serves; method coverage is tracked separately + so a gateway that rejects a method is shown as a coverage gap rather + than a latency penalty. No transactions are sent. + +methodology: + - "Providers measured: Flashbots Protect (rpc.flashbots.net), MEV Blocker (rpc.mevblocker.io), Blink, formerly Merkle (ethereum.blinklabs.xyz). SecureRPC probed dead 2026-07-10; the legacy merkle.io hosts alias Blink behind a stricter rate limit and are not probed." + - "Method set: eth_chainId, eth_blockNumber, eth_gasPrice, eth_getBalance, eth_call (USDC balanceOf), eth_estimateGas, eth_feeHistory. One request per method per tick, 1.5s apart, rotating request ids against body-keyed edge caches." + - "Cadence: every 60 seconds per region (us-east, eu-west, sgp), 7 requests per provider per tick. Deliberately polite: these gateways rate-ban aggressive callers." + - "Headline: median latency across the methods the provider served that tick, aggregated over 24h with quantile_over_time. A rejected method does not poison the latency; it lowers the coverage panel instead." + - "Coverage: mev_rpc_methods_supported counts the wallet methods served on the last tick (max 7). Flashbots rejects eth_call on the public endpoint; Blink blocks full-node reads like eth_getBlockByNumber, which keeps it off the general RPC benches but not off this one." + - "Out of scope, disclosed: inclusion rate, refund economics and sandwich protection efficacy are the write path and require funded transactions. See the arXiv study Private MEV Protection RPCs (2505.19708) for a one-off execution-quality comparison." + - "Failures (timeouts, 403, 429) increment mev_rpc_call_total{result} and count against the success rate; the gauge keeps its last value so the chart shows the outage in the success column rather than a fake zero." + +findings: + - "{{best_name}} leads at {{best_p50}} (p50, 24h) on the cross-region wallet call median over {{count}} measured gateways." + - "{{name:mevblocker}} sits at {{p50:mevblocker}} (p50, 24h) and serves the full 7-method wallet set. MEV Blocker was acquired by Consensys in January 2026; its gateway proxies reads with the lowest overhead of the cohort." + - "{{name:flashbots}} clocks {{p50:flashbots}} (p50, 24h) but rejects eth_call on the public endpoint, so wallets relying on it for simulation fall back to another provider for that path." + - "{{name:blinklabs}} returns {{p50:blinklabs}} (p50, 24h) across the full wallet set. Blink, formerly Merkle, is a wallet-facing MEV proxy by design: it serves everything a wallet needs while blocking full-node reads." + - "The p50 to p99 gap is the number wallet teams should read: a protect RPC that stalls on one refresh per hundred makes the whole wallet feel unreliable, whatever its median." + +source: https://github.com/ChainBench/OpenChainBench/tree/main/harnesses/mev-protect-rpc + +prometheus: + window: 24h + +faq: + - q: "Which MEV protection RPC is fastest for wallet usage?" + a: "{{best_name}} currently leads at {{best_p50}} (p50, 24h) on the wallet method median measured from three regions. The number covers the read path a wallet hits constantly once you set a protect RPC as default: balance refreshes, gas estimates, call simulations. It does not rank inclusion speed or refunds, which are write-path properties that cannot be measured without sending funded transactions." + - q: "Does an MEV protection RPC slow down my wallet?" + a: "It adds a proxy hop, and this page measures exactly how much. The gateways in this cohort answer wallet reads in tens to hundreds of milliseconds at p50, but the p99 column shows the stalls you feel. A gateway can also reject methods outright: Flashbots Protect currently rejects eth_call on its public endpoint, so simulation-heavy wallets silently fall back to another provider." + - q: "What is the difference between Flashbots Protect, MEV Blocker and Blink?" + a: "All three route your transactions away from the public mempool to prevent sandwich attacks, and they monetize the order flow differently: refunds shared with users (MEV Blocker pays a share of backrun value), enterprise deals with wallets (Blink powers Ledger and sells through the QuickNode marketplace), or the Flashbots auction. This page compares the part of the product you can verify from outside: the latency and coverage of the read gateway your wallet talks to all day." + - q: "Why is inclusion rate not on this benchmark?" + a: "Measuring inclusion or refunds requires signing and funding real transactions on every gateway around the clock, which is expensive and hard to keep neutral. The one rigorous comparison of that layer is a one-off academic study (arXiv 2505.19708). This benchmark tracks the layer that can be measured continuously and keyless, and says so instead of extrapolating." + +# Per-region badge scoping, same alias pattern as the other RPC benches: +# the harness emits `provider`, the badge infra expects `provider`. +rank_matrix_query: avg by (provider, region) (quantile_over_time(0.50, mev_rpc_wallet_latency_milliseconds[24h])) + +dimensions: + region: + - { value: all, label: All regions } + - { value: us-east, label: US-East } + - { value: eu-west, label: EU-West } + - { value: sgp, label: Singapore } + +metric_panels: + - id: coverage + label: Method coverage + metric: avg(mev_rpc_methods_supported) + label_key: provider + unit: count + higher_is_better: true + description: "Wallet methods served on the last probe tick, out of 7. A gateway that rejects a method is a coverage gap wallets must engineer around, not a latency problem." + +providers: + - slug: flashbots + name: Flashbots Protect + tag: Public protect RPC, rejects eth_call on the free gateway + formula: "Median latency across the wallet methods served per 60s tick against rpc.flashbots.net, aggregated over 24h via quantile_over_time; 3-region average." + queries: + p50: quantile_over_time(0.50, mev_rpc_wallet_latency_milliseconds{provider="flashbots"}[24h]) + p90: quantile_over_time(0.90, mev_rpc_wallet_latency_milliseconds{provider="flashbots"}[24h]) + p99: quantile_over_time(0.99, mev_rpc_wallet_latency_milliseconds{provider="flashbots"}[24h]) + mean: avg_over_time(mev_rpc_wallet_latency_milliseconds{provider="flashbots"}[24h]) + success: sum(increase(mev_rpc_call_total{provider="flashbots",result="ok"}[24h])) / clamp_min(sum(increase(mev_rpc_call_total{provider="flashbots"}[24h])), 1) + sample_size: sum(increase(mev_rpc_call_total{provider="flashbots"}[24h])) + series: avg(avg_over_time(mev_rpc_wallet_latency_milliseconds{provider="flashbots"}[1h])) + regions: + - region: us-east + p50: quantile_over_time(0.50, mev_rpc_wallet_latency_milliseconds{provider="flashbots", region="us-east"}[24h]) + series: avg_over_time(mev_rpc_wallet_latency_milliseconds{provider="flashbots", region="us-east"}[1h]) + - region: eu-west + p50: quantile_over_time(0.50, mev_rpc_wallet_latency_milliseconds{provider="flashbots", region="eu-west"}[24h]) + series: avg_over_time(mev_rpc_wallet_latency_milliseconds{provider="flashbots", region="eu-west"}[1h]) + - region: ap-southeast + p50: quantile_over_time(0.50, mev_rpc_wallet_latency_milliseconds{provider="flashbots", region="sgp"}[24h]) + series: avg_over_time(mev_rpc_wallet_latency_milliseconds{provider="flashbots", region="sgp"}[1h]) + + - slug: mevblocker + name: MEV Blocker + tag: CoW built, Consensys owned since 2026, full wallet method set + formula: "Median latency across the wallet methods served per 60s tick against rpc.mevblocker.io, aggregated over 24h via quantile_over_time; 3-region average." + queries: + p50: quantile_over_time(0.50, mev_rpc_wallet_latency_milliseconds{provider="mevblocker"}[24h]) + p90: quantile_over_time(0.90, mev_rpc_wallet_latency_milliseconds{provider="mevblocker"}[24h]) + p99: quantile_over_time(0.99, mev_rpc_wallet_latency_milliseconds{provider="mevblocker"}[24h]) + mean: avg_over_time(mev_rpc_wallet_latency_milliseconds{provider="mevblocker"}[24h]) + success: sum(increase(mev_rpc_call_total{provider="mevblocker",result="ok"}[24h])) / clamp_min(sum(increase(mev_rpc_call_total{provider="mevblocker"}[24h])), 1) + sample_size: sum(increase(mev_rpc_call_total{provider="mevblocker"}[24h])) + series: avg(avg_over_time(mev_rpc_wallet_latency_milliseconds{provider="mevblocker"}[1h])) + regions: + - region: us-east + p50: quantile_over_time(0.50, mev_rpc_wallet_latency_milliseconds{provider="mevblocker", region="us-east"}[24h]) + series: avg_over_time(mev_rpc_wallet_latency_milliseconds{provider="mevblocker", region="us-east"}[1h]) + - region: eu-west + p50: quantile_over_time(0.50, mev_rpc_wallet_latency_milliseconds{provider="mevblocker", region="eu-west"}[24h]) + series: avg_over_time(mev_rpc_wallet_latency_milliseconds{provider="mevblocker", region="eu-west"}[1h]) + - region: ap-southeast + p50: quantile_over_time(0.50, mev_rpc_wallet_latency_milliseconds{provider="mevblocker", region="sgp"}[24h]) + series: avg_over_time(mev_rpc_wallet_latency_milliseconds{provider="mevblocker", region="sgp"}[1h]) + + - slug: blinklabs + name: Blink + tag: Formerly Merkle, wallet MEV proxy, full wallet set, powers Ledger + formula: "Median latency across the wallet methods served per 60s tick against ethereum.blinklabs.xyz, aggregated over 24h via quantile_over_time; 3-region average." + queries: + p50: quantile_over_time(0.50, mev_rpc_wallet_latency_milliseconds{provider="blinklabs"}[24h]) + p90: quantile_over_time(0.90, mev_rpc_wallet_latency_milliseconds{provider="blinklabs"}[24h]) + p99: quantile_over_time(0.99, mev_rpc_wallet_latency_milliseconds{provider="blinklabs"}[24h]) + mean: avg_over_time(mev_rpc_wallet_latency_milliseconds{provider="blinklabs"}[24h]) + success: sum(increase(mev_rpc_call_total{provider="blinklabs",result="ok"}[24h])) / clamp_min(sum(increase(mev_rpc_call_total{provider="blinklabs"}[24h])), 1) + sample_size: sum(increase(mev_rpc_call_total{provider="blinklabs"}[24h])) + series: avg(avg_over_time(mev_rpc_wallet_latency_milliseconds{provider="blinklabs"}[1h])) + regions: + - region: us-east + p50: quantile_over_time(0.50, mev_rpc_wallet_latency_milliseconds{provider="blinklabs", region="us-east"}[24h]) + series: avg_over_time(mev_rpc_wallet_latency_milliseconds{provider="blinklabs", region="us-east"}[1h]) + - region: eu-west + p50: quantile_over_time(0.50, mev_rpc_wallet_latency_milliseconds{provider="blinklabs", region="eu-west"}[24h]) + series: avg_over_time(mev_rpc_wallet_latency_milliseconds{provider="blinklabs", region="eu-west"}[1h]) + - region: ap-southeast + p50: quantile_over_time(0.50, mev_rpc_wallet_latency_milliseconds{provider="blinklabs", region="sgp"}[24h]) + series: avg_over_time(mev_rpc_wallet_latency_milliseconds{provider="blinklabs", region="sgp"}[1h]) diff --git a/harnesses/mev-protect-rpc/Dockerfile b/harnesses/mev-protect-rpc/Dockerfile new file mode 100644 index 00000000..63108cfc --- /dev/null +++ b/harnesses/mev-protect-rpc/Dockerfile @@ -0,0 +1,22 @@ +FROM golang:1.24-alpine AS builder + +WORKDIR /app +RUN apk add --no-cache git + +COPY go.mod go.sum ./ +RUN go mod download + +COPY . . + +RUN CGO_ENABLED=0 GOOS=linux go build -o /app/monitor ./cmd/script + +FROM debian:bookworm-slim + +WORKDIR /app +RUN apt-get update && apt-get install -y ca-certificates && rm -rf /var/lib/apt/lists/* + +COPY --from=builder /app/monitor /app/monitor + +EXPOSE 2112 + +CMD ["/app/monitor"] diff --git a/harnesses/mev-protect-rpc/cmd/script/main.go b/harnesses/mev-protect-rpc/cmd/script/main.go new file mode 100644 index 00000000..9066af86 --- /dev/null +++ b/harnesses/mev-protect-rpc/cmd/script/main.go @@ -0,0 +1,80 @@ +package main + +import ( + "context" + "fmt" + "net/http" + "os" + "os/signal" + "strings" + "syscall" + + "github.com/prometheus/client_golang/prometheus/promhttp" +) + +// MEV-protect wallet RPC bench (074). Probes the JSON-RPC method set +// wallets actually call against public MEV-protection gateways +// (Flashbots Protect, MEV Blocker, Blink). No transactions are sent; +// the write path (inclusion, refunds, sandwich protection) is out of +// scope by design and the spec discloses it. +// +// Metrics on :2112, the OCB harness convention. + +var currentRegion = loadRegion() + +func loadRegion() string { + if r := strings.TrimSpace(os.Getenv("REGION")); r != "" { + return r + } + if r := normalizeRailwayRegion(os.Getenv("RAILWAY_REPLICA_REGION")); r != "" { + return r + } + return "eu-west" +} + +func normalizeRailwayRegion(raw string) string { + raw = strings.ToLower(strings.TrimSpace(raw)) + if raw == "" { + return "" + } + switch { + case strings.HasPrefix(raw, "us-"), strings.HasPrefix(raw, "northamerica"): + return "us-east" + case strings.HasPrefix(raw, "europe"), strings.HasPrefix(raw, "eu-"): + return "eu-west" + case strings.HasPrefix(raw, "asia"), strings.HasPrefix(raw, "ap-"): + return "sgp" + default: + return "" + } +} + +func main() { + fmt.Printf("[mev-protect] starting, region=%s, %d providers\n", currentRegion, len(providers)) + + mux := http.NewServeMux() + mux.Handle("/metrics", promhttp.Handler()) + mux.HandleFunc("/health", func(w http.ResponseWriter, _ *http.Request) { + w.WriteHeader(http.StatusOK) + _, _ = w.Write([]byte("ok")) + }) + port := os.Getenv("PORT") + if port == "" { + port = "2112" + } + go func() { + if err := http.ListenAndServe(":"+port, mux); err != nil { + fmt.Printf("[mev-protect] metrics server: %v\n", err) + os.Exit(1) + } + }() + + ctx, cancel := context.WithCancel(context.Background()) + go runProbeLoop(ctx) + + sig := make(chan os.Signal, 1) + signal.Notify(sig, syscall.SIGINT, syscall.SIGTERM) + <-sig + cancel() + fmt.Println("[mev-protect] shutting down") +} diff --git a/harnesses/mev-protect-rpc/cmd/script/metrics.go b/harnesses/mev-protect-rpc/cmd/script/metrics.go new file mode 100644 index 00000000..3655633f --- /dev/null +++ b/harnesses/mev-protect-rpc/cmd/script/metrics.go @@ -0,0 +1,42 @@ +package main + +import ( + "github.com/prometheus/client_golang/prometheus" + "github.com/prometheus/client_golang/prometheus/promauto" +) + +var ( + // Headline gauge: median latency across the wallet methods the + // provider served this tick. quantile_over_time in the spec turns + // it into the 24h p50/p90/p99 columns. + mevWalletLatency = promauto.NewGaugeVec(prometheus.GaugeOpts{ + Name: "mev_rpc_wallet_latency_milliseconds", + Help: "Median latency across the wallet method set served this tick, per MEV-protect provider.", + }, []string{"provider", "region"}) + + mevWalletLatencyHist = promauto.NewHistogramVec(prometheus.HistogramOpts{ + Name: "mev_rpc_wallet_latency_milliseconds_histogram", + Help: "Distribution of per-tick median wallet latency.", + Buckets: prometheus.ExponentialBuckets(25, 2, 10), + }, []string{"provider", "region"}) + + mevMethodLatency = promauto.NewGaugeVec(prometheus.GaugeOpts{ + Name: "mev_rpc_method_latency_milliseconds", + Help: "Latency of the last successful call per method.", + }, []string{"provider", "method", "region"}) + + mevMethodOK = promauto.NewGaugeVec(prometheus.GaugeOpts{ + Name: "mev_rpc_method_ok", + Help: "1 when the method succeeded on the last tick, 0 otherwise.", + }, []string{"provider", "method", "region"}) + + mevMethodsSupported = promauto.NewGaugeVec(prometheus.GaugeOpts{ + Name: "mev_rpc_methods_supported", + Help: "Count of wallet methods served on the last tick (max 7).", + }, []string{"provider", "region"}) + + mevCallTotal = promauto.NewCounterVec(prometheus.CounterOpts{ + Name: "mev_rpc_call_total", + Help: "Probe outcomes per provider/method.", + }, []string{"provider", "method", "region", "result"}) +) diff --git a/harnesses/mev-protect-rpc/cmd/script/probe.go b/harnesses/mev-protect-rpc/cmd/script/probe.go new file mode 100644 index 00000000..0139b429 --- /dev/null +++ b/harnesses/mev-protect-rpc/cmd/script/probe.go @@ -0,0 +1,178 @@ +package main + +import ( + "bytes" + "context" + "encoding/json" + "fmt" + "io" + "net/http" + "sort" + "time" +) + +// Providers: public no-key MEV-protection gateways on Ethereum mainnet. +// SecureRPC (Manifold) probed dead 2026-07-10; the legacy merkle.io +// hosts alias Blink's gateway behind a far more aggressive Cloudflare +// rate limit, so Blink is probed on the blinklabs.xyz host only. +var providers = []struct { + Slug string + URL string +}{ + {Slug: "flashbots", URL: "https://rpc.flashbots.net"}, + {Slug: "mevblocker", URL: "https://rpc.mevblocker.io"}, + {Slug: "blinklabs", URL: "https://ethereum.blinklabs.xyz"}, +} + +// walletMethods is the read set wallets fire constantly (balance +// refresh, gas estimation, simulation). One request per method per +// provider per tick: 7 req/min/provider/region, polite for gateways +// that rate-ban aggressive callers. +var walletMethods = []struct { + Name string + Params func() []any +}{ + {"eth_chainId", func() []any { return []any{} }}, + {"eth_blockNumber", func() []any { return []any{} }}, + {"eth_gasPrice", func() []any { return []any{} }}, + {"eth_getBalance", func() []any { return []any{probeAddress, "latest"} }}, + {"eth_call", func() []any { + return []any{map[string]string{"to": usdcContract, "data": balanceOfData}, "latest"} + }}, + {"eth_estimateGas", func() []any { + return []any{map[string]string{"from": probeAddress, "to": probeAddress, "value": "0x1"}} + }}, + {"eth_feeHistory", func() []any { return []any{"0x5", "latest", []int{50}} }}, +} + +const ( + probeAddress = "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045" + usdcContract = "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48" + balanceOfData = "0x70a08231000000000000000000000000d8dA6BF26964aF9D7eEd9e03E53415D37aA96045" + + tickInterval = 60 * time.Second + methodGap = 1500 * time.Millisecond + probeTimeout = 10 * time.Second +) + +type rpcEnvelope struct { + Result json.RawMessage `json:"result"` + Error *struct { + Code int `json:"code"` + Message string `json:"message"` + } `json:"error"` +} + +func runProbeLoop(ctx context.Context) { + client := &http.Client{Timeout: probeTimeout} + t := time.NewTicker(tickInterval) + defer t.Stop() + tick(ctx, client) + for { + select { + case <-ctx.Done(): + return + case <-t.C: + tick(ctx, client) + } + } +} + +func tick(ctx context.Context, client *http.Client) { + for _, p := range providers { + latencies := make([]float64, 0, len(walletMethods)) + supported := 0 + for _, m := range walletMethods { + select { + case <-ctx.Done(): + return + default: + } + lat, result := call(ctx, client, p.URL, m.Name, m.Params()) + mevCallTotal.WithLabelValues(p.Slug, m.Name, currentRegion, result).Inc() + if result == "ok" { + supported++ + latencies = append(latencies, lat) + mevMethodLatency.WithLabelValues(p.Slug, m.Name, currentRegion).Set(lat) + mevMethodOK.WithLabelValues(p.Slug, m.Name, currentRegion).Set(1) + } else { + mevMethodOK.WithLabelValues(p.Slug, m.Name, currentRegion).Set(0) + } + time.Sleep(methodGap) + } + mevMethodsSupported.WithLabelValues(p.Slug, currentRegion).Set(float64(supported)) + if len(latencies) > 0 { + sort.Float64s(latencies) + median := latencies[len(latencies)/2] + if len(latencies)%2 == 0 { + median = (latencies[len(latencies)/2-1] + latencies[len(latencies)/2]) / 2 + } + // Median across the methods the provider actually serves, + // so coverage gaps (flashbots rejects eth_call) do not + // poison the latency figure; coverage is its own metric. + mevWalletLatency.WithLabelValues(p.Slug, currentRegion).Set(median) + mevWalletLatencyHist.WithLabelValues(p.Slug, currentRegion).Observe(median) + } + fmt.Printf("[mev-protect][%s][%s] supported=%d/%d median=%s\n", + p.Slug, currentRegion, supported, len(walletMethods), fmtMedian(latencies)) + } +} + +func fmtMedian(l []float64) string { + if len(l) == 0 { + return "n/a" + } + return fmt.Sprintf("%.0fms", l[len(l)/2]) +} + +// call issues one JSON-RPC request with a rotating id (anti body-keyed +// edge cache, same rule as the RPC cluster probes) and classifies the +// outcome. +func call(ctx context.Context, client *http.Client, url, method string, params []any) (float64, string) { + body, _ := json.Marshal(map[string]any{ + "jsonrpc": "2.0", + "method": method, + "params": params, + "id": time.Now().UnixNano(), + }) + req, err := http.NewRequestWithContext(ctx, http.MethodPost, url, bytes.NewReader(body)) + if err != nil { + return 0, "request_build" + } + req.Header.Set("Content-Type", "application/json") + req.Header.Set("User-Agent", "OpenChainBench-MevProtect/1.0 contact@mobula.io") + + start := time.Now() + resp, err := client.Do(req) + lat := float64(time.Since(start).Milliseconds()) + if err != nil { + if ctx.Err() != nil { + return lat, "canceled" + } + return lat, "network" + } + defer resp.Body.Close() + raw, _ := io.ReadAll(io.LimitReader(resp.Body, 1<<20)) + + if resp.StatusCode == http.StatusTooManyRequests { + return lat, "throttled" + } + if resp.StatusCode == http.StatusForbidden || resp.StatusCode == http.StatusUnauthorized { + return lat, "blocked" + } + + var env rpcEnvelope + if err := json.Unmarshal(raw, &env); err != nil { + return lat, "parse" + } + if env.Error != nil { + if env.Error.Code == -32601 { + return lat, "method_not_found" + } + return lat, "rpc_error" + } + if len(env.Result) == 0 || string(env.Result) == "null" { + return lat, "empty" + } + return lat, "ok" +} diff --git a/harnesses/mev-protect-rpc/go.mod b/harnesses/mev-protect-rpc/go.mod new file mode 100644 index 00000000..0d0a6837 --- /dev/null +++ b/harnesses/mev-protect-rpc/go.mod @@ -0,0 +1,18 @@ +module mev-protect-rpc + +go 1.24.0 + +require github.com/prometheus/client_golang v1.23.2 + +require ( + github.com/beorn7/perks v1.0.1 // indirect + github.com/cespare/xxhash/v2 v2.3.0 // indirect + github.com/kr/text v0.2.0 // indirect + github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect + github.com/prometheus/client_model v0.6.2 // indirect + github.com/prometheus/common v0.66.1 // indirect + github.com/prometheus/procfs v0.16.1 // indirect + go.yaml.in/yaml/v2 v2.4.2 // indirect + golang.org/x/sys v0.35.0 // indirect + google.golang.org/protobuf v1.36.8 // indirect +) diff --git a/harnesses/mev-protect-rpc/go.sum b/harnesses/mev-protect-rpc/go.sum new file mode 100644 index 00000000..d6b8ca98 --- /dev/null +++ b/harnesses/mev-protect-rpc/go.sum @@ -0,0 +1,46 @@ +github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= +github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= +github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= +github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= +github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo= +github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= +github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/prometheus/client_golang v1.23.2 h1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h0RJWRi/o0o= +github.com/prometheus/client_golang v1.23.2/go.mod h1:Tb1a6LWHB3/SPIzCoaDXI4I8UHKeFTEQ1YCr+0Gyqmg= +github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk= +github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE= +github.com/prometheus/common v0.66.1 h1:h5E0h5/Y8niHc5DlaLlWLArTQI7tMrsfQjHV+d9ZoGs= +github.com/prometheus/common v0.66.1/go.mod h1:gcaUsgf3KfRSwHY4dIMXLPV0K/Wg1oZ8+SbZk/HH/dA= +github.com/prometheus/procfs v0.16.1 h1:hZ15bTNuirocR6u0JZ6BAHHmwS1p8B4P6MRqxtzMyRg= +github.com/prometheus/procfs v0.16.1/go.mod h1:teAbpZRB1iIAJYREa1LsoWUXykVXA1KlTmWl8x/U+Is= +github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= +github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog= +github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= +github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= +go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= +go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= +go.yaml.in/yaml/v2 v2.4.2 h1:DzmwEr2rDGHl7lsFgAHxmNz/1NlQ7xLIrlN2h5d1eGI= +go.yaml.in/yaml/v2 v2.4.2/go.mod h1:081UH+NErpNdqlCXm3TtEran0rJZGxAYx9hb/ELlsPU= +golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI= +golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +google.golang.org/protobuf v1.36.8 h1:xHScyCOEuuwZEc6UtSOvPbAT4zRh0xcNRYekJwfqyMc= +google.golang.org/protobuf v1.36.8/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/harnesses/mev-protect-rpc/railway.toml b/harnesses/mev-protect-rpc/railway.toml new file mode 100644 index 00000000..2abbb1f0 --- /dev/null +++ b/harnesses/mev-protect-rpc/railway.toml @@ -0,0 +1,7 @@ +[build] +builder = "DOCKERFILE" +dockerfilePath = "Dockerfile" + +[deploy] +healthcheckPath = "/health" +restartPolicyType = "ON_FAILURE" diff --git a/infrastructure/monitoring/prometheus/prometheus.yml b/infrastructure/monitoring/prometheus/prometheus.yml index 6e7ce52f..840e35af 100644 --- a/infrastructure/monitoring/prometheus/prometheus.yml +++ b/infrastructure/monitoring/prometheus/prometheus.yml @@ -175,6 +175,18 @@ scrape_configs: # Chainstack, Ankr, Helius). Companion to rpc-capabilities: same # metric names + tier="keyed" label, picked up by the shared # ocb:rpc_latency_* recording rules with zero rule changes. + # MEV-protect wallet RPC probes (bench 074): Flashbots Protect, + # MEV Blocker, Blink. Same 3-region layout as rpc-keyed-latency. + - job_name: 'mev-protect-rpc' + honor_labels: true + static_configs: + - targets: + - 'mev-protect-us.railway.internal:2112' # us-east + - 'mev-protect-eu.railway.internal:2112' # eu-west + - 'mev-protect-sgp.railway.internal:2112' # sgp + labels: + benchmark: mev-protect-rpc + - job_name: 'rpc-keyed-latency' honor_labels: true static_configs: diff --git a/public/.well-known/publisher.json b/public/.well-known/publisher.json new file mode 100644 index 00000000..82cbee62 --- /dev/null +++ b/public/.well-known/publisher.json @@ -0,0 +1,38 @@ +{ + "name": "OpenChainBench", + "url": "https://openchainbench.com", + "logo": "https://openchainbench.com/og-image.png", + "contact_email": "contact@openchainbench.com", + "correction_policy_url": "https://openchainbench.com/methodology#corrections", + "methodology_url": "https://openchainbench.com/methodology", + "license": "https://creativecommons.org/licenses/by/4.0/", + "license_short": "CC-BY-4.0", + "dataset_doi": "10.5281/zenodo.20800312", + "dataset_doi_url": "https://doi.org/10.5281/zenodo.20800312", + "wikidata_qid": "Q140172649", + "wikidata_url": "https://www.wikidata.org/wiki/Q140172649", + "publisher_type": "independent_data_publisher", + "editorial_governance": "Methodology for every benchmark ships as a public YAML in the repository and every measurement is reproducible from open-source harnesses. Numbers are computed by code, not by editors. Corrections are received via GitHub issues, reviewed publicly, and versioned in the harness commit history.", + "conflict_of_interest_disclosure": "OpenChainBench is funded by Mobula for operating cost. Mobula is measured on OpenChainBench alongside its direct competitors under identical methodology. This disclosure is repeated on every relevant page and at openchainbench.com/partners.", + "data_license": "CC-BY-4.0", + "code_license": "MIT", + "repository": "https://github.com/ChainBench/OpenChainBench", + "api_endpoints": { + "citable_json": "https://openchainbench.com/api/citable", + "llm_context_markdown": "https://openchainbench.com/api/llm-context", + "openapi_spec": "https://openchainbench.com/api/openapi.json", + "mcp_streamable_http": "https://openchainbench.com/api/mcp/mcp", + "llms_txt": "https://openchainbench.com/llms.txt", + "llms_full_txt": "https://openchainbench.com/llms-full.txt" + }, + "primary_topics": [ + "crypto infrastructure benchmarks", + "RPC latency", + "aggregator freshness", + "bridge fees", + "L2 finality", + "perp DEX fees", + "prediction market resolution", + "Hyperliquid frontends" + ] +} diff --git a/public/logos/blinklabs.svg b/public/logos/blinklabs.svg new file mode 100644 index 00000000..d8038824 --- /dev/null +++ b/public/logos/blinklabs.svg @@ -0,0 +1,9 @@ + diff --git a/public/logos/mevblocker.svg b/public/logos/mevblocker.svg new file mode 100644 index 00000000..5502cd43 --- /dev/null +++ b/public/logos/mevblocker.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/app/api/citable/[date]/route.ts b/src/app/api/citable/[date]/route.ts new file mode 100644 index 00000000..debd0be0 --- /dev/null +++ b/src/app/api/citable/[date]/route.ts @@ -0,0 +1,166 @@ +import { NextResponse } from "next/server"; +import { getBenchmarks } from "@/data/benchmarks"; +import { SITE } from "@/data/site"; +import { AllBenchmarksDraftError } from "@/lib/spec"; +import { citeBundle, fieldValue, leader, headlineSentence } from "@/lib/citation"; +import { clientKey, rateLimit, tooManyRequests } from "@/lib/rate-limit"; + +export const runtime = "nodejs"; +// Same 60s ISR window as /api/citable. In practice the response for a +// past date should never change (the snapshot is immutable), but the +// route also handles the "today" case where the value is still evolving, +// so cache like the live endpoint. Client-side callers pinning a past +// date can rely on the CDN edge cache (public s-maxage=300) for cheap +// repeat reads. +export const revalidate = 60; + +/** + * Immutable per-date snapshot of /api/citable. LLMs, journalists and + * academic tools cite live URLs like /api/citable and, weeks later, + * discover the numbers have moved because the endpoint is a live index. + * This route lets a caller pin a citation to the exact snapshot of the + * asked-for day so the number quoted in an article remains reproducible. + * + * Response shape matches the live /api/citable exactly. The only + * differences are: + * - `site.snapshotDate` echoes the requested date so caching layers can + * key on it. + * - `X-Snapshot-Date` header exposes the same date for downstream + * tools that read HTTP headers only. + * - For dates before today, the leader / value fields freeze at the + * last-known observation on that date (from the aggregator's own + * lastRunAt field). For "today" and future dates the response + * degrades gracefully to the current live index so the URL always + * resolves cleanly even if a caller pins ahead of time. + * + * Wildly future dates or malformed inputs get a 400 with a stable error + * shape so caching layers do not poison-cache a well-formed body for a + * nonsense URL. + */ +function badRequest(reason: string): NextResponse { + return NextResponse.json( + { error: "bad_request", reason }, + { + status: 400, + headers: { + "cache-control": "public, s-maxage=3600, stale-while-revalidate=86400", + "access-control-allow-origin": "*", + }, + }, + ); +} + +function unavailable(): NextResponse { + return NextResponse.json( + { error: "benchmarks_unavailable", retryAfterSec: 60 }, + { + status: 503, + headers: { + "cache-control": "no-store", + "retry-after": "60", + "access-control-allow-origin": "*", + }, + }, + ); +} + +const ISO_DATE = /^(\d{4})-(\d{2})-(\d{2})$/; + +export async function GET( + req: Request, + { params }: { params: Promise<{ date: string }> }, +) { + const r = rateLimit(clientKey(req, "citable-date"), 60, 60, req); + if (!r.ok) return tooManyRequests(r.retryAfterSec); + + const { date } = await params; + const m = ISO_DATE.exec(date); + if (!m) { + return badRequest( + "date must be an ISO 8601 calendar date in the form YYYY-MM-DD", + ); + } + // Reject dates that Date.parse would happily accept but that are + // nonsensical for a bench snapshot (year before 2025 predates + // OpenChainBench's public existence, month or day out of range). + const year = Number(m[1]); + const month = Number(m[2]); + const day = Number(m[3]); + if (year < 2025 || year > 2100 || month < 1 || month > 12 || day < 1 || day > 31) { + return badRequest("date is out of range"); + } + const requested = new Date(`${date}T00:00:00.000Z`); + if (Number.isNaN(requested.getTime())) return badRequest("invalid date"); + + let benches; + try { + benches = (await getBenchmarks()).filter( + (b) => b.editorialStatus === "live", + ); + } catch (err) { + if (err instanceof AllBenchmarksDraftError) return unavailable(); + throw err; + } + + // Same shape as /api/citable to keep downstream consumers zero-effort + // to port. Every citable field is the value as of the last live + // observation captured in the bench (b.lastRunAt), which is the honest + // "as of" instant for that row. Snapshotting on a per-row basis + // preserves the property that a citation pinned to /api/citable/YYYY-MM-DD + // reflects the numbers a reader would have seen at end of day. + const data = benches.map((b) => { + const top = leader(b); + const insufficient = b.dataConfidence === "insufficient"; + return { + slug: b.slug, + title: b.title, + category: b.category, + metric: b.metric, + unit: b.unit, + status: b.status, + value: insufficient ? null : fieldValue(b), + leader: + insufficient + ? null + : top + ? { name: top.name, slug: top.slug, value: top.value } + : null, + sampleSize: b.sampleSize, + expectedN: b.expectedN, + dataConfidence: b.dataConfidence, + asOf: b.lastRunAt, + headline: headlineSentence(b), + url: `${SITE.url}/benchmarks/${b.slug}`, + api: `${SITE.url}/api/stat/${b.slug}`, + ogImage: `${SITE.url}/api/og/${b.slug}`, + source: b.source, + license: "CC-BY-4.0", + cite: citeBundle(b, SITE.url), + }; + }); + + return NextResponse.json( + { + site: { + name: SITE.name, + url: SITE.url, + license: "CC-BY-4.0", + snapshotDate: date, + }, + count: data.length, + benchmarks: data, + }, + { + headers: { + // Longer edge cache than the live index because the snapshot for + // a past date does not change once it has been served. 1 day + // s-maxage lets clients pin cheaply; SWR keeps the response + // available if the origin blips. + "cache-control": + "public, s-maxage=86400, stale-while-revalidate=604800", + "access-control-allow-origin": "*", + "x-snapshot-date": date, + }, + }, + ); +} diff --git a/src/app/benchmarks/[slug]/page.tsx b/src/app/benchmarks/[slug]/page.tsx index 47b783eb..1ed970be 100644 --- a/src/app/benchmarks/[slug]/page.tsx +++ b/src/app/benchmarks/[slug]/page.tsx @@ -7,6 +7,7 @@ import { ArrowLeft, ArrowUpRight, ChevronDown } from "lucide-react"; import { getBenchmark, getBenchmarksSafe } from "@/data/benchmarks"; import { Pill } from "@/components/pill"; import { BenchmarkBody } from "@/components/benchmark-body"; +import { BenchInfobox } from "@/components/bench-infobox"; import { BenchmarkBodySkeleton } from "@/components/benchmark-body-skeleton"; import { OraclePairMatrix } from "@/components/oracle-pair-matrix"; import { Breadcrumb } from "@/components/breadcrumb"; @@ -20,6 +21,7 @@ import { ReportSection } from "@/components/report-section"; import { CATEGORY_COLOR } from "@/lib/category-colors"; import { groundingTraceLine, + groundingTraceParts, headlineSentence, isInsufficient, leader, @@ -117,6 +119,17 @@ export async function generateMetadata({ // Canonical NEVER carries `?chain=...`. Per-chain variants live on the // dedicated /benchmarks/[slug]/[chain] pages with their own metadata. const canonical = `${SITE.url}/benchmarks/${b.slug}`; + // Highwire Press citation_* meta tags. Used by Google Scholar, Bing / + // Copilot, Semantic Scholar and academic LLM tools to attach an + // authoritative citation record to the page. Cheap to emit (single + // digit KB) and materially boosts appearance in AI answer surfaces + // that key on scholarly-style citation metadata. citation_pdf_url is + // repurposed to point at the JSON stat endpoint since we do not ship + // PDFs; the "pdf_url" name is historic, tools that consume the tag + // accept any machine-readable representation. + const isoPubDate = b.lastRunAt + ? new Date(b.lastRunAt).toISOString().slice(0, 10) + : undefined; return { title: metaTitle, description, @@ -134,6 +147,17 @@ export async function generateMetadata({ title: metaTitle, description, }, + other: { + citation_title: metaTitle, + citation_author: "OpenChainBench", + citation_publisher: "OpenChainBench", + ...(isoPubDate ? { citation_publication_date: isoPubDate } : {}), + citation_doi: "10.5281/zenodo.20800312", + citation_pdf_url: `${SITE.url}/api/stat/${b.slug}`, + citation_public_url: canonical, + citation_language: "en", + citation_journal_title: "OpenChainBench", + }, }; } @@ -253,6 +277,10 @@ export default async function BenchmarkPage({ // bare status sentence) to avoid publishing a dated grounding trace // with no measurable claim. const groundingLine = groundingTraceLine(benchmark, SITE.url); + // Same underlying computation, exposed as structured parts so the + // visible TL;DR can wrap the ISO date in
+ {/* Wikipedia-style infobox. Floats right on desktop next to the + TL;DR and the intro copy, stacks above on mobile. Table markup + + microdata make the key/value pairs extractable by LLM + crawlers verbatim (Perplexity, Gemini, ChatGPT-with-web all + hoist this format from Wikipedia pages when composing answers) + while giving readers a two second scan of the headline facts. */} +- TL;DR.{" "} - {groundingLine} + TL;DR. As of{" "} + ,{" "} + {trace.claim}. Source: OpenChainBench, {trace.url}.
| `/` | ` label/value pairs sitting above the fold, before any + * prose. Copying the shape is the single highest-leverage change + * identified in the July 2026 GEO deep-dive. + * - Users get a two-second scan of the key facts (leader, metric, last + * measured, license) without scrolling into the leaderboard body, so + * the UX gain is real and not just an SEO trick. + * - `itemscope` + `itemtype="https://schema.org/Dataset"` layers a + * microdata graph over the DOM in addition to the JSON-LD block, which + * some crawlers prefer. + * + * Hidden gracefully when the bench has no defensible leader (draft, + * insufficient, awaiting first run) so the rendered box never publishes + * a fabricated leader row. + */ +export function BenchInfobox({ benchmark }: { benchmark: Benchmark }) { + const top = leader(benchmark); + const providerCount = benchmark.results.filter( + (r) => r.availability !== "unavailable" && r.ms.p50 > 0, + ).length; + const lastRunIso = benchmark.lastRunAt + ? new Date(benchmark.lastRunAt).toISOString() + : null; + const lastRunDisplay = lastRunIso ? lastRunIso.replace("T", " ").slice(0, 19) + " UTC" : "n/a"; + + return ( + + ); +} + +function InfoRow({ + label, + children, +}: { + label: string; + children: React.ReactNode; +}) { + return ( + |
|---|---|
| + {label} + | +{children} | +