Overview
@ethernauta/chain
A registry of 500+ EIP-155 chain definitions. Each chain is a structured object carrying its chain ID, native currency, public RPC endpoints, block explorers, ENS registry (where applicable), bridges, and feature flags.
pnpm add @ethernauta/chain The shape of a chain
import { eip155_1, type Chain } from "@ethernauta/chain";
eip155_1
// {
// chain_id: 1,
// name: "Ethereum Mainnet",
// short_name: "eth",
// native_currency: { name: "Ether", symbol: "ETH", decimals: 18 },
// rpc: ["https://eth.llamarpc.com", ...],
// explorers: [{ name: "Etherscan", url: "...", standard: "EIP3091" }],
// ens: { registry: "0x00000000000C2E074eC69A0dFb2997BA6C7d2e1e" },
// features: ["EIP1559"],
// ...
// } The full Chain schema is exported (chainSchema); inferred type is Chain. Adjunct types: NativeCurrency, Explorer, Bridge, Parent, EnsRegistry, Feature, ShortName, RedFlagSchema.
Naming convention
Every chain is exported as eip155_<chain_id>:
import {
eip155_1, // Ethereum Mainnet
eip155_10, // OP Mainnet
eip155_137, // Polygon
eip155_8453, // Base
eip155_42161, // Arbitrum One
eip155_11155111, // Sepolia
} from "@ethernauta/chain"; The name disambiguates testnets, L2s, sidechains, and forks unambiguously. Chain ID is the only thing that’s ever truly identifying.
Using a chain with a resolver
import { create_reader } from "@ethernauta/transport";
import { eth_block_number } from "@ethernauta/eth";
import { eip155_1, eip155_8453, eip155_42161 } from "@ethernauta/chain";
const reader = create_reader([eip155_1, eip155_8453, eip155_42161]);
// pick at call time
const eth_block = await eth_block_number()(
reader({ chain_id: eip155_1.chain_id }),
);
const base_block = await eth_block_number()(
reader({ chain_id: eip155_8453.chain_id }),
); The reader doesn’t fetch any chain it wasn’t told about. The list you pass is the resolver’s universe.
Why a registry
No magic strings. chain_id: 1 is correct but unreadable. chain_id: eip155_1.chain_id puts the chain at the call site.
Self-contained RPCs. Each chain carries its own RPC URL list. The HTTP transport rotates through them on failure. No environment variable to wire, no separate config file to maintain.
ENS-aware. Chains that have an ENS deployment (Ethereum, Goerli, Sepolia, Holesky) carry their registry address. The ENS resolution flow looks it up automatically.
EIP-155 by definition. The registry is keyed on the canonical chain ID, so re-broadcast attacks across forks can’t happen by accident.
Picking a custom RPC
import { eip155_1 } from "@ethernauta/chain";
const my_chain = {
...eip155_1,
rpc: ["https://my-private-rpc.example.com"],
};
const reader = create_reader([my_chain]); Chain is a plain object. Override what you need. The schema is permissive — the only required fields are chain_id, name, rpc.
See also
- @ethernauta/transport — the resolver factories that consume
Chain[]. - EIP-3085 → wallet_addEthereumChain — adding a chain to a wallet at runtime.
- EIP-3326 → wallet_switchEthereumChain — switching chains.