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, "/") + "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/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: "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/logos/cheqd.svg", stakewolle: "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/logos/stakewolle.svg", nodestake: "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/logos/nodestake.svg", + icon: "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/logos/icon.svg", + iconblockchain: "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/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",