Guide

Registry and aliases

Keys and display names plus the aliases people actually type. All land on the same class. How the registry is seeded and why nothing registers itself.

Five functions

import { chains, create, getChain, has, register } from "@agntn/chains";

chains(); // ["ethereum", "base", "arbitrum", ...] in registration order
has("solana"); // true
create("solana"); // a fresh Solana instance
getChain("sol"); // the same class, by alias
register(MyChain); // a class this package does not ship

create takes a canonical key and throws UnknownChainError for anything else. getChain is the forgiving one, and the one the CLI and the tools use.

What getChain accepts

Three spellings, tried in this order:

  1. The canonical key. ethereum, bsc, zksync, octra. Lowercase, names the chain rather than its ticker. A short name is still a name, so bsc and arbitrum stay keys.
  2. An alias. The ticker spellings and nicknames a caller may already be holding: eth, matic, pol, btc, arb, arb1, op, bnb, binance, avax, ftm, xdai, bera, ltc, pep, xec, ada, sol, xlm, xrp, ripple, apt, trx, oct, ar, xmr, dcr, and a few more like coinbase for Base and mainnet for Ethereum. Each chain's page lists its own.
  3. The display name. Arbitrum One, BNB Chain, zkSync Era, read straight off the registered classes. That round trip matters for agents, which get a name out of one call and put it into the next.

Input is lowercased and trimmed first, so Matic and BTC work. Symbols aren't in the automatic index on purpose: six chains report ETH, and matching on it would make the answer depend on registration order. eth resolves only because it's in the alias table, pointing at Ethereum.

getChain("matic").key; // "polygon"
getChain("BNB Chain").key; // "bsc"
getChain("Arbitrum One").key; // "arbitrum"
getChain(); // Ethereum
getChain(""); // throws UnsupportedChainError

getChain() with no argument still means Ethereum. getChain("") or a blank string doesn't, that's a caller mistake, and it throws rather than quietly answering about the wrong chain. The error message quotes the input, so blank and control character input stays visible in a log.

Where the classes come from

Chain (abstract)
├── EVM (abstract)
│   ├── Ethereum
│   ├── Base
│   ├── Arbitrum
│   └── ...
├── Move (abstract)
│   ├── Aptos
│   └── Sui
├── Bitcoin
├── Solana
├── Stellar
├── Xrpl
├── Ton
├── Tron
├── Octra
├── Arweave
├── Monero
└── Decred

Each chain is its own class holding its own metadata. EVM and Move own the family type and the address format, and everything else is declared per class, down to the coin type all thirteen EVM chains repeat.

One list puts them in the registry: builtins in src/chains/index.ts. No module registers itself as it loads, so the CLI is the only entry that runs anything on import, and sideEffects in package.json says so, which lets a bundler drop whatever your project never touches. The cost is that a chain file counts for nothing until its class joins that list, and a test compares the two so it can't drift quietly.

Registering your own

register(ChainClass) puts a class under its static key. From then on create, getChain, has, chains() and identify see it like any built-in. Registering the same key again replaces the class. The whole recipe, including the two casts the closed key and type unions ask for, is in Custom chains.

@agntn/chains·MIT license· A format check, not proof an address exists. Nothing on this site talks to a chain.