viem-goviem-go

HD Wallet Account

Create accounts from Hierarchical Deterministic (HD) keys and custom derivation paths

A Hierarchical Deterministic (HD) Account is derived from an HD Key and an optional HD path. It can sign transactions and messages with the private key derived at that path.

viem-go uses go-bip32 for BIP-32 HD derivation. The HDKey interface exposes Derive(path), PrivateKey(), and PublicKey().

Import

import "github.com/ChefBingbong/viem-go/accounts"

In viem (TypeScript), HDKey is re-exported from @scure/bip32. In viem-go, you obtain an HDKey from accounts.SeedToHDKey(seed) or from a key that implements the accounts.HDKey interface.

Usage

For most use cases, derive an account directly from a mnemonic with MnemonicToAccount and optional path. That path is applied internally via HD derivation.

import "github.com/ChefBingbong/viem-go/accounts"
mnemonic := "test test test test test test test test test test test junk"
// Default path m/44'/60'/0'/0/0
account, err := accounts.MnemonicToAccount(mnemonic)
// Custom path
account, err := accounts.MnemonicToAccount(mnemonic, &accounts.MnemonicToAccountOptions{
HDOptions: accounts.HDOptions{Path: "m/44'/60'/0'/0/5"},
})

From HD key (seed or custom source)

When you already have a seed (or another HD key source), create a master HDKey with SeedToHDKey, derive at the desired path, then call HDKeyToAccount:

import "github.com/ChefBingbong/viem-go/accounts"
seed := accounts.MnemonicToSeed(mnemonic, "")
masterKey, err := accounts.SeedToHDKey(seed)
if err != nil {
log.Fatal(err)
}
derivedKey, err := masterKey.Derive("m/44'/60'/0'/0/0")
if err != nil {
log.Fatal(err)
}
account, err := accounts.HDKeyToAccount(derivedKey)
if err != nil {
log.Fatal(err)
}
fmt.Printf("Address: %s
", account.GetAddress())

In viem (TypeScript), HDKey has static methods like fromMasterSeed, fromExtendedKey, fromJSON. In viem-go, the main entry point from raw seed is SeedToHDKey(seed); the resulting key’s Derive(path) parses BIP-32 path strings (e.g. m/44'/60'/0'/0/0).

Parameters

hdKey

  • Type: accounts.HDKey (Go) / HDKey (viem)

Interface in Go:

  • Derive(path string) (HDKey, error)
  • PrivateKey() []byte
  • PublicKey() []byte

The key must be derived at the desired path (or pass path via options; see below). viem-go does not expose a separate “mnemonic to HD key” helper; use MnemonicToSeed + SeedToHDKey or MnemonicToAccount with path.

opts (optional)

  • Type: HDKeyToAccountOptions (Go), which embeds HDOptions.

options.accountIndex

  • Type: int (Go) / number (viem)
  • Default: 0

Account index in path m/44'/60'/{accountIndex}'/0/0.

options.addressIndex

  • Type: int (Go) / number (viem)
  • Default: 0

Address index in path m/44'/60'/0'/0/{addressIndex}.

options.changeIndex

  • Type: int (Go) / number (viem)
  • Default: 0

Change index in path m/44'/60'/0'/{changeIndex}/0.

options.path

  • Type: string
  • Format: e.g. m/44'/60'/5'/0/2

When set, this path is used for derivation instead of building from indices. If the hdKey is already derived to the correct node, you can pass empty options or a path that matches.

// Default path m/44'/60'/0'/0/0
account, err := accounts.HDKeyToAccount(hdKey)
// Custom path
account, err := accounts.HDKeyToAccount(hdKey, accounts.HDKeyToAccountOptions{
HDOptions: accounts.HDOptions{Path: "m/44'/60'/5'/0/2"},
})
// Or use indices
account, err := accounts.HDKeyToAccount(hdKey, accounts.HDKeyToAccountOptions{
HDOptions: accounts.HDOptions{
AccountIndex: 1,
AddressIndex: 6,
ChangeIndex: 0,
},
})

HD path reference

PathDescription
m/44'/60'/0'/0/0Standard first Ethereum address
m/44'/60'/0'/0/nnth address (same account)
m/44'/60'/0'/1/nInternal/change addresses
m/44'/60'/n'/0/0Different “accounts” (hardened)

Path parsing in viem-go supports m/ prefix, ' or h for hardened, and numeric segments. Use accounts.DefaultHDPath(accountIndex, changeIndex, addressIndex) to build the default path string.

Accessing the HD key (Go)

HDKeyToAccount returns an *HDAccount, which embeds a local account and exposes the underlying key:

hdAccount, _ := accounts.HDKeyToAccount(derivedKey)

address := hdAccount.GetAddress()
hdKey := hdAccount.GetHdKey()

// Derive another path from the same parent if you kept a reference
child, _ := parentKey.Derive("m/44'/60'/0'/0/1")

Signing

HD accounts support the same signing methods as Private Key Account: Sign, SignMessage, SignTransaction, SignTypedData, SignAuthorization.

sig, err := account.SignMessage(signature.NewSignableMessage("Hello"))
signedTx, err := account.SignTransaction(tx)
sig, err := account.SignTypedData(typedData)
signedAuth, err := account.SignAuthorization(authRequest)

Batch account generation

Generate multiple accounts from one mnemonic by iterating the path (e.g. address index):

func deriveAccounts(mnemonic string, count int) ([]*accounts.HDAccount, error) {
out := make([]*accounts.HDAccount, count)
for i := 0; i < count; i++ {
acc, err := accounts.MnemonicToAccount(mnemonic, &accounts.MnemonicToAccountOptions{
HDOptions: accounts.HDOptions{AddressIndex: i},
})
if err != nil {
return nil, err
}
out[i] = acc
}
return out, nil
}
accounts, _ := deriveAccounts(mnemonic, 10)
for i, acc := range accounts {
fmt.Printf("Account %d: %s
", i, acc.GetAddress())
}

Return type

  • Go: HDKeyToAccount(hdKey HDKey, opts ...HDKeyToAccountOptions) (*HDAccount, error)
  • Must variant: MustHDKeyToAccount(hdKey HDKey, opts ...HDKeyToAccountOptions) *HDAccount

Error handling

  • ErrInvalidHDPath — path string is invalid or derivation failed (e.g. invalid segment).

Account source

The account’s source is set to AccountSourceHD when created via HDKeyToAccount, and AccountSourceMnemonic when created via MnemonicToAccount:

fmt.Println(account.GetSource()) // "hd" or "mnemonic"
fmt.Println(account.GetType())  // "local"