New Case Study:See how Anthropic automated 95% of dependency reviews with Socket.Learn More
Socket
Sign inDemoInstall
Socket

@bitcoinerlab/discovery

Package Overview
Dependencies
Maintainers
1
Versions
16
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@bitcoinerlab/discovery - npm Package Compare versions

Comparing version 1.0.4 to 1.1.0

25

dist/deriveData.d.ts
/// <reference types="node" />
import memoizee from 'memoizee';
import { NetworkId, Descriptor, Account, DescriptorIndex, DiscoveryData, TxStatus, DescriptorData, TxId, TxData } from './types';
import { NetworkId, Descriptor, Account, DescriptorIndex, DiscoveryData, TxStatus, DescriptorData, TxAttribution, TxId, TxData } from './types';
import { Transaction, Network } from 'bitcoinjs-lib';

@@ -12,2 +12,3 @@ export declare function canonicalize(descriptorOrDescriptors: Array<Descriptor> | Descriptor, network: Network): string | string[];

deriveUtxosAndBalanceByOutput: (networkId: NetworkId, txMap: Record<TxId, TxData>, descriptorMap: Record<Descriptor, DescriptorData>, descriptor: Descriptor, index: DescriptorIndex, txStatus: TxStatus) => {
stxos: string[];
utxos: string[];

@@ -17,2 +18,3 @@ balance: number;

deriveUtxosAndBalance: (networkId: NetworkId, txMap: Record<TxId, TxData>, descriptorMap: Record<Descriptor, DescriptorData>, descriptorOrDescriptors: Array<Descriptor> | Descriptor, txStatus: TxStatus) => {
stxos: string[];
utxos: string[];

@@ -24,5 +26,22 @@ balance: number;

deriveAccountDescriptors: ((account: Account) => [Descriptor, Descriptor]) & memoizee.Memoized<(account: Account) => [Descriptor, Descriptor]>;
deriveHistoryByOutput: (txMap: Record<TxId, TxData>, descriptorMap: Record<Descriptor, DescriptorData>, descriptor: Descriptor, index: DescriptorIndex, txStatus: TxStatus) => TxData[];
deriveHistory: (txMap: Record<TxId, TxData>, descriptorMap: Record<Descriptor, DescriptorData>, descriptorOrDescriptors: Array<Descriptor> | Descriptor, txStatus: TxStatus) => TxData[];
deriveHistoryByOutput: (withAttributions: boolean, networkId: NetworkId, txMap: Record<TxId, TxData>, descriptorMap: Record<Descriptor, DescriptorData>, descriptor: Descriptor, index: DescriptorIndex, txStatus: TxStatus) => TxData[] | {
ins: ({
ownedPrevTxo: string;
value: number;
} | {
ownedPrevTxo: string | false;
value?: never;
})[];
outs: {
ownedTxo: string | false;
value: number;
}[];
netReceived: number;
type: "CONSOLIDATED" | "RECEIVED" | "SENT" | "RECEIVED_AND_SENT";
txId: string;
irreversible: boolean;
blockHeight: number;
}[];
deriveHistory: (withAttributions: boolean, networkId: NetworkId, txMap: Record<TxId, TxData>, descriptorMap: Record<Descriptor, DescriptorData>, descriptorOrDescriptors: Array<Descriptor> | Descriptor, txStatus: TxStatus) => TxData[] | TxAttribution[];
transactionFromHex: typeof Transaction.fromHex & memoizee.Memoized<typeof Transaction.fromHex>;
};

@@ -84,9 +84,12 @@ "use strict";

const deriveScriptPubKey = (networkId, descriptor, index) => deriveScriptPubKeyFactory(networkId)(descriptor)(index);
const coreDeriveUtxosByOutput = (networkId, descriptor, index, txDataArray, txStatus) => {
const coreDeriveTxosByOutput = (networkId, descriptor, index, txDataArray, txStatus) => {
const scriptPubKey = deriveScriptPubKey(networkId, descriptor, index);
const allOutputs = [];
const spentOutputs = [];
//All prev outputs (spent or unspent) sent to this output descriptor:
const allPrevOutputs = [];
//all outputs in txDataArray which have been spent.
//May be outputs NOT snt to thil output descriptor:
const spendingTxIdByOutput = {}; //Means: Utxo was spent in txId
//Note that txDataArray cannot be assumed to be in correct order. See:
//https://github.com/Blockstream/esplora/issues/165#issuecomment-1584471718
//TODO: but we should guarantee same order always so use txId as second order criteria?
//TODO: but we should guarantee same order always so use txId as second order criteria? - probably not needed?
for (const txData of txDataArray) {

@@ -106,5 +109,6 @@ if (txStatus === types_1.TxStatus.ALL ||

//Note we create a new Buffer since reverse() mutates the Buffer
const inputId = Buffer.from(input.hash).reverse().toString('hex');
const spentOutputKey = `${inputId}:${input.index}`;
spentOutputs.push(spentOutputKey);
const prevTxId = Buffer.from(input.hash).reverse().toString('hex');
const prevVout = input.index;
const prevUtxo = `${prevTxId}:${prevVout}`;
spendingTxIdByOutput[prevUtxo] = `${txId}:${vin}`; //prevUtxo was spent by txId in input vin
}

@@ -117,3 +121,3 @@ for (let vout = 0; vout < tx.outs.length; vout++) {

const outputKey = `${txId}:${vout}`;
allOutputs.push(outputKey);
allPrevOutputs.push(outputKey);
}

@@ -123,24 +127,37 @@ }

}
// UTXOs are those in allOutputs that are not in spentOutputs
const utxos = allOutputs.filter(output => !spentOutputs.includes(output));
return utxos;
// 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 };
};
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
// coreDeriveUtxosByOutput shares all params wrt the parent
// coreDeriveTxosByOutput shares all params wrt the parent
// function except for additional param txDataArray.
// As soon as txDataArray in coreDeriveUtxosByOutput changes,
// it will resets its memory. However, it always returns the same
// reference if the resulting array is shallowy-equal:
const deriveUtxosByOutput = (0, memoizers_1.memoizeOneWithShallowArraysCheck)(coreDeriveUtxosByOutput);
// As soon as txDataArray in coreDeriveTxosByOutput changes,
// it will resets its memory.
const deriveTxosByOutput = (0, memoizee_1.default)(coreDeriveTxosByOutput, {
max: 1
});
let lastUtxos = null;
let lastStxos = null;
let lastBalance;
return (0, memoizee_1.default)((txMap, descriptorMap) => {
const txDataArray = deriveTxDataArray(txMap, descriptorMap, descriptor, index);
const utxos = deriveUtxosByOutput(networkId, descriptor, index, txDataArray, txStatus);
if (lastUtxos && (0, shallow_equal_1.shallowEqualArrays)(lastUtxos, utxos))
return { utxos: lastUtxos, balance: lastBalance };
let { utxos, stxos } = deriveTxosByOutput(networkId, descriptor, index, txDataArray, txStatus);
let balance;
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);
lastUtxos = utxos;
lastBalance = coreDeriveUtxosBalance(txMap, utxos);
return { utxos, balance: lastBalance };
lastStxos = stxos;
lastBalance = balance;
return { stxos, utxos, balance };
}, { max: 1 });

