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