One key. Every chain.
Typed key generation, address derivation, and message signing for ten blockchains. One interface in TypeScript, the same eighteen tools over MCP, and nothing ever leaves the process.
- 11
- chains
- 2
- curves
- 0
- network calls
- 18
- MCP tools
Derivation
Keys in, addresses out
A private key is an integer. Multiply it by the generator, hash the result, encode the hash, and you have an address. Every chain is the same three steps with different rules. The panel walks the keyspace live.
- Both curves from @noble, nothing else underneath
- Legacy, SegWit, Taproot, EIP-55, base58check, bech32
- Same 32 bytes, ten chains, derived in this tab
private key 1
Bitcoin · secp256k1
- 132-byte secretinteger in 1 … n − 10000000000000000000000000000000000000000000000000000000000000001
- 2public keyk · G, compressed, 33 bytes0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798
- 3addresshash, then encode
- legacyP2PKH · base58check1BgGZ9tcN4rm9KBzDn7KprQz87SZ26SAMH
- segwitP2WPKH · bech32bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4
- taprootP2TR · bech32mbc1pmfr3p9j00pfxjh0zmgp99y8zftmd3s5pmedqhyptwy6lm87hf5sspknck9
One interface
Same calls, every chain
Every blockchain is a class with the same shape. Swap the import and the rest of the code stays. A chain that can't do what you asked throws, Decred on a segwit address, Cardano on an HD walk, instead of handing you something that only looks right.
- generateWallet, getAddress, signMessage on every driver
- Drivers load lazily, only the chains you use ship
- Custom chains extend the same abstract class
import { useBlockchain, blockchains } from "@agntn/keys";
const chain = useBlockchain(await blockchains.bitcoin()());
const wallet = chain.generateWallet();
const signature = chain.signMessage("hello", wallet.private);
// address of private key 1 on this chain
// bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4HD wallets
Mnemonic to wallet in one call
Walk a BIP39 mnemonic down a BIP44 path and get the wallet at the end of it. Paths are parsed and validated, so a hardened segment in the wrong place fails before derivation. Puzzle phrases with a bad checksum go through on request, words untouched.
- BIP32 for secp256k1, SLIP-10 for ed25519
- Bitcoin and Litecoin infer the address type from the purpose
- Broken checksum? Derive anyway and get a warning with the wallet
derivation path
BIP44 · Ethereum · test vector
const wallet = chain.deriveHDWallet(mnemonic, "m/44'/60'/0'/0/0");
// wallet.address
// 0x9858EfFD232B4033E47d90003D41EC34EcaEda94Blockchains
Ten drivers, one shape
Each chain is an adapter over the shared primitives. Adding one means implementing the hashing and encoding rules, not the cryptography.
- Mainnet and testnet address formats
- Address validation for every chain
- Sui on both curves, Cardano enterprise and stake
Agents
Eighteen tools over MCP
Everything the library does is exposed as an MCP tool with the same parameters. Point an agent at it and it derives, validates, and signs without touching the network. The Pi extension runs the same executors from a checkout.
- Keys, WIF, mnemonics, BIP44 paths, addresses, signing
- stdio server started with one command
- A generated mnemonic comes back marked as already in the transcript
toolkeys_get_address
stdio · keys mcp
request
{
"blockchain": "bitcoin",
"publicKey": "0279be667ef9dcbbac…f81798",
"addressType": "segwit"
}result
{
"chain": "bitcoin",
"network": "mainnet",
"address": "bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4"
}Start with one command
Experimental until 1.0. Pin exact versions and keep real funds on a hardware wallet.