diff --git a/benchmarks/mev-protect-rpc.yml b/benchmarks/mev-protect-rpc.yml index e490e236..cd4751a4 100644 --- a/benchmarks/mev-protect-rpc.yml +++ b/benchmarks/mev-protect-rpc.yml @@ -46,7 +46,7 @@ methodology: - "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." + - "Success rate counts transport failures only (timeouts, throttling, network); a method a gateway refuses by policy is a coverage gap shown in the coverage panel, not an outage. Failures increment mev_rpc_call_total{result}; 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." @@ -84,7 +84,7 @@ dimensions: metric_panels: - id: coverage label: Method coverage - metric: avg(mev_rpc_methods_supported) + metric: avg(mev_rpc_methods_supported{benchmark="mev-protect-rpc"}) label_key: provider unit: count higher_is_better: true @@ -100,7 +100,7 @@ providers: 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) + success: sum(increase(mev_rpc_call_total{provider="flashbots",result="ok"}[24h])) / clamp_min(sum(increase(mev_rpc_call_total{provider="flashbots",result!~"blocked|method_not_found"}[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: @@ -123,7 +123,7 @@ providers: 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) + success: sum(increase(mev_rpc_call_total{provider="mevblocker",result="ok"}[24h])) / clamp_min(sum(increase(mev_rpc_call_total{provider="mevblocker",result!~"blocked|method_not_found"}[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: @@ -146,7 +146,7 @@ providers: 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) + success: sum(increase(mev_rpc_call_total{provider="blinklabs",result="ok"}[24h])) / clamp_min(sum(increase(mev_rpc_call_total{provider="blinklabs",result!~"blocked|method_not_found"}[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: diff --git a/benchmarks/perp-open-interest.yml b/benchmarks/perp-open-interest.yml index 6b07347f..64affdb5 100644 --- a/benchmarks/perp-open-interest.yml +++ b/benchmarks/perp-open-interest.yml @@ -38,6 +38,7 @@ abstract: | Higher is better. methodology: + - "Convention: open interest counts each contract once (one side), the standard used by CME, Binance and Coinglass, and the only convention comparable across venues. Aggregators can differ: DefiLlama doubles Lighter and Ostium (long plus short), folds 1,682 options markets into Paradex, and adds HIP-3 sub-dex OI to Hyperliquid. Where our number is about half of theirs, that is the convention gap, not missing data; five venues where conventions align match DefiLlama within 2 percent." - "Cadence: every 5 minutes per venue in parallel, 10 second timeout per request." - "Hyperliquid: info metaAndAssetCtxs, openInterest summed across all assets and priced in USD using the venue's own mark price." - "Aster: fapi openInterest endpoint per instrument, summed across all listed USDT perps." diff --git a/benchmarks/pm-data-freshness.yml b/benchmarks/pm-data-freshness.yml index ffbb7602..8f6836af 100644 --- a/benchmarks/pm-data-freshness.yml +++ b/benchmarks/pm-data-freshness.yml @@ -45,6 +45,7 @@ abstract: | yet cover Kalshi venue data). methodology: + - "Anchor rows: on each venue tab the venue itself (Polymarket, Kalshi) is the canonical T0, so its row reads as the harness network round trip, near zero by construction. It is kept on the board to document the reference point, not as a competing feed; the comparison rows are the data providers measured against it." - "Polymarket T0. `wss://ws-subscriptions-clob.polymarket.com/ws/market` is public, no auth, sub 50ms gateway publish latency from EU West. Cross correlation uses (conditionId, priceUSD rounded to 3 decimals, sizeUSD micros, 5s time bucket)." - "Kalshi T0. `https://api.elections.kalshi.com/v1/social/trades` (REST), polled every 5s with cursor pagination. Every trade carries `create_date` at microsecond precision; that timestamp is the canonical T0. Poll cadence affects only correlation timing, not the freshness number. The official Kalshi WebSocket needs RSA PSS signed headers from a US KYC account and 403s from non US IPs, not viable for a public benchmark." - "Mobula PM WebSocket. `wss://pm-api-prod-eu.mobula.io`. Auth via API key in the subscribe payload. Cloudflare on the gateway requires a browser User Agent on the upgrade request, default Go HTTP UA is silently filtered. Covers Polymarket today, Kalshi coverage is not yet shipped." diff --git a/benchmarks/polymarket-resolution-delay.yml b/benchmarks/polymarket-resolution-delay.yml index 432c2e98..9c058e1c 100644 --- a/benchmarks/polymarket-resolution-delay.yml +++ b/benchmarks/polymarket-resolution-delay.yml @@ -132,7 +132,7 @@ providers: p50: 1000 * histogram_quantile(0.50, sum by (le) (pmres_resolution_delay_seconds_bucket)) p90: 1000 * histogram_quantile(0.90, sum by (le) (pmres_resolution_delay_seconds_bucket)) p99: 1000 * histogram_quantile(0.99, sum by (le) (pmres_resolution_delay_seconds_bucket)) - mean: sum(pmres_resolution_delay_seconds_sum) / sum(pmres_resolution_delay_seconds_count) + mean: 1000 * sum(pmres_resolution_delay_seconds_sum) / sum(pmres_resolution_delay_seconds_count) success: clamp_max(sum(pmres_resolution_delay_seconds_bucket{le="7200"}) / sum(pmres_resolution_delay_seconds_count), 1) sample_size: sum(pmres_resolutions_total) series: 1000 * histogram_quantile(0.50, sum by (le) (pmres_resolution_delay_seconds_bucket)) @@ -145,7 +145,7 @@ providers: p50: 1000 * histogram_quantile(0.50, sum by (le) (pmres_resolution_delay_seconds_bucket{category="sports"})) p90: 1000 * histogram_quantile(0.90, sum by (le) (pmres_resolution_delay_seconds_bucket{category="sports"})) p99: 1000 * histogram_quantile(0.99, sum by (le) (pmres_resolution_delay_seconds_bucket{category="sports"})) - mean: sum(pmres_resolution_delay_seconds_sum{category="sports"}) / sum(pmres_resolution_delay_seconds_count{category="sports"}) + mean: 1000 * sum(pmres_resolution_delay_seconds_sum{category="sports"}) / sum(pmres_resolution_delay_seconds_count{category="sports"}) success: clamp_max(sum(pmres_resolution_delay_seconds_bucket{le="7200",category="sports"}) / sum(pmres_resolution_delay_seconds_count{category="sports"}), 1) sample_size: sum(pmres_resolutions_total{category="sports"}) series: 1000 * histogram_quantile(0.50, sum by (le) (pmres_resolution_delay_seconds_bucket{category="sports"})) @@ -158,7 +158,7 @@ providers: p50: 1000 * histogram_quantile(0.50, sum by (le) (pmres_resolution_delay_seconds_bucket{category="crypto"})) p90: 1000 * histogram_quantile(0.90, sum by (le) (pmres_resolution_delay_seconds_bucket{category="crypto"})) p99: 1000 * histogram_quantile(0.99, sum by (le) (pmres_resolution_delay_seconds_bucket{category="crypto"})) - mean: sum(pmres_resolution_delay_seconds_sum{category="crypto"}) / sum(pmres_resolution_delay_seconds_count{category="crypto"}) + mean: 1000 * sum(pmres_resolution_delay_seconds_sum{category="crypto"}) / sum(pmres_resolution_delay_seconds_count{category="crypto"}) success: clamp_max(sum(pmres_resolution_delay_seconds_bucket{le="7200",category="crypto"}) / sum(pmres_resolution_delay_seconds_count{category="crypto"}), 1) sample_size: sum(pmres_resolutions_total{category="crypto"}) series: 1000 * histogram_quantile(0.50, sum by (le) (pmres_resolution_delay_seconds_bucket{category="crypto"})) @@ -171,7 +171,7 @@ providers: p50: 1000 * histogram_quantile(0.50, sum by (le) (pmres_resolution_delay_seconds_bucket{category="politics"})) p90: 1000 * histogram_quantile(0.90, sum by (le) (pmres_resolution_delay_seconds_bucket{category="politics"})) p99: 1000 * histogram_quantile(0.99, sum by (le) (pmres_resolution_delay_seconds_bucket{category="politics"})) - mean: sum(pmres_resolution_delay_seconds_sum{category="politics"}) / sum(pmres_resolution_delay_seconds_count{category="politics"}) + mean: 1000 * sum(pmres_resolution_delay_seconds_sum{category="politics"}) / sum(pmres_resolution_delay_seconds_count{category="politics"}) success: clamp_max(sum(pmres_resolution_delay_seconds_bucket{le="7200",category="politics"}) / sum(pmres_resolution_delay_seconds_count{category="politics"}), 1) sample_size: sum(pmres_resolutions_total{category="politics"}) series: 1000 * histogram_quantile(0.50, sum by (le) (pmres_resolution_delay_seconds_bucket{category="politics"})) @@ -184,7 +184,7 @@ providers: p50: 1000 * histogram_quantile(0.50, sum by (le) (pmres_resolution_delay_seconds_bucket{category="other"})) p90: 1000 * histogram_quantile(0.90, sum by (le) (pmres_resolution_delay_seconds_bucket{category="other"})) p99: 1000 * histogram_quantile(0.99, sum by (le) (pmres_resolution_delay_seconds_bucket{category="other"})) - mean: sum(pmres_resolution_delay_seconds_sum{category="other"}) / sum(pmres_resolution_delay_seconds_count{category="other"}) + mean: 1000 * sum(pmres_resolution_delay_seconds_sum{category="other"}) / sum(pmres_resolution_delay_seconds_count{category="other"}) success: clamp_max(sum(pmres_resolution_delay_seconds_bucket{le="7200",category="other"}) / sum(pmres_resolution_delay_seconds_count{category="other"}), 1) sample_size: sum(pmres_resolutions_total{category="other"}) series: 1000 * histogram_quantile(0.50, sum by (le) (pmres_resolution_delay_seconds_bucket{category="other"})) diff --git a/src/app/benchmarks/[slug]/page.tsx b/src/app/benchmarks/[slug]/page.tsx index 1ed970be..80f60996 100644 --- a/src/app/benchmarks/[slug]/page.tsx +++ b/src/app/benchmarks/[slug]/page.tsx @@ -463,12 +463,15 @@ export default async function BenchmarkPage({ {benchmark.subtitle}

- {/* 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. */} + {/* Compact "At a glance" card. Rendered as a native
+ element so it collapses to a one line summary on click while + still shipping the full microdata + key/value pairs inside the + collapsed body (LLM crawlers see the content whatever the + visual state). Sits between the subtitle and the TL;DR + grounding trace so a reader has: headline claim (H1) → context + (subtitle) → at-a-glance facts (this card) → canonical + grounding sentence (TL;DR) before entering the leaderboard + body. */} {/* Visible grounding-trace TL;DR. Renders the same canonical line diff --git a/src/app/products/[slug]/page.tsx b/src/app/products/[slug]/page.tsx index 1ce586d2..87c6d1a5 100644 --- a/src/app/products/[slug]/page.tsx +++ b/src/app/products/[slug]/page.tsx @@ -69,11 +69,11 @@ export async function generateMetadata({ // crawlers that peek at the response before the 308 fires still see the // right canonical target. // Merkle rebranded to Blink Labs and went keyed-only; the provider was - // fully delisted 2026-07-10 from the keyless RPC benches. 308 to the - // Blink Labs product page (bench 074 lists them under the new name) so - // the old URL's link equity lands on the successor. + // fully delisted 2026-07-10 so this page has no data left. 308 to the + // capabilities bench for now; flip to /products/blinklabs in the same PR + // that removes mev-protect-rpc from REMOVED_BENCH_SLUGS. if (slug === "merkle") { - permanentRedirect("/products/blinklabs"); + permanentRedirect("/benchmarks/rpc-capabilities"); } if (await isHlBuilderWithHistory(slug)) { const canonicalUrl = `${SITE.url}/hyperliquid/${slug}`; diff --git a/src/components/bench-infobox.tsx b/src/components/bench-infobox.tsx index 78697e08..d1f45286 100644 --- a/src/components/bench-infobox.tsx +++ b/src/components/bench-infobox.tsx @@ -4,26 +4,24 @@ import { fmtUnit } from "@/lib/format"; import { SITE } from "@/data/site"; /** - * Wikipedia-style infobox for benchmark pages. A compact `` of - * key/value pairs that sits directly under the H1/subtitle and above the - * TL;DR grounding trace. + * Compact "At a glance" card that sits under the H1/subtitle on every + * benchmark page. Wrapped in a native
element so it collapses + * to a one-line summary and expands to the full key/value grid on click. * * Why this shape: - * - LLM crawlers (Perplexity, Gemini, ChatGPT-with-web) extract Wikipedia - * infoboxes almost verbatim because they are `
` markup with - * ` - - - + + {children} + ); } diff --git a/src/lib/removed-benches.ts b/src/lib/removed-benches.ts index 32de4dca..84bca2c9 100644 --- a/src/lib/removed-benches.ts +++ b/src/lib/removed-benches.ts @@ -37,6 +37,7 @@ export const REMOVED_BENCH_SLUGS = new Set([ "monad-rpc", "megaeth-rpc", "rpc-keyed-latency", + "mev-protect-rpc", "explorer-chain-coverage", "portfolio-chain-coverage", ]); diff --git a/src/lib/spec.ts b/src/lib/spec.ts index d2a59e88..c68633d0 100644 --- a/src/lib/spec.ts +++ b/src/lib/spec.ts @@ -267,7 +267,7 @@ const loadBenchmarkUnfilteredCached = unstable_cache( // level on VERCEL_ENV=production). Bench SET now differs per env, so // the env is part of the cache key to keep prod and preview entries // from colliding. - ["bench-unfiltered-v23", process.env.VERCEL_ENV === "production" ? "prod" : "all"], + ["bench-unfiltered-v24", process.env.VERCEL_ENV === "production" ? "prod" : "all"], { revalidate: 300, tags: ["benchmarks"] }, ); @@ -416,7 +416,7 @@ const loadAllBenchmarksCached = unstable_cache( // 055-066; sitemap/citable/products must pick up the new slugs). // v25: bumped with bench-unfiltered-v21 (+bench 067 portfolio-chain-coverage). // v26: bumped with bench-unfiltered-v22 (+bench 068 explorer-chain-coverage). - // v27: bumped with bench-unfiltered-v23 (prod-only bench gate); env in key. + // v27: bumped with bench-unfiltered-v24 (prod-only bench gate); env in key. ["all-benchmarks-v27", process.env.VERCEL_ENV === "production" ? "prod" : "all"], { revalidate: 300, tags: ["benchmarks"] }, ); @@ -492,7 +492,7 @@ const loadBenchmarkFiltered = unstable_cache( // 055-066). // v13: bumped with bench-unfiltered-v21 (+bench 067 portfolio-chain-coverage). // v14: bumped with bench-unfiltered-v22 (+bench 068 explorer-chain-coverage). - // v15: bumped with bench-unfiltered-v23 (prod-only bench gate); env in key. + // v15: bumped with bench-unfiltered-v24 (prod-only bench gate); env in key. ["bench-filters-v15", process.env.VERCEL_ENV === "production" ? "prod" : "all"], { revalidate: 300, tags: ["benchmarks"] } );
`/`` 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. + * - Language models parse the content inside
whether it is + * visually expanded or not, so the LLM-grounding pattern (Wikipedia + * infobox-style key/value pairs directly under the H1) is preserved + * in either state. + * - Human readers get a scannable one-line teaser that a click reveals + * into a compact card. The default is expanded so first-time visitors + * see the facts without having to interact. + * - itemscope + itemtype layer a schema.org Dataset microdata graph + * over the DOM in addition to the JSON-LD block so crawlers that + * parse microdata pick up the same facts. * - * Hidden gracefully when the bench has no defensible leader (draft, - * insufficient, awaiting first run) so the rendered box never publishes - * a fabricated leader row. + * Renders nothing when the bench has no defensible leader (draft, + * insufficient, awaiting) so we never publish a fabricated leader row. */ export function BenchInfobox({ benchmark }: { benchmark: Benchmark }) { const top = leader(benchmark); @@ -33,54 +31,103 @@ export function BenchInfobox({ benchmark }: { benchmark: Benchmark }) { const lastRunIso = benchmark.lastRunAt ? new Date(benchmark.lastRunAt).toISOString() : null; - const lastRunDisplay = lastRunIso ? lastRunIso.replace("T", " ").slice(0, 19) + " UTC" : "n/a"; + const lastRunDisplay = lastRunIso + ? lastRunIso.replace("T", " ").slice(0, 19) + " UTC" + : "n/a"; return (
); } -function InfoRow({ +function InfoPair({ label, children, + className, }: { label: string; children: React.ReactNode; + className?: string; }) { return ( -
+ {label} - {children}