UNPKG

@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
"use strict"; // 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;