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
128 changes: 124 additions & 4 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: Current native transfer fee across L1 blockchains
seo_title: "Cheapest blockchain transaction fee 2026: Ethereum, Solana, BNB, Avalanche, TRON, Cardano, SUI, TON, Stellar, Litecoin, Monero USD live"
seo_description: "Live USD cost of a native-token transfer on 11 L1 chains: Ethereum, Solana, BNB, Avalanche, TRON, Cardano, SUI, TON, Stellar, Litecoin, Monero. Slow/standard/fast tiers refreshed every 30s, USD price from Mobula."
subtitle: "Current USD cost of moving the chain's native asset, refreshed every 30 seconds for the same 11 L1s tracked on the finality bench."
title: Current native transfer fee across L1 and L2 blockchains
seo_title: "Cheapest blockchain transaction fee 2026: L1 and L2 USD live (Ethereum, Arbitrum, Optimism, Base, zkSync, Solana, BNB, Avalanche)"
seo_description: "Live USD cost of a native-token transfer across 20 L1 and L2 chains. L1: Ethereum, Solana, BNB, Avalanche, TRON, Cardano, SUI, TON, Stellar, Litecoin, Monero. L2: Arbitrum, Optimism, Base, zkSync, Linea, Scroll, Blast, Mantle, Taiko. Slow/standard/fast tiers refreshed every 30s."
subtitle: "Current USD cost of moving the chain's native asset across 20 L1 and L2 chains, refreshed every 30 seconds."

seo_intro: |
This page shows the live USD cost of sending the native token on every major Layer-1 blockchain. We sample the chain-native fee market (eth_feeHistory for EVM chains, getRecentPrioritizationFees for Solana, koios protocol params for Cardano, fee_stats for Stellar, fee_estimate for Monero, mempool oracles for Litecoin, getChainParameters for TRON, sui_getReferenceGasPrice for SUI, hardcoded typical observed for TON), convert to the chain's smallest unit, then multiply by the native-token USD price pulled from Mobula every 30 seconds. The result is a head-to-head comparison of what it actually costs an end user to move one unit of value on each chain, rather than a gas-price comparison in gwei or lamports which is meaningless across protocols.
Expand Down Expand Up @@ -83,6 +83,7 @@ prometheus:
providers:
- slug: ethereum
name: Ethereum
layer: l1
tag: EIP-1559 fee market, 21000 gas × (base + p50 priority)
formula: "Median USD cost of a native ETH transfer over 24h: (base_fee + p50 priority_fee) × 21000 gas × ETH USD price."
queries:
Expand All @@ -95,6 +96,7 @@ providers:

- slug: bnb
name: BNB Chain
layer: l1
tag: EIP-1559 fee market, 21000 gas
formula: "Median USD cost of a native BNB transfer over 24h."
queries:
Expand All @@ -107,6 +109,7 @@ providers:

- slug: avalanche
name: Avalanche
layer: l1
tag: C-Chain EIP-1559 fee market, 21000 gas
formula: "Median USD cost of a native AVAX transfer over 24h."
queries:
Expand All @@ -119,6 +122,7 @@ providers:

- slug: solana
name: Solana
layer: l1
tag: 5000 lamport base + getRecentPrioritizationFees × 200 CU
formula: "Median USD cost of a native SOL transfer over 24h: 5000 lamports + p50 priority × 200 CU × SOL USD price."
queries:
Expand All @@ -131,6 +135,7 @@ providers:

- slug: tron
name: TRON
layer: l1
tag: Bandwidth model, 268 bytes × getTransactionFee SUN/byte
formula: "Deterministic USD cost of a native TRX transfer using the bandwidth-rate parameter × 268 bytes typical transfer × TRX USD price."
queries:
Expand All @@ -143,6 +148,7 @@ providers:

- slug: cardano
name: Cardano
layer: l1
tag: min_fee_b + size × min_fee_a, deterministic
formula: "Protocol-deterministic ADA transfer cost: koios live min_fee_a/b × 250 bytes typical × ADA USD price."
queries:
Expand All @@ -155,6 +161,7 @@ providers:

