From 1192011611a23d517babe6a6ce37b3e0ce0f6d53 Mon Sep 17 00:00:00 2001 From: Daniele Ricci Date: Wed, 8 Jan 2025 09:59:41 +0100 Subject: [PATCH] feat: add mempool support to GET_ACCOUNT_INFO txids --- README.md | 3 +- src/methods/get-account-info.ts | 6 ++- src/types/message.ts | 1 + src/utils/account.ts | 32 +++++++++--- src/utils/address.ts | 88 ++++++++++++++++++++++----------- 5 files changed, 91 insertions(+), 39 deletions(-) diff --git a/README.md b/README.md index 4a47a4d2..1d04a695 100644 --- a/README.md +++ b/README.md @@ -218,10 +218,11 @@ Input message: "command": "GET_ACCOUNT_INFO"; "params": { "descriptor": string; // account public key in hex (eg. 6d17587575a3b4f0f86ebad3977e8f7e4981faa863eccf5c1467065c74fe3435943769446dd290d103fb3d360128e86de4b47faea73ffb0900c94c6a61ef9ea2) - "details": 'basic' | 'tokens' | 'tokenBalances' |'txids' | 'txs'; + "details": 'basic' | 'tokens' | 'tokenBalances' | 'txids' | 'txs'; "page"?: number; // optional, default 1 "pageSize"?: number; // optional, default 20 "cbor"?: boolean; // optional, get CBOR representation of transactions + "mempool"?: boolean; // optional, get info from mempool as well - WIP - works only with details 'txids' } } ``` diff --git a/src/methods/get-account-info.ts b/src/methods/get-account-info.ts index 494588d5..f1ae0379 100644 --- a/src/methods/get-account-info.ts +++ b/src/methods/get-account-info.ts @@ -21,6 +21,7 @@ export const getAccountInfo = async ( page = 1, pageSize = 25, cbor?: boolean, + mempool?: boolean, ): Promise => { let _addressesCount = 0; const tStart = Date.now(); @@ -104,7 +105,7 @@ export const getAccountInfo = async ( _addressesCount = addresses.length; // just a debug helper - const txids = await getTxidsFromAccountAddresses(addresses, accountEmpty); + const txids = await getTxidsFromAccountAddresses(addresses, accountEmpty, mempool); const paginatedTxsIds = paginate(txids, pageSizeNumber); const requestedPageTxIds = paginatedTxsIds[pageIndex] ?? []; @@ -157,8 +158,9 @@ export default async ( page = 1, pageSize = 25, cbor?: boolean, + mempool?: boolean, ): Promise => { - const data = await getAccountInfo(publicKey, details, page, pageSize, cbor); + const data = await getAccountInfo(publicKey, details, page, pageSize, cbor, mempool); const message = prepareMessage({ id, clientId, data }); return message; diff --git a/src/types/message.ts b/src/types/message.ts index 642d8cb7..b1562345 100644 --- a/src/types/message.ts +++ b/src/types/message.ts @@ -15,6 +15,7 @@ export type Messages = BaseMessage & page?: number; pageSize?: number; cbor?: boolean; + mempool?: boolean; }; } | { diff --git a/src/utils/account.ts b/src/utils/account.ts index a22f0336..099e696a 100644 --- a/src/utils/account.ts +++ b/src/utils/account.ts @@ -1,17 +1,25 @@ import { Address } from '../types/address.js'; -import { Responses } from '@blockfrost/blockfrost-js'; -import { addressesToTxIds, discoverAddresses, getAddressesData } from './address.js'; +import { + AddressesToTxIds, + addressesToTxIds, + discoverAddresses, + getAddressesData, +} from './address.js'; -export const getTxidsFromAccountAddresses = async (addresses: Address[], accountEmpty: boolean) => { +export const getTxidsFromAccountAddresses = async ( + addresses: Address[], + accountEmpty: boolean, + mempool?: boolean, +) => { const uniqueTxIds: ({ address: string; - } & Responses['address_transactions_content'][number])[] = []; + } & AddressesToTxIds[number]['data'][number])[] = []; if (accountEmpty) { return []; } - const transactionsPerAddressList = await addressesToTxIds(addresses); + const transactionsPerAddressList = await addressesToTxIds(addresses, mempool); // filter only unique txs to prevent counting a transaction that was sent from and to the same account twice for (const txsPerAddress of transactionsPerAddressList) { @@ -22,9 +30,17 @@ export const getTxidsFromAccountAddresses = async (addresses: Address[], account } } - const sortedTxIds = uniqueTxIds.sort( - (first, second) => second.block_height - first.block_height || second.tx_index - first.tx_index, - ); + const sortedTxIds = uniqueTxIds.sort((first, second) => { + if (first.mempool) { + return -1; + } + + if (second.mempool) { + return 1; + } + + return second.block_height - first.block_height || second.tx_index - first.tx_index; + }); return sortedTxIds; }; diff --git a/src/utils/address.ts b/src/utils/address.ts index a9439316..47b6719c 100644 --- a/src/utils/address.ts +++ b/src/utils/address.ts @@ -183,40 +183,72 @@ export const utxosWithBlocks = async ( return result; }; +export type AddressesToTxIds = { + address: string; + data: ( + | (Responses['address_transactions_content'][number] & { mempool?: boolean }) + | { tx_hash: string; mempool: true } + )[]; +}[]; + export const addressesToTxIds = async ( addresses: Addresses.Address[], -): Promise<{ address: string; data: Responses['address_transactions_content'] }[]> => { - const promisesBundle: Promise<{ - address: string; - data: Responses['address_transactions_content']; - }>[] = []; - - for (const { address, data: status } of addresses) { - if (status === 'empty') { - continue; - } - - const promise = limiter(() => - // 1 page (100 txs) per address at a time should be more efficient default value - // compared to fetching 10 pages (1000 txs) per address - blockfrostAPI - .addressesTransactionsAll(address, { batchSize: 1 }) - .then(data => ({ address, data })) - .catch(error => { - if (error instanceof BlockfrostServerError && error.status_code === 404) { - return { address, data: [] }; - } else { - throw error; - } - }), - ); + mempool?: boolean, +): Promise => { + const result = await Promise.all( + addresses + .filter(({ data }) => data !== 'empty') + .map(({ address }) => + limiter(() => + // 1 page (100 txs) per address at a time should be more efficient default value + // compared to fetching 10 pages (1000 txs) per address + blockfrostAPI + .addressesTransactionsAll(address, { batchSize: 1 }) + .then(data => ({ address, data })) + .catch(error => { + if (error instanceof BlockfrostServerError && error.status_code === 404) { + return { address, data: [] }; + } else { + throw error; + } + }), + ), + ), + ); - promisesBundle.push(promise); + if (!mempool) { + return result; } - const result = await Promise.all(promisesBundle); + // Fetch the txids list from mempool + const mempoolResult = (await Promise.all( + addresses.map(({ address }) => + limiter(() => + blockfrostAPI + .mempoolByAddress(address) + .then(data => ({ address, data: data.map(element => ({ ...element, mempool: true })) })) + .catch(error => { + if (error instanceof BlockfrostServerError && error.status_code === 404) { + return { address, data: [] }; + } else { + throw error; + } + }), + ), + ), + )) as AddressesToTxIds; - return result; + let resultIdx = 0; + + for (const element of mempoolResult) { + // If the address is empty, result do not contains an entry for it + if (result[resultIdx].address === element.address) { + element.data = [...element.data, ...result[resultIdx].data]; + resultIdx++; + } + } + + return mempoolResult; }; export const getAddressesData = async (