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

466 lines (465 loc) 21.8 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 __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.BitcoinRpcClient = void 0; const axios_1 = __importDefault(require("axios")); const fs = __importStar(require("fs-extra")); const child_process_1 = require("child_process"); const chalk_1 = __importDefault(require("chalk")); const path_1 = __importDefault(require("path")); /** * Client for communicating with the Bitcoin Core via RPC */ class BitcoinRpcClient { constructor(config) { this.config = config; this.baseUrl = `${config.protocol}://${config.host}:${config.port}`; this.auth = { username: config.user, password: config.pass }; } /** * Call a Bitcoin Core RPC method (Inspired from `@caravan/clients` bitcoind method) */ /** * Call a Bitcoin Core RPC method */ async callRpc(method, params = [], wallet) { try { const url = wallet ? `${this.baseUrl}/wallet/${wallet}` : this.baseUrl; const requestConfig = { method: "post", url, auth: this.auth, data: { jsonrpc: "1.0", id: "caravanregtest", method, params, }, timeout: 10000, // Increased timeout }; const response = await (0, axios_1.default)(requestConfig); if (response.data.error) { throw new Error(JSON.stringify(response.data.error)); } return response.data.result; } catch (error) { // More descriptive error message if (error.code === "ECONNREFUSED") { throw new Error(`Connection refused - Bitcoin Core not running at ${this.baseUrl}\n` + `Check your configuration and ensure Bitcoin Core is accessible.`); } else if (error.response && error.response.status === 401) { throw new Error(`Authentication failed - Check RPC username (${this.auth.username}) and password`); } else if (error.response && error.response.status === 502) { throw new Error(`Bad Gateway (502) - Proxy cannot reach Bitcoin Core\n` + `If using Docker mode, ensure containers are running:\n` + ` docker ps | grep caravan-x`); } else if (error.message.includes("timeout")) { throw new Error(`Connection timed out - Bitcoin Core not responding at ${this.baseUrl}`); } // Log the error for debugging console.error(chalk_1.default.red(`RPC call failed for method: ${method}`), error.message); // Try fallback method ONLY if dataDir exists and is accessible if (this.config.dataDir && fs.existsSync(this.config.dataDir)) { try { const cliCommand = `${method} ${params.map((p) => JSON.stringify(p)).join(" ")}`; const result = this.executeCliCommand(cliCommand, wallet); return JSON.parse(result); } catch (cliError) { // Fallback also failed throw new Error(`RPC Error: ${error.message}\n` + `Fallback CLI also failed: ${cliError.message}`); } } // No fallback available throw new Error(error.message); } } /** * Alternative method that uses bitcoin-cli command */ executeCliCommand(command, wallet) { try { // Check if dataDir is accessible if (!fs.existsSync(this.config.dataDir)) { throw new Error(`Data directory not accessible: ${this.config.dataDir}`); } // Check if bitcoin.conf exists const bitcoinConfPath = path_1.default.join(this.config.dataDir, "bitcoin.conf"); if (!fs.existsSync(bitcoinConfPath)) { throw new Error(`bitcoin.conf not found at: ${bitcoinConfPath}`); } // Build bitcoin-cli command let cliCmd = `bitcoin-cli -conf="${bitcoinConfPath}" -datadir="${this.config.dataDir}" -regtest`; if (wallet) { cliCmd += ` -rpcwallet=${wallet}`; } cliCmd += ` ${command}`; // Execute the command const result = (0, child_process_1.execSync)(cliCmd, { encoding: "utf8", timeout: 10000, }); return result.trim(); } catch (error) { throw new Error(`CLI command failed: ${error.message}`); } } // ======== Wallet-related methods ======= /** * Lists all currently loaded wallets. * * This method calls the Bitcoin Core RPC method `listwallets`, which returns an array * of wallet names that are currently loaded in the Bitcoin Core node. * * @returns {Promise<string[]>} A promise that resolves to an array of wallet names. * * @see https://developer.bitcoin.org/reference/rpc/listwallets.html */ async listWallets() { return this.callRpc("listwallets"); } /** * Creates a new wallet. * * This method calls the Bitcoin Core RPC method `createwallet` with the following parameters: * - `name`: The unique name for the new wallet. * - `disablePrivateKeys`: If true, the wallet will be created without private keys (watch-only). * - `blank`: If true, creates a blank wallet without an HD seed. * * Additional fixed parameters used in this call: * - Passphrase is set to an empty string. * - `avoid_reuse` is set to false. * - `descriptors` is set to true. * - `load_on_startup` is set to true. * * @param {string} name - The name of the wallet to be created. * @param {boolean} [disablePrivateKeys=false] - Whether to disable private key generation. * @param {boolean} [blank=false] - Whether to create a blank wallet. * @returns {Promise<any>} A promise that resolves to the wallet creation result. * * @see https://developer.bitcoin.org/reference/rpc/createwallet.html */ async createWallet(name, disablePrivateKeys = false, blank = false) { return this.callRpc("createwallet", [ name, disablePrivateKeys, blank, "", false, true, true, ]); } /** * Retrieves information about the specified wallet. * * This method calls the Bitcoin Core RPC method `getwalletinfo`, which returns an object * containing various wallet details such as balance, transaction count, and more. * * @param {string} wallet - The name of the wallet. * @returns {Promise<any>} A promise that resolves to an object containing wallet information. * * @see https://developer.bitcoin.org/reference/rpc/getwalletinfo.html */ async getWalletInfo(wallet) { return this.callRpc("getwalletinfo", [], wallet); } /** * Generates a new Bitcoin address for receiving funds. * * This method calls the Bitcoin Core RPC method `getnewaddress` with optional parameters: * - `label`: An optional label for the new address. * - `addressType`: The type of Bitcoin address to generate (default is `bech32`). * * @param {string} wallet - The wallet for which to generate the new address. * @param {string} [label=''] - An optional label for the address. * @param {string} [addressType='bech32'] - The type of address to generate (e.g., 'bech32', 'p2sh-segwit'). * @returns {Promise<string>} A promise that resolves to the newly generated Bitcoin address. * * @see https://developer.bitcoin.org/reference/rpc/getnewaddress.html */ async getNewAddress(wallet, label = "", addressType = "bech32") { return this.callRpc("getnewaddress", [label, addressType], wallet); } /** * Retrieves detailed information about a specific Bitcoin address. * * This method calls the Bitcoin Core RPC method `getaddressinfo`, which provides details * about the address (such as its script type, whether it is part of the wallet, etc.). * * @param {string} wallet - The wallet context to use. * @param {string} address - The Bitcoin address to query. * @returns {Promise<any>} A promise that resolves to an object containing address details. * * @see https://developer.bitcoin.org/reference/rpc/getaddressinfo.html */ async getAddressInfo(wallet, address) { return this.callRpc("getaddressinfo", [address], wallet); } /** * Lists unspent transaction outputs (UTXOs) for the specified wallet. * * This method calls the Bitcoin Core RPC method `listunspent` with the following parameters: * - `minConf`: The minimum number of confirmations required for UTXOs (default is 0). * - `maxConf`: The maximum number of confirmations allowed (default is 9999999). * - `addresses`: (Optional) A list of Bitcoin addresses to filter the UTXOs. * * @param {string} wallet - The wallet from which to list UTXOs. * @param {number} [minConf=0] - The minimum confirmations a UTXO must have. * @param {number} [maxConf=9999999] - The maximum confirmations a UTXO can have. * @param {string[]} [addresses=[]] - An optional array of addresses to filter by. * @returns {Promise<any[]>} A promise that resolves to an array of UTXO objects. * * @see https://developer.bitcoin.org/reference/rpc/listunspent.html */ async listUnspent(wallet, minConf = 0, maxConf = 9999999, addresses = []) { return this.callRpc("listunspent", [minConf, maxConf, addresses], wallet); } /** * Retrieves detailed information about a specific transaction. * * This method calls the Bitcoin Core RPC method `gettransaction`, which returns details * about the specified transaction including confirmations, fee, and transaction details. * * @param {string} wallet - The wallet context to use for the transaction. * @param {string} txid - The transaction ID (txid) of the transaction to retrieve. * @returns {Promise<any>} A promise that resolves to an object containing transaction details. * * @see https://developer.bitcoin.org/reference/rpc/gettransaction.html */ async getTransaction(wallet, txid) { return this.callRpc("gettransaction", [txid], wallet); } /** * Sends an amount to a given address. * * This method calls the Bitcoin Core RPC method `sendtoaddress`, which creates a transaction * sending the specified amount to the given address and broadcasts it to the network. * * @param {string} wallet - The wallet to send from. * @param {string} address - The Bitcoin address to send to. * @param {number} amount - The amount in BTC to send. * @param {string} [comment=''] - Optional comment for the transaction. * @param {string} [commentTo=''] - Optional comment about the recipient. * @returns {Promise<string>} A promise that resolves to the transaction ID (txid). * * @see https://developer.bitcoin.org/reference/rpc/sendtoaddress.html */ async sendToAddress(wallet, address, amount, comment = "", commentTo = "") { return this.callRpc("sendtoaddress", [address, amount, comment, commentTo], wallet); } /** * Imports one or more descriptors into the specified wallet. * * This method calls the Bitcoin Core RPC method `importdescriptors`, which takes an array * of descriptor objects. Descriptors define how addresses are derived (e.g., via public keys, scripts). * * Each descriptor object typically contains: * - `desc`: The descriptor string. * - `active`: A boolean indicating whether the descriptor is active. * - `range`: The range of keys to scan (if applicable). * - Additional keys as defined by Bitcoin Core documentation. * * @param {string} wallet - The wallet into which descriptors will be imported. * @param {any[]} descriptors - An array of descriptor objects to be imported. * @returns {Promise<any>} A promise that resolves to the result of the import operation. * * @see https://developer.bitcoin.org/reference/rpc/importdescriptors.html */ async importDescriptors(wallet, descriptors) { try { return await this.callRpc("importdescriptors", [descriptors], wallet); } catch (error) { if (error.message.includes("Method not found")) { console.error(chalk_1.default.yellow("importdescriptors method not supported. Your Bitcoin Core may be outdated.")); throw new Error("importdescriptors method not supported. Please upgrade your Bitcoin Core to use multisig wallets."); } throw error; } } // ========== Transaction-related methods =========== /** * Creates a Partially Signed Bitcoin Transaction (PSBT) using the wallet's funds. * * This method calls the Bitcoin Core RPC method `walletcreatefundedpsbt` with the following parameters: * - An empty array for inputs, letting Bitcoin Core automatically select UTXOs. * - An array of output objects where each key is a Bitcoin address and each value is the amount in BTC. * - A fee rate of 0 (which allows Bitcoin Core to estimate the fee). * - An options object with `includeWatching` set to true, enabling the inclusion of watch-only addresses. * * @param {string} wallet - The wallet to use for creating the PSBT. * @param {Record<string, number>[]} outputs - An array of objects mapping addresses to BTC amounts. * @returns {Promise<any>} A promise that resolves to the created PSBT object. * * @see https://developer.bitcoin.org/reference/rpc/walletcreatefundedpsbt.html */ async createPSBT(wallet, outputs) { return this.callRpc("walletcreatefundedpsbt", [[], outputs, 0, { includeWatching: true }], wallet); } /** * Processes a PSBT by attempting to sign it with the keys available in the wallet. * * This method calls the Bitcoin Core RPC method `walletprocesspsbt` which signs as many inputs as possible. * * @param {string} wallet - The wallet to use for processing the PSBT. * @param {string} psbtBase64 - The base64-encoded PSBT string. * @returns {Promise<any>} A promise that resolves to the processed PSBT object, possibly with partial signatures. * * @see https://developer.bitcoin.org/reference/rpc/walletprocesspsbt.html */ async processPSBT(wallet, psbtBase64) { return this.callRpc("walletprocesspsbt", [psbtBase64], wallet); } /** * Decodes a base64-encoded PSBT. * * This method calls the Bitcoin Core RPC method `decodepsbt` which returns a detailed JSON representation * of the PSBT, including information about inputs, outputs, and any associated signatures. * * @param {string} psbtBase64 - The base64-encoded PSBT string to decode. * @returns {Promise<any>} A promise that resolves to the decoded PSBT details. * * @see https://developer.bitcoin.org/reference/rpc/decodepsbt.html */ async decodePSBT(psbtBase64) { return this.callRpc("decodepsbt", [psbtBase64]); } /** * Finalizes a PSBT by completing all available signatures and assembling the final transaction. * * This method calls the Bitcoin Core RPC method `finalizepsbt`, which finalizes the PSBT. The response may * include the complete raw transaction ready for broadcasting if all required signatures are present. * * @param {string} psbtBase64 - The base64-encoded PSBT string to finalize. * @returns {Promise<any>} A promise that resolves to an object containing the finalized transaction details. * * @see https://developer.bitcoin.org/reference/rpc/finalizepsbt.html */ async finalizePSBT(psbtBase64) { return this.callRpc("finalizepsbt", [psbtBase64]); } /** * Broadcasts a raw transaction to the Bitcoin network. * * This method calls the Bitcoin Core RPC method `sendrawtransaction`, which takes a hexadecimal string of * the serialized transaction and broadcasts it to the network. * * @param {string} hexstring - The raw transaction in hexadecimal format. * @returns {Promise<string>} A promise that resolves to the transaction ID (txid) of the broadcast transaction. * * @see https://developer.bitcoin.org/reference/rpc/sendrawtransaction.html */ async sendRawTransaction(hexstring) { return this.callRpc("sendrawtransaction", [hexstring]); } // ========== Blockchain methods =========== /** * Retrieves general information about the current state of the blockchain. * * This method calls the Bitcoin Core RPC method `getblockchaininfo`, which provides data such as the * current chain, network, number of blocks, and verification progress. * * @returns {Promise<any>} A promise that resolves to an object containing blockchain information. * * @see https://developer.bitcoin.org/reference/rpc/getblockchaininfo.html */ async getBlockchainInfo() { return this.callRpc("getblockchaininfo"); } /** * Mines a specified number of blocks and sends the block rewards to a given address. * * This method calls the Bitcoin Core RPC method `generatetoaddress`, which generates new blocks and * directs the coinbase rewards to the specified address. * * @param {number} blocks - The number of blocks to generate. * @param {string} address - The Bitcoin address to receive the mining rewards. * @returns {Promise<string[]>} A promise that resolves to an array of block hashes for the mined blocks. * * @see https://developer.bitcoin.org/reference/rpc/generatetoaddress.html */ async generateToAddress(blocks, address) { return this.callRpc("generatetoaddress", [blocks, address]); } /** * Estimates the fee rate (in BTC/kB) required for a transaction to be confirmed within a specified number of blocks. * * This method calls the Bitcoin Core RPC method `estimatesmartfee`. The fee estimate helps users determine an * appropriate fee to use to have their transaction confirmed within the target number of blocks. * * @param {number} [blocks=6] - The target number of blocks within which the transaction should be confirmed. * @returns {Promise<any>} A promise that resolves to an object containing the estimated fee rate and additional details. * * @see https://developer.bitcoin.org/reference/rpc/estimatesmartfee.html */ async estimateSmartFee(blocks = 6) { return this.callRpc("estimatesmartfee", [blocks]); } /** * Retrieves detailed information about a specific block. * * This method calls the Bitcoin Core RPC method `getblock`, which returns data such as the block's size, * transactions, and header information. The verbosity parameter controls how much detail is returned: * - A verbosity of 1 returns a JSON object with basic information. * - Higher verbosity levels return more detailed data. * * @param {string} blockhash - The hash of the block to retrieve. * @param {number} [verbosity=1] - The verbosity level (default is 1). * @returns {Promise<any>} A promise that resolves to an object containing block details. * * @see https://developer.bitcoin.org/reference/rpc/getblock.html */ async getBlock(blockhash, verbosity = 1) { return this.callRpc("getblock", [blockhash, verbosity]); } // Utility methods isWalletAddressNotFoundError(error) { return (error.response && error.response.data && error.response.data.error && error.response.data.error.code === -4); } } exports.BitcoinRpcClient = BitcoinRpcClient;