Skip to main content

Documentation Index

Fetch the complete documentation index at: https://voltaire.tevm.sh/llms.txt

Use this file to discover all available pages before exploring further.

Try it Live

Run HDWallet examples in the interactive playground

Overview

Derivation paths define hierarchical routes from master seed to specific keys. BIP-32 defines the structure, BIP-44 standardizes multi-account usage, and SLIP-44 assigns coin types.
Examples:

BIP-32 Path Format

Standard Notation

m / purpose' / coin_type' / account' / change / address_index
Components:
  • m: Master key (root)
  • purpose': Use case (44’ = BIP-44, 49’ = SegWit, 84’ = Native SegWit)
  • coin_type': Cryptocurrency (0’ = Bitcoin, 60’ = Ethereum)
  • account': Account number (0’, 1’, 2’, …)
  • change: External (0) or internal/change (1)
  • address_index: Address within account (0, 1, 2, …)
Hardened Notation:
  • ' (apostrophe): Hardened derivation
  • h: Alternative hardened notation (m/44h/60h/0h)

Path Examples

import { HDWallet } from '@tevm/voltaire/native';

// Ethereum standard (BIP-44)
const eth = "m/44'/60'/0'/0/0";
//  │   │   │   │   │  └─ First address
//  │   │   │   │   └──── External chain (receiving)
//  │   │   │   └──────── First account
//  │   │   └──────────── Ethereum
//  │   └──────────────── BIP-44
//  └──────────────────── Master

// Bitcoin standard
const btc = "m/44'/0'/0'/0/0";
//              │   │
//              │   └──── First account
//              └──────── Bitcoin

// Ethereum second account
const eth2 = "m/44'/60'/1'/0/0";
//                    │
//                    └──── Second account

BIP-44 Standard

Hierarchy Levels

Level 1 - Purpose Fixed at 44’ for BIP-44:
// Always use 44' for BIP-44
const purpose = 44 | HDWallet.HARDENED_OFFSET; // 0x80000000 + 44
Level 2 - Coin Type (SLIP-44)
// Common coin types
const coinTypes = {
  Bitcoin: 0,
  Testnet: 1,
  Ethereum: 60,
  EthereumClassic: 61,
  Litecoin: 2,
  Dogecoin: 3,
};

// Ethereum path
const ethPath = `m/44'/${coinTypes.Ethereum}'/0'/0/0`;
Level 3 - Account
// Multiple accounts for organization
const account0 = "m/44'/60'/0'/0/0"; // Personal
const account1 = "m/44'/60'/1'/0/0"; // Business
const account2 = "m/44'/60'/2'/0/0"; // Trading
Level 4 - Change
// External (receiving addresses)
const external = "m/44'/60'/0'/0/0";
//                           │
//                           └─ 0 = External

// Internal (change addresses, less common in Ethereum)
const internal = "m/44'/60'/0'/1/0";
//                           │
//                           └─ 1 = Internal
Level 5 - Address Index
// Sequential addresses
const addresses = [
  "m/44'/60'/0'/0/0",
  "m/44'/60'/0'/0/1",
  "m/44'/60'/0'/0/2",
  "m/44'/60'/0'/0/3",
  "m/44'/60'/0'/0/4",
];

Ethereum Derivation

import * as Bip39 from '@tevm/voltaire/Bip39';
import { HDWallet } from '@tevm/voltaire/native';

const mnemonic = Bip39.generateMnemonic(256);
const seed = await Bip39.mnemonicToSeed(mnemonic);
const root = HDWallet.fromSeed(seed);

// Derive using full path
const eth0 = HDWallet.derivePath(root, "m/44'/60'/0'/0/0");

// Or use convenience method
const eth0Alt = HDWallet.deriveEthereum(root, 0, 0);
// Equivalent to m/44'/60'/0'/0/0

// Multiple addresses
const eth1 = HDWallet.deriveEthereum(root, 0, 1); // m/44'/60'/0'/0/1
const eth2 = HDWallet.deriveEthereum(root, 0, 2); // m/44'/60'/0'/0/2

