Guide

Getting Started

Install the package, load a chain, and get a wallet out in five lines.
Still experimental. Package name, public API, the lazy loader, and the MCP tool surface can all change before 1.0. Pin exact versions if you build on it now.
Never point this at real funds. Every key that passes through the library, the explorer, or the MCP server is plaintext in a process, a log, or an agent transcript. Treat it as burned the moment it exists. Real money belongs on a hardware wallet.

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 eight 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

First wallet

my-wallet.js
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 is 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

ChainCurveAddresses
Bitcoinsecp256k1legacy, p2sh, segwit, p2wsh, taproot
Ethereumsecp256k1EIP-55 hex
Basesecp256k1EIP-55 hex, same as Ethereum
TRONsecp256k1base58check, starts with T
Solanaed25519base58 public key
Aptosed255190x hex, SHA3-256
Suied25519 or secp256k10x hex, Blake2b
Cardanoed25519bech32 base, enterprise, stake

Each chain has its own page under Blockchains with the exact rules and the gotchas.

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. secp256k1 chains get it from @noble/curves, ed25519 chains from Web Crypto, so the same code works 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.

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 is 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 pages explain how to pick one.

@agntn/keys·MIT license· Keys never leave the browser.