Generate a mnemonic + derive keys
Generate a BIP39 mnemonic and derive secret keys from it at different address indices, fully offline. There is no network call anywhere in this recipe: mnemonic generation and key derivation are pure local cryptography. No devnet wallet, no funds, no gas.
One mnemonic is the root of many accounts: deriveKey(0), deriveKey(1), and so
on each produce a distinct key and address. Because derivation is deterministic,
the same phrase always regenerates the same keys, which is exactly what makes a
mnemonic a portable backup.
Prerequisites
- Node.js >= 20.19.0.
- Nothing else. This recipe generates its own throwaway mnemonic and never touches the network.
Install
mkdir generate-mnemonic-derive-keys
cd generate-mnemonic-derive-keys
# Create the project files shown on this page.
npm install
npm run build
npm start
Complete project files omitted from the main walkthrough
{
"name": "cookbook-recipe-generate-mnemonic-derive-keys",
"version": "1.0.0",
"private": true,
"description": "Cookbook recipe — generate a BIP39 mnemonic and derive secret keys at different address indices. Fully offline: no devnet, no network call.",
"scripts": {
"build": "tsc",
"start": "node dist/index.js",
"typecheck": "tsc --noEmit --strict"
},
"dependencies": {
"@multiversx/sdk-core": "15.4.1",
"bignumber.js": "9.3.1",
"protobufjs": "7.6.5"
},
"devDependencies": {
"@types/node": "20.19.43",
"typescript": "5.9.3"
},
"engines": {
"node": ">=20.19.0"
}
}
{
"compilerOptions": {
"target": "ES2022",
"lib": ["esnext"],
"module": "commonjs",
"moduleResolution": "node",
"outDir": "dist",
"rootDir": "src",
"skipLibCheck": true,
"strict": true,
"noImplicitAny": true,
"strictNullChecks": true,
"noUncheckedIndexedAccess": true,
"exactOptionalPropertyTypes": true,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"forceConsistentCasingInFileNames": true,
"esModuleInterop": true,
"resolveJsonModule": true,
"types": ["node"]
},
"include": ["src/**/*.ts"],
"exclude": ["node_modules", "dist"]
}
The code
// src/index.ts — generate a mnemonic and derive secret keys from it at
// several address indices, then show the derivation is deterministic.
//
// Fully offline. No devnet, no network call of any kind — mnemonic
// generation and key derivation are pure local cryptography (BIP39
// entropy -> words, then a deterministic derivation to Ed25519 keys). A
// fresh mnemonic is generated on every run, so exact words/addresses differ
// run to run; the shape of the output does not.
//
// Modeled on the mx-sdk-js-core cookbook source (cookbook/basics.ts,
// "Generating a mnemonic" / "Deriving secret keys from a mnemonic").
import { Mnemonic } from '@multiversx/sdk-core';
function main(): void {
// === 1. Generate a fresh 24-word mnemonic. ===
// This is the ROOT SECRET. Anyone holding these words controls every
// account derived from them. Real code must never log a mnemonic; this
// recipe prints only the count and the first/last word because the phrase
// is a throwaway that is never funded.
const mnemonic = Mnemonic.generate();
const words = mnemonic.getWords();
console.log(`Generated a ${words.length}-word mnemonic.`);
console.log(
`First word: "${words[0] ?? ''}", last word: "${words[words.length - 1] ?? ''}".`,
);
// === 2. One mnemonic derives many accounts, selected by addressIndex. ===
// deriveKey(i) walks the MultiversX derivation path at account index i and
// returns a UserSecretKey; its public key gives the on-chain Address.
console.log('\nAddresses derived from this one mnemonic:');
for (const addressIndex of [0, 1, 2]) {
const secretKey = mnemonic.deriveKey(addressIndex);
const address = secretKey.generatePublicKey().toAddress();
console.log(` addressIndex ${addressIndex} -> ${address.toBech32()}`);
}
// === 3. Derivation is deterministic. ===
// Re-importing the same phrase with Mnemonic.fromString and deriving at the
// same index yields the same key/address every time — this is what makes a
// mnemonic a portable backup.
const restored = Mnemonic.fromString(mnemonic.toString());
const original0 = mnemonic.deriveKey(0).generatePublicKey().toAddress().toBech32();
const restored0 = restored.deriveKey(0).generatePublicKey().toAddress().toBech32();
console.log(`\nRe-derived from the same phrase (addressIndex 0) matches: ${original0 === restored0}`);
// === 4. deriveKey() defaults to addressIndex 0. ===
const defaultAddress = mnemonic.deriveKey().generatePublicKey().toAddress().toBech32();
console.log(`deriveKey() with no argument == deriveKey(0): ${defaultAddress === original0}`);
console.log('\nExpected: three distinct addresses, then two "true" lines.');
}
main();
Run it
A fresh mnemonic is generated every run, so the exact words and addresses differ;
the shape does not. Three distinct addresses, then two true lines:
Generated a 24-word mnemonic.
First word: "lesson", last word: "grief".
Addresses derived from this one mnemonic:
addressIndex 0 -> erd1f03w2cg7lxx8k8e9dafw3rxe092qhzlceq3xv6rqsj4kn5rsq6fq086964
addressIndex 1 -> erd1r64f3gmpq23zf634x80evgfcpa5n8vt9jtmpqgtd5wuwwkez94usxeke2q
addressIndex 2 -> erd1ulvfmlh43ejl42jngekfu2pn2gugrewrxxgwgzmrf4h3flywqgeqkkl78g
Re-derived from the same phrase (addressIndex 0) matches: true
deriveKey() with no argument == deriveKey(0): true
How it works
Grounded in the installed @multiversx/sdk-core v15.4.1 Mnemonic class:
- Generate.
Mnemonic.generate()returns a fresh 24-word phrase;getWords()lists the words. - Derive at indices.
mnemonic.deriveKey(addressIndex)returns aUserSecretKey; itsgeneratePublicKey().toAddress()gives the on-chainAddress. One mnemonic backs many accounts, selected byaddressIndex. - Determinism.
Mnemonic.fromString(phrase)re-imports the phrase and derives the identical key/address. - Default index.
deriveKey()with no argument isderiveKey(0).
Pitfalls
Anyone holding these 24 words controls every account derived from them. This recipe prints only the word count and first/last word because the phrase is a throwaway that is never funded. In production, treat the phrase like a private key: never log it, never send it over the wire.
deriveKey(0), deriveKey(1), deriveKey(2) are three different accounts from
the same mnemonic. The optional second argument, deriveKey(addressIndex, password), is a BIP39 passphrase (a "25th word"), a separate concept. Leave it
unset unless you deliberately use one.
Deriving a key is free and offline; the account does not exist on-chain until it receives EGLD. Fetching its nonce or balance needs a network provider, see Fetch an account's on-chain state.
See also
- Create an Account from a KeyPair, secret key, or mnemonic
turns a derived key into an
Account. - Save and load a keystore encrypts a mnemonic (or a single key) at rest.
- Save and load a PEM (dev only) is the unencrypted dev-wallet format.