@defillama/sdk 5.0.235 → 5.0.238

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (69) hide show
  1. package/build/ChainApi.d.ts +23 -1
  2. package/build/ChainApi.js +13 -0
  3. package/build/abi/tron.js +7 -1
  4. package/build/chains/algorand.d.ts +277 -0
  5. package/build/chains/algorand.js +618 -0
  6. package/build/chains/aptos.d.ts +230 -0
  7. package/build/chains/aptos.js +612 -0
  8. package/build/chains/cardano.d.ts +242 -0
  9. package/build/chains/cardano.js +407 -0
  10. package/build/chains/cosmos.d.ts +252 -0
  11. package/build/chains/cosmos.js +618 -0
  12. package/build/chains/icp.d.ts +235 -0
  13. package/build/chains/icp.js +1040 -0
  14. package/build/chains/index.d.ts +23 -0
  15. package/build/chains/index.js +59 -0
  16. package/build/chains/near.d.ts +192 -0
  17. package/build/chains/near.js +446 -0
  18. package/build/chains/rpc.d.ts +125 -0
  19. package/build/chains/rpc.js +326 -0
  20. package/build/chains/starknet.d.ts +205 -0
  21. package/build/chains/starknet.js +884 -0
  22. package/build/chains/stellar.d.ts +410 -0
  23. package/build/chains/stellar.js +1082 -0
  24. package/build/chains/substrate.d.ts +333 -0
  25. package/build/chains/substrate.js +1042 -0
  26. package/build/chains/sui.d.ts +365 -0
  27. package/build/chains/sui.js +1135 -0
  28. package/build/chains/svm.d.ts +340 -0
  29. package/build/chains/svm.js +760 -0
  30. package/build/chains/tezos.d.ts +225 -0
  31. package/build/chains/tezos.js +412 -0
  32. package/build/chains/ton.d.ts +353 -0
  33. package/build/chains/ton.js +788 -0
  34. package/build/chains/tron.d.ts +45 -0
  35. package/build/chains/tron.js +110 -0
  36. package/build/chains/utxo.d.ts +202 -0
  37. package/build/chains/utxo.js +592 -0
  38. package/build/chains/xrpl.d.ts +179 -0
  39. package/build/chains/xrpl.js +467 -0
  40. package/build/index.d.ts +2 -1
  41. package/build/index.js +3 -1
  42. package/build/providers.json +1 -1
  43. package/build/util/LlamaProvider.js +2 -0
  44. package/build/util/blocks.js +292 -84
  45. package/build/util/chainUtils/data.json +80 -0
  46. package/build/util/chainUtils/index.d.ts +1 -0
  47. package/build/util/chainUtils/index.js +23 -0
  48. package/build/util/coins.js +14 -3
  49. package/build/util/common.js +6 -4
  50. package/build/util/computeTVL.js +1 -1
  51. package/build/util/cosmos.d.ts +1 -11
  52. package/build/util/cosmos.js +6 -66
  53. package/build/util/env.d.ts +1 -0
  54. package/build/util/env.js +18 -0
  55. package/build/util/indexer.compatibility.d.ts +6 -0
  56. package/build/util/indexer.compatibility.js +64 -0
  57. package/build/util/indexer.d.ts +24 -2
  58. package/build/util/indexer.js +333 -294
  59. package/build/util/indexerSql.d.ts +8 -1
  60. package/build/util/indexerSql.js +53 -22
  61. package/build/util/logs.d.ts +3 -0
  62. package/build/util/logs.decode.shared.js +5 -4
  63. package/build/util/logs.js +57 -26
  64. package/build/util/transactions.d.ts +23 -1
  65. package/build/util/updateChainLabels.d.ts +1 -0
  66. package/build/util/updateChainLabels.js +5 -0
  67. package/build/util/updateProviderList.js +40 -18
  68. package/package.json +20 -7
  69. package/README.md +0 -41
@@ -14,6 +14,7 @@ exports.isIndexer2Enabled = isIndexer2Enabled;
14
14
  const axios_1 = __importDefault(require("axios"));
15
15
  const http_1 = __importDefault(require("http"));
16
16
  const https_1 = __importDefault(require("https"));
17
+ const promises_1 = require("stream/promises");
17
18
  const stream_json_1 = require("stream-json");
18
19
  const Pick_1 = require("stream-json/filters/Pick");
19
20
  const StreamArray_1 = require("stream-json/streamers/StreamArray");
@@ -24,11 +25,12 @@ const debugLog_1 = require("./debugLog");
24
25
  const env_1 = require("./env");
25
26
  const logs_1 = require("./logs");
26
27
  const logs_decode_shared_1 = require("./logs.decode.shared");
28
+ const indexer_compatibility_1 = require("./indexer.compatibility");
27
29
  const LLAMA_INDEXER_V2_ENDPOINT = (0, env_1.getEnvValue)("LLAMA_INDEXER_V2_ENDPOINT");
28
30
  const LLAMA_INDEXER_V2_API_KEY = (0, env_1.getEnvValue)("LLAMA_INDEXER_V2_API_KEY");
29
31
  const LLAMA_INDEXER_V4_ENDPOINT = (0, env_1.getEnvValue)("LLAMA_INDEXER_V4_ENDPOINT");
30
32
  const LLAMA_INDEXER_V4_API_KEY = (_a = (0, env_1.getEnvValue)("LLAMA_INDEXER_V4_API_KEY")) !== null && _a !== void 0 ? _a : LLAMA_INDEXER_V2_API_KEY;
31
- const LLAMA_INDEXER_PREFER_V4 = (0, env_1.getEnvValue)("LLAMA_INDEXER_PREFER_V4") === "true"; // route all chains through v4
33
+ const LLAMA_INDEXER_PREFER_V4 = (0, env_1.getEnvValue)("LLAMA_INDEXER_PREFER_V4") !== "false"; // explicit false allows rollback
32
34
  const addressChunkSize = +(0, env_1.getEnvValue)("LLAMA_INDEXER_ADDRESS_CHUNK_SIZE") || 100;
33
35
  const INDEXER_REQUEST_TIMEOUT_MS = +(0, env_1.getEnvValue)("LLAMA_INDEXER_TIMEOUT_MS");
