Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 19 additions & 2 deletions benchmarks/l2-block-time.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,16 @@

slug: l2-block-time
number: "009"
title: Fastest L2 block time, live across Arbitrum, Base, Optimism and 6 more
title: Fastest L2 block time, live across Robinhood, Arbitrum, Base and 7 more
seo_title: "Fastest L2 block time 2026"
seo_description: "Fastest L2 sequencer block time live: Arbitrum, Blast, Optimism, Base, Linea and more ranked by newHeads interval."
seo_description: "Fastest L2 sequencer block time live: Robinhood Chain, Arbitrum, Optimism, Base, Linea and more ranked by real block interval."
subtitle: Wall-clock interval in milliseconds between two consecutive newHeads events on each L2 sequencer, refreshed continuously.

per_chain_explainer:
- slug: robinhood
h2: "Robinhood Chain block time"
body: |
Robinhood Chain block time is {{p50:robinhood}} (p50, 24h), the most aggressive cadence in the cohort: the Arbitrum Orbit stack lets Robinhood's sequencer target ~100 ms blocks for tokenized-stock settlement. The chain launched mainnet on July 1, 2026, pays gas in ETH and settles to Ethereum blobs. Measurement note: Robinhood exposes no public WebSocket, so block time here is derived from block-number deltas over 2-second HTTP poll windows, an accurate average that does not capture per-block jitter.
- slug: arbitrum
h2: "Arbitrum One block time"
body: |
Expand Down Expand Up @@ -66,6 +70,7 @@ abstract: |
outlier sample is capped at 5 min so a reconnect can't pollute p50.

methodology:
- "Robinhood Chain exception: the sequencer exposes no public WebSocket, so its block time is derived from eth_blockNumber deltas polled every 2 seconds over HTTP. A window that advanced N blocks contributes N samples of (elapsed / N) ms, which preserves per-block weighting and yields an accurate average; per-block jitter is not captured on this chain and this note is the disclosure."
- "Cadence: continuous WebSocket subscription per L2, one persistent connection, no polling."
- "Subscribe payload: `{\"jsonrpc\":\"2.0\",\"id\":1,\"method\":\"eth_subscribe\",\"params\":[\"newHeads\"]}`. No API key, public endpoints (publicnode.com mostly, era.zksync.io/ws for zkSync)."
- "Sample: block-time(N) = T_recv(N) − T_recv(N-1), measured in milliseconds with `time.Now()` precision on the harness clock. Block-number duplicates from the same connection are filtered."
Expand Down Expand Up @@ -101,6 +106,18 @@ faq:
a: "Polygon zkEVM and Mode don't expose a public WebSocket endpoint without an API key, so we defer them to v2 when an API-keyed contributor endpoint lands. Starknet uses the Cairo stack with a different RPC shape, it would live in a dedicated bench rather than be shoehorned into the EVM cohort here. Berachain, Sonic and other adjacent chains are L1s, not L2s, so they live in the l1-finality bench."

providers:
- slug: robinhood
name: Robinhood Chain
tag: Arbitrum Orbit, ~100ms blocks, HTTP-polled (no public WSS)
formula: "Median milliseconds per block on Robinhood Chain, derived from eth_blockNumber deltas over 2s HTTP poll windows (no public WebSocket), p50 over 24h."
queries:
p50: histogram_quantile(0.50, sum(rate(l2_block_time_milliseconds_histogram_bucket{chain="robinhood"}[24h])) by (le))
p90: histogram_quantile(0.90, sum(rate(l2_block_time_milliseconds_histogram_bucket{chain="robinhood"}[24h])) by (le))
p99: histogram_quantile(0.99, sum(rate(l2_block_time_milliseconds_histogram_bucket{chain="robinhood"}[24h])) by (le))
mean: sum(rate(l2_block_time_milliseconds_histogram_sum{chain="robinhood"}[24h])) / sum(rate(l2_block_time_milliseconds_histogram_count{chain="robinhood"}[24h]))
success: avg_over_time(l2_block_time_health{chain="robinhood"}[24h])
sample_size: sum(increase(l2_block_time_samples_total{chain="robinhood"}[24h]))
series: avg_over_time(l2_block_time_milliseconds{chain="robinhood"}[1h])
- slug: arbitrum
name: Arbitrum One
tag: Nitro fast-finality, sub-second sequencer
Expand Down
22 changes: 19 additions & 3 deletions benchmarks/network-fees.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@

