Skip to content

Repository files navigation

@agntn/chains

npm version npm downloads license Ask DeepWiki

⛓️ Thirty blockchains as classes, and an address check that actually decodes. Ask for matic and you get Polygon, chain ID and coin type included. Paste an address and you get the chains that would take it. Same thing from the terminal, from TypeScript or from an agent.

Why?

Every web3 thing I write needs the same few facts. Polygon's chain ID. Bitcoin's coin type. Which explorer to link. And is this string even an address? I kept declaring all of that again in every library, a bit differently each time. The address check was a regex counting characters, so a TRON address passed as Bitcoin. Now it all lives here once, as classes, and the check decodes the bytes.

Docs, one page per chain and a playground are at chains.agntn.dev. The playground is this library running in your browser.

✨ Features

  • 🧬 Thirty chains, one abstract Chain. Each chain is its own class with its own facts. EVM and Move hold what a family shares.
  • 🏷️ Aliases people actually type. matic, btc, arb, ripple. Display names work too, so BNB Chain comes back as bsc.
  • 🔍 Validators that decode. Base58Check, Bech32, CashAddr, CIP-19, whatever the chain uses. Checksums get checked.
  • 🕵️ Identify an address of unknown origin. Every validator gets a go and you learn the family.
  • 🔗 Transaction ids, same idea. 0x and 64 hex on the EVM chains and Aptos, 64 hex on the UTXO chains, Monero, TRON and the XRP Ledger, lowercase only on Stellar and Octra because their nodes read nothing else, base58 decoded to 64 bytes on Solana and 32 on Sui, hex or padded base64 on TON, 43 base64url characters on Arweave. A txid pasted wrong fails here, not three calls later inside an RPC.
  • 🧾 Metadata checked, not remembered. Every decimals value was looked up at the source. XEC really has two.
  • 🫙 Missing stays missing. Octra has no coin type and no CAIP-2, so you get undefined. Nothing made up.
  • 🪶 The core imports nothing at runtime. Nothing registers itself on import either, so your bundler drops what you don't use.
  • 🤖 CLI, library and agent tools give the same answer. Six commands, five tools, one set of executors.
  • 🧯 Errors you catch by type. InvalidAddressError carries .chain and .address. No message parsing.

📦 Install

pnpm add @agntn/chains

Node.js 24 or newer.

🚀 First call

npx @agntn/chains info matic
Polygon PoS (polygon)
  symbol      POL
  decimals    18
  type        evm
  bip44       60
  chainId     0x89
  caip2       eip155:137
  explorer    https://polygonscan.com
  rpc         https://polygon-bor-rpc.publicnode.com

No key, no config, no network. All of that sits in the Polygon class. matic is an alias, POL is the token's name these days, polygon is the canonical key. From here on I'll write plain chains. That's pnpm exec chains in a project, or pnpm add -g @agntn/chains once.

Now an address you found in a log:

chains identify 0x1f9840a85d5aF5bf1D1762F925BDADdC4201F984
ethereum   evm     Ethereum
base       evm     Base
arbitrum   evm     Arbitrum One
optimism   evm     Optimism
polygon    evm     Polygon PoS
bsc        evm     BNB Chain
avalanche  evm     Avalanche C-Chain
fantom     evm     Fantom Opera
gnosis     evm     Gnosis Chain
linea      evm     Linea
zksync     evm     zkSync Era
scroll     evm     Scroll
berachain  evm     Berachain
arc        evm     Arc

Fourteen. Not helpful? It's the honest answer. Every EVM chain uses the same 20 bytes, so no decoder can tell them apart. Give it a format that belongs to one chain and the list gets short:

chains identify bc1qjvm9jkrjw9uvsn8905dwa6eau0guyc9laau03a
bitcoin    utxo    Bitcoin

One. Now a typo. This is the Bitcoin wiki's example address with the last character changed from 2 to 3:

chains validate btc 1BvBMSEYstWetqTFn5Au4m4GFg7xJaNVN3

 ERROR  Invalid bitcoin address: "1BvBMSEYstWetqTFn5Au4m4GFg7xJaNVN3"

Right length, right alphabet, right first character. The checksum still says no. Exit code is 1, so scripts can trust it.

A few more:

chains resolve "BNB Chain"
chains resolve xrp
chains list --type utxo
chains info oct --json
chains identify 11111111111111111111111111111111
chains validate eth 0x5c504ed432cb51138bcf09aa5e8a410dd4a1e204ef84bfed1be16dfba1b22060 --txid
chains list --json | jq -r '.[] | select(.type == "move") | .key'

Commands

Command What it does Example
info Metadata for one chain, Ethereum if you name none chains info matic
resolve Key, ticker, alias or display name in, key out chains resolve "BNB Chain"
validate One address, or with --txid one txid, against one chain's format chains validate btc bc1q...
identify Every registered chain that accepts an address chains identify 0x1f98...
list The registry, --type for one family chains list --type utxo
mcp The MCP server on stdio chains mcp

