UNPKG

@ledgerhq/coin-tron

Version:
68 lines 3.46 kB
import { promiseAllBatched } from "@ledgerhq/live-promise"; import uniqBy from "lodash/uniqBy"; import { fetchTronAccountTxsPage, getBlock } from "../network"; import { fromTrongridTxInfoToOperation } from "../network/trongrid/trongrid-adapters"; import { compareTxsByTimestamp, dropTxsAfterNextCursor, dropTxsBeforeCursor, parseCursor, } from "./cursor"; import { TronEmptyPage } from "../types/errors"; export async function listOperations(address, options) { const { limit, order, cursor } = options; const parsedCursor = parseCursor(cursor); // For asc: cursor timestamp is the new lower bound (we're moving forward in time) // For desc: cursor timestamp is the new upper bound (we're moving backward in time) // minTimestamp remains the lower bound (the stopping point) let fetchMinTimestamp; let fetchMaxTimestamp; if (order === "asc") { fetchMinTimestamp = parsedCursor ? parsedCursor.timestamp : options.minTimestamp; fetchMaxTimestamp = undefined; } else { fetchMinTimestamp = options.minTimestamp; fetchMaxTimestamp = parsedCursor?.timestamp; } // Fetch native and TRC20 transactions in parallel from TronGrid. // Both endpoints are queried with the same timestamp bounds to ensure // we can properly merge and sort them chronologically. const { nativeTxs, trc20Txs } = await fetchTronAccountTxsPage(address, { limit, minTimestamp: fetchMinTimestamp, maxTimestamp: fetchMaxTimestamp, order, }); // TronGrid occasionally returns an empty page for a valid cursor (transient API failure). // A cursor is only issued when TronGrid indicated hasNextPage=true, so 0 results here // is never a legitimate end-of-stream — throw so the client can retry with the same cursor. if (parsedCursor && nativeTxs.txs.length === 0 && trc20Txs.txs.length === 0) { throw new TronEmptyPage(`TronGrid returned empty page for cursor ${cursor} — transient failure, retry required`); } // Merge and dedupe: some transactions appear in both native and TRC20 results const mergedTxs = uniqBy([...nativeTxs.txs, ...trc20Txs.txs], tx => tx.txID); const sortedTxs = [...mergedTxs].sort(compareTxsByTimestamp(order)); const afterCursorTxs = dropTxsBeforeCursor({ txs: sortedTxs, order, cursor: parsedCursor }); const { txs: pageTxs, nextCursor } = dropTxsAfterNextCursor({ order, cursor, pageTxs: afterCursorTxs, nativeResult: nativeTxs, trc20Result: trc20Txs, }); const blocksByHeight = new Map(); const uniqueHeights = Array.from(new Set(pageTxs.map(tx => tx.blockHeight).filter((h) => typeof h === "number"))); await promiseAllBatched(5, uniqueHeights, async (height) => { const fetchedBlock = await getBlock(height); blocksByHeight.set(height, fetchedBlock); }); const operations = pageTxs.map(tx => { const height = tx.blockHeight; if (typeof height !== "number") { throw new Error(`Transaction ${tx.txID} has no block height`); } const txBlock = blocksByHeight.get(height); if (!txBlock) { throw new Error(`Block ${height} not found for transaction ${tx.txID}`); } return fromTrongridTxInfoToOperation(tx, txBlock, address); }); return { items: operations, next: nextCursor }; } //# sourceMappingURL=listOperations.js.map