From 99bb913ecdba55e780d826aa203c8000707b2795 Mon Sep 17 00:00:00 2001 From: Flotapponnier <160007691+Flotapponnier@users.noreply.github.com> Date: Fri, 28 Aug 2026 21:51:52 +0200 Subject: [PATCH 01/15] fix(harnesses): cap LZ/CCIP/Axelar cardinality to bench-tracked chains only (#2187) --- .../axelar-gmp-latency/cmd/script/chains.go | 21 ++++++++++++++ .../axelar-gmp-latency/cmd/script/main.go | 6 ++-- .../cmd/script/chains.go | 26 +++++++++++++++++ .../chainlink-ccip-latency/cmd/script/main.go | 4 +++ .../cmd/script/chains.go | 29 +++++++++++++++++++ .../cmd/script/main.go | 4 +++ 6 files changed, 88 insertions(+), 2 deletions(-) diff --git a/harnesses/axelar-gmp-latency/cmd/script/chains.go b/harnesses/axelar-gmp-latency/cmd/script/chains.go index 2c83c54f..2b62f176 100644 --- a/harnesses/axelar-gmp-latency/cmd/script/chains.go +++ b/harnesses/axelar-gmp-latency/cmd/script/chains.go @@ -14,6 +14,27 @@ import "strings" // - Cosmos chains (osmosis, injective, sei, celestia, kava) are // Axelar-exclusive coverage vs Wormhole/LayerZero/CCIP/Hyperlane // +// axelarTrackedChains is the set of OCB canonical slugs we record metrics for. +// Only messages where BOTH source AND destination are in this set are +// observed, capping cardinality at len²×buckets. +// Derived from the bench YAML provider slugs. +var axelarTrackedChains = map[string]bool{ + "ethereum": true, + "polygon": true, + "base": true, + "moonbeam": true, + "osmosis": true, + "arbitrum": true, + "avalanche": true, + "bnb": true, + "celo": true, + "injective": true, + "linea": true, + "mantle": true, + "optimism": true, + "scroll": true, +} + // Unknown names fall through to `chain-` so we never drop data. var axelarChainSlug = map[string]string{ // EVM L1s diff --git a/harnesses/axelar-gmp-latency/cmd/script/main.go b/harnesses/axelar-gmp-latency/cmd/script/main.go index a3652e99..55ff6b41 100644 --- a/harnesses/axelar-gmp-latency/cmd/script/main.go +++ b/harnesses/axelar-gmp-latency/cmd/script/main.go @@ -173,8 +173,10 @@ func poll(ctx context.Context, client *http.Client, seen *lruSet) error { totalMs := float64(m.TimeSpent.Total) * 1000 if totalMs > 0 && totalMs <= maxLatencyMs { dst := canonicalizeAxelarChain(m.Call.ReturnValues.DestinationChain) - axelarE2ELatencyMs.WithLabelValues(src, dst).Observe(totalMs) - axelarSeenTotal.WithLabelValues(src, dst).Inc() + if axelarTrackedChains[src] && axelarTrackedChains[dst] { + axelarE2ELatencyMs.WithLabelValues(src, dst).Observe(totalMs) + axelarSeenTotal.WithLabelValues(src, dst).Inc() + } } seen.add(m.ID) diff --git a/harnesses/chainlink-ccip-latency/cmd/script/chains.go b/harnesses/chainlink-ccip-latency/cmd/script/chains.go index e551d3c4..bb3a17fe 100644 --- a/harnesses/chainlink-ccip-latency/cmd/script/chains.go +++ b/harnesses/chainlink-ccip-latency/cmd/script/chains.go @@ -12,6 +12,32 @@ package main // makes the mapping legible and the audit trail obvious when CCIP // adds a new chain we didn't anticipate. // +// ccipTrackedChains is the set of OCB canonical slugs we record metrics for. +// Only messages where BOTH source AND destination are in this set are +// observed, capping cardinality at len²×buckets. +// Derived from the bench YAML provider slugs. +var ccipTrackedChains = map[string]bool{ + "ethereum": true, + "bnb": true, + "polygon": true, + "avalanche": true, + "arbitrum": true, + "base": true, + "robinhood": true, + "berachain": true, + "celo": true, + "ink": true, + "linea": true, + "mantle": true, + "monad": true, + "moonbeam": true, + "optimism": true, + "scroll": true, + "solana": true, + "unichain": true, + "world-chain": true, +} + // Only mainnet entries are mapped; testnet rows are dropped in main.go // via the `environment != "mainnet"` guard so we never emit test-chain // latency. diff --git a/harnesses/chainlink-ccip-latency/cmd/script/main.go b/harnesses/chainlink-ccip-latency/cmd/script/main.go index 19b6e3c8..c4a0cece 100644 --- a/harnesses/chainlink-ccip-latency/cmd/script/main.go +++ b/harnesses/chainlink-ccip-latency/cmd/script/main.go @@ -189,6 +189,10 @@ func poll(ctx context.Context, client *http.Client, seen *lruSet) error { seen.add(m.MessageID) continue } + if !ccipTrackedChains[srcSlug] || !ccipTrackedChains[dstSlug] { + seen.add(m.MessageID) + continue + } ccipLatencyMs.WithLabelValues(srcSlug, dstSlug).Observe(deltaMs) ccipSeenTotal.WithLabelValues(srcSlug, dstSlug).Inc() seen.add(m.MessageID) diff --git a/harnesses/layerzero-message-latency/cmd/script/chains.go b/harnesses/layerzero-message-latency/cmd/script/chains.go index 2c888741..ded44292 100644 --- a/harnesses/layerzero-message-latency/cmd/script/chains.go +++ b/harnesses/layerzero-message-latency/cmd/script/chains.go @@ -10,6 +10,35 @@ package main // - LayerZero exposes a bunch of exotic chains (orderly, flare, ape, // robinhood, hyperliquid) that map to our slugs where they exist. // +// lzTrackedChains is the set of OCB canonical slugs we record metrics for. +// Only messages where BOTH source AND destination are in this set are +// observed. This caps cardinality at len²×buckets instead of the full +// N×N cross-product of all chains LayerZero supports. +// Derived from the bench YAML provider slugs — add here when adding a +// new chain to the bench. +var lzTrackedChains = map[string]bool{ + "ethereum": true, + "solana": true, + "bnb": true, + "arbitrum": true, + "base": true, + "optimism": true, + "polygon": true, + "avalanche": true, + "robinhood": true, + "monad": true, + "berachain": true, + "celo": true, + "injective": true, + "ink": true, + "linea": true, + "mantle": true, + "moonbeam": true, + "scroll": true, + "sui": true, + "unichain": true, +} + // Unknown names fall through to a synthetic `chain-` slug in // main.go so we never drop data silently. var lzChainSlug = map[string]string{ diff --git a/harnesses/layerzero-message-latency/cmd/script/main.go b/harnesses/layerzero-message-latency/cmd/script/main.go index 5f7a4f6d..3ccfc0d0 100644 --- a/harnesses/layerzero-message-latency/cmd/script/main.go +++ b/harnesses/layerzero-message-latency/cmd/script/main.go @@ -206,6 +206,10 @@ func poll(ctx context.Context, client *http.Client, seen *lruSet) error { continue } dstSlug := chainSlug(m.Pathway.Receiver.Chain) + if !lzTrackedChains[srcSlug] || !lzTrackedChains[dstSlug] { + seen.add(m.GUID) + continue + } lzLatencyMs.WithLabelValues(srcSlug, dstSlug).Observe(deltaMs) lzSeenTotal.WithLabelValues(srcSlug, dstSlug).Inc() seen.add(m.GUID) From 2708d1a859153fb2bd7e62fd6b6136b92a4d701c Mon Sep 17 00:00:00 2001 From: Flotapponnier <160007691+Flotapponnier@users.noreply.github.com> Date: Fri, 28 Aug 2026 21:53:11 +0200 Subject: [PATCH 02/15] fix(cache): strip heavy fields from aggregate blob to pass 2MB limit (#2188) Hub pages only need card data. Drop seriesByRegion24h, metricPanels, editorial text (seoIntro/faq/findings/methodology/disclaimer/abstract/ perChainExplainer) from the blob aggregate slim. Cuts serialized size from ~4.3 MB to under 2 MB so unstable_cache actually persists the entry instead of silently dropping it on every render. --- src/lib/aggregate-blob.ts | 35 +++++++++++++++++++++++++++++++++-- 1 file changed, 33 insertions(+), 2 deletions(-) diff --git a/src/lib/aggregate-blob.ts b/src/lib/aggregate-blob.ts index 0c2ff32e..f920fb1f 100644 --- a/src/lib/aggregate-blob.ts +++ b/src/lib/aggregate-blob.ts @@ -26,6 +26,37 @@ import type { Benchmark } from "@/types/benchmark"; import { loadSpecsUncached } from "@/lib/materialize/load"; import { overlayEditorial, slimBenchmarkForCache } from "@/lib/spec"; +// Aggressive slim for the aggregate blob. Hub pages (homepage, categories, +// chains, products) only need card data — they never render editorial text +// or metric panels. Stripping these fields drops the serialized aggregate +// from ~4.3 MB to well under the 2 MB unstable_cache ceiling. +// +// Fields stripped beyond slimBenchmarkForCache (which already removes +// 7d/30d series): +// - extras.seriesByRegion24h (only used on bench detail pages) +// - metricPanels (only used on bench detail pages) +// - seoIntro, faq, disclaimer (editorial, bench detail only) +// - perChainExplainer (bench detail + worker's sitemap.json handles sitemap) +// - findings, methodology (bench detail only; required fields → []) +// - abstract (bench detail only; required field → "") +function slimForBlobAggregate(b: Benchmark): Benchmark { + const base = slimBenchmarkForCache(b); + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const { seriesByRegion24h: _sbr, ...slimExtras } = base.extras; + return { + ...base, + extras: slimExtras, + metricPanels: undefined, + seoIntro: undefined, + faq: undefined, + perChainExplainer: undefined, + disclaimer: undefined, + findings: [], + methodology: [], + abstract: "", + }; +} + // On any Vercel deployment (production or preview), use the self-hosted // CDN proxy (/api/aggregate on openchainbench.com) so Vercel functions // pay ~1 ms (edge cache hit) instead of ~12 s fetching the 7.5 MB blob @@ -113,7 +144,7 @@ async function fetchAndProject(): Promise { for (const bench of raw.benches) { const spec = specBySlug.get(bench.slug); if (!spec) continue; // Bench in blob no longer has a spec — skip. - projected.push(slimBenchmarkForCache(overlayEditorial(bench, spec))); + projected.push(slimForBlobAggregate(overlayEditorial(bench, spec))); } return projected.sort((a, b) => (a.number ?? "").localeCompare(b.number ?? ""), @@ -130,6 +161,6 @@ async function fetchAndProject(): Promise { */ export const loadAggregateFromBlob = unstable_cache( fetchAndProject, - ["aggregate-blob-v2"], + ["aggregate-blob-v3"], { revalidate: 60, tags: ["bench-aggregate", "benchmarks"] }, ); From 9039b8d75d76de62b9a0e8231918dcd8b0e689fe Mon Sep 17 00:00:00 2001 From: Flotapponnier <160007691+Flotapponnier@users.noreply.github.com> Date: Sat, 29 Aug 2026 15:26:43 +0200 Subject: [PATCH 03/15] fix: include still-open Gains positions in carry projection (#2189) * fix(cache): strip heavy fields from aggregate blob to pass 2MB limit Hub pages only need card data. Drop seriesByRegion24h, metricPanels, editorial text (seoIntro/faq/findings/methodology/disclaimer/abstract/ perChainExplainer) from the blob aggregate slim. Cuts serialized size from ~4.3 MB to under 2 MB so unstable_cache actually persists the entry instead of silently dropping it on every render. * fix: include still-open Gains positions in carry projection --- src/app/api/fee-compare/route.ts | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/app/api/fee-compare/route.ts b/src/app/api/fee-compare/route.ts index cd724592..bb3fb0e9 100644 --- a/src/app/api/fee-compare/route.ts +++ b/src/app/api/fee-compare/route.ts @@ -1121,16 +1121,19 @@ function reconstructGainsPositions(trades: GainsApiTrade[], cutoffMs: number): P else if (CLOSE_ACTIONS.has(t.action)) e.close = t; } + const now = Date.now(); const slices: PositionSlice[] = []; for (const { open, close } of byId.values()) { - if (!open || !close) continue; + if (!open) continue; const openMs = new Date(open.date).getTime(); if (openMs < cutoffMs) continue; + // Still-open positions use now as close time (same as reconstructHlPositions) + const closeMs = close ? new Date(close.date).getTime() : now; slices.push({ coin: open.pair.split("/")[0], notionalUsd: open.size * open.leverage, openMs, - closeMs: new Date(close.date).getTime(), + closeMs, isLong: open.buy !== false, }); } From b37c7bb5499b3a4985949981d99add8967fcca4f Mon Sep 17 00:00:00 2001 From: Flotapponnier <160007691+Flotapponnier@users.noreply.github.com> Date: Sat, 29 Aug 2026 15:34:02 +0200 Subject: [PATCH 04/15] =?UTF-8?q?fix:=20update=20Gains=20action=20names=20?= =?UTF-8?q?v5=E2=86=92v6,=20handle=20position=20size=20increases=20(#2190)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix(cache): strip heavy fields from aggregate blob to pass 2MB limit Hub pages only need card data. Drop seriesByRegion24h, metricPanels, editorial text (seoIntro/faq/findings/methodology/disclaimer/abstract/ perChainExplainer) from the blob aggregate slim. Cuts serialized size from ~4.3 MB to under 2 MB so unstable_cache actually persists the entry instead of silently dropping it on every render. * fix: include still-open Gains positions in carry projection * fix: update Gains open action names to v6 API, handle TradePosSizeIncrease --- src/app/api/fee-compare/route.ts | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/app/api/fee-compare/route.ts b/src/app/api/fee-compare/route.ts index bb3fb0e9..b4f9e2e1 100644 --- a/src/app/api/fee-compare/route.ts +++ b/src/app/api/fee-compare/route.ts @@ -1110,7 +1110,10 @@ function augmentWithHlOpenPositions( } function reconstructGainsPositions(trades: GainsApiTrade[], cutoffMs: number): PositionSlice[] { - const OPEN_ACTIONS = new Set(["MarketOpened", "LimitOrderExecuted"]); + // v5 names: MarketOpened, LimitOrderExecuted — v6 names: TradeOpenedMarket, TradeOpenedLimit + const OPEN_ACTIONS = new Set(["MarketOpened", "LimitOrderExecuted", "TradeOpenedMarket", "TradeOpenedLimit"]); + // TradePosSizeIncrease updates the position size; use latest size as notional + const INCREASE_ACTIONS = new Set(["TradePosSizeIncrease"]); const CLOSE_ACTIONS = new Set(["TradeClosedMarket", "TradeClosedTP", "TradeClosedSL", "TradeClosedLIQ"]); const byId = new Map(); @@ -1118,6 +1121,9 @@ function reconstructGainsPositions(trades: GainsApiTrade[], cutoffMs: number): P if (!byId.has(t.id)) byId.set(t.id, {}); const e = byId.get(t.id)!; if (OPEN_ACTIONS.has(t.action)) e.open = t; + else if (INCREASE_ACTIONS.has(t.action) && e.open) { + e.open = { ...e.open, size: t.size, leverage: t.leverage }; + } else if (CLOSE_ACTIONS.has(t.action)) e.close = t; } From 26a1ef1d4c439c442c020b2af0ddeb18ee0481b4 Mon Sep 17 00:00:00 2001 From: Flotapponnier <160007691+Flotapponnier@users.noreply.github.com> Date: Sat, 29 Aug 2026 16:31:11 +0200 Subject: [PATCH 05/15] fix: Gains carry projection (v6 action names + still-open positions) * fix(cache): strip heavy fields from aggregate blob to pass 2MB limit Hub pages only need card data. Drop seriesByRegion24h, metricPanels, editorial text (seoIntro/faq/findings/methodology/disclaimer/abstract/ perChainExplainer) from the blob aggregate slim. Cuts serialized size from ~4.3 MB to under 2 MB so unstable_cache actually persists the entry instead of silently dropping it on every render. * fix: include still-open Gains positions in carry projection * fix: update Gains open action names to v6 API, handle TradePosSizeIncrease From 9aec777581fe338171ac53b3e20451151ac759e3 Mon Sep 17 00:00:00 2001 From: Flotapponnier <160007691+Flotapponnier@users.noreply.github.com> Date: Sat, 29 Aug 2026 17:20:12 +0200 Subject: [PATCH 06/15] fix: widen coin name column for long tickers (xyz:NVDA) * fix(cache): strip heavy fields from aggregate blob to pass 2MB limit Hub pages only need card data. Drop seriesByRegion24h, metricPanels, editorial text (seoIntro/faq/findings/methodology/disclaimer/abstract/ perChainExplainer) from the blob aggregate slim. Cuts serialized size from ~4.3 MB to under 2 MB so unstable_cache actually persists the entry instead of silently dropping it on every render. * fix: include still-open Gains positions in carry projection * fix: update Gains open action names to v6 API, handle TradePosSizeIncrease * fix: widen coin name column in top markets to fit xyz:NVDA --- src/components/fee-compare-client.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/fee-compare-client.tsx b/src/components/fee-compare-client.tsx index 6e931a1c..a7ada7a7 100644 --- a/src/components/fee-compare-client.tsx +++ b/src/components/fee-compare-client.tsx @@ -956,7 +956,7 @@ function HlTopCoinsCard({ key={c.coin} className="flex items-center gap-3 px-5 py-3 hover:bg-ink/2 transition-colors" > - + {c.coin} {c.fills} fills From 4ee82730a3ac57d8cd11ea087de1270d53da2436 Mon Sep 17 00:00:00 2001 From: Flotapponnier <160007691+Flotapponnier@users.noreply.github.com> Date: Sat, 29 Aug 2026 18:02:13 +0200 Subject: [PATCH 07/15] feat(rpc): add Union bench #253 (Nodes.Guru, Stake And Relax, High Stakes) (#2194) --- benchmarks/union-rpc.yml | 138 ++++++++++++++++++ .../rpc-capabilities/cmd/script/config.go | 11 ++ public/logos/union.svg | 4 + src/data/provider-registry.ts | 20 +++ src/lib/brand.ts | 3 +- src/lib/logo-manifest.ts | 1 + 6 files changed, 176 insertions(+), 1 deletion(-) create mode 100644 benchmarks/union-rpc.yml create mode 100644 public/logos/union.svg diff --git a/benchmarks/union-rpc.yml b/benchmarks/union-rpc.yml new file mode 100644 index 00000000..0373e481 --- /dev/null +++ b/benchmarks/union-rpc.yml @@ -0,0 +1,138 @@ +# OpenChainBench. Bench No 253 + +slug: union-rpc +number: "253" +title: Fastest free Union RPC, live no-key endpoint latency +seo_title: "Fastest free Union RPC 2026" +seo_description: "{{best_name}} leads free Union RPC at {{best_p50}} (block height p50, 24h). 3 providers measured every 60s from 3 regions." +subtitle: HTTP round-trip latency for Tendermint /status queries against every available public Union (union-1) endpoint, audited every 60 seconds from 3 regions. + +category: RPCs +status: live +metric: RPC latency +unit: ms +higher_is_better: false + +seo_intro: | + Union is a trust-minimised, zero-knowledge cross-chain protocol built on a custom Cosmos SDK chain (chain ID union-1). It enables secure interoperability between Cosmos, EVM, and other ecosystems via ZK proof-based consensus verification — no external validators or multisigs required. The chain uses the standard Tendermint RPC interface; block height is fetched via the /status endpoint. Public keyless RPC nodes are provided by Nodes.Guru, Stake And Relax, and High Stakes. Every provider was live-verified with consecutive block-height probes at launch. + +abstract: | + Per-chain member of the RPC latency cluster, extended to Union (union-1). + We measure the round-trip latency of a Tendermint /status query against + every available public Union endpoint that sustains continuous probing: + 3 providers at launch, every 60 seconds, from us-east, eu-west and + Singapore. Each provider is probed via a GET /status request + from which the block number is extracted. + The harness classifies every response (ok / http_err / jsonrpc_err / stale / timeout) + with a Cosmos-scaled staleness gap (20 blocks, around 80 s at 4 s/block). + The cross-chain view lives on the parent rpc-capabilities benchmark; + this page is the Union-scoped answer with per-region breakdowns as + a first-class dimension. + +methodology: + - "Cadence: every 60 seconds per provider, from each of 3 probe regions (us-east Virginia, eu-west Amsterdam, sgp Singapore). Headline p50/p90/p99 aggregate across all 3 regions via Prometheus avg(quantile_over_time(...)); per-region breakdowns are first-class on this page via the region tabs." + - "Payload: GET /status. The result.sync_info.latest_block_height field (string-encoded integer) is extracted as the current block height." + - "Latency: client-side round-trip delta in milliseconds, exposed as both a gauge and a histogram (buckets 50 ms to 10 s), so percentiles are computed via Prometheus quantile_over_time over the last 24 hours." + - "Call-result classification: ok (parsable block height returned), http_err, jsonrpc_err, stale (block more than 20 behind the cross-provider tip), timeout." + - "This page is part of the per-chain RPC cluster derived from the cross-chain rpc-capabilities benchmark; the identical harness, cadence and exclusion rules apply on every chain." + - "Chain scope: every query on this page is pinned to chain=union. Provider coverage at launch: 3 endpoints (Nodes.Guru, Stake And Relax, High Stakes)." + +findings: + - "{{best_name}} leads free Union RPC at {{best_p50}} (Tendermint /status p50, 24h) across 3 measured providers." + +faq: + - q: "What is the fastest free Union RPC right now?" + a: "{{best_name}} currently leads at {{best_p50}} (Union block height p50 over the last 24h), measured against 3 providers probed every 60 seconds from us-east, eu-west and Singapore. The leaderboard re-sorts continuously against fresh Prometheus samples." + - q: "Which Union RPC endpoints work without an API key?" + a: "3 endpoints sustain continuous keyless probing at launch: Nodes.Guru (rpc-1.union.nodes.guru), Stake And Relax (union-rpc.stakeandrelax.net), and High Stakes (union-rpc.highstakes.ch). Every listed endpoint was live-verified before inclusion." + - q: "What is Union and why does its RPC latency matter?" + a: "Union is a ZK-based cross-chain interoperability protocol with a native Cosmos SDK chain (union-1). It connects blockchains without trusted intermediaries by verifying consensus proofs on-chain. Developers building cross-chain applications, bridges, or omnichain protocols on Union need reliable low-latency RPC access to query transactions, proofs, and chain state." + - q: "Does the fastest Union RPC change by region?" + a: "Often. Community validators like Nodes.Guru and High Stakes host in different datacentres. The region tabs re-scope every number to a single probe origin so you can pick the best endpoint for your user base." + - q: "What makes Union different from other cross-chain protocols?" + a: "Union replaces trusted validator sets and multisigs with ZK consensus proofs, making cross-chain message passing verifiable on-chain. This means no external trust assumptions — the security of the bridge reduces to the security of the underlying chains and ZK proof system." + +source: https://github.com/ChainBench/OpenChainBench/tree/main/harnesses/rpc-capabilities + +prometheus: + window: 24h + freshness_metric: rpc_latency_milliseconds + +rank_matrix_query: avg by (provider, region) (ocb:rpc_latency_milliseconds:p50_24h{chain="union"}) + +dimensions: + region: + - { value: all, label: All regions } + - { value: us-east, label: US-East } + - { value: eu-west, label: EU-West } + - { value: sgp, label: Singapore } + +providers: + - slug: nodes-guru + name: Nodes.Guru + tag: Nodes.Guru public Union RPC, no API key required + formula: "50th percentile over 24h of client-side round-trip latency (ms) for a GET /status sent every 60s from 3 regions to rpc-1.union.nodes.guru." + queries: + p50: avg(ocb:rpc_latency_milliseconds:p50_24h{provider="nodes-guru", chain="union"}) + p90: avg(ocb:rpc_latency_milliseconds:p90_24h{provider="nodes-guru", chain="union"}) + p99: avg(ocb:rpc_latency_milliseconds:p99_24h{provider="nodes-guru", chain="union"}) + mean: avg(ocb:rpc_latency_milliseconds:mean_24h{provider="nodes-guru", chain="union"}) + success: sum(ocb:rpc_call:ok_rate_24h{provider="nodes-guru", chain="union"}) / sum(ocb:rpc_call:rate_24h{provider="nodes-guru", chain="union"}) + sample_size: sum(ocb:rpc_call:increase_24h{provider="nodes-guru", chain="union"}) + series: avg(avg_over_time(rpc_latency_milliseconds{provider="nodes-guru", chain="union"}[1h])) + regions: + - region: us-east + p50: avg(ocb:rpc_latency_milliseconds:p50_24h{provider="nodes-guru", chain="union", region="us-east"}) + series: avg_over_time(rpc_latency_milliseconds{provider="nodes-guru", chain="union", region="us-east"}[1h]) + - region: eu-west + p50: avg(ocb:rpc_latency_milliseconds:p50_24h{provider="nodes-guru", chain="union", region="eu-west"}) + series: avg_over_time(rpc_latency_milliseconds{provider="nodes-guru", chain="union", region="eu-west"}[1h]) + - region: ap-southeast + p50: avg(ocb:rpc_latency_milliseconds:p50_24h{provider="nodes-guru", chain="union", region="sgp"}) + series: avg_over_time(rpc_latency_milliseconds{provider="nodes-guru", chain="union", region="sgp"}[1h]) + + - slug: stakeandrelax + name: Stake And Relax + tag: Stake And Relax public Union RPC, no API key required + formula: "50th percentile over 24h of client-side round-trip latency (ms) for a GET /status sent every 60s from 3 regions to union-rpc.stakeandrelax.net." + queries: + p50: avg(ocb:rpc_latency_milliseconds:p50_24h{provider="stakeandrelax", chain="union"}) + p90: avg(ocb:rpc_latency_milliseconds:p90_24h{provider="stakeandrelax", chain="union"}) + p99: avg(ocb:rpc_latency_milliseconds:p99_24h{provider="stakeandrelax", chain="union"}) + mean: avg(ocb:rpc_latency_milliseconds:mean_24h{provider="stakeandrelax", chain="union"}) + success: sum(ocb:rpc_call:ok_rate_24h{provider="stakeandrelax", chain="union"}) / sum(ocb:rpc_call:rate_24h{provider="stakeandrelax", chain="union"}) + sample_size: sum(ocb:rpc_call:increase_24h{provider="stakeandrelax", chain="union"}) + series: avg(avg_over_time(rpc_latency_milliseconds{provider="stakeandrelax", chain="union"}[1h])) + regions: + - region: us-east + p50: avg(ocb:rpc_latency_milliseconds:p50_24h{provider="stakeandrelax", chain="union", region="us-east"}) + series: avg_over_time(rpc_latency_milliseconds{provider="stakeandrelax", chain="union", region="us-east"}[1h]) + - region: eu-west + p50: avg(ocb:rpc_latency_milliseconds:p50_24h{provider="stakeandrelax", chain="union", region="eu-west"}) + series: avg_over_time(rpc_latency_milliseconds{provider="stakeandrelax", chain="union", region="eu-west"}[1h]) + - region: ap-southeast + p50: avg(ocb:rpc_latency_milliseconds:p50_24h{provider="stakeandrelax", chain="union", region="sgp"}) + series: avg_over_time(rpc_latency_milliseconds{provider="stakeandrelax", chain="union", region="sgp"}[1h]) + + - slug: highstakes + name: High Stakes + tag: High Stakes public Union RPC, no API key required + formula: "50th percentile over 24h of client-side round-trip latency (ms) for a GET /status sent every 60s from 3 regions to union-rpc.highstakes.ch." + queries: + p50: avg(ocb:rpc_latency_milliseconds:p50_24h{provider="highstakes", chain="union"}) + p90: avg(ocb:rpc_latency_milliseconds:p90_24h{provider="highstakes", chain="union"}) + p99: avg(ocb:rpc_latency_milliseconds:p99_24h{provider="highstakes", chain="union"}) + mean: avg(ocb:rpc_latency_milliseconds:mean_24h{provider="highstakes", chain="union"}) + success: sum(ocb:rpc_call:ok_rate_24h{provider="highstakes", chain="union"}) / sum(ocb:rpc_call:rate_24h{provider="highstakes", chain="union"}) + sample_size: sum(ocb:rpc_call:increase_24h{provider="highstakes", chain="union"}) + series: avg(avg_over_time(rpc_latency_milliseconds{provider="highstakes", chain="union"}[1h])) + regions: + - region: us-east + p50: avg(ocb:rpc_latency_milliseconds:p50_24h{provider="highstakes", chain="union", region="us-east"}) + series: avg_over_time(rpc_latency_milliseconds{provider="highstakes", chain="union", region="us-east"}[1h]) + - region: eu-west + p50: avg(ocb:rpc_latency_milliseconds:p50_24h{provider="highstakes", chain="union", region="eu-west"}) + series: avg_over_time(rpc_latency_milliseconds{provider="highstakes", chain="union", region="eu-west"}[1h]) + - region: ap-southeast + p50: avg(ocb:rpc_latency_milliseconds:p50_24h{provider="highstakes", chain="union", region="sgp"}) + series: avg_over_time(rpc_latency_milliseconds{provider="highstakes", chain="union", region="sgp"}[1h]) diff --git a/harnesses/rpc-capabilities/cmd/script/config.go b/harnesses/rpc-capabilities/cmd/script/config.go index c985e447..18646067 100644 --- a/harnesses/rpc-capabilities/cmd/script/config.go +++ b/harnesses/rpc-capabilities/cmd/script/config.go @@ -1693,6 +1693,17 @@ func chains() []Chain { {Slug: "cosmos-directory", Name: "Cosmos Directory", URL: envDefault("RPC_URL_SENTINEL_COSMOSDIRECTORY", "https://rpc.cosmos.directory/sentinel")}, }, }, + // 2026-08-29 wave-12. Union — Cosmos SDK (union-1), Tendermint /status. Nodes.Guru + Stake And Relax + High Stakes. + { + Slug: "union", + Name: "Union", + Kind: "cosmos", + Providers: []Provider{ + {Slug: "nodes-guru", Name: "Nodes.Guru", URL: envDefault("RPC_URL_UNION_NODESGURU", "https://rpc-1.union.nodes.guru")}, + {Slug: "stakeandrelax", Name: "Stake And Relax", URL: envDefault("RPC_URL_UNION_STAKEANDRELAX", "https://union-rpc.stakeandrelax.net")}, + {Slug: "highstakes", Name: "High Stakes", URL: envDefault("RPC_URL_UNION_HIGHSTAKES", "https://union-rpc.highstakes.ch")}, + }, + }, // 2026-08-28 wave-11. Fetch.ai (FetchHub-4) — Cosmos SDK, Tendermint /status. Official + PublicNode + Cosmos Directory. { Slug: "fetchhub", diff --git a/public/logos/union.svg b/public/logos/union.svg new file mode 100644 index 00000000..a1957ae2 --- /dev/null +++ b/public/logos/union.svg @@ -0,0 +1,4 @@ + + + UNO + diff --git a/src/data/provider-registry.ts b/src/data/provider-registry.ts index a3e40769..9046f4ba 100644 --- a/src/data/provider-registry.ts +++ b/src/data/provider-registry.ts @@ -2619,6 +2619,26 @@ export const PROVIDER_REGISTRY: Record = { "Fetch.ai official public Tendermint RPC node for the FetchHub-4 mainnet. Keyless endpoint maintained by the Fetch.ai / ASI Alliance team.", twitter: "@Fetch_ai", }, + + // ─── Union providers (bench 253) ───────────────────────────────────── + "nodes-guru": { + url: "https://nodes.guru", + description: + "Nodes.Guru community validator and public RPC operator. Runs keyless Tendermint RPC endpoints for multiple Cosmos SDK chains including Union.", + twitter: "@nodes_guru", + }, + stakeandrelax: { + url: "https://stakeandrelax.net", + description: + "Stake And Relax community validator providing public keyless Tendermint RPC for Cosmos SDK chains including Union.", + twitter: "@StakeAndRelax", + }, + highstakes: { + url: "https://highstakes.ch", + description: + "High Stakes Swiss validator and public RPC operator. Provides keyless Tendermint RPC endpoints for multiple Cosmos SDK chains including Union.", + twitter: "@HighStakesCH", + }, }; /** diff --git a/src/lib/brand.ts b/src/lib/brand.ts index 8fb6c311..453dc405 100644 --- a/src/lib/brand.ts +++ b/src/lib/brand.ts @@ -173,11 +173,12 @@ const BRANDS: Record = { acala: { color: "#E40C5B" }, // acala red/pink (official brand) interlay: { color: "#1A3BDB" }, // interlay blue (official brand) - // ─── Cosmos SDK chains (benches 247, 250-252) ─── + // ─── Cosmos SDK chains (benches 247, 250-253) ─── babylon: { color: "#F8811A" }, // babylon orange (official brand) chihuahua: { color: "#E05F2A" }, // chihuahua orange-red (official brand) sentinel: { color: "#00C2FF" }, // sentinel cyan (official brand) fetchhub: { color: "#3B2D8E" }, // fetch.ai deep purple (official brand) + union: { color: "#6366F1" }, // union indigo (brand kit) "cosmos-directory": { color: "#1B1B2F", dark: true }, // cosmos directory dark navy // ─── Bitcoin Cash chain + providers (bench 244) ─── diff --git a/src/lib/logo-manifest.ts b/src/lib/logo-manifest.ts index 3fca81c4..d221da4d 100644 --- a/src/lib/logo-manifest.ts +++ b/src/lib/logo-manifest.ts @@ -331,6 +331,7 @@ const RAW: Record = { chihuahua: "/logos/chihuahua.svg", sentinel: "/logos/sentinel.svg", fetchhub: "/logos/fetchai.svg", + union: "/logos/union.svg", "cosmos-directory": "/logos/cosmos-directory.svg", // ─── Oracle deviation (bench 025) — additional brand logos ─── From e1def893b3c5d0dad593be2dfcc13bd36c20b5ce Mon Sep 17 00:00:00 2001 From: Flotapponnier <160007691+Flotapponnier@users.noreply.github.com> Date: Sat, 29 Aug 2026 18:14:01 +0200 Subject: [PATCH 08/15] fix(union-rpc): remove em dashes from YAML (#2196) --- benchmarks/union-rpc.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/benchmarks/union-rpc.yml b/benchmarks/union-rpc.yml index 0373e481..c075123a 100644 --- a/benchmarks/union-rpc.yml +++ b/benchmarks/union-rpc.yml @@ -14,7 +14,7 @@ unit: ms higher_is_better: false seo_intro: | - Union is a trust-minimised, zero-knowledge cross-chain protocol built on a custom Cosmos SDK chain (chain ID union-1). It enables secure interoperability between Cosmos, EVM, and other ecosystems via ZK proof-based consensus verification — no external validators or multisigs required. The chain uses the standard Tendermint RPC interface; block height is fetched via the /status endpoint. Public keyless RPC nodes are provided by Nodes.Guru, Stake And Relax, and High Stakes. Every provider was live-verified with consecutive block-height probes at launch. + Union is a trust-minimised, zero-knowledge cross-chain protocol built on a custom Cosmos SDK chain (chain ID union-1). It enables secure interoperability between Cosmos, EVM, and other ecosystems via ZK proof-based consensus verification, with no external validators or multisigs required. The chain uses the standard Tendermint RPC interface; block height is fetched via the /status endpoint. Public keyless RPC nodes are provided by Nodes.Guru, Stake And Relax, and High Stakes. Every provider was live-verified with consecutive block-height probes at launch. abstract: | Per-chain member of the RPC latency cluster, extended to Union (union-1). @@ -50,7 +50,7 @@ faq: - q: "Does the fastest Union RPC change by region?" a: "Often. Community validators like Nodes.Guru and High Stakes host in different datacentres. The region tabs re-scope every number to a single probe origin so you can pick the best endpoint for your user base." - q: "What makes Union different from other cross-chain protocols?" - a: "Union replaces trusted validator sets and multisigs with ZK consensus proofs, making cross-chain message passing verifiable on-chain. This means no external trust assumptions — the security of the bridge reduces to the security of the underlying chains and ZK proof system." + a: "Union replaces trusted validator sets and multisigs with ZK consensus proofs, making cross-chain message passing verifiable on-chain. This means no external trust assumptions: the security of the bridge reduces to the security of the underlying chains and ZK proof system." source: https://github.com/ChainBench/OpenChainBench/tree/main/harnesses/rpc-capabilities From 9591ec1ba243e6669b204b096ced9c4cc3c76f9f Mon Sep 17 00:00:00 2001 From: Flotapponnier <160007691+Flotapponnier@users.noreply.github.com> Date: Sat, 29 Aug 2026 19:15:56 +0200 Subject: [PATCH 09/15] feat(rpc): Shentu #254 + MANTRA Chain #255 (#2199) * feat(rpc): add Shentu bench #254 and MANTRA Chain bench #255 * fix: remove accidental dev-portal submodule --- benchmarks/mantrachain-rpc.yml | 138 ++++++++++++++++++ benchmarks/shentu-rpc.yml | 138 ++++++++++++++++++ .../rpc-capabilities/cmd/script/config.go | 22 +++ public/logos/itrocket.svg | 5 + public/logos/mantrachain.svg | 4 + public/logos/shentu-official.svg | 4 + public/logos/shentu.svg | 4 + src/data/provider-registry.ts | 22 +++ src/lib/brand.ts | 18 ++- src/lib/logo-manifest.ts | 8 + 10 files changed, 357 insertions(+), 6 deletions(-) create mode 100644 benchmarks/mantrachain-rpc.yml create mode 100644 benchmarks/shentu-rpc.yml create mode 100644 public/logos/itrocket.svg create mode 100644 public/logos/mantrachain.svg create mode 100644 public/logos/shentu-official.svg create mode 100644 public/logos/shentu.svg diff --git a/benchmarks/mantrachain-rpc.yml b/benchmarks/mantrachain-rpc.yml new file mode 100644 index 00000000..e68ac84f --- /dev/null +++ b/benchmarks/mantrachain-rpc.yml @@ -0,0 +1,138 @@ +# OpenChainBench. Bench No 255 + +slug: mantrachain-rpc +number: "255" +title: Fastest free MANTRA RPC, live no-key endpoint latency +seo_title: "Fastest free MANTRA Chain RPC 2026" +seo_description: "{{best_name}} leads free MANTRA RPC at {{best_p50}} (block height p50, 24h). 3 providers measured every 60s from 3 regions." +subtitle: HTTP round-trip latency for Tendermint /status queries against every available public MANTRA Chain (mantra-1) endpoint, audited every 60 seconds from 3 regions. + +category: RPCs +status: live +metric: RPC latency +unit: ms +higher_is_better: false + +seo_intro: | + MANTRA Chain is a Cosmos SDK blockchain (chain ID mantra-1) purpose-built for real-world asset (RWA) tokenization. It is a permissioned, regulatory-compliant Layer 1 focused on bringing tokenized financial assets on-chain, including real estate, bonds, and commodities. The chain uses the standard Tendermint RPC interface; block height is fetched via the /status endpoint. Public keyless RPC nodes are provided by the MANTRA official team, ITRocket, and Polkachu. Every provider was live-verified with consecutive block-height probes at launch. + +abstract: | + Per-chain member of the RPC latency cluster, extended to MANTRA Chain (mantra-1). + We measure the round-trip latency of a Tendermint /status query against + every available public MANTRA endpoint that sustains continuous probing: + 3 providers at launch, every 60 seconds, from us-east, eu-west and + Singapore. Each provider is probed via a GET /status request + from which the block number is extracted. + The harness classifies every response (ok / http_err / jsonrpc_err / stale / timeout) + with a Cosmos-scaled staleness gap (20 blocks, around 120 s at 6 s/block). + The cross-chain view lives on the parent rpc-capabilities benchmark; + this page is the MANTRA-scoped answer with per-region breakdowns as + a first-class dimension. + +methodology: + - "Cadence: every 60 seconds per provider, from each of 3 probe regions (us-east Virginia, eu-west Amsterdam, sgp Singapore). Headline p50/p90/p99 aggregate across all 3 regions via Prometheus avg(quantile_over_time(...)); per-region breakdowns are first-class on this page via the region tabs." + - "Payload: GET /status. The result.sync_info.latest_block_height field (string-encoded integer) is extracted as the current block height." + - "Latency: client-side round-trip delta in milliseconds, exposed as both a gauge and a histogram (buckets 50 ms to 10 s), so percentiles are computed via Prometheus quantile_over_time over the last 24 hours." + - "Call-result classification: ok (parsable block height returned), http_err, jsonrpc_err, stale (block more than 20 behind the cross-provider tip), timeout." + - "This page is part of the per-chain RPC cluster derived from the cross-chain rpc-capabilities benchmark; the identical harness, cadence and exclusion rules apply on every chain." + - "Chain scope: every query on this page is pinned to chain=mantrachain. Provider coverage at launch: 3 endpoints (MANTRA official, ITRocket, Polkachu)." + +findings: + - "{{best_name}} leads free MANTRA Chain RPC at {{best_p50}} (Tendermint /status p50, 24h) across 3 measured providers." + +faq: + - q: "What is the fastest free MANTRA Chain RPC right now?" + a: "{{best_name}} currently leads at {{best_p50}} (MANTRA block height p50 over the last 24h), measured against 3 providers probed every 60 seconds from us-east, eu-west and Singapore. The leaderboard re-sorts continuously against fresh Prometheus samples." + - q: "Which MANTRA Chain RPC endpoints work without an API key?" + a: "3 endpoints sustain continuous keyless probing at launch: MANTRA official (rpc.mantrachain.io), ITRocket (mantra-mainnet-rpc.itrocket.net), and Polkachu (mantra-rpc.polkachu.com). Every listed endpoint was live-verified before inclusion." + - q: "What is MANTRA Chain and why does its RPC latency matter?" + a: "MANTRA Chain is a Cosmos SDK Layer 1 built for real-world asset tokenization under regulatory frameworks. It enables compliant issuance and trading of tokenized financial assets such as real estate, bonds, and commodities. Developers building RWA applications, compliance tooling, or DeFi protocols on MANTRA need reliable low-latency RPC access to query asset state, transactions, and governance." + - q: "Does the fastest MANTRA RPC change by region?" + a: "Often. The official MANTRA node and community validators are hosted across different regions. The region tabs re-scope every number to a single probe origin so you can pick the best endpoint for your user base." + - q: "What is the OM token on MANTRA Chain?" + a: "OM is the native staking and governance token of MANTRA Chain. It is used for validator staking, on-chain governance, and fee payment. The mantra-1 mainnet launched in 2024 with a focus on regulated RWA markets in the Middle East and Asia." + +source: https://github.com/ChainBench/OpenChainBench/tree/main/harnesses/rpc-capabilities + +prometheus: + window: 24h + freshness_metric: rpc_latency_milliseconds + +rank_matrix_query: avg by (provider, region) (ocb:rpc_latency_milliseconds:p50_24h{chain="mantrachain"}) + +dimensions: + region: + - { value: all, label: All regions } + - { value: us-east, label: US-East } + - { value: eu-west, label: EU-West } + - { value: sgp, label: Singapore } + +providers: + - slug: mantrachain-official + name: MANTRA + tag: MANTRA Chain official public RPC node, no API key required + formula: "50th percentile over 24h of client-side round-trip latency (ms) for a GET /status sent every 60s from 3 regions to rpc.mantrachain.io." + queries: + p50: avg(ocb:rpc_latency_milliseconds:p50_24h{provider="mantrachain-official", chain="mantrachain"}) + p90: avg(ocb:rpc_latency_milliseconds:p90_24h{provider="mantrachain-official", chain="mantrachain"}) + p99: avg(ocb:rpc_latency_milliseconds:p99_24h{provider="mantrachain-official", chain="mantrachain"}) + mean: avg(ocb:rpc_latency_milliseconds:mean_24h{provider="mantrachain-official", chain="mantrachain"}) + success: sum(ocb:rpc_call:ok_rate_24h{provider="mantrachain-official", chain="mantrachain"}) / sum(ocb:rpc_call:rate_24h{provider="mantrachain-official", chain="mantrachain"}) + sample_size: sum(ocb:rpc_call:increase_24h{provider="mantrachain-official", chain="mantrachain"}) + series: avg(avg_over_time(rpc_latency_milliseconds{provider="mantrachain-official", chain="mantrachain"}[1h])) + regions: + - region: us-east + p50: avg(ocb:rpc_latency_milliseconds:p50_24h{provider="mantrachain-official", chain="mantrachain", region="us-east"}) + series: avg_over_time(rpc_latency_milliseconds{provider="mantrachain-official", chain="mantrachain", region="us-east"}[1h]) + - region: eu-west + p50: avg(ocb:rpc_latency_milliseconds:p50_24h{provider="mantrachain-official", chain="mantrachain", region="eu-west"}) + series: avg_over_time(rpc_latency_milliseconds{provider="mantrachain-official", chain="mantrachain", region="eu-west"}[1h]) + - region: ap-southeast + p50: avg(ocb:rpc_latency_milliseconds:p50_24h{provider="mantrachain-official", chain="mantrachain", region="sgp"}) + series: avg_over_time(rpc_latency_milliseconds{provider="mantrachain-official", chain="mantrachain", region="sgp"}[1h]) + + - slug: itrocket + name: ITRocket + tag: ITRocket public MANTRA Chain RPC, no API key required + formula: "50th percentile over 24h of client-side round-trip latency (ms) for a GET /status sent every 60s from 3 regions to mantra-mainnet-rpc.itrocket.net." + queries: + p50: avg(ocb:rpc_latency_milliseconds:p50_24h{provider="itrocket", chain="mantrachain"}) + p90: avg(ocb:rpc_latency_milliseconds:p90_24h{provider="itrocket", chain="mantrachain"}) + p99: avg(ocb:rpc_latency_milliseconds:p99_24h{provider="itrocket", chain="mantrachain"}) + mean: avg(ocb:rpc_latency_milliseconds:mean_24h{provider="itrocket", chain="mantrachain"}) + success: sum(ocb:rpc_call:ok_rate_24h{provider="itrocket", chain="mantrachain"}) / sum(ocb:rpc_call:rate_24h{provider="itrocket", chain="mantrachain"}) + sample_size: sum(ocb:rpc_call:increase_24h{provider="itrocket", chain="mantrachain"}) + series: avg(avg_over_time(rpc_latency_milliseconds{provider="itrocket", chain="mantrachain"}[1h])) + regions: + - region: us-east + p50: avg(ocb:rpc_latency_milliseconds:p50_24h{provider="itrocket", chain="mantrachain", region="us-east"}) + series: avg_over_time(rpc_latency_milliseconds{provider="itrocket", chain="mantrachain", region="us-east"}[1h]) + - region: eu-west + p50: avg(ocb:rpc_latency_milliseconds:p50_24h{provider="itrocket", chain="mantrachain", region="eu-west"}) + series: avg_over_time(rpc_latency_milliseconds{provider="itrocket", chain="mantrachain", region="eu-west"}[1h]) + - region: ap-southeast + p50: avg(ocb:rpc_latency_milliseconds:p50_24h{provider="itrocket", chain="mantrachain", region="sgp"}) + series: avg_over_time(rpc_latency_milliseconds{provider="itrocket", chain="mantrachain", region="sgp"}[1h]) + + - slug: polkachu + name: Polkachu + tag: Polkachu public MANTRA Chain RPC, no API key required + formula: "50th percentile over 24h of client-side round-trip latency (ms) for a GET /status sent every 60s from 3 regions to mantra-rpc.polkachu.com." + queries: + p50: avg(ocb:rpc_latency_milliseconds:p50_24h{provider="polkachu", chain="mantrachain"}) + p90: avg(ocb:rpc_latency_milliseconds:p90_24h{provider="polkachu", chain="mantrachain"}) + p99: avg(ocb:rpc_latency_milliseconds:p99_24h{provider="polkachu", chain="mantrachain"}) + mean: avg(ocb:rpc_latency_milliseconds:mean_24h{provider="polkachu", chain="mantrachain"}) + success: sum(ocb:rpc_call:ok_rate_24h{provider="polkachu", chain="mantrachain"}) / sum(ocb:rpc_call:rate_24h{provider="polkachu", chain="mantrachain"}) + sample_size: sum(ocb:rpc_call:increase_24h{provider="polkachu", chain="mantrachain"}) + series: avg(avg_over_time(rpc_latency_milliseconds{provider="polkachu", chain="mantrachain"}[1h])) + regions: + - region: us-east + p50: avg(ocb:rpc_latency_milliseconds:p50_24h{provider="polkachu", chain="mantrachain", region="us-east"}) + series: avg_over_time(rpc_latency_milliseconds{provider="polkachu", chain="mantrachain", region="us-east"}[1h]) + - region: eu-west + p50: avg(ocb:rpc_latency_milliseconds:p50_24h{provider="polkachu", chain="mantrachain", region="eu-west"}) + series: avg_over_time(rpc_latency_milliseconds{provider="polkachu", chain="mantrachain", region="eu-west"}[1h]) + - region: ap-southeast + p50: avg(ocb:rpc_latency_milliseconds:p50_24h{provider="polkachu", chain="mantrachain", region="sgp"}) + series: avg_over_time(rpc_latency_milliseconds{provider="polkachu", chain="mantrachain", region="sgp"}[1h]) diff --git a/benchmarks/shentu-rpc.yml b/benchmarks/shentu-rpc.yml new file mode 100644 index 00000000..49d538b6 --- /dev/null +++ b/benchmarks/shentu-rpc.yml @@ -0,0 +1,138 @@ +# OpenChainBench. Bench No 254 + +slug: shentu-rpc +number: "254" +title: Fastest free Shentu RPC, live no-key endpoint latency +seo_title: "Fastest free Shentu RPC 2026" +seo_description: "{{best_name}} leads free Shentu RPC at {{best_p50}} (block height p50, 24h). 3 providers measured every 60s from 3 regions." +subtitle: HTTP round-trip latency for Tendermint /status queries against every available public Shentu (shentu-2.2) endpoint, audited every 60 seconds from 3 regions. + +category: RPCs +status: live +metric: RPC latency +unit: ms +higher_is_better: false + +seo_intro: | + Shentu is a Cosmos SDK blockchain (chain ID shentu-2.2) focused on blockchain security. It provides a decentralized security oracle, on-chain bug bounty platform (CertiK Shield), and formal verification tools for smart contracts. The chain uses the standard Tendermint RPC interface; block height is fetched via the /status endpoint. Public keyless RPC nodes are provided by the Shentu official team, Polkachu, and High Stakes. Every provider was live-verified with consecutive block-height probes at launch. + +abstract: | + Per-chain member of the RPC latency cluster, extended to Shentu (shentu-2.2). + We measure the round-trip latency of a Tendermint /status query against + every available public Shentu endpoint that sustains continuous probing: + 3 providers at launch, every 60 seconds, from us-east, eu-west and + Singapore. Each provider is probed via a GET /status request + from which the block number is extracted. + The harness classifies every response (ok / http_err / jsonrpc_err / stale / timeout) + with a Cosmos-scaled staleness gap (20 blocks, around 120 s at 6 s/block). + The cross-chain view lives on the parent rpc-capabilities benchmark; + this page is the Shentu-scoped answer with per-region breakdowns as + a first-class dimension. + +methodology: + - "Cadence: every 60 seconds per provider, from each of 3 probe regions (us-east Virginia, eu-west Amsterdam, sgp Singapore). Headline p50/p90/p99 aggregate across all 3 regions via Prometheus avg(quantile_over_time(...)); per-region breakdowns are first-class on this page via the region tabs." + - "Payload: GET /status. The result.sync_info.latest_block_height field (string-encoded integer) is extracted as the current block height." + - "Latency: client-side round-trip delta in milliseconds, exposed as both a gauge and a histogram (buckets 50 ms to 10 s), so percentiles are computed via Prometheus quantile_over_time over the last 24 hours." + - "Call-result classification: ok (parsable block height returned), http_err, jsonrpc_err, stale (block more than 20 behind the cross-provider tip), timeout." + - "This page is part of the per-chain RPC cluster derived from the cross-chain rpc-capabilities benchmark; the identical harness, cadence and exclusion rules apply on every chain." + - "Chain scope: every query on this page is pinned to chain=shentu. Provider coverage at launch: 3 endpoints (Shentu official, Polkachu, High Stakes)." + +findings: + - "{{best_name}} leads free Shentu RPC at {{best_p50}} (Tendermint /status p50, 24h) across 3 measured providers." + +faq: + - q: "What is the fastest free Shentu RPC right now?" + a: "{{best_name}} currently leads at {{best_p50}} (Shentu block height p50 over the last 24h), measured against 3 providers probed every 60 seconds from us-east, eu-west and Singapore. The leaderboard re-sorts continuously against fresh Prometheus samples." + - q: "Which Shentu RPC endpoints work without an API key?" + a: "3 endpoints sustain continuous keyless probing at launch: Shentu official (rpc.shentu.org), Polkachu (shentu-rpc.polkachu.com), and High Stakes (shentu-rpc.highstakes.ch). Every listed endpoint was live-verified before inclusion." + - q: "What is Shentu and why does its RPC latency matter?" + a: "Shentu is a Cosmos SDK blockchain built by CertiK, focused on blockchain security infrastructure. It powers the CertiK Shield decentralized reimbursement platform and a security oracle that scores smart contracts on-chain. Developers integrating with CertiK Shield, querying security scores, or building on the Shentu ecosystem need reliable low-latency RPC access." + - q: "Does the fastest Shentu RPC change by region?" + a: "Yes. The official Shentu node and community validators are hosted in different regions. The region tabs re-scope every number to a single probe origin so you can pick the best endpoint for your user base." + - q: "What is the CTK token on Shentu?" + a: "CTK (CertiK) is the native staking and governance token of the Shentu chain (denominated as uctk on-chain). It is used to stake in the CertiK Shield protection pool, pay for security oracle queries, and participate in on-chain governance." + +source: https://github.com/ChainBench/OpenChainBench/tree/main/harnesses/rpc-capabilities + +prometheus: + window: 24h + freshness_metric: rpc_latency_milliseconds + +rank_matrix_query: avg by (provider, region) (ocb:rpc_latency_milliseconds:p50_24h{chain="shentu"}) + +dimensions: + region: + - { value: all, label: All regions } + - { value: us-east, label: US-East } + - { value: eu-west, label: EU-West } + - { value: sgp, label: Singapore } + +providers: + - slug: shentu-official + name: Shentu + tag: Shentu official public RPC node, no API key required + formula: "50th percentile over 24h of client-side round-trip latency (ms) for a GET /status sent every 60s from 3 regions to rpc.shentu.org." + queries: + p50: avg(ocb:rpc_latency_milliseconds:p50_24h{provider="shentu-official", chain="shentu"}) + p90: avg(ocb:rpc_latency_milliseconds:p90_24h{provider="shentu-official", chain="shentu"}) + p99: avg(ocb:rpc_latency_milliseconds:p99_24h{provider="shentu-official", chain="shentu"}) + mean: avg(ocb:rpc_latency_milliseconds:mean_24h{provider="shentu-official", chain="shentu"}) + success: sum(ocb:rpc_call:ok_rate_24h{provider="shentu-official", chain="shentu"}) / sum(ocb:rpc_call:rate_24h{provider="shentu-official", chain="shentu"}) + sample_size: sum(ocb:rpc_call:increase_24h{provider="shentu-official", chain="shentu"}) + series: avg(avg_over_time(rpc_latency_milliseconds{provider="shentu-official", chain="shentu"}[1h])) + regions: + - region: us-east + p50: avg(ocb:rpc_latency_milliseconds:p50_24h{provider="shentu-official", chain="shentu", region="us-east"}) + series: avg_over_time(rpc_latency_milliseconds{provider="shentu-official", chain="shentu", region="us-east"}[1h]) + - region: eu-west + p50: avg(ocb:rpc_latency_milliseconds:p50_24h{provider="shentu-official", chain="shentu", region="eu-west"}) + series: avg_over_time(rpc_latency_milliseconds{provider="shentu-official", chain="shentu", region="eu-west"}[1h]) + - region: ap-southeast + p50: avg(ocb:rpc_latency_milliseconds:p50_24h{provider="shentu-official", chain="shentu", region="sgp"}) + series: avg_over_time(rpc_latency_milliseconds{provider="shentu-official", chain="shentu", region="sgp"}[1h]) + + - slug: polkachu + name: Polkachu + tag: Polkachu public Shentu RPC, no API key required + formula: "50th percentile over 24h of client-side round-trip latency (ms) for a GET /status sent every 60s from 3 regions to shentu-rpc.polkachu.com." + queries: + p50: avg(ocb:rpc_latency_milliseconds:p50_24h{provider="polkachu", chain="shentu"}) + p90: avg(ocb:rpc_latency_milliseconds:p90_24h{provider="polkachu", chain="shentu"}) + p99: avg(ocb:rpc_latency_milliseconds:p99_24h{provider="polkachu", chain="shentu"}) + mean: avg(ocb:rpc_latency_milliseconds:mean_24h{provider="polkachu", chain="shentu"}) + success: sum(ocb:rpc_call:ok_rate_24h{provider="polkachu", chain="shentu"}) / sum(ocb:rpc_call:rate_24h{provider="polkachu", chain="shentu"}) + sample_size: sum(ocb:rpc_call:increase_24h{provider="polkachu", chain="shentu"}) + series: avg(avg_over_time(rpc_latency_milliseconds{provider="polkachu", chain="shentu"}[1h])) + regions: + - region: us-east + p50: avg(ocb:rpc_latency_milliseconds:p50_24h{provider="polkachu", chain="shentu", region="us-east"}) + series: avg_over_time(rpc_latency_milliseconds{provider="polkachu", chain="shentu", region="us-east"}[1h]) + - region: eu-west + p50: avg(ocb:rpc_latency_milliseconds:p50_24h{provider="polkachu", chain="shentu", region="eu-west"}) + series: avg_over_time(rpc_latency_milliseconds{provider="polkachu", chain="shentu", region="eu-west"}[1h]) + - region: ap-southeast + p50: avg(ocb:rpc_latency_milliseconds:p50_24h{provider="polkachu", chain="shentu", region="sgp"}) + series: avg_over_time(rpc_latency_milliseconds{provider="polkachu", chain="shentu", region="sgp"}[1h]) + + - slug: highstakes + name: High Stakes + tag: High Stakes public Shentu RPC, no API key required + formula: "50th percentile over 24h of client-side round-trip latency (ms) for a GET /status sent every 60s from 3 regions to shentu-rpc.highstakes.ch." + queries: + p50: avg(ocb:rpc_latency_milliseconds:p50_24h{provider="highstakes", chain="shentu"}) + p90: avg(ocb:rpc_latency_milliseconds:p90_24h{provider="highstakes", chain="shentu"}) + p99: avg(ocb:rpc_latency_milliseconds:p99_24h{provider="highstakes", chain="shentu"}) + mean: avg(ocb:rpc_latency_milliseconds:mean_24h{provider="highstakes", chain="shentu"}) + success: sum(ocb:rpc_call:ok_rate_24h{provider="highstakes", chain="shentu"}) / sum(ocb:rpc_call:rate_24h{provider="highstakes", chain="shentu"}) + sample_size: sum(ocb:rpc_call:increase_24h{provider="highstakes", chain="shentu"}) + series: avg(avg_over_time(rpc_latency_milliseconds{provider="highstakes", chain="shentu"}[1h])) + regions: + - region: us-east + p50: avg(ocb:rpc_latency_milliseconds:p50_24h{provider="highstakes", chain="shentu", region="us-east"}) + series: avg_over_time(rpc_latency_milliseconds{provider="highstakes", chain="shentu", region="us-east"}[1h]) + - region: eu-west + p50: avg(ocb:rpc_latency_milliseconds:p50_24h{provider="highstakes", chain="shentu", region="eu-west"}) + series: avg_over_time(rpc_latency_milliseconds{provider="highstakes", chain="shentu", region="eu-west"}[1h]) + - region: ap-southeast + p50: avg(ocb:rpc_latency_milliseconds:p50_24h{provider="highstakes", chain="shentu", region="sgp"}) + series: avg_over_time(rpc_latency_milliseconds{provider="highstakes", chain="shentu", region="sgp"}[1h]) diff --git a/harnesses/rpc-capabilities/cmd/script/config.go b/harnesses/rpc-capabilities/cmd/script/config.go index 18646067..580bbb0e 100644 --- a/harnesses/rpc-capabilities/cmd/script/config.go +++ b/harnesses/rpc-capabilities/cmd/script/config.go @@ -1693,6 +1693,28 @@ func chains() []Chain { {Slug: "cosmos-directory", Name: "Cosmos Directory", URL: envDefault("RPC_URL_SENTINEL_COSMOSDIRECTORY", "https://rpc.cosmos.directory/sentinel")}, }, }, + // 2026-08-29 wave-12. Shentu — Cosmos SDK (shentu-2.2), Tendermint /status. Shentu official + Polkachu + High Stakes. + { + Slug: "shentu", + Name: "Shentu", + Kind: "cosmos", + Providers: []Provider{ + {Slug: "shentu-official", Name: "Shentu", URL: envDefault("RPC_URL_SHENTU_OFFICIAL", "https://rpc.shentu.org:443")}, + {Slug: "polkachu", Name: "Polkachu", URL: envDefault("RPC_URL_SHENTU_POLKACHU", "https://shentu-rpc.polkachu.com:443")}, + {Slug: "highstakes", Name: "High Stakes", URL: envDefault("RPC_URL_SHENTU_HIGHSTAKES", "https://shentu-rpc.highstakes.ch")}, + }, + }, + // 2026-08-29 wave-12. MANTRA Chain — Cosmos SDK (mantra-1), Tendermint /status. Official + ITRocket + Polkachu. + { + Slug: "mantrachain", + Name: "MANTRA Chain", + Kind: "cosmos", + Providers: []Provider{ + {Slug: "mantrachain-official", Name: "MANTRA", URL: envDefault("RPC_URL_MANTRA_OFFICIAL", "https://rpc.mantrachain.io")}, + {Slug: "itrocket", Name: "ITRocket", URL: envDefault("RPC_URL_MANTRA_ITROCKET", "https://mantra-mainnet-rpc.itrocket.net:443")}, + {Slug: "polkachu", Name: "Polkachu", URL: envDefault("RPC_URL_MANTRA_POLKACHU", "https://mantra-rpc.polkachu.com:443")}, + }, + }, // 2026-08-29 wave-12. Union — Cosmos SDK (union-1), Tendermint /status. Nodes.Guru + Stake And Relax + High Stakes. { Slug: "union", diff --git a/public/logos/itrocket.svg b/public/logos/itrocket.svg new file mode 100644 index 00000000..775cfe67 --- /dev/null +++ b/public/logos/itrocket.svg @@ -0,0 +1,5 @@ + + + ITROCKET + 🚀 + diff --git a/public/logos/mantrachain.svg b/public/logos/mantrachain.svg new file mode 100644 index 00000000..10bcec3c --- /dev/null +++ b/public/logos/mantrachain.svg @@ -0,0 +1,4 @@ + + + OM + diff --git a/public/logos/shentu-official.svg b/public/logos/shentu-official.svg new file mode 100644 index 00000000..4ca2baba --- /dev/null +++ b/public/logos/shentu-official.svg @@ -0,0 +1,4 @@ + + + CTK + diff --git a/public/logos/shentu.svg b/public/logos/shentu.svg new file mode 100644 index 00000000..4ca2baba --- /dev/null +++ b/public/logos/shentu.svg @@ -0,0 +1,4 @@ + + + CTK + diff --git a/src/data/provider-registry.ts b/src/data/provider-registry.ts index 9046f4ba..b2de7fc8 100644 --- a/src/data/provider-registry.ts +++ b/src/data/provider-registry.ts @@ -2620,6 +2620,28 @@ export const PROVIDER_REGISTRY: Record = { twitter: "@Fetch_ai", }, + // ─── Shentu providers (bench 254) ──────────────────────────────────── + "shentu-official": { + url: "https://www.shentu.technology", + description: + "Shentu Chain official public Tendermint RPC node for the shentu-2.2 mainnet. Keyless endpoint maintained by the CertiK / Shentu Foundation team.", + twitter: "@ShentuChain", + }, + + // ─── MANTRA Chain providers (bench 255) ────────────────────────────── + "mantrachain-official": { + url: "https://www.mantrachain.io", + description: + "MANTRA Chain official public Tendermint RPC node for the mantra-1 mainnet. Keyless endpoint for real-world asset tokenization on Cosmos.", + twitter: "@MANTRA_Chain", + }, + itrocket: { + url: "https://itrocket.net", + description: + "ITRocket community validator and public RPC operator. Provides keyless Tendermint RPC endpoints for multiple Cosmos SDK chains.", + twitter: "@ITRocketTeam", + }, + // ─── Union providers (bench 253) ───────────────────────────────────── "nodes-guru": { url: "https://nodes.guru", diff --git a/src/lib/brand.ts b/src/lib/brand.ts index 453dc405..0d2d6e05 100644 --- a/src/lib/brand.ts +++ b/src/lib/brand.ts @@ -173,12 +173,18 @@ const BRANDS: Record = { acala: { color: "#E40C5B" }, // acala red/pink (official brand) interlay: { color: "#1A3BDB" }, // interlay blue (official brand) - // ─── Cosmos SDK chains (benches 247, 250-253) ─── - babylon: { color: "#F8811A" }, // babylon orange (official brand) - chihuahua: { color: "#E05F2A" }, // chihuahua orange-red (official brand) - sentinel: { color: "#00C2FF" }, // sentinel cyan (official brand) - fetchhub: { color: "#3B2D8E" }, // fetch.ai deep purple (official brand) - union: { color: "#6366F1" }, // union indigo (brand kit) + // ─── Cosmos SDK chains (benches 247, 250-255) ─── + babylon: { color: "#F8811A" }, // babylon orange (official brand) + chihuahua: { color: "#E05F2A" }, // chihuahua orange-red (official brand) + sentinel: { color: "#00C2FF" }, // sentinel cyan (official brand) + fetchhub: { color: "#3B2D8E" }, // fetch.ai deep purple (official brand) + union: { color: "#6366F1" }, // union indigo (brand kit) + shentu: { color: "#1A6DFF" }, // shentu blue (certik brand) + mantrachain: { color: "#E8A020" }, // mantra gold (om token brand) + "nodes-guru": { color: "#F59E0B" }, // nodes.guru amber + stakeandrelax: { color: "#10B981" }, // stake and relax emerald + highstakes: { color: "#3B82F6" }, // high stakes blue + itrocket: { color: "#E53E3E" }, // itrocket red "cosmos-directory": { color: "#1B1B2F", dark: true }, // cosmos directory dark navy // ─── Bitcoin Cash chain + providers (bench 244) ─── diff --git a/src/lib/logo-manifest.ts b/src/lib/logo-manifest.ts index d221da4d..b5250d68 100644 --- a/src/lib/logo-manifest.ts +++ b/src/lib/logo-manifest.ts @@ -332,6 +332,12 @@ const RAW: Record = { sentinel: "/logos/sentinel.svg", fetchhub: "/logos/fetchai.svg", union: "/logos/union.svg", + shentu: "/logos/shentu.svg", + mantrachain: "/logos/mantrachain.svg", + "nodes-guru": "/logos/nodes-guru.svg", + stakeandrelax: "/logos/stakeandrelax.svg", + highstakes: "/logos/highstakes.svg", + itrocket: "/logos/itrocket.svg", "cosmos-directory": "/logos/cosmos-directory.svg", // ─── Oracle deviation (bench 025) — additional brand logos ─── @@ -744,6 +750,8 @@ const ALIASES: Record = { // Chihuahua + Fetch.ai official node aliases → chain logo "chihuahua-official": "chihuahua", "fetchai-official": "fetchhub", + "shentu-official": "shentu", + "mantrachain-official": "mantrachain", // Non-EVM wave-3 (benches 222-231) — provider-official aliases to chain slug "ecadinfra": "tezos", From 270e98b6054810f3a8855327cd48aeca1db6d36c Mon Sep 17 00:00:00 2001 From: Flotapponnier <160007691+Flotapponnier@users.noreply.github.com> Date: Sat, 29 Aug 2026 22:26:36 +0200 Subject: [PATCH 10/15] feat: Band Protocol #256 + cheqd #257 benches --- benchmarks/bandchain-rpc.yml | 35 +++++++++++++++++++ benchmarks/cheqd-rpc.yml | 35 +++++++++++++++++++ .../rpc-capabilities/cmd/script/config.go | 22 ++++++++++++ public/logos/bandchain.svg | 4 +++ public/logos/cheqd.svg | 4 +++ public/logos/nodestake.svg | 5 +++ public/logos/stakewolle.svg | 5 +++ src/data/provider-registry.ts | 30 ++++++++++++++++ src/lib/brand.ts | 8 +++++ src/lib/logo-manifest.ts | 6 ++++ 10 files changed, 154 insertions(+) create mode 100644 benchmarks/bandchain-rpc.yml create mode 100644 benchmarks/cheqd-rpc.yml create mode 100644 public/logos/bandchain.svg create mode 100644 public/logos/cheqd.svg create mode 100644 public/logos/nodestake.svg create mode 100644 public/logos/stakewolle.svg diff --git a/benchmarks/bandchain-rpc.yml b/benchmarks/bandchain-rpc.yml new file mode 100644 index 00000000..67a1c01f --- /dev/null +++ b/benchmarks/bandchain-rpc.yml @@ -0,0 +1,35 @@ +id: 256 +slug: bandchain-rpc +title: "Band Protocol RPC" +chain: bandchain +description: "Latency and availability benchmark for Band Protocol public RPC endpoints" +category: rpc +kind: cosmos + +providers: + - slug: band-official + name: "Band Protocol" + url: "http://rpc.laozi1.bandchain.org:80" + - slug: highstakes + name: "High Stakes" + url: "https://bandprotocol-rpc.highstakes.ch" + - slug: stakewolle + name: "Stakewolle" + url: "https://public.stakewolle.com/cosmos/bandchain/rpc" + +seo_intro: | + Band Protocol is a cross-chain oracle network built on Cosmos SDK that aggregates and connects real-world data and APIs to smart contracts. The laozi-mainnet hosts its decentralized data oracle infrastructure, enabling DeFi protocols across multiple blockchains to access tamper-proof price feeds. + + This benchmark continuously measures RPC latency, availability and block-height freshness across public Band Protocol Tendermint endpoints from three geographic regions. Use it to select the fastest endpoint for your integration or validator setup. + +faq: + - q: "What does this benchmark measure?" + a: "Each probe issues a GET /status request with anti-cache headers to retrieve the latest block height. We record round-trip latency (p50/p90/p99), HTTP availability, and whether the returned block height is current. Probes run every 30 seconds from US East, EU West, and AP Southeast." + - q: "Which endpoints are included?" + a: "The benchmark covers the Band Protocol official endpoint (rpc.laozi1.bandchain.org), High Stakes, and Stakewolle. All three are keyless public endpoints requiring no authentication." + - q: "Why does Band Protocol RPC performance matter?" + a: "Band Protocol validators and oracle scripts depend on reliable RPC access to submit data requests and retrieve oracle results. DeFi protocols integrating Band price feeds need low-latency RPC for real-time data consumption." + - q: "How are oracle scripts affected by RPC latency?" + a: "Yoda (the oracle daemon) and Bothan (data proxy) both rely on RPC to monitor pending data requests and submit responses within the request window. High latency or downtime directly reduces oracle reliability and can cause missed rewards." + - q: "Can I contribute an endpoint?" + a: "Yes. Open an issue or PR at github.com/ChainBench/OpenChainBench with the endpoint URL and operator name. We verify liveness and independence before adding." diff --git a/benchmarks/cheqd-rpc.yml b/benchmarks/cheqd-rpc.yml new file mode 100644 index 00000000..c5d02883 --- /dev/null +++ b/benchmarks/cheqd-rpc.yml @@ -0,0 +1,35 @@ +id: 257 +slug: cheqd-rpc +title: "cheqd RPC" +chain: cheqd +description: "Latency and availability benchmark for cheqd public RPC endpoints" +category: rpc +kind: cosmos + +providers: + - slug: cheqd-official + name: "cheqd" + url: "https://rpc.cheqd.net" + - slug: publicnode + name: "PublicNode" + url: "https://cheqd-rpc.publicnode.com:443" + - slug: nodestake + name: "NodeStake" + url: "https://rpc.cheqd.nodestake.org" + +seo_intro: | + cheqd is a purpose-built Cosmos SDK blockchain for decentralized identity, enabling self-sovereign identity (SSI) and verifiable credentials at scale. The cheqd-mainnet-1 network anchors DIDs and credential schemas used by enterprises, governments, and developers building trust infrastructure. + + This benchmark continuously measures RPC latency, availability and block-height freshness across public cheqd Tendermint endpoints from three geographic regions. Use it to select the most reliable endpoint for identity resolution, node operation, or application integration. + +faq: + - q: "What does this benchmark measure?" + a: "Each probe issues a GET /status request with anti-cache headers to retrieve the latest block height. We record round-trip latency (p50/p90/p99), HTTP availability, and whether the returned block height is current. Probes run every 30 seconds from US East, EU West, and AP Southeast." + - q: "Which endpoints are included?" + a: "The benchmark covers the official cheqd endpoint (rpc.cheqd.net), PublicNode, and NodeStake. All three are keyless public endpoints requiring no API key." + - q: "Why does cheqd RPC performance matter?" + a: "DID resolution, verifiable credential anchoring, and CHEQ token transfers all depend on RPC availability. Applications using the cheqd DID method resolve identifiers via RPC, making latency directly visible to end users." + - q: "How does RPC latency affect DID resolution?" + a: "The Universal Resolver and cheqd-specific resolvers query RPC endpoints to fetch DID documents. Slow or unavailable endpoints increase credential verification times and can break SSI flows in production." + - q: "Can I contribute an endpoint?" + a: "Yes. Open an issue or PR at github.com/ChainBench/OpenChainBench with the endpoint URL and operator name. We verify liveness and independence before adding." diff --git a/harnesses/rpc-capabilities/cmd/script/config.go b/harnesses/rpc-capabilities/cmd/script/config.go index 580bbb0e..dc35473f 100644 --- a/harnesses/rpc-capabilities/cmd/script/config.go +++ b/harnesses/rpc-capabilities/cmd/script/config.go @@ -1715,6 +1715,28 @@ func chains() []Chain { {Slug: "polkachu", Name: "Polkachu", URL: envDefault("RPC_URL_MANTRA_POLKACHU", "https://mantra-rpc.polkachu.com:443")}, }, }, + // 2026-08-29 wave-13. Band Protocol — Cosmos SDK (laozi-mainnet), Tendermint /status. Official + High Stakes + Stakewolle. + { + Slug: "bandchain", + Name: "Band Protocol", + Kind: "cosmos", + Providers: []Provider{ + {Slug: "band-official", Name: "Band Protocol", URL: envDefault("RPC_URL_BAND_OFFICIAL", "http://rpc.laozi1.bandchain.org:80")}, + {Slug: "highstakes", Name: "High Stakes", URL: envDefault("RPC_URL_BAND_HIGHSTAKES", "https://bandprotocol-rpc.highstakes.ch")}, + {Slug: "stakewolle", Name: "Stakewolle", URL: envDefault("RPC_URL_BAND_STAKEWOLLE", "https://public.stakewolle.com/cosmos/bandchain/rpc")}, + }, + }, + // 2026-08-29 wave-13. cheqd — Cosmos SDK (cheqd-mainnet-1), Tendermint /status. Official + PublicNode + NodeStake. + { + Slug: "cheqd", + Name: "cheqd", + Kind: "cosmos", + Providers: []Provider{ + {Slug: "cheqd-official", Name: "cheqd", URL: envDefault("RPC_URL_CHEQD_OFFICIAL", "https://rpc.cheqd.net")}, + {Slug: "publicnode", Name: "PublicNode", URL: envDefault("RPC_URL_CHEQD_PUBLICNODE", "https://cheqd-rpc.publicnode.com:443")}, + {Slug: "nodestake", Name: "NodeStake", URL: envDefault("RPC_URL_CHEQD_NODESTAKE", "https://rpc.cheqd.nodestake.org")}, + }, + }, // 2026-08-29 wave-12. Union — Cosmos SDK (union-1), Tendermint /status. Nodes.Guru + Stake And Relax + High Stakes. { Slug: "union", diff --git a/public/logos/bandchain.svg b/public/logos/bandchain.svg new file mode 100644 index 00000000..ec9ec2f2 --- /dev/null +++ b/public/logos/bandchain.svg @@ -0,0 +1,4 @@ + + + BAND + diff --git a/public/logos/cheqd.svg b/public/logos/cheqd.svg new file mode 100644 index 00000000..10461cae --- /dev/null +++ b/public/logos/cheqd.svg @@ -0,0 +1,4 @@ + + + CHEQD + diff --git a/public/logos/nodestake.svg b/public/logos/nodestake.svg new file mode 100644 index 00000000..609a7590 --- /dev/null +++ b/public/logos/nodestake.svg @@ -0,0 +1,5 @@ + + + NODE + STAKE + diff --git a/public/logos/stakewolle.svg b/public/logos/stakewolle.svg new file mode 100644 index 00000000..08a9774d --- /dev/null +++ b/public/logos/stakewolle.svg @@ -0,0 +1,5 @@ + + + STAKE + WOLLE + diff --git a/src/data/provider-registry.ts b/src/data/provider-registry.ts index b2de7fc8..dd0f2bd4 100644 --- a/src/data/provider-registry.ts +++ b/src/data/provider-registry.ts @@ -2642,6 +2642,36 @@ export const PROVIDER_REGISTRY: Record = { twitter: "@ITRocketTeam", }, + + // ─── Band Protocol providers (bench 256) ──────────────────────────── + "band-official": { + url: "https://www.bandprotocol.com", + description: + "Band Protocol official public Tendermint RPC node for the laozi-mainnet. Keyless endpoint maintained by the Band Protocol team.", + twitter: "@BandProtocol", + }, + stakewolle: { + url: "https://stakewolle.com", + description: + "Stakewolle community validator and public RPC provider. Runs keyless Tendermint RPC endpoints for multiple Cosmos SDK chains.", + twitter: "@stakewolle", + }, + + // ─── cheqd providers (bench 257) ───────────────────────────────────── + "cheqd-official": { + url: "https://cheqd.io", + description: + "cheqd official public Tendermint RPC node for cheqd-mainnet-1. Keyless endpoint maintained by the cheqd Network team.", + twitter: "@cheqd_io", + }, + nodestake: { + url: "https://nodestake.org", + description: + "NodeStake community validator and public RPC operator. Provides keyless Tendermint RPC endpoints for multiple Cosmos SDK chains.", + twitter: "@NodeStake", + }, + + // ─── Union providers (bench 253) ───────────────────────────────────── "nodes-guru": { url: "https://nodes.guru", diff --git a/src/lib/brand.ts b/src/lib/brand.ts index 0d2d6e05..7ce6a49e 100644 --- a/src/lib/brand.ts +++ b/src/lib/brand.ts @@ -187,6 +187,14 @@ const BRANDS: Record = { itrocket: { color: "#E53E3E" }, // itrocket red "cosmos-directory": { color: "#1B1B2F", dark: true }, // cosmos directory dark navy + // ─── Band Protocol + cheqd (benches 256-257) ─── + bandchain: { color: "#516AFF" }, // band protocol indigo (official brand) + cheqd: { color: "#00B59C" }, // cheqd teal (official brand) + "band-official": { color: "#516AFF" }, // band official inherits band indigo + "cheqd-official": { color: "#00B59C" }, // cheqd official inherits cheqd teal + stakewolle: { color: "#F97316" }, // stakewolle orange + nodestake: { color: "#8B5CF6" }, // nodestake violet + // ─── Bitcoin Cash chain + providers (bench 244) ─── "bitcoin-cash": { color: "#0AC18E" }, // bch official green bitcore: { color: "#1A1D21", dark: true }, // bitpay dark diff --git a/src/lib/logo-manifest.ts b/src/lib/logo-manifest.ts index b5250d68..e893db5a 100644 --- a/src/lib/logo-manifest.ts +++ b/src/lib/logo-manifest.ts @@ -339,6 +339,10 @@ const RAW: Record = { highstakes: "/logos/highstakes.svg", itrocket: "/logos/itrocket.svg", "cosmos-directory": "/logos/cosmos-directory.svg", + bandchain: "/logos/bandchain.svg", + cheqd: "/logos/cheqd.svg", + stakewolle: "/logos/stakewolle.svg", + nodestake: "/logos/nodestake.svg", // ─── Oracle deviation (bench 025) — additional brand logos ─── // (pairs alias to chain/asset logos in the ALIASES block below) @@ -752,6 +756,8 @@ const ALIASES: Record = { "fetchai-official": "fetchhub", "shentu-official": "shentu", "mantrachain-official": "mantrachain", + "band-official": "bandchain", + "cheqd-official": "cheqd", // Non-EVM wave-3 (benches 222-231) — provider-official aliases to chain slug "ecadinfra": "tezos", From 1d3ad1c11bf4c9a03e58f181c680434f27b01ef7 Mon Sep 17 00:00:00 2001 From: Flotapponnier <160007691+Flotapponnier@users.noreply.github.com> Date: Sat, 29 Aug 2026 23:04:48 +0200 Subject: [PATCH 11/15] fix: bench-blob revalidate 300s to fix perp ISR conflict (#2206) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * merge: dev → main (Gains carry fix + vault fee split) (#2191) * fix(harnesses): cap LZ/CCIP/Axelar cardinality to bench-tracked chains only (#2187) * fix(cache): strip heavy fields from aggregate blob to pass 2MB limit (#2188) Hub pages only need card data. Drop seriesByRegion24h, metricPanels, editorial text (seoIntro/faq/findings/methodology/disclaimer/abstract/ perChainExplainer) from the blob aggregate slim. Cuts serialized size from ~4.3 MB to under 2 MB so unstable_cache actually persists the entry instead of silently dropping it on every render. * fix: include still-open Gains positions in carry projection (#2189) * fix(cache): strip heavy fields from aggregate blob to pass 2MB limit Hub pages only need card data. Drop seriesByRegion24h, metricPanels, editorial text (seoIntro/faq/findings/methodology/disclaimer/abstract/ perChainExplainer) from the blob aggregate slim. Cuts serialized size from ~4.3 MB to under 2 MB so unstable_cache actually persists the entry instead of silently dropping it on every render. * fix: include still-open Gains positions in carry projection * fix: update Gains action names v5→v6, handle position size increases (#2190) * fix(cache): strip heavy fields from aggregate blob to pass 2MB limit Hub pages only need card data. Drop seriesByRegion24h, metricPanels, editorial text (seoIntro/faq/findings/methodology/disclaimer/abstract/ perChainExplainer) from the blob aggregate slim. Cuts serialized size from ~4.3 MB to under 2 MB so unstable_cache actually persists the entry instead of silently dropping it on every render. * fix: include still-open Gains positions in carry projection * fix: update Gains open action names to v6 API, handle TradePosSizeIncrease * feat(rpc): Union bench #253 (#2195) * fix(harnesses): cap LZ/CCIP/Axelar cardinality to bench-tracked chains only (#2187) * fix(cache): strip heavy fields from aggregate blob to pass 2MB limit (#2188) Hub pages only need card data. Drop seriesByRegion24h, metricPanels, editorial text (seoIntro/faq/findings/methodology/disclaimer/abstract/ perChainExplainer) from the blob aggregate slim. Cuts serialized size from ~4.3 MB to under 2 MB so unstable_cache actually persists the entry instead of silently dropping it on every render. * fix: include still-open Gains positions in carry projection (#2189) * fix(cache): strip heavy fields from aggregate blob to pass 2MB limit Hub pages only need card data. Drop seriesByRegion24h, metricPanels, editorial text (seoIntro/faq/findings/methodology/disclaimer/abstract/ perChainExplainer) from the blob aggregate slim. Cuts serialized size from ~4.3 MB to under 2 MB so unstable_cache actually persists the entry instead of silently dropping it on every render. * fix: include still-open Gains positions in carry projection * fix: update Gains action names v5→v6, handle position size increases (#2190) * fix(cache): strip heavy fields from aggregate blob to pass 2MB limit Hub pages only need card data. Drop seriesByRegion24h, metricPanels, editorial text (seoIntro/faq/findings/methodology/disclaimer/abstract/ perChainExplainer) from the blob aggregate slim. Cuts serialized size from ~4.3 MB to under 2 MB so unstable_cache actually persists the entry instead of silently dropping it on every render. * fix: include still-open Gains positions in carry projection * fix: update Gains open action names to v6 API, handle TradePosSizeIncrease * fix: Gains carry projection (v6 action names + still-open positions) * fix(cache): strip heavy fields from aggregate blob to pass 2MB limit Hub pages only need card data. Drop seriesByRegion24h, metricPanels, editorial text (seoIntro/faq/findings/methodology/disclaimer/abstract/ perChainExplainer) from the blob aggregate slim. Cuts serialized size from ~4.3 MB to under 2 MB so unstable_cache actually persists the entry instead of silently dropping it on every render. * fix: include still-open Gains positions in carry projection * fix: update Gains open action names to v6 API, handle TradePosSizeIncrease * fix: widen coin name column for long tickers (xyz:NVDA) * fix(cache): strip heavy fields from aggregate blob to pass 2MB limit Hub pages only need card data. Drop seriesByRegion24h, metricPanels, editorial text (seoIntro/faq/findings/methodology/disclaimer/abstract/ perChainExplainer) from the blob aggregate slim. Cuts serialized size from ~4.3 MB to under 2 MB so unstable_cache actually persists the entry instead of silently dropping it on every render. * fix: include still-open Gains positions in carry projection * fix: update Gains open action names to v6 API, handle TradePosSizeIncrease * fix: widen coin name column in top markets to fit xyz:NVDA * feat(rpc): add Union bench #253 (Nodes.Guru, Stake And Relax, High Stakes) (#2194) * fix(union-rpc): remove em dashes from YAML * feat(union): add provider SVG logos and brand colors * feat: Shentu #254 + MANTRA Chain #255 benches * fix(harnesses): cap LZ/CCIP/Axelar cardinality to bench-tracked chains only (#2187) * fix(cache): strip heavy fields from aggregate blob to pass 2MB limit (#2188) Hub pages only need card data. Drop seriesByRegion24h, metricPanels, editorial text (seoIntro/faq/findings/methodology/disclaimer/abstract/ perChainExplainer) from the blob aggregate slim. Cuts serialized size from ~4.3 MB to under 2 MB so unstable_cache actually persists the entry instead of silently dropping it on every render. * fix: include still-open Gains positions in carry projection (#2189) * fix(cache): strip heavy fields from aggregate blob to pass 2MB limit Hub pages only need card data. Drop seriesByRegion24h, metricPanels, editorial text (seoIntro/faq/findings/methodology/disclaimer/abstract/ perChainExplainer) from the blob aggregate slim. Cuts serialized size from ~4.3 MB to under 2 MB so unstable_cache actually persists the entry instead of silently dropping it on every render. * fix: include still-open Gains positions in carry projection * fix: update Gains action names v5→v6, handle position size increases (#2190) * fix(cache): strip heavy fields from aggregate blob to pass 2MB limit Hub pages only need card data. Drop seriesByRegion24h, metricPanels, editorial text (seoIntro/faq/findings/methodology/disclaimer/abstract/ perChainExplainer) from the blob aggregate slim. Cuts serialized size from ~4.3 MB to under 2 MB so unstable_cache actually persists the entry instead of silently dropping it on every render. * fix: include still-open Gains positions in carry projection * fix: update Gains open action names to v6 API, handle TradePosSizeIncrease * fix: Gains carry projection (v6 action names + still-open positions) * fix(cache): strip heavy fields from aggregate blob to pass 2MB limit Hub pages only need card data. Drop seriesByRegion24h, metricPanels, editorial text (seoIntro/faq/findings/methodology/disclaimer/abstract/ perChainExplainer) from the blob aggregate slim. Cuts serialized size from ~4.3 MB to under 2 MB so unstable_cache actually persists the entry instead of silently dropping it on every render. * fix: include still-open Gains positions in carry projection * fix: update Gains open action names to v6 API, handle TradePosSizeIncrease * fix: widen coin name column for long tickers (xyz:NVDA) * fix(cache): strip heavy fields from aggregate blob to pass 2MB limit Hub pages only need card data. Drop seriesByRegion24h, metricPanels, editorial text (seoIntro/faq/findings/methodology/disclaimer/abstract/ perChainExplainer) from the blob aggregate slim. Cuts serialized size from ~4.3 MB to under 2 MB so unstable_cache actually persists the entry instead of silently dropping it on every render. * fix: include still-open Gains positions in carry projection * fix: update Gains open action names to v6 API, handle TradePosSizeIncrease * fix: widen coin name column in top markets to fit xyz:NVDA * feat(rpc): add Union bench #253 (Nodes.Guru, Stake And Relax, High Stakes) (#2194) * fix(union-rpc): remove em dashes from YAML (#2196) * feat(rpc): Shentu #254 + MANTRA Chain #255 (#2199) * feat(rpc): add Shentu bench #254 and MANTRA Chain bench #255 * fix: remove accidental dev-portal submodule * fix: bench-blob revalidate 300s to fix perp ISR conflict --- public/logos/highstakes.svg | 5 +++++ public/logos/nodes-guru.svg | 5 +++++ public/logos/stakeandrelax.svg | 5 +++++ src/lib/bench-blob.ts | 2 +- 4 files changed, 16 insertions(+), 1 deletion(-) create mode 100644 public/logos/highstakes.svg create mode 100644 public/logos/nodes-guru.svg create mode 100644 public/logos/stakeandrelax.svg diff --git a/public/logos/highstakes.svg b/public/logos/highstakes.svg new file mode 100644 index 00000000..220f0fad --- /dev/null +++ b/public/logos/highstakes.svg @@ -0,0 +1,5 @@ + + + HIGH + STAKES + diff --git a/public/logos/nodes-guru.svg b/public/logos/nodes-guru.svg new file mode 100644 index 00000000..c55d6fd0 --- /dev/null +++ b/public/logos/nodes-guru.svg @@ -0,0 +1,5 @@ + + + NODES + GURU + diff --git a/public/logos/stakeandrelax.svg b/public/logos/stakeandrelax.svg new file mode 100644 index 00000000..3664a52d --- /dev/null +++ b/public/logos/stakeandrelax.svg @@ -0,0 +1,5 @@ + + + STAKE + RELAX + diff --git a/src/lib/bench-blob.ts b/src/lib/bench-blob.ts index 07f96cde..45e825e5 100644 --- a/src/lib/bench-blob.ts +++ b/src/lib/bench-blob.ts @@ -54,7 +54,7 @@ async function fetchJson(url: string): Promise { try { const res = await fetch(url, { signal: AbortSignal.timeout(FETCH_TIMEOUT_MS), - cache: "no-store", + next: { revalidate: 300 }, }); if (!res.ok) return null; return await res.json(); From 3914371bf3e86930711dc83446bcc4892acbc695 Mon Sep 17 00:00:00 2001 From: Flotapponnier <160007691+Flotapponnier@users.noreply.github.com> Date: Sun, 30 Aug 2026 00:48:14 +0200 Subject: [PATCH 12/15] =?UTF-8?q?fix(fee-compare):=20clearer=20Gains=20lab?= =?UTF-8?q?els=20=E2=80=94=20Position=20size=20+=20vault=20fee=20(#2207)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix(fee-compare): rename Volume→Position size + clearer vault fee label * fix(fee-compare): show total position value + inline vault fee explanation --- src/components/fee-compare-client.tsx | 25 +++++++++++++++---------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/src/components/fee-compare-client.tsx b/src/components/fee-compare-client.tsx index a7ada7a7..59c816dd 100644 --- a/src/components/fee-compare-client.tsx +++ b/src/components/fee-compare-client.tsx @@ -741,24 +741,29 @@ function WalletSide({ return (
-

Volume

+

Total position value

{fmtUsd(volume)}

+

+ Sum of (collateral × leverage) across all trades in the period. +

Taker fees

{fmtUsd(gW.feesUsdc)}

{hasVault && ( -
-

- OI vault fee{" "} - OI imbalance surcharge -

-

- +{fmtUsd(gW.vaultFeesUsdc)} + <> +

+

Vault fee

+

+ +{fmtUsd(gW.vaultFeesUsdc)} +

+
+

+ Gains charges an extra fee to LPs when your trade increases the long/short imbalance. Hyperliquid does not have this — it uses funding rates instead.

-
+ )} {hasFunding && (
@@ -1220,7 +1225,7 @@ function GainsTradeTable({

{fmtUsd(netCost)}

{fmtUsd(t.tradingFee)} taker - {hasVault ? ` +${fmtUsd(t.vaultFee)} vault` : ""} + {hasVault ? ` +${fmtUsd(t.vaultFee)} vault LP` : ""} {t.borrowingFee > 0.001 ? ` +${fmtUsd(t.borrowingFee)} borrow` : ""} {Math.abs(t.fundingFee) > 0.001 ? ` ${t.fundingFee > 0 ? "+" : "−"}${fmtUsd(Math.abs(t.fundingFee))} fund` From f0b1bbde5bddc37e73a48b836510b108a862237e Mon Sep 17 00:00:00 2001 From: Flotapponnier <160007691+Flotapponnier@users.noreply.github.com> Date: Sun, 30 Aug 2026 00:48:47 +0200 Subject: [PATCH 13/15] fix(fee-compare): total position value + inline vault fee explanation (#2208) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix(fee-compare): rename Volume→Position size + clearer vault fee label * fix(fee-compare): show total position value + inline vault fee explanation From f0ac45f2425aed0a93358c3a82abe82928649393 Mon Sep 17 00:00:00 2001 From: Flotapponnier <160007691+Flotapponnier@users.noreply.github.com> Date: Sun, 30 Aug 2026 01:07:58 +0200 Subject: [PATCH 14/15] =?UTF-8?q?fix(fee-compare):=20remove=20vault=20fee?= =?UTF-8?q?=20=E2=80=94=20carry=20fees=20settled=20per-action,=20not=20OI?= =?UTF-8?q?=20surcharge=20(#2209)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app/api/fee-compare/route.ts | 27 +++++++++++---------------- src/components/fee-compare-client.tsx | 20 +------------------- 2 files changed, 12 insertions(+), 35 deletions(-) diff --git a/src/app/api/fee-compare/route.ts b/src/app/api/fee-compare/route.ts index b4f9e2e1..d6d53301 100644 --- a/src/app/api/fee-compare/route.ts +++ b/src/app/api/fee-compare/route.ts @@ -132,6 +132,7 @@ type GainsApiTrade = { realizedTradingFeesCollateral?: number; realizedFundingFeesCollateral?: number; realizedNewBorrowingFeesCollateral?: number; + realizedOldBorrowingFeesCollateral?: number; }; }; }; @@ -180,8 +181,7 @@ type HlWalletData = { type GainsWalletData = { events: number; - feesUsdc: number; // pure taker fee only (uiRealizedPnlData) - vaultFeesUsdc: number; // OI imbalance vault surcharge (tradeFeesData - uiRealizedPnlData) + feesUsdc: number; fundingFeesUsdc: number; fundingEstimated: boolean; borrowingFeesUsdc: number; @@ -193,8 +193,7 @@ type GainsWalletData = { pair: string; action: string; notional: number; - tradingFee: number; // taker fee only - vaultFee: number; // OI vault surcharge + tradingFee: number; fundingFee: number; borrowingFee: number; equivFee?: number; // equivalent fee on the other venue @@ -1525,23 +1524,20 @@ export async function GET(req: Request) { const otherSlug = slug === venueA ? venueB : venueA; const otherRate = slug === venueA ? rateB : rateA; let feesUsdc = 0; - let vaultFeesUsdc = 0; let fundingFeesUsdc = 0; let borrowingFeesUsdc = 0; let notionalUsd = 0; const recentTrades: GainsWalletData["recentTrades"] = []; for (const t of usdcTrades) { - // uiRealizedPnlData = pure taker fee; tradeFeesData = taker + OI vault surcharge + // Gains settles carry (funding + borrowing) on every action, not just closes. + // uiRealizedPnlData breaks down taker, funding, and borrowing separately — use it for all. const takerFee = t.meta?.uiRealizedPnlData?.realizedTradingFeesCollateral ?? t.meta?.tradeFeesData?.realizedTradingFeesCollateral ?? 0; - const totalTradingFee = t.meta?.tradeFeesData?.realizedTradingFeesCollateral ?? takerFee; - const vaultFee = Math.max(0, totalTradingFee - takerFee); - const isClose = CLOSE_ACTIONS.has(t.action); - const fundingFee = isClose ? (t.meta?.uiRealizedPnlData?.realizedFundingFeesCollateral ?? 0) : 0; - const borrowingFee = isClose ? (t.meta?.uiRealizedPnlData?.realizedNewBorrowingFeesCollateral ?? 0) : 0; + const fundingFee = t.meta?.uiRealizedPnlData?.realizedFundingFeesCollateral ?? 0; + const borrowingFee = (t.meta?.uiRealizedPnlData?.realizedNewBorrowingFeesCollateral ?? 0) + + (t.meta?.uiRealizedPnlData?.realizedOldBorrowingFeesCollateral ?? 0); feesUsdc += takerFee; - vaultFeesUsdc += vaultFee; fundingFeesUsdc += fundingFee; borrowingFeesUsdc += borrowingFee; const tradeNotional = t.size * t.leverage; @@ -1550,7 +1546,7 @@ export async function GET(req: Request) { const equivFee = otherSlug === "hyperliquid" ? tradeNotional * (gainsData.perSide[t.pair.split("/")[0]] ?? otherRate) : tradeNotional * otherRate; - recentTrades.push({ date: t.date, pair: t.pair, action: t.action, notional: tradeNotional, tradingFee: takerFee, vaultFee, fundingFee, borrowingFee, equivFee, pnl_net: t.pnl_net }); + recentTrades.push({ date: t.date, pair: t.pair, action: t.action, notional: tradeNotional, tradingFee: takerFee, fundingFee, borrowingFee, equivFee, pnl_net: t.pnl_net }); } } @@ -1566,17 +1562,16 @@ export async function GET(req: Request) { } } - const netCostUsdc = feesUsdc + vaultFeesUsdc + fundingFeesUsdc + borrowingFeesUsdc; + const netCostUsdc = feesUsdc + fundingFeesUsdc + borrowingFeesUsdc; walletData = { events: usdcTrades.length, feesUsdc, - vaultFeesUsdc, fundingFeesUsdc, fundingEstimated, borrowingFeesUsdc, netCostUsdc, positionSizeUsdc: notionalUsd, - avgFeeRateBps: notionalUsd > 0 ? (feesUsdc / notionalUsd) * 10000 : 0, + avgFeeRateBps: notionalUsd > 0 ? (netCostUsdc / notionalUsd) * 10000 : 0, recentTrades, } satisfies GainsWalletData; } else if (fetchEvmWallet && slug === "gmx-v2" && gmxWalletData) { diff --git a/src/components/fee-compare-client.tsx b/src/components/fee-compare-client.tsx index 59c816dd..736879dc 100644 --- a/src/components/fee-compare-client.tsx +++ b/src/components/fee-compare-client.tsx @@ -75,7 +75,6 @@ type HlWalletData = { type GainsWalletData = { events: number; feesUsdc: number; - vaultFeesUsdc: number; fundingFeesUsdc: number; fundingEstimated: boolean; borrowingFeesUsdc: number; @@ -88,7 +87,6 @@ type GainsWalletData = { action: string; notional: number; tradingFee: number; - vaultFee: number; fundingFee: number; borrowingFee: number; equivFee?: number; @@ -735,7 +733,6 @@ function WalletSide({ })()} {venue.slug === "gains" && (() => { const gW = w as GainsWalletData; - const hasVault = (gW.vaultFeesUsdc ?? 0) > 0.5; const hasFunding = Math.abs(gW.fundingFeesUsdc) > 0.5; const hasBorrowing = gW.borrowingFeesUsdc > 0.5; return ( @@ -752,19 +749,6 @@ function WalletSide({

Taker fees

{fmtUsd(gW.feesUsdc)}

- {hasVault && ( - <> -
-

Vault fee

-

- +{fmtUsd(gW.vaultFeesUsdc)} -

-
-

- Gains charges an extra fee to LPs when your trade increases the long/short imbalance. Hyperliquid does not have this — it uses funding rates instead. -

- - )} {hasFunding && (

@@ -1202,8 +1186,7 @@ function GainsTradeTable({ {rows.map((t, i) => { - const hasVault = (t.vaultFee ?? 0) > 0.001; - const netCost = t.tradingFee + (t.vaultFee ?? 0) + t.fundingFee + t.borrowingFee; + const netCost = t.tradingFee + t.fundingFee + t.borrowingFee; const diff = hasEquiv && t.equivFee !== undefined ? t.equivFee - netCost : undefined; return ( @@ -1225,7 +1208,6 @@ function GainsTradeTable({

{fmtUsd(netCost)}

{fmtUsd(t.tradingFee)} taker - {hasVault ? ` +${fmtUsd(t.vaultFee)} vault LP` : ""} {t.borrowingFee > 0.001 ? ` +${fmtUsd(t.borrowingFee)} borrow` : ""} {Math.abs(t.fundingFee) > 0.001 ? ` ${t.fundingFee > 0 ? "+" : "−"}${fmtUsd(Math.abs(t.fundingFee))} fund` From 2197a7d87168d6ee852b7add154394b44a935df0 Mon Sep 17 00:00:00 2001 From: Flotapponnier <160007691+Flotapponnier@users.noreply.github.com> Date: Sun, 30 Aug 2026 14:41:05 +0200 Subject: [PATCH 15/15] feat(rpc): ICON bench #258 (#2210) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * merge: dev → main (Gains carry fix + vault fee split) (#2191) * fix(harnesses): cap LZ/CCIP/Axelar cardinality to bench-tracked chains only (#2187) * fix(cache): strip heavy fields from aggregate blob to pass 2MB limit (#2188) Hub pages only need card data. Drop seriesByRegion24h, metricPanels, editorial text (seoIntro/faq/findings/methodology/disclaimer/abstract/ perChainExplainer) from the blob aggregate slim. Cuts serialized size from ~4.3 MB to under 2 MB so unstable_cache actually persists the entry instead of silently dropping it on every render. * fix: include still-open Gains positions in carry projection (#2189) * fix(cache): strip heavy fields from aggregate blob to pass 2MB limit Hub pages only need card data. Drop seriesByRegion24h, metricPanels, editorial text (seoIntro/faq/findings/methodology/disclaimer/abstract/ perChainExplainer) from the blob aggregate slim. Cuts serialized size from ~4.3 MB to under 2 MB so unstable_cache actually persists the entry instead of silently dropping it on every render. * fix: include still-open Gains positions in carry projection * fix: update Gains action names v5→v6, handle position size increases (#2190) * fix(cache): strip heavy fields from aggregate blob to pass 2MB limit Hub pages only need card data. Drop seriesByRegion24h, metricPanels, editorial text (seoIntro/faq/findings/methodology/disclaimer/abstract/ perChainExplainer) from the blob aggregate slim. Cuts serialized size from ~4.3 MB to under 2 MB so unstable_cache actually persists the entry instead of silently dropping it on every render. * fix: include still-open Gains positions in carry projection * fix: update Gains open action names to v6 API, handle TradePosSizeIncrease * feat(rpc): Union bench #253 (#2195) * fix(harnesses): cap LZ/CCIP/Axelar cardinality to bench-tracked chains only (#2187) * fix(cache): strip heavy fields from aggregate blob to pass 2MB limit (#2188) Hub pages only need card data. Drop seriesByRegion24h, metricPanels, editorial text (seoIntro/faq/findings/methodology/disclaimer/abstract/ perChainExplainer) from the blob aggregate slim. Cuts serialized size from ~4.3 MB to under 2 MB so unstable_cache actually persists the entry instead of silently dropping it on every render. * fix: include still-open Gains positions in carry projection (#2189) * fix(cache): strip heavy fields from aggregate blob to pass 2MB limit Hub pages only need card data. Drop seriesByRegion24h, metricPanels, editorial text (seoIntro/faq/findings/methodology/disclaimer/abstract/ perChainExplainer) from the blob aggregate slim. Cuts serialized size from ~4.3 MB to under 2 MB so unstable_cache actually persists the entry instead of silently dropping it on every render. * fix: include still-open Gains positions in carry projection * fix: update Gains action names v5→v6, handle position size increases (#2190) * fix(cache): strip heavy fields from aggregate blob to pass 2MB limit Hub pages only need card data. Drop seriesByRegion24h, metricPanels, editorial text (seoIntro/faq/findings/methodology/disclaimer/abstract/ perChainExplainer) from the blob aggregate slim. Cuts serialized size from ~4.3 MB to under 2 MB so unstable_cache actually persists the entry instead of silently dropping it on every render. * fix: include still-open Gains positions in carry projection * fix: update Gains open action names to v6 API, handle TradePosSizeIncrease * fix: Gains carry projection (v6 action names + still-open positions) * fix(cache): strip heavy fields from aggregate blob to pass 2MB limit Hub pages only need card data. Drop seriesByRegion24h, metricPanels, editorial text (seoIntro/faq/findings/methodology/disclaimer/abstract/ perChainExplainer) from the blob aggregate slim. Cuts serialized size from ~4.3 MB to under 2 MB so unstable_cache actually persists the entry instead of silently dropping it on every render. * fix: include still-open Gains positions in carry projection * fix: update Gains open action names to v6 API, handle TradePosSizeIncrease * fix: widen coin name column for long tickers (xyz:NVDA) * fix(cache): strip heavy fields from aggregate blob to pass 2MB limit Hub pages only need card data. Drop seriesByRegion24h, metricPanels, editorial text (seoIntro/faq/findings/methodology/disclaimer/abstract/ perChainExplainer) from the blob aggregate slim. Cuts serialized size from ~4.3 MB to under 2 MB so unstable_cache actually persists the entry instead of silently dropping it on every render. * fix: include still-open Gains positions in carry projection * fix: update Gains open action names to v6 API, handle TradePosSizeIncrease * fix: widen coin name column in top markets to fit xyz:NVDA * feat(rpc): add Union bench #253 (Nodes.Guru, Stake And Relax, High Stakes) (#2194) * fix(union-rpc): remove em dashes from YAML * feat(union): add provider SVG logos and brand colors * feat: Shentu #254 + MANTRA Chain #255 benches * fix(harnesses): cap LZ/CCIP/Axelar cardinality to bench-tracked chains only (#2187) * fix(cache): strip heavy fields from aggregate blob to pass 2MB limit (#2188) Hub pages only need card data. Drop seriesByRegion24h, metricPanels, editorial text (seoIntro/faq/findings/methodology/disclaimer/abstract/ perChainExplainer) from the blob aggregate slim. Cuts serialized size from ~4.3 MB to under 2 MB so unstable_cache actually persists the entry instead of silently dropping it on every render. * fix: include still-open Gains positions in carry projection (#2189) * fix(cache): strip heavy fields from aggregate blob to pass 2MB limit Hub pages only need card data. Drop seriesByRegion24h, metricPanels, editorial text (seoIntro/faq/findings/methodology/disclaimer/abstract/ perChainExplainer) from the blob aggregate slim. Cuts serialized size from ~4.3 MB to under 2 MB so unstable_cache actually persists the entry instead of silently dropping it on every render. * fix: include still-open Gains positions in carry projection * fix: update Gains action names v5→v6, handle position size increases (#2190) * fix(cache): strip heavy fields from aggregate blob to pass 2MB limit Hub pages only need card data. Drop seriesByRegion24h, metricPanels, editorial text (seoIntro/faq/findings/methodology/disclaimer/abstract/ perChainExplainer) from the blob aggregate slim. Cuts serialized size from ~4.3 MB to under 2 MB so unstable_cache actually persists the entry instead of silently dropping it on every render. * fix: include still-open Gains positions in carry projection * fix: update Gains open action names to v6 API, handle TradePosSizeIncrease * fix: Gains carry projection (v6 action names + still-open positions) * fix(cache): strip heavy fields from aggregate blob to pass 2MB limit Hub pages only need card data. Drop seriesByRegion24h, metricPanels, editorial text (seoIntro/faq/findings/methodology/disclaimer/abstract/ perChainExplainer) from the blob aggregate slim. Cuts serialized size from ~4.3 MB to under 2 MB so unstable_cache actually persists the entry instead of silently dropping it on every render. * fix: include still-open Gains positions in carry projection * fix: update Gains open action names to v6 API, handle TradePosSizeIncrease * fix: widen coin name column for long tickers (xyz:NVDA) * fix(cache): strip heavy fields from aggregate blob to pass 2MB limit Hub pages only need card data. Drop seriesByRegion24h, metricPanels, editorial text (seoIntro/faq/findings/methodology/disclaimer/abstract/ perChainExplainer) from the blob aggregate slim. Cuts serialized size from ~4.3 MB to under 2 MB so unstable_cache actually persists the entry instead of silently dropping it on every render. * fix: include still-open Gains positions in carry projection * fix: update Gains open action names to v6 API, handle TradePosSizeIncrease * fix: widen coin name column in top markets to fit xyz:NVDA * feat(rpc): add Union bench #253 (Nodes.Guru, Stake And Relax, High Stakes) (#2194) * fix(union-rpc): remove em dashes from YAML (#2196) * feat(rpc): Shentu #254 + MANTRA Chain #255 (#2199) * feat(rpc): add Shentu bench #254 and MANTRA Chain bench #255 * fix: remove accidental dev-portal submodule * feat: Band Protocol #256 + cheqd #257 benches * fix(harnesses): cap LZ/CCIP/Axelar cardinality to bench-tracked chains only (#2187) * fix(cache): strip heavy fields from aggregate blob to pass 2MB limit (#2188) Hub pages only need card data. Drop seriesByRegion24h, metricPanels, editorial text (seoIntro/faq/findings/methodology/disclaimer/abstract/ perChainExplainer) from the blob aggregate slim. Cuts serialized size from ~4.3 MB to under 2 MB so unstable_cache actually persists the entry instead of silently dropping it on every render. * fix: include still-open Gains positions in carry projection (#2189) * fix(cache): strip heavy fields from aggregate blob to pass 2MB limit Hub pages only need card data. Drop seriesByRegion24h, metricPanels, editorial text (seoIntro/faq/findings/methodology/disclaimer/abstract/ perChainExplainer) from the blob aggregate slim. Cuts serialized size from ~4.3 MB to under 2 MB so unstable_cache actually persists the entry instead of silently dropping it on every render. * fix: include still-open Gains positions in carry projection * fix: update Gains action names v5→v6, handle position size increases (#2190) * fix(cache): strip heavy fields from aggregate blob to pass 2MB limit Hub pages only need card data. Drop seriesByRegion24h, metricPanels, editorial text (seoIntro/faq/findings/methodology/disclaimer/abstract/ perChainExplainer) from the blob aggregate slim. Cuts serialized size from ~4.3 MB to under 2 MB so unstable_cache actually persists the entry instead of silently dropping it on every render. * fix: include still-open Gains positions in carry projection * fix: update Gains open action names to v6 API, handle TradePosSizeIncrease * fix: Gains carry projection (v6 action names + still-open positions) * fix(cache): strip heavy fields from aggregate blob to pass 2MB limit Hub pages only need card data. Drop seriesByRegion24h, metricPanels, editorial text (seoIntro/faq/findings/methodology/disclaimer/abstract/ perChainExplainer) from the blob aggregate slim. Cuts serialized size from ~4.3 MB to under 2 MB so unstable_cache actually persists the entry instead of silently dropping it on every render. * fix: include still-open Gains positions in carry projection * fix: update Gains open action names to v6 API, handle TradePosSizeIncrease * fix: widen coin name column for long tickers (xyz:NVDA) * fix(cache): strip heavy fields from aggregate blob to pass 2MB limit Hub pages only need card data. Drop seriesByRegion24h, metricPanels, editorial text (seoIntro/faq/findings/methodology/disclaimer/abstract/ perChainExplainer) from the blob aggregate slim. Cuts serialized size from ~4.3 MB to under 2 MB so unstable_cache actually persists the entry instead of silently dropping it on every render. * fix: include still-open Gains positions in carry projection * fix: update Gains open action names to v6 API, handle TradePosSizeIncrease * fix: widen coin name column in top markets to fit xyz:NVDA * feat(rpc): add Union bench #253 (Nodes.Guru, Stake And Relax, High Stakes) (#2194) * fix(union-rpc): remove em dashes from YAML (#2196) * feat(rpc): Shentu #254 + MANTRA Chain #255 (#2199) * feat(rpc): add Shentu bench #254 and MANTRA Chain bench #255 * fix: remove accidental dev-portal submodule * feat: Band Protocol #256 + cheqd #257 benches * fix: bench-blob revalidate 300s to fix perp ISR conflict (#2206) * merge: dev → main (Gains carry fix + vault fee split) (#2191) * fix(harnesses): cap LZ/CCIP/Axelar cardinality to bench-tracked chains only (#2187) * fix(cache): strip heavy fields from aggregate blob to pass 2MB limit (#2188) Hub pages only need card data. Drop seriesByRegion24h, metricPanels, editorial text (seoIntro/faq/findings/methodology/disclaimer/abstract/ perChainExplainer) from the blob aggregate slim. Cuts serialized size from ~4.3 MB to under 2 MB so unstable_cache actually persists the entry instead of silently dropping it on every render. * fix: include still-open Gains positions in carry projection (#2189) * fix(cache): strip heavy fields from aggregate blob to pass 2MB limit Hub pages only need card data. Drop seriesByRegion24h, metricPanels, editorial text (seoIntro/faq/findings/methodology/disclaimer/abstract/ perChainExplainer) from the blob aggregate slim. Cuts serialized size from ~4.3 MB to under 2 MB so unstable_cache actually persists the entry instead of silently dropping it on every render. * fix: include still-open Gains positions in carry projection * fix: update Gains action names v5→v6, handle position size increases (#2190) * fix(cache): strip heavy fields from aggregate blob to pass 2MB limit Hub pages only need card data. Drop seriesByRegion24h, metricPanels, editorial text (seoIntro/faq/findings/methodology/disclaimer/abstract/ perChainExplainer) from the blob aggregate slim. Cuts serialized size from ~4.3 MB to under 2 MB so unstable_cache actually persists the entry instead of silently dropping it on every render. * fix: include still-open Gains positions in carry projection * fix: update Gains open action names to v6 API, handle TradePosSizeIncrease * feat(rpc): Union bench #253 (#2195) * fix(harnesses): cap LZ/CCIP/Axelar cardinality to bench-tracked chains only (#2187) * fix(cache): strip heavy fields from aggregate blob to pass 2MB limit (#2188) Hub pages only need card data. Drop seriesByRegion24h, metricPanels, editorial text (seoIntro/faq/findings/methodology/disclaimer/abstract/ perChainExplainer) from the blob aggregate slim. Cuts serialized size from ~4.3 MB to under 2 MB so unstable_cache actually persists the entry instead of silently dropping it on every render. * fix: include still-open Gains positions in carry projection (#2189) * fix(cache): strip heavy fields from aggregate blob to pass 2MB limit Hub pages only need card data. Drop seriesByRegion24h, metricPanels, editorial text (seoIntro/faq/findings/methodology/disclaimer/abstract/ perChainExplainer) from the blob aggregate slim. Cuts serialized size from ~4.3 MB to under 2 MB so unstable_cache actually persists the entry instead of silently dropping it on every render. * fix: include still-open Gains positions in carry projection * fix: update Gains action names v5→v6, handle position size increases (#2190) * fix(cache): strip heavy fields from aggregate blob to pass 2MB limit Hub pages only need card data. Drop seriesByRegion24h, metricPanels, editorial text (seoIntro/faq/findings/methodology/disclaimer/abstract/ perChainExplainer) from the blob aggregate slim. Cuts serialized size from ~4.3 MB to under 2 MB so unstable_cache actually persists the entry instead of silently dropping it on every render. * fix: include still-open Gains positions in carry projection * fix: update Gains open action names to v6 API, handle TradePosSizeIncrease * fix: Gains carry projection (v6 action names + still-open positions) * fix(cache): strip heavy fields from aggregate blob to pass 2MB limit Hub pages only need card data. Drop seriesByRegion24h, metricPanels, editorial text (seoIntro/faq/findings/methodology/disclaimer/abstract/ perChainExplainer) from the blob aggregate slim. Cuts serialized size from ~4.3 MB to under 2 MB so unstable_cache actually persists the entry instead of silently dropping it on every render. * fix: include still-open Gains positions in carry projection * fix: update Gains open action names to v6 API, handle TradePosSizeIncrease * fix: widen coin name column for long tickers (xyz:NVDA) * fix(cache): strip heavy fields from aggregate blob to pass 2MB limit Hub pages only need card data. Drop seriesByRegion24h, metricPanels, editorial text (seoIntro/faq/findings/methodology/disclaimer/abstract/ perChainExplainer) from the blob aggregate slim. Cuts serialized size from ~4.3 MB to under 2 MB so unstable_cache actually persists the entry instead of silently dropping it on every render. * fix: include still-open Gains positions in carry projection * fix: update Gains open action names to v6 API, handle TradePosSizeIncrease * fix: widen coin name column in top markets to fit xyz:NVDA * feat(rpc): add Union bench #253 (Nodes.Guru, Stake And Relax, High Stakes) (#2194) * fix(union-rpc): remove em dashes from YAML * feat(union): add provider SVG logos and brand colors * feat: Shentu #254 + MANTRA Chain #255 benches * fix(harnesses): cap LZ/CCIP/Axelar cardinality to bench-tracked chains only (#2187) * fix(cache): strip heavy fields from aggregate blob to pass 2MB limit (#2188) Hub pages only need card data. Drop seriesByRegion24h, metricPanels, editorial text (seoIntro/faq/findings/methodology/disclaimer/abstract/ perChainExplainer) from the blob aggregate slim. Cuts serialized size from ~4.3 MB to under 2 MB so unstable_cache actually persists the entry instead of silently dropping it on every render. * fix: include still-open Gains positions in carry projection (#2189) * fix(cache): strip heavy fields from aggregate blob to pass 2MB limit Hub pages only need card data. Drop seriesByRegion24h, metricPanels, editorial text (seoIntro/faq/findings/methodology/disclaimer/abstract/ perChainExplainer) from the blob aggregate slim. Cuts serialized size from ~4.3 MB to under 2 MB so unstable_cache actually persists the entry instead of silently dropping it on every render. * fix: include still-open Gains positions in carry projection * fix: update Gains action names v5→v6, handle position size increases (#2190) * fix(cache): strip heavy fields from aggregate blob to pass 2MB limit Hub pages only need card data. Drop seriesByRegion24h, metricPanels, editorial text (seoIntro/faq/findings/methodology/disclaimer/abstract/ perChainExplainer) from the blob aggregate slim. Cuts serialized size from ~4.3 MB to under 2 MB so unstable_cache actually persists the entry instead of silently dropping it on every render. * fix: include still-open Gains positions in carry projection * fix: update Gains open action names to v6 API, handle TradePosSizeIncrease * fix: Gains carry projection (v6 action names + still-open positions) * fix(cache): strip heavy fields from aggregate blob to pass 2MB limit Hub pages only need card data. Drop seriesByRegion24h, metricPanels, editorial text (seoIntro/faq/findings/methodology/disclaimer/abstract/ perChainExplainer) from the blob aggregate slim. Cuts serialized size from ~4.3 MB to under 2 MB so unstable_cache actually persists the entry instead of silently dropping it on every render. * fix: include still-open Gains positions in carry projection * fix: update Gains open action names to v6 API, handle TradePosSizeIncrease * fix: widen coin name column for long tickers (xyz:NVDA) * fix(cache): strip heavy fields from aggregate blob to pass 2MB limit Hub pages only need card data. Drop seriesByRegion24h, metricPanels, editorial text (seoIntro/faq/findings/methodology/disclaimer/abstract/ perChainExplainer) from the blob aggregate slim. Cuts serialized size from ~4.3 MB to under 2 MB so unstable_cache actually persists the entry instead of silently dropping it on every render. * fix: include still-open Gains positions in carry projection * fix: update Gains open action names to v6 API, handle TradePosSizeIncrease * fix: widen coin name column in top markets to fit xyz:NVDA * feat(rpc): add Union bench #253 (Nodes.Guru, Stake And Relax, High Stakes) (#2194) * fix(union-rpc): remove em dashes from YAML (#2196) * feat(rpc): Shentu #254 + MANTRA Chain #255 (#2199) * feat(rpc): add Shentu bench #254 and MANTRA Chain bench #255 * fix: remove accidental dev-portal submodule * fix: bench-blob revalidate 300s to fix perp ISR conflict * fix(fee-compare): total position value + inline vault fee explanation * fix(fee-compare): remove vault fee — carry fees settled per-action, not OI surcharge (#2209) * feat(rpc): ICON bench #258 — new icon probe kind, 3 keyless providers --- benchmarks/icon-rpc.yml | 35 +++++++++++ .../rpc-capabilities/cmd/script/config.go | 11 ++++ .../rpc-capabilities/cmd/script/probe.go | 59 ++++++++++++++++++- public/logos/icon.svg | 4 ++ public/logos/iconblockchain.svg | 5 ++ src/data/provider-registry.ts | 18 ++++++ src/lib/brand.ts | 6 ++ src/lib/logo-manifest.ts | 4 ++ 8 files changed, 141 insertions(+), 1 deletion(-) create mode 100644 benchmarks/icon-rpc.yml create mode 100644 public/logos/icon.svg create mode 100644 public/logos/iconblockchain.svg diff --git a/benchmarks/icon-rpc.yml b/benchmarks/icon-rpc.yml new file mode 100644 index 00000000..f45ec814 --- /dev/null +++ b/benchmarks/icon-rpc.yml @@ -0,0 +1,35 @@ +id: 258 +slug: icon-rpc +title: "Fastest free ICON RPC, live no-key endpoint latency" +chain: icon +description: "Latency and availability benchmark for ICON public RPC endpoints" +category: rpc +kind: icon + +providers: + - slug: icon-solidwallet + name: "ICON Foundation" + url: "https://ctz.solidwallet.io" + - slug: icon-community + name: "ICON Community" + url: "https://api.icon.community" + - slug: iconblockchain + name: "iconblockchain.xyz" + url: "https://api.iconblockchain.xyz" + +seo_intro: | + ICON is a South Korean L1 blockchain focused on interoperability and enterprise adoption, using a Delegated Proof of Contribution (DPoC) consensus with ~2-second block finality. ICON nodes expose a JSON-RPC 2.0 API: POST /api/v3 with method icx_getLastBlock returns the latest block height. Free public endpoints are available keyless from the ICON Foundation (ctz.solidwallet.io), ICON Community (api.icon.community), and iconblockchain.xyz. + + This benchmark continuously measures RPC latency, availability and block-height freshness across these public ICON endpoints from three geographic regions. Every provider was live-verified with consecutive keyless icx_getLastBlock probes at launch. + +faq: + - q: "What does this benchmark measure?" + a: "Each probe issues a POST /api/v3 icx_getLastBlock request to retrieve the latest block height. We record round-trip latency (p50/p90/p99), HTTP availability, and whether the returned block height is current. Probes run every 60 seconds from US East, EU West, and AP Southeast." + - q: "Which endpoints are included?" + a: "The benchmark covers the ICON Foundation endpoint (ctz.solidwallet.io), ICON Community (api.icon.community), and iconblockchain.xyz. All three are keyless public endpoints requiring no API key." + - q: "Why does ICON RPC performance matter?" + a: "ICON powers ICX transfers, BTP cross-chain messages, and DApps across the ICON ecosystem. Low-latency RPC access is critical for wallets, DEX aggregators integrating ICON, and validators monitoring chain health." + - q: "How does ICON's ~2-second block time affect staleness detection?" + a: "With blocks every ~2 seconds, a gap of 150 blocks represents roughly 5 minutes of drift — the same threshold we use to classify other fast-finality chains as stale. A provider returning a block more than 150 behind the cross-provider tip is marked stale." + - q: "Can I contribute an endpoint?" + a: "Yes. Open an issue or PR at github.com/ChainBench/OpenChainBench with the endpoint URL and operator name. We verify liveness and independence before adding." diff --git a/harnesses/rpc-capabilities/cmd/script/config.go b/harnesses/rpc-capabilities/cmd/script/config.go index dc35473f..b61dc0d0 100644 --- a/harnesses/rpc-capabilities/cmd/script/config.go +++ b/harnesses/rpc-capabilities/cmd/script/config.go @@ -1837,6 +1837,17 @@ func chains() []Chain { {Slug: "waves-exchange", Name: "Waves Exchange Node", URL: envDefault("RPC_URL_WAVES_EXCHANGE", "https://nodes.waves.exchange")}, }, }, + // 2026-08-29 wave-13. ICON blockchain — JSON-RPC icx_getLastBlock /api/v3, ~2 s/block. 3 keyless providers. + { + Slug: "icon", + Name: "ICON", + Kind: "icon", + Providers: []Provider{ + {Slug: "icon-solidwallet", Name: "ICON Foundation", URL: envDefault("RPC_URL_ICON_SOLIDWALLET", "https://ctz.solidwallet.io")}, + {Slug: "icon-community", Name: "ICON Community", URL: envDefault("RPC_URL_ICON_COMMUNITY", "https://api.icon.community")}, + {Slug: "iconblockchain", Name: "iconblockchain.xyz", URL: envDefault("RPC_URL_ICON_ICONBLOCKCHAIN", "https://api.iconblockchain.xyz")}, + }, + }, // 2026-08-18 wave-8. WAX gaming blockchain (Antelope) — REST GET /v1/chain/get_info, ~0.5 s/block. 3 keyless providers. { Slug: "wax", diff --git a/harnesses/rpc-capabilities/cmd/script/probe.go b/harnesses/rpc-capabilities/cmd/script/probe.go index 6a2569e2..77a2560c 100644 --- a/harnesses/rpc-capabilities/cmd/script/probe.go +++ b/harnesses/rpc-capabilities/cmd/script/probe.go @@ -104,6 +104,9 @@ const ( // veChainStaleBlockGap: VeChain produces one block every ~10 s, // so 30 blocks ≈ 5 min. veChainStaleBlockGap uint64 = 30 + // iconStaleBlockGap: ICON produces one block every ~2 s, + // so 150 blocks ≈ 5 min. + iconStaleBlockGap uint64 = 150 ) // chainTips tracks the highest block seen for each chain across all @@ -328,6 +331,8 @@ func probeOne(ctx context.Context, c Chain, p Provider) { block, result, latency, err = callWavesBlock(probeCtx, p.URL) case "vechain": block, result, latency, err = callVeChainBlock(probeCtx, p.URL) + case "icon": + block, result, latency, err = callICONBlock(probeCtx, p.URL) default: block, hash, result, latency, err = callLatestBlock(probeCtx, p.URL) } @@ -385,6 +390,8 @@ func probeOne(ctx context.Context, c Chain, p Provider) { gap = wavesStaleBlockGap case "vechain": gap = veChainStaleBlockGap + case "icon": + gap = iconStaleBlockGap } if tip > 0 && block+gap < tip { result = "stale" @@ -406,7 +413,7 @@ func probeOne(ctx context.Context, c Chain, p Provider) { case "solana", "polkadot", "cosmos", "starknet", "stellar", "sui", "aptos", "xrpl", "algorand", "gram", "near", "flow", "hedera", "ckb", "multiversx", "neo", - "tezos", "antelope", "waves", "vechain", "dogecoin", "zcash", "bitcoin-cash", "litecoin": + "tezos", "antelope", "waves", "vechain", "icon", "dogecoin", "zcash", "bitcoin-cash", "litecoin": // no consensus participation default: if result == "ok" || result == "stale" { @@ -1465,6 +1472,56 @@ func callVeChainBlock(ctx context.Context, url string) (blockNum uint64, result return blk.Number, "ok", latencyMs, nil } +type iconLastBlock struct { + Result struct { + Height int64 `json:"height"` + } `json:"result"` +} + +// callICONBlock probes an ICON node via POST /api/v3 icx_getLastBlock. +// Returns the block height. Staleness uses iconStaleBlockGap in probeOne. +func callICONBlock(ctx context.Context, url string) (height uint64, result string, latencyMs float64, err error) { + target := strings.TrimRight(url, "/") + "/api/v3" + body := []byte(fmt.Sprintf( + `{"jsonrpc":"2.0","method":"icx_getLastBlock","id":%d,"params":{}}`, + time.Now().UnixNano(), + )) + req, _ := http.NewRequestWithContext(ctx, "POST", target, bytes.NewReader(body)) + req.Header.Set("Content-Type", "application/json") + req.Header.Set("User-Agent", "OpenChainBench/1.0 (+https://openchainbench.com)") + client := &http.Client{Timeout: probeTimeout} + + start := time.Now() + resp, err := client.Do(req) + latencyMs = float64(time.Since(start).Nanoseconds()) / 1e6 + + if err != nil { + if ctx.Err() != nil || strings.Contains(err.Error(), "deadline exceeded") || strings.Contains(err.Error(), "Timeout") { + return 0, "timeout", latencyMs, err + } + return 0, "http_err", latencyMs, err + } + defer resp.Body.Close() + + if resp.StatusCode != 200 { + _, _ = io.Copy(io.Discard, resp.Body) + return 0, "http_err", latencyMs, fmt.Errorf("status %d", resp.StatusCode) + } + + raw, err := io.ReadAll(resp.Body) + if err != nil { + return 0, "http_err", latencyMs, err + } + var blk iconLastBlock + if err := json.Unmarshal(raw, &blk); err != nil { + return 0, "http_err", latencyMs, err + } + if blk.Result.Height <= 0 { + return 0, "jsonrpc_err", latencyMs, fmt.Errorf("icon block missing height") + } + return uint64(blk.Result.Height), "ok", latencyMs, nil +} + // callNeoBlockCount probes a NEO N3 node via getblockcount JSON-RPC. // The result is a plain decimal integer. Staleness uses neoStaleBlockGap in probeOne. func callNeoBlockCount(ctx context.Context, url string) (count uint64, result string, latencyMs float64, err error) { diff --git a/public/logos/icon.svg b/public/logos/icon.svg new file mode 100644 index 00000000..5d65cf52 --- /dev/null +++ b/public/logos/icon.svg @@ -0,0 +1,4 @@ + + + ICON + diff --git a/public/logos/iconblockchain.svg b/public/logos/iconblockchain.svg new file mode 100644 index 00000000..ca414b9c --- /dev/null +++ b/public/logos/iconblockchain.svg @@ -0,0 +1,5 @@ + + + ICON + BLOCKCHAIN + diff --git a/src/data/provider-registry.ts b/src/data/provider-registry.ts index dd0f2bd4..afdb9033 100644 --- a/src/data/provider-registry.ts +++ b/src/data/provider-registry.ts @@ -2672,6 +2672,24 @@ export const PROVIDER_REGISTRY: Record = { }, + // ─── ICON providers (bench 258) ────────────────────────────────────── + "icon-solidwallet": { + url: "https://www.icondev.io", + description: + "ICON Foundation official public RPC node at ctz.solidwallet.io. Keyless icx_getLastBlock endpoint maintained by the ICON Foundation.", + twitter: "@helloiconworld", + }, + "icon-community": { + url: "https://icon.community", + description: + "ICON Community public RPC at api.icon.community. Keyless icx_getLastBlock endpoint run by the ICON community.", + twitter: "@helloiconworld", + }, + iconblockchain: { + url: "https://iconblockchain.xyz", + description: + "iconblockchain.xyz community-operated public ICON RPC node. Keyless icx_getLastBlock endpoint.", + }, // ─── Union providers (bench 253) ───────────────────────────────────── "nodes-guru": { url: "https://nodes.guru", diff --git a/src/lib/brand.ts b/src/lib/brand.ts index 7ce6a49e..b5d76ef2 100644 --- a/src/lib/brand.ts +++ b/src/lib/brand.ts @@ -195,6 +195,12 @@ const BRANDS: Record = { stakewolle: { color: "#F97316" }, // stakewolle orange nodestake: { color: "#8B5CF6" }, // nodestake violet + // ─── ICON (bench 258) ─── + icon: { color: "#00B8CC" }, // ICON teal (official brand) + "icon-solidwallet": { color: "#00B8CC" }, // ICON Foundation inherits brand teal + "icon-community": { color: "#1A9CBB" }, // ICON Community slightly darker teal + iconblockchain: { color: "#0D7A9E" }, // iconblockchain.xyz dark cyan + // ─── Bitcoin Cash chain + providers (bench 244) ─── "bitcoin-cash": { color: "#0AC18E" }, // bch official green bitcore: { color: "#1A1D21", dark: true }, // bitpay dark diff --git a/src/lib/logo-manifest.ts b/src/lib/logo-manifest.ts index e893db5a..9c3f74ba 100644 --- a/src/lib/logo-manifest.ts +++ b/src/lib/logo-manifest.ts @@ -343,6 +343,8 @@ const RAW: Record = { cheqd: "/logos/cheqd.svg", stakewolle: "/logos/stakewolle.svg", nodestake: "/logos/nodestake.svg", + icon: "/logos/icon.svg", + iconblockchain: "/logos/iconblockchain.svg", // ─── Oracle deviation (bench 025) — additional brand logos ─── // (pairs alias to chain/asset logos in the ALIASES block below) @@ -758,6 +760,8 @@ const ALIASES: Record = { "mantrachain-official": "mantrachain", "band-official": "bandchain", "cheqd-official": "cheqd", + "icon-solidwallet": "icon", + "icon-community": "icon", // Non-EVM wave-3 (benches 222-231) — provider-official aliases to chain slug "ecadinfra": "tezos",