@bitcoinerlab/discovery
Version:
A TypeScript library for retrieving Bitcoin funds from ranged descriptors, leveraging @bitcoinerlab/explorer for standardized access to multiple blockchain explorers.
588 lines (587 loc) • 30.5 kB
JavaScript
;
// Copyright (c) 2023 Jose-Luis Landabaso - https://bitcoinerlab.com
// Distributed under the MIT software license
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.deriveDataFactory = exports.canonicalize = void 0;
const memoizee_1 = __importDefault(require("memoizee"));
const memoizers_1 = require("./memoizers");
const shallow_equal_1 = require("shallow-equal");
const types_1 = require("./types");
const bitcoinjs_lib_1 = require("bitcoinjs-lib");
const descriptors_1 = require("@bitcoinerlab/descriptors");
const secp256k1 = __importStar(require("@bitcoinerlab/secp256k1"));
const { Output, expand } = (0, descriptors_1.DescriptorsFactory)(secp256k1);
const networks_1 = require("./networks");
function canonicalize(descriptorOrDescriptors, network) {
let isDifferent = false;
const descriptorArray = Array.isArray(descriptorOrDescriptors)
? descriptorOrDescriptors
: [descriptorOrDescriptors];
const canonicalDescriptors = [];
descriptorArray.forEach(descriptor => {
const canonicalDescriptor = expand({
descriptor,
network
}).canonicalExpression;
if (descriptor !== canonicalDescriptor)
isDifferent = true;
canonicalDescriptors.push(canonicalDescriptor);
});
if (Array.isArray(descriptorOrDescriptors)) {
if (isDifferent)
return canonicalDescriptors;
else
return descriptorOrDescriptors;
}
else {
const canonicalDescriptor = canonicalDescriptors[0];
if (!canonicalDescriptor)
throw new Error(`Could not canonicalize ${descriptorOrDescriptors}`);
return canonicalDescriptor;
}
}
exports.canonicalize = canonicalize;
function deriveDataFactory({ descriptorsCacheSize = 0, outputsPerDescriptorCacheSize = 0 }) {
/**
* Compares two transactions based on their blockHeight and input dependencies.
* Can be used as callback in Array.sort function to sort from old to new.
*
* @param txWithOrderA - The first transaction data to compare.
* @param txWithOrderB - The second transaction data to compare.
*
*
* txWithOrderA and txWithOrderB should contain the `blockHeight` (use 0 if
* in the mempool) and either `tx` (`Transaction` type) or `txHex` (the
* hexadecimal representation of the transaction)
*
* @returns < 0 if txWithOrderA is older than txWithOrderB, > 01 if txWithOrderA is newer than txWithOrderB, and 0 if undecided.
*/
function compareTxOrder(txWithOrderA, txWithOrderB) {
// txWithOrderA is in mempool and txWithOrderB no, so txWithOrderA is newer
if (txWithOrderA.blockHeight === 0 && txWithOrderB.blockHeight !== 0)
return 1;
// txWithOrderB is in mempool and txWithOrderA no, so txWithOrderB is newer
if (txWithOrderB.blockHeight === 0 && txWithOrderA.blockHeight !== 0)
return -1;
// If blockHeight is different and none are in the mempool, sort by blockHeight
if (txWithOrderA.blockHeight !== txWithOrderB.blockHeight)
return txWithOrderA.blockHeight - txWithOrderB.blockHeight;
// If blockHeight is the same, check input dependencies
let txA = txWithOrderA.tx;
let txIdA;
if (!txA) {
if (!txWithOrderA.txHex)
throw new Error('Pass tx or txHex');
const { tx, txId } = transactionFromHex(txWithOrderA.txHex);
txA = tx;
txIdA = txId;
}
let txB = txWithOrderB.tx;
let txIdB;
if (!txB) {
if (!txWithOrderB.txHex)
throw new Error('Pass tx or txHex');
const { tx, txId } = transactionFromHex(txWithOrderB.txHex);
txB = tx;
txIdB = txId;
}
//getHash is slow, try to avoid it if we can use a cached getId:
const txHashB = txIdB ? hex2RevBuf(txIdB) : txB.getHash();
// txA is newer because it uses an input from txB
for (const Ainput of txA.ins)
if (Ainput.hash.equals(txHashB))
return 1;
//getHash is slow, try to avoid it if we can use a cached getId:
const txHashA = txIdA ? hex2RevBuf(txIdA) : txA.getHash();
// txB is newer because it uses an input from txA
for (const Binput of txB.ins)
if (Binput.hash.equals(txHashA))
return -1;
return 0; // Cannot decide, keep the original order
}
const deriveScriptPubKeyFactory = (0, memoizee_1.default)((networkId) => (0, memoizee_1.default)((descriptor) => (0, memoizee_1.default)((index) => {
const network = (0, networks_1.getNetwork)(networkId);
//Note there is no need to pass a network (bitcoin will be default) but
//it's not important anyway since the scriptPubKey does not depend on
//the network
const output = index === 'non-ranged'
? new Output({ descriptor, network })
: new Output({ descriptor, index, network });
const scriptPubKey = output.getScriptPubKey();
return scriptPubKey;
}, { primitive: true, max: outputsPerDescriptorCacheSize }), { primitive: true, max: descriptorsCacheSize }), { primitive: true } //unbounded cache (no max setting) since Search Space is small
);
const deriveScriptPubKey = (networkId, descriptor, index) => deriveScriptPubKeyFactory(networkId)(descriptor)(index);
const coreDeriveTxosByOutput = (networkId, descriptor, index, txWithOrderArray, txStatus) => {
const scriptPubKey = deriveScriptPubKey(networkId, descriptor, index);
const txoMap = {};
//All prev outputs (spent or unspent) sent to this output descriptor:
const allPrevOutputs = [];
//all outputs in txWithOrderArray which have been spent.
//May be outputs NOT snt to thil output descriptor:
const spendingTxIdByOutput = {}; //Means: Utxo was spent in txId
//Note that txWithOrderArray cannot be assumed to be in correct order if
//in the same block and if one does not depend on the other. See:
//https://github.com/Blockstream/esplora/issues/165#issuecomment-1584471718
for (const txData of txWithOrderArray) {
if (txStatus === types_1.TxStatus.ALL ||
(txStatus === types_1.TxStatus.IRREVERSIBLE && txData.irreversible) ||
(txStatus === types_1.TxStatus.CONFIRMED && txData.blockHeight !== 0)) {
const txHex = txData.txHex;
if (!txHex)
throw new Error(`txHex not yet retrieved for an element of ${descriptor}, ${index}`);
const { tx, txId } = transactionFromHex(txHex);
for (const [vin, input] of tx.ins.entries()) {
const prevTxId = buf2RevHex(input.hash);
const prevVout = input.index;
const prevUtxo = `${prevTxId}:${prevVout}`;
spendingTxIdByOutput[prevUtxo] = `${txId}:${vin}`; //prevUtxo was spent by txId in input vin
}
for (const [vout, output] of tx.outs.entries()) {
if (output.script.equals(scriptPubKey)) {
const txo = `${txId}:${vout}`;
allPrevOutputs.push(txo);
txoMap[txo] = `${descriptor}~${index}`;
}
}
}
}
// UTXOs are those in allPrevOutputs that have not been spent
const utxos = allPrevOutputs.filter(output => !Object.keys(spendingTxIdByOutput).includes(output));
const stxos = allPrevOutputs
.filter(output => Object.keys(spendingTxIdByOutput).includes(output))
.map(txo => `${txo}:${spendingTxIdByOutput[txo]}`);
return { utxos, stxos, txoMap };
};
const deriveUtxosAndBalanceByOutputFactory = (0, memoizee_1.default)((networkId) => (0, memoizee_1.default)((txStatus) => (0, memoizee_1.default)((descriptor) => (0, memoizee_1.default)((index) => {
// Create one function per each expression x index x txStatus
// coreDeriveTxosByOutput shares all params wrt the parent
// function except for additional param txWithOrderArray.
// As soon as txWithOrderArray in coreDeriveTxosByOutput changes,
// it will resets its memory.
const deriveTxosByOutput = (0, memoizee_1.default)(coreDeriveTxosByOutput, {
max: 1
});
let lastUtxos = null;
let lastStxos = null;
let lastTxoMap = null;
let lastBalance;
return (0, memoizee_1.default)((txMap, descriptorMap) => {
const txWithOrderArray = deriveTxDataArray(txMap, descriptorMap, descriptor, index);
let { utxos, stxos, txoMap } = deriveTxosByOutput(networkId, descriptor, index, txWithOrderArray, txStatus);
let balance;
if (lastTxoMap && (0, shallow_equal_1.shallowEqualObjects)(lastTxoMap, txoMap))
txoMap = lastTxoMap;
if (lastStxos && (0, shallow_equal_1.shallowEqualArrays)(lastStxos, stxos))
stxos = lastStxos;
if (lastUtxos && (0, shallow_equal_1.shallowEqualArrays)(lastUtxos, utxos)) {
utxos = lastUtxos;
balance = lastBalance;
}
else
balance = coreDeriveUtxosBalance(txMap, utxos);
lastTxoMap = txoMap;
lastUtxos = utxos;
lastStxos = stxos;
lastBalance = balance;
return { txoMap, stxos, utxos, balance };
}, { max: 1 });
}, { primitive: true, max: outputsPerDescriptorCacheSize }), { primitive: true, max: descriptorsCacheSize }), { primitive: true } //unbounded cache (no max setting) since Search Space is small
), { primitive: true } //unbounded cache (no max setting) since Search Space is small
);
const deriveUtxosAndBalanceByOutput = (networkId, txMap, descriptorMap, descriptor, index, txStatus) => deriveUtxosAndBalanceByOutputFactory(networkId)(txStatus)(descriptor)(index)(txMap, descriptorMap);
const coreDeriveTxDataArray = (txIds, txMap) => txIds.map(txId => {
const txData = txMap[txId];
if (!txData)
throw new Error(`txData not saved for ${txId}`);
return txData;
});
const deriveTxDataArrayFactory = (0, memoizee_1.default)((descriptor) => (0, memoizee_1.default)((index) => {
return (0, memoizers_1.memoizeOneWithShallowArraysCheck)((txMap, descriptorMap) => {
const range = deriveUsedRange(descriptorMap[descriptor]);
const txIds = range[index]?.txIds || [];
const txWithOrderArray = coreDeriveTxDataArray(txIds, txMap);
return txWithOrderArray;
});
}, { primitive: true, max: outputsPerDescriptorCacheSize }), { primitive: true, max: descriptorsCacheSize });
const deriveTxDataArray = (txMap, descriptorMap, descriptor, index) => deriveTxDataArrayFactory(descriptor)(index)(txMap, descriptorMap);
const deriveAttributions = (txHistory, networkId, txMap, descriptorMap, descriptorOrDescriptors, txStatus) => {
const { utxos, stxos } = deriveUtxosAndBalance(networkId, txMap, descriptorMap, descriptorOrDescriptors, txStatus);
//Suposedly Set.has is faster than Array.includes:
//https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set#performance
const txoSet = new Set([
...utxos,
...stxos.map(stxo => {
const [txId, voutStr] = stxo.split(':');
if (txId === undefined || voutStr === undefined) {
throw new Error(`Undefined txId or vout for STXO: ${stxo}`);
}
return `${txId}:${voutStr}`;
})
]);
return txHistory.map(txData => {
const { txHex, irreversible, blockHeight } = txData;
if (!txHex)
throw new Error(`Error: txHex not found`);
const { tx, txId } = transactionFromHex(txHex);
const ins = tx.ins.map(input => {
const prevTxId = buf2RevHex(input.hash);
const prevVout = input.index;
const prevTxo = `${prevTxId}:${prevVout}`;
const ownedPrevTxo = txoSet.has(prevTxo)
? prevTxo
: false;
if (ownedPrevTxo) {
const prevTxHex = txMap[prevTxId]?.txHex;
if (!prevTxHex)
throw new Error(`txHex not set for ${prevTxId}`);
const { tx: prevTx } = transactionFromHex(prevTxHex);
const value = prevTx.outs[prevVout]?.value;
if (value === undefined)
throw new Error(`value should exist for ${prevTxId}:${prevVout}`);
return { ownedPrevTxo, value };
}
else
return { ownedPrevTxo };
});
const outs = tx.outs.map((output, vout) => {
const txo = `${txId}:${vout}`;
const value = output.value;
const ownedTxo = txoSet.has(txo) ? txo : false;
return { ownedTxo, value };
});
let netReceived = 0;
//What I receive in my descriptors:
for (const output of outs)
netReceived += output.ownedTxo ? output.value : 0;
//What i send from my descriptors:
for (const input of ins) {
if (input.ownedPrevTxo) {
const value = input.value;
if (value === undefined)
throw new Error('input.value should be defined for ownedPrevTxo');
netReceived -= value;
}
}
const allInputsOwned = ins.every(input => input.ownedPrevTxo);
const someInputsOwned = ins.some(input => input.ownedPrevTxo);
const allOutputsOwned = outs.every(output => output.ownedTxo);
const someOutputsNotOwned = outs.some(output => !output.ownedTxo);
const someOutputsOwned = outs.some(output => output.ownedTxo);
const someInputsNotOwned = ins.some(input => !input.ownedPrevTxo);
let type;
if (allInputsOwned && allOutputsOwned)
type = 'CONSOLIDATED';
else if (someInputsNotOwned &&
someInputsOwned &&
someOutputsNotOwned &&
someOutputsOwned)
type = 'RECEIVED_AND_SENT';
else if (someInputsOwned && someOutputsNotOwned)
type = 'SENT';
else if (someInputsNotOwned && someOutputsOwned)
type = 'RECEIVED';
else
throw new Error('Transaction type could not be determined.');
return {
ins,
outs,
netReceived,
type,
txId,
irreversible,
blockHeight
};
});
};
const deriveHistoryByOutputFactory = (0, memoizee_1.default)((withAttributions) => (0, memoizee_1.default)((networkId) => (0, memoizee_1.default)((txStatus) => (0, memoizee_1.default)((descriptor) => (0, memoizee_1.default)((index) => {
return (0, memoizers_1.memoizeOneWithShallowArraysCheck)((txMap, descriptorMap) => {
const txAllHistory = deriveTxDataArray(txMap, descriptorMap, descriptor, index);
const txHistory = txAllHistory.filter(txData => txStatus === types_1.TxStatus.ALL ||
(txStatus === types_1.TxStatus.IRREVERSIBLE &&
txData.irreversible) ||
(txStatus === types_1.TxStatus.CONFIRMED &&
txData.blockHeight !== 0));
const sortedHistory = txHistory.sort(compareTxOrder);
if (withAttributions)
return deriveAttributions(sortedHistory, networkId, txMap, descriptorMap, descriptor, txStatus);
else
return sortedHistory;
});
}, {
primitive: true,
max: outputsPerDescriptorCacheSize
}), { primitive: true, max: descriptorsCacheSize }), { primitive: true } //unbounded cache (no max setting) since Search Space is small
), { primitive: true } //unbounced cache for networkId
), { primitive: true } //unbounded cache (no max setting) since withAttributions is space is 2
);
const deriveHistoryByOutput = (withAttributions, networkId, txMap, descriptorMap, descriptor, index, txStatus) => deriveHistoryByOutputFactory(withAttributions)(networkId)(txStatus)(descriptor)(index)(txMap, descriptorMap);
const coreDeriveHistory = (withAttributions, networkId, descriptorMap, txMap, descriptorOrDescriptors, txStatus) => {
const txHistory = [];
const descriptorArray = Array.isArray(descriptorOrDescriptors)
? descriptorOrDescriptors
: [descriptorOrDescriptors];
for (const descriptor of descriptorArray) {
const range = deriveUsedRange(descriptorMap[descriptor]);
Object.keys(range)
.sort() //Sort it to be deterministic
.forEach(indexStr => {
const index = indexStr === 'non-ranged' ? indexStr : Number(indexStr);
txHistory.push(...deriveHistoryByOutput(
//Derive the normal txHistory without attributions (false).
//This will be enhanced later if withAttributions is set.
//Note that deriveAttributions uses txHistory (normal history)
//as input
false, networkId, txMap, descriptorMap, descriptor, index, txStatus));
});
}
//Deduplicate in case of an expression receiving from another expression
//and sort again by blockHeight
const dedupedHistory = [...new Set(txHistory)];
//since we have txs belonging to different expressions let's try to order
//them from old to new (blockHeight ascending order).
//Note that we cannot guarantee to keep correct order to txs
//that belong to the same blockHeight except when one of the txs depends
//on the other.
//See https://github.com/Blockstream/esplora/issues/165#issuecomment-1584471718
const sortedHistory = dedupedHistory.sort(compareTxOrder);
if (withAttributions)
return deriveAttributions(sortedHistory, networkId, txMap, descriptorMap, descriptorOrDescriptors, txStatus);
else
return sortedHistory;
};
const deriveHistoryFactory = (0, memoizee_1.default)((withAttributions) => (0, memoizee_1.default)((networkId) => (0, memoizee_1.default)((txStatus) => (0, memoizee_1.default)((descriptorOrDescriptors) => {
return (0, memoizers_1.memoizeOneWithShallowArraysCheck)((txMap, descriptorMap) => coreDeriveHistory(withAttributions, networkId, descriptorMap, txMap, descriptorOrDescriptors, txStatus));
}, { primitive: true, max: descriptorsCacheSize }), { primitive: true } //unbounded cache (no max setting) since Search Space is small
), { primitive: true } //unbounded cache for NetworkId
), { primitive: true } //unbounded cache (no max setting) since withAttributions is space is 2
);
const deriveHistory = (withAttributions, networkId, txMap, descriptorMap, descriptorOrDescriptors, txStatus) => deriveHistoryFactory(withAttributions)(networkId)(txStatus)(descriptorOrDescriptors)(txMap, descriptorMap);
const coreDeriveTxos = (networkId, descriptorMap, txMap, descriptorOrDescriptors, txStatus) => {
const utxos = [];
const stxos = [];
const txoMap = {};
const descriptorArray = Array.isArray(descriptorOrDescriptors)
? descriptorOrDescriptors
: [descriptorOrDescriptors];
for (const descriptor of descriptorArray) {
const range = deriveUsedRange(descriptorMap[descriptor]);
Object.keys(range)
.sort() //Sort it to be deterministic
.forEach(indexStr => {
const index = indexStr === 'non-ranged' ? indexStr : Number(indexStr);
const { utxos: utxosByO, stxos: stxosByO, txoMap: txoMapByO } = deriveUtxosAndBalanceByOutput(networkId, txMap, descriptorMap, descriptor, index, txStatus);
utxos.push(...utxosByO);
stxos.push(...stxosByO);
Object.assign(txoMap, txoMapByO);
});
}
//Deduplicate in case of expression: Array<Descriptor> with duplicated
//descriptorOrDescriptors
const dedupedUtxos = [...new Set(utxos)];
const dedupedStxos = [...new Set(stxos)];
return { utxos: dedupedUtxos, stxos: dedupedStxos, txoMap };
};
//unbound memoizee wrt TxStatus is fine since it has a small Search Space
//however the search space for expressions must be bounded
//returns {balance, utxos}. The reference of utxos will be kept the same for
//each tuple of txStatus x expressions
const deriveUtxosAndBalanceFactory = (0, memoizee_1.default)((networkId) => (0, memoizee_1.default)((txStatus) => (0, memoizee_1.default)((descriptorOrDescriptors) => {
let lastTxoMap = null;
let lastUtxos = null;
let lastStxos = null;
let lastBalance;
return (0, memoizee_1.default)((txMap, descriptorMap) => {
let { utxos, stxos, txoMap } = coreDeriveTxos(networkId, descriptorMap, txMap, descriptorOrDescriptors, txStatus);
let balance;
if (lastTxoMap && (0, shallow_equal_1.shallowEqualObjects)(lastTxoMap, txoMap))
txoMap = lastTxoMap;
if (lastStxos && (0, shallow_equal_1.shallowEqualArrays)(lastStxos, stxos))
stxos = lastStxos;
if (lastUtxos && (0, shallow_equal_1.shallowEqualArrays)(lastUtxos, utxos)) {
utxos = lastUtxos;
balance = lastBalance;
}
else
balance = coreDeriveUtxosBalance(txMap, utxos);
lastTxoMap = txoMap;
lastUtxos = utxos;
lastStxos = stxos;
lastBalance = balance;
return { stxos, utxos, txoMap, balance };
}, { max: 1 });
}, { primitive: true, max: descriptorsCacheSize } //potentially ininite search space. limit to 100 descriptorOrDescriptors per txStatus combination
), { primitive: true } //unbounded cache (no max setting) since Search Space is small
), { primitive: true } //unbounded cache (no max setting) since Search Space is small
);
const deriveUtxosAndBalance = (networkId, txMap, descriptorMap, descriptorOrDescriptors, txStatus) => deriveUtxosAndBalanceFactory(networkId)(txStatus)(descriptorOrDescriptors)(txMap, descriptorMap);
const transactionFromHex = (0, memoizee_1.default)((txHex) => {
const tx = bitcoinjs_lib_1.Transaction.fromHex(txHex);
const txId = tx.getId();
return { tx, txId };
}, {
primitive: true,
max: 1000
});
const hex2RevBuf = (0, memoizee_1.default)((idOrHash) => {
return Buffer.from(idOrHash).reverse();
}, {
primitive: true,
max: 1000
});
const buf2RevHex = (0, memoizee_1.default)((idOrHash) => {
//Note we create a new Buffer since reverse() mutates the Buffer
return Buffer.from(idOrHash).reverse().toString('hex');
}, {
primitive: true,
max: 1000
});
const coreDeriveUtxosBalance = (txMap, utxos) => {
let balance = 0;
const firstDuplicate = utxos.find((element, index, arr) => {
return arr.indexOf(element) !== index;
});
if (firstDuplicate !== undefined)
throw new Error(`Duplicated utxo: ${firstDuplicate}`);
for (const utxo of utxos) {
const [txId, voutStr] = utxo.split(':');
if (txId === undefined || voutStr === undefined)
throw new Error(`Undefined txId or vout for UTXO: ${utxo}`);
const vout = parseInt(voutStr);
const txData = txMap[txId];
if (!txData)
throw new Error(`txData not saved for ${txId}, vout:${vout} - ${utxo}`);
const txHex = txData.txHex;
if (!txHex)
throw new Error(`txHex not yet retrieved for ${txId}`);
const { tx } = transactionFromHex(txHex);
const output = tx.outs[vout];
if (!output)
throw new Error(`Error: invalid output for ${txId}:${vout}`);
const outputValue = output.value; // value in satoshis
balance += outputValue;
}
return balance;
};
/**
* Filters the provided descriptor object's range with
* records that have non-empty transaction ID arrays.
*
* @returns An object containing only the records from the input range with
* non-empty txIds arrays.
*/
function deriveUsedRange(descriptorData) {
const usedRange = {};
if (!descriptorData)
return usedRange;
for (const [indexStr, outputData] of Object.entries(descriptorData.range)) {
const index = indexStr === 'non-ranged' ? indexStr : Number(indexStr);
if (outputData.txIds.length > 0)
usedRange[index] = outputData;
}
return usedRange;
}
function isDescriptorUsed(descriptorData) {
return Object.keys(deriveUsedRange(descriptorData)).length !== 0;
}
const coreDeriveUsedDescriptors = (discoveryData, networkId) => {
const descriptorMap = discoveryData[networkId].descriptorMap;
return Object.keys(descriptorMap)
.filter(descriptor => isDescriptorUsed(descriptorMap[descriptor]))
.sort();
};
const deriveUsedDescriptorsFactory = (0, memoizee_1.default)((networkId) => {
return (0, memoizers_1.memoizeOneWithShallowArraysCheck)((discoveryData) => coreDeriveUsedDescriptors(discoveryData, networkId));
}, { primitive: true } //unbounded cache (no max setting) since Search Space is small
);
/**
* Extracts and returns descriptor expressions that are associated with at
* least one utilized scriptPubKey from the discovery information. The
* function is optimized to maintain and return the same array object for
* each unique networkId if the resulting expressions did not change.
* This helps to avoid unnecessary data processing and memory usage.
*
* @returns Descriptor expressions.
*/
const deriveUsedDescriptors = (
/** The network identifier. */
discoveryData,
/** Descriptor expressions. */
networkId) => deriveUsedDescriptorsFactory(networkId)(discoveryData);
/**
* Derives the accounts from the discoveryData.
* Descriptor expressions of an account share the same pattern, except for
* their keyInfo, which can end with either /0/* or /1/*.
* An Account is represented by its external descriptor.
*
* @param {NetworkId} networkId
* @returns {Array<Account>}- Returns an array of accounts.
*/
const coreDeriveUsedAccounts = (discoveryData, networkId) => {
const descriptors = coreDeriveUsedDescriptors(discoveryData, networkId);
const network = (0, networks_1.getNetwork)(networkId);
const accountSet = new Set(); //Use Set to avoid duplicates
for (const descriptor of descriptors) {
const { expansionMap } = expand({ descriptor, network });
if (expansionMap)
for (const keyInfo of Object.values(expansionMap)) {
if (!keyInfo) {
throw new Error(`Missing keyInfo in expansionMap for descriptor ${descriptor}`);
}
if (keyInfo.keyPath === '/0/*' || keyInfo.keyPath === '/1/*') {
const account = descriptor.replace(/\/1\/\*/g, '/0/*');
accountSet.add(account);
}
}
}
return Array.from(accountSet).sort(); //sort the Array so it's deterministic
};
const deriveUsedAccountsFactory = (0, memoizee_1.default)((networkId) => {
return (0, memoizers_1.memoizeOneWithShallowArraysCheck)((discoveryData) => coreDeriveUsedAccounts(discoveryData, networkId));
}, { primitive: true } //unbounded cache (no max setting) since Search Space is small
);
const deriveUsedAccounts = (discoveryData, networkId) => deriveUsedAccountsFactory(networkId)(discoveryData);
const deriveAccountDescriptors = (0, memoizee_1.default)((account) => [
account,
account.replace(/\/0\/\*/g, '/1/*')
], { primitive: true, max: descriptorsCacheSize });
return {
deriveScriptPubKey,
deriveUtxosAndBalanceByOutput,
deriveUtxosAndBalance,
deriveUsedDescriptors,
deriveUsedAccounts,
deriveAccountDescriptors,
deriveHistoryByOutput,
deriveHistory,
transactionFromHex,
compareTxOrder
};
}
exports.deriveDataFactory = deriveDataFactory;