UNPKG

@xlink-network/xlink-sdk

Version:
619 lines (603 loc) 18.1 kB
"use strict"; var __create = Object.create; var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; var __getProtoOf = Object.getPrototypeOf; var __hasOwnProp = Object.prototype.hasOwnProperty; var __export = (target, all) => { for (var name in all) __defProp(target, name, { get: all[name], enumerable: true }); }; var __copyProps = (to, from, except, desc) => { if (from && typeof from === "object" || typeof from === "function") { for (let key of __getOwnPropNames(from)) if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); } return to; }; var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps( // If the importer is in node compatibility mode or this is not an ESM // file that has been converted to a CommonJS file using a Babel- // compatible transform (i.e. "__esModule" has not been set), then set // "default" to the CommonJS "module.exports" for node compatibility. isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target, mod )); var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); // src/bitcoinUtils/createSendBitcoinTransaction.ts var createSendBitcoinTransaction_exports = {}; __export(createSendBitcoinTransaction_exports, { createSendBitcoinTransaction: () => createSendBitcoinTransaction }); module.exports = __toCommonJS(createSendBitcoinTransaction_exports); // src/bitcoinUtils/createTransaction.ts var btc = __toESM(require("@scure/btc-signer")); // src/utils/arrayHelpers.ts function hasAny(ary) { return ary.length > 0; } function last(ary) { return ary[ary.length - 1]; } var _concat = [].concat; var _reduce = [].reduce; function reduce(fn, initialValue, inputArray) { return _reduce.call(inputArray, fn, initialValue); } function sortBy(iteratee, ary) { const _iteratee = Array.isArray(iteratee) ? iteratee : [iteratee]; return ary.map((value, index, ary2) => ({ value, index, criteria: _iteratee.map((f) => f(value, index, ary2)) })).sort((left, right) => compareMultiple(left, right)).map((v) => v.value); } function compareMultiple(obj, oth) { const objCriteria = obj.criteria; const othCriteria = oth.criteria; const length = objCriteria.length; let index = -1; while (++index < length) { const objCri = objCriteria[index]; const othCri = othCriteria[index]; let result = 0; if (typeof objCri === typeof othCri) { result = Number(objCri - othCri); } else { result = Number(objCri) - Number(othCri); } if (result) return result; } return obj.index - oth.index; } // src/bitcoinUtils/createTransaction.ts function createTransaction(inputUTXOs, recipients, opReturnData) { const tx = new btc.Transaction({ allowUnknownOutputs: true, allowLegacyWitnessUtxo: true }); inputUTXOs.forEach((utxo) => { tx.addInput({ txid: utxo.txId, index: utxo.index, witnessUtxo: utxo.scriptPubKey == null ? void 0 : { script: utxo.scriptPubKey, amount: utxo.amount }, tapInternalKey: "tapInternalKey" in utxo ? utxo.tapInternalKey : void 0, redeemScript: "redeemScript" in utxo ? utxo.redeemScript : void 0, // Enable RBF sequence: btc.DEFAULT_SEQUENCE - 2 }); }); recipients.forEach((recipient) => { tx.addOutput({ script: recipient.addressScriptPubKey, amount: recipient.satsAmount }); }); if (hasAny(opReturnData)) { opReturnData.forEach((data) => { tx.addOutput({ script: btc.Script.encode(["RETURN", data]), amount: 0n }); }); } return tx; } // src/bitcoinUtils/prepareTransaction.ts var import_btc_utils = require("@c4/btc-utils"); var btc3 = __toESM(require("@scure/btc-signer")); // src/utils/bigintHelpers.ts var MAX_BIGINT = BigInt(Number.MAX_VALUE); function sum(nums) { return nums.reduce((acc, val) => acc + val, 0n); } function max(nums) { return nums.reduce((acc, val) => acc > val ? acc : val, 0n); } // src/bitcoinUtils/bitcoinHelpers.ts var btc2 = __toESM(require("@scure/btc-signer")); var import_btc_signer = require("@scure/btc-signer"); // src/utils/BigNumber.ts var import_big = require("big.js"); import_big.Big.PE = 21; import_big.Big.NE = -21; var toBig = (num) => { if (num instanceof import_big.Big) { return num; } return new import_big.Big(num); }; var fromBig = (num) => { return num; }; var BigNumber; ((BigNumber2) => { ({ roundUp: BigNumber2.roundUp, roundDown: BigNumber2.roundDown, roundHalfUp: BigNumber2.roundHalfUp, roundHalfEven: BigNumber2.roundHalfEven } = import_big.Big); let defaultRoundingMode = BigNumber2.roundHalfUp; BigNumber2.setDefaultRoundingMode = (roundingMode) => { defaultRoundingMode = roundingMode; }; BigNumber2.isBigNumber = (num) => { return num instanceof import_big.Big; }; BigNumber2.safeFrom = (value) => { try { return (0, BigNumber2.from)(value); } catch (e) { return void 0; } }; BigNumber2.from = (value) => { return fromBig(toBig(value)); }; BigNumber2.toString = (value) => { return toBig(value).toString(); }; BigNumber2.toNumber = (value) => { return toBig(value).toNumber(); }; BigNumber2.toBigInt = curry2( (options, value) => { return BigInt( (0, BigNumber2.toFixed)( { precision: 0, roundingMode: options.roundingMode ?? defaultRoundingMode }, toBig(value) ) ); } ); BigNumber2.toFixed = curry2( (options, value) => { return toBig(value).toFixed( options.precision, options.roundingMode ?? defaultRoundingMode ); } ); BigNumber2.toExponential = curry2( (options, value) => { return toBig(value).toExponential( options.precision, options.roundingMode ?? defaultRoundingMode ); } ); BigNumber2.isNegative = (value) => { return toBig(value).lt(0); }; BigNumber2.isGtZero = (value) => { return toBig(value).gt(0); }; BigNumber2.isZero = (value) => { return toBig(value).eq(0); }; BigNumber2.isEq = curry2( (value, a) => { return toBig(value).eq(toBig(a)); } ); BigNumber2.isGt = curry2( (value, a) => { return toBig(value).gt(toBig(a)); } ); BigNumber2.isGte = curry2( (value, a) => { return toBig(value).gte(toBig(a)); } ); BigNumber2.isLt = curry2( (value, a) => { return toBig(value).lt(toBig(a)); } ); BigNumber2.isLte = curry2( (value, a) => { return toBig(value).lte(toBig(a)); } ); BigNumber2.setPrecision = curry2( (options, value) => { return fromBig( toBig( toBig(value).toPrecision( options.precision, options.roundingMode ?? defaultRoundingMode ) ) ); } ); BigNumber2.getPrecision = (value) => { return toBig(value).c.length - (toBig(value).e + 1); }; BigNumber2.getIntegerLength = (value) => { return toBig(value).e + 1; }; BigNumber2.leftMoveDecimals = curry2( (distance, value) => (0, BigNumber2.moveDecimals)({ distance }, value) ); BigNumber2.rightMoveDecimals = curry2( (distance, value) => (0, BigNumber2.moveDecimals)({ distance: -distance }, value) ); BigNumber2.moveDecimals = curry2( (options, value) => { if (options.distance > 0) { return fromBig(toBig(value).div(10 ** options.distance)); } if (options.distance < 0) { return fromBig(toBig(value).mul(10 ** -options.distance)); } return (0, BigNumber2.from)(value); } ); BigNumber2.getDecimalPart = curry2( (options, value) => { const formatted = (0, BigNumber2.toFixed)( { precision: Math.min((0, BigNumber2.getPrecision)(value), options.precision), roundingMode: BigNumber2.roundDown }, value ); const [, decimals] = formatted.split("."); if (decimals == null) return void 0; return decimals; } ); BigNumber2.abs = (value) => { return fromBig(toBig(value).abs()); }; BigNumber2.neg = (value) => { return fromBig(toBig(value).neg()); }; BigNumber2.sqrt = (value) => { return fromBig(toBig(value).sqrt()); }; BigNumber2.add = curry2( (value, a) => { return fromBig(toBig(value).add(toBig(a))); } ); BigNumber2.minus = curry2( (value, a) => { return fromBig(toBig(value).minus(toBig(a))); } ); BigNumber2.mul = curry2( (value, a) => { return fromBig(toBig(value).mul(toBig(a))); } ); BigNumber2.cumulativeMul = curry2( (value, as) => { return reduce( (acc, currentRate) => [ ...acc, BigNumber2.mul(last(acc), currentRate) ], [(0, BigNumber2.from)(value)], as ); } ); BigNumber2.div = curry2( (value, a) => { return fromBig(toBig(value).div(toBig(a))); } ); BigNumber2.pow = curry2((value, a) => { return fromBig(toBig(value).pow(a)); }); BigNumber2.round = curry2( (options, value) => { return fromBig( toBig(value).round( options.precision, options.roundingMode ?? defaultRoundingMode ) ); } ); BigNumber2.toPrecision = curry2( (options, value) => { return toBig(value).toPrecision( options.precision, options.roundingMode ?? defaultRoundingMode ); } ); BigNumber2.ascend = curry2( (a, b) => (0, BigNumber2.isLt)(a, b) ? -1 : (0, BigNumber2.isGt)(a, b) ? 1 : 0 ); BigNumber2.descend = curry2( (a, b) => (0, BigNumber2.isLt)(a, b) ? 1 : (0, BigNumber2.isGt)(a, b) ? -1 : 0 ); BigNumber2.sort = (comparator, numbers) => { const _numbers = numbers.map((a) => fromBig(toBig(a))); _numbers.sort(comparator); return _numbers; }; BigNumber2.max = (numbers) => { return (0, BigNumber2.from)((0, BigNumber2.sort)(BigNumber2.descend, numbers)[0]); }; BigNumber2.min = (numbers) => { return (0, BigNumber2.from)((0, BigNumber2.sort)(BigNumber2.ascend, numbers)[0]); }; BigNumber2.clamp = (range, n) => { const [min2, max3] = range; if ((0, BigNumber2.isGte)(n, max3)) return max3; if ((0, BigNumber2.isLte)(n, min2)) return min2; return n; }; BigNumber2.sum = (numbers) => { return numbers.map((n) => fromBig(toBig(n))).reduce((acc, n) => (0, BigNumber2.add)(acc, n), BigNumber2.ZERO); }; BigNumber2.ZERO = BigNumber2.from(0); BigNumber2.ONE = BigNumber2.from(1); })(BigNumber || (BigNumber = {})); function curry2(fn) { return function(a, b) { if (arguments.length > 1) { return fn(a, b); } return (b2) => fn(a, b2); }; } // src/bitcoinUtils/bitcoinHelpers.ts function sumUTXO(utxos) { return sum(utxos.map((utxo) => utxo.amount)); } function isSameUTXO(utxo1, utxo2) { return utxo1.txId === utxo2.txId && utxo1.index === utxo2.index; } // src/utils/errors.ts var XLinkSDKErrorBase = class extends Error { constructor(...args) { super(...args); this.name = "XLinkSDKErrorBase"; } }; // src/bitcoinUtils/errors.ts var InsufficientBitcoinBalanceError = class extends XLinkSDKErrorBase { constructor(...args) { super(...args); this.message = this.message || "Insufficient Bitcoin balance with network fees"; } }; var UnsupportedBitcoinInput = class extends XLinkSDKErrorBase { constructor(txid, index, ...args) { super(...args); this.txid = txid; this.index = index; this.message = this.message || `Not supported bitcoin input: ${txid}:${index}`; } }; // src/bitcoinUtils/prepareTransaction.ts 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( (0, import_btc_utils.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: btc3.Script.encode(["RETURN", data]) })) ]; try { const vSize = (0, import_btc_utils.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 import_btc_utils.UnsupportedInputTypeError) { const input = e.cause; throw new UnsupportedBitcoinInput(input.txId, input.index); } throw e; } } // src/utils/hexHelpers.ts var hexes = Array.from( { length: 256 }, (_v, i) => i.toString(16).padStart(2, "0") ); // src/utils/typeHelpers.ts function isNotNull(input) { return input != null; } function assertExclude(asserting, excluding) { return; } ((assertExclude2) => { assertExclude2.i = () => null; })(assertExclude || (assertExclude = {})); // src/bitcoinUtils/selectUTXOs.ts var reselectSpendableUTXOsFactory = (availableUTXOs, getUTXOSpendable) => { return async (satsToSend, pinnedUTXOs, _lastTimeSelectedUTXOs) => { const lastTimeSelectedUTXOs = await Promise.all( _lastTimeSelectedUTXOs.map(getUTXOSpendable) ).then((utxos) => utxos.filter(isNotNull)); const otherAvailableUTXOs = await Promise.all( availableUTXOs.filter( (availableUTXO) => !lastTimeSelectedUTXOs.find( (selectedUTXO) => isSameUTXO(availableUTXO, selectedUTXO) ) ).map(getUTXOSpendable) ).then((utxos) => utxos.filter(isNotNull)); return selectUTXOs(satsToSend, lastTimeSelectedUTXOs, otherAvailableUTXOs); }; }; function selectUTXOs(satsToSend, selectedUTXOs, otherAvailableUTXOs) { const inputs = []; let sumValue = 0n; otherAvailableUTXOs = otherAvailableUTXOs.slice(); if (hasAny(selectedUTXOs)) { inputs.push(...selectedUTXOs); sumValue = sum([sumValue, ...selectedUTXOs.map((o) => o.amount)]); } otherAvailableUTXOs = sortBy( [(o) => -o.amount, (o) => o.blockHeight], otherAvailableUTXOs ); for (const utxo of otherAvailableUTXOs) { inputs.push(utxo); sumValue = sumValue + utxo.amount; if (sumValue >= satsToSend) { break; } } return inputs; } // src/bitcoinUtils/createSendBitcoinTransaction.ts async function createSendBitcoinTransaction(options) { const opReturnData = options.opReturnData ?? []; const { inputs, recipients: newRecipients, changeAmount } = await prepareTransaction({ recipients: options.recipients, changeAddressScriptPubKey: options.changeAddressScriptKey, feeRate: options.feeRate, opReturnData, reselectSpendableUTXOs: reselectSpendableUTXOsFactory( options.availableFeeUtxos, options.getUTXOSpendable ) }); const tx = createTransaction( inputs, newRecipients.concat( changeAmount === 0n ? [] : { addressScriptPubKey: options.changeAddressScriptKey, satsAmount: changeAmount } ), opReturnData ); return { tx }; } // Annotate the CommonJS export names for ESM import in node: 0 && (module.exports = { createSendBitcoinTransaction }); //# sourceMappingURL=createSendBitcoinTransaction.js.map