@xlink-network/xlink-sdk
Version:
138 lines (136 loc) • 3.97 kB
JavaScript
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