--json on info, identify and list. info, resolve and validate exit 1 when they fail. identify doesn't, a miss is an answer too. More in the CLI guide.

🧠 Library

import { create, getChain, identify, InvalidAddressError } from "@agntn/chains";

const polygon = getChain("matic");
polygon.key; // "polygon"
polygon.chainId; // "0x89"
polygon.caip2; // "eip155:137"

getChain("btc").assertAddress("bc1qjvm9jkrjw9uvsn8905dwa6eau0guyc9laau03a"); // returns it
getChain("eth").assertTxid("0x5c504ed432cb51138bcf09aa5e8a410dd4a1e204ef84bfed1be16dfba1b22060"); // the first mainnet transaction, returned too

try {
  getChain("btc").assertAddress("1BvBMSEYstWetqTFn5Au4m4GFg7xJaNVN3");
} catch (error) {
  error instanceof InvalidAddressError; // true
  (error as InvalidAddressError).chain; // "bitcoin"
}

identify("0x1f9840a85d5aF5bf1D1762F925BDADdC4201F984").matches.length; // 13
create("bitcoin").decimals; // 8

Most of the API is right there. create(key) wants the canonical key. getChain(whatever) takes any spelling and defaults to Ethereum. chains() lists the keys, register(Yours) adds one. assertAddress is a format check and nothing more. It doesn't know if the address exists. assertTxid is the same thing for a transaction id, on every chain that ships. A custom chain that skips it throws TxidValidationUnsupportedError, and validatesTxid tells you before you ask. Errors are one family under ChainsError, six of them. More: Registry, Address validation, Identify, Metadata.

🗺️ Chains

Family Chains What the check decodes
evm ethereum, base, arbitrum, optimism, polygon, bsc, avalanche, fantom, gnosis, linea, zksync, scroll, berachain, arc 40 hex digits behind 0x, mixed case has to pass EIP-55
utxo bitcoin, litecoin, pepecoin, ecash, cardano, decred Base58Check with the checksum, BLAKE-256 for Decred, Bech32 and Bech32m for bc1 and ltc1, CashAddr for eCash, CIP-19 for Cardano
solana solana 32 base58 bytes, exactly
stellar stellar SEP-23 Strkeys with the CRC16, muxed accounts and contracts included
xrpl xrpl Base58Check under the ledger's own alphabet, classic accounts and X-addresses
move aptos, sui All 32 bytes of hex, or the one-digit short form AIP-40 allows
ton ton The TEP-2 friendly form in either base64 alphabet, tag, workchain and CRC16 checked
tron tron 25 Base58Check bytes under version 0x41
octra octra oct and 44 characters, that's the whole format
arweave arweave 43 characters of base64url, a 32-byte hash
monero monero Block base58, the network byte, the 69 or 77 byte envelope and the Keccak checksum

Transaction ids: 0x and 64 hex digits on evm and Aptos, 64 hex digits on utxo, monero, tron and xrpl, the same 64 in lowercase only on stellar and octra, base58 decoding to 64 bytes on solana and 32 on Sui, 64 hex digits or 44 characters of padded base64 on ton, the address rule on arweave. Testnet addresses are refused wherever the format can tell. Each chain's page says which checksum is verified and which is left alone: Chains.

🤖 Agents

chains mcp
pi install npm:@agntn/chains
omp install @agntn/chains
{
  "mcpServers": {
    "chains": { "command": "npx", "args": ["-y", "@agntn/chains", "mcp"] }
  }
}

Five tools, the same five on MCP, Pi and OMP. A rejected address is an answer, not a tool error. An unknown chain comes back with the keys that do exist. And fourteen EVM matches are fourteen possibilities, the tool says so itself. Agents guide.

🚫 What this does not do

Keys. No mnemonics, no derivation, no signing. That's @agntn/keys. No RPC either. rpcDefault is a string you hand to something else. And a passing address is well formed, not funded, and not yours.

🧩 Adding a chain

Missing one? Extend Chain, add a key, a type, the metadata and an assertAddress, then register() it. getChain and identify won't know the difference. Nano is the worked example: Custom chains.

🛠️ Development

pnpm install
pnpm dev         # obuild --stub
pnpm fmt         # builds, then oxlint --fix and oxfmt
pnpm lint        # builds, then oxlint
pnpm typecheck   # tsc, then a build and the extensions against dist/
pnpm test        # vitest
pnpm build       # obuild
pnpm docs        # the Docus site, bundled from src/

💛 Thanks

Anthropic and OpenAI both give open source projects access to their models, through Claude for Open Source and Codex for Open Source. A lot of this package was written with that help. Thanks, both of you <3

📄 License

MIT

About

Canonical blockchain classes with registry, aliases, and address validation

Topics

Resources

Stars

1 star

Watchers

1 watching

Forks

Releases

Used by

Contributors

Languages