- slug: sui
name: Sui
layer: l1
tag: suix_getReferenceGasPrice × 2_000_000 gas budget
formula: "USD cost of a native SUI transfer: live reference gas price × 2M gas budget × SUI USD price."
queries:
Expand All @@ -167,6 +174,7 @@ providers:

- slug: ton
name: TON
layer: l1
tag: Hardcoded 0.005 TON typical wallet-v4 transfer
formula: "Conservative typical-observed cost of a TON wallet transfer × TON USD price. TON has no fee-estimate RPC; this is the observed median."
queries:
Expand All @@ -179,6 +187,7 @@ providers:

- slug: stellar
name: Stellar
layer: l1
tag: 100 stroops base per operation, surge during congestion
formula: "Live horizon fee_stats.last_ledger_base_fee × 1 op × XLM USD price."
queries:
Expand All @@ -191,6 +200,7 @@ providers:

- slug: litecoin
name: Litecoin
layer: l1
tag: litecoinspace fee oracle × 225 vBytes
formula: "Mempool-based litoshi/vByte × 225 vBytes typical P2WPKH transfer × LTC USD price."
queries:
Expand All @@ -203,6 +213,7 @@ providers:

- slug: monero
name: Monero
layer: l1
tag: get_fee_estimate × 1500 bytes RingCT
formula: "Monero-rpc fee per byte × 1500 bytes typical 1-in-2-out RingCT × XMR USD price."
queries:
Expand All @@ -212,3 +223,112 @@ providers:
mean: avg_over_time(tx_fee_native_transfer_usd{chain="monero",tier="std"}[24h])
success: avg_over_time(tx_fee_health{chain="monero"}[24h])
series: tx_fee_native_transfer_usd{chain="monero",tier="std"}

