@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
JavaScript
"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
});
}