UNPKG

@dolaned/wallet-sdk-ts

Version:

Wallet SDK for the Nexa blockchain

398 lines (375 loc) 15 kB
import {HDPrivateKey, Message, Networkish, Networks, TransactionBuilder,} from "libnexa-ts"; import * as Bip39 from 'bip39' import {rostrumProvider} from "../network/RostrumProvider"; import {AccountType, discoverWallet,} from "../utils/WalletUtils"; import {isBuffer, isNil, isString} from "lodash-es"; import WalletTransactionCreator from "./transactions/WalletTransactionCreator"; import AccountStore from "./accounts/AccountStore"; import {BaseAccount} from "./accounts/interfaces/BaseAccountInterface"; import ValidationUtils from "../utils/ValidationUtils"; import {AddressKey} from "../models/wallet.entities"; /** * Main Wallet class for managing Nexa blockchain wallet operations * * This class provides comprehensive wallet functionality including: * - Creating wallets from seed phrases or private keys * - Account discovery and management * - Transaction creation and signing * - Message signing and verification * - Multi-account support with different account types * * @example * ```typescript * // Create a new wallet with random seed phrase * const wallet = Wallet.create(); * * // Restore wallet from existing seed phrase * const wallet = Wallet.fromSeedPhrase('your twelve word seed phrase here'); * * // Initialize wallet (discovers accounts and balances) * await wallet.initialize(); * * // Create a new account * const account = await wallet.newAccount('DefaultAccount'); * * // Create and send a transaction * const tx = wallet.newTransaction(account) * .to('nexa:address', 1000000) // 1 NEXA in satoshis * .sign(); * * const txId = await wallet.sendTransaction(tx.toHex()); * ``` */ export default class Wallet { /** The master HD private key derived from the seed phrase */ private readonly masterKey!: HDPrivateKey; /** Store for managing wallet accounts */ private _accountStore: AccountStore; /** The blockchain network this wallet operates on */ private readonly _network: Networkish /** The BIP39 seed phrase used to generate this wallet (if created from phrase) */ private readonly phrase?: string; /** * Creates a new Wallet instance * * @param data - Optional wallet data: * - undefined: Generate new random seed phrase * - string: Use as BIP39 seed phrase * - HDPrivateKey: Use as master key directly * @param network - Network name ('mainnet', 'testnet', 'regtest'). Defaults to 'mainnet' * * @example * ```typescript * // Create new wallet with random seed * const wallet = new Wallet(); * * // Create from seed phrase * const wallet = new Wallet('abandon abandon abandon...'); * * // Create from master key * const masterKey = HDPrivateKey.fromString('xprv...'); * const wallet = new Wallet(masterKey); * * // Create on testnet * const wallet = new Wallet(undefined, 'testnet'); * ``` */ constructor(data?: string | HDPrivateKey | undefined, network?: string) { this._network = Networks.get(network) ?? Networks.mainnet this._accountStore = new AccountStore() if(isNil(data)) { this.phrase = Bip39.generateMnemonic(128, undefined, Bip39.wordlists.english) const seed = Bip39.mnemonicToSeedSync(this.phrase, '') const masterKey = HDPrivateKey.fromSeed(seed, this._network ?? Networks.mainnet) this.masterKey = masterKey.deriveChild(44, true).deriveChild(29223, true) } else if(data instanceof HDPrivateKey) { this.masterKey = data } else if (isString(data)) { this.phrase = data const seed = Bip39.mnemonicToSeedSync(this.phrase, '') const masterKey = HDPrivateKey.fromSeed(seed, this._network ?? Networks.mainnet) this.masterKey = masterKey.deriveChild(44, true).deriveChild(29223, true) } } /** * Create a new wallet with a randomly generated seed phrase * * This is the recommended way to create a new wallet for first-time users. * The generated seed phrase should be securely stored by the user. * * @returns A new Wallet instance with a random 12-word seed phrase * * @example * ```typescript * const wallet = Wallet.create(); * console.log(wallet.export().phrase); // Store this securely! * ``` */ public static create(): Wallet { return new Wallet() } /** * Create a wallet from an existing BIP39 seed phrase * * Use this method to restore a wallet from a previously generated seed phrase. * The seed phrase should be a valid BIP39 mnemonic. * * @param phrase - The BIP39 seed phrase (12 or 24 words) * @param network - Optional network name ('mainnet', 'testnet', 'regtest') * @returns A new Wallet instance restored from the seed phrase * @throws {Error} If the seed phrase is invalid or not provided * * @example * ```typescript * const wallet = Wallet.fromSeedPhrase( * 'abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about', * 'testnet' * ); * ``` */ public static fromSeedPhrase(phrase: string, network?: string): Wallet { ValidationUtils.validateArgument(isString(phrase), 'seedphrase must be provided') return new Wallet(phrase, network) } /** * Create a wallet from an extended private key (xpriv) * * Use this method to create a wallet from a master private key in extended format. * This is useful for advanced users who want to use a specific key derivation. * * @param xpriv - The extended private key string (starts with 'xprv') * @param network - Optional network name ('mainnet', 'testnet', 'regtest') * @returns A new Wallet instance using the provided master key * @throws {Error} If the private key is invalid or not provided * * @example * ```typescript * const wallet = Wallet.fromXpriv( * 'xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHi' * ); * ``` */ public static fromXpriv(xpriv: string, network?: string): Wallet { ValidationUtils.validateArgument(isString(xpriv), 'private key must be provided') const masterKey = HDPrivateKey.fromString(xpriv) return new Wallet(masterKey, network) } /** * Initialize the wallet by discovering accounts and loading balances * * This method performs account discovery using the BIP44 derivation path * and scans for existing accounts with transaction history or balances. * Must be called before using the wallet's accounts. * * @returns Promise that resolves when initialization is complete * * @example * ```typescript * const wallet = Wallet.fromSeedPhrase('your seed phrase'); * await wallet.initialize(); * * // Now you can access discovered accounts * const accounts = wallet.accountStore.listAccounts(); * ``` */ public async initialize(): Promise<void> { const walletAccounts: BaseAccount[] = await discoverWallet(this.masterKey) for(const account of walletAccounts){ this._accountStore.importAccount(account) } } /** * Create a new transaction builder for this wallet * * @param fromAccount - The account to send the transaction from * @param x - Optional existing transaction data: * - TransactionBuilder: Use existing transaction builder * - string: Parse from hex string * - Buffer: Parse from binary buffer * - undefined: Create new empty transaction * @returns A new WalletTransactionCreator instance * * @example * ```typescript * const account = wallet.accountStore.getAccount(0); * const tx = wallet.newTransaction(account) * .to('nexa:address', 1000000) // 1 NEXA * .sign(); * * // Or from existing transaction hex * const tx = wallet.newTransaction(account, 'raw_tx_hex') * .sign(); * ``` */ public newTransaction(fromAccount: BaseAccount, x?: TransactionBuilder | string | Buffer): WalletTransactionCreator { let tx: WalletTransactionCreator; if (x instanceof TransactionBuilder) { tx = new WalletTransactionCreator(fromAccount, x); } else if (isString(x)) { tx = new WalletTransactionCreator(fromAccount).parseTxHex(x); } else if (isBuffer(x) && !isNil(x)) { tx = new WalletTransactionCreator(fromAccount).parseTxBuffer(x); } else { tx = new WalletTransactionCreator(fromAccount); } return tx.onNetwork(this._network); } /** * Create a new account for this wallet * * @param accountType - The type of account to create: * - 'DefaultAccount': Standard account for general use * - 'VaultAccount': Secured account with additional protection * - 'DappAccount': Account optimized for dApp interactions * @returns Promise that resolves to the newly created account * * @example * ```typescript * const defaultAccount = await wallet.newAccount('DefaultAccount'); * const vaultAccount = await wallet.newAccount('VaultAccount'); * const dappAccount = await wallet.newAccount('DappAccount'); * ``` */ public async newAccount(accountType: AccountType): Promise<BaseAccount>{ return await this.accountStore.createAccount(accountType, this.masterKey) } /** * Broadcast a signed transaction to the Nexa network * * @param transaction - The signed transaction in hex format * @returns Promise that resolves to the transaction ID (txid) * @throws {Error} If the transaction is invalid or broadcast fails * * @example * ```typescript * const tx = wallet.newTransaction(account) * .to('nexa:address', 1000000) * .sign(); * * const txId = await wallet.sendTransaction(tx.toHex()); * console.log('Transaction sent:', txId); * ``` */ public async sendTransaction(transaction: string): Promise<string> { ValidationUtils.validateArgument(isString(transaction), 'transaction must be present and valid') return rostrumProvider.broadcast(transaction) } /** * Sign a message using a specific address from this wallet * * The message is signed using the private key associated with the given address. * This can be used for authentication or to prove ownership of an address. * * @param message - The message to sign * @param addressToUse - The address whose private key should sign the message * @returns The signature as a base64-encoded string * @throws {Error} If the address is not owned by this wallet * * @example * ```typescript * const account = wallet.accountStore.getAccount(0); * const address = account.getReceiveAddress(); * const signature = wallet.signMessage('Hello World', address); * ``` */ public signMessage(message: string, addressToUse: string): string { let msg = new Message(message); const addressKey = this.accountStore.findKeyForAddress(addressToUse) ValidationUtils.validateArgument(isNil(addressKey), "You dont own this private key") return msg.sign(addressKey?.key.privateKey!) } /** * Verify a message signature against an address * * This method can verify signatures created by any address, not just addresses * owned by this wallet. It's useful for verifying messages from other parties. * * @param message - The original message that was signed * @param signature - The signature to verify (base64-encoded) * @param address - The address that supposedly signed the message * @returns true if the signature is valid, false otherwise * @throws {Error} If any parameters are missing or invalid * * @example * ```typescript * const isValid = wallet.verifyMessage( * 'Hello World', * 'signature_string', * 'nexa:address' * ); * console.log('Signature valid:', isValid); * ``` */ public verifyMessage(message: string, signature: string, address: string): boolean { ValidationUtils.validateArgument(!isNil(message), 'message is required') ValidationUtils.validateArgument(!isNil(signature), 'signature is required') ValidationUtils.validateArgument(!isNil(address), 'address is required ') let msg = new Message(message); const addressKey = this.accountStore.findKeyForAddress(address) ValidationUtils.validateArgument(isNil(addressKey), "You dont own this private key") return msg.verify(address, signature) } /** * Export the wallet data for backup or storage * * Returns an object containing the wallet's seed phrase, master key, and accounts. * This data can be used to restore the wallet later. The seed phrase should be * stored securely as it provides full access to the wallet. * * @returns Object containing wallet data * @property {string} phrase - The BIP39 seed phrase (if available) * @property {HDPrivateKey} masterKey - The master private key * @property {BaseAccount[]} accounts - Array of discovered accounts * * @example * ```typescript * const walletData = wallet.export(); * * // Store the seed phrase securely * const seedPhrase = walletData.phrase; * * // Later, restore the wallet * const restoredWallet = Wallet.fromSeedPhrase(seedPhrase); * ``` */ public export(): any { return { phrase: this.phrase, masterKey: this.masterKey, accounts: this._accountStore.listAccounts(), accountIndexes: this.accountStore.listAccounts().keys() } } /** * Get the account store for managing wallet accounts * * The account store provides methods to create, import, and manage accounts * within this wallet. Each account has its own set of addresses and keys. * * @returns The wallet's account store * * @example * ```typescript * const accountStore = wallet.accountStore; * const accounts = accountStore.listAccounts(); * const firstAccount = accountStore.getAccount(0); * ``` */ get accountStore(): AccountStore { return this._accountStore; } /** * Get the network this wallet is operating on * * @returns The network object (mainnet, testnet, or regtest) * * @example * ```typescript * const network = wallet.network; * console.log('Network:', network.name); * ``` */ get network(): Networkish { return this._network; } }