Bitcoin Derivation

// Bitcoin uses change addresses more commonly
const btc0 = HDWallet.derivePath(root, "m/44'/0'/0'/0/0"); // Receiving
const btcChange = HDWallet.derivePath(root, "m/44'/0'/0'/1/0"); // Change

// Or use convenience method
const btc0Alt = HDWallet.deriveBitcoin(root, 0, 0);

Hardened vs Normal Derivation

Hardened Derivation

Index ≥ 2^31 (0x80000000):
// Hardened indices
const hardened = [
  HDWallet.HARDENED_OFFSET + 0,  // 2147483648
  HDWallet.HARDENED_OFFSET + 1,  // 2147483649
  HDWallet.HARDENED_OFFSET + 44, // 2147483692 (for BIP-44)
];

// Notation in path
const hardenedPath = "m/44'/60'/0'"; // 44', 60', 0' all hardened

Normal Derivation

Index < 2^31:
// Normal indices
const normal = [0, 1, 2, 3, 4, /* ... */, 2147483647];

// Notation in path
const normalPath = "m/44'/60'/0'/0/0"; // Last two (0, 0) are normal

Security Implications

Hardened Derivation:
  • Requires private key
  • More secure (leaked child key doesn’t compromise parent)
  • Used for: purpose, coin_type, account
Normal Derivation:
  • Can derive from public key only
  • Allows watch-only wallets
  • Used for: change, address_index
// xpub can derive normal children only
const xpub = root.toExtendedPublicKey();
const pubOnly = HDWallet.fromPublicExtendedKey(xpub);

// ✅ Can derive normal children
const child0 = HDWallet.deriveChild(pubOnly, 0); // Works

// ❌ Cannot derive hardened children
try {
  HDWallet.deriveChild(pubOnly, HDWallet.HARDENED_OFFSET);
} catch (error) {
  console.error('Cannot derive hardened from public key');
}

Common Derivation Paths

Ethereum Wallets

MetaMask / Standard:
m/44'/60'/0'/0/0   First account
m/44'/60'/0'/0/1   Second address
m/44'/60'/0'/0/2   Third address
Ledger Live:
m/44'/60'/0'/0/0   Default
m/44'/60'/1'/0/0   Second account
m/44'/60'/2'/0/0   Third account
MyEtherWallet (Legacy):
m/44'/60'/0'/0     No final index (deprecated)

Multi-Coin Wallets

const paths = {
  // Ethereum
  ETH: "m/44'/60'/0'/0/0",

  // Bitcoin
  BTC: "m/44'/0'/0'/0/0",

  // Litecoin
  LTC: "m/44'/2'/0'/0/0",

  // Dogecoin
  DOGE: "m/44'/3'/0'/0/0",

  // Ethereum Classic
  ETC: "m/44'/61'/0'/0/0",
};

// Derive all from same seed
for (const [coin, path] of Object.entries(paths)) {
  const key = HDWallet.derivePath(root, path);
  console.log(`${coin}:`, key.getPublicKey());
}

Path Parsing and Validation

Validate Path Format

import { HDWallet } from '@tevm/voltaire/native';

// Valid paths
const valid = [
  "m/44'/60'/0'/0/0",
  "m/0",
  "m/0'/1'/2'",
  "m/44h/60h/0h/0/0", // h notation
];

valid.forEach(path => {
  console.log(path, HDWallet.isValidPath(path)); // All true
});

// Invalid paths
const invalid = [
  "44'/60'/0'/0/0",    // Missing 'm'
  "m//44'/60'/0'",     // Empty level
  "m/44'/60'/0'/0/",   // Trailing slash
  "m/invalid",         // Non-numeric
];

invalid.forEach(path => {
  console.log(path, HDWallet.isValidPath(path)); // All false
});

Parse Index

// Parse index strings
console.log(HDWallet.parseIndex("0"));   // 0
console.log(HDWallet.parseIndex("44"));  // 44
console.log(HDWallet.parseIndex("0'"));  // 2147483648 (HARDENED_OFFSET)
console.log(HDWallet.parseIndex("60h")); // 2147483708 (HARDENED_OFFSET + 60)