@@ -166,6 +183,96 @@ }, { primitive: true, max: outputsPerDescriptorCacheSize }), { primitive: true, max: descriptorsCacheSize }), { primitive: true } //unbounded cache (no max setting) since Search Space is small

const deriveTxDataArray = (txMap, descriptorMap, descriptor, index) => deriveTxDataArrayFactory(descriptor)(index)(txMap, descriptorMap);
const deriveHistoryByOutputFactory = (0, memoizee_1.default)((txStatus) => (0, memoizee_1.default)((descriptor) => (0, memoizee_1.default)((index) => {
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 = transactionFromHex(txHex);
const txId = tx.getId();
const ins = tx.ins.map(input => {
const prevTxId = Buffer.from(input.hash).reverse().toString('hex');
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 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 txDataArray = deriveTxDataArray(txMap, descriptorMap, descriptor, index);
return txDataArray.filter(txData => txStatus === types_1.TxStatus.ALL ||
const txAllHistory = deriveTxDataArray(txMap, descriptorMap, descriptor, index);
const txHistory = txAllHistory.filter(txData => txStatus === types_1.TxStatus.ALL ||
(txStatus === types_1.TxStatus.IRREVERSIBLE &&

@@ -175,2 +282,6 @@ txData.irreversible) ||

txData.blockHeight !== 0));
if (withAttributions)
return deriveAttributions(txHistory, networkId, txMap, descriptorMap, descriptor, txStatus);
else
return txHistory;
});

@@ -181,6 +292,8 @@ }, {

}), { 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 = (txMap, descriptorMap, descriptor, index, txStatus) => deriveHistoryByOutputFactory(txStatus)(descriptor)(index)(txMap, descriptorMap);
const coreDeriveHistory = (descriptorMap, txMap, descriptorOrDescriptors, txStatus) => {
const history = [];
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)

@@ -195,3 +308,8 @@ ? descriptorOrDescriptors

const index = indexStr === 'non-ranged' ? indexStr : Number(indexStr);
history.push(...deriveHistoryByOutput(txMap, descriptorMap, descriptor, index, txStatus));
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));
});