- slug: arbitrum
name: Arbitrum One
layer: l2
tag: "Optimistic rollup (Nitro), 21000 gas"
formula: "Median USD cost of a native ETH transfer on Arbitrum One over 24h. L2 execution cost only; L1 data posting fee excluded (Phase 2)."
queries:
p50: quantile_over_time(0.50, tx_fee_native_transfer_usd{chain="arbitrum",tier="std"}[24h])
p90: quantile_over_time(0.90, tx_fee_native_transfer_usd{chain="arbitrum",tier="fast"}[24h])
p99: quantile_over_time(0.99, tx_fee_native_transfer_usd{chain="arbitrum",tier="fast"}[24h])
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: optimism
name: Optimism
layer: l2
tag: "OP Stack optimistic rollup, 21000 gas"
formula: "Median USD cost of a native ETH transfer on Optimism over 24h. L2 execution cost only; L1 data posting fee excluded (Phase 2)."
queries:
p50: quantile_over_time(0.50, tx_fee_native_transfer_usd{chain="optimism",tier="std"}[24h])
p90: quantile_over_time(0.90, tx_fee_native_transfer_usd{chain="optimism",tier="fast"}[24h])
p99: quantile_over_time(0.99, tx_fee_native_transfer_usd{chain="optimism",tier="fast"}[24h])
mean: avg_over_time(tx_fee_native_transfer_usd{chain="optimism",tier="std"}[24h])
success: avg_over_time(tx_fee_health{chain="optimism"}[24h])
series: tx_fee_native_transfer_usd{chain="optimism",tier="std"}
- slug: base
name: Base
layer: l2
tag: "OP Stack optimistic rollup (Coinbase), 21000 gas"
formula: "Median USD cost of a native ETH transfer on Base over 24h. L2 execution cost only; L1 data posting fee excluded (Phase 2)."
queries:
p50: quantile_over_time(0.50, tx_fee_native_transfer_usd{chain="base",tier="std"}[24h])
p90: quantile_over_time(0.90, tx_fee_native_transfer_usd{chain="base",tier="fast"}[24h])
p99: quantile_over_time(0.99, tx_fee_native_transfer_usd{chain="base",tier="fast"}[24h])
mean: avg_over_time(tx_fee_native_transfer_usd{chain="base",tier="std"}[24h])
success: avg_over_time(tx_fee_health{chain="base"}[24h])
series: tx_fee_native_transfer_usd{chain="base",tier="std"}
- slug: zksync
name: zkSync Era
layer: l2
tag: "ZK rollup, 21000 gas"
formula: "Median USD cost of a native ETH transfer on zkSync Era over 24h. L2 execution cost only; L1 data posting fee excluded (Phase 2)."
queries:
p50: quantile_over_time(0.50, tx_fee_native_transfer_usd{chain="zksync",tier="std"}[24h])
p90: quantile_over_time(0.90, tx_fee_native_transfer_usd{chain="zksync",tier="fast"}[24h])
p99: quantile_over_time(0.99, tx_fee_native_transfer_usd{chain="zksync",tier="fast"}[24h])
mean: avg_over_time(tx_fee_native_transfer_usd{chain="zksync",tier="std"}[24h])
success: avg_over_time(tx_fee_health{chain="zksync"}[24h])
series: tx_fee_native_transfer_usd{chain="zksync",tier="std"}
- slug: linea
name: Linea
layer: l2
tag: "ZK rollup (Stark), 21000 gas"
formula: "Median USD cost of a native ETH transfer on Linea over 24h. L2 execution cost only; L1 data posting fee excluded (Phase 2)."
queries:
p50: quantile_over_time(0.50, tx_fee_native_transfer_usd{chain="linea",tier="std"}[24h])
p90: quantile_over_time(0.90, tx_fee_native_transfer_usd{chain="linea",tier="fast"}[24h])
p99: quantile_over_time(0.99, tx_fee_native_transfer_usd{chain="linea",tier="fast"}[24h])
mean: avg_over_time(tx_fee_native_transfer_usd{chain="linea",tier="std"}[24h])
success: avg_over_time(tx_fee_health{chain="linea"}[24h])
series: tx_fee_native_transfer_usd{chain="linea",tier="std"}
- slug: scroll
name: Scroll
layer: l2
tag: "zkEVM rollup, 21000 gas"
formula: "Median USD cost of a native ETH transfer on Scroll over 24h. L2 execution cost only; L1 data posting fee excluded (Phase 2)."
queries:
p50: quantile_over_time(0.50, tx_fee_native_transfer_usd{chain="scroll",tier="std"}[24h])
p90: quantile_over_time(0.90, tx_fee_native_transfer_usd{chain="scroll",tier="fast"}[24h])
p99: quantile_over_time(0.99, tx_fee_native_transfer_usd{chain="scroll",tier="fast"}[24h])
mean: avg_over_time(tx_fee_native_transfer_usd{chain="scroll",tier="std"}[24h])
success: avg_over_time(tx_fee_health{chain="scroll"}[24h])
series: tx_fee_native_transfer_usd{chain="scroll",tier="std"}
- slug: blast
name: Blast
layer: l2
tag: "OP Stack optimistic rollup (Blast), 21000 gas"
formula: "Median USD cost of a native ETH transfer on Blast over 24h. L2 execution cost only; L1 data posting fee excluded (Phase 2)."
queries:
p50: quantile_over_time(0.50, tx_fee_native_transfer_usd{chain="blast",tier="std"}[24h])
p90: quantile_over_time(0.90, tx_fee_native_transfer_usd{chain="blast",tier="fast"}[24h])
p99: quantile_over_time(0.99, tx_fee_native_transfer_usd{chain="blast",tier="fast"}[24h])
mean: avg_over_time(tx_fee_native_transfer_usd{chain="blast",tier="std"}[24h])
success: avg_over_time(tx_fee_health{chain="blast"}[24h])
series: tx_fee_native_transfer_usd{chain="blast",tier="std"}
- slug: mantle
name: Mantle
layer: l2
tag: "OP Stack optimistic rollup (Mantle), 21000 gas"
formula: "Median USD cost of a native ETH transfer on Mantle over 24h. L2 execution cost only; L1 data posting fee excluded (Phase 2)."
queries:
p50: quantile_over_time(0.50, tx_fee_native_transfer_usd{chain="mantle",tier="std"}[24h])
p90: quantile_over_time(0.90, tx_fee_native_transfer_usd{chain="mantle",tier="fast"}[24h])
p99: quantile_over_time(0.99, tx_fee_native_transfer_usd{chain="mantle",tier="fast"}[24h])
mean: avg_over_time(tx_fee_native_transfer_usd{chain="mantle",tier="std"}[24h])
success: avg_over_time(tx_fee_health{chain="mantle"}[24h])
series: tx_fee_native_transfer_usd{chain="mantle",tier="std"}
- slug: taiko
name: Taiko
layer: l2
tag: "Based rollup, 21000 gas"
formula: "Median USD cost of a native ETH transfer on Taiko over 24h. L2 execution cost only; L1 data posting fee excluded (Phase 2)."
queries:
p50: quantile_over_time(0.50, tx_fee_native_transfer_usd{chain="taiko",tier="std"}[24h])
p90: quantile_over_time(0.90, tx_fee_native_transfer_usd{chain="taiko",tier="fast"}[24h])
p99: quantile_over_time(0.99, tx_fee_native_transfer_usd{chain="taiko",tier="fast"}[24h])
mean: avg_over_time(tx_fee_native_transfer_usd{chain="taiko",tier="std"}[24h])
success: avg_over_time(tx_fee_health{chain="taiko"}[24h])
series: tx_fee_native_transfer_usd{chain="taiko",tier="std"}
37 changes: 35 additions & 2 deletions src/components/benchmark-body.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
import { DonutChart } from "@/components/donut-chart";
import { RegionGrid } from "@/components/region-grid";
import { MetricViewTabs } from "@/components/metric-view-tabs";
import { LayerFilter, type LayerSelection } from "@/components/layer-filter";
import { CountLeaderboard } from "@/components/count-leaderboard";
import { SummaryStat } from "@/components/summary-stat";
import { ViewSwitcher } from "@/components/view-switcher";
Expand Down Expand Up @@ -143,23 +144,29 @@
const searchParams = useSearchParams();
const urlChain = searchParams.get("chain");
const urlRegion = searchParams.get("region");
const urlLayer = searchParams.get("layer");
const resolvedInitialChain =
(urlChain && chainOptions.find((c) => c.value === urlChain)?.value) ?? initialChain;
const resolvedInitialRegion =
(urlRegion && regionOptions.find((r) => r.value === urlRegion)?.value) ?? initialRegion;
const resolvedInitialLayer: LayerSelection =
urlLayer === "l1" || urlLayer === "l2" ? urlLayer : "all";

