@arklabs/wallet-sdk
Version:
Bitcoin wallet SDK with Taproot and Ark integration
149 lines (148 loc) • 4.95 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.vtxosToTxs = vtxosToTxs;
const wallet_1 = require("../wallet");
/**
* Helper function to find vtxos that were spent in a settlement
*/
function findVtxosSpentInSettlement(vtxos, vtxo) {
if (vtxo.virtualStatus.state === "pending") {
return [];
}
return vtxos.filter((v) => {
if (!v.spentBy)
return false;
return v.spentBy === vtxo.virtualStatus.batchTxID;
});
}
/**
* Helper function to find vtxos that were spent in a payment
*/
function findVtxosSpentInPayment(vtxos, vtxo) {
return vtxos.filter((v) => {
if (!v.spentBy)
return false;
return v.spentBy === vtxo.txid;
});
}
/**
* Helper function to find vtxos that resulted from a spentBy transaction
*/
function findVtxosResultedFromSpentBy(vtxos, spentBy) {
return vtxos.filter((v) => {
if (v.virtualStatus.state !== "pending" &&
v.virtualStatus.batchTxID === spentBy) {
return true;
}
return v.txid === spentBy;
});
}
/**
* Helper function to reduce vtxos to their total amount
*/
function reduceVtxosAmount(vtxos) {
return vtxos.reduce((sum, v) => sum + v.value, 0);
}
/**
* Helper function to get a vtxo from a list of vtxos
*/
function getVtxo(resultedVtxos, spentVtxos) {
if (resultedVtxos.length === 0) {
return spentVtxos[0];
}
return resultedVtxos[0];
}
function vtxosToTxs(spendable, spent, boardingRounds) {
const txs = [];
// Receive case
// All vtxos are received unless:
// - they resulted from a settlement (either boarding or refresh)
// - they are the change of a spend tx
let vtxosLeftToCheck = [...spent];
for (const vtxo of [...spendable, ...spent]) {
if (vtxo.virtualStatus.state !== "pending" &&
boardingRounds.has(vtxo.virtualStatus.batchTxID || "")) {
continue;
}
const settleVtxos = findVtxosSpentInSettlement(vtxosLeftToCheck, vtxo);
vtxosLeftToCheck = removeVtxosFromList(vtxosLeftToCheck, settleVtxos);
const settleAmount = reduceVtxosAmount(settleVtxos);
if (vtxo.value <= settleAmount) {
continue; // settlement or change, ignore
}
const spentVtxos = findVtxosSpentInPayment(vtxosLeftToCheck, vtxo);
vtxosLeftToCheck = removeVtxosFromList(vtxosLeftToCheck, spentVtxos);
const spentAmount = reduceVtxosAmount(spentVtxos);
if (vtxo.value <= spentAmount) {
continue; // settlement or change, ignore
}
const txKey = {
roundTxid: vtxo.virtualStatus.batchTxID || "",
boardingTxid: "",
redeemTxid: "",
};
let settled = vtxo.virtualStatus.state !== "pending";
if (vtxo.virtualStatus.state === "pending") {
txKey.redeemTxid = vtxo.txid;
if (vtxo.spentBy) {
settled = true;
}
}
txs.push({
key: txKey,
amount: vtxo.value - settleAmount - spentAmount,
type: wallet_1.TxType.TxReceived,
createdAt: vtxo.createdAt.getTime(),
settled,
});
}
// send case
// All "spentBy" vtxos are payments unless:
// - they are settlements
// aggregate spent by spentId
const vtxosBySpentBy = new Map();
for (const v of spent) {
if (!v.spentBy)
continue;
if (!vtxosBySpentBy.has(v.spentBy)) {
vtxosBySpentBy.set(v.spentBy, []);
}
const currentVtxos = vtxosBySpentBy.get(v.spentBy);
vtxosBySpentBy.set(v.spentBy, [...currentVtxos, v]);
}
for (const [sb, vtxos] of vtxosBySpentBy) {
const resultedVtxos = findVtxosResultedFromSpentBy([...spendable, ...spent], sb);
const resultedAmount = reduceVtxosAmount(resultedVtxos);
const spentAmount = reduceVtxosAmount(vtxos);
if (spentAmount <= resultedAmount) {
continue; // settlement or change, ignore
}
const vtxo = getVtxo(resultedVtxos, vtxos);
const txKey = {
roundTxid: vtxo.virtualStatus.batchTxID || "",
boardingTxid: "",
redeemTxid: "",
};
if (vtxo.virtualStatus.state === "pending") {
txKey.redeemTxid = vtxo.txid;
}
txs.push({
key: txKey,
amount: spentAmount - resultedAmount,
type: wallet_1.TxType.TxSent,
createdAt: vtxo.createdAt.getTime(),
settled: true,
});
}
return txs;
}
function removeVtxosFromList(vtxos, vtxosToRemove) {
return vtxos.filter((v) => {
for (const vtxoToRemove of vtxosToRemove) {
if (v.txid === vtxoToRemove.txid && v.vout === vtxoToRemove.vout) {
return false;
}
}
return true;
});
}