@@ -201,16 +319,35 @@ }

//and sort again by blockHeight
const dedupedHistory = [...new Set(history)];
//since we have txs belonging to different expressions let's try to oder
//them. Note that we cannot guarantee to keep correct order to txs
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
//TODO: but we should guarantee same order always so use txId as second order criteria?
return dedupedHistory.sort((txDataA, txDataB) => txDataA.blockHeight - txDataB.blockHeight);
//TODO: but we should guarantee same order always so use txId as second order criteria? - probably not needed?
const sortedHistory = dedupedHistory.sort((txDataA, txDataB) => {
if (txDataA.blockHeight === 0 && txDataB.blockHeight === 0) {
return 0; // Both are in mempool, keep their relative order unchanged
}
if (txDataA.blockHeight === 0) {
return 1; // txDataA is in mempool, so it should come after txDataB
}
if (txDataB.blockHeight === 0) {
return -1; // txDataB is in mempool, so it should come after txDataA
}
return txDataA.blockHeight - txDataB.blockHeight; // Regular ascending order sort
});
if (withAttributions)
return deriveAttributions(sortedHistory, networkId, txMap, descriptorMap, descriptorOrDescriptors, txStatus);
else
return sortedHistory;
};
const deriveHistoryFactory = (0, memoizee_1.default)((txStatus) => (0, memoizee_1.default)((descriptorOrDescriptors) => {
return (0, memoizers_1.memoizeOneWithShallowArraysCheck)((txMap, descriptorMap) => coreDeriveHistory(descriptorMap, txMap, descriptorOrDescriptors, txStatus));
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 = (txMap, descriptorMap, descriptorOrDescriptors, txStatus) => deriveHistoryFactory(txStatus)(descriptorOrDescriptors)(txMap, descriptorMap);
const coreDeriveUtxos = (networkId, descriptorMap, txMap, descriptorOrDescriptors, txStatus) => {
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 descriptorArray = Array.isArray(descriptorOrDescriptors)

@@ -225,3 +362,5 @@ ? descriptorOrDescriptors

const index = indexStr === 'non-ranged' ? indexStr : Number(indexStr);
utxos.push(...deriveUtxosAndBalanceByOutput(networkId, txMap, descriptorMap, descriptor, index, txStatus).utxos);
const { utxos: utxosByO, stxos: stxosByO } = deriveUtxosAndBalanceByOutput(networkId, txMap, descriptorMap, descriptor, index, txStatus);
utxos.push(...utxosByO);
stxos.push(...stxosByO);
});

@@ -232,3 +371,4 @@ }

const dedupedUtxos = [...new Set(utxos)];
return dedupedUtxos;
const dedupedStxos = [...new Set(stxos)];
return { utxos: dedupedUtxos, stxos: dedupedStxos };
};

