Files

102 lines
2.7 KiB
Go

package main
import (
"crypto/ecdsa"
"fmt"
"math/big"
"github.com/luxfi/geth/common"
gethcrypto "github.com/luxfi/geth/crypto"
bip32 "github.com/luxfi/go-bip32"
bip39 "github.com/luxfi/go-bip39"
)
// Wallet holds a derived private key and its Ethereum address.
type Wallet struct {
Index int
PrivateKey *ecdsa.PrivateKey
Address common.Address
}
// DeriveWallets generates N wallets from a BIP-39 mnemonic using the
// ETH BIP-44 derivation path m/44'/60'/0'/0/{index}.
// Indices range from startIndex to startIndex+count-1.
func DeriveWallets(mnemonic string, startIndex, count int) ([]*Wallet, error) {
if !bip39.IsMnemonicValid(mnemonic) {
return nil, fmt.Errorf("invalid mnemonic")
}
// Convert mnemonic to seed (no passphrase)
seed := bip39.NewSeed(mnemonic, "")
// Derive master key
masterKey, err := bip32.NewMasterKey(seed)
if err != nil {
return nil, fmt.Errorf("master key derivation failed: %w", err)
}
// BIP-44 path: m/44'/60'/0'/0/{index}
// Hardened indices use offset 0x80000000
const hardenedOffset = 0x80000000
purpose, err := masterKey.NewChildKey(44 + hardenedOffset) // 44'
if err != nil {
return nil, fmt.Errorf("purpose derivation failed: %w", err)
}
coinType, err := purpose.NewChildKey(60 + hardenedOffset) // 60'
if err != nil {
return nil, fmt.Errorf("coin type derivation failed: %w", err)
}
account, err := coinType.NewChildKey(0 + hardenedOffset) // 0'
if err != nil {
return nil, fmt.Errorf("account derivation failed: %w", err)
}
change, err := account.NewChildKey(0) // 0 (external chain)
if err != nil {
return nil, fmt.Errorf("change derivation failed: %w", err)
}
wallets := make([]*Wallet, 0, count)
for i := startIndex; i < startIndex+count; i++ {
child, err := change.NewChildKey(uint32(i))
if err != nil {
return nil, fmt.Errorf("child key derivation failed for index %d: %w", i, err)
}
// The child key bytes are the raw 32-byte private key
privKey, err := gethcrypto.ToECDSA(child.Key)
if err != nil {
return nil, fmt.Errorf("ECDSA conversion failed for index %d: %w", i, err)
}
addr := gethcrypto.PubkeyToAddress(privKey.PublicKey)
wallets = append(wallets, &Wallet{
Index: i,
PrivateKey: privKey,
Address: addr,
})
}
return wallets, nil
}
// ShortAddr returns a shortened address string for logging.
func (w *Wallet) ShortAddr() string {
s := w.Address.Hex()
return s[:6] + ".." + s[len(s)-4:]
}
// FundingRequired checks if a wallet needs funding on a given chain.
// Returns the deficit amount in wei (0 if no funding needed).
func FundingRequired(balance *big.Int, minBalance *big.Int) *big.Int {
if balance.Cmp(minBalance) >= 0 {
return big.NewInt(0)
}
deficit := new(big.Int).Sub(minBalance, balance)
return deficit
}