Guide

Generating Wallets

One call for keys plus address, importing a key you already have, and walking a mnemonic down a path.

What a wallet is here

Keys plus the address they map to. Nothing else, no storage, no balance, no transactions.

interface Wallet extends Keys {
  address: string;
}

New wallet

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

const chain = useBlockchain(new Bitcoin());
const wallet = chain.generateWallet();

wallet.keys.private;
wallet.keys.public;
wallet.address;

Two optional arguments: key options first, address type second. The key options go to generateKeys, the address type to getAddress.

chain.generateWallet({ compressed: false }); // uncompressed public key, legacy address
chain.generateWallet({}, "p2sh"); // starts with 3
chain.generateWallet({}, "taproot"); // starts with bc1p

That empty object in the middle is annoying but honest: the address type is the second parameter and there is no overload that lets you skip the first.

A key you already have

const privateKey = "7f9e5b9e3bbed34a4c28c8c1665525fc2cd7afb4fdc7edca3eb93ddf8a31ef56";
const wallet = chain.deriveWallet(privateKey);
const segwitWallet = chain.deriveWallet(privateKey, {}, "segwit");

Same arguments as generateWallet, minus the randomness.

From a mnemonic

deriveHDWallet walks a BIP39 mnemonic down a derivation path and returns the wallet at the end:

const mnemonic =
  "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about";

chain.deriveHDWallet(mnemonic, "m/84'/0'/0'/0/0").address; // bc1q...
chain.deriveHDWallet(mnemonic, "m/44'/0'/0'/0/0", { passphrase: "TREZOR" }, "p2sh");

Things that will bite you:

  • The mnemonic has to pass the English BIP39 checksum. A typo in word eleven fails before any derivation happens.
  • secp256k1 chains derive with BIP32, ed25519 chains with SLIP-10. SLIP-10 only has hardened children, so Solana, Aptos, and Sui on ed25519 need every segment hardened: m/44'/501'/0'/0'.
  • Bitcoin reads the purpose when you do not pass an address type. m/44' gives legacy, m/49' p2sh, m/84' segwit, m/86' taproot. An explicit type wins.
  • Cardano throws. CIP-1852 starts from the entropy, not from the BIP39 seed, and pretending otherwise would hand you addresses that no Cardano wallet recognises.

The mnemonic above is the public BIP39 test vector. It is fine for docs and tests and for nothing else.

Security, again

The library does not store keys, encrypt keys, or forget keys. Whatever calls it holds the private key in memory as a plain string, and so does every log line that prints a wallet object. Generate throwaway keys for tests, keep real ones on hardware, and be twice as careful in a browser tab where an extension can read the page.

The whole method

This is all generateWallet is:

generateWallet(options?: KeyOptions, addressType?: AddressType): Wallet {
  const keys = this.generateKeys(options);
  const address = this.getAddress(keys.keys.public, addressType);

  return { ...keys, address };
}

It lives on AbstractBlockchain, so every chain, including one you write yourself, gets it for free.

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