@@ -241,10 +381,19 @@ //unbound memoizee wrt TxStatus is fine since it has a small Search Space

let lastUtxos = null;
let lastStxos = null;
let lastBalance;
return (0, memoizee_1.default)((txMap, descriptorMap) => {
const utxos = coreDeriveUtxos(networkId, descriptorMap, txMap, descriptorOrDescriptors, txStatus);
if (lastUtxos && (0, shallow_equal_1.shallowEqualArrays)(lastUtxos, utxos))
return { utxos: lastUtxos, balance: lastBalance };
let { utxos, stxos } = coreDeriveTxos(networkId, descriptorMap, txMap, descriptorOrDescriptors, txStatus);
let balance;
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);
lastUtxos = utxos;
lastBalance = coreDeriveUtxosBalance(txMap, utxos);
return { utxos, balance: lastBalance };
lastStxos = stxos;
lastBalance = balance;
return { stxos, utxos, balance };
}, { max: 1 });

@@ -269,3 +418,3 @@ }, { primitive: true, max: descriptorsCacheSize } //potentially ininite search space. limit to 100 descriptorOrDescriptors per txStatus combination

const [txId, voutStr] = utxo.split(':');
if (!txId || !voutStr)
if (txId === undefined || voutStr === undefined)
throw new Error(`Undefined txId or vout for UTXO: ${utxo}`);

@@ -272,0 +421,0 @@ const vout = parseInt(voutStr);

68

dist/discovery.d.ts

@@ -6,3 +6,3 @@ /// <reference types="node" />

import type { Explorer } from '@bitcoinerlab/explorer';
import { OutputCriteria, NetworkId, TxId, TxHex, TxData, Descriptor, Account, DiscoveryData, Utxo, TxStatus } from './types';
import { OutputCriteria, NetworkId, TxId, TxData, Descriptor, Account, DiscoveryData, Utxo, TxStatus, Stxo, TxHex, TxAttribution } from './types';
/**

@@ -324,2 +324,4 @@ * Creates and returns a Discovery class for discovering funds in a Bitcoin network

* one or more descriptor expressions and transaction status.
* In addition it also retrieves spent transaction outputs (STXOS) which correspond
* to previous UTXOs that have been spent.
*

@@ -340,5 +342,8 @@ * This method is useful for accessing the available funds for specific

* scriptPubKeys and the total balance of these UTXOs.
* It also returns previous UTXOs that had been
* eventually spent as stxos: Array<Stxo>
*/
getUtxosAndBalance({ descriptor, index, descriptors, txStatus }: OutputCriteria): {
utxos: Array<Utxo>;
stxos: Array<Stxo>;
balance: number;

@@ -382,17 +387,41 @@ };