34
36
  const indexerChainIdChainMapping = {
@@ -76,31 +78,29 @@ const indexerConfigs = {
76
78
  v2: { endpoint: LLAMA_INDEXER_V2_ENDPOINT, apiKey: LLAMA_INDEXER_V2_API_KEY },
77
79
  v4: { endpoint: LLAMA_INDEXER_V4_ENDPOINT, apiKey: LLAMA_INDEXER_V4_API_KEY },
78
80
  };
79
- // http agents for streaming (keep-alive)
80
81
  const httpAgent = new http_1.default.Agent({ keepAlive: true, maxSockets: 256, maxFreeSockets: 64 });
81
82
  const httpsAgent = new https_1.default.Agent({ keepAlive: true, maxSockets: 256, maxFreeSockets: 64 });
82
- const axiosInstances = {
83
- v2: axios_1.default.create({
84
- headers: { "x-api-key": LLAMA_INDEXER_V2_API_KEY },
85
- baseURL: LLAMA_INDEXER_V2_ENDPOINT,
86
- httpAgent,
87
- httpsAgent,
88
- timeout: INDEXER_REQUEST_TIMEOUT_MS,
89
- }),
90
- v4: axios_1.default.create({
91
- headers: { "x-api-key": LLAMA_INDEXER_V4_API_KEY },
92
- baseURL: LLAMA_INDEXER_V4_ENDPOINT,
83
+ function createIndexerClient(version) {
84
+ const { endpoint, apiKey } = indexerConfigs[version];
85
+ return axios_1.default.create({
86
+ headers: { "x-api-key": apiKey },
87
+ baseURL: endpoint,
93
88
  httpAgent,
94
89
  httpsAgent,
95
90
  timeout: INDEXER_REQUEST_TIMEOUT_MS,
96
- }),
91
+ });
92
+ }
93
+ const axiosInstances = {
94
+ v2: createIndexerClient("v2"),
95
+ v4: createIndexerClient("v4"),
97
96
  };
97
+ const INDEXER_HEAD_BUFFER = 50;
98
98
  function checkIndexerConfig(version) {
99
99
  const { endpoint, apiKey } = indexerConfigs[version];
100
100
  if (!endpoint || !apiKey)
101
101
  throw new Error(`Llama Indexer (${version}) URL/api key is not set`);
102
102
  }
103
- // v4-only chains always go to v4; the rest go to v2 unless PREFER_V4 is set or v2 is not configured
103
+ // Prefer a configured v4 deployment; retain v2-only installations and explicit rollback.
104
104
  function getChainIndexerVersion(chain) {
105
105
  if (v4OnlyChainSet.has(chain))
106
106
  return "v4";
@@ -117,13 +117,7 @@ function getChainId(chain) {
117
117
  throw new Error("Chain not supported");
118
118
  return +chainId;
119
119
  }
120
- function getSupportedChains() {
121
- return new Set(Object.values(allChainIdChainMapping));
122
- }
123
- exports.supportedChainSet2 = getSupportedChains();
124
- function supportsCursorPagination(version) {
125
- return version === "v4";
126
- }
120
+ exports.supportedChainSet2 = new Set(Object.values(allChainIdChainMapping));
127
121
  function getLogCursor(log) {
128
122
  var _a, _b, _c;
129
123
  if (!log)
@@ -142,13 +136,14 @@ function getTransferCursor(transfer) {
142
136
  cursor.after_id = String(transfer.id);
143
137
  return cursor;
144
138
  }
145
- function applyCursorParams(params, cursor) {
146
- if (!cursor)
139
+ function getTransactionCursor(transaction) {
140
+ if (!transaction)
147
141
  return;
148
- params.after_block = cursor.after_block;
149
- params.after_index = cursor.after_index;
150
- if (cursor.after_id !== undefined)
151
- params.after_id = cursor.after_id;
142
+ return { after_block: +transaction.block_number, after_index: +transaction.transaction_index };
143
+ }
144
+ function safePush(target, source) {
145
+ for (const row of source)
146
+ target.push(row);
152
147
  }
153
148
  function applyLocalOffset(rows, remainingOffset) {
154
149
  if (remainingOffset <= 0)
@@ -157,6 +152,72 @@ function applyLocalOffset(rows, remainingOffset) {
157
152
  return { rows: [], remainingOffset: remainingOffset - rows.length };
158
153
  return { rows: rows.slice(remainingOffset), remainingOffset: 0 };
159
154
  }
155
+ function assertCursorAdvances(next, previous) {
156
+ if (!next || !Number.isSafeInteger(next.after_block) || !Number.isSafeInteger(next.after_index))
157
+ throw new Error("Indexer v4 returned a page without a valid cursor");
158
+ if (!previous)
159
+ return;
160
+ const advances = next.after_block > previous.after_block ||
161
+ (next.after_block === previous.after_block && (next.after_index > previous.after_index ||
162
+ (next.after_index === previous.after_index && next.after_id !== undefined && previous.after_id !== undefined &&
163
+ BigInt(next.after_id) > BigInt(previous.after_id))));
164
+ if (!advances)
165
+ throw new Error("Indexer v4 pagination cursor did not advance");
166
+ }
167
+ // Translate the public offset/limit contract into keyset pagination. Even all=false
168
+ // may need several requests: the v4 server caps offsets, not the public SDK API.
169
+ async function* getV4Pages(path, key, params, limit, offset, all, getCursor) {
170
+ if (!Number.isSafeInteger(limit) || limit < 0 || !Number.isSafeInteger(offset) || offset < 0)
171
+ throw new Error("Indexer pagination requires a non-negative integer limit and offset");
172
+ if (limit === 0)
173
+ return;
174
+ let cursor;
175
+ let remainingOffset = offset;
176
+ let remainingLimit = all ? Infinity : limit;
177
+ while (remainingLimit > 0) {
178
+ const pageParams = { ...params, limit, offset: 0, includeTotal: false, ...cursor };
179
+ const response = await axiosInstances.v4(path, {
180
+ params: pageParams,
181
+ ...(key === "transfers" ? { responseType: "text", transformResponse: (data) => data } : {}),
182
+ }).catch((e) => { throw (0, common_1.formError)(e); });
183
+ const data = key === "transfers" && typeof response.data === "string"
184
+ ? await (0, indexer_compatibility_1.parseTransferResponse)(response.data) : response.data;
185
+ const page = data[key];
186
+ if (!Array.isArray(page))
187
+ throw new Error(`Indexer v4 returned an invalid ${key} response`);
188
+ // Capture the cursor before consumers normalize/mutate the raw rows.
189
+ const next = getCursor(page[page.length - 1]);
190
+ const result = applyLocalOffset(page, remainingOffset);
191
+ remainingOffset = result.remainingOffset;
192
+ const rows = result.rows.slice(0, remainingLimit);
193
+ remainingLimit -= rows.length;
194
+ const hasMore = page.length >= limit && remainingLimit > 0;
195
+ if (hasMore || (page.length && cursor))
196
+ assertCursorAdvances(next, cursor);
197
+ if (rows.length)
198
+ yield rows.map(row => (0, indexer_compatibility_1.normalizeV4Row)(row, key));
199
+ if (!hasMore)
200
+ return;
201
+ cursor = next;
202
+ }
203
+ }
204
+ async function* getIndexerPages(version, path, key, params, limit, offset, all, getCursor) {
205
+ if (version === "v4") {
206
+ yield* getV4Pages(path, key, params, limit, offset, all, getCursor);
207
+ return;
208
+ }
209
+ let count = 0;
210
+ do {
211
+ const { data } = await axiosInstances.v2(path, { params: { ...params, limit, offset } })
212
+ .catch((e) => { throw (0, common_1.formError)(e); });
213
+ const rows = data[key];
214
+ yield rows;
215
+ count += rows.length;
216
+ if (!rows.length || rows.length < limit || (key === "logs" && typeof data.totalCount === "number" && data.totalCount <= count))
217
+ return;
218
+ offset += limit;
219
+ } while (all);
220
+ }
160
221
  const syncStates = {
161
222
  v2: { chainIndexStatus: {} },
162
223
  v4: { chainIndexStatus: {} },
@@ -181,94 +242,88 @@ async function getChainIndexStatus(version) {
181
242
  }
182
243
  });
183
244
  return (state.chainIndexStatus = info);
184
- })();
245
+ })().catch((error) => {
246
+ state.timestamp = undefined;
247
+ state.chainIndexStatus = {};
248
+ throw error;
249
+ });
185
250
  return state.chainIndexStatus;
186
251
  }
