UNPKG

@nawab_kibria/bitcoin-lib

Version:

A comprehensive Bitcoin HD wallet library with BIP84, BIP44, BIP49 support, mnemonic generation and restoration

473 lines (472 loc) 19.9 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; Object.defineProperty(exports, "__esModule", { value: true }); exports.WalletManager = void 0; const bitcoin = __importStar(require("bitcoinjs-lib")); const bip39 = __importStar(require("bip39")); const bip32_1 = require("bip32"); const ecc = __importStar(require("tiny-secp256k1")); const BitcoinWallet_1 = require("./BitcoinWallet"); const ElectrumClient_1 = require("./ElectrumClient"); const TransactionBuilder_1 = require("./TransactionBuilder"); // Initialize BIP32 factory const bip32 = (0, bip32_1.BIP32Factory)(ecc); class WalletManager { constructor(config) { this.isConnected = false; this.isWatchOnly = false; this.config = Object.assign({ defaultAddressType: 'native-segwit', defaultAddressCount: 20 }, config); } /** * 1. Generate a new wallet with mnemonic and extended keys */ generateWallet(strength = 256) { this.mnemonic = bip39.generateMnemonic(strength); const seed = bip39.mnemonicToSeedSync(this.mnemonic); this.extendedPrivateKey = bip32.fromSeed(seed, this.config.network); this.extendedPublicKey = this.extendedPrivateKey.neutered(); this.isWatchOnly = false; // Initialize BitcoinWallet this.wallet = new BitcoinWallet_1.BitcoinWallet({ mnemonic: this.mnemonic, network: this.config.network, addressCount: this.config.defaultAddressCount }); console.log('✅ New wallet generated successfully'); return this.exportWallet(); } /** * 2. Export wallet (extended keys, mnemonic) */ exportWallet() { var _a; if (!this.extendedPublicKey) { throw new Error('No wallet to export. Generate or import a wallet first.'); } return { mnemonic: this.isWatchOnly ? undefined : this.mnemonic, extendedPrivateKey: this.isWatchOnly ? undefined : (_a = this.extendedPrivateKey) === null || _a === void 0 ? void 0 : _a.toBase58(), extendedPublicKey: this.extendedPublicKey.toBase58(), network: this.config.network.bech32, isWatchOnly: this.isWatchOnly }; } /** * 3. Import wallet from extended key or mnemonic */ importWallet(options) { return __awaiter(this, void 0, void 0, function* () { try { if (options.mnemonic) { if (!bip39.validateMnemonic(options.mnemonic)) { throw new Error('Invalid mnemonic phrase'); } this.mnemonic = options.mnemonic; const seed = bip39.mnemonicToSeedSync(this.mnemonic, options.passphrase || ''); this.extendedPrivateKey = bip32.fromSeed(seed, this.config.network); this.extendedPublicKey = this.extendedPrivateKey.neutered(); this.isWatchOnly = false; // Initialize BitcoinWallet this.wallet = new BitcoinWallet_1.BitcoinWallet({ mnemonic: this.mnemonic, network: this.config.network, addressCount: this.config.defaultAddressCount }); } else if (options.extendedPrivateKey) { this.extendedPrivateKey = bip32.fromBase58(options.extendedPrivateKey, this.config.network); this.extendedPublicKey = this.extendedPrivateKey.neutered(); this.isWatchOnly = false; } else if (options.extendedPublicKey) { this.extendedPublicKey = bip32.fromBase58(options.extendedPublicKey, this.config.network); this.isWatchOnly = true; } else { throw new Error('Must provide mnemonic, extendedPrivateKey, or extendedPublicKey'); } console.log(`✅ Wallet imported successfully (${this.isWatchOnly ? 'watch-only' : 'full control'})`); } catch (error) { throw new Error(`Failed to import wallet: ${error.message}`); } }); } /** * Connect to Electrum server */ connect() { return __awaiter(this, void 0, void 0, function* () { if (!this.electrumClient) { this.electrumClient = (0, ElectrumClient_1.createElectrumClient)(this.config.electrumServer.host, this.config.electrumServer.port, this.config.electrumServer.ssl || false); } yield this.electrumClient.connect(); this.isConnected = true; // Initialize TransactionBuilder this.transactionBuilder = new TransactionBuilder_1.TransactionBuilder(this.config.network); console.log('✅ Connected to Electrum server'); }); } /** * Disconnect from Electrum server */ disconnect() { if (this.electrumClient) { this.electrumClient.disconnect(); this.isConnected = false; console.log('🔌 Disconnected from Electrum server'); } } /** * Derive address at specific index */ deriveAddress(index, addressType) { var _a, _b, _c; const type = addressType || this.config.defaultAddressType; let addressInfo; if (this.wallet) { // Use wallet for mnemonic-based addresses let addresses; switch (type) { case 'legacy': addresses = this.wallet.generateLegacyAddresses(index + 1); addressInfo = addresses[index]; break; case 'segwit': addresses = this.wallet.generateSegwitAddresses(index + 1); addressInfo = addresses[index]; break; case 'native-segwit': addresses = this.wallet.generateNativeSegwitAddresses(index + 1); addressInfo = addresses[index]; break; default: throw new Error(`Unsupported address type: ${type}`); } } else if (this.extendedPrivateKey || this.extendedPublicKey) { // Derive from extended key using proper BIP paths const key = this.extendedPrivateKey || this.extendedPublicKey; const coinType = this.config.network === bitcoin.networks.bitcoin ? 0 : 1; switch (type) { case 'legacy': // BIP44: m/44'/coin'/0'/0/index const legacyPath = `m/44'/${coinType}'/0'/0/${index}`; const legacyChildKey = key.derivePath(legacyPath); const p2pkh = bitcoin.payments.p2pkh({ pubkey: legacyChildKey.publicKey, network: this.config.network }); addressInfo = { address: p2pkh.address, publicKey: legacyChildKey.publicKey.toString('hex'), privateKey: this.isWatchOnly ? undefined : (_a = legacyChildKey.privateKey) === null || _a === void 0 ? void 0 : _a.toString('hex'), derivationPath: legacyPath }; break; case 'segwit': // BIP49: m/49'/coin'/0'/0/index const segwitPath = `m/49'/${coinType}'/0'/0/${index}`; const segwitChildKey = key.derivePath(segwitPath); const p2sh = bitcoin.payments.p2sh({ redeem: bitcoin.payments.p2wpkh({ pubkey: segwitChildKey.publicKey, network: this.config.network }), network: this.config.network }); addressInfo = { address: p2sh.address, publicKey: segwitChildKey.publicKey.toString('hex'), privateKey: this.isWatchOnly ? undefined : (_b = segwitChildKey.privateKey) === null || _b === void 0 ? void 0 : _b.toString('hex'), derivationPath: segwitPath }; break; case 'native-segwit': // BIP84: m/84'/coin'/0'/0/index const nativeSegwitPath = `m/84'/${coinType}'/0'/0/${index}`; const nativeSegwitChildKey = key.derivePath(nativeSegwitPath); const p2wpkh = bitcoin.payments.p2wpkh({ pubkey: nativeSegwitChildKey.publicKey, network: this.config.network }); addressInfo = { address: p2wpkh.address, publicKey: nativeSegwitChildKey.publicKey.toString('hex'), privateKey: this.isWatchOnly ? undefined : (_c = nativeSegwitChildKey.privateKey) === null || _c === void 0 ? void 0 : _c.toString('hex'), derivationPath: nativeSegwitPath }; break; default: throw new Error(`Unsupported address type: ${type}`); } } else { throw new Error('No wallet or extended key available for address derivation'); } return { address: addressInfo.address, index, addressType: type, path: addressInfo.derivationPath || `m/0/${index}`, publicKey: addressInfo.publicKey, privateKey: addressInfo.privateKey }; } /** * 4. Fetch balance for address */ fetchBalance(address) { return __awaiter(this, void 0, void 0, function* () { this.ensureConnected(); return yield this.electrumClient.getBalance(address); }); } /** * 5. Fetch UTXOs for address */ fetchUTXOs(address) { return __awaiter(this, void 0, void 0, function* () { this.ensureConnected(); return yield this.electrumClient.getUTXOs(address); }); } /** * 6. Create transaction */ createTransaction(fromAddress_1, toAddress_1, amount_1, feeRate_1, privateKey_1) { return __awaiter(this, arguments, void 0, function* (fromAddress, toAddress, amount, feeRate, privateKey, addressType = 'native-segwit') { this.ensureConnected(); if (this.isWatchOnly) { throw new Error('Cannot create transaction with watch-only wallet'); } // Get UTXOs for the from address const utxos = yield this.fetchUTXOs(fromAddress); if (utxos.length === 0) { throw new Error('No UTXOs found for the from address'); } // Get the address info to obtain the correct public key // For simplicity, we'll assume index 0, but in a real implementation // you'd need to track which index corresponds to which address const addressInfo = this.deriveAddress(0, addressType); // Select UTXOs for the transaction const { selectedUTXOs, totalValue, estimatedFee } = this.transactionBuilder.selectUTXOs(utxos, amount, feeRate); // Convert UTXOs to transaction inputs const inputs = selectedUTXOs.map(utxo => ({ txHash: utxo.tx_hash, outputIndex: utxo.tx_pos, value: utxo.value, address: fromAddress, addressType: addressType, privateKey: privateKey, publicKey: addressInfo.publicKey, // Use the correct public key derivationPath: addressInfo.path })); // Create outputs const outputs = [ { address: toAddress, value: amount } ]; // Calculate change const changeValue = totalValue - amount - estimatedFee; const changeAddress = fromAddress; // Send change back to sender // Create transaction options const options = { feeRate, changeAddress: changeValue > 546 ? changeAddress : undefined, // Only if above dust limit rbf: true }; return yield this.transactionBuilder.createTransaction(inputs, outputs, options); }); } /** * 7. Estimate fees */ estimateFees() { return __awaiter(this, void 0, void 0, function* () { this.ensureConnected(); return yield this.electrumClient.estimateFees(); }); } /** * 8. Broadcast transaction */ broadcastTransaction(txHex) { return __awaiter(this, void 0, void 0, function* () { this.ensureConnected(); return yield this.electrumClient.broadcastTransaction(txHex); }); } /** * 9. Transaction history for address */ getTransactionHistory(address) { return __awaiter(this, void 0, void 0, function* () { this.ensureConnected(); return yield this.electrumClient.getHistory(address); }); } /** * Get wallet status and information */ getWalletInfo() { return { isImported: !!(this.wallet || this.extendedPublicKey), isWatchOnly: this.isWatchOnly, isConnected: this.isConnected, network: this.config.network.bech32, defaultAddressType: this.config.defaultAddressType, hasMnemonic: !!this.mnemonic, hasExtendedKey: !!this.extendedPublicKey }; } /** * Generate multiple addresses for different types */ generateAddresses(count = 5) { if (!this.wallet && !this.extendedPublicKey) { throw new Error('No wallet imported. Generate or import a wallet first.'); } const addresses = { legacy: [], segwit: [], nativeSegwit: [] }; for (let i = 0; i < count; i++) { addresses.legacy.push(this.deriveAddress(i, 'legacy')); addresses.segwit.push(this.deriveAddress(i, 'segwit')); addresses.nativeSegwit.push(this.deriveAddress(i, 'native-segwit')); } return addresses; } /** * Get balances for multiple addresses */ getMultipleBalances(addresses) { return __awaiter(this, void 0, void 0, function* () { this.ensureConnected(); return yield this.electrumClient.getMultipleBalances(addresses); }); } /** * Estimate transaction size and fees for given parameters */ estimateTransactionCost(fromAddress_1, toAddress_1, amount_1) { return __awaiter(this, arguments, void 0, function* (fromAddress, toAddress, amount, addressType = 'native-segwit') { this.ensureConnected(); // Get UTXOs for the from address const utxos = yield this.fetchUTXOs(fromAddress); if (utxos.length === 0) { throw new Error('No UTXOs found for the from address'); } // Estimate transaction size based on address type let inputSize; switch (addressType) { case 'legacy': inputSize = 148; break; case 'segwit': inputSize = 91; break; case 'native-segwit': inputSize = 68; break; default: inputSize = 68; } const outputSize = 34; // P2WPKH output const baseSize = 10; // version, lock time, etc. const estimatedSize = baseSize + (utxos.length * inputSize) + (2 * outputSize); // 2 outputs (to + change) // Get fee estimates const feeEstimates = yield this.estimateFees(); // Calculate recommended fees const recommendedFees = { fastestFee: Math.ceil(estimatedSize * feeEstimates.fastestFee), halfHourFee: Math.ceil(estimatedSize * feeEstimates.halfHourFee), hourFee: Math.ceil(estimatedSize * feeEstimates.hourFee), economyFee: Math.ceil(estimatedSize * feeEstimates.economyFee) }; return { estimatedSize, feeEstimates, recommendedFees }; }); } // Private helper methods ensureConnected() { if (!this.isConnected || !this.electrumClient) { throw new Error('Not connected to Electrum server. Call connect() first.'); } } /** * Get mnemonic (only if wallet was created/imported with mnemonic) */ getMnemonic() { if (this.isWatchOnly) { throw new Error('Mnemonic not available for watch-only wallets'); } return this.mnemonic; } /** * Get extended private key (only for full control wallets) */ getExtendedPrivateKey() { var _a; if (this.isWatchOnly) { throw new Error('Extended private key not available for watch-only wallets'); } return (_a = this.extendedPrivateKey) === null || _a === void 0 ? void 0 : _a.toBase58(); } /** * Get extended public key */ getExtendedPublicKey() { var _a; return (_a = this.extendedPublicKey) === null || _a === void 0 ? void 0 : _a.toBase58(); } } exports.WalletManager = WalletManager;