Skip to content
agntnPublic

About

A TypeScript library for interacting with various blockchains.

Topics

Resources

Contributing

Stars

5 stars

Watchers

0 watching

Forks

Latest commit

Β 

History

317 Commits

Folders and files

NameName
Last commit message
Last commit date
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

@agntn/keys

npm version npm downloads license Ask DeepWiki

πŸ”‘ Keys, addresses and signatures from Bitcoin to the XRP Ledger, from a mnemonic or from nothing at all. Bitcoin gets its five address types, Solana gets ed25519, your agent gets them as MCP tools, and none of it should ever meet real money.

Warning

@agntn/keys is experimental. The public API and the tool surfaces can still move before the first stable release. Pin exact versions if you build on it now.

Caution

Not audited. This code has never had a security audit. Do not use it in production, with real funds or with sensitive data. It is meant for agents, puzzles and local experiments only. It comes as is, without warranty of any kind, and the authors are not liable for any loss, as the MIT license states. Anything that matters wants an audited library.

Why?

Every chain has its own wallet library and its own idea of what a key is. One wants a Buffer, one a Uint8Array, one has a KeyPair class and a second one for testnet. Then a puzzle hands you a mnemonic, and all of them say "invalid checksum" and stop talking to you. So here it's one Blockchain interface and the same generateWallet() on every chain. The puzzle cases live in the API, not in a fork.

The docs live at keys.agntn.dev, keyspace explorer included.

✨ Features

  • ⛓️ Twenty-four chains, one interface. Bitcoin and its forks, Ethereum, Solana, Cosmos, Monero, TON and the rest of the table below. Same methods on every one.
  • 🧬 Two curves. secp256k1 and ed25519. Sui and the XRP Ledger take either.
  • 🏠 Bitcoin the way Bitcoin wants it. Legacy, P2SH, segwit, P2WSH and taproot. Your path picks the type.
  • 🌱 Mnemonic in, wallet out. BIP39 into BIP32 or SLIP-10, passphrase optional, all ten word lists.
  • πŸ‘€ An xpub is enough to watch. Addresses from an xpub, ypub or zpub, no secret anywhere in the call.
  • 🧩 Puzzle mnemonics are welcome. Bad checksum, missing word, wrong order, a typo. There's a function for each.
  • ✍️ Signatures the way wallets write them. Core's signmessage, ethers, Keplr, polkadot.js, monero-wallet-cli and more, each on the chain it belongs to.
  • πŸ”Œ One chain at a time. blockchains.solana()() imports Solana and nothing else. A Bitcoin tool never pays for Cardano.
  • πŸ€– Tools for your agent. MCP over stdio or HTTP, plus Pi and OMP extensions. Same code behind all of them.

πŸ“¦ Install

pnpm add @agntn/keys

Node.js 26 or newer. Pure JavaScript all the way down, nothing to compile.

πŸš€ First wallet

import { blockchains, useBlockchain } from "@agntn/keys";

const eth = useBlockchain(await blockchains.ethereum()());
console.log(eth.generateWallet());
{
  keys: {
    private: 'ac959e31102bc6ac0c9404f162d79ef83d4c2542efe6ad760fe9def09367c912',
    public: '02c162cbe709894b813b9c107c6021bb875ba6c86a1628a17c97a75103686131a1'
  },
  address: '0xf322A26408EcE18b4b4b41f3923E3B325954e8CA'
}

That private key now lives in a README on GitHub. Most burned key you'll see today. Good, that's the only kind this package is for. The double call is the lazy loader: the first takes options, the second imports the chain. No network anywhere, it's all math.

The most public mnemonic on earth, one path per address type:

const mnemonic =
  "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about";
const btc = useBlockchain(await blockchains.bitcoin()());

