UNPKG

@nawab_kibria/bitcoin-lib

Version:

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

608 lines (607 loc) 23.3 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.ElectrumClient = void 0; exports.createElectrumClient = createElectrumClient; const net = __importStar(require("net")); const tls = __importStar(require("tls")); const crypto = __importStar(require("crypto")); const bitcoin = __importStar(require("bitcoinjs-lib")); class ElectrumClient { constructor(config) { this.socket = null; this.requestId = 0; this.pendingRequests = new Map(); this.connected = false; this.buffer = ''; this.config = Object.assign({ timeout: 30000, ssl: false }, config); } /** * Connect to the Electrum server */ connect() { return __awaiter(this, void 0, void 0, function* () { return new Promise((resolve, reject) => { try { // Create socket based on SSL preference if (this.config.ssl) { this.socket = tls.connect({ host: this.config.host, port: this.config.port, rejectUnauthorized: false // For self-signed certificates }); } else { this.socket = net.createConnection({ host: this.config.host, port: this.config.port }); } // Set timeout this.socket.setTimeout(this.config.timeout); // Handle connection events this.socket.on('connect', () => { this.connected = true; console.log(`Connected to Electrum server at ${this.config.host}:${this.config.port}`); resolve(); }); this.socket.on('data', (data) => { this.handleData(data.toString()); }); this.socket.on('error', (error) => { console.error('Socket error:', error); this.connected = false; reject(error); }); this.socket.on('close', () => { console.log('Connection to Electrum server closed'); this.connected = false; }); this.socket.on('timeout', () => { var _a; console.error('Socket timeout'); (_a = this.socket) === null || _a === void 0 ? void 0 : _a.destroy(); this.connected = false; reject(new Error('Connection timeout')); }); } catch (error) { reject(error); } }); }); } /** * Disconnect from the Electrum server */ disconnect() { if (this.socket) { this.socket.destroy(); this.socket = null; this.connected = false; } } /** * Check if connected to the server */ isConnected() { return this.connected; } /** * Handle incoming data from the server */ handleData(data) { this.buffer += data; // Process complete JSON messages const lines = this.buffer.split('\n'); this.buffer = lines.pop() || ''; // Keep the incomplete line in buffer for (const line of lines) { if (line.trim()) { try { const response = JSON.parse(line); this.handleResponse(response); } catch (error) { console.error('Failed to parse response:', error, 'Data:', line); } } } } /** * Handle server response */ handleResponse(response) { const pendingRequest = this.pendingRequests.get(response.id); if (pendingRequest) { this.pendingRequests.delete(response.id); if (response.error) { pendingRequest.reject(new Error(`Electrum error: ${response.error.message}`)); } else { pendingRequest.resolve(response.result); } } } /** * Send a request to the Electrum server */ sendRequest(method_1) { return __awaiter(this, arguments, void 0, function* (method, params = []) { if (!this.connected || !this.socket) { throw new Error('Not connected to Electrum server'); } const id = ++this.requestId; const request = { id, method, params }; return new Promise((resolve, reject) => { // Store the pending request this.pendingRequests.set(id, { resolve, reject }); // Set timeout for this specific request const timeout = setTimeout(() => { this.pendingRequests.delete(id); reject(new Error(`Request timeout for method: ${method}`)); }, this.config.timeout); // Clear timeout when request completes const originalResolve = resolve; const originalReject = reject; this.pendingRequests.set(id, { resolve: (value) => { clearTimeout(timeout); originalResolve(value); }, reject: (error) => { clearTimeout(timeout); originalReject(error); } }); // Send the request const message = JSON.stringify(request) + '\n'; this.socket.write(message); }); }); } /** * Get server version and protocol version */ getServerVersion() { return __awaiter(this, void 0, void 0, function* () { const result = yield this.sendRequest('server.version', ['bitcoin-lib', '1.4']); return { server: result[0], protocol: result[1] }; }); } /** * Get address balance */ getBalance(address) { return __awaiter(this, void 0, void 0, function* () { if (!this.connected || !this.socket) { throw new Error('Not connected to Electrum server'); } // Convert address to script hash (SHA256 of the script, reversed) const scriptHash = this.addressToScriptHash(address); const result = yield this.sendRequest('blockchain.scripthash.get_balance', [scriptHash]); return { confirmed: result.confirmed, unconfirmed: result.unconfirmed }; }); } /** * Get UTXOs for an address */ getUTXOs(address) { return __awaiter(this, void 0, void 0, function* () { if (!this.connected || !this.socket) { throw new Error('Not connected to Electrum server'); } const scriptHash = this.addressToScriptHash(address); const result = yield this.sendRequest('blockchain.scripthash.listunspent', [scriptHash]); return result.map((utxo) => ({ tx_hash: utxo.tx_hash, tx_pos: utxo.tx_pos, value: utxo.value, height: utxo.height })); }); } /** * Get transaction history for an address */ getHistory(address) { return __awaiter(this, void 0, void 0, function* () { const scriptHash = this.addressToScriptHash(address); const result = yield this.sendRequest('blockchain.scripthash.get_history', [scriptHash]); return result.map((item) => ({ tx_hash: item.tx_hash, height: item.height, fee: item.fee })); }); } /** * Get raw transaction by hash */ getTransaction(txHash) { return __awaiter(this, void 0, void 0, function* () { return yield this.sendRequest('blockchain.transaction.get', [txHash]); }); } /** * Get transaction details with verbose output */ getTransactionVerbose(txHash) { return __awaiter(this, void 0, void 0, function* () { return yield this.sendRequest('blockchain.transaction.get', [txHash, true]); }); } /** * Broadcast a raw transaction */ broadcastTransaction(rawTx) { return __awaiter(this, void 0, void 0, function* () { try { const txid = yield this.sendRequest('blockchain.transaction.broadcast', [rawTx]); return { txid: txid, success: true }; } catch (error) { return { txid: '', success: false, error: error.message || 'Failed to broadcast transaction' }; } }); } /** * Estimate transaction fees for different confirmation targets */ estimateFees() { return __awaiter(this, void 0, void 0, function* () { try { // Electrum fee estimation for different confirmation targets const [fastestFee, halfHourFee, hourFee, economyFee] = yield Promise.all([ this.sendRequest('blockchain.estimatefee', [1]), // Next block this.sendRequest('blockchain.estimatefee', [3]), // ~30 minutes this.sendRequest('blockchain.estimatefee', [6]), // ~1 hour this.sendRequest('blockchain.estimatefee', [144]) // ~24 hours ]); // Convert from BTC/kB to sat/vB const convertFee = (feeInBtcPerKb) => { if (feeInBtcPerKb <= 0) return 1; // Minimum 1 sat/vB return Math.ceil((feeInBtcPerKb * 100000000) / 1000); // BTC/kB to sat/vB }; return { fastestFee: convertFee(fastestFee), halfHourFee: convertFee(halfHourFee), hourFee: convertFee(hourFee), economyFee: convertFee(economyFee), minimumFee: 1 // 1 sat/vB minimum }; } catch (error) { // Fallback fee estimates if server doesn't support fee estimation console.warn('Fee estimation failed, using fallback values:', error); return { fastestFee: 50, // 50 sat/vB halfHourFee: 25, // 25 sat/vB hourFee: 10, // 10 sat/vB economyFee: 5, // 5 sat/vB minimumFee: 1 // 1 sat/vB }; } }); } /** * Get estimated fee for transaction confirmation in specified blocks (legacy method) */ getEstimateFee() { return __awaiter(this, arguments, void 0, function* (blocks = 6) { try { const result = yield this.sendRequest('blockchain.estimatefee', [blocks]); // Convert from BTC/kB to sat/vB if (result <= 0) return 1; return Math.ceil((result * 100000000) / 1000); } catch (error) { console.warn('Fee estimation failed, using fallback:', error); return 10; // 10 sat/vB fallback } }); } /** * Get relay fee from the server */ getRelayFee() { return __awaiter(this, void 0, void 0, function* () { try { const relayFee = yield this.sendRequest('blockchain.relayfee', []); // Convert from BTC to satoshis return Math.ceil(relayFee * 100000000); } catch (error) { console.warn('Failed to get relay fee, using default:', error); return 1000; // Default 1000 satoshis } }); } /** * Get mempool fee histogram */ getMempoolFeeHistogram() { return __awaiter(this, void 0, void 0, function* () { try { return yield this.sendRequest('mempool.get_fee_histogram', []); } catch (error) { console.warn('Mempool fee histogram not available:', error); return []; } }); } /** * Check if transaction is in mempool */ isTransactionInMempool(txid) { return __awaiter(this, void 0, void 0, function* () { try { const tx = yield this.getTransactionVerbose(txid); return tx && tx.confirmations === 0; } catch (error) { return false; } }); } /** * Get transaction confirmation status */ getTransactionStatus(txid) { return __awaiter(this, void 0, void 0, function* () { try { const tx = yield this.getTransactionVerbose(txid); if (!tx.confirmations || tx.confirmations === 0) { return { confirmed: false, confirmations: 0 }; } return { confirmed: true, confirmations: tx.confirmations, blockHeight: tx.block_height }; } catch (error) { throw new Error(`Failed to get transaction status: ${error}`); } }); } /** * Get multiple address balances at once */ getMultipleBalances(addresses) { return __awaiter(this, void 0, void 0, function* () { const results = {}; // Use Promise.all for parallel requests const promises = addresses.map((address) => __awaiter(this, void 0, void 0, function* () { try { const balance = yield this.getBalance(address); results[address] = balance; } catch (error) { console.error(`Failed to get balance for ${address}:`, error); results[address] = { confirmed: 0, unconfirmed: 0 }; } })); yield Promise.all(promises); return results; }); } /** * Get multiple address UTXOs at once */ getMultipleUTXOs(addresses) { return __awaiter(this, void 0, void 0, function* () { const results = {}; const promises = addresses.map((address) => __awaiter(this, void 0, void 0, function* () { try { const utxos = yield this.getUTXOs(address); results[address] = utxos; } catch (error) { console.error(`Failed to get UTXOs for ${address}:`, error); results[address] = []; } })); yield Promise.all(promises); return results; }); } /** * Convert Bitcoin address to script hash for Electrum protocol */ addressToScriptHash(address) { try { // Decode the address to get the output script let outputScript; // Try to decode as different address types try { // Try bech32/bech32m (native segwit and taproot) first const decoded = bitcoin.address.fromBech32(address); if (decoded.version === 0) { if (decoded.data.length === 20) { // P2WPKH (Native SegWit v0) outputScript = bitcoin.script.compile([ bitcoin.opcodes.OP_0, decoded.data ]); } else if (decoded.data.length === 32) { // P2WSH (Native SegWit v0) outputScript = bitcoin.script.compile([ bitcoin.opcodes.OP_0, decoded.data ]); } else { throw new Error('Invalid witness v0 program length'); } } else if (decoded.version === 1 && decoded.data.length === 32) { // P2TR (Taproot - Witness v1) outputScript = bitcoin.script.compile([ bitcoin.opcodes.OP_1, decoded.data ]); } else { throw new Error(`Unsupported witness version: ${decoded.version}`); } } catch (bech32Error) { // Try base58 (legacy and nested segwit) try { const decoded = bitcoin.address.fromBase58Check(address); // Determine network and address type const networks = [bitcoin.networks.bitcoin, bitcoin.networks.testnet, bitcoin.networks.regtest]; let network; let isP2SH = false; for (const net of networks) { if (decoded.version === net.pubKeyHash) { network = net; isP2SH = false; break; } else if (decoded.version === net.scriptHash) { network = net; isP2SH = true; break; } } if (!network) { throw new Error('Unknown address version'); } if (isP2SH) { // P2SH (nested segwit or regular script) outputScript = bitcoin.script.compile([ bitcoin.opcodes.OP_HASH160, decoded.hash, bitcoin.opcodes.OP_EQUAL ]); } else { // P2PKH (legacy) outputScript = bitcoin.script.compile([ bitcoin.opcodes.OP_DUP, bitcoin.opcodes.OP_HASH160, decoded.hash, bitcoin.opcodes.OP_EQUALVERIFY, bitcoin.opcodes.OP_CHECKSIG ]); } } catch (base58Error) { throw new Error(`Invalid address format: ${address}`); } } // Hash the output script with SHA256 const scriptHash = crypto.createHash('sha256').update(outputScript).digest(); // Reverse the hash bytes (Electrum protocol requirement) const reversed = Buffer.from(scriptHash).reverse(); return reversed.toString('hex'); } catch (error) { throw new Error(`Failed to convert address to script hash: ${error}`); } } /** * Subscribe to address changes (for real-time updates) */ subscribeToAddress(address, callback) { return __awaiter(this, void 0, void 0, function* () { const scriptHash = this.addressToScriptHash(address); // Store callback for this script hash // This would need a more sophisticated implementation for production const result = yield this.sendRequest('blockchain.scripthash.subscribe', [scriptHash]); // Handle subscription updates in handleResponse method console.log(`Subscribed to address ${address}, current status: ${result}`); }); } /** * Ping the server to keep connection alive */ ping() { return __awaiter(this, void 0, void 0, function* () { yield this.sendRequest('server.ping'); }); } /** * Get server features */ getServerFeatures() { return __awaiter(this, void 0, void 0, function* () { return yield this.sendRequest('server.features'); }); } } exports.ElectrumClient = ElectrumClient; // Helper function to create a client with default regtest configuration function createElectrumClient(host = 'localhost', port = 50001, ssl = false) { return new ElectrumClient({ host, port, ssl }); }