mirror of
https://github.com/bitcoinresearchkit/brk.git
synced 2026-07-01 14:29:01 -07:00
120 lines
2.5 KiB
JavaScript
120 lines
2.5 KiB
JavaScript
import { mapConcurrent } from "../../concurrent.js";
|
|
|
|
const UTXO_CONCURRENCY = 4;
|
|
|
|
const utxosByBucketKey =
|
|
/** @type {Map<string, Promise<Map<string, ApiUtxo[]>>>} */ (new Map());
|
|
|
|
/**
|
|
* @typedef {import("../../scan/index.js").WalletAddress} WalletAddress
|
|
*/
|
|
|
|
/**
|
|
* @typedef {Object} ApiUtxoStatus
|
|
* @property {boolean} confirmed
|
|
*
|
|
* @typedef {Object} ApiUtxo
|
|
* @property {string} txid
|
|
* @property {number} vout
|
|
* @property {number} value
|
|
* @property {ApiUtxoStatus} status
|
|
*/
|
|
|
|
/**
|
|
* @typedef {Object} UtxoClient
|
|
* @property {(address: string, options?: { cache?: boolean }) => Promise<ApiUtxo[]>} getAddressUtxos
|
|
*/
|
|
|
|
/**
|
|
* @typedef {Object} AddressUtxos
|
|
* @property {ApiUtxo[]} utxos
|
|
*/
|
|
|
|
/**
|
|
* @param {unknown} error
|
|
*/
|
|
function isNotFound(error) {
|
|
return (
|
|
error instanceof Error &&
|
|
/** @type {{ status?: unknown }} */ (error).status === 404
|
|
);
|
|
}
|
|
|
|
/**
|
|
* @param {readonly string[]} addresses
|
|
*/
|
|
function createBucketKey(addresses) {
|
|
return [...addresses].sort().join("\n");
|
|
}
|
|
|
|
/**
|
|
* @param {UtxoClient} client
|
|
* @param {string} address
|
|
* @returns {Promise<ApiUtxo[]>}
|
|
*/
|
|
async function fetchAddressUtxos(client, address) {
|
|
try {
|
|
return await client.getAddressUtxos(address, { cache: false });
|
|
} catch (error) {
|
|
if (isNotFound(error)) return [];
|
|
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @param {UtxoClient} client
|
|
* @param {readonly string[]} addresses
|
|
* @returns {Promise<Map<string, ApiUtxo[]>>}
|
|
*/
|
|
async function fetchBucketUtxos(client, addresses) {
|
|
const entries = await mapConcurrent(
|
|
addresses,
|
|
UTXO_CONCURRENCY,
|
|
async (address) => {
|
|
return /** @type {const} */ ([
|
|
address,
|
|
await fetchAddressUtxos(client, address),
|
|
]);
|
|
},
|
|
);
|
|
|
|
return new Map(entries);
|
|
}
|
|
|
|
/**
|
|
* @param {UtxoClient} client
|
|
* @param {WalletAddress} address
|
|
* @returns {Promise<AddressUtxos>}
|
|
*/
|
|
async function load(client, address) {
|
|
if (address.balance <= 0 || address.historyAddresses.length === 0) {
|
|
return {
|
|
utxos: [],
|
|
};
|
|
}
|
|
|
|
const key = createBucketKey(address.historyAddresses);
|
|
let utxos = utxosByBucketKey.get(key);
|
|
|
|
if (!utxos) {
|
|
utxos = fetchBucketUtxos(client, address.historyAddresses).catch(
|
|
(error) => {
|
|
utxosByBucketKey.delete(key);
|
|
throw error;
|
|
},
|
|
);
|
|
utxosByBucketKey.set(key, utxos);
|
|
}
|
|
|
|
const bucketUtxos = await utxos;
|
|
|
|
return {
|
|
utxos: bucketUtxos.get(address.address) ?? [],
|
|
};
|
|
}
|
|
|
|
export const utxoCache = /** @type {const} */ ({
|
|
load,
|
|
});
|