for (const path of ["m/44'/0'/0'/0/0", "m/49'/0'/0'/0/0", "m/84'/0'/0'/0/0", "m/86'/0'/0'/0/0"]) {
  console.log(path, btc.deriveHDWallet(mnemonic, { path }).address);
}
m/44'/0'/0'/0/0 1LqBGSKuX5yYUonjxT5qGfpUsXKYYWeabA
m/49'/0'/0'/0/0 37VucYSaXLCAsxYyAPfbSi9eh4iEcbShgf
m/84'/0'/0'/0/0 bc1qcr8te4kr609gcawutmrza0j4xv80jy8z306fyu
m/86'/0'/0'/0/0 bc1p5cyxnuxmeuwuvkwfem96lqzszd02n6xdcjrs20cac6yqjjwudpxqkedrcr

Nobody passed an address type. 44 is legacy, 49 is P2SH, 84 is segwit, 86 is taproot. The path already said which one you meant. Disagree with your own path? Put addressType right next to it.

Only have the public half? The account's extended public key walks the normal levels under it:

const zpub =
  "zpub6rFR7y4Q2AijBEqTUquhVz398htDFrtymD9xYYfG1m4wAcvPhXNfE3EfH1r1ADqtfSdVCToUG868RvUUkgDKf31mGDtKsAYz2oz2AGutZYs";
console.log(btc.deriveXpubWallet(zpub, { path: "m/0/0" }));
{
  keys: {
    public: '0330d54fd0dd420a6e5f8d3624f5f3482cae350f79d5f0753bf5beef9c2d91af3c'
  },
  address: 'bc1qcr8te4kr609gcawutmrza0j4xv80jy8z306fyu',
  prefix: 'zpub',
  addressType: 'segwit'
}

Same address as m/84'/0'/0'/0/0 above, and the z picked segwit. Don't lean on that too hard. Plenty of wallets export a BIP84 account as a plain xpub, so pass addressType: "segwit" with the path. Hardened levels and xprv get refused.

🧠 Library

import { blockchains, useBlockchain } from "@agntn/keys";
import { convertPublicKey } from "@agntn/keys/secp256k1";
import { encode } from "@agntn/keys/wif";

const sol = useBlockchain(await blockchains.solana()());
const wallet = sol.deriveHDWallet(mnemonic, { path: "m/44'/501'/0'/0'", passphrase: "TREZOR" });
const signature = sol.signMessage("hello", { privateKey: wallet.keys.private });
sol.verifyMessage("hello", { signature, publicKey: wallet.keys.public }); // true
sol.validateAddress(wallet.address); // true

const btc = useBlockchain(await blockchains.bitcoin()());
const { keys } = btc.generateWallet();
encode(keys.private, { chain: "bitcoin" }); // WIF, K... or L..., compressed
convertPublicKey(keys.public, { compressed: false }); // 04..., 130 hex chars

There isn't much more to it. Every chain has generateKeyPrivate, getKeyPublic, getAddress, validateAddress, signMessage and verifyMessage. One trap: a legacy address hashes the public key bytes. Compressed and uncompressed give two addresses from one key, so keep the compressed flag next to it. Checking an Ethereum signature with ethers? Sign with { recovered: true }. Skip it and ethers reads your 64 bytes as EIP-2098 compact, then answers with the wrong address instead of an error. Everything else: Keys, Addresses, Wallets, EVM chains.

🧩 Puzzles

A puzzle mnemonic with a broken checksum is not a wrong answer, it's Tuesday. The claimed Movie Enigma solution has one:

import { inspect, getMnemonicWordCandidates } from "@agntn/keys/bip39";

const puzzle =
  "path mad alien apology escape spare miss goddess leopard crime visit clock start first blade guard close barrel term screen matrix toy ghost shine";

console.log(inspect(puzzle));
const wallet = btc.deriveHDWallet(puzzle, { path: "m/84'/0'/0'/0/0", allowInvalidChecksum: true });
console.log(wallet.address);
console.log(wallet.warnings);
console.log(getMnemonicWordCandidates(puzzle.replace(/shine$/, "?")));
{
  valid: false,
  words: 24,
  wordCountValid: true,
  wordlistValid: true,
  checksumValid: false
}
bc1q94ecsn0qk8lap2gefrycnms3ruepy889z969a6
[
  'BIP39 checksum is invalid. Derived from the supplied words without repairing the checksum.'
]
[
  'aware',  'divide',
  'embark', 'globe',
  'pact',   'roof',
  'solve',  'today'
]

