From 7a390c73847440fd0acd7e8e0b9a23a91a7dfbd8 Mon Sep 17 00:00:00 2001 From: Flotapponnier <160007691+Flotapponnier@users.noreply.github.com> Date: Wed, 5 Aug 2026 15:22:14 +0200 Subject: [PATCH 1/3] fix: improve data-api report, retire indexing-freshness, add CompareBenchCard panel tabs (#1778) --- src/app/compare/[slug]/page.tsx | 49 +- src/components/compare-bench-card.tsx | 450 ++++++++++++++++++ .../2026-08-state-of-crypto-data-apis.mdx | 224 +++++++++ 3 files changed, 697 insertions(+), 26 deletions(-) create mode 100644 src/components/compare-bench-card.tsx create mode 100644 src/content/reports/data-api/2026-08-state-of-crypto-data-apis.mdx diff --git a/src/app/compare/[slug]/page.tsx b/src/app/compare/[slug]/page.tsx index 4abcd033..d70a15b9 100644 --- a/src/app/compare/[slug]/page.tsx +++ b/src/app/compare/[slug]/page.tsx @@ -19,6 +19,8 @@ import { buildBreadcrumbJsonLd, safeJsonLd } from "@/lib/jsonld"; import { SITE } from "@/data/site"; import { CREATOR_PUBLISHER, DATASET_LICENSE } from "@/lib/dataset-jsonld"; import type { Benchmark } from "@/types/benchmark"; +import { CompareBenchCard } from "@/components/compare-bench-card"; +import type { CompareBench } from "@/components/compare-bench-card"; import { computeInputsHash, readPairCache, @@ -293,31 +295,7 @@ type ChainRegionEntry = BreakdownRow & { regionRows: BreakdownRow[]; }; -type SharedBench = { - slug: string; - title: string; - category: Benchmark["category"]; - unit: Benchmark["unit"]; - metric: string; - higherIsBetter: boolean; - lastRunAt: Benchmark["lastRunAt"]; - aResult: Panel; - bResult: Panel; - /** Aggregate winner side. "tie" when p50 are equal. */ - aggregateWinner: "a" | "b" | "tie"; - /** Per chain side by side rows, populated only for benches with - * `dimensions.chain` and where both providers have positive p50 in - * the filtered variant. */ - chainBreakdown: BreakdownRow[]; - /** Per region side by side rows, same gating as chainBreakdown. */ - regionBreakdown: BreakdownRow[]; - /** Chain x region matrix, populated only for benches that expose - * BOTH `dimensions.chain` and `dimensions.region`. When present, the - * renderer uses this nested structure as a single 2D table and - * drops the flat chainBreakdown + regionBreakdown so we don't stack - * three tables for the same data. */ - chainRegionMatrix: ChainRegionEntry[]; -}; +type SharedBench = CompareBench; /** Sort comparator that respects `higherIsBetter`. Returns: * "a" if A leads, "b" if B leads, "tie" if both equal. */ @@ -640,6 +618,24 @@ async function buildSharedBenches( : Promise.resolve([]), ]); + const panelScopes = (fullBench.metricPanels ?? []) + .filter((p) => p.tab !== false) + .flatMap((p) => { + const aVal = p.values[aAppearances.slug]; + const bVal = p.values[bAppearances.slug]; + if (aVal == null || bVal == null) return []; + return [ + { + id: p.id, + label: p.label, + unit: p.unit, + higherIsBetter: p.higherIsBetter, + aValue: aVal, + bValue: bVal, + }, + ]; + }); + return { slug: fullBench.slug, title: fullBench.title, @@ -654,6 +650,7 @@ async function buildSharedBenches( chainRegionMatrix, chainBreakdown, regionBreakdown, + panelScopes, } satisfies SharedBench; }), ); @@ -902,7 +899,7 @@ export default async function ComparePage({
{shared.map((s) => ( - bVal ? "a" : "b"; + return aVal < bVal ? "a" : "b"; +} + +export function CompareBenchCard({ + bench, + aName, + bName, +}: { + bench: CompareBench; + aName: string; + bName: string; +}) { + const [activePanelId, setActivePanelId] = useState(null); + + const activePanel = bench.panelScopes.find((p) => p.id === activePanelId) ?? null; + + const effectiveUnit = activePanel?.unit ?? bench.unit; + const effectiveHigherIsBetter = activePanel?.higherIsBetter ?? bench.higherIsBetter; + const effectiveAVal = activePanel?.aValue ?? bench.aResult.p50; + const effectiveBVal = activePanel?.bValue ?? bench.bResult.p50; + + const panelWinner = + activePanel && (activePanel.aValue > 0 || activePanel.bValue > 0) + ? decideWinner(effectiveAVal, effectiveBVal, effectiveHigherIsBetter) + : bench.aggregateWinner; + + return ( +
+
+

+ + {bench.title} + +

+ + {bench.category} + +
+ + {bench.panelScopes.length > 0 && ( +
+ + View + + setActivePanelId(null)} + /> + {bench.panelScopes.map((p) => ( + setActivePanelId(p.id)} + /> + ))} +
+ )} + +
+ + +
+ + {!activePanel && + (bench.chainRegionMatrix.length > 0 ? ( + + ) : ( + <> + {bench.chainBreakdown.length > 0 && ( + + )} + {bench.regionBreakdown.length > 0 && ( + + )} + + ))} + +
+ Rolling 24h · {activePanel ? activePanel.label : bench.metric} + + Raw JSON + +
+
+ ); +} + +function PanelTab({ + label, + active, + onClick, +}: { + label: string; + active: boolean; + onClick: () => void; +}) { + return ( + + ); +} + +function AggregatePanel({ + name, + value, + details, + unit, + winner, + loser, +}: { + name: string; + value: number; + details: Panel | null; + unit: Benchmark["unit"]; + winner: boolean; + loser: boolean; +}) { + const hasData = value > 0; + const containerCls = winner + ? "border-good/60 bg-good/5" + : loser + ? "border-bad/40 bg-bad/5" + : "border-rule bg-surface"; + const headlineCls = winner ? "text-good" : loser ? "text-bad" : "text-ink"; + + return ( +
+
+

+ {name} +

+ {winner && hasData && ( + + Leads + + )} + {loser && hasData && ( + + Trails + + )} +
+ {hasData ? ( + <> +

+ {fmtValue(value, unit)} + + {unitSuffix(unit, value)} + +

+ {details && ( +
+
p99
+
{fmtUnit(details.p99, unit)}
+
rank
+
#{details.rank}
+ {details.sampleSize ? ( + <> +
samples
+
+ {Math.round(details.sampleSize).toLocaleString()} +
+ + ) : null} +
+ )} + + ) : ( +

No data in window

+ )} +
+ ); +} + +function ChainRegionMatrix({ + entries, + aName, + bName, + unit, +}: { + entries: ChainRegionEntry[]; + aName: string; + bName: string; + unit: Benchmark["unit"]; +}) { + const regionMap = new Map(); + for (const entry of entries) { + for (const r of entry.regionRows) { + if (!regionMap.has(r.value)) regionMap.set(r.value, r.label); + } + } + const regions = Array.from(regionMap.entries()).map(([value, label]) => ({ + value, + label, + })); + + const valueCell = (win: boolean, lose: boolean, isAggregate = false) => { + const color = win ? "text-good font-medium" : lose ? "text-bad" : "text-ink"; + return `py-2 px-2 text-right whitespace-nowrap ${isAggregate ? "border-l border-rule" : ""} ${color}`; + }; + const emptyCell = (isAggregate = false) => + `py-2 px-2 text-right text-ink-faint ${isAggregate ? "border-l border-rule" : ""}`; + + return ( +
+

+ Per chain · per region +

+
+ + + + + + {regions.map((r) => ( + + ))} + + + + + {entries.map((entry) => { + const byRegion = new Map(entry.regionRows.map((r) => [r.value, r] as const)); + return ( + + + + + {regions.map((r) => { + const row = byRegion.get(r.value); + return row ? ( + + ) : ( + + ); + })} + + + + + {regions.map((r) => { + const row = byRegion.get(r.value); + return row ? ( + + ) : ( + + ); + })} + + + + ); + })} + +
+ Chain + + Provider + + {r.label} + + Aggregate +
+ {entry.label} + + {aName} + + {fmtUnit(row.aP50, unit)} + + - + + {fmtUnit(entry.aP50, unit)} +
+ {bName} + + {fmtUnit(row.bP50, unit)} + + - + + {fmtUnit(entry.bP50, unit)} +
+
+
+ ); +} + +function BreakdownTable({ + title, + rows, + aName, + bName, + unit, +}: { + title: string; + rows: BreakdownRow[]; + aName: string; + bName: string; + unit: Benchmark["unit"]; +}) { + return ( +
+

+ {title} +

+
+ + + + + + + + + + {rows.map((row) => ( + + + + + + ))} + +
+ {title === "Per region" ? "Region" : "Chain"} + {aName}{bName}
{row.label} + {fmtUnit(row.aP50, unit)} + + {fmtUnit(row.bP50, unit)} +
+
+
+ ); +} diff --git a/src/content/reports/data-api/2026-08-state-of-crypto-data-apis.mdx b/src/content/reports/data-api/2026-08-state-of-crypto-data-apis.mdx new file mode 100644 index 00000000..b6d3c4f3 --- /dev/null +++ b/src/content/reports/data-api/2026-08-state-of-crypto-data-apis.mdx @@ -0,0 +1,224 @@ +--- +title: "Best Crypto Data API 2026: Price Feeds, Coverage, and Chain Breadth Ranked" +category: "data-api" +slug: "2026-08-state-of-crypto-data-apis" +publishedAt: "2026-08-04" +period: "August 2026" +summary: "Seven live benchmarks across six categories reveal a fragmented market: no single provider leads price feeds, token metadata, DEX coverage, NFT data, asset registry, and wallet labeling simultaneously. This report maps where each provider wins, where it falls short, and why." +heroFinding: "GeckoTerminal indexes 253 blockchains for DEX data but publishes prices 12 seconds after they move. The fastest price aggregators close that gap to under one second — but cover a fraction of those chains. No provider in this cohort leads more than two of the six categories measured." +author: "OpenChainBench Research" +readingTime: 14 +canonical: "https://openchainbench.com/reports/data-api/2026-08-state-of-crypto-data-apis" +--- + + +- Seven independent benchmarks across price feeds, token metadata, asset registry, DEX coverage, NFT data, and wallet labeling covering 15+ providers. +- No provider leads all six categories. The market is structurally fragmented by use case. +- Price aggregators: p50 head lag ranges from 707 ms to over 12 seconds depending on architecture. The gap widens to 7.9x on Solana. +- Token metadata coverage is a statistical tie at the top: two providers within 0.6 percentage points of each other. +- Asset registry breadth spans 81 to 461 chains — a 5.7x range reflecting a decade-scale difference in onboarding investment. +- Wallet labeling is dominated by chain-native specialists: coverage drops sharply for any provider working across multiple chains simultaneously. +- NFT metadata shows the widest intra-cohort gap of any category: 23 percentage points between leader and the largest general-purpose provider. + + + + +## Methodology + +Every number in this report is derived from OpenChainBench's live Prometheus instance and bench blob CDN. The seven benchmarks in scope run independent harnesses at the cadences described below. No numbers come from provider marketing pages or self-reported latency figures. + +Benchmarks in scope: [aggregator-head-lag](/benchmarks/aggregator-head-lag), [metadata-coverage](/benchmarks/metadata-coverage), [asset-registry-coverage](/benchmarks/asset-registry-coverage), [token-quote-coverage](/benchmarks/token-quote-coverage), [wallet-labels-coverage](/benchmarks/wallet-labels-coverage), [dex-network-coverage](/benchmarks/dex-network-coverage), [nft-collection-metadata](/benchmarks/nft-collection-metadata). + +The aggregator head-lag harness samples every 15 seconds from three geographic regions (US East, EU West, Singapore). All price-feed latency figures are p50 over a 24-hour rolling window. Coverage benches (metadata, asset registry, DEX, NFT) check a fixed test set on cadences ranging from every 30 minutes to every 6 hours. All harnesses are open source at [github.com/ChainBench/OpenChainBench](https://github.com/ChainBench/OpenChainBench/tree/main/harnesses). + +## The Six-Category Divide + +The crypto data API market is frequently described as a competitive space with a handful of dominant players. The benchmark data tells a different story: no single provider leads more than two of the six categories measured in this report. + +Category leaders as of August 2026: + +| Category | Benchmark | Leader | Value | +|---|---|---|---| +| Price Feeds | aggregator-head-lag | Mobula | 707 ms | +| Token Metadata | metadata-coverage | Codex | 64.3% | +| Asset Registry | asset-registry-coverage | CoinGecko | 461 chains | +| Token Quotes | token-quote-coverage | Jupiter | 96.6% | +| DEX Coverage | dex-network-coverage | GeckoTerminal | 253 chains | +| NFT Data | nft-collection-metadata | Moralis | 97.1% | +| Wallet Labels | wallet-labels-coverage | Helius | 84.1% | + +Seven distinct providers occupy the seven category-leader slots above. This fragmentation is structural, not accidental. Price feed freshness, asset registry breadth, DEX indexing, and wallet labeling require different infrastructure investments, different data pipelines, and different trade-offs between depth and breadth. The market has not yet produced a provider who executes well across all of them simultaneously. + +## Price Feed Head Lag + + + +The headline figure — 707 ms for Mobula — is a cross-chain, cross-region median. The distribution underneath it matters more than the single number. + +Codex trails by 1.65x globally. That gap widens to 7.9x on Solana and narrows to near-zero on Base, where the two providers differ by only 20 ms. The chain you're pricing determines whether the ranking matters. + +GeckoTerminal is in a separate category entirely. Its p50 of 12,489 ms — over twelve seconds — is not a latency ranking failure. It reflects a fundamentally different data pipeline architecture. GeckoTerminal does not attempt to be a real-time price feed in the sense that Mobula or Codex do. Its DEX indexing product (the best in coverage, as shown below) operates on a model where pool state is synced in batches, not streamed event by event. The head lag figure is a consequence of that architecture choice, not a quality deficit in isolation. + +The practical implication: if your application displays prices and a 12-second delay between on-chain events and your UI is visible to users, GeckoTerminal's price endpoint is not viable for that use case. If you need DEX pool metadata, pool history, or the deepest chain coverage for off-chain analytics, GeckoTerminal is the strongest option in the field. + +### Regional variance + +Mobula's regional spread is remarkably tight: 717 ms from US East versus 709 ms from EU West — an 8 ms difference. This consistency suggests Mobula distributes its indexing pipeline geographically rather than running from a single origin. Codex shows a similar pattern (1,169 ms US vs 1,178 ms EU). Neither provider penalizes European users meaningfully relative to US users. Singapore data was unavailable in this report cycle. + +## Solana: Block Architecture and Its Consequences + +The most striking figure in the price feed bench is Mobula's head lag on Solana: **99 ms**. A tenth of a second from on-chain event to API emission. + +Codex reaches the same chain in 779 ms. The 7.9x gap is not a Codex failure — it is a reflection of what Solana's architecture makes possible. Solana's block time is approximately 400 ms, versus Base and BNB where blocks land every 2 and 3 seconds respectively. An aggregator that subscribes to Solana's native websocket feed and processes confirmations in real time can publish prices faster than any EVM chain allows, because the chain itself confirms faster. + +**Head lag by chain (p50, 24 h)** + +| Chain | Mobula | Codex | GeckoTerminal | +|---|---:|---:|---:| +| Solana | 99 ms | 779 ms | 13,433 ms | +| Base | 826 ms | 846 ms | 12,217 ms | +| Robinhood Chain | 891 ms | 1,092 ms | 10,640 ms | +| BNB Chain | 1,032 ms | 1,970 ms | 13,703 ms | + +BNB Chain is the slowest chain for every provider in the cohort. BNB's block time is nominally 3 seconds but can vary; its validator set and consensus mechanism create additional confirmation latency that EVM aggregators must wait for before emitting a confirmed price. Mobula's BNB lag (1,032 ms) is 10x its Solana lag despite running on the same indexing infrastructure. + +The implication for builders: a "sub-second price feed" claim means different things on different chains. Verify the per-chain figures before assuming a provider's headline latency applies to your chain. + +## Token Metadata Coverage + + + +The metadata bench is one of the few categories where the ranking is genuinely ambiguous. Codex leads at 64.3%, Mobula follows at 63.7%. The gap is 0.6 percentage points — within normal measurement noise for this bench. Both providers are effectively tied on metadata coverage for newly-launched tokens. + +Jupiter's 24.9% requires context. Jupiter is Solana-only; it returns zero coverage on EVM chains (Base, BNB) by construction. The bench scores it on the full multi-chain sample set, so Jupiter's 24.9% headline undercounts its actual performance on Solana-only tokens. The cross-chain headline is the correct figure for any builder working with EVM tokens or multi-chain applications; for Solana-native metadata, consult the per-chain breakdown on the bench page. + +The more important observation in this category: both leading providers are under 65%. Fully one-third of newly-launched tokens have incomplete metadata across all providers in the cohort. Logo, description, Twitter, or website fields are missing for most new tokens regardless of which aggregator you use. Applications that need metadata for brand-new tokens should design for missing fields as the default case, not the exception. + +## Asset Registry Breadth + + + +CoinGecko's 461-chain registry is more than twice as wide as CoinPaprika's 307 chains and nearly six times Mobula's 81. This is the clearest expression of the breadth-versus-depth trade-off in the data API market. + +CoinGecko's registry breadth reflects a decade of manual chain onboarding and a community-submission model. Reaching 461 chains means accepting chains with thin liquidity, inactive validators, and minimal trading activity. The registry count measures scope, not data quality. A chain with one active token and a single liquidity pool is still counted. + +CoinGecko's registry breadth does not correlate with real-time price freshness — CoinGecko has no entry in the aggregator-head-lag bench. The asset registry and the price feed are different products serving different use cases: token discovery and contract-address lookup versus live market data. A builder who needs both must combine providers. + +The practical decision: if you need to answer "does this contract exist on chain X", CoinGecko's registry is the deepest lookup available. If you need a real-time price for a token on that chain, you need a provider with both registry coverage and a live price pipeline. + +## Quote Coverage for New Tokens + + + +The token quote bench measures something different from all other coverage benches: it tests providers on tokens created within the last hour, sourced from live launchpad feeds. This is the hardest case — the token may have no liquidity on major venues, no metadata, and may exist only on a single chain. + +Jupiter's 96.6% is the strongest absolute figure in the entire data API cohort across all seven benchmarks. On Solana, where the majority of its probe tokens live, Jupiter routes nearly every token successfully. Jupiter quotes 96 of 100 freshly launched tokens, a direct consequence of its native integration with Solana's pool infrastructure — it sees new pools seconds after creation. + +KyberSwap at 92.9% covers EVM chains competently. Mobula at 76.4% trails both, meaning roughly one in four new tokens across chains cannot be quoted. For applications that handle established tokens only (top-1,000 by market cap), all three providers will perform near 100%. The quote-coverage bench is specifically relevant for launchpad analytics, meme-token apps, or any product that needs to quote tokens within minutes of their creation. + +## DEX Coverage: Breadth vs. Freshness + + + +GeckoTerminal's 253 chains is a market-leading figure by a wide margin. Codex's 122 chains is the next closest at roughly half. Sim by Dune at 64 covers EVM mainnets only. DexPaprika at 36 is the narrowest in the cohort. + +The juxtaposition with the head-lag bench is the clearest illustration of the breadth-freshness trade-off in the entire dataset. GeckoTerminal leads DEX coverage by 2x and trails on price freshness by 17x. These are not separate failures — they are the same architecture decision viewed from two angles. Indexing 253 chains with a streaming price pipeline is not technically feasible on a data API provider's infrastructure budget in 2026. The choice to cover more chains is the choice to accept a longer synchronization cycle. + +For DEX analytics, backtesting, chain comparisons, or any use case that does not require sub-second prices, GeckoTerminal's breadth is the correct trade-off. For applications that need current prices on a specific chain, providers in the head-lag bench offer far fresher data on their supported chains, with DEX chain count as the cost. + +## NFT Metadata: The Alchemy Gap + + + +The NFT metadata bench has the narrowest competitive field in this report: three providers, all benchmarked against a fixed set of 50 Ethereum blue-chip collections. All three return 100% API availability — the ranking is driven entirely by coverage completeness. + +Moralis leads at 97.1%, OpenSea at 93.2%, and Alchemy at 73.7%. The 23-percentage-point gap between Moralis and Alchemy is the largest spread between a cohort leader and a major incumbent in any category in this report. + +Alchemy's gap is specific to the `floor_eth` field. Alchemy's `getContractMetadata` endpoint focuses on on-chain collection metadata — name, image, external URL — rather than marketplace-sourced order book data. Delivering a live floor price requires actively polling or subscribing to marketplace orders, which is not the primary function of Alchemy's contract metadata endpoint. OpenSea, which operates its own marketplace, surfaces floor prices naturally — though the bench notes this costs two API calls per collection versus one for Moralis. + +For builders who need collection-level metadata plus floor prices from a single endpoint, Moralis is the clear choice. For on-chain metadata only (name, image, external URL), Alchemy is competitive despite the lower headline figure — the gap collapses when `floor_eth` is excluded from the score. + +## Wallet Labels: No One Owns the Graph + + + +The wallet labeling bench has the most providers of any category in this report: nine across eleven chains. The leaderboard structure is clear: chain-native specialists occupy the top three positions. + +| Provider | Coverage | Primary Chain | +|---|---:|---| +| Helius | 84.1% | Solana | +| StellarExpert | 80.0% | Stellar | +| XRPScan | 79.8% | XRP | +| Blockscout | 55.9% | EVM (explorer) | +| OLI | 50.4% | EVM (standard) | +| Mobula | 43.5% | Multi-chain | +| TonAPI | 35.4% | TON | +| WalletExplorer | 19.9% | Bitcoin / EVM | + +Helius (Solana), StellarExpert (Stellar), and XRPScan (XRP) each maintain manually curated entity graphs for their specific chain. Their coverage is built on years of chain-specific research, community tagging, and validator partnerships — not algorithmic entity resolution. The result is a 40-percentage-point lead over general-purpose multi-chain providers. + +This is not a quality failure by multi-chain providers. It reflects the fundamental difficulty of maintaining a curated entity graph across many chains simultaneously. Wallet labeling is editorial work at scale — someone must decide that address 0x... is "Binance Hot Wallet 14" — and chain-native teams have the ecosystem context and community relationships to do that work accurately and quickly. + +OLI (Open Labels Initiative), which attempts a decentralized labeling standard on EVM chains, sits at 50.4% — marginally ahead of Blockscout but not dramatically better than general-purpose providers. The curation problem is harder than the coordination problem: even with a shared protocol, high-coverage entity resolution requires significant editorial investment. + +TonAPI at 35.4% reflects TON's still-developing ecosystem tooling. WalletExplorer at 19.9% has near-perfect API availability (99.8%) but the narrowest entity graph in the cohort — it has labels, just very few of them. + +## Cross-Provider Scorecard + +Across seven benchmarks, the competitive landscape resolves into four archetypes. + +**Speed specialists** optimize for real-time data at the cost of breadth. Mobula (707 ms head lag on cross-chain p50) and Codex lead their primary category and support a narrower set of chains than the broadest players. + +**Coverage maximalists** maximize breadth at the cost of freshness. GeckoTerminal (253 DEX chains, 12.5 s head lag) and CoinGecko (461 asset registry chains, no real-time price bench) define this archetype. Their value is "find any chain, any token" rather than "get the latest price fast." + +**Vertical specialists** dominate a single chain or use case. Jupiter (96.6% Solana quote coverage), Helius (84.1% Solana wallet labels), and Moralis (97.1% NFT metadata) each lead in a category where their infrastructure confers a structural advantage. None of them lead in a second category. + +**Generalists** achieve mid-table finishes across multiple categories. Codex appears in multiple benches with competitive but rarely dominant scores — it leads token metadata by 0.6 pp and indexes 122 DEX chains. No generalist is the obvious answer for an application that needs everything, because no single-provider answer exists yet. + +## Decision Framework + + +Price freshness is the primary constraint. Check the per-chain breakdown on the [aggregator-head-lag](/benchmarks/aggregator-head-lag) bench before committing to a provider — Base is a near coin-flip between the two leaders, Solana is not. Both top providers run near-100% success rates across their supported chains. + + + +Quote coverage on fresh tokens is the constraint. Jupiter is the only choice for Solana launchpad tokens (96.6%). For EVM chains (Base, BNB), KyberSwap (92.9%) leads. Build a fallback path for missing quotes — no provider in the cohort covers every freshly launched token across all chains. + + + +CoinGecko's 461-chain registry is the deepest single source for contract address lookup. For DEX pool metadata on niche chains, GeckoTerminal covers 253. Both are static or near-static lookups — safe to cache aggressively. Neither is a real-time price source. + + + +Moralis leads (97.1%) and delivers floor prices in a single API call. OpenSea is competitive (93.2%) but costs two calls per collection. Alchemy's 73.7% headline is largely driven by the floor-price gap — if you don't display floor prices, Alchemy is closer to parity on the remaining four fields. + + + +Wallet labeling requires chain-native providers for maximum coverage. For Solana: Helius (84.1%). For XRP: XRPScan (79.8%). For Stellar: StellarExpert (80%). For EVM: Blockscout (55.9%) or OLI (50.4%). No single provider reaches above 85% across all eleven chains simultaneously — plan for missing labels on non-specialist chains as the baseline, not the edge case. + + + +GeckoTerminal's 253-chain index is the only viable answer for breadth. Accept the 12-second price lag as a feature of the product architecture, not a bug. For analytics workloads running on historical or near-real-time data, the lag is irrelevant. For anything requiring current prices, use a provider from the head-lag bench on the subset of chains they support. + + +## Conclusion + +The clearest finding across all seven benchmarks is that the crypto data API market has not converged. The providers who win on price freshness lose on chain breadth; the providers who win on chain breadth lose on price freshness. Chain-native specialists dominate their corner of the graph but fall to mid-table the moment you need coverage outside their primary ecosystem. + +This fragmentation is not a market failure in the short-term sense. It is the expected outcome of an industry where the technical demands of each category — streaming price indexing, registry maintenance, DEX pool tracking, entity resolution — are genuinely different and resource-intensive. No provider has had the time or capital to lead across all of them. + +The practical consequence for builders in 2026: most production applications using crypto data will need two or more providers simultaneously. A real-time price feed from a speed specialist, combined with a chain-agnostic registry from a breadth maximalist, is the pattern the benchmark data supports — not a single-vendor stack. The decision framework above maps which combination makes sense for each use case. + +## Sources + +All data in this report is derived from OpenChainBench's live benchmarks. Figures are p50 over a 24-hour rolling window unless noted. Bench data is live and updates continuously — figures in this report reflect the state as of August 4, 2026. + +- **Price feeds:** [aggregator-head-lag](/benchmarks/aggregator-head-lag) · [api/stat/aggregator-head-lag](/api/stat/aggregator-head-lag) +- **Token metadata:** [metadata-coverage](/benchmarks/metadata-coverage) · [asset-registry-coverage](/benchmarks/asset-registry-coverage) · [token-quote-coverage](/benchmarks/token-quote-coverage) +- **Wallet data:** [wallet-labels-coverage](/benchmarks/wallet-labels-coverage) +- **DEX:** [dex-network-coverage](/benchmarks/dex-network-coverage) +- **NFT:** [nft-collection-metadata](/benchmarks/nft-collection-metadata) +- **Data API hub:** [/data-api](/data-api) — live cross-bench rankings, updated every 60 seconds +- **Harness source:** [github.com/ChainBench/OpenChainBench/harnesses](https://github.com/ChainBench/OpenChainBench/tree/main/harnesses) +- **License:** All data and figures in this report are published under [CC BY 4.0](https://creativecommons.org/licenses/by/4.0/). You may reproduce them with attribution to OpenChainBench and a link to the canonical URL. +- **Corrections:** File a [GitHub issue](https://github.com/ChainBench/OpenChainBench/issues/new). Material corrections are applied in place with a dated note. From a429ed8e2c8c7b4d6804d909555e4edca89ec3fb Mon Sep 17 00:00:00 2001 From: Flotapponnier <160007691+Flotapponnier@users.noreply.github.com> Date: Wed, 5 Aug 2026 15:37:56 +0200 Subject: [PATCH 2/3] chore: remove old BenchCard, clean up unused imports after CompareBenchCard extraction (#1779) --- src/app/compare/[slug]/page.tsx | 361 +------------------------------- 1 file changed, 1 insertion(+), 360 deletions(-) diff --git a/src/app/compare/[slug]/page.tsx b/src/app/compare/[slug]/page.tsx index d70a15b9..69765b0b 100644 --- a/src/app/compare/[slug]/page.tsx +++ b/src/app/compare/[slug]/page.tsx @@ -1,5 +1,4 @@ import type { Metadata } from "next"; -import { Fragment } from "react"; import { notFound, redirect } from "next/navigation"; import Link from "next/link"; import { ArrowLeft, ArrowUpRight } from "lucide-react"; @@ -12,13 +11,12 @@ import { } from "@/data/compare-pairs"; import { getProviderRegistry } from "@/data/provider-registry"; import { ProviderLogo } from "@/components/provider-logo"; -import { fmtUnit, fmtValue, unitSuffix } from "@/lib/format"; +import { fmtUnit } from "@/lib/format"; import { capDescription } from "@/lib/seo-text"; import { Breadcrumb } from "@/components/breadcrumb"; import { buildBreadcrumbJsonLd, safeJsonLd } from "@/lib/jsonld"; import { SITE } from "@/data/site"; import { CREATOR_PUBLISHER, DATASET_LICENSE } from "@/lib/dataset-jsonld"; -import type { Benchmark } from "@/types/benchmark"; import { CompareBenchCard } from "@/components/compare-bench-card"; import type { CompareBench } from "@/components/compare-bench-card"; import { @@ -989,360 +987,3 @@ function ProviderHeader({ ); } -function BenchCard({ - bench, - aName, - bName, -}: { - bench: SharedBench; - aName: string; - bName: string; -}) { - return ( -
-
-

- - {bench.title} - -

- - {bench.category} - -
- -
- - -
- - {bench.chainRegionMatrix.length > 0 ? ( - - ) : ( - <> - {bench.chainBreakdown.length > 0 && ( - - )} - {bench.regionBreakdown.length > 0 && ( - - )} - - )} - -
- Rolling 24h · {bench.metric} - - Raw JSON - -
-
- ); -} - -function AggregatePanel({ - name, - panel, - unit, - winner, - loser, -}: { - name: string; - panel: Panel; - unit: Benchmark["unit"]; - winner: boolean; - loser: boolean; -}) { - const hasData = panel.rank > 0 && panel.p50 > 0; - const containerCls = winner - ? "border-good/60 bg-good/5" - : loser - ? "border-bad/40 bg-bad/5" - : "border-rule bg-surface"; - const headlineCls = winner - ? "text-good" - : loser - ? "text-bad" - : "text-ink"; - return ( -
-
-

- {name} -

- {winner && hasData && ( - - Leads - - )} - {loser && hasData && ( - - Trails - - )} -
- {hasData ? ( - <> -

- {fmtValue(panel.p50, unit)} - - {unitSuffix(unit, panel.p50)} - -

-
-
p99
-
- {fmtUnit(panel.p99, unit)} -
-
rank
-
#{panel.rank}
- {panel.sampleSize ? ( - <> -
samples
-
- {Math.round(panel.sampleSize).toLocaleString()} -
- - ) : null} -
- - ) : ( -

No data in window

- )} -
- ); -} - -/** Single flat 2D matrix used when a bench exposes both `chain` and - * `region` dimensions. Rows are grouped per chain (rowspan on the chain - * cell), two sub-rows per chain (one per provider). Columns expand - * across every region observed for the pair plus an aggregate column on - * the right. Each value cell is colored by the per-cell winner so the - * table reads as a heatmap: green = leads here, red = trails. */ -function ChainRegionMatrix({ - entries, - aName, - bName, - unit, -}: { - entries: ChainRegionEntry[]; - aName: string; - bName: string; - unit: Benchmark["unit"]; -}) { - const regionMap = new Map(); - for (const entry of entries) { - for (const r of entry.regionRows) { - if (!regionMap.has(r.value)) regionMap.set(r.value, r.label); - } - } - const regions = Array.from(regionMap.entries()).map(([value, label]) => ({ - value, - label, - })); - - const valueCell = (win: boolean, lose: boolean, isAggregate = false) => { - const color = win - ? "text-good font-medium" - : lose - ? "text-bad" - : "text-ink"; - return `py-2 px-2 text-right whitespace-nowrap ${isAggregate ? "border-l border-rule" : ""} ${color}`; - }; - const emptyCell = (isAggregate = false) => - `py-2 px-2 text-right text-ink-faint ${isAggregate ? "border-l border-rule" : ""}`; - - return ( -
-

- Per chain · per region -

-
- - - - - - {regions.map((r) => ( - - ))} - - - - - {entries.map((entry) => { - const byRegion = new Map( - entry.regionRows.map((r) => [r.value, r] as const), - ); - return ( - - - - - {regions.map((r) => { - const row = byRegion.get(r.value); - return row ? ( - - ) : ( - - ); - })} - - - - - {regions.map((r) => { - const row = byRegion.get(r.value); - return row ? ( - - ) : ( - - ); - })} - - - - ); - })} - -
- Chain - - Provider - - {r.label} - - Aggregate -
- {entry.label} - - {aName} - - {fmtUnit(row.aP50, unit)} - - - - - {fmtUnit(entry.aP50, unit)} -
- {bName} - - {fmtUnit(row.bP50, unit)} - - - - - {fmtUnit(entry.bP50, unit)} -
-
-
- ); -} - -function BreakdownTable({ - title, - rows, - aName, - bName, - unit, -}: { - title: string; - rows: BreakdownRow[]; - aName: string; - bName: string; - unit: Benchmark["unit"]; -}) { - return ( -
-

- {title} -

-
- - - - - - - - - - {rows.map((row) => ( - - - - - - ))} - -
- {title === "Per region" ? "Region" : "Chain"} - {aName}{bName}
{row.label} - {fmtUnit(row.aP50, unit)} - - {fmtUnit(row.bP50, unit)} -
-
-
- ); -} From 5002cc0e6df70a88dc2d242307dae4ea4cd70cfb Mon Sep 17 00:00:00 2001 From: Florent Tapponnier <160007691+Flotapponnier@users.noreply.github.com> Date: Wed, 5 Aug 2026 17:46:26 +0200 Subject: [PATCH 3/3] fix: compare page shows all scopes as columns instead of view tabs --- src/components/compare-bench-card.tsx | 248 +++++++++++++------------- 1 file changed, 126 insertions(+), 122 deletions(-) diff --git a/src/components/compare-bench-card.tsx b/src/components/compare-bench-card.tsx index fd3c9db8..cdc0154f 100644 --- a/src/components/compare-bench-card.tsx +++ b/src/components/compare-bench-card.tsx @@ -1,6 +1,6 @@ "use client"; -import { Fragment, useState } from "react"; +import { Fragment } from "react"; import Link from "next/link"; import type { Benchmark } from "@/types/benchmark"; import { fmtUnit, fmtValue, unitSuffix } from "@/lib/format"; @@ -70,19 +70,7 @@ export function CompareBenchCard({ aName: string; bName: string; }) { - const [activePanelId, setActivePanelId] = useState(null); - - const activePanel = bench.panelScopes.find((p) => p.id === activePanelId) ?? null; - - const effectiveUnit = activePanel?.unit ?? bench.unit; - const effectiveHigherIsBetter = activePanel?.higherIsBetter ?? bench.higherIsBetter; - const effectiveAVal = activePanel?.aValue ?? bench.aResult.p50; - const effectiveBVal = activePanel?.bValue ?? bench.bResult.p50; - - const panelWinner = - activePanel && (activePanel.aValue > 0 || activePanel.bValue > 0) - ? decideWinner(effectiveAVal, effectiveBVal, effectiveHigherIsBetter) - : bench.aggregateWinner; + const hasScopes = bench.panelScopes.length > 0; return (
@@ -97,79 +85,63 @@ export function CompareBenchCard({ - {bench.panelScopes.length > 0 && ( -
- - View - - setActivePanelId(null)} - /> - {bench.panelScopes.map((p) => ( - setActivePanelId(p.id)} + {hasScopes ? ( + + ) : ( + <> +
+ - ))} -
- )} - -
- - -
+ +
- {!activePanel && - (bench.chainRegionMatrix.length > 0 ? ( - - ) : ( - <> - {bench.chainBreakdown.length > 0 && ( - - )} - {bench.regionBreakdown.length > 0 && ( - - )} - - ))} + {bench.chainRegionMatrix.length > 0 ? ( + + ) : ( + <> + {bench.chainBreakdown.length > 0 && ( + + )} + {bench.regionBreakdown.length > 0 && ( + + )} + + )} + + )}
- Rolling 24h · {activePanel ? activePanel.label : bench.metric} + Rolling 24h · {bench.metric} void; + bench: CompareBench; + aName: string; + bName: string; }) { + const cols = bench.panelScopes; + return ( - +
+ + + + + {cols.map((c) => ( + + ))} + + + + {(["a", "b"] as const).map((side) => { + const name = side === "a" ? aName : bName; + return ( + + + {cols.map((c) => { + const val = side === "a" ? c.aValue : c.bValue; + const winner = decideWinner(c.aValue, c.bValue, c.higherIsBetter); + const leads = winner === side; + const trails = winner !== side && winner !== "tie"; + const hasData = val > 0; + return ( + + ); + })} + + ); + })} + +
+ Provider + + {c.label} +
+ {name} + + {hasData ? fmtUnit(val, c.unit) : "-"} +
+
); } @@ -249,9 +266,7 @@ function AggregatePanel({
{hasData ? ( <> -

+

{fmtValue(value, unit)} {unitSuffix(unit, value)} @@ -298,10 +313,7 @@ function ChainRegionMatrix({ if (!regionMap.has(r.value)) regionMap.set(r.value, r.label); } } - const regions = Array.from(regionMap.entries()).map(([value, label]) => ({ - value, - label, - })); + const regions = Array.from(regionMap.entries()).map(([value, label]) => ({ value, label })); const valueCell = (win: boolean, lose: boolean, isAggregate = false) => { const color = win ? "text-good font-medium" : lose ? "text-bad" : "text-ink"; @@ -358,9 +370,7 @@ function ChainRegionMatrix({ {fmtUnit(row.aP50, unit)} ) : ( - - - - + - ); })} @@ -378,9 +388,7 @@ function ChainRegionMatrix({ {fmtUnit(row.bP50, unit)} ) : ( - - - - + - ); })} @@ -430,14 +438,10 @@ function BreakdownTable({ {rows.map((row) => ( {row.label} - + {fmtUnit(row.aP50, unit)} - + {fmtUnit(row.bP50, unit)}