@xlink-network/xlink-sdk
Version:
619 lines (603 loc) • 18.1 kB
JavaScript
;
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