Without the flag deriveHDWallet throws. With it you get the wallet, a warning and your words exactly as given. The checksum would take eight last words, and shine isn't one of them. Each of the eight opens a different wallet. That's how a "fixed" mnemonic loses a puzzle, so the fixing stays with you.

Holding something else? Here's what to reach for. Every module below lives under @agntn/keys/.

You've got Library Agent tool More
Four letters off a steel plate lookupPrefixes in bip39 keys_bip39_words_lookup Wallets
yelow, copied off a photo repairWords in bip39 keys_bip39_words_repair Wallets
The right words, one per clue, no order orderWords in bip39 keys_bip39_words_order Wallets
The right words and still no coins keys_hd_wallet_scan Wallets
An Electrum seed deriveSeed in electrum keys_electrum_wallet_derive Wallets
A brainwallet phrase, salted or plain derive in brainwallet keys_brainwallet_derive Wallets
An xpub and one leaked child xprv recoverParent in bip32 keys_bip32_parent_recover Wallets
A 6P... key decrypt in bip38 keys_bip38_decrypt Keys
A UTC--... keystore file decrypt in store keys_store_decrypt Keys
An Arweave keyfile getAddress in arweave keys_arweave_address_get Keys
A signed message recoverMessageSigner on the chain keys_message_recover EVM chains
A bc1q or bc1p proof verify in bip322 keys_bip322_verify Bitcoin
A hint with a PGP signature under it verify in openpgp keys_openpgp_verify Keys
The same r on two signatures extractSignatures in transaction, recoverReusedNonce keys_transaction_signatures_extract, keys_secp256k1_nonce_recover Bitcoin
A multisig, a redeemScript, a descriptor script and descriptor keys_script_address_get, keys_descriptor_derive Bitcoin

The tools that open a secret answer agents with an address, a public key or a fingerprint. BIP38 and the parent recovery hand over the key itself only with revealKey: true. And one trap no table shows: any well formed signature recovers some key for any message. Check the address match, not just that a key came out.

⛓️ Chains

Chain Curve Address Formats Testnet
Bitcoin secp256k1 legacy, p2sh, segwit, p2wsh, taproot βœ…
Bitcoin Cash secp256k1 legacy P2PKH in CashAddr βœ…
Bitcoin Gold secp256k1 legacy, p2sh, segwit, p2wsh βœ…
Bitcoin SV secp256k1 legacy P2PKH βœ…
Litecoin secp256k1 legacy, p2sh, segwit, p2wsh, taproot βœ…
Dash secp256k1 legacy P2PKH βœ…
Decred secp256k1 legacy ECDSA P2PKH βœ…
Dogecoin secp256k1 legacy P2PKH βœ…
Zcash secp256k1 transparent P2PKH βœ…
eCash secp256k1 legacy P2PKH in CashAddr βœ…
Ethereum secp256k1 EIP-55 checksum -
Base secp256k1 EVM-compatible -
Solana ed25519 base58 -
Stellar ed25519 StrKey -
Aptos ed25519 0x-prefixed hex -
Cardano ed25519 payment, stake, enterprise βœ…
SUI ed25519, secp256k1 0x-prefixed hex (blake2b) -
TRON secp256k1 base58check βœ…
XRP Ledger secp256k1, ed25519 classic r address (base58) -
NEAR ed25519 implicit account (hex) -
Cosmos secp256k1 bech32 under any prefix -
Polkadot sr25519, ed25519 SS58 under any network prefix βœ…
Monero ed25519 standard, subaddress, integrated βœ…
TON ed25519 v3r2, v4r2, v5r1 wallets, UQ and EQ βœ…

Most of them need nothing past the loader. A few have opinions:

  • Cosmos takes a prefix, so blockchains.cosmos({ prefix: "osmo" })() writes Osmosis addresses.
  • Polkadot takes ss58Prefix, 2 for Kusama. It writes sr25519 like the wallets, until addressType: "ed25519" says otherwise.
  • Monero, TON and the XRP Ledger restore from their own seeds through deriveSeedWallet.
  • TON writes one address per wallet contract, so addressType picks v4r2, v5r1 or v3r2.
  • Decred and Cardano refuse deriveHDWallet on purpose.

Testnet is a constructor option, blockchains.bitcoin({ network: "testnet" })(), and segwit starts with tb1q. Per chain quirks: Blockchains.

πŸ’» Terminal

npx @agntn/keys wallet-derive --chain bitcoin --private-key 0000000000000000000000000000000000000000000000000000000000000001
Address type: legacy
Public key: 0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798
Address: 1BgGZ9tcN4rm9KBzDn7KprQz87SZ26SAMH

Yes, that's private key 1, the most famous throwaway in Bitcoin. Every agent tool is a command too: keys_wallet_derive is keys wallet-derive. The flags come from the tool schema through runCli of @agntn/tools, so --help can't drift from what MCP lists. Add --json for the details. Want the bare keys? pnpm add -g @agntn/keys. A real key typed here lands in your shell history.

πŸ€– Agents

npx -y @agntn/keys mcp
claude mcp add --transport http keys https://keys.agntn.dev/mcp # nothing to install
pi install npm:@agntn/keys
omp install @agntn/keys
{
  "mcpServers": {
    "keys": { "command": "npx", "args": ["-y", "@agntn/keys", "mcp"] }
  }
}

Every tool runs on every one of these. The Pi and OMP extensions in extensions call the same executors as the server. Ask for a mnemonic and this is the whole answer:

Language: english
Mnemonic: donate crumble bridge february pottery photo swear cactus tobacco timber climb elite
Words: 12
This mnemonic is saved in the transcript. Never use it for real funds.

That last line is not decoration. Keys, seeds and signatures cross the model's context as plain text and stay in the transcript. The HTTP server adds a Cloudflare worker on the way. Public puzzle material and throwaway keys only.

🚫 What this does not do

Balances, transactions, broadcasting, anything that needs a node. @agntn/explorers reads chains and @agntn/chains describes them, this one only makes keys. It doesn't keep them either. Where your keystore file lives is your call. Curve math moved out too: adding points or lifting an x lives in @agntn/curves, and @agntn/keys/secp256k1 still hands out those functions. Keys keeps the keys.

πŸ” Security

Curves and HD derivation come from @paulmillr: @noble/curves, @scure/bip32 and @scure/bip39, micro-key-producer for SLIP-10. Hashes come from our own @agntn/hashes, plain TypeScript with no node:crypto underneath. Base encodings come from our own @agntn/encodings, CashAddr and StrKey included. Random bytes come from globalThis.crypto through noble's randomSecretKey. No node: import in the library, so a browser bundle needs no shim. None of that makes this package audited.

Caution

Never use this with real funds or with any wallet that has ever been used. Generated and signed material is handled as plaintext. Treat every key it touches as burned the moment it is produced. Generate fresh throwaway keys for testing only and assume anything passing through @agntn/keys is compromised. Keys that control real funds belong on a hardware wallet, never in a process, log, or agent transcript.

βž• Adding a chain

Want a twenty-fifth? Extend AbstractBlockchain, or AbstractEVMBlockchain if it's EVM, where a name and a bip44 coin type is the whole class. Register it in the lazy loader and mirror a test file. The docs site lists chains by hand, so the checklist covers that too. Walkthrough: Creating custom blockchains.

πŸ› οΈ Development

pnpm install
pnpm --dir docs install   # the /mcp test borrows Zod and the toolkit from here
pnpm dev          # vp test in watch mode
pnpm lint         # builds first, then vp lint and vp fmt --check
pnpm test:types   # tsc over the library and the type tests, vue-tsc over the docs app
pnpm build        # obuild
pnpm test:mcp     # builds, then calls every tool over stdio
pnpm playground playground/bip39-demo.ts

πŸ’› Thanks

Building this package was possible thanks to the open source programs from Anthropic and OpenAI, Claude for Open Source and Codex for Open Source <3

πŸ“„ License

MIT

About

A TypeScript library for interacting with various blockchains.

Topics

Resources

Contributing

Stars

5 stars

Watchers

0 watching

Forks

Releases

Used by

Contributors

Languages