Name a chain. Check an address.

Twenty-nine blockchains as classes, one file each: chain ID, CAIP-2, coin type, decimals, explorer, default RPC. The spellings people actually type resolve to the same class, and every chain checks its own address format by decoding it, not by counting characters. Library, CLI, MCP server, Pi and OMP extensions. Nothing here touches a network.

29
chains
11
families
4
agent tools
0
network calls

Registry

matic, btc, arb. Same class every time

getChain("matic") hands you the Polygon class with everything on it, and create("polygon") does the same from the canonical key. Symbols stay out of the automatic index, six chains report ETH and the answer would depend on registration order. This panel walks through 29 chains, every value read off the class in your browser.

  • Keys name the chain, not the ticker: ethereum, bsc, octra. Tickers are aliases
  • Display names round trip, so the name one tool prints resolves in the next call
  • getChain('') throws. A blank string is a mistake, not a request for Ethereum
Registry and aliases
TSethereum.ts
import { getChain } from "@agntn/chains";

// "eth" resolves to "ethereum"
const chain = getChain("eth");

chain.key;      // "ethereum"
chain.name;     // "Ethereum"
chain.symbol;   // "ETH", 18 decimals
chain.chainId;  // "0x1"
chain.caip2;    // "eip155:1"
chain.bip44;    // 60
chain.explorer; // "https://etherscan.io"
Ethereum · evm

Address validation

Decode the bytes, not count the characters

A 34-character window takes a Bitcoin address, a TRON address and half the typos in between. So the validators decode. The panel checks the current sample against its own chain, against a chain with another format, and with one character changed. Where the format carries a checksum the last row fails on it, and each chain's page says which checks stay unverified.

  • Base58 addresses are decoded: Solana wants 32 bytes, TRON 25 under 0x41, XRP its own alphabet
  • Bech32 and Bech32m checksums are verified on Bitcoin, Litecoin and Cardano, the CashAddr one on eCash
  • Rejected is an answer. InvalidAddressError carries the chain key and the address
How each format is checked

chain.assertAddress(address)

live · in your browser

  1. its own chain

    create("ethereum").assertAddress("0x1f9840a85d5aF5…ADdC4201F984")

    returned unchanged

  2. a chain with another format

    create("bitcoin").assertAddress("0x1f9840a85d5aF5…ADdC4201F984")

    InvalidAddressError { chain: "bitcoin" }

  3. one character off

    create("ethereum").assertAddress("0x1f9840a85d5aF5…DADdC4201F98")

    InvalidAddressError { chain: "ethereum" }

Identify

One address, every validator at once

An address of unknown origin gets run through the whole registry. The families light up as the walk goes on: one for most chains, EVM for the 0x address that thirteen chains share, Move for the short 0x1 that Aptos and Sui both write. A match narrows the family. It doesn't say the address is used there, and the tool text says that out loud.

  • identify(address) partitions the registry: chains that accept the format, chains with no validator
  • An EVM address matches all thirteen EVM chains. That's the honest answer, not a bug
  • No validator means unchecked, never a silent no. Right now every built-in chain has one
Identify an address

identify("0x1f9840a85d5a…dC4201F984")

29 validators

EVM

13 of 13

UTXO

0 of 6

Solana

0 of 1

Stellar

0 of 1

XRP Ledger

0 of 1

Move

0 of 2

TON

0 of 1

TRON

0 of 1

Octra

0 of 1

Arweave

0 of 1

Monero

0 of 1

unchecked

0 chains

13 of 29 checked chains accept it: ethereum, base, arbitrum, optimism, polygon, bsc, avalanche, fantom, gnosis, linea, zksync, scroll, berachain. A format match narrows the family, it doesn't prove the address is used on any of them.

Chains

Twenty-nine chains, eleven families

Each chain is a class in its own file with its own metadata. The family classes hold only what their members share: EVM and Move own the address format, everything else is declared per chain, coin type included. The page for a chain says which bytes are checked and which checksum is left alone.

  • Thirteen EVM chains that share one address format and one coin type, 60
  • Six UTXO chains, and a UTXO heritage doesn't mean Bitcoin's encoding: CashAddr, CIP-19, two version bytes on Decred
  • Solana, Stellar, XRP Ledger, Aptos, Sui, TON, TRON, Octra, Arweave and Monero on their own terms
All chains

Agents

Four tools, three hosts

chains mcp serves the tools over stdio, the Pi and OMP extensions render them in the terminal. All three call the same executors, so they answer identically and a fix lands once. Absent fields say so out loud, bip44: none, because a missing coin type reads as not shown and invites a model to supply one from memory.

  • chains_lookup, chains_validate_address, chains_identify_address, chains_list
  • The text carries the whole answer, and a miss names the keys that exist, so the next call has somewhere to go
  • A rejected address is an answer, not a tool error. Only an unknown chain sets isError
MCP, Pi and OMP

toolchains_lookup

MCP · Pi · OMP

input

{
  "chain": "eth"
}

output · content[0].text

Ethereum (ethereum)
symbol: ETH
decimals: 18
type: evm
chainId: 0x1
caip2: eip155:1
bip44: 60
explorer: https://etherscan.io
rpc: https://ethereum-rpc.publicnode.com

Your chain

Extend Chain, call register

Every built-in is a concrete class extending the exported abstract Chain, or EVM when the format is Ethereum's. Yours is the same shape, one file. Throw InvalidAddressError with your key and the address, and identify treats your chain like any other. No plugin manifest.

  • A static key, the metadata fields, and assertAddress when the format is known
  • register(MyChain) puts it behind create, getChain, identify and has
  • Nothing registers itself on import. The registry is one list, so a bundler can drop what you never touch
Custom chains
TSnano.ts
import { Chain, InvalidAddressError, register } from "@agntn/chains";
import type { ChainKey, ChainType } from "@agntn/chains";

// The shape of a Nano account. Its checksum stays unchecked here.
const ADDRESS = /^nano_[13][13456789abcdefghijkmnopqrstuwxyz]{59}$/;

class Nano extends Chain {
  static readonly key = "nano" as ChainKey;
  readonly type = "nano" as ChainType;
  readonly name = "Nano";
  readonly symbol = "XNO";
  override readonly decimals = 30;
  readonly explorer = "https://nanexplorer.com/nano";
  readonly bip44 = 165;

  override assertAddress(address: string): string {
    if (!ADDRESS.test(address)) throw new InvalidAddressError(this.key, address);
    return address;
  }
}

register(Nano);

Start with one command

Pre-1.0, so pin exact versions. And a green check here means the string fits the format, nothing more. It isn't proof the address exists, and it isn't proof it belongs to who you think.

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