UNPKG

@xchainjs/xchain-bitcoin

Version:

Custom Bitcoin client and utilities used by XChainJS clients

782 lines (736 loc) 33.1 kB
'use strict'; var ecc = require('@bitcoin-js/tiny-secp256k1-asmjs'); var xchainClient = require('@xchainjs/xchain-client'); var xchainCrypto = require('@xchainjs/xchain-crypto'); var bip32 = require('bip32'); var Bitcoin = require('bitcoinjs-lib'); var ecpair = require('ecpair'); var xchainUtxo = require('@xchainjs/xchain-utxo'); var xchainUtil = require('@xchainjs/xchain-util'); var xchainUtxoProviders = require('@xchainjs/xchain-utxo-providers'); var AppBtc = require('@ledgerhq/hw-app-btc'); function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; } function _interopNamespace(e) { if (e && e.__esModule) return e; var n = Object.create(null); if (e) { Object.keys(e).forEach(function (k) { if (k !== 'default') { var d = Object.getOwnPropertyDescriptor(e, k); Object.defineProperty(n, k, d.get ? d : { enumerable: true, get: function () { return e[k]; } }); } }); } n.default = e; return Object.freeze(n); } var ecc__namespace = /*#__PURE__*/_interopNamespace(ecc); var Bitcoin__namespace = /*#__PURE__*/_interopNamespace(Bitcoin); var AppBtc__default = /*#__PURE__*/_interopDefault(AppBtc); exports.AddressFormat = void 0; (function (AddressFormat) { AddressFormat[AddressFormat["P2WPKH"] = 0] = "P2WPKH"; AddressFormat[AddressFormat["P2TR"] = 1] = "P2TR"; })(exports.AddressFormat || (exports.AddressFormat = {})); /****************************************************************************** Copyright (c) Microsoft Corporation. Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. ***************************************************************************** */ /* global Reflect, Promise, SuppressedError, Symbol, Iterator */ function __awaiter(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()); }); } typeof SuppressedError === "function" ? SuppressedError : function (error, suppressed, message) { var e = new Error(message); return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e; }; function getDefaultExportFromCjs (x) { return x && x.__esModule && Object.prototype.hasOwnProperty.call(x, 'default') ? x['default'] : x; } var utils; var hasRequiredUtils; function requireUtils () { if (hasRequiredUtils) return utils; hasRequiredUtils = 1; // baseline estimates, used to improve performance var TX_EMPTY_SIZE = 4 + 1 + 1 + 4; var TX_INPUT_BASE = 32 + 4 + 1 + 4; var TX_INPUT_PUBKEYHASH = 107; var TX_OUTPUT_BASE = 8 + 1; var TX_OUTPUT_PUBKEYHASH = 25; function inputBytes (input) { return TX_INPUT_BASE + (input.script ? input.script.length : TX_INPUT_PUBKEYHASH) } function outputBytes (output) { return TX_OUTPUT_BASE + (output.script ? output.script.length : TX_OUTPUT_PUBKEYHASH) } function dustThreshold (output, feeRate) { /* ... classify the output for input estimate */ return inputBytes({}) * feeRate } function transactionBytes (inputs, outputs) { return TX_EMPTY_SIZE + inputs.reduce(function (a, x) { return a + inputBytes(x) }, 0) + outputs.reduce(function (a, x) { return a + outputBytes(x) }, 0) } function uintOrNaN (v) { if (typeof v !== 'number') return NaN if (!isFinite(v)) return NaN if (Math.floor(v) !== v) return NaN if (v < 0) return NaN return v } function sumForgiving (range) { return range.reduce(function (a, x) { return a + (isFinite(x.value) ? x.value : 0) }, 0) } function sumOrNaN (range) { return range.reduce(function (a, x) { return a + uintOrNaN(x.value) }, 0) } var BLANK_OUTPUT = outputBytes({}); function finalize (inputs, outputs, feeRate) { var bytesAccum = transactionBytes(inputs, outputs); var feeAfterExtraOutput = feeRate * (bytesAccum + BLANK_OUTPUT); var remainderAfterExtraOutput = sumOrNaN(inputs) - (sumOrNaN(outputs) + feeAfterExtraOutput); // is it worth a change output? if (remainderAfterExtraOutput > dustThreshold({}, feeRate)) { outputs = outputs.concat({ value: remainderAfterExtraOutput }); } var fee = sumOrNaN(inputs) - sumOrNaN(outputs); if (!isFinite(fee)) return { fee: feeRate * bytesAccum } return { inputs: inputs, outputs: outputs, fee: fee } } utils = { dustThreshold: dustThreshold, finalize: finalize, inputBytes: inputBytes, outputBytes: outputBytes, sumOrNaN: sumOrNaN, sumForgiving: sumForgiving, transactionBytes: transactionBytes, uintOrNaN: uintOrNaN }; return utils; } var accumulative$1; var hasRequiredAccumulative; function requireAccumulative () { if (hasRequiredAccumulative) return accumulative$1; hasRequiredAccumulative = 1; var utils = requireUtils(); // add inputs until we reach or surpass the target value (or deplete) // worst-case: O(n) accumulative$1 = function accumulative (utxos, outputs, feeRate) { if (!isFinite(utils.uintOrNaN(feeRate))) return {} var bytesAccum = utils.transactionBytes([], outputs); var inAccum = 0; var inputs = []; var outAccum = utils.sumOrNaN(outputs); for (var i = 0; i < utxos.length; ++i) { var utxo = utxos[i]; var utxoBytes = utils.inputBytes(utxo); var utxoFee = feeRate * utxoBytes; var utxoValue = utils.uintOrNaN(utxo.value); // skip detrimental input if (utxoFee > utxo.value) { if (i === utxos.length - 1) return { fee: feeRate * (bytesAccum + utxoBytes) } continue } bytesAccum += utxoBytes; inAccum += utxoValue; inputs.push(utxo); var fee = feeRate * bytesAccum; // go again? if (inAccum < outAccum + fee) continue return utils.finalize(inputs, outputs, feeRate) } return { fee: feeRate * bytesAccum } }; return accumulative$1; } var accumulativeExports = requireAccumulative(); var accumulative = /*@__PURE__*/getDefaultExportFromCjs(accumulativeExports); /** * Minimum transaction fee * 1000 satoshi/kB (similar to current `minrelaytxfee`) * @see https://github.com/bitcoin/bitcoin/blob/db88db47278d2e7208c50d16ab10cb355067d071/src/validation.h#L56 */ const MIN_TX_FEE = 1000; // Decimal places for Bitcoin const BTC_DECIMAL = 8; // Lower and upper bounds for fee rates const LOWER_FEE_BOUND = 1; const UPPER_FEE_BOUND = 1000; // Symbols for Bitcoin const BTC_SYMBOL = '₿'; const BTC_SATOSHI_SYMBOL = '⚡'; /** * Chain identifier for Bitcoin mainnet */ const BTCChain = 'BTC'; /** * Base "chain" asset on bitcoin main net. */ const AssetBTC = { chain: BTCChain, symbol: 'BTC', ticker: 'BTC', type: xchainUtil.AssetType.NATIVE }; // Explorer providers for Bitcoin const BTC_MAINNET_EXPLORER = new xchainClient.ExplorerProvider('https://blockstream.info/', 'https://blockstream.info/address/%%ADDRESS%%', 'https://blockstream.info/tx/%%TX_ID%%'); const BTC_TESTNET_EXPLORER = new xchainClient.ExplorerProvider('https://blockstream.info/testnet/', 'https://blockstream.info/testnet/address/%%ADDRESS%%', 'https://blockstream.info/testnet/tx/%%TX_ID%%'); const blockstreamExplorerProviders = { [xchainClient.Network.Testnet]: BTC_TESTNET_EXPLORER, [xchainClient.Network.Stagenet]: BTC_MAINNET_EXPLORER, [xchainClient.Network.Mainnet]: BTC_MAINNET_EXPLORER, }; // Sochain data providers for Bitcoin const testnetSochainProvider = new xchainUtxoProviders.SochainProvider('https://sochain.com/api/v3', process.env.SOCHAIN_API_KEY || '', BTCChain, AssetBTC, 8, xchainUtxoProviders.SochainNetwork.BTCTEST); const mainnetSochainProvider = new xchainUtxoProviders.SochainProvider('https://sochain.com/api/v3', process.env.SOCHAIN_API_KEY || '', BTCChain, AssetBTC, 8, xchainUtxoProviders.SochainNetwork.BTC); const SochainDataProviders = { [xchainClient.Network.Testnet]: testnetSochainProvider, [xchainClient.Network.Stagenet]: mainnetSochainProvider, [xchainClient.Network.Mainnet]: mainnetSochainProvider, }; // Haskoin data providers for Bitcoin const testnetHaskoinProvider = new xchainUtxoProviders.HaskoinProvider('https://api.haskoin.com', BTCChain, AssetBTC, 8, xchainUtxoProviders.HaskoinNetwork.BTCTEST); const mainnetHaskoinProvider = new xchainUtxoProviders.HaskoinProvider('https://api.haskoin.com', BTCChain, AssetBTC, 8, xchainUtxoProviders.HaskoinNetwork.BTC); const HaskoinDataProviders = { [xchainClient.Network.Testnet]: testnetHaskoinProvider, [xchainClient.Network.Stagenet]: mainnetHaskoinProvider, [xchainClient.Network.Mainnet]: mainnetHaskoinProvider, }; // Blockcypher data providers for Bitcoin const testnetBlockcypherProvider = new xchainUtxoProviders.BlockcypherProvider('https://api.blockcypher.com/v1', BTCChain, AssetBTC, 8, xchainUtxoProviders.BlockcypherNetwork.BTCTEST, process.env.BLOCKCYPHER_API_KEY || ''); const mainnetBlockcypherProvider = new xchainUtxoProviders.BlockcypherProvider('https://api.blockcypher.com/v1', BTCChain, AssetBTC, 8, xchainUtxoProviders.BlockcypherNetwork.BTC, process.env.BLOCKCYPHER_API_KEY || ''); const BlockcypherDataProviders = { [xchainClient.Network.Testnet]: testnetBlockcypherProvider, [xchainClient.Network.Stagenet]: mainnetBlockcypherProvider, [xchainClient.Network.Mainnet]: mainnetBlockcypherProvider, }; // Bitgo data providers for Bitcoin const mainnetBitgoProvider = new xchainUtxoProviders.BitgoProvider({ baseUrl: 'https://app.bitgo.com', chain: BTCChain, }); const BitgoProviders = { [xchainClient.Network.Testnet]: undefined, [xchainClient.Network.Stagenet]: mainnetBitgoProvider, [xchainClient.Network.Mainnet]: mainnetBitgoProvider, }; const tapRootDerivationPaths = { [xchainClient.Network.Mainnet]: `86'/0'/0'/0/`, [xchainClient.Network.Testnet]: `86'/1'/0'/0/`, [xchainClient.Network.Stagenet]: `86'/0'/0'/0/`, }; // Import statements for necessary modules and types // Constants defining the sizes of various components in a Bitcoin transaction const TX_EMPTY_SIZE = 4 + 1 + 1 + 4; // Total size of an empty transaction const TX_INPUT_BASE = 32 + 4 + 1 + 4; // Size of a base input in a transaction const TX_INPUT_PUBKEYHASH = 107; // Size of an input with a public key hash const TX_OUTPUT_BASE = 8 + 1; // Size of a base output in a transaction const TX_OUTPUT_PUBKEYHASH = 25; // Size of an output with a public key hash /** * Function to calculate the size of an input in a transaction. * @param {UTXO} input - The UTXO (Unspent Transaction Output) for which to calculate the size. * @returns {number} The size of the input. */ const inputBytes = (input) => { var _a, _b; return TX_INPUT_BASE + (((_a = input.witnessUtxo) === null || _a === void 0 ? void 0 : _a.script) ? (_b = input.witnessUtxo) === null || _b === void 0 ? void 0 : _b.script.length : TX_INPUT_PUBKEYHASH); }; /** * Function to get the Bitcoin network to be used with bitcoinjs. * * @param {Network} network - The network type (Mainnet, Testnet, or Stagenet). * @returns {Bitcoin.Network} The Bitcoin network. */ const btcNetwork = (network) => { switch (network) { case xchainClient.Network.Mainnet: case xchainClient.Network.Stagenet: return Bitcoin__namespace.networks.bitcoin; // Return the Bitcoin mainnet or stagenet network case xchainClient.Network.Testnet: return Bitcoin__namespace.networks.testnet; // Return the Bitcoin testnet network } }; /** * Function to validate a Bitcoin address. * @param {Address} address - The Bitcoin address to validate. * @param {Network} network - The network type (Mainnet, Testnet, or Stagenet). * @returns {boolean} `true` if the address is valid, `false` otherwise. */ const validateAddress = (address, network) => { try { Bitcoin__namespace.address.toOutputScript(address, btcNetwork(network)); // Try to convert the address to an output script using the specified network return true; // If successful, the address is valid } catch (error) { return false; // If an error occurs, the address is invalid } }; /** * Function to get the address prefix based on the network. * @param {Network} network - The network type (Mainnet, Testnet, or Stagenet). * @returns {string} The address prefix based on the network. */ const getPrefix = (network) => { switch (network) { case xchainClient.Network.Mainnet: case xchainClient.Network.Stagenet: return 'bc1'; // Return the address prefix for Bitcoin mainnet or stagenet case xchainClient.Network.Testnet: return 'tb1'; // Return the address prefix for Bitcoin testnet } }; /** * Converts a public key to an X-only public key. * @param pubKey The public key to convert. * @returns The X-only public key. */ const toXOnly = (pubKey) => (pubKey.length === 32 ? pubKey : pubKey.subarray(1, 33)); // Default parameters for the Bitcoin UTXO client const defaultBTCParams = { network: xchainClient.Network.Mainnet, phrase: '', explorerProviders: blockstreamExplorerProviders, dataProviders: [BitgoProviders, BlockcypherDataProviders], rootDerivationPaths: { [xchainClient.Network.Mainnet]: `84'/0'/0'/0/`, [xchainClient.Network.Testnet]: `84'/1'/0'/0/`, [xchainClient.Network.Stagenet]: `84'/0'/0'/0/`, }, feeBounds: { lower: LOWER_FEE_BOUND, upper: UPPER_FEE_BOUND, }, }; /** * Custom Bitcoin client */ class Client extends xchainUtxo.Client { /** * Constructor * Initializes the client with network type and other parameters. * @param {UtxoClientParams} params */ constructor(params = Object.assign(Object.assign({}, defaultBTCParams), { addressFormat: exports.AddressFormat.P2WPKH })) { var _a, _b, _c; super(BTCChain, { network: params.network, rootDerivationPaths: params.rootDerivationPaths, phrase: params.phrase, feeBounds: params.feeBounds, explorerProviders: params.explorerProviders, dataProviders: params.dataProviders, }); this.addressFormat = params.addressFormat || exports.AddressFormat.P2WPKH; if (this.addressFormat === exports.AddressFormat.P2TR) { if (!((_a = this.rootDerivationPaths) === null || _a === void 0 ? void 0 : _a.mainnet.startsWith(`86'`)) || !((_b = this.rootDerivationPaths) === null || _b === void 0 ? void 0 : _b.testnet.startsWith(`86'`)) || !((_c = this.rootDerivationPaths) === null || _c === void 0 ? void 0 : _c.stagenet.startsWith(`86'`))) { throw Error(`Unsupported derivation paths for Taproot client. Use 86' paths`); } } Bitcoin__namespace.initEccLib(ecc__namespace); } /** * Get BTC asset info. * @returns {AssetInfo} BTC asset information. */ getAssetInfo() { const assetInfo = { asset: AssetBTC, decimal: BTC_DECIMAL, }; return assetInfo; } /** * Validate the given Bitcoin address. * @param {string} address Bitcoin address to validate. * @returns {boolean} `true` if the address is valid, `false` otherwise. */ validateAddress(address) { return validateAddress(address, this.network); } /** * Compile memo into a buffer. * @param {string} memo Memo to compile. * @returns {Buffer} Compiled memo. */ compileMemo(memo) { const data = Buffer.from(memo, 'utf8'); // converts MEMO to buffer return Bitcoin__namespace.script.compile([Bitcoin__namespace.opcodes.OP_RETURN, data]); // Compile OP_RETURN script } /** * Get transaction fee from UTXOs. * @param {UTXO[]} inputs UTXOs to calculate fee from. * @param {FeeRate} feeRate Fee rate. * @param {Buffer | null} data Compiled memo (Optional). * @returns {number} Transaction fee. */ getFeeFromUtxos(inputs, feeRate, data = null) { // Calculate input size based on inputs const inputSizeBasedOnInputs = inputs.length > 0 ? inputs.reduce((a, x) => a + inputBytes(x), 0) + inputs.length // +1 byte for each input signature : 0; // Calculate sum let sum = TX_EMPTY_SIZE + inputSizeBasedOnInputs + TX_OUTPUT_BASE + TX_OUTPUT_PUBKEYHASH + TX_OUTPUT_BASE + TX_OUTPUT_PUBKEYHASH; if (data) { sum += TX_OUTPUT_BASE + data.length; } // Calculate fee const fee = sum * feeRate; return fee > MIN_TX_FEE ? fee : MIN_TX_FEE; } /** * Build a Bitcoin transaction.* * @param param0 * @deprecated */ buildTx({ amount, recipient, memo, feeRate, sender, spendPendingUTXO = true, }) { return __awaiter(this, void 0, void 0, function* () { // Check memo length if (memo && memo.length > 80) { throw new Error('memo too long, must not be longer than 80 chars.'); } // This section of the code is responsible for preparing a transaction by building a Bitcoin PSBT (Partially Signed Bitcoin Transaction). if (!this.validateAddress(recipient)) throw new Error('Invalid address'); // Determine whether to only use confirmed UTXOs or include pending UTXOs based on the spendPendingUTXO flag. const confirmedOnly = !spendPendingUTXO; // Scan UTXOs associated with the sender's address. const utxos = yield this.scanUTXOs(sender, confirmedOnly); // Throw an error if there are no available UTXOs to cover the transaction. if (utxos.length === 0) throw new Error('Insufficient Balance for transaction'); // Round up the fee rate to the nearest integer. const feeRateWhole = Math.ceil(feeRate); // Compile the memo into a Buffer if provided. const compiledMemo = memo ? this.compileMemo(memo) : null; // Initialize an array to store the target outputs of the transaction. const targetOutputs = []; // 1. Add the recipient address and amount to the target outputs. targetOutputs.push({ address: recipient, value: amount.amount().toNumber(), }); // 2. Add the compiled memo to the target outputs if it exists. if (compiledMemo) { targetOutputs.push({ script: compiledMemo, value: 0 }); } // Use the coinselect library to determine the inputs and outputs for the transaction. const { inputs, outputs } = accumulative(utxos, targetOutputs, feeRateWhole); // If no suitable inputs or outputs are found, throw an error indicating insufficient balance. if (!inputs || !outputs) throw new Error('Insufficient Balance for transaction'); // Initialize a new Bitcoin PSBT object. const psbt = new Bitcoin__namespace.Psbt({ network: btcNetwork(this.network) }); // Network-specific if (this.addressFormat === exports.AddressFormat.P2WPKH) { // Add inputs to the PSBT from the accumulated inputs. inputs.forEach((utxo) => psbt.addInput({ hash: utxo.hash, index: utxo.index, witnessUtxo: utxo.witnessUtxo, })); } else { const { pubkey, output } = Bitcoin__namespace.payments.p2tr({ address: sender, }); inputs.forEach((utxo) => psbt.addInput({ hash: utxo.hash, index: utxo.index, witnessUtxo: { value: utxo.value, script: output }, tapInternalKey: pubkey, })); } // Add outputs to the PSBT from the accumulated outputs. outputs.forEach((output) => { // If the output address is not specified, it's considered a change address and set to the sender's address. if (!output.address) { //an empty address means this is the change address output.address = sender; } // Add the output to the PSBT. if (!output.script) { psbt.addOutput(output); } else { // If the output is a memo, add it to the PSBT to avoid dust error. if (compiledMemo) { psbt.addOutput({ script: compiledMemo, value: 0 }); } } }); return { psbt, utxos, inputs }; }); } /** * Prepare transfer. * * @param {TxParams&Address&FeeRate&boolean} params The transfer options. * @returns {PreparedTx} The raw unsigned transaction. */ prepareTx({ sender, memo, amount, recipient, spendPendingUTXO = true, feeRate, }) { return __awaiter(this, void 0, void 0, function* () { // Build the transaction using the provided parameters. const { psbt, utxos, inputs } = yield this.buildTx({ sender, recipient, amount, feeRate, memo, spendPendingUTXO, }); // Return the raw unsigned transaction (PSBT) and associated UTXOs. return { rawUnsignedTx: psbt.toBase64(), utxos, inputs }; }); } } const ECPair = ecpair.ECPairFactory(ecc__namespace); /** * Custom Bitcoin client extended to support keystore functionality */ class ClientKeystore extends Client { constructor(params = Object.assign(Object.assign({}, defaultBTCParams), { addressFormat: exports.AddressFormat.P2WPKH })) { super(params); } /** * @deprecated This function eventually will be removed. Use getAddressAsync instead. * Get the address associated with the given index. * @param {number} index The index of the address. * @returns {Address} The Bitcoin address. * @throws {"index must be greater than zero"} Thrown if the index is less than zero. * @throws {"Phrase must be provided"} Thrown if the phrase has not been set before. * @throws {"Address not defined"} Thrown if failed to create the address from the phrase. */ getAddress(index = 0) { // Check if the index is valid if (index < 0) { throw new Error('index must be greater than zero'); } // Check if the phrase has been set if (this.phrase) { const btcNetwork$1 = btcNetwork(this.network); const btcKeys = this.getBtcKeys(this.phrase, index); // Generate the address using the Bitcoinjs library let address; if (this.addressFormat === exports.AddressFormat.P2WPKH) { address = Bitcoin__namespace.payments.p2wpkh({ pubkey: btcKeys.publicKey, network: btcNetwork$1, }).address; } else { address = Bitcoin__namespace.payments.p2tr({ internalPubkey: toXOnly(btcKeys.publicKey), network: btcNetwork$1, }).address; } // Throw an error if the address is not defined if (!address) { throw new Error('Address not defined'); } return address; } throw new Error('Phrase must be provided'); } /** * Get the current address asynchronously. * @param {number} index The index of the address. * @returns {Promise<Address>} A promise that resolves to the Bitcoin address. * @throws {"Phrase must be provided"} Thrown if the phrase has not been set before. */ getAddressAsync(index = 0) { return __awaiter(this, void 0, void 0, function* () { return this.getAddress(index); }); } /** * @private * Get the Bitcoin keys derived from the given phrase. * * @param {string} phrase The phrase to be used for generating the keys. * @param {number} index The index of the address. * @returns {Bitcoin.ECPair.ECPairInterface} The Bitcoin key pair. * @throws {"Could not get private key from phrase"} Thrown if failed to create BTC keys from the given phrase. */ getBtcKeys(phrase, index = 0) { const btcNetwork$1 = btcNetwork(this.network); const seed = xchainCrypto.getSeed(phrase); const bip32$1 = bip32.BIP32Factory(ecc__namespace); const master = bip32$1.fromSeed(seed, btcNetwork$1).derivePath(this.getFullDerivationPath(index)); if (!master.privateKey) { throw new Error('Could not get private key from phrase'); } return ECPair.fromPrivateKey(master.privateKey, { network: btcNetwork$1 }); } /** * Transfer BTC. * * @param {TxParams&FeeRate} params The transfer options including the fee rate. * @returns {Promise<TxHash|string>} A promise that resolves to the transaction hash or an error message. * @throws {"memo too long"} Thrown if the memo is longer than 80 characters. */ transfer(params) { return __awaiter(this, void 0, void 0, function* () { // Set the default fee rate to `fast` const feeRate = params.feeRate || (yield this.getFeeRates())[xchainClient.FeeOption.Fast]; // Check if the fee rate is within the fee bounds xchainClient.checkFeeBounds(this.feeBounds, feeRate); // Get the address index from the parameters or use the default value const fromAddressIndex = (params === null || params === void 0 ? void 0 : params.walletIndex) || 0; // Get the Bitcoin keys const btcKeys = this.getBtcKeys(this.phrase, fromAddressIndex); // Prepare the transaction const { rawUnsignedTx } = yield this.prepareTx(Object.assign(Object.assign({}, params), { sender: this.getAddress(fromAddressIndex), feeRate })); // Build the PSBT const psbt = Bitcoin__namespace.Psbt.fromBase64(rawUnsignedTx); // Sign all inputs psbt.signAllInputs(this.addressFormat === exports.AddressFormat.P2WPKH ? btcKeys : btcKeys.tweak(Bitcoin__namespace.crypto.taggedHash('TapTweak', toXOnly(btcKeys.publicKey)))); // Finalize inputs psbt.finalizeAllInputs(); // Extract the transaction hex const txHex = psbt.extractTransaction().toHex(); // Extract the transaction hash const txHash = psbt.extractTransaction().getId(); try { // Broadcast the transaction and return the transaction hash const txId = yield this.roundRobinBroadcastTx(txHex); return txId; } catch (err) { // If broadcasting fails, return an error message with a link to the explorer const error = `Server error, please check explorer for tx confirmation ${this.explorerProviders[this.network].getExplorerTxUrl(txHash)}`; return error; } }); } } /** * Custom Ledger Bitcoin client */ class ClientLedger extends Client { // Constructor // eslint-disable-next-line @typescript-eslint/no-explicit-any constructor(params) { super(params); this.transport = params.transport; } // Get the Ledger BTC application instance getApp() { return __awaiter(this, void 0, void 0, function* () { if (this.app) { return this.app; } this.app = new AppBtc__default.default({ transport: this.transport }); return this.app; }); } // Get the current address synchronously getAddress() { throw Error('Sync method not supported for Ledger'); } // Get the current address asynchronously getAddressAsync(index = 0, verify = false) { return __awaiter(this, void 0, void 0, function* () { const app = yield this.getApp(); const result = yield app.getWalletPublicKey(this.getFullDerivationPath(index), { format: this.addressFormat === exports.AddressFormat.P2TR ? 'bech32m' : 'bech32', verify, }); return result.bitcoinAddress; }); } // Transfer BTC from Ledger transfer(params) { return __awaiter(this, void 0, void 0, function* () { const app = yield this.getApp(); const fromAddressIndex = (params === null || params === void 0 ? void 0 : params.walletIndex) || 0; // Get fee rate const feeRate = params.feeRate || (yield this.getFeeRates())[xchainClient.FeeOption.Fast]; // Check if the fee rate is within the fee bounds xchainClient.checkFeeBounds(this.feeBounds, feeRate); // Get sender address const sender = yield this.getAddressAsync(fromAddressIndex); // Prepare transaction const { rawUnsignedTx, inputs } = yield this.prepareTx(Object.assign(Object.assign({}, params), { sender, feeRate })); const psbt = Bitcoin__namespace.Psbt.fromBase64(rawUnsignedTx); // Prepare Ledger inputs const ledgerInputs = inputs.map(({ txHex, hash, index }) => { if (!txHex) { throw Error(`Missing 'txHex' for UTXO (txHash ${hash})`); } const utxoTx = Bitcoin__namespace.Transaction.fromHex(txHex); const splittedTx = app.splitTransaction(txHex, utxoTx.hasWitnesses()); return [splittedTx, index, null, null]; }); // Prepare associated keysets const associatedKeysets = ledgerInputs.map(() => this.getFullDerivationPath(fromAddressIndex)); // Serialize unsigned transaction const unsignedHex = psbt.data.globalMap.unsignedTx.toBuffer().toString('hex'); const newTx = app.splitTransaction(unsignedHex, true); const outputScriptHex = app.serializeTransactionOutputs(newTx).toString('hex'); // Create payment transaction const txHex = yield app.createPaymentTransaction({ inputs: ledgerInputs, associatedKeysets, outputScriptHex, segwit: true, useTrustedInputForSegwit: true, additionals: [this.addressFormat === exports.AddressFormat.P2TR ? 'bech32m' : 'bech32'], }); // Broadcast transaction const txHash = yield this.broadcastTx(txHex); // Throw error if no transaction hash is received if (!txHash) { throw Error('No Tx hash'); } return txHash; }); } } exports.AssetBTC = AssetBTC; exports.BTCChain = BTCChain; exports.BTC_DECIMAL = BTC_DECIMAL; exports.BTC_SATOSHI_SYMBOL = BTC_SATOSHI_SYMBOL; exports.BTC_SYMBOL = BTC_SYMBOL; exports.BitgoProviders = BitgoProviders; exports.BlockcypherDataProviders = BlockcypherDataProviders; exports.Client = ClientKeystore; exports.ClientLedger = ClientLedger; exports.HaskoinDataProviders = HaskoinDataProviders; exports.LOWER_FEE_BOUND = LOWER_FEE_BOUND; exports.MIN_TX_FEE = MIN_TX_FEE; exports.SochainDataProviders = SochainDataProviders; exports.UPPER_FEE_BOUND = UPPER_FEE_BOUND; exports.blockstreamExplorerProviders = blockstreamExplorerProviders; exports.defaultBTCParams = defaultBTCParams; exports.getPrefix = getPrefix; exports.tapRootDerivationPaths = tapRootDerivationPaths; exports.validateAddress = validateAddress; //# sourceMappingURL=index.js.map