From a4544975568b1807755fd8f65c90b18714f29e34 Mon Sep 17 00:00:00 2001 From: Florent Tapponnier Date: Tue, 9 Jun 2026 22:36:10 +0200 Subject: [PATCH 1/3] feat(about): add Independence and disclosure section Addresses the Mobula conflict-of-interest gap flagged in external SEO audits and required for the 'neutral methodology arbiter' positioning. New section iii on the About page declares: - Mobula funds the project + why - Mobula competes in 5 benchmarks and leads 2 (made explicit above the fold) - Three safeguards: open Prometheus data, open harness source, public methodology review (with the Coinpaprika story as the live example) - No paid tier, no sponsored slots, no token policy - Hosting funding source E-E-A-T signal: makes the conflict legible, links the recent methodology review PRs (#349, #352, #353), and gives Search Console / AI citation engines a concrete reason to treat the bench as authoritative. --- src/app/about/page.tsx | 28 +++++++++++++++++++++++++--- 1 file changed, 25 insertions(+), 3 deletions(-) diff --git a/src/app/about/page.tsx b/src/app/about/page.tsx index 07bda322..202c7bd5 100644 --- a/src/app/about/page.tsx +++ b/src/app/about/page.tsx @@ -52,7 +52,29 @@ export default function AboutPage() { 13 live benchmarks. ~150 (provider × chain) probe pairs. Every metric is queryable on the public Prometheus and reproducible from the harness source.

- + +

+ OpenChainBench is funded by Mobula because we needed honest infrastructure benchmark data internally and saw no neutral source existed. The site is open-sourced because the industry needs it. +

+

+ Mobula competes in five live benchmarks: aggregator head lag, bridge fee, bridge quote latency, network coverage and metadata coverage. Mobula currently leads aggregator head lag and bridge quote latency. This is a real conflict of interest and we want it visible above the fold, not buried. +

+

+ Three safeguards stand against the conflict: +

+
    +
  • ·Open Prometheus data. Every number on the site is a literal quantile_over_time query. Anyone can hit our /api/citable or /api/stat/<slug> endpoint and re-derive the leaderboard with their own aggregation. If Mobula were inflating its rank, the raw data would show it.
  • +
  • ·Open harness source. Every harness is on GitHub under harnesses/. Clone, run docker compose up, your /metrics endpoint emits the same numbers ours does within 30 seconds.
  • +
  • ·Public methodology review. We invite external review and ship the fixes publicly. In June 2026 the Coinpaprika data team flagged four issues on the stablecoin peg and oracle deviation benches. We shipped three of them in pull requests #349, #352 and #353 within twenty-four hours and pushed back on the fourth with citations to CME, Chainlink and CoinGecko convention.
  • +
+

+ We do not run a paid tier. We do not sell ranking slots. We do not take provider sponsorship in exchange for inclusion. There is no token. If you spot any deviation from this policy, file a private security advisory and we will treat it as the integrity incident it would be. +

+

+ Hosting and infrastructure costs are paid by Mobula. The site runs on Vercel; harnesses run on Railway. We are open to grant funding that preserves editorial independence, but we will not accept funding from any party we benchmark. +

+ +

Every benchmark is a YAML spec plus a harness. The spec describes what to measure, which providers, which Prometheus queries hold the numbers; the harness runs continuously on Railway and exposes those metrics. A single shared Prometheus scrapes every harness; the site queries Prometheus directly and re-renders every minute. Every provider is rendered with equal visual weight. readers do their own ranking.

@@ -62,7 +84,7 @@ export default function AboutPage() { walks through the steps. New providers, new metrics, new chains. all welcome via pull request.

- +
  • ·Read the{" "}live benchmarks{" "}.
  • ·Reproduce any number. the{" "}methodology{" "}page tells you how.
  • @@ -70,7 +92,7 @@ export default function AboutPage() {
  • ·Discuss ideas in{" "}GitHub Discussions{" "}or follow{" "}@OpenChainBench.
- +

See a number you can't reproduce? File a{" "} From b4f3a83eeabd999d560be8ce4fad2df588da8ae6 Mon Sep 17 00:00:00 2001 From: Florent Tapponnier Date: Tue, 9 Jun 2026 22:43:45 +0200 Subject: [PATCH 2/3] feat(badge): add Share on X intent + Markdown copy on product badge cards Providers who clear the rank-1 threshold see embed-ready badge cards on their /products/[slug] page. Today it surfaces an SVG preview plus an HTML copy block. This adds two more ship-helpers: - 'Share on X' link with a pre-baked tweet intent. Opens X with the ranking claim, the bench URL and the @OpenChainBench tag pre-filled. Removes the friction of having to write the post; anchors every share back to us as a tagged tweet that the provider's followers will see. - 'Copy Markdown' details block for GitHub READMEs and docs. The markdown form ![alt](badgeUrl) is what 80% of crypto provider docs use, second only to HTML in landing pages. Both reuse the existing /api/badge// dynamic SVG so the displayed rank always reflects the live leaderboard. --- src/app/products/[slug]/page.tsx | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/src/app/products/[slug]/page.tsx b/src/app/products/[slug]/page.tsx index bdbbe46f..f84554ed 100644 --- a/src/app/products/[slug]/page.tsx +++ b/src/app/products/[slug]/page.tsx @@ -415,6 +415,14 @@ export default async function ProviderPage({ const badgeUrl = `${SITE.url}${badgePath}`; const targetUrl = `${SITE.url}/benchmarks/${a.benchmark.slug}`; const html = `Ranked #1 on OpenChainBench: ${a.benchmark.title}`; + const markdown = `[![Ranked #1 on OpenChainBench: ${a.benchmark.title}](${badgeUrl})](${targetUrl})`; + // Pre-baked X intent. Providers click → tweet draft opens + // with the ranking claim, the bench URL and the OCB handle + // already filled in. Removes the friction of writing the + // post themselves and gives us the canonical anchor text + // back as a tagged tweet on every share. + const tweetText = `Independently benchmarked #1 on ${a.benchmark.title} by @OpenChainBench.\n\nReproducible methodology, live data:`; + const tweetIntent = `https://x.com/intent/tweet?text=${encodeURIComponent(tweetText)}&url=${encodeURIComponent(targetUrl)}`; return (

  • @@ -430,6 +438,17 @@ export default async function ProviderPage({ decoding="async" /> +

    Copy HTML @@ -438,6 +457,14 @@ export default async function ProviderPage({ {html}
    +
    + + Copy Markdown + +
    +{markdown}
    +                    
    +
  • ); })} From 1b716f968dfecd5db2248a2522957aa30cb93a5c Mon Sep 17 00:00:00 2001 From: Florent Tapponnier Date: Tue, 9 Jun 2026 22:54:46 +0200 Subject: [PATCH 3/3] fix(badge): emit per-chain badges when provider only leads on subset of chains Closes the misleading-badge bug: a provider with a global aggregate #1 on a chain-dimensioned bench (like dRPC #1 cross-region on rpc-capabilities) was getting an unscoped 'Ranked #1 on Fastest free public RPC' badge even when they only led the rankings on a subset of chains. Embedders broadcasting that badge would parrot an inaccurate claim and torch the bench's neutrality positioning the first time a competitor diffed the per-chain ranks. New logic in /products/[slug] Embeddable badges section: - Benches without chainDimensions: keep one global aggregate badge per #1 finish (unchanged behavior). - Benches with chainDimensions where the provider leads on every declared chain: still one global badge. A true cross-chain leader earns the unscoped claim. - Benches with chainDimensions where the provider leads on a subset only: drop the global badge entirely, emit one badge PER chain led using the existing ?chain= query param. Each card's title, alt text, HTML / Markdown snippets and X intent tweet include the chain label so the claim downstream is honest. The /api/badge/[slug]/[provider]?chain= endpoint already supported the per-chain rank computation. This PR just stops the product page from generating the misleading unscoped URL when a chain-restricted leader exists. --- src/app/products/[slug]/page.tsx | 86 ++++++++++++++++++++++++++++---- 1 file changed, 77 insertions(+), 9 deletions(-) diff --git a/src/app/products/[slug]/page.tsx b/src/app/products/[slug]/page.tsx index f84554ed..18203784 100644 --- a/src/app/products/[slug]/page.tsx +++ b/src/app/products/[slug]/page.tsx @@ -403,7 +403,69 @@ export default async function ProviderPage({ stays accurate without redeploying.

      - {sorted.filter((a) => a.rank === 1).map((a) => { + {(() => { + // Generate the list of badge cards to render. For benches WITHOUT + // chain dimensions, the global aggregate badge is honest and we + // emit one card per #1 finish. For benches WITH chain dimensions + // we walk rankPerChain instead: + // - If the provider leads on EVERY declared chain (and the + // aggregate #1 confirms it), we keep a single global badge. + // A true cross-chain leader gets the unscoped claim. + // - Otherwise we emit one badge PER chain the provider leads + // and SKIP the global aggregate badge entirely. Without this + // guard a provider that wins one chain out of ten still + // shows a misleading "#1 on Fastest free public RPC" badge + // even though they trail elsewhere. Embedders broadcasting + // that badge would be parroting an inaccurate claim, which + // is the exact reputation hit we built the bench to avoid. + type BadgeCard = { + key: string; + title: string; + chain?: { value: string; label: string }; + benchSlug: string; + }; + const cards: BadgeCard[] = []; + for (const a of sorted) { + const chainDims = a.benchmark.chainDimensions ?? []; + const realChains = chainDims.filter((c) => c.value !== "all"); + const perChain = a.rankPerChain ?? {}; + const wonChains = realChains.filter( + (c) => perChain[c.value]?.rank === 1, + ); + const hasChainDims = realChains.length > 0; + const isGlobalNumberOne = a.rank === 1; + if (!hasChainDims) { + if (isGlobalNumberOne) { + cards.push({ + key: a.benchmark.slug, + title: a.benchmark.title, + benchSlug: a.benchmark.slug, + }); + } + continue; + } + const leadsAllChains = + realChains.length > 0 && + wonChains.length === realChains.length; + if (isGlobalNumberOne && leadsAllChains) { + cards.push({ + key: a.benchmark.slug, + title: a.benchmark.title, + benchSlug: a.benchmark.slug, + }); + continue; + } + for (const c of wonChains) { + cards.push({ + key: `${a.benchmark.slug}-${c.value}`, + title: a.benchmark.title, + chain: c, + benchSlug: a.benchmark.slug, + }); + } + } + return cards; + })().map((card) => { // Absolute URL is the one shipped to embedders (it has to // work from any third-party origin), but the in-page preview // uses a relative path so it loads under the current @@ -411,28 +473,34 @@ export default async function ProviderPage({ // shows the browser's broken-image glyph on every non-prod // origin (staging Preview URLs, Vercel branch previews, etc.) // because the CSP refuses the cross-origin fetch. - const badgePath = `/api/badge/${a.benchmark.slug}/${p.slug}`; + const qs = card.chain ? `?chain=${encodeURIComponent(card.chain.value)}` : ""; + const badgePath = `/api/badge/${card.benchSlug}/${p.slug}${qs}`; const badgeUrl = `${SITE.url}${badgePath}`; - const targetUrl = `${SITE.url}/benchmarks/${a.benchmark.slug}`; - const html = `Ranked #1 on OpenChainBench: ${a.benchmark.title}`; - const markdown = `[![Ranked #1 on OpenChainBench: ${a.benchmark.title}](${badgeUrl})](${targetUrl})`; + const targetUrl = `${SITE.url}/benchmarks/${card.benchSlug}${qs}`; + const scopeSuffix = card.chain ? ` on ${card.chain.label}` : ""; + const cardTitle = card.chain + ? `${card.title} — ${card.chain.label}` + : card.title; + const altText = `Ranked #1 on OpenChainBench: ${card.title}${scopeSuffix}`; + const html = `${altText}`; + const markdown = `[![${altText}](${badgeUrl})](${targetUrl})`; // Pre-baked X intent. Providers click → tweet draft opens // with the ranking claim, the bench URL and the OCB handle // already filled in. Removes the friction of writing the // post themselves and gives us the canonical anchor text // back as a tagged tweet on every share. - const tweetText = `Independently benchmarked #1 on ${a.benchmark.title} by @OpenChainBench.\n\nReproducible methodology, live data:`; + const tweetText = `Independently benchmarked #1 on ${card.title}${scopeSuffix} by @OpenChainBench.\n\nReproducible methodology, live data:`; const tweetIntent = `https://x.com/intent/tweet?text=${encodeURIComponent(tweetText)}&url=${encodeURIComponent(targetUrl)}`; return ( -
    • +
    • - {a.benchmark.title} + {cardTitle}

      {/* eslint-disable-next-line @next/next/no-img-element */}