slug: network-fees
number: "031"
title: Cheapest blockchain transaction fee, live across 14 L1 and L2 chains
title: Cheapest blockchain transaction fee, live across 15 L1 and L2 chains
seo_title: "Cheapest crypto transaction fee 2026"
seo_description: "Cheapest blockchain transaction fee in USD live across 14 chains. Solana, Base, Arbitrum, BNB Chain ranked."
subtitle: "Live USD cost of one native token transaction on 14 Layer 1 and Layer 2 chains, refreshed every 30 seconds."
seo_description: "Cheapest blockchain transaction fee in USD live across 15 chains. Solana, Base, Robinhood Chain, Arbitrum, BNB ranked."
subtitle: "Live USD cost of one native token transaction on 15 Layer 1 and Layer 2 chains, refreshed every 30 seconds."

seo_intro: |
This page answers one question. How much does it cost in dollars to send one transaction on each major blockchain right now. We track 14 chains in parallel and refresh the number every 30 seconds. The eight Layer 1 chains are Ethereum, Solana, BNB Chain, TRON, Cardano, Sui, Litecoin and Monero. The six Layer 2 rollups are Arbitrum, Base, zkSync Era, Linea, Mantle and Taiko. For every chain we query its own fee market directly (eth_feeHistory for the EVM family, getRecentPrioritizationFees on Solana, koios epoch params on Cardano, get_fee_estimate on Monero, the mempool oracle on Litecoin, getChainParameters on TRON, suix_getReferenceGasPrice on Sui), convert the result to the smallest native unit, then multiply by the live USD price of the chain's native token from Mobula. The output is the actual dollar amount a wallet user pays today. No gwei to lamport conversion, no marketing claim. Compare Ethereum gas now versus Solana fee in USD, see whether Arbitrum is still cheaper than Base today, find out which Layer 1 has the lowest transaction cost this minute.
Expand Down Expand Up @@ -71,6 +71,10 @@ per_chain_explainer:
h2: "Arbitrum transaction fee"
body: |
Arbitrum One native transfer fee is {{p50:arbitrum}} (p50, 24h). Arbitrum Nitro runs an EIP-1559 fee market against the sequencer at sub-second cadence; native ETH transfers are 21000 gas and sit at fractions of a cent because the sequencer's priority fee is near-zero by design. The L1 data-posting component (Ethereum blobs since Dencun) is not yet included in this figure, only L2 execution cost. Computed via `eth_feeHistory` on the Arbitrum sequencer RPC times live ETH price.
- slug: robinhood
h2: "Robinhood Chain transaction fee"
body: |
Robinhood Chain native transfer fee is {{p50:robinhood}} (p50, 24h). Robinhood's Arbitrum Orbit rollup posts a very low base fee (~0.05 gwei) and zero priority rewards, the typical Orbit pattern where the centralized sequencer needs no tip market; ETH is the gas asset and the L1 blob-posting cost is excluded from this figure like every other L2 on this page. Computed via `eth_feeHistory` on the official sequencer RPC times live ETH price.
- slug: base
h2: "Base transaction fee"
body: |
Expand Down Expand Up @@ -264,6 +268,18 @@ providers:
mean: avg_over_time(tx_fee_native_transfer_usd{chain="arbitrum",tier="std"}[24h])
success: avg_over_time(tx_fee_health{chain="arbitrum"}[24h])
series: tx_fee_native_transfer_usd{chain="arbitrum",tier="std"}
- slug: robinhood
name: Robinhood Chain
layer: l2
tag: "Arbitrum Orbit rollup Robinhood, 21000 gas"
formula: "Median USD cost of a native ETH transfer on Robinhood Chain over 24h. L2 execution cost only, L1 data posting fee excluded for now."
queries:
p50: quantile_over_time(0.50, tx_fee_native_transfer_usd{chain="robinhood",tier="std"}[24h])
p90: quantile_over_time(0.90, tx_fee_native_transfer_usd{chain="robinhood",tier="fast"}[24h])
p99: quantile_over_time(0.99, tx_fee_native_transfer_usd{chain="robinhood",tier="fast"}[24h])
mean: avg_over_time(tx_fee_native_transfer_usd{chain="robinhood",tier="std"}[24h])
success: avg_over_time(tx_fee_health{chain="robinhood"}[24h])
series: tx_fee_native_transfer_usd{chain="robinhood",tier="std"}
- slug: base
name: Base
layer: l2
Expand Down
1 change: 1 addition & 0 deletions harnesses/chain-kpis/cmd/script/registry.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ var Registry = []Chain{
{Slug: "arbitrum", DefiLlama: "Arbitrum", Mobula: "Arbitrum", NativeSymbol: "ETH"},
{Slug: "optimism", DefiLlama: "Optimism", Mobula: "Optimistic", NativeSymbol: "ETH"},
{Slug: "base", DefiLlama: "Base", Mobula: "Base", NativeSymbol: "ETH"},
{Slug: "robinhood", DefiLlama: "Robinhood Chain", Mobula: "Robinhood Chain", NativeSymbol: "ETH"},
{Slug: "zksync", DefiLlama: "ZKsync Era", Mobula: "ZkSync", NativeSymbol: "ETH"},
{Slug: "linea", DefiLlama: "Linea", Mobula: "Linea", NativeSymbol: "ETH"},
{Slug: "scroll", DefiLlama: "Scroll", Mobula: "Scroll", NativeSymbol: "ETH"},
Expand Down
12 changes: 12 additions & 0 deletions harnesses/l2-block-time/cmd/script/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,12 @@ type L2Chain struct {
Slug string
Name string
URL string
// Poll switches the chain to HTTP block-number polling instead of a
// WebSocket newHeads subscription, for sequencers that expose no
// public WSS (Robinhood Chain). Block time is then derived from
// block-number deltas over each poll window; per-block jitter is
// not captured and the methodology discloses it.
Poll bool
}

// l2Chains is the source of truth for which Layer-2s appear in the
Expand All @@ -22,6 +28,12 @@ type L2Chain struct {
// rebuild — useful if a public endpoint goes flaky.
func l2Chains() []L2Chain {
return []L2Chain{
{
Slug: "robinhood",
Name: "Robinhood Chain",
URL: envDefault("RPC_HTTP_ROBINHOOD", "https://rpc.mainnet.chain.robinhood.com"),
Poll: true,
},
{
Slug: "arbitrum",
Name: "Arbitrum One",
Expand Down
106 changes: 106 additions & 0 deletions harnesses/l2-block-time/cmd/script/sequencer_poll.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
package main

import (
"bytes"
"encoding/json"
"fmt"
"io"
"net/http"
"time"
)

// HTTP polling fallback for sequencers with no public WebSocket
// (Robinhood Chain). Every pollInterval we read eth_blockNumber; a
// window that advanced by N blocks contributes N histogram samples of
// (elapsed / N) ms. This yields an accurate average block time while
// keeping the "one sample per block" weighting of the WS path; what it
// cannot capture is per-block jitter, which the bench methodology
// discloses for polled chains.
const (
pollInterval = 2 * time.Second
pollTimeout = 10 * time.Second
maxWindowBlend = 600 // safety cap on samples per window (reconnect gaps)
)

func runPollChain(ch L2Chain) error {
client := &http.Client{Timeout: pollTimeout}
var lastNum int64
var lastAt time.Time
fails := 0

t := time.NewTicker(pollInterval)
defer t.Stop()
fmt.Printf("[%s] HTTP polling mode, interval=%s\n", ch.Slug, pollInterval)

for now := range t.C {
num, err := fetchBlockNumber(client, ch.URL)
if err != nil || num == 0 {
fails++
if fails >= 5 {
blockTimeHealth.WithLabelValues(ch.Slug).Set(0)
}
if fails == 5 || fails%150 == 0 {
fmt.Printf("[%s] poll error x%d: %v\n", ch.Slug, fails, err)
}
continue
}
fails = 0
blockTimeHealth.WithLabelValues(ch.Slug).Set(1)

if lastNum > 0 && num > lastNum && !lastAt.IsZero() {
delta := num - lastNum
elapsedMs := float64(now.Sub(lastAt).Milliseconds())
perBlock := elapsedMs / float64(delta)
if perBlock > 0 && perBlock < maxSampleMs {
blockTimeGauge.WithLabelValues(ch.Slug).Set(perBlock)
n := int(delta)
if n > maxWindowBlend {
n = maxWindowBlend
}
for i := 0; i < n; i++ {
blockTimeHistogram.WithLabelValues(ch.Slug).Observe(perBlock)
}
blockTimeSampleCtr.WithLabelValues(ch.Slug).Add(float64(n))
}
}
lastNum = num
lastAt = now
}
return nil
}

func fetchBlockNumber(client *http.Client, url string) (int64, error) {
body := []byte(fmt.Sprintf(
`{"jsonrpc":"2.0","method":"eth_blockNumber","params":[],"id":%d}`,
time.Now().UnixNano(),
))
req, err := http.NewRequest("POST", url, bytes.NewReader(body))
if err != nil {
return 0, err
}
req.Header.Set("Content-Type", "application/json")
req.Header.Set("User-Agent", "OpenChainBench/1.0 (+https://openchainbench.com)")
resp, err := client.Do(req)
if err != nil {
return 0, err
}
defer resp.Body.Close()
raw, err := io.ReadAll(io.LimitReader(resp.Body, 1<<16))
if err != nil {
return 0, err
}
if resp.StatusCode != 200 {
return 0, fmt.Errorf("status %d", resp.StatusCode)
}
var env struct {
Result string `json:"result"`
}
if err := json.Unmarshal(raw, &env); err != nil {
return 0, err
}
n := parseHexInt64(env.Result)
if n == 0 {
return 0, fmt.Errorf("empty block number")
}
return n, nil
}
7 changes: 6 additions & 1 deletion harnesses/l2-block-time/cmd/script/sequencer_ws.go
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,12 @@ func StartSequencerWS() {
go func() {
backoff := minBackoff
for {
err := runChain(ch)
var err error
if ch.Poll {
err = runPollChain(ch)
} else {
err = runChain(ch)
}
blockTimeHealth.WithLabelValues(ch.Slug).Set(0)
blockTimeReconnectCtr.WithLabelValues(ch.Slug).Inc()
if err != nil {
Expand Down
9 changes: 9 additions & 0 deletions harnesses/transaction-fee/cmd/script/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,15 @@ func loadConfig() *Config {
HasPriorityMarket: true,
Layer: "l2",
},
{
Slug: "robinhood",
Name: "Robinhood Chain",
Kind: KindEVM,
RPCURL: getenvDefault("RPC_ROBINHOOD", "https://rpc.mainnet.chain.robinhood.com"),
MobulaSlug: "ethereum",
HasPriorityMarket: true,
Layer: "l2",
},
{
Slug: "base",
Name: "Base",
Expand Down
8 changes: 8 additions & 0 deletions src/lib/chains.ts
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,14 @@ export const CHAINS: ChainEntry[] = [
description:
"OP Stack reference rollup, 2 s sequencer cadence, EIP-4844 blob calldata settlement on Ethereum.",
},
{
slug: "robinhood",
label: "Robinhood Chain",
category: "L2",
nativeSymbol: "ETH",
description:
"Robinhood's Arbitrum Orbit rollup for tokenized stocks, ~100 ms blocks, ETH gas, Ethereum blob settlement, sequencer operated by Robinhood with 10% of chain fees shared to ArbitrumDAO.",
},
{
slug: "base",
label: "Base",
Expand Down
Loading