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
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;
};
})();
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;