UNPKG

@atomiqlabs/sdk

Version:

atomiq labs SDK for cross-chain swaps between smart chains and bitcoin

159 lines (158 loc) 5.79 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.utils = exports.DUST_THRESHOLDS = void 0; const Logger_1 = require("../../utils/Logger"); const logger = (0, Logger_1.getLogger)("CoinSelect: "); // baseline estimates, used to improve performance const TX_EMPTY_SIZE = 4 + 1 + 1 + 4; const TX_INPUT_BASE = 32 + 4 + 1 + 4; const WITNESS_OVERHEAD = 2 / 4; const P2WPKH_WITNESS = (1 + 1 + 72 + 1 + 33) / 4; const P2TR_WITNESS = (1 + 1 + 65) / 4; const TX_INPUT_PUBKEYHASH = 107; const TX_INPUT_P2SH_P2WPKH = 23 + P2WPKH_WITNESS + 1; const TX_INPUT_P2WPKH = 0 + P2WPKH_WITNESS; const TX_INPUT_P2WSH = 0 + (1 + 1 + 64) / 4; const TX_INPUT_P2TR = 0 + P2TR_WITNESS; const TX_OUTPUT_BASE = 8 + 1; const TX_OUTPUT_PUBKEYHASH = 25; const TX_OUTPUT_P2SH_P2WPKH = 23; const TX_OUTPUT_P2WPKH = 22; const TX_OUTPUT_P2WSH = 34; const TX_OUTPUT_P2TR = 34; const INPUT_BYTES = { "p2sh-p2wpkh": TX_INPUT_P2SH_P2WPKH, "p2wpkh": TX_INPUT_P2WPKH, "p2tr": TX_INPUT_P2TR, "p2pkh": TX_INPUT_PUBKEYHASH, "p2wsh": TX_INPUT_P2WSH }; function inputBytes(input) { if (input.script == null && input.type == null) throw new Error("Needs either script or type defined!"); return TX_INPUT_BASE + (input.script ? input.script.length : INPUT_BYTES[input.type]); } const OUTPUT_BYTES = { "p2sh-p2wpkh": TX_OUTPUT_P2SH_P2WPKH, "p2wpkh": TX_OUTPUT_P2WPKH, "p2tr": TX_OUTPUT_P2TR, "p2pkh": TX_OUTPUT_PUBKEYHASH, "p2wsh": TX_OUTPUT_P2WSH }; function outputBytes(output) { if (output.script == null && output.type == null) throw new Error("Needs either script or type defined!"); return TX_OUTPUT_BASE + (output.script ? output.script.length : OUTPUT_BYTES[output.type]); } exports.DUST_THRESHOLDS = { "p2sh-p2wpkh": 540, "p2wpkh": 294, "p2tr": 330, "p2pkh": 546, "p2wsh": 330 }; function dustThreshold(output) { return exports.DUST_THRESHOLDS[output.type]; } function transactionBytes(inputs, outputs, changeType) { let size = TX_EMPTY_SIZE; let isSegwit = false; if (changeType != null && changeType !== "p2pkh") { size += WITNESS_OVERHEAD; isSegwit = true; } for (let input of inputs) { if (!isSegwit && (input.type !== "p2pkh")) { isSegwit = true; size += WITNESS_OVERHEAD; } size += inputBytes(input); } for (let output of outputs) { size += outputBytes(output); } return Math.ceil(size); } function numberOrNaN(v) { if (typeof v !== 'number') return NaN; if (!isFinite(v)) return NaN; if (v < 0) return NaN; return v; } 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((a, x) => a + (isFinite(x.value) ? x.value : 0), 0); } function sumOrNaN(range) { return range.reduce((a, x) => a + uintOrNaN(x.value), 0); } function finalize(inputs, outputs, feeRate, changeType, cpfpAddFee = 0) { const bytesAccum = transactionBytes(inputs, outputs, changeType ?? undefined); logger.debug("finalize(): Transaction bytes: ", bytesAccum); if (changeType != null) { const feeAfterExtraOutput = (feeRate * (bytesAccum + outputBytes({ type: changeType }))) + cpfpAddFee; logger.debug("finalize(): TX fee after adding change output: ", feeAfterExtraOutput); const remainderAfterExtraOutput = Math.floor(sumOrNaN(inputs) - (sumOrNaN(outputs) + feeAfterExtraOutput)); logger.debug("finalize(): Leaves change (changeType=" + changeType + ") value: ", remainderAfterExtraOutput); // is it worth a change output? if (remainderAfterExtraOutput >= dustThreshold({ type: changeType })) { outputs = outputs.concat({ value: remainderAfterExtraOutput, type: changeType }); } } const fee = sumOrNaN(inputs) - sumOrNaN(outputs); logger.debug("finalize(): Re-calculated total fee: ", fee); if (!isFinite(fee) || fee < 0) return { fee: (feeRate * bytesAccum) + cpfpAddFee }; let txVSize = exports.utils.transactionBytes(inputs, outputs); let txFee = fee; const cpfpSortedInputs = [...inputs].sort((a, b) => (b.cpfp?.txEffectiveFeeRate ?? 0) - (a.cpfp?.txEffectiveFeeRate ?? 0)); cpfpSortedInputs.forEach(input => { if (input.cpfp == null) return; const currentEffectiveFeeRate = txFee / txVSize; if (currentEffectiveFeeRate > input.cpfp.txEffectiveFeeRate) { txVSize += input.cpfp.txVsize; txFee += input.cpfp.txVsize * input.cpfp.txEffectiveFeeRate; } }); return { inputs: inputs, outputs: outputs, effectiveFeeRate: txFee / txVSize, fee: fee }; } function isDetrimentalInput(feeRate, utxo) { const utxoBytes = exports.utils.inputBytes(utxo); const utxoFee = feeRate * utxoBytes; let cpfpFee = 0; if (utxo.cpfp != null && utxo.cpfp.txEffectiveFeeRate < feeRate) cpfpFee = Math.ceil(utxo.cpfp.txVsize * (feeRate - utxo.cpfp.txEffectiveFeeRate)); // skip detrimental input return utxoFee + cpfpFee > utxo.value; } exports.utils = { dustThreshold: dustThreshold, finalize: finalize, inputBytes: inputBytes, outputBytes: outputBytes, sumOrNaN: sumOrNaN, sumForgiving: sumForgiving, transactionBytes: transactionBytes, uintOrNaN: uintOrNaN, numberOrNaN: numberOrNaN, isDetrimentalInput };