From 6023fde547af19850c5a25736d501bfbbaa2f3ed Mon Sep 17 00:00:00 2001 From: Florent Tapponnier <160007691+Flotapponnier@users.noreply.github.com> Date: Thu, 13 Aug 2026 18:51:17 +0200 Subject: [PATCH 1/6] fix: resolve 6 validate-specs errors from dev merge (em-dashes, blast findings length, sol unit, trading-app providers) --- benchmarks/blast-rpc.yml | 4 ++-- benchmarks/monad-rpc.yml | 2 +- benchmarks/perp-cost-slope.yml | 2 +- benchmarks/trading-app-execution.yml | 18 ++++++++++++++++++ src/lib/format.ts | 8 ++++++++ src/lib/spec-schema.ts | 4 ++-- src/types/benchmark.ts | 6 +++--- 7 files changed, 35 insertions(+), 9 deletions(-) diff --git a/benchmarks/blast-rpc.yml b/benchmarks/blast-rpc.yml index 899b7b0e..3a75a2a5 100644 --- a/benchmarks/blast-rpc.yml +++ b/benchmarks/blast-rpc.yml @@ -30,7 +30,7 @@ methodology: findings: - "{{best_name}} currently leads free Blast RPC at {{best_p50}} (`eth_getBlockByNumber` p50, 24h) across 4 measured providers." - - "`rpc.blast.io` posts ~2 ms in every region simultaneously. On-chain response headers confirm geo-distributed nodes: `blast_blast-mainnet_fra` from eu-west, `blast_blast-mainnet_sin` from Singapore, a US node from us-east. Blast runs RPC nodes inside the same GCP datacenters as our Railway probes, producing sub-2 ms figures that reflect co-located infrastructure rather than typical user latency. A user in Paris reaches the Frankfurt node in ~40 ms; a user outside GCP will see 30-100 ms depending on location. Heads are fresh per stale detection." + - "`rpc.blast.io` posts ~2 ms in every region. Response headers confirm geo-distributed GCP nodes co-located with our Railway probes (`blast_blast-mainnet_fra` from eu-west, `blast_blast-mainnet_sin` from Singapore, US node from us-east). Sub-2 ms reflects co-location, not real-world latency; outside GCP expect 30-100 ms. Heads are fresh per stale detection." - "{{name:drpc}} ({{p50:drpc}}) is the honest-infrastructure comparison point: anycast consistency across the three regions with real node round trips behind it, the profile that wins it 10 of the 12 expansion chains." - "{{name:tenderly}} sits at the expansion's familiar flat ~330 ms in all regions on Blast, single-origin routing on a gateway that is genuinely quick on the major chains." @@ -44,7 +44,7 @@ faq: - q: "How is Blast RPC latency measured here?" a: "One identical JSON-RPC POST (`eth_getBlockByNumber`) every 60 seconds against each provider from each of 3 regions, with the same plain HTTP client. Wall-clock round-trip is recorded at millisecond precision; p50/p90/p99 are computed via Prometheus `quantile_over_time` over 24 hours. Responses are classified (`ok` / `http_err` / `jsonrpc_err` / `stale` / `timeout`) so an endpoint stuck on an old head or returning errors behind HTTP 200 is never ranked as fastest. The harness is open source and every number on this page is a public Prometheus query you can run yourself." - q: "Is rpc.blast.io really that fast, or is something else going on?" - a: "Blast runs geo-distributed RPC nodes inside major cloud datacenters (GCP us-east, GCP europe-west4/Frankfurt, GCP asia-southeast1/Singapore). Our Railway probes run in the same three GCP regions, so each probe hits the co-located Blast node with a sub-LAN round trip. The `x-node-id` response header confirms this: `blast_blast-mainnet_fra` from eu-west, `blast_blast-mainnet_sin` from Singapore. The blocks are fresh — this is not a cache hit — but the 1-2 ms figure reflects network co-location, not what a developer outside GCP infra will experience. From a typical residential connection or a non-GCP cloud, expect 30-100 ms." + a: "Blast runs geo-distributed RPC nodes inside major cloud datacenters (GCP us-east, GCP europe-west4/Frankfurt, GCP asia-southeast1/Singapore). Our Railway probes run in the same three GCP regions, so each probe hits the co-located Blast node with a sub-LAN round trip. The `x-node-id` response header confirms this: `blast_blast-mainnet_fra` from eu-west, `blast_blast-mainnet_sin` from Singapore. The blocks are fresh (not a cache hit) but the 1-2 ms figure reflects network co-location, not what a developer outside GCP infra will experience. From a typical residential connection or a non-GCP cloud, expect 30-100 ms." source: https://github.com/ChainBench/OpenChainBench/tree/main/harnesses/rpc-capabilities diff --git a/benchmarks/monad-rpc.yml b/benchmarks/monad-rpc.yml index 0f4885d8..3f0581da 100644 --- a/benchmarks/monad-rpc.yml +++ b/benchmarks/monad-rpc.yml @@ -47,7 +47,7 @@ methodology: findings: - "{{best_name}} currently leads the free Monad RPC field at {{best_p50}} (p50, 24h) across 5 measured providers." - - "{{name:monad-official}} answers at {{p50:monad-official}}. On-chain response headers (`x-node-id: monad_monad-mainnet_fra`) confirm geo-distributed nodes co-located with our Railway probes in GCP datacenters (Frankfurt for eu-west, equivalent US and Singapore nodes). The sub-2 ms figure reflects network co-location, not what a developer outside GCP infra will experience — expect 30-120 ms from a typical connection. Rate-limit: 25 rps." + - "{{name:monad-official}} answers at {{p50:monad-official}}. On-chain response headers (`x-node-id: monad_monad-mainnet_fra`) confirm geo-distributed nodes co-located with our Railway probes in GCP datacenters (Frankfurt for eu-west, equivalent US and Singapore nodes). The sub-2 ms figure reflects network co-location, not what a developer outside GCP infra will experience; expect 30-120 ms from a typical connection. Rate-limit: 25 rps." - "{{name:drpc}} sits at {{p50:drpc}} and {{name:tenderly}} at {{p50:tenderly}}: the multi-chain gateways reached Monad within weeks of mainnet, but route through their global meshes rather than chain-local nodes." - "{{name:bloxroute}} at {{p50:bloxroute}} is the outlier entry: a BDN operator exposing a keyless endpoint, unusual for a company whose core product is paid low-latency access." diff --git a/benchmarks/perp-cost-slope.yml b/benchmarks/perp-cost-slope.yml index f8787ac6..a1b83ba2 100644 --- a/benchmarks/perp-cost-slope.yml +++ b/benchmarks/perp-cost-slope.yml @@ -88,7 +88,7 @@ per_chain_explainer: findings: - "{{name:gains}} and {{name:gmx}} always score 1.00 by construction, not by performance: oracle-priced venues charge a flat percentage of notional with no orderbook, so the $1M all-in is identical to the $1k all-in in basis points regardless of market conditions. Their top leaderboard position reflects fee structure, not execution quality." - - "Among orderbook venues, {{name:hyperliquid}} sits at {{p50:hyperliquid}} cost slope (24h avg) — the lowest impact in the cohort at $1M notional, driven by the deepest onchain book." + - "Among orderbook venues, {{name:hyperliquid}} sits at {{p50:hyperliquid}} cost slope (24h avg), the lowest impact in the cohort at $1M notional, driven by the deepest onchain book." - "Venues showing N/A in the slope column cannot reliably fill a $1M market order. The fill-rate panel shows what fraction of measurement cycles the $1M tier was successfully absorbed." - "The cost slope is asset-specific: use the chain tabs to compare ETH (deepest books), BTC (tightest books) and SOL (thinnest books) separately." diff --git a/benchmarks/trading-app-execution.yml b/benchmarks/trading-app-execution.yml index 227c77cd..a4c2f2f9 100644 --- a/benchmarks/trading-app-execution.yml +++ b/benchmarks/trading-app-execution.yml @@ -57,6 +57,24 @@ faq: - q: "Which platforms are tracked on EVM chains?" a: "GMGN, Maestro, and Banana Gun are tracked on Ethereum. GMGN, Maestro, Axiom, and Banana Gun are tracked on BSC. GMGN, Maestro, and Banana Gun are tracked on Base. pump.fun Terminal is tracked on all three EVM chains." +providers: + - slug: axiom + name: Axiom + - slug: gmgn + name: GMGN + - slug: trojan + name: Trojan + - slug: maestro + name: Maestro + - slug: pump-fun + name: pump.fun + - slug: photon + name: Photon + - slug: bullx + name: BullX + - slug: banana-gun + name: Banana Gun + # No prometheus section -- this bench uses a custom page renderer # at src/app/benchmarks/trading-app-execution/page.tsx that reads # directly from the exec REST API (exec.openchainbench.com). diff --git a/src/lib/format.ts b/src/lib/format.ts index 47d1b7dd..0e522411 100644 --- a/src/lib/format.ts +++ b/src/lib/format.ts @@ -96,6 +96,13 @@ export function fmtUnit(value: number, unit: string) { // clean integers, not decimal noise. return formatCompactCount(Math.round(value)); } + if (unit === "sol") { + if (value === 0) return "0 SOL"; + if (value > 0 && value < 0.0001) return `${value.toExponential(2)} SOL`; + if (value < 0.001) return `${value.toFixed(6)} SOL`; + if (value < 1) return `${value.toFixed(4)} SOL`; + return `${value.toFixed(3)} SOL`; + } if (unit === "gwei") { // Same near-zero semantics as count (gas-estimation gaps converge // toward zero), but the unit is carried so API consumers and the @@ -177,6 +184,7 @@ export function unitSuffix(unit: string, value?: number): string { if (unit === "count") return ""; if (unit === "gwei") return " gwei"; if (unit === "usd") return ""; + if (unit === "sol") return " SOL"; return " ms"; } diff --git a/src/lib/spec-schema.ts b/src/lib/spec-schema.ts index b2d9018f..6e23d850 100644 --- a/src/lib/spec-schema.ts +++ b/src/lib/spec-schema.ts @@ -249,7 +249,7 @@ export const SpecSchema = z /* Metric */ metric: z.string().min(1).max(100), /** ms / s for latencies; pct for fees as percent of notional; bps for basis points; slots for Solana slot delta. */ - unit: z.enum(["ms", "s", "sec", "pct", "bps", "bp", "count", "slots", "usd", "gwei", "x"]), + unit: z.enum(["ms", "s", "sec", "pct", "bps", "bp", "count", "slots", "usd", "gwei", "x", "sol"]), /** True when bigger numbers are better (coverage, count). Default false: * latency, fees, drift. every existing bench is "lower is better". */ higher_is_better: z.boolean().default(false), @@ -440,7 +440,7 @@ export const SpecSchema = z /** The PromQL label that holds each provider's slug. Defaults to * "builder"; other benches may use "provider", "venue", etc. */ label_key: z.string().min(1).max(40).default("builder"), - unit: z.enum(["ms", "s", "sec", "pct", "bps", "bp", "count", "slots", "usd", "gwei", "x"]), + unit: z.enum(["ms", "s", "sec", "pct", "bps", "bp", "count", "slots", "usd", "gwei", "x", "sol"]), higher_is_better: z.boolean().default(false), /** When false the panel is data-only: it is loaded and can feed * ledger_columns window variants, but renders no chart tab. diff --git a/src/types/benchmark.ts b/src/types/benchmark.ts index 47b3f597..e0f1dae3 100644 --- a/src/types/benchmark.ts +++ b/src/types/benchmark.ts @@ -139,7 +139,7 @@ export type MetricPanel = { label: string; description?: string; metric: string; - unit: "ms" | "s" | "sec" | "pct" | "bps" | "bp" | "count" | "slots" | "usd" | "gwei" | "x"; + unit: "ms" | "s" | "sec" | "pct" | "bps" | "bp" | "count" | "slots" | "usd" | "gwei" | "x" | "sol"; higherIsBetter: boolean; /** When false the panel is data-only (feeds ledger column window * variants) and renders no chart tab. */ @@ -233,7 +233,7 @@ export type Benchmark = { dataConfidence?: "healthy" | "low" | "insufficient"; abstract: string; metric: string; - unit: "ms" | "s" | "sec" | "pct" | "bps" | "bp" | "count" | "slots" | "usd" | "gwei" | "x"; + unit: "ms" | "s" | "sec" | "pct" | "bps" | "bp" | "count" | "slots" | "usd" | "gwei" | "x" | "sol"; higherIsBetter: boolean; /** Optional drill-down dimensions exposed by the bench. When set, the * bench page renders one tab selector per dimension and the queries get @@ -313,7 +313,7 @@ export type LedgerColumn = { panel?: string; /** Display unit override; defaults to the panel's unit (panel columns) * or the bench unit (slot columns). */ - unit?: "ms" | "s" | "sec" | "pct" | "bps" | "bp" | "count" | "slots" | "usd" | "gwei" | "x"; + unit?: "ms" | "s" | "sec" | "pct" | "bps" | "bp" | "count" | "slots" | "usd" | "gwei" | "x" | "sol"; /** Per-window value sources for the ledger's timeframe toggle: window * key to metric_panels id. Columns without a mapping keep their 24h * value when a longer window is selected. */ From a65c1eb86757e89f20234ce9d2932321acda1e7d Mon Sep 17 00:00:00 2001 From: Florent Tapponnier <160007691+Flotapponnier@users.noreply.github.com> Date: Thu, 13 Aug 2026 18:52:59 +0200 Subject: [PATCH 2/6] fix: remove duplicate provider-registry entries (coinbase/kraken/etc introduced by dev merge) --- src/data/provider-registry.ts | 32 -------------------------------- 1 file changed, 32 deletions(-) diff --git a/src/data/provider-registry.ts b/src/data/provider-registry.ts index a8857191..25ed2e52 100644 --- a/src/data/provider-registry.ts +++ b/src/data/provider-registry.ts @@ -1722,38 +1722,6 @@ export const PROVIDER_REGISTRY: Record = { twitter: "@BitgetWallet", }, - // ─── App store / CEX (bench № 202) ───────────────────────────────── - coinbase: { - url: "https://www.coinbase.com", - description: - "Coinbase is the largest US-regulated centralized exchange, publicly listed on Nasdaq (COIN). It offers spot trading, staking, and a self-custody wallet, primarily targeting retail investors in the United States.", - twitter: "@coinbase", - }, - kraken: { - url: "https://www.kraken.com", - description: - "Kraken is a US-registered centralized exchange founded in 2011, offering spot, margin and futures trading across 300+ assets with a strong reputation for security and regulatory compliance.", - twitter: "@krakenfx", - }, - "binance-us": { - url: "https://www.binance.us", - description: - "Binance.US is the US-regulated affiliate of Binance, operating under FinCEN MSB registration. It offers spot trading for US customers on a subset of the global Binance order book.", - twitter: "@BinanceUS", - }, - robinhood: { - url: "https://robinhood.com", - description: - "Robinhood is a US commission-free investing app with 25M+ funded accounts. It added crypto trading in 2018, launched its own L2 chain (Robinhood Chain) in 2025, and is the #1 most-reviewed financial app on the US App Store with nearly 5 million ratings.", - twitter: "@RobinhoodApp", - }, - cryptocom: { - url: "https://crypto.com", - description: - "Crypto.com is a Singapore-headquartered centralized exchange serving 100M+ users globally. The app offers spot and derivatives trading, a Visa card, DeFi wallet and staking, making it one of the most feature-complete crypto super apps on iOS.", - twitter: "@cryptocom", - }, - // ─── Perp funding venues (bench № 036) ───────────────────────────── bybit: { url: "https://www.bybit.com", From d0abc34521403aa91fdcfa1896257b76659dc953 Mon Sep 17 00:00:00 2001 From: Florent Tapponnier <160007691+Flotapponnier@users.noreply.github.com> Date: Thu, 13 Aug 2026 18:54:57 +0200 Subject: [PATCH 3/6] fix: remove duplicate logo-manifest and brand entries from dev merge --- src/lib/brand.ts | 8 -------- src/lib/logo-manifest.ts | 6 ------ 2 files changed, 14 deletions(-) diff --git a/src/lib/brand.ts b/src/lib/brand.ts index 8fb5d4c6..6a572557 100644 --- a/src/lib/brand.ts +++ b/src/lib/brand.ts @@ -104,14 +104,6 @@ const BRANDS: Record = { lighter: { color: "#FF2E63" }, // hot magenta gains: { color: "#10E2A4" }, // emerald - // ─── App store / CEX ─── - invo: { color: "#7B5EA7" }, // invo purple - robinhood: { color: "#00C805" }, // robinhood green - cryptocom: { color: "#002D74", dark: true }, // crypto.com navy - coinbase: { color: "#0052FF" }, // coinbase blue - bybit: { color: "#F7A600" }, // bybit yellow - kraken: { color: "#5741D9" }, // kraken purple - "binance-us": { color: "#F0B90B" }, // binance yellow }; const REGION_VALUES = new Set(["us-east", "eu-west", "ap-southeast", "sgp", "global"]); diff --git a/src/lib/logo-manifest.ts b/src/lib/logo-manifest.ts index 014d5877..5718a758 100644 --- a/src/lib/logo-manifest.ts +++ b/src/lib/logo-manifest.ts @@ -331,8 +331,6 @@ const RAW: Record = { kraken: "/logos/kraken.png", kucoin: "/logos/kucoin.png", mexc: "/logos/mexc.png", - "binance-us": "/logos/binance-us.svg", - cryptocom: "/logos/cryptocom.svg", // ─── Hyperliquid HIP-3 deployers (bench № 035) ─── xyz: "/logos/xyz.png", @@ -356,10 +354,6 @@ const RAW: Record = { coinpilot: "/logos/coinpilot.png", echosync: "/logos/echosync.png", fomo: "/logos/fomo.png", - "pump-fun": "/logos/pump-fun.jpg", - gmgn: "/logos/gmgn.jpg", - moonshot: "/logos/moonshot.svg", - bullx: "/logos/bullx.png", slash: "/logos/slash.png", topdog: "/logos/topdog.jpg", "markets-mobile": "/logos/markets-mobile.svg", From 933c71b9127a4cc34e49e0d9a4a55fe7649384dc Mon Sep 17 00:00:00 2001 From: Florent Tapponnier <160007691+Flotapponnier@users.noreply.github.com> Date: Thu, 13 Aug 2026 18:59:14 +0200 Subject: [PATCH 4/6] fix: add sol unit to views.ts and snapshot.ts UnitSchema --- src/lib/snapshot.ts | 1 + src/lib/views.ts | 1 + 2 files changed, 2 insertions(+) diff --git a/src/lib/snapshot.ts b/src/lib/snapshot.ts index 96a4f5fe..ae2e16a7 100644 --- a/src/lib/snapshot.ts +++ b/src/lib/snapshot.ts @@ -58,6 +58,7 @@ const UnitSchema = z.enum([ "usd", "gwei", "x", + "sol", ]); const StalenessMetaSchema = z.object({ diff --git a/src/lib/views.ts b/src/lib/views.ts index 7d6b9356..54051f79 100644 --- a/src/lib/views.ts +++ b/src/lib/views.ts @@ -45,6 +45,7 @@ const ALLOWED_BY_UNIT: Record = { // a comparison bar, which is the at-a-glance read users want. usd: ["countLeaderboard", "rankedBar", "donut", "distribution", "timeseries"], x: ["countLeaderboard", "rankedBar", "donut", "distribution", "timeseries"], + sol: ["countLeaderboard", "rankedBar", "donut", "distribution", "timeseries"], }; /** From d34b4908bf97dbd8e3a04639f3039c59bdcc1fdc Mon Sep 17 00:00:00 2001 From: Florent Tapponnier <160007691+Flotapponnier@users.noreply.github.com> Date: Thu, 13 Aug 2026 19:01:20 +0200 Subject: [PATCH 5/6] fix: wrap Date.now() in useMemo to satisfy react-compiler purity rule --- src/components/exec-bench-table.tsx | 13 ++++++++----- src/components/trading-apps-leaderboard.tsx | 7 +++++-- 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/src/components/exec-bench-table.tsx b/src/components/exec-bench-table.tsx index 575305a9..ac60824f 100644 --- a/src/components/exec-bench-table.tsx +++ b/src/components/exec-bench-table.tsx @@ -1,6 +1,6 @@ "use client"; -import { useState } from "react"; +import { useMemo, useState } from "react"; import type { ExecLeaderboardResponse } from "@/lib/solana-exec"; import { PLATFORM_DISPLAY, lamportsToSOL, fmtCUPrice } from "@/lib/solana-exec"; @@ -42,10 +42,13 @@ export function ExecBenchTable({ (b.windows[win]?.txCount ?? 0) - (a.windows[win]?.txCount ?? 0), ); - const stale = - data.updatedAt - ? Date.now() - new Date(data.updatedAt).getTime() > 3 * 60 * 60 * 1000 - : false; + const stale = useMemo( + () => + data.updatedAt + ? Date.now() - new Date(data.updatedAt).getTime() > 3 * 60 * 60 * 1000 + : false, + [data.updatedAt] + ); return (
diff --git a/src/components/trading-apps-leaderboard.tsx b/src/components/trading-apps-leaderboard.tsx index dd645631..00a33963 100644 --- a/src/components/trading-apps-leaderboard.tsx +++ b/src/components/trading-apps-leaderboard.tsx @@ -2,7 +2,7 @@ import Image from "next/image"; import Link from "next/link"; -import { useState } from "react"; +import { useMemo, useState } from "react"; import { logoPath } from "@/lib/logo-manifest"; import type { AppMeta } from "@/lib/trading-apps-config"; import type { DLPlatformData } from "@/lib/defillama"; @@ -357,7 +357,10 @@ export function TradingAppsLeaderboard({ function FOMODataNotice({ latestDate }: { latestDate: string | null }) { if (!latestDate) return null; - const ageHours = (Date.now() - new Date(latestDate).getTime()) / 3_600_000; + const ageHours = useMemo( + () => (Date.now() - new Date(latestDate).getTime()) / 3_600_000, + [latestDate] + ); if (ageHours <= 36) return null; return (

From 620ecdbde451d9d658150dbf58e21f764cb3c7a2 Mon Sep 17 00:00:00 2001 From: Florent Tapponnier <160007691+Flotapponnier@users.noreply.github.com> Date: Thu, 13 Aug 2026 19:03:27 +0200 Subject: [PATCH 6/6] fix: move useMemo before early return in exec-bench-table (hooks order) --- src/components/exec-bench-table.tsx | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/src/components/exec-bench-table.tsx b/src/components/exec-bench-table.tsx index ac60824f..e3d085b2 100644 --- a/src/components/exec-bench-table.tsx +++ b/src/components/exec-bench-table.tsx @@ -28,6 +28,13 @@ export function ExecBenchTable({ data: ExecLeaderboardResponse | null; }) { const [win, setWin] = useState("24h"); + const stale = useMemo( + () => + data?.updatedAt + ? Date.now() - new Date(data.updatedAt).getTime() > 3 * 60 * 60 * 1000 + : false, + [data?.updatedAt] + ); if (!data) { return ( @@ -42,14 +49,6 @@ export function ExecBenchTable({ (b.windows[win]?.txCount ?? 0) - (a.windows[win]?.txCount ?? 0), ); - const stale = useMemo( - () => - data.updatedAt - ? Date.now() - new Date(data.updatedAt).getTime() > 3 * 60 * 60 * 1000 - : false, - [data.updatedAt] - ); - return (