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
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"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
chain.assertAddress(address)
live · in your browser
its own chain
create("ethereum").assertAddress("0x1f9840a85d5aF5…ADdC4201F984")returned unchanged
a chain with another format
create("bitcoin").assertAddress("0x1f9840a85d5aF5…ADdC4201F984")InvalidAddressError { chain: "bitcoin" }
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("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
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
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
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.comYour 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
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.