UNPKG

caravan-x

Version:

A terminal-based utility for managing Caravan multisig wallets in regtest mode. This tool simplifies development and testing with Caravan by providing an easy-to-use interface

337 lines (336 loc) 12.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; }; })(); Object.defineProperty(exports, "__esModule", { value: true }); exports.BitcoinService = void 0; const bitcoin = __importStar(require("bitcoinjs-lib")); const ecpair_1 = require("ecpair"); const ecc = __importStar(require("tiny-secp256k1")); const bip32 = __importStar(require("bip32")); const terminal_1 = require("../utils/terminal"); // Initialize libraries bitcoin.initEccLib(ecc); const ECPair = (0, ecpair_1.ECPairFactory)(ecc); const BIP32 = bip32.BIP32Factory(ecc); /** * Service for Bitcoin wallet operations */ class BitcoinService { constructor(rpc, isRegtest = true) { this.rpc = rpc; this.network = isRegtest ? bitcoin.networks.regtest : bitcoin.networks.testnet; } /** * Create a new wallet */ async createWallet(name, options = {}) { const { disablePrivateKeys = false, blank = false, descriptorWallet = true, } = options; try { console.log((0, terminal_1.boxText)(`${(0, terminal_1.keyValue)("disablePrivateKeys", disablePrivateKeys.toString())}\n` + `${(0, terminal_1.keyValue)("blank", blank.toString())}\n` + `${(0, terminal_1.keyValue)("descriptorWallet", descriptorWallet.toString())}`, { title: `Creating Wallet: ${name}`, titleColor: terminal_1.colors.info })); // Try the direct RPC call first with a more compatible format try { const result = await this.rpc.callRpc("createwallet", [ name, // wallet_name disablePrivateKeys, // disable_private_keys blank, // blank "", // passphrase false, // avoid_reuse descriptorWallet, // descriptors true, // load_on_startup ]); return result; } catch (error) { // If the above fails, try with named parameters if (error.message.includes("unknown named parameter") || error.message.includes("incorrect number of parameters")) { console.log(terminal_1.colors.warning("Using alternative wallet creation method")); // Create params object for named parameters const params = { wallet_name: name, disable_private_keys: disablePrivateKeys, blank: blank, }; // Only add descriptors if needed (for compatibility with older versions) if (descriptorWallet) { params.descriptors = true; } const result = await this.rpc.callRpc("createwallet", [params]); return result; } else { // Last resort: try the core createWallet method with fewer parameters console.log(terminal_1.colors.warning("Using simplified wallet creation")); const result = await this.rpc.createWallet(name, disablePrivateKeys, blank); return result; } } } catch (error) { console.error((0, terminal_1.formatError)(`Error creating wallet ${name}:`), error); throw error; } } /** * Get info about a wallet */ async getWalletInfo(wallet) { try { const info = await this.rpc.getWalletInfo(wallet); return info; } catch (error) { console.error((0, terminal_1.formatError)(`Error getting info for wallet ${wallet}:`), error); throw error; } } /** * Generate a new address from a wallet */ async getNewAddress(wallet, label = "") { try { const address = await this.rpc.getNewAddress(wallet, label); return address; } catch (error) { console.error((0, terminal_1.formatError)(`Error generating address for wallet ${wallet}:`), error); throw error; } } /** * Get detailed info about an address */ async getAddressInfo(wallet, address) { try { const info = await this.rpc.getAddressInfo(wallet, address); return info; } catch (error) { console.error((0, terminal_1.formatError)(`Error getting info for address ${address} in wallet ${wallet}:`), error); throw error; } } /** * List wallets on the node */ async listWallets() { try { const wallets = await this.rpc.listWallets(); return wallets; } catch (error) { console.error((0, terminal_1.formatError)("Error listing wallets:"), error); throw error; } } /** * List unspent outputs for an address or wallet */ async listUnspent(wallet, addresses = []) { try { const utxos = await this.rpc.listUnspent(wallet, 0, 9999999, addresses); return utxos; } catch (error) { console.error((0, terminal_1.formatError)(`Error listing unspent outputs for wallet ${wallet}:`), error); throw error; } } /** * Get transaction details */ async getTransaction(wallet, txid) { try { const tx = await this.rpc.getTransaction(wallet, txid); return tx; } catch (error) { console.error((0, terminal_1.formatError)(`Error getting transaction ${txid} in wallet ${wallet}:`), error); throw error; } } /** * Send to address */ async sendToAddress(wallet, address, amount) { try { const txid = await this.rpc.callRpc("sendtoaddress", [address, amount], wallet); return txid; } catch (error) { console.error((0, terminal_1.formatError)(`Error sending to address ${address} from wallet ${wallet}:`), error); throw error; } } /** * Create a wallet with a known private key (for testing) */ async createPrivateKeyWallet(name, wif) { try { // Create the wallet await this.createWallet(name, { disablePrivateKeys: false, blank: false, descriptorWallet: false, // Explicitly create a legacy wallet that supports importprivkey }); let keyPair; if (wif) { // Use provided private key keyPair = ECPair.fromWIF(wif, this.network); } else { // Generate a random private key keyPair = ECPair.makeRandom({ network: this.network }); wif = keyPair.toWIF(); } // Import the private key const privateKey = wif; try { await this.rpc.callRpc("importprivkey", [privateKey, "imported", false], name); } catch (error) { console.error((0, terminal_1.formatError)(`Error importing private key to wallet ${name}:`), error); throw error; } // Get new address from the wallet for verification const address = await this.getNewAddress(name); return { wallet: name, wif: privateKey, address }; } catch (error) { console.error((0, terminal_1.formatError)(`Error creating private key wallet ${name}:`), error); throw error; } } /** * Generate an extended public key from wallet */ async getExtendedPubKey(wallet, path = "m/44'/1'/0'") { try { // For regtest, we'll manually derive the xpub from seed try { const dumpResult = await this.rpc.callRpc("dumpwallet", ["/tmp/temp-wallet.txt"], wallet); // Extract seed or private key info from the wallet dump // @ts-ignore const seed = Buffer.from(dumpResult, "hex"); const node = BIP32.fromSeed(seed, this.network); // Derive the path const derivedNode = node.derivePath(path); // Get the extended public key const xpub = derivedNode.neutered().toBase58(); // Get the root fingerprint // @ts-ignore const rootFingerprint = node.fingerprint.toString("hex"); return { xpub, path, rootFingerprint }; } catch (error) { console.log(terminal_1.colors.warning("Using alternative method to get xpub")); // Fallback to a more direct method try { const result = await this.rpc.callRpc("getdescriptorinfo", [`wpkh(${path})`], wallet); return { xpub: result.descriptor, path }; } catch (fallbackError) { console.error("Fallback also failed:", fallbackError); throw error; } } } catch (error) { console.error((0, terminal_1.formatError)(`Error getting extended public key for wallet ${wallet}:`), error); throw error; } } /** * Create a software wallet for use in Caravan multisig */ async createCaravanKeyWallet(name, path = "m/84'/1'/0'") { // Create a wallet with private keys const walletName = `${name}_key`; await this.createWallet(walletName); // Generate HD seed await this.rpc.callRpc("sethdseed", [true], walletName); // Get the xpub information const xpubInfo = await this.getExtendedPubKey(walletName, path); // For the demo, we can also extract a private key let wif; try { const address = await this.getNewAddress(walletName); const dumpResult = await this.rpc.callRpc("dumpprivkey", [address], walletName); wif = dumpResult; } catch (error) { console.warn((0, terminal_1.formatWarning)(`Could not dump private key for ${walletName}:`), error); } return { wallet: walletName, xpub: xpubInfo.xpub, path: xpubInfo.path, // @ts-ignore rootFingerprint: xpubInfo.rootFingerprint, // @ts-ignore wif, }; } /** * Extract private key for address */ async dumpPrivateKey(wallet, address) { try { const key = await this.rpc.callRpc("dumpprivkey", [address], wallet); return key; } catch (error) { console.error((0, terminal_1.formatError)(`Error dumping private key for address ${address} in wallet ${wallet}:`), error); throw error; } } /** * Generate blocks to a specific address */ async generateToAddress(numBlocks, address) { try { const hashes = await this.rpc.generateToAddress(numBlocks, address); return hashes; } catch (error) { console.error((0, terminal_1.formatError)(`Error generating ${numBlocks} blocks to address ${address}:`), error); throw error; } } } exports.BitcoinService = BitcoinService;