import { PrivateKey, PublicKey, Transaction } from '@bsv/sdk'
/**
* Multi-Account Wallet Manager
*
* Manages multiple accounts with address derivation and UTXO tracking
*/
class WalletManager {
private accounts: Map<string, Account> = new Map()
private hdWallet: HDWallet
constructor(mnemonic?: string) {
this.hdWallet = new HDWallet(mnemonic)
}
/**
* Create new account
*/
createAccount(name: string, accountIndex: number = 0): Account {
if (this.accounts.has(name)) {
throw new Error(`Account ${name} already exists`)
}
const account: Account = {
name,
accountIndex,
addresses: [],
nextReceiveIndex: 0,
nextChangeIndex: 0,
balance: 0,
utxos: []
}
// Generate initial addresses
this.generateAddresses(account, 'receive', 20)
this.generateAddresses(account, 'change', 20)
this.accounts.set(name, account)
console.log(`Created account: ${name}`)
console.log(`First address: ${account.addresses[0].address}`)
return account
}
/**
* Generate addresses for account
*/
private generateAddresses(
account: Account,
type: 'receive' | 'change',
count: number
): void {
const chain = type === 'receive' ? 0 : 1
const startIndex = type === 'receive'
? account.nextReceiveIndex
: account.nextChangeIndex
for (let i = 0; i < count; i++) {
const index = startIndex + i
const path = `m/44'/0'/${account.accountIndex}'/${chain}/${index}`
const privateKey = this.hdWallet.deriveKey(path)
const address = privateKey.toPublicKey().toAddress()
account.addresses.push({
address,
path,
index,
type,
used: false,
balance: 0
})
}
if (type === 'receive') {
account.nextReceiveIndex += count
} else {
account.nextChangeIndex += count
}
}
/**
* Get account
*/
getAccount(name: string): Account | undefined {
return this.accounts.get(name)
}
/**
* Get next unused receiving address
*/
getNextReceivingAddress(accountName: string): string {
const account = this.accounts.get(accountName)
if (!account) {
throw new Error(`Account ${accountName} not found`)
}
// Find first unused receiving address
const unused = account.addresses.find(
addr => addr.type === 'receive' && !addr.used
)
if (!unused) {
// Generate more addresses
this.generateAddresses(account, 'receive', 20)
return this.getNextReceivingAddress(accountName)
}
return unused.address
}
/**
* Get next change address
*/
getNextChangeAddress(accountName: string): string {
const account = this.accounts.get(accountName)
if (!account) {
throw new Error(`Account ${accountName} not found`)
}
// Find first unused change address
const unused = account.addresses.find(
addr => addr.type === 'change' && !addr.used
)
if (!unused) {
// Generate more addresses
this.generateAddresses(account, 'change', 20)
return this.getNextChangeAddress(accountName)
}
return unused.address
}
/**
* Mark address as used
*/
markAddressUsed(address: string): void {
for (const account of this.accounts.values()) {
const addr = account.addresses.find(a => a.address === address)
if (addr) {
addr.used = true
return
}
}
}
/**
* Get private key for address
*/
getPrivateKey(address: string): PrivateKey | null {
for (const account of this.accounts.values()) {
const addr = account.addresses.find(a => a.address === address)
if (addr) {
return this.hdWallet.deriveKey(addr.path)
}
}
return null
}
/**
* Get all accounts
*/
getAllAccounts(): Account[] {
return Array.from(this.accounts.values())
}
/**
* Get wallet balance across all accounts
*/
getTotalBalance(): number {
let total = 0
for (const account of this.accounts.values()) {
total += account.balance
}
return total
}
/**
* Update account balance and UTXOs
*/
updateAccountBalance(
accountName: string,
utxos: UTXO[]
): void {
const account = this.accounts.get(accountName)
if (!account) {
throw new Error(`Account ${accountName} not found`)
}
account.utxos = utxos
account.balance = utxos.reduce((sum, utxo) => sum + utxo.satoshis, 0)
// Mark addresses as used
utxos.forEach(utxo => {
this.markAddressUsed(utxo.address)
})
console.log(`Updated ${accountName} balance: ${account.balance} sats`)
}
/**
* Export wallet
*/
export(): WalletExport {
return {
mnemonic: this.hdWallet.getMnemonic(),
accounts: Array.from(this.accounts.entries()).map(([name, account]) => ({
name,
accountIndex: account.accountIndex,
addressCount: account.addresses.length,
balance: account.balance
}))
}
}
/**
* Get wallet statistics
*/
getStats(): WalletStats {
let totalAddresses = 0
let usedAddresses = 0
let totalUTXOs = 0
for (const account of this.accounts.values()) {
totalAddresses += account.addresses.length
usedAddresses += account.addresses.filter(a => a.used).length
totalUTXOs += account.utxos.length
}
return {
accountCount: this.accounts.size,
totalAddresses,
usedAddresses,
totalUTXOs,
totalBalance: this.getTotalBalance()
}
}
}
interface Account {
name: string
accountIndex: number
addresses: AddressInfo[]
nextReceiveIndex: number
nextChangeIndex: number
balance: number
utxos: UTXO[]
}
interface AddressInfo {
address: string
path: string
index: number
type: 'receive' | 'change'
used: boolean
balance: number
}
interface UTXO {
txid: string
vout: number
satoshis: number
address: string
script?: any
}
interface WalletExport {
mnemonic: string
accounts: Array<{
name: string
accountIndex: number
addressCount: number
balance: number
}>
}
interface WalletStats {
accountCount: number
totalAddresses: number
usedAddresses: number
totalUTXOs: number
totalBalance: number
}
/**
* Usage Example
*/
async function walletManagerExample() {
console.log('=== Creating Wallet Manager ===')
// Create wallet manager
const manager = new WalletManager()
// Create accounts
const personal = manager.createAccount('personal', 0)
const business = manager.createAccount('business', 1)
console.log('\n=== Personal Account ===')
console.log('First address:', manager.getNextReceivingAddress('personal'))
console.log('Change address:', manager.getNextChangeAddress('personal'))
console.log('\n=== Business Account ===')
console.log('First address:', manager.getNextReceivingAddress('business'))
// Simulate receiving funds
console.log('\n=== Updating Balances ===')
const personalAddress = manager.getNextReceivingAddress('personal')
manager.updateAccountBalance('personal', [
{
txid: 'tx1...',
vout: 0,
satoshis: 100000,
address: personalAddress
},
{
txid: 'tx2...',
vout: 1,
satoshis: 50000,
address: personalAddress
}
])
// Get wallet statistics
const stats = manager.getStats()
console.log('\n=== Wallet Statistics ===')
console.log('Accounts:', stats.accountCount)
console.log('Total addresses:', stats.totalAddresses)
console.log('Used addresses:', stats.usedAddresses)
console.log('Total balance:', stats.totalBalance, 'sats')
// Export wallet
const exported = manager.export()
console.log('\n=== Export ===')
console.log('Mnemonic:', exported.mnemonic)
console.log('Accounts:', exported.accounts)
}