UNPKG

@xlink-network/xlink-sdk

Version:
138 lines (136 loc) 3.97 kB
import { InsufficientBitcoinBalanceError, UnsupportedBitcoinInput } from "./chunk-QDMW3AJX.mjs"; import { sumUTXO } from "./chunk-VA3HOX5B.mjs"; import { max, sum } from "./chunk-YV2TZGS6.mjs"; // src/bitcoinUtils/prepareTransaction.ts import { estimateTransactionVSizeAfterSign, getOutputDustThreshold, UnsupportedInputTypeError } from "@c4/btc-utils"; import * as btc from "@scure/btc-signer"; async function prepareTransaction(txInfo) { const { recipients, changeAddressScriptPubKey, opReturnData = [], selectedUTXOs = [], feeRate, reselectSpendableUTXOs } = txInfo; const newRecipients = await Promise.all( recipients.map(async (r) => { const dustThreshold = await getOutputDustThresholdForOutput( r.addressScriptPubKey ); return { ...r, satsAmount: max([r.satsAmount, dustThreshold]) }; }) ); const newRecipientAddresses = newRecipients.map((r) => r.addressScriptPubKey).concat(changeAddressScriptPubKey); const satsToSend = sum(newRecipients.map((r) => r.satsAmount)); let lastSelectedUTXOs = selectedUTXOs.slice(); let lastSelectedUTXOSatsInTotal = sumUTXO(lastSelectedUTXOs); let calculatedFee = await calculateFee({ recipientAddressScriptPubKeys: newRecipientAddresses, opReturnData, selectedUTXOs: lastSelectedUTXOs, feeRate }); let loopTimes = 0; while (lastSelectedUTXOSatsInTotal < satsToSend + calculatedFee.fee) { const newSatsToSend = satsToSend + calculatedFee.fee; const newSelectedUTXOs = await reselectSpendableUTXOs( newSatsToSend, selectedUTXOs, lastSelectedUTXOs ); const newSelectedUTXOSatsInTotal = sumUTXO(newSelectedUTXOs); if (newSelectedUTXOSatsInTotal < lastSelectedUTXOSatsInTotal) { throw new InsufficientBitcoinBalanceError(); } lastSelectedUTXOSatsInTotal = newSelectedUTXOSatsInTotal; lastSelectedUTXOs = newSelectedUTXOs; calculatedFee = await calculateFee({ recipientAddressScriptPubKeys: newRecipientAddresses, opReturnData, selectedUTXOs: newSelectedUTXOs, feeRate }); loopTimes++; if (loopTimes > 500) { throw new InsufficientBitcoinBalanceError(); } } const changeOutputDustThreshold = await getOutputDustThresholdForOutput( changeAddressScriptPubKey ); const changeAmount = lastSelectedUTXOSatsInTotal - sum([satsToSend, calculatedFee.fee]); let finalChangeAmount; let finalFeeAmount; if (changeAmount < changeOutputDustThreshold) { finalChangeAmount = 0n; finalFeeAmount = lastSelectedUTXOSatsInTotal - satsToSend; } else { finalChangeAmount = changeAmount; finalFeeAmount = calculatedFee.fee; } return { inputs: lastSelectedUTXOs, recipients: newRecipients, changeAmount: finalChangeAmount, fee: finalFeeAmount, estimatedVSize: calculatedFee.estimatedVSize }; } async function getOutputDustThresholdForOutput(outputAddressScriptPubKey) { return BigInt( Math.ceil( getOutputDustThreshold({ scriptPubKey: outputAddressScriptPubKey }) ) ); } var DEFAULT_MIN_RELAY_TX_FEE = 1000n; async function calculateFee(info) { const outputs = [ ...info.recipientAddressScriptPubKeys.map((r) => ({ scriptPubKey: r })), ...info.opReturnData.map((data) => ({ scriptPubKey: btc.Script.encode(["RETURN", data]) })) ]; try { const vSize = estimateTransactionVSizeAfterSign({ inputs: info.selectedUTXOs, outputs }); const txSize = BigInt(Math.ceil(vSize)); return { estimatedVSize: vSize, fee: max([info.feeRate * txSize, DEFAULT_MIN_RELAY_TX_FEE]) }; } catch (e) { if (e instanceof UnsupportedInputTypeError) { const input = e.cause; throw new UnsupportedBitcoinInput(input.txId, input.index); } throw e; } } export { prepareTransaction, calculateFee }; //# sourceMappingURL=chunk-STX72JIW.mjs.map