187
- // ---------------------------------------------------------------------------
188
- // Streaming support (disabled by default, opt-in via clientStreaming: true)
189
- // ---------------------------------------------------------------------------
190
- async function streamJsonArrayFromIndexer(opts) {
191
- const { path, version, arrayKey = "logs", onItem, onChunkStats, shouldStop } = opts;
252
+ async function streamLogs(opts) {
253
+ const { path, version, onItem, onChunkStats, shouldStop } = opts;
192
254
  const controller = new AbortController();
193
255
  let bytesReceived = 0;
194
256
  let itemsProcessed = 0;
195
257
  const res = await axiosInstances[version].get(path, {
196
258
  responseType: "stream",
197
- timeout: 0,
259
+ timeout: INDEXER_REQUEST_TIMEOUT_MS,
198
260
  maxContentLength: Infinity,
199
261
  maxBodyLength: Infinity,
200
262
  headers: { Accept: "application/json", "Accept-Encoding": "gzip" },
201
263
  signal: controller.signal,
202
- }).catch((e) => {
203
- if (axios_1.default.isCancel(e) || (e === null || e === void 0 ? void 0 : e.code) === 'ERR_CANCELED')
204
- return { data: null };
205
- throw (0, common_1.formError)(e);
206
- });
207
- if (!(res === null || res === void 0 ? void 0 : res.data))
208
- return;
209
- await new Promise((resolve, reject) => {
210
- const stream = res.data;
211
- const isCancellationError = (err) => {
212
- var _a;
213
- return (err === null || err === void 0 ? void 0 : err.name) === "CanceledError" ||
214
- ((_a = err === null || err === void 0 ? void 0 : err.message) === null || _a === void 0 ? void 0 : _a.includes("aborted")) ||
215
- axios_1.default.isCancel(err) ||
216
- (err === null || err === void 0 ? void 0 : err.code) === 'ERR_CANCELED';
217
- };
218
- const handleError = (err) => isCancellationError(err) ? resolve() : reject(err);
219
- stream.on('data', (chunk) => {
220
- bytesReceived += chunk.length;
221
- onChunkStats === null || onChunkStats === void 0 ? void 0 : onChunkStats({ chunkSize: chunk.length, bytesReceived, itemsProcessed });
222
- });
223
- const jsonParser = (0, stream_json_1.parser)();
224
- const pickFilter = (0, Pick_1.pick)({ filter: arrayKey });
225
- const arrayStream = (0, StreamArray_1.streamArray)();
226
- arrayStream.on('data', (data) => {
227
- if (shouldStop === null || shouldStop === void 0 ? void 0 : shouldStop()) {
228
- controller.abort();
229
- stream.destroy();
230
- return;
264
+ }).catch((e) => { throw (0, common_1.formError)(e); });
265
+ let stopped = false;
266
+ let processingError;
267
+ try {
268
+ await (0, promises_1.pipeline)(res.data, async function* (source) {
269
+ for await (const chunk of source) {
270
+ bytesReceived += chunk.length;
271
+ onChunkStats === null || onChunkStats === void 0 ? void 0 : onChunkStats({ chunkSize: chunk.length, bytesReceived, itemsProcessed });
272
+ yield chunk;
231
273
  }
232
- try {
233
- onItem(data.value);
274
+ }, (0, stream_json_1.parser)(), (0, Pick_1.pick)({ filter: "logs" }), (0, StreamArray_1.streamArray)(), async (source) => {
275
+ for await (const { value } of source) {
276
+ try {
277
+ await onItem(value);
278
+ }
279
+ catch (e) {
280
+ processingError = e;
281
+ throw e;
282
+ }
234
283
  itemsProcessed++;
235
- if (itemsProcessed % 100000 === 0) {
236
- onChunkStats === null || onChunkStats === void 0 ? void 0 : onChunkStats({ chunkSize: 100000, bytesReceived, itemsProcessed });
284
+ if (shouldStop === null || shouldStop === void 0 ? void 0 : shouldStop()) {
285
+ stopped = true;
286
+ controller.abort();
287
+ break;
237
288
  }
238
289
  }
239
- catch { }
240
290
  });
241
- const finish = () => {
242
- const rem = itemsProcessed % 100000;
243
- if (rem > 0) {
244
- onChunkStats === null || onChunkStats === void 0 ? void 0 : onChunkStats({ chunkSize: rem, bytesReceived, itemsProcessed });
245
- }
246
- resolve();
247
- };
248
- arrayStream.on('end', finish);
249
- arrayStream.on('close', finish);
250
- arrayStream.on('error', handleError);
251
- jsonParser.on('error', handleError);
252
- pickFilter.on('error', handleError);
253
- stream.on('error', handleError);
254
- stream.pipe(jsonParser).pipe(pickFilter).pipe(arrayStream);
291
+ }
292
+ catch (e) {
293
+ // Only our requested early stop is successful; remote truncation and decode
294
+ // or processor errors must reject instead of returning incomplete results.
295
+ if (!stopped)
296
+ throw processingError !== null && processingError !== void 0 ? processingError : e;
297
+ }
298
+ onChunkStats === null || onChunkStats === void 0 ? void 0 : onChunkStats({ chunkSize: 0, bytesReceived, itemsProcessed });
299
+ }
300
+ async function decodeStreamingLogs(logs, transformLog, batchDecoder, onlyArgs) {
301
+ for (const log of logs)
302
+ (0, logs_decode_shared_1.normalizeLog)(log, true);
303
+ if (!batchDecoder)
304
+ return Promise.all(logs.map(log => transformLog(log)));
305
+ // The historical streaming fast path returns args without buffered parsedLog metadata.
306
+ const args = await batchDecoder(logs);
307
+ return logs.map((log, i) => {
308
+ if (onlyArgs)
309
+ return args[i];
310
+ const decoded = { ...log, args: args[i] };
311
+ if (!decoded.transactionHash && decoded._originalTransactionHash)
312
+ decoded.transactionHash = decoded._originalTransactionHash;
313
+ return decoded;
255
314
  });
256
315
  }
257
- // ---------------------------------------------------------------------------
258
- // LOGS (v2) – streaming opt-in, legacy default
259
- // ---------------------------------------------------------------------------
260
316
  async function getLogs(options) {
261
- // Set collect=false by default when processor is provided AND collect is not explicitly set AND clientStreaming = true
262
- // if you pass processor without collect=true, you won't get results back
263
- var _a, _b, _c, _d, _e, _f;
264
- let { all = true, limit = 1000, offset: initialOffset = 0, target, targets = [], flatten = true, debugMode = false, noTarget = false, maxBlockRange, processor, clientStreaming = false, // default OFF to keep retro-compat
265
- decoderType = "viem", // default to viem for performance
266
- onWireStats, onDecodeStats, } = options;
317
+ var _a, _b, _c, _d, _e;
318
+ let { all = true, limit = 1000, offset: initialOffset = 0, target, targets = [], flatten = true, debugMode = false, noTarget = false, maxBlockRange, processor, clientStreaming = false, decoderType = "viem", onWireStats, onDecodeStats, } = options;
267
319
  if (processor && typeof options.collect === "undefined" && clientStreaming) {
268
- options.collect = false;
320
+ options = { ...options, collect: false };
269
321
  }
270
- const { chain, fromBlock, toBlock, topic, topic1, topic2, topic3, transformLog, } = await (0, logs_1.getLogParams)(options, true);
271
- const viemFastPath = decoderType === "viem" && options.eventAbi
322
+ const { chain, fromBlock, toBlock, topic, topic1, topic2, topic3, topics, transformLog, } = await (0, logs_1.getLogParams)(options, true);
323
+ const viemFastPath =
324
+ // Preserve the historical streaming shape, including entireLog: the viem
325
+ // fast path exposes args without adding the buffered decoder's parsedLog.
326
+ decoderType === "viem" && options.eventAbi && options.parseLog !== false && !options.allowParseFailure
272
327
  ? (0, logs_decode_shared_1.createViemFastPathBatchDecoder)(options.eventAbi)
273
328
  : null;
274
329
  if (!debugMode)
@@ -277,7 +332,11 @@ async function getLogs(options) {
277
332
  if (processor && (targets === null || targets === void 0 ? void 0 : targets.length) > 1 && !flatten)
278
333
  throw new Error("processor is not supported with multiple targets when flatten=false");
279
334
  const blockRange = toBlock - fromBlock;
335
+ if (!Number.isSafeInteger(fromBlock) || !Number.isSafeInteger(toBlock) || fromBlock > toBlock)
336
+ throw new Error("getLogs requires integer block bounds with fromBlock <= toBlock");
280
337
  const effectiveMaxBlockRange = maxBlockRange !== null && maxBlockRange !== void 0 ? maxBlockRange : (noTarget ? 10000 : Infinity);
338
+ if (!(effectiveMaxBlockRange > 0) || (effectiveMaxBlockRange !== Infinity && !Number.isSafeInteger(effectiveMaxBlockRange)))
339
+ throw new Error("maxBlockRange must be a positive integer");
281
340
  if (noTarget && blockRange > 500000) {
282
341
  throw new Error("When noTarget is true, block range must be less than 500k blocks. Please narrow down your block range.");
283
342
  }
@@ -286,33 +345,32 @@ async function getLogs(options) {
286
345
  checkIndexerConfig(indexerVersion);
287
346
  const chainId = getChainId(chain);
288
347
  const chainIndexStatus = await getChainIndexStatus(indexerVersion);
289
- const lastIndexedBlock = (_b = (_a = chainIndexStatus[chain]) === null || _a === void 0 ? void 0 : _a.block) !== null && _b !== void 0 ? _b : 0;
348
+ const lastIndexedBlock = Number((_b = (_a = chainIndexStatus[chain]) === null || _a === void 0 ? void 0 : _a.block) !== null && _b !== void 0 ? _b : 0);
349
+ if (!Number.isSafeInteger(lastIndexedBlock) || lastIndexedBlock < 0)
350
+ throw new Error(`Indexer returned an invalid last indexed block for ${chain}`);
290
351
  if (lastIndexedBlock < toBlock) {
291
352
  const percentageMissing = ((toBlock - lastIndexedBlock) / (toBlock - fromBlock)) * 100;
292
353
  if (percentageMissing > 50)
293
354
  throw new Error(`Indexer not up to date for ${chain}. Last indexed block: ${lastIndexedBlock}, requested block: ${toBlock}`);
294
355
  if (env_1.ENV_CONSTANTS.GET_LOGS_INDEXER)
295
356
  (0, debugLog_1.debugLog)(`Indexer only partially up to date for ${chain}. Last indexed block: ${lastIndexedBlock}, requested block: ${toBlock}, missing ${Number(percentageMissing).toFixed(2)}%. Pulling part of the logs through RPC calls.`);
296
- const breakBlock = lastIndexedBlock - 50; // small buffer
297
- const indexerLogs = await getLogs({ ...options, fromBlock, toBlock: breakBlock });
298
- const rpcLogs = await (0, logs_1.getLogs)({ ...options, fromBlock: breakBlock + 1, toBlock, skipIndexer: true });
299
- // When flatten=false, preserve per-target buckets: merge RPC logs into their corresponding indexer bucket so the result still maintains one element per target.
300
- if (!flatten) {
301
- return indexerLogs.map((bucket, i) => bucket.concat(rpcLogs[i]));
302
- }
303
- return indexerLogs.concat(rpcLogs);
357
+ // An empty indexer prefix uses RPC for exactly the original window. The two
358
+ // inclusive ranges otherwise meet at breakBlock / breakBlock + 1.
359
+ const breakBlock = Math.max(fromBlock - 1, lastIndexedBlock - INDEXER_HEAD_BUFFER);
360
+ return getPartiallyIndexedLogs(breakBlock);
304
361
  }
305
362
  // Re-curse if the requested range is too large
306
363
  if (blockRange > effectiveMaxBlockRange) {
307
364
  const results = [];
308
- for (let currentFromBlock = fromBlock; currentFromBlock < toBlock; currentFromBlock += effectiveMaxBlockRange) {
365
+ for (let currentFromBlock = fromBlock; currentFromBlock <= toBlock; currentFromBlock += effectiveMaxBlockRange) {
309
366
  const currentToBlock = Math.min(currentFromBlock + effectiveMaxBlockRange - 1, toBlock);
310
367
  const chunk = await getLogs({ ...options, fromBlock: currentFromBlock, toBlock: currentToBlock });
311
368
  results.push(chunk);
312
369
  }
313
- if (flatten || !(targets === null || targets === void 0 ? void 0 : targets.length))
370
+ const splitTargets = typeof target === "string" ? [target] : targets;
371
+ if (flatten || !splitTargets.length)
314
372
  return results.flat();
315
- return targets.map((_, i) => results.map((r) => r[i]).flat());
373
+ return splitTargets.map((_, i) => results.map((r) => r[i]).flat());
316
374
  }
317
375
  let address = target;
318
376
  if (typeof target === "string")
@@ -326,36 +384,15 @@ async function getLogs(options) {
326
384
  const addressChunks = (0, _1.sliceIntoChunks)((_c = address === null || address === void 0 ? void 0 : address.split(",")) !== null && _c !== void 0 ? _c : [], addressChunkSize);
327
385
  if (noTarget && addressChunks.length === 0)
328
386
  addressChunks.push(undefined);
329
- // Safely push large arrays without stack overflow (avoid push.apply argument limits (~125k))
330
- const safePush = (target, source) => {
331
- if (source.length === 0)
332
- return;
333
- // For very large arrays, split into chunks
334
- if (source.length > 100000) {
335
- const CHUNK_SIZE = 50000; // Safe chunk size well below push.apply limit
336
- for (let i = 0; i < source.length; i += CHUNK_SIZE) {
337
- const chunk = source.slice(i, i + CHUNK_SIZE);
338
- Array.prototype.push.apply(target, chunk);
339
- }
340
- }
341
- else {
342
- Array.prototype.push.apply(target, source);
343
- }
344
- };
345
- // -----------------------------------------------------------------------
346
- // Streaming (opt-in) — NO server-side parsing; mirror legacy semantics
347
- // -----------------------------------------------------------------------
348
387
  if (clientStreaming) {
349
388
  const useAll = all === true;
350
389
  const effectiveLimit = useAll ? Number.POSITIVE_INFINITY : ((_d = options.limit) !== null && _d !== void 0 ? _d : limit);
351
390
  const shouldLimit = !useAll && Number.isFinite(effectiveLimit);
352
- const effectiveOffset = useAll ? 0 : ((_f = (_e = options.offset) !== null && _e !== void 0 ? _e : initialOffset) !== null && _f !== void 0 ? _f : 0);
353
- const outPairs = [];
391
+ const effectiveOffset = (_e = options.offset) !== null && _e !== void 0 ? _e : initialOffset;
392
+ const collected = [];
354
393
  const start = debugMode ? Date.now() : 0;
355
394
  const MICRO_BATCH_SIZE = +(process.env.LLAMA_INDEXER_MICRO_BATCH || 10000);
356
- // Check if we need splitByAddress (mapping by target address)
357
395
  const splitByAddress = (targets === null || targets === void 0 ? void 0 : targets.length) && !flatten;
358
- // If splitByAddress, build buckets directly during flushBatch
359
396
  const addressBuckets = splitByAddress ? targets.map(() => []) : [];
360
397
  const addressIndexMap = splitByAddress
361
398
  ? Object.fromEntries(targets.map((t, i) => [t.toLowerCase(), i]))
@@ -367,42 +404,12 @@ async function getLogs(options) {
367
404
  if (!batch.length)
368
405
  return;
369
406
  const t0 = Date.now();
370
- // Normalize logs before decoding (required for fast-path and consistency)
371
- for (const log of batch) {
372
- (0, logs_decode_shared_1.normalizeLog)(log, true); // isIndexerCall = true
373
- }
374
- // Use fast-path if available, otherwise fallback to default
375
- const batchFn = viemFastPath !== null && viemFastPath !== void 0 ? viemFastPath : transformLog.batch;
376
- let transformedLogs;
377
- if (batchFn) {
378
- // Fast-path returns only args, need to reconstruct full log objects
379
- const decodedArgs = await batchFn(batch);
380
- transformedLogs = new Array(batch.length);
381
- for (let i = 0; i < batch.length; i++) {
382
- if (options.onlyArgs) {
383
- // If onlyArgs is true, return only the args (like transformLog does)
384
- transformedLogs[i] = decodedArgs[i];
385
- }
386
- else {
387
- transformedLogs[i] = {
388
- ...batch[i],
389
- args: decodedArgs[i]
390
- };
391
- if (!transformedLogs[i].transactionHash && transformedLogs[i]._originalTransactionHash) {
392
- transformedLogs[i].transactionHash = transformedLogs[i]._originalTransactionHash;
393
- }
394
- }
395
- }
396
- }
397
- else {
398
- transformedLogs = await Promise.all(batch.map((log) => transformLog(log)));
399
- }
407
+ const transformedLogs = await decodeStreamingLogs(batch, transformLog, viemFastPath !== null && viemFastPath !== void 0 ? viemFastPath : transformLog.batch, options.onlyArgs);
400
408
  const decodeTime = Date.now() - t0;
401
409
  onDecodeStats === null || onDecodeStats === void 0 ? void 0 : onDecodeStats({ batchSize: batch.length, decodeTime, itemsDecoded: batch.length });
402
410
  if (processor)
403
411
  await processor(transformedLogs);
404
- if (splitByAddress) {
405
- // Map directly to address buckets without storing pairs
412
+ if (splitByAddress && options.collect !== false) {
406
413
  for (let i = 0; i < batch.length; i++) {
407
414
  const raw = batch[i];
408
415
  const transformed = transformedLogs[i];
@@ -413,14 +420,8 @@ async function getLogs(options) {
413
420
  }
414
421
  }
415
422
  else if (options.collect !== false) {
416
- // Build pairs if collect=true (needed for final return)
417
- const pairs = new Array(batch.length);
418
- for (let i = 0; i < batch.length; i++) {
419
- pairs[i] = { raw: batch[i], transformed: transformedLogs[i] };
420
- }
421
- Array.prototype.push.apply(outPairs, pairs);
423
+ safePush(collected, transformedLogs);
422
424
  }
423
- // If collect=false and not splitByAddress, nothing to store (processor already handled it)
424
425
  };
425
426
  for (const chunk of addressChunks) {
426
427
  if (Array.isArray(chunk) && chunk.length === 0)
@@ -446,19 +447,15 @@ async function getLogs(options) {
446
447
  qs.set("offset", "0");
447
448
  const transformBatch = [];
448
449
  let stopNow = false;
449
- let flushPromise = Promise.resolve();
450
- // Chain flushes sequentially to avoid concurrent decoding (CPU/GC thrashing)
451
- const scheduleFlush = (batch) => {
452
- flushPromise = flushPromise.then(() => flushBatch(batch));
453
- };
454
- await streamJsonArrayFromIndexer({
450
+ await streamLogs({
455
451
  path: `/logs?${qs.toString()}`,
456
452
  version: indexerVersion,
457
- arrayKey: "logs",
458
- onItem: (raw) => {
453
+ onItem: async (raw) => {
459
454
  var _a, _b, _c;
460
455
  if (stopNow)
461
456
  return;
457
+ if (indexerVersion === "v4")
458
+ (0, indexer_compatibility_1.normalizeV4Row)(raw, "logs");
462
459
  const okAddress = !addressSet.size || addressSet.has((_c = (_b = ((_a = raw.source) !== null && _a !== void 0 ? _a : raw.address)) === null || _b === void 0 ? void 0 : _b.toLowerCase) === null || _c === void 0 ? void 0 : _c.call(_b));
463
460
  if (!okAddress)
464
461
  return;
@@ -472,30 +469,30 @@ async function getLogs(options) {
472
469
  }
473
470
  transformBatch.push(raw);
474
471
  remainingLimit--;
472
+ if (remainingLimit <= 0)
473
+ stopNow = true;
475
474
  if (transformBatch.length >= MICRO_BATCH_SIZE) {
476
- // Sequential flush: chain to avoid concurrent decoding
477
475
  const batch = transformBatch.splice(0, MICRO_BATCH_SIZE);
478
- scheduleFlush(batch);
476
+ await flushBatch(batch);
479
477
  }
480
478
  },
481
479
  shouldStop: () => stopNow,
482
- onChunkStats: (s) => onWireStats === null || onWireStats === void 0 ? void 0 : onWireStats(s),
480
+ onChunkStats: onWireStats,
483
481
  });
484
482
  if (transformBatch.length > 0) {
485
- scheduleFlush(transformBatch.splice(0, transformBatch.length));
483
+ await flushBatch(transformBatch.splice(0, transformBatch.length));
486
484
  }
487
- await flushPromise;
488
485
  if (remainingLimit <= 0)
489
486
  break;
490
487
  }
491
488
  if (debugMode) {
492
489
  const ms = Date.now() - start;
493
- (0, debugLog_1.debugLog)(`[Indexer] stream finished: ${outPairs.length} items in ${ms}ms`);
490
+ (0, debugLog_1.debugLog)(`[Indexer] stream finished: ${collected.length} items in ${ms}ms`);
494
491
  }
495
492
  if (splitByAddress) {
496
493
  return addressBuckets;
497
494
  }
498
- return (options.collect !== false) ? outPairs.map(i => i.transformed) : [];
495
+ return collected;
499
496
  }
500
497
  const allLogsPairs = [];
501
498
  const debugTimeKey = `Indexer-getLogs-${chain}-${topic}-${address}_${Math.random()}`;
@@ -503,48 +500,24 @@ async function getLogs(options) {
503
500
  (0, debugLog_1.debugLog)("[Indexer] Pulling logs " + debugTimeKey);
504
501
  console.time(debugTimeKey);
505
502
  }
506
- const useCursorPagination = supportsCursorPagination(indexerVersion) && all;
507
503
  for (const chunk of addressChunks) {
508
504
  if (Array.isArray(chunk) && chunk.length === 0)
509
505
  throw new Error("Address chunk cannot be empty");
510
- let chunkOffset = useCursorPagination ? 0 : initialOffset;
511
- let remainingOffset = useCursorPagination ? initialOffset : 0;
512
- let logCount = 0;
513
- let hasMore = true;
514
- let cursor;
515
- do {
516
- const params = {
517
- addresses: hasAddressFilter ? chunk === null || chunk === void 0 ? void 0 : chunk.join(",") : undefined,
518
- chainId,
519
- topic0: topic,
520
- from_block: fromBlock,
521
- to_block: toBlock,
522
- topic1,
523
- topic2,
524
- topic3,
525
- limit,
526
- offset: chunkOffset,
527
- noTarget,
528
- };
529
- if (useCursorPagination) {
530
- params.includeTotal = false;
531
- applyCursorParams(params, cursor);
532
- }
533
- const { data: { logs: _logs, totalCount }, } = await axiosInstances[indexerVersion](`/logs`, { params }).catch((e) => { throw (0, common_1.formError)(e); });
534
- const nextCursor = useCursorPagination ? getLogCursor(_logs[_logs.length - 1]) : undefined;
535
- const offsetResult = useCursorPagination ? applyLocalOffset(_logs, remainingOffset) : { rows: _logs, remainingOffset };
536
- remainingOffset = offsetResult.remainingOffset;
537
- const filtered = offsetResult.rows.filter((l) => {
506
+ const params = {
507
+ addresses: hasAddressFilter ? chunk === null || chunk === void 0 ? void 0 : chunk.join(",") : undefined,
508
+ chainId, topic0: topic, from_block: fromBlock, to_block: toBlock,
509
+ topic1, topic2, topic3, noTarget,
510
+ };
511
+ for await (const rows of getIndexerPages(indexerVersion, "/logs", "logs", params, limit, initialOffset, all, getLogCursor)) {
512
+ const filtered = rows.filter((l) => {
538
513
  var _a, _b, _c;
539
514
  const isWhitelisted = !addressSet.size || addressSet.has((_c = (_b = ((_a = l.source) !== null && _a !== void 0 ? _a : l.address)) === null || _b === void 0 ? void 0 : _b.toLowerCase) === null || _c === void 0 ? void 0 : _c.call(_b));
540
515
  return !!isWhitelisted;
541
516
  });
542
517
  const t0 = Date.now();
543
- // Normalize logs before decoding (required for consistency)
544
518
  for (const log of filtered) {
545
- (0, logs_decode_shared_1.normalizeLog)(log, true); // isIndexerCall = true
519
+ (0, logs_decode_shared_1.normalizeLog)(log, true);
546
520
  }
547
- // Legacy path: use transformLog batch if available, otherwise transform individually
548
521
  const transformBatchFn = transformLog.batch || ((logs) => Promise.all(logs.map((log) => transformLog(log))));
549
522
  const transformedLogs = await transformBatchFn(filtered);
550
523
  const transformedPair = filtered.map((log, idx) => ({ raw: log, transformed: transformedLogs[idx] }));
@@ -553,33 +526,10 @@ async function getLogs(options) {
553
526
  onDecodeStats({ batchSize: filtered.length, decodeTime, itemsDecoded: filtered.length });
554
527
  if (processor)
555
528
  await processor(transformedPair.map((i) => i.transformed));
556
- // Safe push helper to avoid stack overflow on large arrays
557
- // Only accumulate if collect is enabled (default true for backward compatibility)
558
- const shouldCollect = (options.collect !== false);
559
- if (shouldCollect) {
529
+ if (options.collect !== false) {
560
530
  safePush(allLogsPairs, transformedPair);
561
531
  }
562
- logCount += _logs.length;
563
- if (useCursorPagination && nextCursor) {
564
- cursor = nextCursor;
565
- chunkOffset = 0;
566
- }
567
- else {
568
- chunkOffset += limit;
569
- }
570
- if (_logs.length === 0) {
571
- hasMore = false;
572
- }
573
- else if (_logs.length < limit) {
574
- hasMore = false;
575
- }
576
- else if (typeof totalCount === 'number' && totalCount <= logCount) {
577
- hasMore = false;
578
- }
579
- else {
580
- hasMore = true;
581
- }
582
- } while (all && hasMore);
532
+ }
583
533
  }
584
534
  if (debugMode) {
585
535
  console.timeEnd(debugTimeKey);
@@ -599,9 +549,111 @@ async function getLogs(options) {
599
549
  });
600
550
  return mapped;
601
551
  }
602
- // Return empty array if collect=false (processor handles results)
603
- const shouldCollect = (options.collect !== false);
604
- return shouldCollect ? allLogsPairs.map(i => i.transformed) : [];
552
+ return allLogsPairs.map(i => i.transformed);
553
+ async function getPartiallyIndexedLogs(breakBlock) {
554
+ if (!Number.isSafeInteger(limit) || limit < 0 || !Number.isSafeInteger(initialOffset) || initialOffset < 0)
555
+ throw new Error("Indexer pagination requires a non-negative integer limit and offset");
556
+ const requestedTargets = typeof target === "string" ? [target] : targets;
557
+ const uniqueTargets = [...new Set(requestedTargets.map(t => t.toLowerCase()))];
558
+ const splitByAddress = requestedTargets.length > 0 && !flatten;
559
+ const collected = splitByAddress ? requestedTargets.map(() => []) : [];
560
+ const targetBuckets = new Map();
561
+ requestedTargets.forEach((t, i) => {
562
+ var _a;
563
+ const key = t.toLowerCase();
564
+ targetBuckets.set(key, [...((_a = targetBuckets.get(key)) !== null && _a !== void 0 ? _a : []), i]);
565
+ });
566
+ let remainingOffset = initialOffset;
567
+ let remainingLimit = all ? Infinity : limit;
568
+ if (limit === 0)
569
+ return collected;
570
+ const rpcTransform = (await (0, logs_1.getLogParams)(options, false)).transformLog;
571
+ const finished = Symbol("partial log limit reached");
572
+ let seenBlock;
573
+ const seen = new Set();
574
+ async function consume(rawRows, isIndexer, start, end) {
575
+ var _a, _b, _c, _d;
576
+ const selected = [];
577
+ for (const row of rawRows) {
578
+ const block = Number((_a = row.blockNumber) !== null && _a !== void 0 ? _a : row.block_number);
579
+ if (!Number.isSafeInteger(block))
580
+ throw new Error("Log returned without a valid block number");
581
+ if (block < start || block > end)
582
+ continue;
583
+ if (block !== seenBlock) {
584
+ seenBlock = block;
585
+ seen.clear();
586
+ }
587
+ const hash = (_b = row.transactionHash) !== null && _b !== void 0 ? _b : row.transaction_hash;
588
+ const index = (_d = (_c = row.logIndex) !== null && _c !== void 0 ? _c : row.index) !== null && _d !== void 0 ? _d : row.log_index;
589
+ if (hash !== undefined && index !== undefined) {
590
+ const key = `${String(hash).toLowerCase()}:${index}`;
591
+ if (seen.has(key))
592
+ continue;
593
+ seen.add(key);
594
+ }
595
+ if (remainingOffset > 0) {
596
+ remainingOffset--;
597
+ continue;
598
+ }
599
+ if (remainingLimit <= 0)
600
+ break;
601
+ selected.push(clientStreaming && !isIndexer ? { ...row } : row);
602
+ remainingLimit--;
603
+ }
604
+ if (selected.length) {
605
+ const transform = isIndexer ? transformLog : rpcTransform;
606
+ const startTime = Date.now();
607
+ const decoded = clientStreaming
608
+ ? await decodeStreamingLogs(selected, transform, viemFastPath !== null && viemFastPath !== void 0 ? viemFastPath : transform.batch, options.onlyArgs)
609
+ : await Promise.all(selected.map(row => transform(row)));
610
+ onDecodeStats === null || onDecodeStats === void 0 ? void 0 : onDecodeStats({ batchSize: selected.length, decodeTime: Date.now() - startTime, itemsDecoded: selected.length });
611
+ if (processor)
612
+ await processor(decoded);
613
+ if (options.collect !== false) {
614
+ if (splitByAddress)
615
+ selected.forEach((row, i) => {
616
+ var _a, _b, _c;
617
+ for (const bucket of (_c = targetBuckets.get((_b = ((_a = row.source) !== null && _a !== void 0 ? _a : row.address)) === null || _b === void 0 ? void 0 : _b.toLowerCase())) !== null && _c !== void 0 ? _c : [])
618
+ collected[bucket].push(decoded[i]);
619
+ });
620
+ else
621
+ safePush(collected, decoded);
622
+ }
623
+ }
624
+ if (remainingLimit <= 0)
625
+ throw finished;
626
+ }
627
+ const rawOptions = {
628
+ ...options, chain, topic, topics, target: undefined, targets: uniqueTargets,
629
+ eventAbi: undefined, parseLog: false, entireLog: true, onlyArgs: false,
630
+ flatten: true, onDecodeStats: undefined,
631
+ };
632
+ try {
633
+ if (breakBlock >= fromBlock) {
634
+ await getLogs({
635
+ ...rawOptions, fromBlock, toBlock: breakBlock, all: true, offset: 0, collect: false,
636
+ processor: rows => consume(rows, true, fromBlock, breakBlock),
637
+ });
638
+ }
639
+ // RPC cache reads may extend the physical fetch by a buffer, but logs.ts
640
+ // filters to these exact bounds before any decoding or processing here.
641
+ for (let start = breakBlock + 1; start <= toBlock; start += effectiveMaxBlockRange) {
642
+ const end = Math.min(toBlock, start + effectiveMaxBlockRange - 1);
643
+ const rows = await (0, logs_1.getLogs)({
644
+ ...rawOptions, fromBlock: start, toBlock: end, skipIndexer: true,
645
+ maxBlockRange: undefined, processor: undefined,
646
+ });
647
+ rows.sort((a, b) => { var _a, _b; return a.blockNumber - b.blockNumber || ((_a = a.logIndex) !== null && _a !== void 0 ? _a : a.index) - ((_b = b.logIndex) !== null && _b !== void 0 ? _b : b.index); });
648
+ await consume(rows, false, start, end);
649
+ }
650
+ }
651
+ catch (error) {
652
+ if (error !== finished)
653
+ throw error;
654
+ }
655
+ return collected;
656
+ }
605
657
  }
606
658
  async function getTokenTransfers({ chain = "ethereum", fromAddressFilter, fromBlock, toBlock, all = true, limit = 1000, offset = 0, target, targets = [], flatten = true, fromTimestamp, toTimestamp, debugMode = false, transferType = "in", token, tokens, }) {
607
659
  var _a, _b;
@@ -651,55 +703,27 @@ async function getTokenTransfers({ chain = "ethereum", fromAddressFilter, fromBl
651
703
  (0, debugLog_1.debugLog)("[Indexer] Pulling token transfers " + debugTimeKey);
652
704
  console.time(debugTimeKey);
653
705
  }
654
- const useCursorPagination = supportsCursorPagination(indexerVersion) && all;
655
- let currentOffset = useCursorPagination ? 0 : offset;
656
- let remainingOffset = useCursorPagination ? offset : 0;
657
- let hasMore = true;
658
- let cursor;
659
- do {
660
- const params = {
661
- addresses,
662
- chainId,
663
- from_block: fromBlock,
664
- to_block: toBlock,
665
- limit,
666
- offset: currentOffset,
667
- tokens,
668
- from_address: false,
669
- to_address: false,
670
- };
671
- if (useCursorPagination) {
672
- params.includeTotal = false;
673
- applyCursorParams(params, cursor);
674
- }
675
- switch (transferType) {
676
- case "in":
677
- params.to_address = true;
678
- break;
679
- case "out":
680
- params.from_address = true;
681
- break;
682
- case "all":
683
- params.from_address = true;
684
- params.to_address = true;
685
- break;
686
- default:
687
- throw new Error("Invalid transferType");
688
- }
689
- const { data: { transfers: _logs }, } = await axiosInstances[indexerVersion](`/token-transfers`, { params }).catch((e) => { throw (0, common_1.formError)(e); });
690
- const nextCursor = useCursorPagination ? getTransferCursor(_logs[_logs.length - 1]) : undefined;
691
- const offsetResult = useCursorPagination ? applyLocalOffset(_logs, remainingOffset) : { rows: _logs, remainingOffset };
692
- remainingOffset = offsetResult.remainingOffset;
693
- rawTransfers.push(...offsetResult.rows);
694
- if (useCursorPagination && nextCursor) {
695
- cursor = nextCursor;
696
- currentOffset = 0;
697
- }
698
- else {
699
- currentOffset += limit;
700
- }
701
- hasMore = _logs.length === limit;
702
- } while (all && hasMore);
706
+ const params = {
707
+ addresses, chainId, from_block: fromBlock, to_block: toBlock, tokens,
708
+ from_address: false, to_address: false,
709
+ };
710
+ switch (transferType) {
711
+ case "in":
712
+ params.to_address = true;
713
+ break;
714
+ case "out":
715
+ params.from_address = true;
716
+ break;
717
+ case "all":
718
+ params.from_address = true;
719
+ params.to_address = true;
720
+ break;
721
+ default:
722
+ throw new Error("Invalid transferType");
723
+ }
724
+ for await (const rows of getIndexerPages(indexerVersion, "/token-transfers", "transfers", params, limit, offset, all, getTransferCursor)) {
725
+ safePush(rawTransfers, rows);
726
+ }
703
727
  const filteredTransfers = rawTransfers.filter((l) => {
704
728
  if (!fromFilterEnabled)
705
729
  return true;
@@ -766,7 +790,22 @@ async function getTransactions({ chain = "ethereum", addresses, transaction_hash
766
790
  (0, debugLog_1.debugLog)("[Indexer] Pulling transactions " + debugTimeKey);
767
791
  console.time(debugTimeKey);
768
792
  }
769
- const { data: { transactions } } = await axiosInstances[indexerVersion](`/transactions`, { params }).catch((e) => { throw (0, common_1.formError)(e); });
793
+ let transactions;
794
+ if (indexerVersion === "v4") {
795
+ transactions = [];
796
+ // all=false with limit=0 historically omitted limit (server default 1000).
797
+ const requestedLimit = typeof limit === "number" && limit > 0 ? limit : 1000;
798
+ // v2 ignores limit when all=true. Preserve that public contract without
799
+ // turning a small caller limit into hundreds of v4 network round trips.
800
+ const pageLimit = all ? Math.max(1000, requestedLimit) : requestedLimit;
801
+ for await (const rows of getV4Pages("/transactions", "transactions", params, pageLimit, offset, all, getTransactionCursor)) {
802
+ safePush(transactions, rows);
803
+ }
804
+ }
805
+ else {
806
+ const { data } = await axiosInstances.v2('/transactions', { params }).catch((e) => { throw (0, common_1.formError)(e); });
807
+ transactions = data.transactions;
808
+ }
770
809
  if (debugMode) {
771
810
  console.timeEnd(debugTimeKey);
772
811
  (0, debugLog_1.debugLog)("Transactions pulled " + chain, addresses || transaction_hashes, (transactions === null || transactions === void 0 ? void 0 : transactions.length) || 0);