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() {
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 = ``;
- const markdown = `[](${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 = ``;
+ const markdown = `[](${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 (
-