*
* This method is useful for accessing transaction records associated with one or more
* descriptor expressions and transaction status.
* This method accesses transaction records associated with descriptor expressions
* and transaction status.
*
* The return value is computed based on the current state of discoveryData. The method
* uses memoization to maintain the same object reference for the returned result, given
* the same input parameters, as long as the corresponding transaction records in
* discoveryData haven't changed.
* When `withAttributions` is `false`, it returns an array of historical transactions
* (`Array<TxData>`). See {@link TxData TxData}.
*
* This can be useful in environments such as React where preserving object identity can
* prevent unnecessary re-renders.
* To determine if each transaction corresponds to a sent/received transaction, set
* `withAttributions` to `true`.
*
* @param outputCriteria
* @returns An array containing transaction info associated with the descriptor expressions.
* When `withAttributions` is `true`, this function returns an array of
* {@link TxAttribution TxAttribution} elements.
*
* `TxAttribution` identifies the owner of the previous output for each input and
* the owner of the output for each transaction.
*
* This is useful in wallet applications to specify whether inputs are from owned
* outputs (e.g., change from a previous transaction) or from third parties. It
* also specifies if outputs are destined to third parties or are internal change.
* This helps wallet apps show transaction history with "Sent" or "Received" labels,
* considering only transactions with third parties.
*
* See {@link TxAttribution TxAttribution} for a complete list of items returned per
* transaction.
*
* The return value is computed based on the current state of `discoveryData`. The
* method uses memoization to maintain the same object reference for the returned
* result, given the same input parameters, as long as the corresponding transaction
* records in `discoveryData` haven't changed.
*
* This can be useful in environments such as React, where preserving object identity
* can prevent unnecessary re-renders.
*
* @param outputCriteria - Criteria for selecting transaction outputs, including descriptor
* expressions, transaction status, and whether to include attributions.
* @param withAttributions - Whether to include attributions in the returned data.
* @returns An array containing transaction information associated with the descriptor
* expressions.
*/
getHistory({ descriptor, index, descriptors, txStatus }: OutputCriteria): Array<TxData>;
getHistory({ descriptor, index, descriptors, txStatus }: OutputCriteria, withAttributions?: boolean): Array<TxData> | Array<TxAttribution>;
/**

@@ -442,9 +471,18 @@ * Retrieves the hexadecimal representation of a transaction (TxHex) from the

/**
* Given an unspent tx output, this function retrieves its descriptor.
* Given an unspent tx output, this function retrieves its descriptor (if still unspent).
* Alternatively, pass a txo (any transaction output, which may have been
* spent already or not) and this function will also retrieve its descriptor.
* txo can be in any of these formats: `${txId}:${vout}` or
* using its extended form: `${txId}:${vout}:${recipientTxId}:${recipientVin}`
*
* This query can be quite slow so use wisely.
*
* Returns the descriptor (and index if ranged) or undefined if not found.
*/
getDescriptor({ utxo }: {
getDescriptor({ utxo, txo }: {
/**
* The UTXO.
*/
utxo: Utxo;
utxo?: Utxo;
txo?: Utxo;
}): {

@@ -451,0 +489,0 @@ descriptor: Descriptor;

@@ -315,2 +315,4 @@ "use strict";

* one or more descriptor expressions and transaction status.
* In addition it also retrieves spent transaction outputs (STXOS) which correspond
* to previous UTXOs that have been spent.
*

@@ -331,2 +333,4 @@ * This method is useful for accessing the available funds for specific

* scriptPubKeys and the total balance of these UTXOs.
* It also returns previous UTXOs that had been
* eventually spent as stxos: Array<Stxo>
*/

@@ -337,3 +341,3 @@ getUtxosAndBalance({ descriptor, index, descriptors, txStatus = types_1.TxStatus.ALL }) {

...(descriptors ? { descriptors } : {}),
...(index ? { index } : {})
...(index !== undefined ? { index } : {})
});

@@ -389,3 +393,3 @@ if ((descriptor && descriptors) || !(descriptor || descriptors))

let index = 0;
while (__classPrivateFieldGet(this, _Discovery_derivers, "f").deriveHistoryByOutput(txMap, descriptorMap, (0, deriveData_1.canonicalize)(descriptor, network), index, txStatus).length)
while (__classPrivateFieldGet(this, _Discovery_derivers, "f").deriveHistoryByOutput(false, networkId, txMap, descriptorMap, (0, deriveData_1.canonicalize)(descriptor, network), index, txStatus).length)
index++;

@@ -397,17 +401,41 @@ return index;

*
* This method is useful for accessing transaction records associated with one or more
* descriptor expressions and transaction status.
* This method accesses transaction records associated with descriptor expressions
* and transaction status.
*
* The return value is computed based on the current state of discoveryData. The method
* uses memoization to maintain the same object reference for the returned result, given
* the same input parameters, as long as the corresponding transaction records in
* discoveryData haven't changed.
* When `withAttributions` is `false`, it returns an array of historical transactions
* (`Array<TxData>`). See {@link TxData TxData}.
*
* This can be useful in environments such as React where preserving object identity can
* prevent unnecessary re-renders.
* To determine if each transaction corresponds to a sent/received transaction, set
* `withAttributions` to `true`.
*
* @param outputCriteria
* @returns An array containing transaction info associated with the descriptor expressions.
* When `withAttributions` is `true`, this function returns an array of
* {@link TxAttribution TxAttribution} elements.
*
* `TxAttribution` identifies the owner of the previous output for each input and
* the owner of the output for each transaction.
*
* This is useful in wallet applications to specify whether inputs are from owned
* outputs (e.g., change from a previous transaction) or from third parties. It
* also specifies if outputs are destined to third parties or are internal change.
* This helps wallet apps show transaction history with "Sent" or "Received" labels,
* considering only transactions with third parties.
*
* See {@link TxAttribution TxAttribution} for a complete list of items returned per
* transaction.
*
* The return value is computed based on the current state of `discoveryData`. The
* method uses memoization to maintain the same object reference for the returned
* result, given the same input parameters, as long as the corresponding transaction
* records in `discoveryData` haven't changed.
*
* This can be useful in environments such as React, where preserving object identity
* can prevent unnecessary re-renders.
*
* @param outputCriteria - Criteria for selecting transaction outputs, including descriptor
* expressions, transaction status, and whether to include attributions.
* @param withAttributions - Whether to include attributions in the returned data.
* @returns An array containing transaction information associated with the descriptor
* expressions.
*/
getHistory({ descriptor, index, descriptors, txStatus = types_1.TxStatus.ALL }) {
getHistory({ descriptor, index, descriptors, txStatus = types_1.TxStatus.ALL }, withAttributions = false) {
if ((descriptor && descriptors) || !(descriptor || descriptors))

@@ -422,3 +450,3 @@ throw new Error(`Pass descriptor or descriptors`);

...(descriptors ? { descriptors } : {}),
...(index ? { index } : {})
...(index !== undefined ? { index } : {})
});

@@ -428,9 +456,11 @@ const networkId = (0, networks_1.getNetworkId)(network);

const txMap = __classPrivateFieldGet(this, _Discovery_discoveryData, "f")[networkId].txMap;
let txDataArray = [];
if (descriptor &&
(typeof index !== 'undefined' || !descriptor.includes('*'))) {
const internalIndex = typeof index === 'number' ? index : 'non-ranged';
return __classPrivateFieldGet(this, _Discovery_derivers, "f").deriveHistoryByOutput(txMap, descriptorMap, descriptorOrDescriptors, internalIndex, txStatus);
txDataArray = __classPrivateFieldGet(this, _Discovery_derivers, "f").deriveHistoryByOutput(withAttributions, networkId, txMap, descriptorMap, descriptorOrDescriptors, internalIndex, txStatus);
}
else
return __classPrivateFieldGet(this, _Discovery_derivers, "f").deriveHistory(txMap, descriptorMap, descriptorOrDescriptors, txStatus);
txDataArray = __classPrivateFieldGet(this, _Discovery_derivers, "f").deriveHistory(withAttributions, networkId, txMap, descriptorMap, descriptorOrDescriptors, txStatus);
return txDataArray;
}

@@ -455,3 +485,3 @@ /**

if (!txHex)
throw new Error(`Error: txHex not found`);
throw new Error(`Error: txHex not found for ${txId} while getting TxHex`);
return txHex;

@@ -482,21 +512,37 @@ }

/**
* Given an unspent tx output, this function retrieves its descriptor.
* Given an unspent tx output, this function retrieves its descriptor (if still unspent).
* Alternatively, pass a txo (any transaction output, which may have been
* spent already or not) and this function will also retrieve its descriptor.
* txo can be in any of these formats: `${txId}:${vout}` or
* using its extended form: `${txId}:${vout}:${recipientTxId}:${recipientVin}`
*
* This query can be quite slow so use wisely.
*
* Returns the descriptor (and index if ranged) or undefined if not found.
*/
getDescriptor({ utxo }) {
getDescriptor({ utxo, txo }) {
if (utxo && txo)
throw new Error('Pass either txo or utxo, not both');
if (utxo)
txo = utxo;
const networkId = (0, networks_1.getNetworkId)(network);
const split = utxo.split(':');
if (!txo)
throw new Error('Pass either txo or utxo');
const split = txo.split(':');
if (split.length !== 2)
throw new Error(`Error: invalid utxo: ${utxo}`);
throw new Error(`Error: invalid txo: ${txo}`);
const txId = split[0];
if (!txId)
throw new Error(`Error: invalid utxo: ${utxo}`);
throw new Error(`Error: invalid txo: ${txo}`);
const strVout = split[1];
if (!strVout)
throw new Error(`Error: invalid utxo: ${utxo}`);
throw new Error(`Error: invalid txo: ${txo}`);
const vout = parseInt(strVout);
if (vout.toString() !== strVout)
throw new Error(`Error: invalid utxo: ${utxo}`);
const txHex = __classPrivateFieldGet(this, _Discovery_discoveryData, "f")[networkId].txMap[txId]?.txHex;
if (!txHex)
throw new Error(`Error: txHex not found for ${utxo}`);
throw new Error(`Error: invalid txo: ${txo}`);
//const txHex = this.#discoveryData[networkId].txMap[txId]?.txHex;
//if (!txHex)
// throw new Error(
// `Error: txHex not found for ${txo} while looking for its descriptor.`
// );
const descriptorMap = __classPrivateFieldGet(this, _Discovery_discoveryData, "f")[networkId].descriptorMap;

@@ -511,13 +557,41 @@ const descriptors = __classPrivateFieldGet(this, _Discovery_derivers, "f").deriveUsedDescriptors(__classPrivateFieldGet(this, _Discovery_discoveryData, "f"), networkId);

const index = isRanged && Number(indexStr);
if (this.getUtxosAndBalance({
if (!txo)
throw new Error('txo not defined');
const { utxos, stxos } = this.getUtxosAndBalance({
descriptor,
...(isRanged ? { index: Number(indexStr) } : {})
}).utxos.includes(utxo)) {
if (output)
throw new Error(`output {${descriptor}, ${index}} is already represented by {${output.descriptor}, ${output.index}} .`);
output = {
descriptor,
...(isRanged ? { index: Number(indexStr) } : {})
};
});
if (utxo) {
if (utxos.includes(txo)) {
if (output)
throw new Error(`output {${descriptor}, ${index}} is already represented by {${output.descriptor}, ${output.index}} .`);
output = {
descriptor,
...(isRanged ? { index: Number(indexStr) } : {})
};
}
}
else {
//Descriptor txos (Unspent txos and Spent txos). Note that
//stxos have this format: `${txId}:${vout}:${recipientTxId}:${recipientVin}`
//so normalize to Utxo format:
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}`;
})
]);
if (txoSet.has(txo)) {
if (output)
throw new Error(`output {${descriptor}, ${index}} is already represented by {${output.descriptor}, ${output.index}} .`);
output = {
descriptor,
...(isRanged ? { index: Number(indexStr) } : {})
};
}
}
});

@@ -631,3 +705,2 @@ });

});
//console.log('TRACE', { scriptHash, txHistoryArray });
__classPrivateFieldSet(this, _Discovery_discoveryData, (0, immer_1.produce)(__classPrivateFieldGet(this, _Discovery_discoveryData, "f"), discoveryData => {

@@ -712,3 +785,6 @@ // Update txMap

else if (descriptor &&
!this.whenFetched({ descriptor, ...(index ? { index } : {}) }))
!this.whenFetched({
descriptor,
...(index !== undefined ? { index } : {})
}))
throw new Error(`Cannot derive data from ${descriptor}/${index} since it has not been previously fetched`);

@@ -715,0 +791,0 @@ };

import { DiscoveryFactory, DiscoveryInstance } from './discovery';
export { DiscoveryFactory, DiscoveryInstance };
export { OutputCriteria, TxStatus, Account, Utxo } from './types';
export { OutputCriteria, TxStatus, Account, Utxo, Stxo, TxAttribution } from './types';

@@ -60,2 +60,9 @@ /**

/**
* Type definition for Spent Transaction Output. Format:
* `${txId}:${vout}:${recipientTxId}:${recipientVin}`,
* that is, a previous Utxo ${txId}:${vout} was eventually spent in this tx:
* ${recipientTxId}:${recipientVin}
*/
export type Stxo = string;
/**
* Type definition for Transaction Information.

@@ -78,2 +85,60 @@ */

/**
* Represents the attribution details of a transaction.
*
* `TxAttribution` is used to mark the owner of the inputs and outputs for each
* transaction.
*
* This can be used in wallet apps to specify whether inputs are from owned
* outputs (e.g., change in a previous transaction) or come from third parties.
* Similarly, it specifies when outputs are either destined to third parties or
* correspond to internal change. This is useful because wallet apps typically
* show transaction history with "Sent" or "Received" labels, considering only
* ins/outs from third parties.
*
* - `ownedPrevTxo/ownedTxo` indicates the ownership of the previous output/next
* output:
* - `false` if the previous/next output cannot be described by one of the
* owned descriptors.
* - An object containing the descriptor and optional index (for ranged
* descriptors).
* - `value` is the amount received/sent in this input/output. `value` will not
* be set in inputs when inputs are not owned.
*
* - `netReceived` indicates the net amount received by the controlled
* descriptors in this transaction. If > 0, it means funds were received;
* otherwise, funds were sent.
*
* - `type`:
* - `CONSOLIDATED`: ALL inputs and outputs are from/to owned descriptors.
* - `RECEIVED_AND_SENT` if:
* - SOME outputs are NOT owned and SOME inputs are owned, and
* - SOME outputs are owned and SOME inputs are NOT owned.
* This is an edge case that typically won't occur in wallets.
* - `SENT`:
* - if there are SOME outputs NOT owned and SOME inputs are owned.
* - not `RECEIVED_AND_SENT`.
* - `RECEIVED`:
* - if there are SOME outputs owned and SOME inputs are NOT owned.
* - not `RECEIVED_AND_SENT`.
*
* Tip: You can use `getDescriptor({txo: owned})` to see what descriptor
* corresponds to `getDescriptor({txo: ins[x].ownedPrevTxo})` or
* `getDescriptor({txo: outs[y].ownedTxo})`.
*/
export type TxAttribution = {
txId: TxId;
blockHeight: number;
irreversible: boolean;
ins: Array<{
ownedPrevTxo: Utxo | false;
value?: number;
}>;
outs: Array<{
ownedTxo: Utxo | false;
value: number;
}>;
netReceived: number;
type: 'CONSOLIDATED' | 'RECEIVED' | 'SENT' | 'RECEIVED_AND_SENT';
};
/**
* Type definition for Script Public Key Information.

@@ -80,0 +145,0 @@ */

@@ -5,3 +5,3 @@ {

"homepage": "https://github.com/bitcoinerlab/discovery",
"version": "1.0.4",
"version": "1.1.0",
"author": "Jose-Luis Landabaso",

@@ -47,7 +47,7 @@ "license": "MIT",

"dependencies": {
"@bitcoinerlab/descriptors": "^2.0.1",
"@bitcoinerlab/explorer": "^0.1.2",
"@bitcoinerlab/secp256k1": "^1.0.5",
"@bitcoinerlab/descriptors": "^2.1.0",
"@bitcoinerlab/explorer": "^0.1.3",
"@bitcoinerlab/secp256k1": "^1.1.1",
"@types/memoizee": "^0.4.8",
"bitcoinjs-lib": "^6.1.3",
"bitcoinjs-lib": "^6.1.5",
"immer": "^9.0.21",

@@ -54,0 +54,0 @@ "lodash.clonedeep": "^4.5.0",

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc