@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
JavaScript
;
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;