const [chain, setChain] = useState<string | null>(resolvedInitialChain);
const [region, setRegion] = useState<string | null>(resolvedInitialRegion);
const [layer, setLayer] = useState<LayerSelection>(resolvedInitialLayer);

useEffect(() => {
const url = new URL(window.location.href);
syncParam(url, "chain", chain, chainOptions);
syncParam(url, "region", region, regionOptions);
if (layer === "all") url.searchParams.delete("layer");
else url.searchParams.set("layer", layer);
const next = url.pathname + (url.search ? url.search : "");
if (next !== window.location.pathname + window.location.search) {
window.history.replaceState(null, "", next);
}
}, [chain, region, chainOptions, regionOptions]);
}, [chain, region, layer, chainOptions, regionOptions]);

const fallbackChain = chainOptions[0]?.value ?? null;
const fallbackRegion = regionOptions[0]?.value ?? null;
Expand All @@ -171,6 +178,29 @@
Object.values(variants)[0];
if (!benchmark) return null;

// L1/L2 filter counts, derived from the full unfiltered results so the
// pill counts are stable as the user toggles the filter.
const layerCounts = useMemo(() => {

Check failure on line 183 in src/components/benchmark-body.tsx

View workflow job for this annotation

GitHub Actions / check

React Hook "useMemo" is called conditionally. React Hooks must be called in the exact same order in every component render

Check failure on line 183 in src/components/benchmark-body.tsx

View workflow job for this annotation

GitHub Actions / check

React Hook "useMemo" is called conditionally. React Hooks must be called in the exact same order in every component render
let l1 = 0;
let l2 = 0;
for (const r of benchmark.results) {
if (r.layer === "l1") l1++;
else if (r.layer === "l2") l2++;
}
return { all: benchmark.results.length, l1, l2 };
}, [benchmark.results]);

