@bitcoinerlab/discovery
Advanced tools
Comparing version 1.2.2 to 1.2.3
/// <reference types="node" /> | ||
import memoizee from 'memoizee'; | ||
import { NetworkId, Descriptor, Account, DescriptorIndex, DiscoveryData, TxStatus, DescriptorData, TxAttribution, TxId, TxData } from './types'; | ||
import { NetworkId, Descriptor, Account, DescriptorIndex, DiscoveryData, TxStatus, DescriptorData, TxAttribution, TxId, TxData, TxWithOrder, TxoMap } 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) => { | ||
txoMap: TxoMap; | ||
stxos: string[]; | ||
@@ -20,2 +21,3 @@ utxos: string[]; | ||
utxos: string[]; | ||
txoMap: TxoMap; | ||
balance: number; | ||
@@ -45,3 +47,10 @@ }; | ||
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>; | ||
transactionFromHex: ((txHex: string) => { | ||
tx: Transaction; | ||
txId: string; | ||
}) & memoizee.Memoized<(txHex: string) => { | ||
tx: Transaction; | ||
txId: string; | ||
}>; | ||
compareTxOrder: <TA extends TxWithOrder, TB extends TxWithOrder>(txWithOrderA: TA, txWithOrderB: TB) => number; | ||
}; |
@@ -71,2 +71,59 @@ "use strict"; | ||
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) => { | ||
@@ -85,13 +142,14 @@ const network = (0, networks_1.getNetwork)(networkId); | ||
const deriveScriptPubKey = (networkId, descriptor, index) => deriveScriptPubKeyFactory(networkId)(descriptor)(index); | ||
const coreDeriveTxosByOutput = (networkId, descriptor, index, txDataArray, txStatus) => { | ||
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 txDataArray which have been spent. | ||
//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 txDataArray cannot be assumed to be in correct order. See: | ||
//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 | ||
//TODO: but we should guarantee same order always so use txId as second order criteria? - probably not needed? | ||
for (const txData of txDataArray) { | ||
for (const txData of txWithOrderArray) { | ||
if (txStatus === types_1.TxStatus.ALL || | ||
@@ -103,10 +161,5 @@ (txStatus === types_1.TxStatus.IRREVERSIBLE && txData.irreversible) || | ||
throw new Error(`txHex not yet retrieved for an element of ${descriptor}, ${index}`); | ||
const tx = transactionFromHex(txHex); | ||
const txId = tx.getId(); | ||
for (let vin = 0; vin < tx.ins.length; vin++) { | ||
const input = tx.ins[vin]; | ||
if (!input) | ||
throw new Error(`Error: invalid input for ${txId}:${vin}`); | ||
//Note we create a new Buffer since reverse() mutates the Buffer | ||
const prevTxId = Buffer.from(input.hash).reverse().toString('hex'); | ||
const { tx, txId } = transactionFromHex(txHex); | ||
for (const [vin, input] of tx.ins.entries()) { | ||
const prevTxId = buf2RevHex(input.hash); | ||
const prevVout = input.index; | ||
@@ -116,9 +169,7 @@ const prevUtxo = `${prevTxId}:${prevVout}`; | ||
} | ||
for (let vout = 0; vout < tx.outs.length; vout++) { | ||
const outputScript = tx.outs[vout]?.script; | ||
if (!outputScript) | ||
throw new Error(`Error: invalid output script for ${txId}:${vout}`); | ||
if (outputScript.equals(scriptPubKey)) { | ||
const outputKey = `${txId}:${vout}`; | ||
allPrevOutputs.push(outputKey); | ||
for (const [vout, output] of tx.outs.entries()) { | ||
if (output.script.equals(scriptPubKey)) { | ||
const txo = `${txId}:${vout}`; | ||
allPrevOutputs.push(txo); | ||
txoMap[txo] = `${descriptor}~${index}`; | ||
} | ||
@@ -133,3 +184,3 @@ } | ||
.map(txo => `${txo}:${spendingTxIdByOutput[txo]}`); | ||
return { utxos, stxos }; | ||
return { utxos, stxos, txoMap }; | ||
}; | ||
@@ -139,4 +190,4 @@ const deriveUtxosAndBalanceByOutputFactory = (0, memoizee_1.default)((networkId) => (0, memoizee_1.default)((txStatus) => (0, memoizee_1.default)((descriptor) => (0, memoizee_1.default)((index) => { | ||
// coreDeriveTxosByOutput shares all params wrt the parent | ||
// function except for additional param txDataArray. | ||
// As soon as txDataArray in coreDeriveTxosByOutput changes, | ||
// function except for additional param txWithOrderArray. | ||
// As soon as txWithOrderArray in coreDeriveTxosByOutput changes, | ||
// it will resets its memory. | ||
@@ -148,7 +199,10 @@ const deriveTxosByOutput = (0, memoizee_1.default)(coreDeriveTxosByOutput, { | ||
let lastStxos = null; | ||
let lastTxoMap = null; | ||
let lastBalance; | ||
return (0, memoizee_1.default)((txMap, descriptorMap) => { | ||
const txDataArray = deriveTxDataArray(txMap, descriptorMap, descriptor, index); | ||
let { utxos, stxos } = deriveTxosByOutput(networkId, descriptor, index, txDataArray, txStatus); | ||
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)) | ||
@@ -162,6 +216,7 @@ stxos = lastStxos; | ||
balance = coreDeriveUtxosBalance(txMap, utxos); | ||
lastTxoMap = txoMap; | ||
lastUtxos = utxos; | ||
lastStxos = stxos; | ||
lastBalance = balance; | ||
return { stxos, utxos, balance }; | ||
return { txoMap, stxos, utxos, balance }; | ||
}, { max: 1 }); | ||
@@ -182,4 +237,4 @@ }, { primitive: true, max: outputsPerDescriptorCacheSize }), { primitive: true, max: descriptorsCacheSize }), { primitive: true } //unbounded cache (no max setting) since Search Space is small | ||
const txIds = range[index]?.txIds || []; | ||
const txDataArray = coreDeriveTxDataArray(txIds, txMap); | ||
return txDataArray; | ||
const txWithOrderArray = coreDeriveTxDataArray(txIds, txMap); | ||
return txWithOrderArray; | ||
}); | ||
@@ -206,6 +261,5 @@ }, { primitive: true, max: outputsPerDescriptorCacheSize }), { primitive: true, max: descriptorsCacheSize }); | ||
throw new Error(`Error: txHex not found`); | ||
const tx = transactionFromHex(txHex); | ||
const txId = tx.getId(); | ||
const { tx, txId } = transactionFromHex(txHex); | ||
const ins = tx.ins.map(input => { | ||
const prevTxId = Buffer.from(input.hash).reverse().toString('hex'); | ||
const prevTxId = buf2RevHex(input.hash); | ||
const prevVout = input.index; | ||
@@ -220,3 +274,3 @@ const prevTxo = `${prevTxId}:${prevVout}`; | ||
throw new Error(`txHex not set for ${prevTxId}`); | ||
const prevTx = transactionFromHex(prevTxHex); | ||
const { tx: prevTx } = transactionFromHex(prevTxHex); | ||
const value = prevTx.outs[prevVout]?.value; | ||
@@ -288,6 +342,7 @@ if (value === undefined) | ||
txData.blockHeight !== 0)); | ||
const sortedHistory = txHistory.sort(compareTxOrder); | ||
if (withAttributions) | ||
return deriveAttributions(txHistory, networkId, txMap, descriptorMap, descriptor, txStatus); | ||
return deriveAttributions(sortedHistory, networkId, txMap, descriptorMap, descriptor, txStatus); | ||
else | ||
return txHistory; | ||
return sortedHistory; | ||
}); | ||
@@ -327,16 +382,6 @@ }, { | ||
//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? - 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 | ||
}); | ||
//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) | ||
@@ -357,2 +402,3 @@ return deriveAttributions(sortedHistory, networkId, txMap, descriptorMap, descriptorOrDescriptors, txStatus); | ||
const stxos = []; | ||
const txoMap = {}; | ||
const descriptorArray = Array.isArray(descriptorOrDescriptors) | ||
@@ -367,5 +413,6 @@ ? descriptorOrDescriptors | ||
const index = indexStr === 'non-ranged' ? indexStr : Number(indexStr); | ||
const { utxos: utxosByO, stxos: stxosByO } = deriveUtxosAndBalanceByOutput(networkId, txMap, descriptorMap, descriptor, index, txStatus); | ||
const { utxos: utxosByO, stxos: stxosByO, txoMap: txoMapByO } = deriveUtxosAndBalanceByOutput(networkId, txMap, descriptorMap, descriptor, index, txStatus); | ||
utxos.push(...utxosByO); | ||
stxos.push(...stxosByO); | ||
Object.assign(txoMap, txoMapByO); | ||
}); | ||
@@ -377,3 +424,3 @@ } | ||
const dedupedStxos = [...new Set(stxos)]; | ||
return { utxos: dedupedUtxos, stxos: dedupedStxos }; | ||
return { utxos: dedupedUtxos, stxos: dedupedStxos, txoMap }; | ||
}; | ||
@@ -385,2 +432,3 @@ //unbound memoizee wrt TxStatus is fine since it has a small Search Space | ||
const deriveUtxosAndBalanceFactory = (0, memoizee_1.default)((networkId) => (0, memoizee_1.default)((txStatus) => (0, memoizee_1.default)((descriptorOrDescriptors) => { | ||
let lastTxoMap = null; | ||
let lastUtxos = null; | ||
@@ -390,4 +438,6 @@ let lastStxos = null; | ||
return (0, memoizee_1.default)((txMap, descriptorMap) => { | ||
let { utxos, stxos } = coreDeriveTxos(networkId, descriptorMap, txMap, descriptorOrDescriptors, txStatus); | ||
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)) | ||
@@ -401,6 +451,7 @@ stxos = lastStxos; | ||
balance = coreDeriveUtxosBalance(txMap, utxos); | ||
lastTxoMap = txoMap; | ||
lastUtxos = utxos; | ||
lastStxos = stxos; | ||
lastBalance = balance; | ||
return { stxos, utxos, balance }; | ||
return { stxos, utxos, txoMap, balance }; | ||
}, { max: 1 }); | ||
@@ -412,6 +463,23 @@ }, { primitive: true, max: descriptorsCacheSize } //potentially ininite search space. limit to 100 descriptorOrDescriptors per txStatus combination | ||
const deriveUtxosAndBalance = (networkId, txMap, descriptorMap, descriptorOrDescriptors, txStatus) => deriveUtxosAndBalanceFactory(networkId)(txStatus)(descriptorOrDescriptors)(txMap, descriptorMap); | ||
const transactionFromHex = (0, memoizee_1.default)(bitcoinjs_lib_1.Transaction.fromHex, { | ||
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) => { | ||
@@ -435,3 +503,3 @@ let balance = 0; | ||
throw new Error(`txHex not yet retrieved for ${txId}`); | ||
const tx = transactionFromHex(txHex); | ||
const { tx } = transactionFromHex(txHex); | ||
const output = tx.outs[vout]; | ||
@@ -536,5 +604,6 @@ if (!output) | ||
deriveHistory, | ||
transactionFromHex | ||
transactionFromHex, | ||
compareTxOrder | ||
}; | ||
} | ||
exports.deriveDataFactory = deriveDataFactory; |
@@ -6,3 +6,3 @@ /// <reference types="node" /> | ||
import type { Explorer } from '@bitcoinerlab/explorer'; | ||
import { OutputCriteria, NetworkId, TxId, TxData, Descriptor, Account, DescriptorIndex, DiscoveryData, Utxo, TxStatus, Stxo, TxHex, TxAttribution } from './types'; | ||
import { OutputCriteria, NetworkId, TxId, TxData, Descriptor, Account, DescriptorIndex, DiscoveryData, Utxo, TxStatus, Stxo, TxHex, TxAttribution, TxWithOrder, TxoMap } from './types'; | ||
/** | ||
@@ -369,2 +369,6 @@ * Creates and returns a Discovery class for discovering funds in a Bitcoin network | ||
* eventually spent as stxos: Array<Stxo> | ||
* Finally, it returns `txoMap`. `txoMap` maps all the txos (unspent or spent | ||
* outputs) with their corresponding `indexedDescriptor: IndexedDescriptor` | ||
* (see {@link IndexedDescriptor IndexedDescriptor}) | ||
* | ||
*/ | ||
@@ -374,2 +378,3 @@ getUtxosAndBalance({ descriptor, index, descriptors, txStatus }: OutputCriteria): { | ||
stxos: Array<Stxo>; | ||
txoMap: TxoMap; | ||
balance: number; | ||
@@ -474,4 +479,7 @@ }; | ||
* object given the transaction | ||
* ID (TxId) or a Unspent Transaction Output (Utxo). The transaction data is obtained by first getting | ||
* the transaction hexadecimal representation using getTxHex() method. | ||
* ID (TxId) or a Unspent Transaction Output (Utxo) or the hexadecimal | ||
* representation of the transaction (it will then use memoization). | ||
* The transaction data is obtained by first getting | ||
* the transaction hexadecimal representation using getTxHex() method | ||
* (unless the txHex was passed). | ||
* | ||
@@ -486,3 +494,3 @@ * Use this method for quick access to the Transaction object, which avoids the | ||
*/ | ||
getTransaction({ txId, utxo }: { | ||
getTransaction({ txId, txHex, utxo }: { | ||
/** | ||
@@ -493,2 +501,6 @@ * The transaction ID. | ||
/** | ||
* The transaction txHex. | ||
*/ | ||
txHex?: TxId; | ||
/** | ||
* The UTXO. | ||
@@ -499,2 +511,17 @@ */ | ||
/** | ||
* 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, > 0 if | ||
* txWithOrderA is newer than txWithOrderB, and 0 if undecided. | ||
*/ | ||
compareTxOrder<TA extends TxWithOrder, TB extends TxWithOrder>(txWithOrderA: TA, txWithOrderB: TB): number; | ||
/** | ||
* Given an unspent tx output, this function retrieves its descriptor (if still unspent). | ||
@@ -506,4 +533,2 @@ * Alternatively, pass a txo (any transaction output, which may have been | ||
* | ||
* This query can be quite slow so use wisely. | ||
* | ||
* Returns the descriptor (and index if ranged) or undefined if not found. | ||
@@ -510,0 +535,0 @@ */ |
@@ -333,2 +333,6 @@ "use strict"; | ||
* eventually spent as stxos: Array<Stxo> | ||
* Finally, it returns `txoMap`. `txoMap` maps all the txos (unspent or spent | ||
* outputs) with their corresponding `indexedDescriptor: IndexedDescriptor` | ||
* (see {@link IndexedDescriptor IndexedDescriptor}) | ||
* | ||
*/ | ||
@@ -450,11 +454,11 @@ getUtxosAndBalance({ descriptor, index, descriptors, txStatus = types_1.TxStatus.ALL }) { | ||
const txMap = __classPrivateFieldGet(this, _Discovery_discoveryData, "f")[networkId].txMap; | ||
let txDataArray = []; | ||
let txWithOrderArray = []; | ||
if (descriptor && | ||
(typeof index !== 'undefined' || !descriptor.includes('*'))) { | ||
const internalIndex = typeof index === 'number' ? index : 'non-ranged'; | ||
txDataArray = __classPrivateFieldGet(this, _Discovery_derivers, "f").deriveHistoryByOutput(withAttributions, networkId, txMap, descriptorMap, descriptorOrDescriptors, internalIndex, txStatus); | ||
txWithOrderArray = __classPrivateFieldGet(this, _Discovery_derivers, "f").deriveHistoryByOutput(withAttributions, networkId, txMap, descriptorMap, descriptorOrDescriptors, internalIndex, txStatus); | ||
} | ||
else | ||
txDataArray = __classPrivateFieldGet(this, _Discovery_derivers, "f").deriveHistory(withAttributions, networkId, txMap, descriptorMap, descriptorOrDescriptors, txStatus); | ||
return txDataArray; | ||
txWithOrderArray = __classPrivateFieldGet(this, _Discovery_derivers, "f").deriveHistory(withAttributions, networkId, txMap, descriptorMap, descriptorOrDescriptors, txStatus); | ||
return txWithOrderArray; | ||
} | ||
@@ -486,4 +490,7 @@ /** | ||
* object given the transaction | ||
* ID (TxId) or a Unspent Transaction Output (Utxo). The transaction data is obtained by first getting | ||
* the transaction hexadecimal representation using getTxHex() method. | ||
* ID (TxId) or a Unspent Transaction Output (Utxo) or the hexadecimal | ||
* representation of the transaction (it will then use memoization). | ||
* The transaction data is obtained by first getting | ||
* the transaction hexadecimal representation using getTxHex() method | ||
* (unless the txHex was passed). | ||
* | ||
@@ -498,10 +505,28 @@ * Use this method for quick access to the Transaction object, which avoids the | ||
*/ | ||
getTransaction({ txId, utxo }) { | ||
const txHex = this.getTxHex({ | ||
...(utxo ? { utxo } : {}), | ||
...(txId ? { txId } : {}) | ||
}); | ||
return __classPrivateFieldGet(this, _Discovery_derivers, "f").transactionFromHex(txHex); | ||
getTransaction({ txId, txHex, utxo }) { | ||
if (!txHex) | ||
txHex = this.getTxHex({ | ||
...(utxo ? { utxo } : {}), | ||
...(txId ? { txId } : {}) | ||
}); | ||
return __classPrivateFieldGet(this, _Discovery_derivers, "f").transactionFromHex(txHex).tx; | ||
} | ||
/** | ||
* 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, > 0 if | ||
* txWithOrderA is newer than txWithOrderB, and 0 if undecided. | ||
*/ | ||
compareTxOrder(txWithOrderA, txWithOrderB) { | ||
return __classPrivateFieldGet(this, _Discovery_derivers, "f").compareTxOrder(txWithOrderA, txWithOrderB); | ||
} | ||
/** | ||
* Given an unspent tx output, this function retrieves its descriptor (if still unspent). | ||
@@ -513,4 +538,2 @@ * Alternatively, pass a txo (any transaction output, which may have been | ||
* | ||
* This query can be quite slow so use wisely. | ||
* | ||
* Returns the descriptor (and index if ranged) or undefined if not found. | ||
@@ -527,3 +550,5 @@ */ | ||
const split = txo.split(':'); | ||
if (split.length !== 2) | ||
if (utxo && split.length !== 2) | ||
throw new Error(`Error: invalid utxo: ${utxo}`); | ||
if (!utxo && split.length !== 3) | ||
throw new Error(`Error: invalid txo: ${txo}`); | ||
@@ -539,57 +564,21 @@ const txId = split[0]; | ||
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; | ||
const descriptors = __classPrivateFieldGet(this, _Discovery_derivers, "f").deriveUsedDescriptors(__classPrivateFieldGet(this, _Discovery_discoveryData, "f"), networkId); | ||
let output; | ||
descriptors.forEach(descriptor => { | ||
const range = descriptorMap[descriptor]?.range || | ||
{}; | ||
Object.keys(range).forEach(indexStr => { | ||
const isRanged = indexStr !== 'non-ranged'; | ||
const index = isRanged && Number(indexStr); | ||
if (!txo) | ||
throw new Error('txo not defined'); | ||
const { utxos, stxos } = this.getUtxosAndBalance({ | ||
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) } : {}) | ||
}; | ||
} | ||
} | ||
}); | ||
}); | ||
const { txoMap } = this.getUtxosAndBalance({ descriptors }); | ||
const indexedDescriptor = txoMap[txo]; | ||
if (indexedDescriptor) { | ||
const splitTxo = (str) => { | ||
const lastIndex = str.lastIndexOf('~'); | ||
if (lastIndex === -1) | ||
throw new Error(`Separator '~' not found in string`); | ||
return [str.slice(0, lastIndex), str.slice(lastIndex + 1)]; | ||
}; | ||
const [descriptor, internalIndex] = splitTxo(indexedDescriptor); | ||
output = { | ||
descriptor, | ||
...(internalIndex === 'non-ranged' | ||
? {} | ||
: { index: Number(internalIndex) }) | ||
}; | ||
} | ||
return output; | ||
@@ -613,4 +602,3 @@ } | ||
const DETECT_RETRY_MAX = 20; | ||
const tx = __classPrivateFieldGet(this, _Discovery_derivers, "f").transactionFromHex(txHex); | ||
const txId = tx.getId(); | ||
const { txId } = __classPrivateFieldGet(this, _Discovery_derivers, "f").transactionFromHex(txHex); | ||
await explorer.push(txHex); | ||
@@ -664,4 +652,3 @@ //Now, make sure it made it to the mempool: | ||
throw new Error('txData must contain complete txHex information'); | ||
const tx = __classPrivateFieldGet(this, _Discovery_derivers, "f").transactionFromHex(txHex); | ||
const txId = tx.getId(); | ||
const { tx, txId } = __classPrivateFieldGet(this, _Discovery_derivers, "f").transactionFromHex(txHex); | ||
const networkId = (0, networks_1.getNetworkId)(network); | ||
@@ -668,0 +655,0 @@ __classPrivateFieldSet(this, _Discovery_discoveryData, (0, immer_1.produce)(__classPrivateFieldGet(this, _Discovery_discoveryData, "f"), discoveryData => { |
import { DiscoveryFactory, DiscoveryInstance } from './discovery'; | ||
export { DiscoveryFactory, DiscoveryInstance }; | ||
export { OutputCriteria, TxStatus, Account, Utxo, Stxo, TxAttribution } from './types'; | ||
export { TxWithOrder, OutputCriteria, TxStatus, Account, TxoMap, Utxo, Stxo, IndexedDescriptor, TxAttribution } from './types'; |
@@ -0,1 +1,2 @@ | ||
import type { Transaction } from 'bitcoinjs-lib'; | ||
/** | ||
@@ -52,2 +53,25 @@ * Versions the structure of the data model. This variable should to be | ||
/** | ||
* A string representing an indexed descriptor for ranged descriptors or a | ||
* descriptor followed by a separator and the keyword "non-ranged". | ||
* | ||
* An `IndexedDescriptor` is a descriptor representation what must correspond to | ||
* a single output. | ||
* | ||
* - If it is ranged, then add an integer after the separaror (a | ||
* tilde "\~"). | ||
* - It it is non-ranged, add the string "non-ranged" after the tilde "\~". | ||
* | ||
* Examples: | ||
* pkh([73c5da0a/44'/1'/0']tpubDC5FSnBiZDMmhiuCmWAYsLwgLYrrT9rAqvTySfuCCrgsWz8wxMXUS9Tb9iVMvcRbvFcAHGkMD5Kx8koh4GquNGNTfohfk7pgjhaPCdXpoba/0/*)\~12 | ||
* pkh([73c5da0a/44'/1'/0']tpubDC5FSnBiZDMmhiuCmWAYsLwgLYrrT9rAqvTySfuCCrgsWz8wxMXUS9Tb9iVMvcRbvFcAHGkMD5Kx8koh4GquNGNTfohfk7pgjhaPCdXpoba)\~non-ranged | ||
*/ | ||
export type IndexedDescriptor = string; | ||
/** | ||
* a Txo is represented in a similar manner as a Utxo, that is, | ||
* prevtxId:vout. Hovewer, we use a different type name to denote we're dealing | ||
* here with tx outputs that may have been spent or not | ||
*/ | ||
type Txo = string; | ||
export type TxoMap = Record<Txo, IndexedDescriptor>; | ||
/** | ||
* Type definition for Transaction ID. | ||
@@ -67,2 +91,7 @@ */ | ||
export type Stxo = string; | ||
export type TxWithOrder = { | ||
blockHeight: number; | ||
tx?: Transaction; | ||
txHex?: string; | ||
}; | ||
/** | ||
@@ -205,1 +234,2 @@ * Type definition for Transaction Information. | ||
export type DiscoveryData = Record<NetworkId, NetworkData>; | ||
export {}; |
@@ -5,3 +5,3 @@ { | ||
"homepage": "https://github.com/bitcoinerlab/discovery", | ||
"version": "1.2.2", | ||
"version": "1.2.3", | ||
"author": "Jose-Luis Landabaso", | ||
@@ -8,0 +8,0 @@ "license": "MIT", |
147673
2564