UNPKG

@btc-vision/bitcoin-rpc

Version:

The one and only fully typed Bitcoin RPC client for Node.js

1,665 lines (1,326 loc) 53.2 kB
import { Logger } from '@btc-vision/bsi-common'; import { AddPeerAddressParams, Blockhash, CreateWalletDescriptorParams, CreateWalletParams, EstimateSmartFeeParams, GenerateBlockParams, GenerateParams, GetBestBlockHeaderParams, GetBlockFilterParams, GetBlockHeaderParams, GetBlockParams, GetBlockStatsParams, GetBlockTemplateParams, GetChainTxStatsParams, GetDeploymentInfoParams, GetHDKeysParams, GetIndexInfoParams, GetMempoolEntryByIdParams, GetMemPoolParams, GetNodeAddressesParams, GetRawTransactionParams, GetTxOutParams, GetTxOutProofParams, Height, ImportDescriptorsParams, ListDescriptorsParams, MigrateWalletParams, PsbtBumpFeeParams, RPCClient, RPCIniOptions, SendAllParams, SendMsgToPeerParams, SendParams, SendRawTransactionParams, SimulateRawTransactionParams, SubmitPackageParams, TestmemPoolAcceptParams, TxId, UpgradeWalletParams, Verbose, VerifyChainLockParams, } from './external/rpc.js'; import { AddressByLabel } from './types/AddressByLabel.js'; import { RPCConfig } from './interfaces/RPCConfig.js'; import { BasicBlockInfo } from './types/BasicBlockInfo.js'; import { BitcoinRawTransactionParams, IRawTransaction, RawTransaction, } from './types/BitcoinRawTransaction.js'; import { BitcoinVerbosity } from './types/BitcoinVerbosity.js'; import { BlockchainInfo } from './types/BlockchainInfo.js'; import { BlockData, BlockDataWithTransactionData } from './types/BlockData.js'; import { BlockFilterInfo } from './types/BlockFilterInfo.js'; import { BlockHeaderInfo } from './types/BlockHeaderInfo.js'; import { BlockStats } from './types/BlockStats.js'; import { ChainTipInfo } from './types/ChainTipInfo.js'; import { ChainTxStats } from './types/ChainTxStats.js'; import { CreateWalletResponse } from './types/CreateWalletResponse.js'; import { FeeEstimation, SmartFeeEstimation } from './types/FeeEstimation.js'; import { MempoolInfo } from './types/MempoolInfo.js'; import { MemPoolTransactionInfo, RawMemPoolTransactionInfo, } from './types/MemPoolTransactionInfo.js'; import { TransactionOutputInfo } from './types/TransactionOutputInfo.js'; import { TransactionOutputSetInfo } from './types/TransactionOutputSetInfo.js'; import { WalletInfo } from './types/WalletInfo.js'; import { RawMempool } from './types/RawMempool.js'; import { BestBlockHeader, ChainLockVerification, DeploymentInfo, GenerateBlockResult, HDKeyInfo, ImportDescriptorsResult, IndexInfo, ListDescriptorsResult, MempoolEntryById, MigrateWalletResult, NodeAddress, PackageResult, PeerAddressResult, PsbtBumpFeeResult, SendAllResult, SendResult, SimulateTransactionResult, TestMempoolAcceptResult, UpgradeWalletResult, WalletDescriptor, } from './types/NewMethods.js'; import { BlockTemplate } from './types/BlockTemplate.js'; export class BitcoinRPC extends Logger { public readonly logColor: string = '#fa9600'; private rpc: RPCClient | null = null; private blockchainInfo: BlockchainInfo | null = null; private currentBlockInfo: BasicBlockInfo | null = null; private purgeInterval: NodeJS.Timeout | null = null; constructor( private readonly cacheClearInterval: number = 1000, private readonly enableDebug: boolean = false, ) { super(); this.purgeCachedData(); } public destroy(): void { if (this.purgeInterval) { clearInterval(this.purgeInterval); } this.rpc = null; this.blockchainInfo = null; this.currentBlockInfo = null; } public getRpcConfigFromBlockchainConfig(rpcInfo: RPCConfig): RPCIniOptions { return { url: `${rpcInfo.BITCOIND_HTTPS ? 'https' : 'http'}://${rpcInfo.BITCOIND_HOST}`, port: rpcInfo.BITCOIND_PORT, user: rpcInfo.BITCOIND_USERNAME, pass: rpcInfo.BITCOIND_PASSWORD, }; } public async getBestBlockHash(): Promise<string | null> { this.debugMessage('getBestBlockHash'); if (!this.rpc) { throw new Error('RPC not initialized'); } const bestBlockHash = (await this.rpc.getbestblockhash().catch((e: unknown) => { this.error(`Error getting best block hash: ${e}`); return; })) as string | null; return bestBlockHash || null; } public async getBlockBatch(blockHashes: string[]): Promise<BlockData[] | null> { this.debugMessage('getBlockBatch'); if (!this.rpc) { throw new Error('RPC not initialized'); } const blockData: BlockData[] | null = (await this.rpc .getblockBatch(blockHashes) .catch((e: unknown) => { this.error(`Error getting block batch: ${e}`); return null; })) as BlockData[] | null; return blockData || null; } /** * @description Returns the header of the best (tip) block in the longest blockchain. */ public async getBestBlockHeader( verbose: boolean = true, ): Promise<BestBlockHeader | string | null> { this.debugMessage('getBestBlockHeader'); if (!this.rpc) { throw new Error('RPC not initialized'); } const params: GetBestBlockHeaderParams = { verbose }; const result = await this.rpc.getbestblockheader(params).catch((e: unknown) => { this.error(`Error getting best block header: ${e}`); return null; }); return result as BestBlockHeader | string | null; } /** * @description Returns an object containing various state info regarding deployments of consensus changes. */ public async getDeploymentInfo(blockHash?: string): Promise<DeploymentInfo | null> { this.debugMessage(`getDeploymentInfo ${blockHash || 'tip'}`); if (!this.rpc) { throw new Error('RPC not initialized'); } const params: GetDeploymentInfoParams = blockHash ? { blockhash: blockHash } : {}; const result = await this.rpc.getdeploymentinfo(params).catch((e: unknown) => { this.error(`Error getting deployment info: ${e}`); return null; }); return result as DeploymentInfo | null; } /** * @description Returns mempool data for given transaction by wtxid */ public async getMempoolEntryById(wtxid: string): Promise<MempoolEntryById | null> { this.debugMessage(`getMempoolEntryById ${wtxid}`); if (!this.rpc) { throw new Error('RPC not initialized'); } const params: GetMempoolEntryByIdParams = { wtxid }; const result = await this.rpc.getmempoolentrybyid(params).catch((e: unknown) => { this.error(`Error getting mempool entry by id: ${e}`); return null; }); return result as MempoolEntryById | null; } /** * @description Mine up to nblocks blocks immediately to an address in the wallet. * @deprecated Use generateToAddress instead */ public async generate( nBlocks: number, maxTries?: number, wallet?: string, ): Promise<string[] | null> { this.debugMessage(`generate ${nBlocks} blocks`); if (!this.rpc) { throw new Error('RPC not initialized'); } const params: GenerateParams = { nblocks: nBlocks, maxtries: maxTries, }; const result = await this.rpc.generate(params, wallet).catch((e: unknown) => { this.error(`Error generating blocks: ${e}`); return null; }); return result as string[] | null; } /** * @description Mine a block with a set of ordered transactions immediately to a specified address. */ public async generateBlock( output: string, transactions: string[] = [], submit: boolean = true, ): Promise<GenerateBlockResult | null> { this.debugMessage(`generateBlock to ${output}`); if (!this.rpc) { throw new Error('RPC not initialized'); } const params: GenerateBlockParams = { output, transactions, submit, }; const result = await this.rpc.generateblock(params).catch((e: unknown) => { this.error(`Error generating block: ${e}`); return null; }); return result as GenerateBlockResult | null; } /** * @description Add the address of a potential peer to the address manager. */ public async addPeerAddress( address: string, port: number, tried: boolean = false, ): Promise<PeerAddressResult | null> { this.debugMessage(`addPeerAddress ${address}:${port}`); if (!this.rpc) { throw new Error('RPC not initialized'); } const params: AddPeerAddressParams = { address, port, tried }; const result = await this.rpc.addpeeraddress(params).catch((e: unknown) => { this.error(`Error adding peer address: ${e}`); return null; }); return result as PeerAddressResult | null; } /** * @description Return known addresses which can potentially be used to find new nodes in the network */ public async getNodeAddresses(count?: number): Promise<NodeAddress[] | null> { this.debugMessage(`getNodeAddresses ${count || 'default'}`); if (!this.rpc) { throw new Error('RPC not initialized'); } const params: GetNodeAddressesParams = count ? { count } : {}; const result = await this.rpc.getnodeaddresses(params).catch((e: unknown) => { this.error(`Error getting node addresses: ${e}`); return null; }); return result as NodeAddress[] | null; } /** * @description Send a p2p message to a peer specified by id. */ public async sendMsgToPeer(peerId: number, msgType: string, data: string): Promise<void> { this.debugMessage(`sendMsgToPeer to peer ${peerId}`); if (!this.rpc) { throw new Error('RPC not initialized'); } const params: SendMsgToPeerParams = { peer_id: peerId, msg_type: msgType, data, }; await this.rpc.sendmsgtopeer(params).catch((e: unknown) => { this.error(`Error sending message to peer: ${e}`); throw e; }); } /** * @description Submit a package of raw transactions to local node. */ public async submitPackage( packageTxs: string[], maxFeeRate?: number | string, maxBurnAmount?: number | string, ): Promise<PackageResult | null> { this.debugMessage(`submitPackage with ${packageTxs.length} transactions`); if (!this.rpc) { throw new Error('RPC not initialized'); } const params: SubmitPackageParams = { package: packageTxs, maxfeerate: maxFeeRate, maxburnamount: maxBurnAmount, }; const result = await this.rpc.submitpackage(params).catch((e: unknown) => { this.error(`Error submitting package: ${e}`); return null; }); return result as PackageResult | null; } /** * @description Returns the status of one or all available indices. */ public async getIndexInfo(indexName?: string): Promise<IndexInfo | null> { this.debugMessage(`getIndexInfo ${indexName || 'all'}`); if (!this.rpc) { throw new Error('RPC not initialized'); } const params: GetIndexInfoParams = indexName ? { index_name: indexName } : {}; const result = await this.rpc.getindexinfo(params).catch((e: unknown) => { this.error(`Error getting index info: ${e}`); return null; }); return result as IndexInfo | null; } /** * @description Verifies that a ChainLock is valid for the block. */ public async verifyChainLock( blockHash: string, signature: string, height: number, ): Promise<ChainLockVerification | null> { this.debugMessage(`verifyChainLock for block ${blockHash}`); if (!this.rpc) { throw new Error('RPC not initialized'); } const params: VerifyChainLockParams = { blockhash: blockHash, signature, height, }; const result = await this.rpc.verifychainlock(params).catch((e: unknown) => { this.error(`Error verifying chainlock: ${e}`); return null; }); return result as ChainLockVerification | null; } /** * @description Create a new descriptor for the wallet. */ public async createWalletDescriptor( type: 'wpkh' | 'pkh' | 'sh' | 'tr', internal?: boolean, hdkey?: string, wallet?: string, ): Promise<WalletDescriptor | null> { this.debugMessage(`createWalletDescriptor type: ${type}`); if (!this.rpc) { throw new Error('RPC not initialized'); } const params: CreateWalletDescriptorParams = { type, internal, hdkey, }; const result = await this.rpc.createwalletdescriptor(params, wallet).catch((e: unknown) => { this.error(`Error creating wallet descriptor: ${e}`); return null; }); return result as WalletDescriptor | null; } /** * @description List all BIP 32 HD keys in the wallet. */ public async getHDKeys( activeOnly: boolean = false, privateKeys: boolean = false, wallet?: string, ): Promise<HDKeyInfo[] | null> { this.debugMessage(`getHDKeys`); if (!this.rpc) { throw new Error('RPC not initialized'); } const params: GetHDKeysParams = { active_only: activeOnly, private: privateKeys, }; const result = await this.rpc.gethdkeys(params, wallet).catch((e: unknown) => { this.error(`Error getting HD keys: ${e}`); return null; }); return result as HDKeyInfo[] | null; } /** * @description Import descriptors to the wallet. */ public async importDescriptors( requests: ImportDescriptorsParams['requests'], wallet?: string, ): Promise<ImportDescriptorsResult | null> { this.debugMessage(`importDescriptors with ${requests.length} descriptors`); if (!this.rpc) { throw new Error('RPC not initialized'); } const params: ImportDescriptorsParams = { requests }; const result = await this.rpc.importdescriptors(params, wallet).catch((e: unknown) => { this.error(`Error importing descriptors: ${e}`); return null; }); return result as ImportDescriptorsResult | null; } /** * @description List descriptors imported into a descriptor-enabled wallet. */ public async listDescriptors( includePrivate: boolean = false, wallet?: string, ): Promise<ListDescriptorsResult | null> { this.debugMessage(`listDescriptors`); if (!this.rpc) { throw new Error('RPC not initialized'); } const params: ListDescriptorsParams = { private: includePrivate }; const result = await this.rpc.listdescriptors(params, wallet).catch((e: unknown) => { this.error(`Error listing descriptors: ${e}`); return null; }); return result as ListDescriptorsResult | null; } /** * @description Migrate a legacy wallet to a descriptor wallet. */ public async migrateWallet( walletName?: string, passphrase?: string, ): Promise<MigrateWalletResult | null> { this.debugMessage(`migrateWallet ${walletName || 'default'}`); if (!this.rpc) { throw new Error('RPC not initialized'); } const params: MigrateWalletParams = { wallet_name: walletName, passphrase, }; const result = await this.rpc.migratewallet(params).catch((e: unknown) => { this.error(`Error migrating wallet: ${e}`); return null; }); return result as MigrateWalletResult | null; } /** * @description Entirely clears and refills the keypool. */ public async newKeypool(wallet?: string): Promise<void> { this.debugMessage(`newKeypool`); if (!this.rpc) { throw new Error('RPC not initialized'); } await this.rpc.newkeypool(wallet).catch((e: unknown) => { this.error(`Error creating new keypool: ${e}`); throw e; }); } public async getBlockTemplate(params: GetBlockTemplateParams): Promise<BlockTemplate | null> { this.debugMessage('getBlockTemplate'); if (!this.rpc) { throw new Error('RPC not initialized'); } const result: BlockTemplate | null = (await this.rpc .getblocktemplate(params) .catch((e: unknown) => { this.error(`Error getting block template: ${e}`); return null; })) as BlockTemplate | null; return result || null; } public async submitBlock(blockHex: string): Promise<string | null> { this.debugMessage('submitBlock'); if (!this.rpc) { throw new Error('RPC not initialized'); } // submitblock returns null on success, or a rejection reason string on failure return (await this.rpc.submitblock({ hexdata: blockHex }).catch((e: unknown) => { this.error(`Error submitting block: ${e}`); throw e; })) as string | null; } /** * @description Bumps the fee of an opt-in-RBF transaction, replacing it with a new transaction. */ public async psbtBumpFee( txid: string, options?: PsbtBumpFeeParams['options'], wallet?: string, ): Promise<PsbtBumpFeeResult | null> { this.debugMessage(`psbtBumpFee for ${txid}`); if (!this.rpc) { throw new Error('RPC not initialized'); } const params: PsbtBumpFeeParams = { txid, options }; const result = await this.rpc.psbtbumpfee(params, wallet).catch((e: unknown) => { this.error(`Error bumping fee with PSBT: ${e}`); return null; }); return result as PsbtBumpFeeResult | null; } /** * @description Send a transaction with advanced options. */ public async send( outputs: SendParams['outputs'], options?: { confTarget?: number; estimateMode?: 'UNSET' | 'ECONOMICAL' | 'CONSERVATIVE'; feeRate?: number | string; advancedOptions?: SendParams['options']; }, wallet?: string, ): Promise<SendResult | null> { this.debugMessage(`send transaction`); if (!this.rpc) { throw new Error('RPC not initialized'); } const params: SendParams = { outputs, conf_target: options?.confTarget, estimate_mode: options?.estimateMode, fee_rate: options?.feeRate, options: options?.advancedOptions, }; const result = await this.rpc.send(params, wallet).catch((e: unknown) => { this.error(`Error sending transaction: ${e}`); return null; }); return result as SendResult | null; } /** * @description Send all outputs from the wallet. */ public async sendAll( recipients: SendAllParams['recipients'], options?: { confTarget?: number; estimateMode?: 'UNSET' | 'ECONOMICAL' | 'CONSERVATIVE'; feeRate?: number | string; advancedOptions?: SendAllParams['options']; }, wallet?: string, ): Promise<SendAllResult | null> { this.debugMessage(`sendAll transaction`); if (!this.rpc) { throw new Error('RPC not initialized'); } const params: SendAllParams = { recipients, conf_target: options?.confTarget, estimate_mode: options?.estimateMode, fee_rate: options?.feeRate, options: options?.advancedOptions, }; const result = await this.rpc.sendall(params, wallet).catch((e: unknown) => { this.error(`Error sending all: ${e}`); return null; }); return result as SendAllResult | null; } /** * @description Simulate raw transactions. */ public async simulateRawTransaction( rawTxs: string[] = [], includeWatchOnly: boolean = false, wallet?: string, ): Promise<SimulateTransactionResult | null> { this.debugMessage(`simulateRawTransaction`); if (!this.rpc) { throw new Error('RPC not initialized'); } const params: SimulateRawTransactionParams = { rawtxs: rawTxs, options: { include_watchonly: includeWatchOnly }, }; const result = await this.rpc.simulaterawtransaction(params, wallet).catch((e: unknown) => { this.error(`Error simulating transaction: ${e}`); return null; }); return result as SimulateTransactionResult | null; } /** * @description Upgrade the wallet to a newer version. */ public async upgradeWallet( version?: number, wallet?: string, ): Promise<UpgradeWalletResult | null> { this.debugMessage(`upgradeWallet to version ${version || 'latest'}`); if (!this.rpc) { throw new Error('RPC not initialized'); } const params: UpgradeWalletParams = version ? { version } : {}; const result = await this.rpc.upgradewallet(params, wallet).catch((e: unknown) => { this.error(`Error upgrading wallet: ${e}`); return null; }); return result as UpgradeWalletResult | null; } public async getBlockAsHexString(blockHash: string): Promise<string | null> { this.debugMessage('getBlockAsHexString'); if (!this.rpc) { throw new Error('RPC not initialized'); } const param: GetBlockParams = { blockhash: blockHash, verbosity: BitcoinVerbosity.NONE, }; const blockData: string | null = (await this.rpc.getblock(param).catch((e: unknown) => { this.error(`Error getting block data: ${e}`); return null; })) as string | null; return blockData == '' ? null : blockData; } public async estimateSmartFee( confTarget: number, estimateMode: FeeEstimation = FeeEstimation.CONSERVATIVE, ): Promise<SmartFeeEstimation> { this.debugMessage('estimateSmartFee'); if (!this.rpc) { throw new Error('RPC not initialized'); } const opts: EstimateSmartFeeParams = { conf_target: confTarget, estimate_mode: estimateMode, }; return (await this.rpc.estimatesmartfee(opts)) as SmartFeeEstimation; } public async joinPSBTs(psbts: string[]): Promise<string | null> { this.debugMessage('joinPSBTs'); if (!this.rpc) { throw new Error('RPC not initialized'); } const result: string | null = (await this.rpc .joinpsbts({ txs: psbts, }) .catch((e: unknown) => { this.error(`Error joining PSBTs: ${e}`); return ''; })) as string | null; return result || null; } public async getBlockInfoOnly(blockHash: string): Promise<BlockData | null> { this.debugMessage('getBlockInfoOnly'); if (!this.rpc) { throw new Error('RPC not initialized'); } const param: GetBlockParams = { blockhash: blockHash, verbosity: BitcoinVerbosity.RAW, }; const blockData: BlockData | null = (await this.rpc.getblock(param).catch((e: unknown) => { this.error(`Error getting block data: ${e}`); return null; })) as BlockData | null; return blockData || null; } public async getBlockInfoWithTransactionData( blockHash: string, ): Promise<BlockDataWithTransactionData | null> { this.debugMessage(`getBlockInfoWithTransactionData ${blockHash}`); if (!this.rpc) { throw new Error('RPC not initialized'); } const param: GetBlockParams = { blockhash: blockHash, verbosity: 2, }; const blockData: BlockDataWithTransactionData | null = (await this.rpc .getblock(param) .catch((e: unknown) => { this.error(`Error getting block data: ${e}`); return null; })) as BlockDataWithTransactionData | null; return blockData || null; } public async getBlockHashes(height: number, count: number): Promise<(string | null)[] | null> { this.debugMessage(`getBlockHashes ${height} ${count}`); if (!this.rpc) { throw new Error('RPC not initialized'); } const param: Height = { height, }; const blockHashes: (string | null)[] | null = (await this.rpc .getblockhashes(param, count) .catch((e: unknown) => { this.error(`Error getting block hashes: ${e}`); return []; })) as (string | null)[] | null; return blockHashes || null; } public async getBlocksInfoWithTransactionData( blockHashes: string[], ): Promise<(BlockDataWithTransactionData | null)[] | null> { if (!this.rpc) { throw new Error('RPC not initialized'); } const blockData: (BlockDataWithTransactionData | null)[] | null = (await this.rpc .getblockBatch(blockHashes, 2) .catch((e: unknown) => { this.error(`Error getting block data: ${e}`); return null; })) as unknown as (BlockDataWithTransactionData | null)[]; return blockData || null; } public async getBlockCount(): Promise<number | null> { this.debugMessage('getBlockCount'); if (!this.rpc) { throw new Error('RPC not initialized'); } const blockCount: number | null = (await this.rpc.getblockcount().catch((e: unknown) => { this.error(`Error getting block count: ${e}`); return 0; })) as number | null; return blockCount ?? null; } public async getBlockFilter( blockHash: string, filterType?: string, ): Promise<BlockFilterInfo | null> { this.debugMessage(`getBlockFilter ${blockHash}`); if (!this.rpc) { throw new Error('RPC not initialized'); } const param: GetBlockFilterParams = { blockhash: blockHash, filtertype: filterType, }; const result: BlockFilterInfo | null = (await this.rpc .getblockfilter(param) .catch((e: unknown) => { this.error(`Error getting block filter: ${e}`); return null; })) as BlockFilterInfo | null; return result || null; } public async getBlockHash(height: number): Promise<string | null> { this.debugMessage(`getBlockHash ${height}`); if (!this.rpc) { throw new Error('RPC not initialized'); } const param: Height = { height: height, }; const result: string | null = (await this.rpc.getblockhash(param).catch((e: unknown) => { this.error(`Error getting block hash: ${e}`); return ''; })) as string | null; return result || null; } public async getChainInfo(): Promise<BlockchainInfo | null> { this.debugMessage('getChainInfo'); if (!this.rpc) { throw new Error('RPC not initialized'); } this.blockchainInfo = (await this.rpc.getblockchaininfo()) as BlockchainInfo | null; if (this.blockchainInfo) { this.currentBlockInfo = { blockHeight: this.blockchainInfo.blocks, blockHash: this.blockchainInfo.bestblockhash, }; } return this.blockchainInfo; } public async getWalletInfo(walletName: string): Promise<WalletInfo | null> { this.debugMessage(`getWalletInfo ${walletName}`); if (!this.rpc) { throw new Error('RPC not initialized'); } const walletInfo: WalletInfo | null = (await this.rpc .getwalletinfo(walletName) .catch((e: unknown) => { this.error(`Error getting wallet info: ${e}`); return null; })) as WalletInfo | null; return walletInfo || null; } public async createWallet(params: CreateWalletParams): Promise<CreateWalletResponse | null> { this.debugMessage('createWallet'); if (!this.rpc) { throw new Error('RPC not initialized'); } const wallet: CreateWalletResponse | null = (await this.rpc .createwallet(params) .catch((e: unknown) => { this.error(`Error creating wallet: ${e}`); return ''; })) as CreateWalletResponse | null; return wallet || null; } public async loadWallet(filename: string): Promise<CreateWalletResponse | null> { this.debugMessage(`loadWallet ${filename}`); if (!this.rpc) { throw new Error('RPC not initialized'); } const params: { filename: string } = { filename, }; const wallet: CreateWalletResponse | null = (await this.rpc .loadwallet(params) .catch((e: unknown) => { this.error(`Error loading wallet: ${e}`); return ''; })) as CreateWalletResponse | null; return wallet || null; } public async listWallets(): Promise<string[] | null> { this.debugMessage('listWallets'); if (!this.rpc) { throw new Error('RPC not initialized'); } const wallets: string[] | null = (await this.rpc.listwallets().catch((e: unknown) => { this.error(`Error listing wallets: ${e}`); return []; })) as string[] | null; return wallets || null; } public async getNewAddress(label: string, wallet?: string): Promise<string | null> { this.debugMessage(`getNewAddress ${label}`); if (!this.rpc) { throw new Error('RPC not initialized'); } const params: { label: string } = { label, }; const address: string | null = (await this.rpc .getnewaddress(params, wallet) .catch((e: unknown) => { this.error(`Error getting new address: ${e}`); return ''; })) as string | null; return address || null; } public async generateToAddress( nBlock: number, address: string, wallet?: string, ): Promise<string[] | null> { this.debugMessage(`generateToAddress ${nBlock} ${address}`); if (!this.rpc) { throw new Error('RPC not initialized'); } const params: { nblocks: number; address: string } = { nblocks: nBlock, address, }; const blockHashes: string[] | null = (await this.rpc .generatetoaddress(params, wallet) .catch((e: unknown) => { this.error(`Error generating to address: ${e}`); return []; })) as string[] | null; return blockHashes || null; } public async importPrivateKey( privateKey: string, label: string, rescan?: boolean, wallet?: string, ): Promise<void> { this.debugMessage(`importPrivateKey ${label}`); if (!this.rpc) { throw new Error('RPC not initialized'); } const params: { privkey: string; label: string; rescan?: boolean } = { privkey: privateKey, label, rescan, }; await this.rpc.importprivkey(params, wallet).catch((e: unknown) => { this.error(`Error importing private key: ${e}`); }); } public async invalidateBlock(blockHash: string): Promise<void> { this.debugMessage(`invalidateBlock ${blockHash}`); if (!this.rpc) { throw new Error('RPC not initialized'); } const param = { blockhash: blockHash, }; await this.rpc.invalidateblock(param).catch((e: unknown) => { this.error(`Error invalidating block: ${e}`); throw e; }); } public async reconsiderBlock(blockHash: string): Promise<void> { this.debugMessage(`reconsiderBlock ${blockHash}`); if (!this.rpc) { throw new Error('RPC not initialized'); } const param = { blockhash: blockHash, }; await this.rpc.reconsiderblock(param).catch((e: unknown) => { this.error(`Error reconsidering block: ${e}`); throw e; }); } public async getAddressByLabel(label: string, wallet?: string): Promise<AddressByLabel | null> { this.debugMessage(`getAddressByLabel ${label}`); if (!this.rpc) { throw new Error('RPC not initialized'); } const params: { label: string } = { label, }; const address: AddressByLabel | null = (await this.rpc .getaddressesbylabel(params, wallet) .catch((e: unknown) => { this.error(`Error getting address by label: ${e}`); throw e; })) as AddressByLabel | null; return address || null; } public async sendRawTransaction(params: SendRawTransactionParams): Promise<string | null> { this.debugMessage('sendRawTransaction'); if (!this.rpc) { throw new Error('RPC not initialized'); } const txId: string | null = (await this.rpc .sendrawtransaction(params) .catch((e: unknown) => { throw e; })) as string | null; return txId || null; } /** * @description Returns result of mempool acceptance tests indicating if raw transaction(s) would be accepted by mempool. */ public async testMempoolAccept( rawtxs: string[], maxfeerate?: number | string, ): Promise<TestMempoolAcceptResult[]> { this.debugMessage(`testMempoolAccept with ${rawtxs.length} transactions`); if (!this.rpc) { throw new Error('RPC not initialized'); } const params: TestmemPoolAcceptParams = { rawtxs, maxfeerate, }; const result = await this.rpc.testmempoolaccept(params).catch((e: unknown) => { this.error(`Error testing mempool accept: ${e}`); throw e; }); return result as TestMempoolAcceptResult[]; } public async decodeRawTransaction( hexString: string, isWitness: boolean = false, ): Promise<IRawTransaction | null> { this.debugMessage(`decodeRawTransaction ${hexString}`); if (!this.rpc) { throw new Error('RPC not initialized'); } const params: { hexstring: string; iswitness?: boolean } = { hexstring: hexString, iswitness: isWitness, }; const rawTx: IRawTransaction | null = (await this.rpc .decoderawtransaction(params) .catch((e: unknown) => { this.error(`Error decoding raw transaction: ${e}`); return null; })) as IRawTransaction | null; return rawTx || null; } public async dumpPrivateKey(address: string, wallet?: string): Promise<string | null> { this.debugMessage(`dumpPrivateKey ${address}`); if (!this.rpc) { throw new Error('RPC not initialized'); } const privateKey: string | null = (await this.rpc .dumpprivkey( { address, }, wallet, ) .catch((e: unknown) => { this.error(`Error dumping private key: ${e}`); return ''; })) as string | null; return privateKey || null; } public async getRawTransaction<V extends BitcoinVerbosity>( parameters: BitcoinRawTransactionParams, ): Promise<RawTransaction<V> | null> { this.debugMessage(`getRawTransaction ${parameters.txId}`); if (!this.rpc) { throw new Error('RPC not initialized'); } const params: GetRawTransactionParams = { txid: parameters.txId, verbose: parameters.verbose !== BitcoinVerbosity.RAW, }; if (parameters.blockHash) { params.blockhash = parameters.blockHash; } const rawTx: RawTransaction<V> | null = (await this.rpc .getrawtransaction(params) .catch((e: unknown) => { this.error(`Error getting raw transaction: ${e}`); return null; })) as RawTransaction<V> | null; return rawTx || null; } public async getRawTransactions<V extends BitcoinVerbosity>( txs: string[], verbose?: V, ): Promise<(RawTransaction<V> | null)[] | null> { this.debugMessage(`getRawTransactions ${txs}`); if (!this.rpc) { throw new Error('RPC not initialized'); } const txsInfo: (RawTransaction<V> | null)[] | null = (await this.rpc .getrawtransactionBatch(txs, verbose !== BitcoinVerbosity.RAW) .catch((e: unknown) => { this.error(`Error getting raw transactions: ${e}`); return null; })) as (RawTransaction<V> | null)[]; return txsInfo || null; } public async getBlockHeight(): Promise<BasicBlockInfo | null> { this.debugMessage('getBlockHeight'); if (!this.rpc) { throw new Error('RPC not initialized'); } if (!this.currentBlockInfo) { await this.getChainInfo(); } return this.currentBlockInfo; } public async getBlockHeader( blockHash: string, verbose?: boolean, ): Promise<BlockHeaderInfo | null> { this.debugMessage(`getBlockHeader ${blockHash} ${verbose}`); if (!this.rpc) { throw new Error('RPC not initialized'); } const param: GetBlockHeaderParams = { blockhash: blockHash, verbose: verbose, }; const header: BlockHeaderInfo | null = (await this.rpc .getblockheader(param) .catch((e: unknown) => { this.error(`Error getting block header: ${e}`); return ''; })) as BlockHeaderInfo | null; return header || null; } public async getBlockStatsByHeight( height: number, stats?: string[], ): Promise<BlockStats | null> { this.debugMessage(`getBlockStatsByHeight ${height} ${stats}`); if (!this.rpc) { throw new Error('RPC not initialized'); } const param: GetBlockStatsParams = { hash_or_height: height, stats: stats, }; const blockStats: BlockStats | null = (await this.rpc .getblockstats(param) .catch((e: unknown) => { this.error(`Error getting block stats: ${e}`); return null; })) as BlockStats | null; return blockStats || null; } public async getBlockStatsByHash( blockHash: string, stats?: string[], ): Promise<BlockStats | null> { this.debugMessage(`getBlockStatsByHash ${blockHash} ${stats}`); if (!this.rpc) { throw new Error('RPC not initialized'); } const param: GetBlockStatsParams = { hash_or_height: blockHash, stats: stats, }; const blockStats: BlockStats | null = (await this.rpc .getblockstats(param) .catch((e: unknown) => { this.error(`Error getting block stats: ${e}`); return null; })) as BlockStats | null; return blockStats || null; } public async getChainTips(): Promise<ChainTipInfo[] | null> { this.debugMessage('getChainTips'); if (!this.rpc) { throw new Error('RPC not initialized'); } const tips: ChainTipInfo[] | null = (await this.rpc.getchaintips().catch((e: unknown) => { this.error(`Error getting chain tips: ${e}`); return null; })) as ChainTipInfo[] | null; return tips || null; } public async getChainTxStats(param: GetChainTxStatsParams): Promise<ChainTxStats | null> { this.debugMessage('getChainTxStats'); if (!this.rpc) { throw new Error('RPC not initialized'); } const chainTxStats: ChainTxStats | null = (await this.rpc .getchaintxstats(param) .catch((e: unknown) => { this.error(`Error getting chain tx stats: ${e}`); return null; })) as ChainTxStats | null; return chainTxStats || null; } public async getDifficulty(): Promise<number | null> { this.debugMessage('getDifficulty'); if (!this.rpc) { throw new Error('RPC not initialized'); } const difficulty: number | null = (await this.rpc.getdifficulty().catch((e: unknown) => { this.error(`Error getting difficulty: ${e}`); return 0; })) as number | null; return difficulty ?? null; } public async getMempoolAncestors<V extends BitcoinVerbosity>( txId: string, verbose?: V, ): Promise<MemPoolTransactionInfo<V> | null> { this.debugMessage(`getMempoolAncestors ${txId}`); if (!this.rpc) { throw new Error('RPC not initialized'); } const param: GetMemPoolParams = { txid: txId, verbose: verbose !== BitcoinVerbosity.RAW, }; const transactionInfo: MemPoolTransactionInfo<V> | null = (await this.rpc .getmempoolancestors(param) .catch((e: unknown) => { this.error(`Error getting mempool ancestors: ${e}`); return null; })) as MemPoolTransactionInfo<V> | null; return transactionInfo || null; } public async getMempoolDescendants<V extends BitcoinVerbosity>( txid: string, verbose?: V, ): Promise<MemPoolTransactionInfo<V> | null> { this.debugMessage(`getMempoolDescendants ${txid}`); if (!this.rpc) { throw new Error('RPC not initialized'); } const param: GetMemPoolParams = { txid: txid, verbose: verbose !== BitcoinVerbosity.RAW, }; const transactionInfo: MemPoolTransactionInfo<V> | null = (await this.rpc .getmempooldescendants(param) .catch((e: unknown) => { this.error(`Error getting mempool descendants: ${e}`); return null; })) as MemPoolTransactionInfo<V> | null; return transactionInfo || null; } public async getMempoolEntry(txid: string): Promise<RawMemPoolTransactionInfo | null> { this.debugMessage(`getMempoolEntry ${txid}`); if (!this.rpc) { throw new Error('RPC not initialized'); } const param: TxId = { txid: txid, }; const transactionInfo: RawMemPoolTransactionInfo | null = (await this.rpc .getmempoolentry(param) .catch((e: unknown) => { this.error(`Error getting mempool entry: ${e}`); return null; })) as RawMemPoolTransactionInfo | null; return transactionInfo || null; } public async getMempoolInfo(): Promise<MempoolInfo<BitcoinVerbosity.NONE> | null> { this.debugMessage('getMempoolInfo'); if (!this.rpc) { throw new Error('RPC not initialized'); } const mempoolInfo: MempoolInfo<BitcoinVerbosity.NONE> | null = (await this.rpc .getmempoolinfo() .catch((e: unknown) => { this.error(`Error getting mempool info: ${e}`); return null; })) as MempoolInfo<BitcoinVerbosity.NONE> | null; return mempoolInfo || null; } public async getRawMempool<V extends BitcoinVerbosity>( verbose?: V, ): Promise<RawMempool<V> | null> { this.debugMessage('getRawMempool'); if (!this.rpc) { throw new Error('RPC not initialized'); } const param: Verbose = { verbose: verbose !== BitcoinVerbosity.RAW, }; const mempoolInfo: RawMempool<V> | null = (await this.rpc .getrawmempool(param) .catch((e: unknown) => { this.error(`Error getting raw mempool: ${e}`); return null; })) as RawMempool<V> | null; return mempoolInfo || null; } public async getTxOut( txid: string, voutNumber: number, includeMempool?: boolean, ): Promise<TransactionOutputInfo | null> { this.debugMessage(`getTxOut ${txid} ${voutNumber}`); if (!this.rpc) { throw new Error('RPC not initialized'); } const param: GetTxOutParams = { n: voutNumber, txid: txid, include_mempool: includeMempool, }; const txOuputInfo: TransactionOutputInfo | null = (await this.rpc .gettxout(param) .catch((e: unknown) => { this.error(`Error getting tx out: ${e}`); return null; })) as TransactionOutputInfo | null; return txOuputInfo || null; } public async getTxOutProof(txids: string[], blockHash?: string): Promise<string | null> { this.debugMessage(`getTxOutProof ${txids} ${blockHash}`); if (!this.rpc) { throw new Error('RPC not initialized'); } const param: GetTxOutProofParams = { txids: txids, blockhash: blockHash, }; const txOuputProof: string | null = (await this.rpc .gettxoutproof(param) .catch((e: unknown) => { this.error(`Error getting tx out proof: ${e}`); return ''; })) as string | null; return txOuputProof || null; } public async getTxOutSetInfo(): Promise<TransactionOutputSetInfo | null> { this.debugMessage('getTxOutSetInfo'); if (!this.rpc) { throw new Error('RPC not initialized'); } const txOuputSetInfo: TransactionOutputSetInfo | null = (await this.rpc .gettxoutsetinfo() .catch((e: unknown) => { this.error(`Error getting tx out set info: ${e}`); return null; })) as TransactionOutputSetInfo | null; return txOuputSetInfo || null; } public async preciousBlock(blockHash: string): Promise<void> { this.debugMessage(`preciousBlock ${blockHash}`); if (!this.rpc) { throw new Error('RPC not initialized'); } const param: Blockhash = { blockhash: blockHash, }; await this.rpc.preciousblock(param).catch((e: unknown) => { this.error(`Error precious block: ${e}`); }); } public async pruneBlockChain(height: number): Promise<number | null> { this.debugMessage(`pruneBlockChain ${height}`); if (!this.rpc) { throw new Error('RPC not initialized'); } const param: Height = { height: height, }; const prunedHeight: number | null = (await this.rpc .pruneblockchain(param) .catch((e: unknown) => { this.error(`Error pruning blockchain: ${e}`); return 0; })) as number | null; return prunedHeight ?? null; } public async saveMempool(): Promise<void> { this.debugMessage('saveMempool'); if (!this.rpc) { throw new Error('RPC not initialized'); } await this.rpc.savemempool().catch((e: unknown) => { this.error(`Error saving mempool: ${e}`); }); } public async verifyChain(checkLevel?: number, nblocks?: number): Promise<boolean | null> { this.debugMessage(`verifyChain ${checkLevel} ${nblocks}`); if (!this.rpc) { throw new Error('RPC not initialized'); } const param: { checklevel?: number; nblocks?: number } = {