// Filtered benchmark for the ledger table only. Charts above keep the
// full provider set so the chart vs ledger don't disagree on which
// providers exist.
const ledgerBenchmark = useMemo(() => {

Check failure on line 196 in src/components/benchmark-body.tsx

View workflow job for this annotation

GitHub Actions / check

React Hook "useMemo" is called conditionally. React Hooks must be called in the exact same order in every component render

Check failure on line 196 in src/components/benchmark-body.tsx

View workflow job for this annotation

GitHub Actions / check

React Hook "useMemo" is called conditionally. React Hooks must be called in the exact same order in every component render
if (layer === "all") return benchmark;
return {
...benchmark,
results: benchmark.results.filter((r) => r.layer === layer),
};
}, [benchmark, layer]);

const isDraft = benchmark.status === "draft";
const { fieldMin, fieldMedian, fieldMax, tailMin, tailMax, tailSpread } =
computeFieldStats(benchmark.results);
Expand All @@ -181,7 +211,7 @@
// they always saw.
const allowedViews = viewsForBenchmark(benchmark);
const defaultView = defaultViewFor(benchmark);
const [view, setView, viewMounted] = useViewPreference(

Check failure on line 214 in src/components/benchmark-body.tsx

View workflow job for this annotation

GitHub Actions / check

React Hook "useViewPreference" is called conditionally. React Hooks must be called in the exact same order in every component render

Check failure on line 214 in src/components/benchmark-body.tsx

View workflow job for this annotation

GitHub Actions / check

React Hook "useViewPreference" is called conditionally. React Hooks must be called in the exact same order in every component render
benchmark.slug,
defaultView,
allowedViews,
Expand All @@ -192,7 +222,7 @@
// hidden when they switch to distribution or donut - the model is
// "this is the field of providers the reader chose to focus on",
// not "what each view chose to drop". Resets on bench navigation.
const [excluded, setExcluded] = useState<Set<string>>(() => new Set());

Check failure on line 225 in src/components/benchmark-body.tsx

View workflow job for this annotation

GitHub Actions / check

React Hook "useState" is called conditionally. React Hooks must be called in the exact same order in every component render

Check failure on line 225 in src/components/benchmark-body.tsx

View workflow job for this annotation

GitHub Actions / check

React Hook "useState" is called conditionally. React Hooks must be called in the exact same order in every component render
const toggleExclude = (slug: string) =>
setExcluded((prev) => {
const next = new Set(prev);
Expand All @@ -208,16 +238,16 @@
// being split between the dimension row and the chart toolbar.
const chartRegions = chartOnlyRegions(benchmark);
const showChartRegionRow = regionOptions.length === 0 && chartRegions.length > 1;
const [chartRegion, setChartRegion] = useState<string>("all");

Check failure on line 241 in src/components/benchmark-body.tsx

View workflow job for this annotation

GitHub Actions / check

React Hook "useState" is called conditionally. React Hooks must be called in the exact same order in every component render. Did you accidentally call a React Hook after an early return?

Check failure on line 241 in src/components/benchmark-body.tsx

View workflow job for this annotation

GitHub Actions / check

React Hook "useState" is called conditionally. React Hooks must be called in the exact same order in every component render. Did you accidentally call a React Hook after an early return?

// Active companion-metric panel. null = main spec metric (default chart
// data, default unit, default header). When a panel id is set, the chart
// pulls its per-provider series from panel.seriesByProvider, swaps the
// header label to panel.label, and the Y-axis unit to panel.unit.
const [activePanelId, setActivePanelId] = useState<string | null>(null);

Check failure on line 247 in src/components/benchmark-body.tsx

View workflow job for this annotation

GitHub Actions / check

React Hook "useState" is called conditionally. React Hooks must be called in the exact same order in every component render. Did you accidentally call a React Hook after an early return?

Check failure on line 247 in src/components/benchmark-body.tsx

View workflow job for this annotation

GitHub Actions / check

React Hook "useState" is called conditionally. React Hooks must be called in the exact same order in every component render. Did you accidentally call a React Hook after an early return?
const activePanel =
benchmark.metricPanels?.find((p) => p.id === activePanelId) ?? null;
const chartRegionOptions: ChainOption[] = useMemo(

Check failure on line 250 in src/components/benchmark-body.tsx

View workflow job for this annotation

GitHub Actions / check

React Hook "useMemo" is called conditionally. React Hooks must be called in the exact same order in every component render. Did you accidentally call a React Hook after an early return?

Check failure on line 250 in src/components/benchmark-body.tsx

View workflow job for this annotation

GitHub Actions / check

React Hook "useMemo" is called conditionally. React Hooks must be called in the exact same order in every component render. Did you accidentally call a React Hook after an early return?
() => [
{ value: "all", label: "All" },
...chartRegions.map((r) => ({ value: r, label: REGION_DISPLAY[r] ?? r })),
Expand Down Expand Up @@ -388,7 +418,10 @@
? `Product ledger · sorted by ${activePanel.label}`
: "Product ledger · sorted by p50"}
</p>
<LedgerTable benchmark={benchmark} activePanel={activePanel} />
{layerCounts.all > 0 && (layerCounts.l1 > 0 || layerCounts.l2 > 0) && (
<LayerFilter selected={layer} onSelect={setLayer} counts={layerCounts} />
)}
<LedgerTable benchmark={ledgerBenchmark} activePanel={activePanel} />
</div>

{benchmark.unit !== "count" &&
Expand Down
63 changes: 63 additions & 0 deletions src/components/layer-filter.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
"use client";

/**
* Three-pill filter (All / L1 / L2) shown above the ledger table on
* benches that mix L1 and L2 providers (currently only `network-fees`).
*
* Pure presentation: holds no state. The parent (benchmark-body) owns
* the active layer and passes it down with the swap callback. The
* parent also decides whether to render this at all — when no provider
* carries a `layer` field, the filter stays hidden.
*/
export type LayerSelection = "all" | "l1" | "l2";

export function LayerFilter({
selected,
onSelect,
counts,
}: {
selected: LayerSelection;
onSelect: (v: LayerSelection) => void;
counts: { all: number; l1: number; l2: number };
}) {
return (
<div className="mb-3 flex flex-wrap items-center gap-1">
<span className="mr-2 text-[10px] uppercase tracking-[0.16em] text-ink-faint">
Layer
</span>
<Pill label="All" count={counts.all} active={selected === "all"} onClick={() => onSelect("all")} />
<Pill label="L1" count={counts.l1} active={selected === "l1"} onClick={() => onSelect("l1")} />
<Pill label="L2" count={counts.l2} active={selected === "l2"} onClick={() => onSelect("l2")} />
</div>
);
}

function Pill({
label,
count,
active,
onClick,
}: {
label: string;
count: number;
active: boolean;
onClick: () => void;
}) {
return (
<button
type="button"
onClick={onClick}
className={[
"rounded px-2.5 py-1 text-[11px] font-sans tabular uppercase tracking-[0.1em] font-medium transition-colors",
active
? "bg-ink text-paper"
: "text-ink-muted hover:text-ink",
].join(" ")}
>
{label}
<span className={["ml-1.5 text-[10px]", active ? "text-paper/70" : "text-ink-faint"].join(" ")}>
{count}
</span>
</button>
);
}
10 changes: 10 additions & 0 deletions src/lib/spec-schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,12 @@ export const ProviderType = z.enum([
"relay", // relayer / settlement network (Relay, deBridge solver layer)
]);

/** Network-layer classification used by benches that mix L1 and L2 chains
* in the same provider set (e.g. network-fees). Powers the L1/L2/All
* toggle pill on the bench page so readers can split the leaderboard
* by execution layer without leaving the page. */
export const ProviderLayer = z.enum(["l1", "l2"]);

const provider = z.object({
/** Stable identifier. also used as the metric label. */
slug,
Expand All @@ -128,6 +134,10 @@ const provider = z.object({
/** Optional architectural category. When set, a badge appears next to
* the name so readers understand the comparison. */
type: ProviderType.optional(),
/** Optional network-layer classification. When set, a bench that mixes
* L1 and L2 providers (network-fees) renders an L1/L2/All toggle pill
* that filters the ledger by this field. */
layer: ProviderLayer.optional(),
/** Optional secondary metric (e.g. "Chains covered") shown in the table. */
secondary: z
.object({ label: z.string(), value: z.string() })
Expand Down
Loading
Loading