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 @@
+
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 @@
+
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",