Generating Wallets
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;
warnings?: readonly string[];
}
warnings only shows up when you asked for a derivation a stricter library would refuse. More on that below.
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's 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. Got a WIF instead of hex? decodeWIF on the keys page turns it into the hex this call wants, compression flag included.
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 be English BIP39 words, 12, 15, 18, 21 or 24 of them, and the checksum has to hold. A typo in word eleven fails before any derivation happens. The checksum part is negotiable, see the next section.
- secp256k1 chains derive with BIP32, ed25519 chains with SLIP-10. SLIP-10 only has hardened children, so Solana, Stellar, Aptos, and Sui on ed25519 need every segment hardened:
m/44'/501'/0'/0'.chain.getDerivationPath(account)writes the path that chain's wallets use, hardened where it has to be and no deeper than the chain goes, so you don't have to remember which one stops where. - Bitcoin and Litecoin read the purpose when you don't 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.
- Decred throws too. Its HD derivation strips leading zero bytes where BIP32 keeps them, so a plain BIP32 walk lands on the wrong key for some paths and looks fine doing it.
deriveWalletwith a private key works on both.
The mnemonic above is the public BIP39 test vector. It's fine for docs and tests and for nothing else.
Puzzle mnemonics
A puzzle phrase with a broken checksum isn't a wrong answer, it's Tuesday. Without a flag deriveHDWallet throws on it. With the flag you get the wallet and a warning, the words exactly as given, nothing repaired:
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";
const wallet = chain.deriveHDWallet(puzzle, "m/84'/0'/0'/0/0", { allowInvalidChecksum: true });
wallet.address; // bc1q94ecsn0qk8lap2gefrycnms3ruepy889z969a6
wallet.warnings;
// [ 'BIP39 checksum is invalid. Derived from the supplied words without repairing the checksum.' ]
The flag only relaxes the checksum. Word count and dictionary are still enforced, whitespace still collapses and NFKD still applies, so it isn't a back door for arbitrary text. Valid phrases give the same result with or without it.
To see which check failed, ask before deriving:
import { inspectBIP39Mnemonic, getMnemonicWordCandidates } from "@agntn/keys/bip39";
inspectBIP39Mnemonic(puzzle);
// { valid: false, words: 24, wordCountValid: true, wordlistValid: true, checksumValid: false }
getMnemonicWordCandidates(puzzle.replace(/shine$/, "?"));
// [ 'aware', 'divide', 'embark', 'globe', 'pact', 'roof', 'solve', 'today' ]
inspectBIP39Mnemonic splits the verdict three ways and never echoes the phrase. checksumValid is null when the count or the dictionary already failed, because there's nothing to check yet. getMnemonicWordCandidates takes a phrase with exactly one ? and returns every English word that makes the checksum pass, in list order. shine isn't one of the eight, and each of the eight opens a different wallet, so the library won't pick one for you. That's how a "fixed" mnemonic loses a puzzle.
Both helpers and the flag are English only. The other nine word lists are there for lookups and generation, lookupBIP39Words and loadBIP39Wordlist on the same import. The MCP and Pi tools expose the same flag and the same diagnostics.
Security, again
The library doesn't 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.