Getting Started
Why this exists
Every chain has its own idea of a key, an address, and a signature. Bitcoin hashes twice and encodes in base58, Ethereum takes the tail of a Keccak hash, Solana just base58 encodes the public key, Cardano wants bech32 with a header byte. Pulling one library per chain into a project means five APIs, five option shapes, and five ways to get an address wrong.
@agntn/keys puts ten chains behind one class shape. Same calls, same option object, same wallet type. The chain decides the rules, you decide the key.
Install
pnpm add @agntn/keys
Node.js 24 or newer. Pure JavaScript all the way down, nothing to compile, and no node: import anywhere, so a browser bundle needs no shim. The Keyspace explorer is exactly that bundle.
First wallet
import { useBlockchain, blockchains } from "@agntn/keys";
// Concrete classes load on demand. First call takes options, second call imports and constructs.
const bitcoin = await blockchains.bitcoin()();
const ethereum = await blockchains.ethereum()();
const bitcoinChain = useBlockchain(bitcoin);
const ethereumChain = useBlockchain(ethereum);
const btcWallet = bitcoinChain.generateWallet();
const ethWallet = ethereumChain.generateWallet();
console.log(btcWallet.address); // 1...
console.log(ethWallet.address); // 0x...
The double call looks odd the first time. It's deliberate: blockchains.bitcoin({ network: "testnet" }) only records the options, and the second () is the moment the chain module actually gets imported. Nothing you never call ends up in your bundle.
What ships
| Chain | Curve | Addresses |
|---|---|---|
| Bitcoin | secp256k1 | legacy, p2sh, segwit, p2wsh, taproot |
| Litecoin | secp256k1 | the same five, with L, M and ltc1 prefixes |
| Decred | secp256k1 | legacy ECDSA P2PKH, starts with Ds |
| Ethereum | secp256k1 | EIP-55 hex |
| Base | secp256k1 | EIP-55 hex, same as Ethereum |
| TRON | secp256k1 | base58check, starts with T |
| Solana | ed25519 | base58 public key |
| Stellar | ed25519 | G StrKey, base32 with a checksum |
| Aptos | ed25519 | 0x hex, SHA3-256 |
| Sui | ed25519 or secp256k1 | 0x hex, Blake2b |
| Cardano | ed25519 | bech32 base, enterprise, stake |
Each chain has its own page under Blockchains with the exact rules and the gotchas. Two of them refuse deriveHDWallet on purpose, Decred and Cardano, and their pages say why.
The three steps
Every chain does the same three things, with different rules for each.
1. A private key is 32 random bytes
const privateKey = bitcoinChain.generateKeyPrivate();
A 64 character hex string. Both curves ask @noble/curves for it, randomSecretKey() on secp256k1 and on ed25519, and noble reads globalThis.crypto underneath. Same code in Node and in the browser.
2. The public key comes from the curve
const publicKey = bitcoinChain.getKeyPublic(privateKey);
const keys = bitcoinChain.generateKeys(); // both at once
secp256k1 chains give you a compressed 33 byte key by default. Pass { compressed: false } for the 65 byte one. ed25519 keys are always 32 bytes.
3. The address is a hash, then an encoding
const address = bitcoinChain.getAddress(publicKey);
const segwit = bitcoinChain.getAddress(publicKey, "segwit");
The second argument is the address type. Chains with one format ignore it, chains with several use it to pick, and Decred throws if you ask for one it doesn't have.
generateWallet() runs all three and hands back { keys: { private, public }, address }.
Checking an address
bitcoinChain.validateAddress("1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa"); // true
Every built in chain validates its own formats, including checksums where the format has one. It's a format check, not a lookup: a valid address can still have zero history on chain.
Which curve am I on
bitcoinChain.curve; // 'secp256k1'
solanaChain.curve; // 'ed25519'
suiChain.curve; // ['ed25519', 'secp256k1']
Sui is the only chain that answers with a list. Its page explains how to pick one.
Agents
The same code runs as an MCP server over stdio, 18 tools from keys_generate_wallet through keys_bip44_path:
npx -y @agntn/keys mcp
{
"mcpServers": {
"keys": { "command": "npx", "args": ["-y", "@agntn/keys", "mcp"] }
}
}
Wallets, HD derivation, addresses, signing, BIP44 paths, mnemonics in all ten BIP39 lists, WIF both ways and public key conversion, with the same parameters the library takes. The Pi extension in packages/pi runs the exact same executors from a checkout. A generated mnemonic comes back with a line saying it's in the transcript now. That line isn't decoration, it's the whole security model: public puzzle material and throwaway keys only.