Check Hardened Path

const paths = [
  "m/44'/60'/0'/0/0", // Has hardened
  "m/0/1/2",          // No hardened
  "m/0'/1/2",         // Mixed
];

paths.forEach(path => {
  console.log(path, HDWallet.isHardenedPath(path));
});
// true, false, true

Advanced Path Patterns

Gap Limit (BIP-44)

Scan for used addresses with gap limit:
async function scanAddresses(root: ExtendedKey, gapLimit = 20): Promise<string[]> {
  const usedAddresses = [];
  let consecutiveUnused = 0;

  for (let i = 0; ; i++) {
    const key = HDWallet.deriveEthereum(root, 0, i);
    const address = deriveAddress(key);

    const hasTransactions = await checkAddressUsed(address);

    if (hasTransactions) {
      usedAddresses.push(address);
      consecutiveUnused = 0;
    } else {
      consecutiveUnused++;

      if (consecutiveUnused >= gapLimit) {
        break; // Stop scanning
      }
    }
  }

  return usedAddresses;
}

Custom Derivation

// Custom path for specific use case
const customPaths = {
  // Hot wallet
  hot: "m/44'/60'/0'/0/0",

  // Cold storage
  cold: "m/44'/60'/1'/0/0",

  // DeFi interactions
  defi: "m/44'/60'/2'/0/0",

  // NFT trading
  nft: "m/44'/60'/3'/0/0",
};

for (const [purpose, path] of Object.entries(customPaths)) {
  const key = HDWallet.derivePath(root, path);
  console.log(`${purpose}:`, deriveAddress(key));
}

Deterministic Per-Service Accounts

// Derive unique account per service
function deriveServiceAccount(root: ExtendedKey, serviceName: string): ExtendedKey {
  // Hash service name to deterministic account index
  const hash = sha256(serviceName);
  const accountIndex = new DataView(hash.buffer).getUint32(0, false);

  // Use as account index (ensure < 2^31 for non-hardened)
  const index = accountIndex % (HDWallet.HARDENED_OFFSET);

  return HDWallet.deriveEthereum(root, index, 0);
}

const uniswapAccount = deriveServiceAccount(root, 'uniswap');
const aaveAccount = deriveServiceAccount(root, 'aave');

Path Constants

// Pre-defined path templates
const BIP44_PATHS = {
  ETH: (account = 0, index = 0) => `m/44'/60'/${account}'/0/${index}`,
  BTC: (account = 0, index = 0) => `m/44'/0'/${account}'/0/${index}`,
  LTC: (account = 0, index = 0) => `m/44'/2'/${account}'/0/${index}`,
};

// Usage
const eth0 = HDWallet.derivePath(root, BIP44_PATHS.ETH(0, 0));
const btc5 = HDWallet.derivePath(root, BIP44_PATHS.BTC(0, 5));

Best Practices

1. Use Standard Paths
// ✅ Standard BIP-44
const standard = "m/44'/60'/0'/0/0";

// ❌ Non-standard (reduces compatibility)
const nonStandard = "m/0/0/0/0/0";
2. Harden Sensitive Levels
// ✅ Hardened: purpose, coin_type, account
const secure = "m/44'/60'/0'/0/0";
//                ^^^ ^^^ ^^^ - Hardened

// ❌ Non-hardened sensitive levels
const insecure = "m/44/60/0/0/0";
3. Document Custom Paths
interface WalletConfig {
  derivationPath: string;
  purpose: string;
  notes?: string;
}

const config: WalletConfig = {
  derivationPath: "m/44'/60'/0'/0/0",
  purpose: 'Standard Ethereum wallet',
  notes: 'Compatible with MetaMask'
};
4. Validate Before Derivation
function safeDervePath(root: ExtendedKey, path: string): ExtendedKey {
  if (!HDWallet.isValidPath(path)) {
    throw new Error(`Invalid derivation path: ${path}`);
  }

  return HDWallet.derivePath(root, path);
}

References