@defillama/sdk 5.0.185 → 5.0.188

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.
@@ -11,20 +11,27 @@ exports.getTransactions = getTransactions;
11
11
  exports.isIndexerEnabled = isIndexerEnabled;
12
12
  exports.isIndexer2Enabled = isIndexer2Enabled;
13
13
  const axios_1 = __importDefault(require("axios"));
14
+ const http_1 = __importDefault(require("http"));
15
+ const https_1 = __importDefault(require("https"));
16
+ const stream_json_1 = require("stream-json");
17
+ const Pick_1 = require("stream-json/filters/Pick");
18
+ const StreamArray_1 = require("stream-json/streamers/StreamArray");
14
19
  const _1 = require(".");
15
20
  const general_1 = require("../general");
16
- const common_1 = require("./common");
17
21
  const blocks_1 = require("./blocks");
18
22
  const cache_1 = require("./cache");
23
+ const common_1 = require("./common");
19
24
  const debugLog_1 = require("./debugLog");
20
25
  const env_1 = require("./env");
21
26
  const logs_1 = require("./logs");
27
+ const logs_decode_shared_1 = require("./logs.decode.shared");
22
28
  const indexerURL = (0, env_1.getEnvValue)("LLAMA_INDEXER_ENDPOINT");
23
29
  const LLAMA_INDEXER_API_KEY = (0, env_1.getEnvValue)("LLAMA_INDEXER_API_KEY");
24
30
  const LLAMA_INDEXER_V2_ENDPOINT = (0, env_1.getEnvValue)("LLAMA_INDEXER_V2_ENDPOINT");
25
31
  const LLAMA_INDEXER_V2_API_KEY = (0, env_1.getEnvValue)("LLAMA_INDEXER_V2_API_KEY");
26
32
  const addressChunkSize = +(0, env_1.getEnvValue)("LLAMA_INDEXER_ADDRESS_CHUNK_SIZE") || 100;
27
- // v1 chains (still used for balances endpoint) -------------------------------------------------
33
+ const INDEXER_REQUEST_TIMEOUT_MS = +(0, env_1.getEnvValue)("LLAMA_INDEXER_TIMEOUT_MS");
34
+ // v1 chains (still used for balances endpoint)
28
35
  const indexerChainIdChainMapping = {
29
36
  1: "ethereum",
30
37
  10: "optimism",
@@ -52,6 +59,7 @@ const indexer2ChainIdChainMapping = {
52
59
  1868: "soneium",
53
60
  80094: "berachain",
54
61
  999: "hyperliquid",
62
+ 143: "monad",
55
63
  };
56
64
  const indexerConfigs = {
57
65
  v1: {
@@ -65,14 +73,21 @@ const indexerConfigs = {
65
73
  chainMapping: indexer2ChainIdChainMapping,
66
74
  },
67
75
  };
76
+ // http agents for streaming (keep-alive)
77
+ const httpAgent = new http_1.default.Agent({ keepAlive: true, maxSockets: 256, maxFreeSockets: 64 });
78
+ const httpsAgent = new https_1.default.Agent({ keepAlive: true, maxSockets: 256, maxFreeSockets: 64 });
68
79
  const axiosInstances = {
69
80
  v1: axios_1.default.create({
70
81
  headers: { "x-api-key": indexerConfigs.v1.apiKey },
71
82
  baseURL: indexerConfigs.v1.endpoint,
83
+ timeout: INDEXER_REQUEST_TIMEOUT_MS,
72
84
  }),
73
85
  v2: axios_1.default.create({
74
86
  headers: { "x-api-key": indexerConfigs.v2.apiKey },
75
87
  baseURL: indexerConfigs.v2.endpoint,
88
+ httpAgent,
89
+ httpsAgent,
90
+ timeout: INDEXER_REQUEST_TIMEOUT_MS,
76
91
  }),
77
92
  };
78
93
  function checkIndexerConfig(version) {
@@ -96,14 +111,8 @@ const chainToIDMapping = {};
96
111
  Object.entries(indexerConfigs.v1.chainMapping).forEach(([id, chain]) => {
97
112
  chainToIDMapping[chain] = +id;
98
113
  });
99
- const chainToIDMapping2 = {};
100
- Object.entries(indexerConfigs.v2.chainMapping).forEach(([id, chain]) => {
101
- chainToIDMapping2[chain] = +id;
102
- });
103
- const state = {
104
- chainIndexStatus: {},
105
- };
106
- const cacheTime = 1 * 60 * 1000; // 1 min
114
+ const state = { chainIndexStatus: {} };
115
+ const cacheTime = 1 * 60 * 1000; // 1 min
107
116
  async function getChainIndexStatus(version = "v2") {
108
117
  checkIndexerConfig(version);
109
118
  if (state.timestamp && Date.now() - state.timestamp < cacheTime)
@@ -125,17 +134,11 @@ async function getChainIndexStatus(version = "v2") {
125
134
  })();
126
135
  return state.chainIndexStatus;
127
136
  }
128
- // ---------------------------------------------------------------------------
129
- // Utilities / Enums / Types
130
- // ---------------------------------------------------------------------------
131
137
  var TokenTypes;
132
138
  (function (TokenTypes) {
133
139
  TokenTypes["ERC20"] = "erc20";
134
140
  TokenTypes["ERC721"] = "erc721";
135
141
  })(TokenTypes || (TokenTypes = {}));
136
- // ---------------------------------------------------------------------------
137
- // Token helpers (still via v1 endpoint)
138
- // ---------------------------------------------------------------------------
139
142
  async function getTokens(address, { onlyWhitelisted = true, skipCacheRead = false, skipCache = false, chain, tokenType, } = {}) {
140
143
  var _a, _b;
141
144
  checkIndexerConfig("v1");
@@ -190,38 +193,119 @@ async function getTokens(address, { onlyWhitelisted = true, skipCacheRead = fals
190
193
  return tokens;
191
194
  }
192
195
  // ---------------------------------------------------------------------------
193
- // LOGS (v2 only)
196
+ // Streaming support (disabled by default, opt-in via clientStreaming: true)
197
+ // ---------------------------------------------------------------------------
198
+ async function streamJsonArrayFromIndexer(opts) {
199
+ const { path, arrayKey = "logs", onItem, onChunkStats, shouldStop } = opts;
200
+ const controller = new AbortController();
201
+ let bytesReceived = 0;
202
+ let itemsProcessed = 0;
203
+ const res = await axiosInstances.v2.get(path, {
204
+ responseType: "stream",
205
+ timeout: 0,
206
+ maxContentLength: Infinity,
207
+ maxBodyLength: Infinity,
208
+ headers: { Accept: "application/json", "Accept-Encoding": "gzip" },
209
+ signal: controller.signal,
210
+ }).catch((e) => {
211
+ if (axios_1.default.isCancel(e) || (e === null || e === void 0 ? void 0 : e.code) === 'ERR_CANCELED')
212
+ return { data: null };
213
+ throw (0, common_1.formError)(e);
214
+ });
215
+ if (!(res === null || res === void 0 ? void 0 : res.data))
216
+ return;
217
+ await new Promise((resolve, reject) => {
218
+ const stream = res.data;
219
+ const isCancellationError = (err) => {
220
+ var _a;
221
+ return (err === null || err === void 0 ? void 0 : err.name) === "CanceledError" ||
222
+ ((_a = err === null || err === void 0 ? void 0 : err.message) === null || _a === void 0 ? void 0 : _a.includes("aborted")) ||
223
+ axios_1.default.isCancel(err) ||
224
+ (err === null || err === void 0 ? void 0 : err.code) === 'ERR_CANCELED';
225
+ };
226
+ const handleError = (err) => isCancellationError(err) ? resolve() : reject(err);
227
+ stream.on('data', (chunk) => {
228
+ bytesReceived += chunk.length;
229
+ onChunkStats === null || onChunkStats === void 0 ? void 0 : onChunkStats({ chunkSize: chunk.length, bytesReceived, itemsProcessed });
230
+ });
231
+ const jsonParser = (0, stream_json_1.parser)();
232
+ const pickFilter = (0, Pick_1.pick)({ filter: arrayKey });
233
+ const arrayStream = (0, StreamArray_1.streamArray)();
234
+ arrayStream.on('data', (data) => {
235
+ if (shouldStop === null || shouldStop === void 0 ? void 0 : shouldStop()) {
236
+ controller.abort();
237
+ stream.destroy();
238
+ return;
239
+ }
240
+ try {
241
+ onItem(data.value);
242
+ itemsProcessed++;
243
+ if (itemsProcessed % 100000 === 0) {
244
+ onChunkStats === null || onChunkStats === void 0 ? void 0 : onChunkStats({ chunkSize: 100000, bytesReceived, itemsProcessed });
245
+ }
246
+ }
247
+ catch { }
248
+ });
249
+ const finish = () => {
250
+ const rem = itemsProcessed % 100000;
251
+ if (rem > 0) {
252
+ onChunkStats === null || onChunkStats === void 0 ? void 0 : onChunkStats({ chunkSize: rem, bytesReceived, itemsProcessed });
253
+ }
254
+ resolve();
255
+ };
256
+ arrayStream.on('end', finish);
257
+ arrayStream.on('close', finish);
258
+ arrayStream.on('error', handleError);
259
+ jsonParser.on('error', handleError);
260
+ pickFilter.on('error', handleError);
261
+ stream.on('error', handleError);
262
+ stream.pipe(jsonParser).pipe(pickFilter).pipe(arrayStream);
263
+ });
264
+ }
265
+ // ---------------------------------------------------------------------------
266
+ // LOGS (v2) – streaming opt-in, legacy default
194
267
  // ---------------------------------------------------------------------------
195
268
  async function getLogs(options) {
196
- var _a, _b, _c;
197
- let { all = true, limit = 1000, offset: initialOffset = 0, target, targets = [], flatten = true, debugMode = false, noTarget = false, maxBlockRange, processor, } = options;
269
+ // Set collect=false by default when processor is provided AND collect is not explicitly set AND clientStreaming = true
270
+ // if you pass processor without collect=true, you won't get results back
271
+ var _a, _b, _c, _d, _e, _f;
272
+ 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
273
+ decoderType = "viem", // default to viem for performance
274
+ onWireStats, onDecodeStats, } = options;
275
+ if (processor && typeof options.collect === "undefined" && clientStreaming) {
276
+ options.collect = false;
277
+ }
198
278
  const { chain, fromBlock, toBlock, topic, topic1, topic2, topic3, transformLog, } = await (0, logs_1.getLogParams)(options, true);
279
+ const viemFastPath = decoderType === "viem" && options.eventAbi
280
+ ? (0, logs_decode_shared_1.createViemFastPathBatchDecoder)(options.eventAbi)
281
+ : null;
199
282
  if (!debugMode)
200
283
  debugMode = debugLog_1.DEBUG_LEVEL2 && !!env_1.ENV_CONSTANTS.GET_LOGS_INDEXER;
284
+ // === Match logs.ts semantic regarding processor + multiple targets (flatten=false) ===
285
+ if (processor && (targets === null || targets === void 0 ? void 0 : targets.length) > 1 && !flatten)
286
+ throw new Error("processor is not supported with multiple targets when flatten=false");
201
287
  const blockRange = toBlock - fromBlock;
202
288
  const effectiveMaxBlockRange = maxBlockRange !== null && maxBlockRange !== void 0 ? maxBlockRange : (noTarget ? 10000 : Infinity);
203
289
  if (noTarget && blockRange > 500000) {
204
290
  throw new Error("When noTarget is true, block range must be less than 500k blocks. Please narrow down your block range.");
205
291
  }
206
- // We now only support v2 for logs ---------------------------------------------------------
292
+ // Ensure the indexer is synced far enough
207
293
  checkIndexerConfig("v2");
208
294
  const chainId = getChainId(chain, "v2");
209
- // Ensure the indexer is synced far enough
210
295
  const chainIndexStatus = await getChainIndexStatus("v2");
211
296
  const lastIndexedBlock = (_b = (_a = chainIndexStatus[chain]) === null || _a === void 0 ? void 0 : _a.block) !== null && _b !== void 0 ? _b : 0;
212
297
  if (lastIndexedBlock < toBlock) {
213
298
  const percentageMissing = ((toBlock - lastIndexedBlock) / (toBlock - fromBlock)) * 100;
214
- if (percentageMissing > 50) // more than 50% of the requested range is missing, throw an error and so we pull the logs through rpc calls
299
+ if (percentageMissing > 50)
215
300
  throw new Error(`Indexer not up to date for ${chain}. Last indexed block: ${lastIndexedBlock}, requested block: ${toBlock}`);
216
301
  if (env_1.ENV_CONSTANTS.GET_LOGS_INDEXER)
217
302
  (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.`);
218
- // now we split the request into two parts, one that goes to the indexer and one that goes through rpc calls
219
- const breakBlock = lastIndexedBlock - 50; // we add a small buffer to avoid missing logs due to reorgs
303
+ const breakBlock = lastIndexedBlock - 50; // small buffer
220
304
  const indexerLogs = await getLogs({ ...options, fromBlock, toBlock: breakBlock });
221
305
  const rpcLogs = await (0, logs_1.getLogs)({ ...options, fromBlock: breakBlock + 1, toBlock, skipIndexer: true });
222
306
  return indexerLogs.concat(rpcLogs);
223
307
  }
224
- // Re‑curse if the requested range is too large ------------------------------------------------
308
+ // Re-curse if the requested range is too large
225
309
  if (blockRange > effectiveMaxBlockRange) {
226
310
  const results = [];
227
311
  for (let currentFromBlock = fromBlock; currentFromBlock < toBlock; currentFromBlock += effectiveMaxBlockRange) {
@@ -233,9 +317,6 @@ async function getLogs(options) {
233
317
  return results.flat();
234
318
  return targets.map((_, i) => results.map((r) => r[i]).flat());
235
319
  }
236
- // -------------------------------------------------------------------------
237
- // Prepare address filters
238
- // -------------------------------------------------------------------------
239
320
  let address = target;
240
321
  if (typeof target === "string")
241
322
  targets = [target];
@@ -248,10 +329,177 @@ async function getLogs(options) {
248
329
  const addressChunks = (0, _1.sliceIntoChunks)((_c = address === null || address === void 0 ? void 0 : address.split(",")) !== null && _c !== void 0 ? _c : [], addressChunkSize);
249
330
  if (noTarget && addressChunks.length === 0)
250
331
  addressChunks.push(undefined);
251
- // -------------------------------------------------------------------------
252
- // Pull logs
253
- // -------------------------------------------------------------------------
254
- const allLogs = [];
332
+ // Safely push large arrays without stack overflow (avoid push.apply argument limits (~125k))
333
+ const safePush = (target, source) => {
334
+ if (source.length === 0)
335
+ return;
336
+ // For very large arrays, split into chunks
337
+ if (source.length > 100000) {
338
+ const CHUNK_SIZE = 50000; // Safe chunk size well below push.apply limit
339
+ for (let i = 0; i < source.length; i += CHUNK_SIZE) {
340
+ const chunk = source.slice(i, i + CHUNK_SIZE);
341
+ Array.prototype.push.apply(target, chunk);
342
+ }
343
+ }
344
+ else {
345
+ Array.prototype.push.apply(target, source);
346
+ }
347
+ };
348
+ // -----------------------------------------------------------------------
349
+ // Streaming (opt-in) — NO server-side parsing; mirror legacy semantics
350
+ // -----------------------------------------------------------------------
351
+ if (clientStreaming) {
352
+ const useAll = options.all === true;
353
+ const effectiveLimit = useAll ? Number.POSITIVE_INFINITY : ((_d = options.limit) !== null && _d !== void 0 ? _d : limit);
354
+ const shouldLimit = !useAll && Number.isFinite(effectiveLimit);
355
+ const effectiveOffset = useAll ? 0 : ((_f = (_e = options.offset) !== null && _e !== void 0 ? _e : initialOffset) !== null && _f !== void 0 ? _f : 0);
356
+ const outPairs = [];
357
+ const start = debugMode ? Date.now() : 0;
358
+ const MICRO_BATCH_SIZE = +(process.env.LLAMA_INDEXER_MICRO_BATCH || 10000);
359
+ // Check if we need splitByAddress (mapping by target address)
360
+ const splitByAddress = (targets === null || targets === void 0 ? void 0 : targets.length) && !flatten;
361
+ // If splitByAddress, build buckets directly during flushBatch
362
+ const addressBuckets = splitByAddress ? targets.map(() => []) : [];
363
+ const addressIndexMap = splitByAddress
364
+ ? Object.fromEntries(targets.map((t, i) => [t.toLowerCase(), i]))
365
+ : {};
366
+ let remainingOffset = effectiveOffset;
367
+ let remainingLimit = shouldLimit ? effectiveLimit : Number.POSITIVE_INFINITY;
368
+ const flushBatch = async (batch) => {
369
+ var _a, _b, _c;
370
+ if (!batch.length)
371
+ return;
372
+ const t0 = Date.now();
373
+ // Normalize logs before decoding (required for fast-path and consistency)
374
+ for (const log of batch) {
375
+ (0, logs_decode_shared_1.normalizeLog)(log, true); // isIndexerCall = true
376
+ }
377
+ // Use fast-path if available, otherwise fallback to default
378
+ const batchFn = viemFastPath !== null && viemFastPath !== void 0 ? viemFastPath : transformLog.batch;
379
+ let transformedLogs;
380
+ if (batchFn) {
381
+ // Fast-path returns only args, need to reconstruct full log objects
382
+ const decodedArgs = await batchFn(batch);
383
+ transformedLogs = new Array(batch.length);
384
+ for (let i = 0; i < batch.length; i++) {
385
+ if (options.onlyArgs) {
386
+ // If onlyArgs is true, return only the args (like transformLog does)
387
+ transformedLogs[i] = decodedArgs[i];
388
+ }
389
+ else {
390
+ transformedLogs[i] = {
391
+ ...batch[i],
392
+ args: decodedArgs[i]
393
+ };
394
+ if (!transformedLogs[i].transactionHash && transformedLogs[i]._originalTransactionHash) {
395
+ transformedLogs[i].transactionHash = transformedLogs[i]._originalTransactionHash;
396
+ }
397
+ }
398
+ }
399
+ }
400
+ else {
401
+ transformedLogs = await Promise.all(batch.map((log) => transformLog(log)));
402
+ }
403
+ const decodeTime = Date.now() - t0;
404
+ onDecodeStats === null || onDecodeStats === void 0 ? void 0 : onDecodeStats({ batchSize: batch.length, decodeTime, itemsDecoded: batch.length });
405
+ if (processor)
406
+ await processor(transformedLogs);
407
+ if (splitByAddress) {
408
+ // Map directly to address buckets without storing pairs
409
+ for (let i = 0; i < batch.length; i++) {
410
+ const raw = batch[i];
411
+ const transformed = transformedLogs[i];
412
+ const idx = addressIndexMap[(_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)];
413
+ if (idx !== undefined) {
414
+ addressBuckets[idx].push(transformed);
415
+ }
416
+ }
417
+ }
418
+ else if (options.collect !== false) {
419
+ // Build pairs if collect=true (needed for final return)
420
+ const pairs = new Array(batch.length);
421
+ for (let i = 0; i < batch.length; i++) {
422
+ pairs[i] = { raw: batch[i], transformed: transformedLogs[i] };
423
+ }
424
+ Array.prototype.push.apply(outPairs, pairs);
425
+ }
426
+ // If collect=false and not splitByAddress, nothing to store (processor already handled it)
427
+ };
428
+ for (const chunk of addressChunks) {
429
+ if (Array.isArray(chunk) && chunk.length === 0)
430
+ throw new Error("Address chunk cannot be empty");
431
+ const qs = new URLSearchParams();
432
+ qs.set("chainId", String(chainId));
433
+ qs.set("topic0", topic);
434
+ if (topic1)
435
+ qs.set("topic1", topic1);
436
+ if (topic2)
437
+ qs.set("topic2", topic2);
438
+ if (topic3)
439
+ qs.set("topic3", topic3);
440
+ if (fromBlock != null)
441
+ qs.set("from_block", String(fromBlock));
442
+ if (toBlock != null)
443
+ qs.set("to_block", String(toBlock));
444
+ if (chunk && Array.isArray(chunk))
445
+ qs.set("addresses", chunk.join(",").toLowerCase());
446
+ if (noTarget)
447
+ qs.set("noTarget", "true");
448
+ qs.set("limit", "all");
449
+ qs.set("offset", "0");
450
+ const transformBatch = [];
451
+ let stopNow = false;
452
+ let flushPromise = Promise.resolve();
453
+ // Chain flushes sequentially to avoid concurrent decoding (CPU/GC thrashing)
454
+ const scheduleFlush = (batch) => {
455
+ flushPromise = flushPromise.then(() => flushBatch(batch));
456
+ };
457
+ await streamJsonArrayFromIndexer({
458
+ path: `/logs?${qs.toString()}`,
459
+ arrayKey: "logs",
460
+ onItem: (raw) => {
461
+ var _a, _b, _c;
462
+ if (stopNow)
463
+ return;
464
+ 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));
465
+ if (!okAddress)
466
+ return;
467
+ if (remainingOffset > 0) {
468
+ remainingOffset--;
469
+ return;
470
+ }
471
+ if (remainingLimit <= 0) {
472
+ stopNow = true;
473
+ return;
474
+ }
475
+ transformBatch.push(raw);
476
+ remainingLimit--;
477
+ if (transformBatch.length >= MICRO_BATCH_SIZE) {
478
+ // Sequential flush: chain to avoid concurrent decoding
479
+ const batch = transformBatch.splice(0, MICRO_BATCH_SIZE);
480
+ scheduleFlush(batch);
481
+ }
482
+ },
483
+ shouldStop: () => stopNow,
484
+ onChunkStats: (s) => onWireStats === null || onWireStats === void 0 ? void 0 : onWireStats(s),
485
+ });
486
+ if (transformBatch.length > 0) {
487
+ scheduleFlush(transformBatch.splice(0, transformBatch.length));
488
+ }
489
+ await flushPromise;
490
+ if (remainingLimit <= 0)
491
+ break;
492
+ }
493
+ if (debugMode) {
494
+ const ms = Date.now() - start;
495
+ (0, debugLog_1.debugLog)(`[Indexer] stream finished: ${outPairs.length} items in ${ms}ms`);
496
+ }
497
+ if (splitByAddress) {
498
+ return addressBuckets;
499
+ }
500
+ return (options.collect !== false) ? outPairs.map(i => i.transformed) : [];
501
+ }
502
+ const allLogsPairs = [];
255
503
  const debugTimeKey = `Indexer-getLogs-${chain}-${topic}-${address}_${Math.random()}`;
256
504
  if (debugMode) {
257
505
  (0, debugLog_1.debugLog)("[Indexer] Pulling logs " + debugTimeKey);
@@ -276,65 +524,77 @@ async function getLogs(options) {
276
524
  limit,
277
525
  offset: chunkOffset,
278
526
  noTarget,
279
- eventAbi: options.eventAbi,
280
- parseLog: options.parseLog,
281
- onlyArgs: options.onlyArgs,
282
527
  };
283
528
  const { data: { logs: _logs, totalCount }, } = await axiosInstances.v2(`/logs`, { params }).catch(e => { throw (0, common_1.formError)(e); });
284
529
  const filtered = _logs.filter((l) => {
285
- const isWhitelisted = !addressSet.size || addressSet.has(l.source.toLowerCase());
286
- if (!isWhitelisted)
287
- return false;
288
- if (l.block_number) {
289
- l.blockNumber = parseInt(l.block_number);
290
- }
291
- return true;
530
+ var _a, _b, _c;
531
+ 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));
532
+ return !!isWhitelisted;
292
533
  });
293
- // we need to the { raw, transformed } structure to allow for post‑processing like clustering based on `source` field
294
- const transformed = filtered.map((log) => ({ raw: log, transformed: transformLog(log) }));
534
+ const t0 = Date.now();
535
+ // Normalize logs before decoding (required for consistency)
536
+ for (const log of filtered) {
537
+ (0, logs_decode_shared_1.normalizeLog)(log, true); // isIndexerCall = true
538
+ }
539
+ // Legacy path: use transformLog batch if available, otherwise transform individually
540
+ const transformBatchFn = transformLog.batch || ((logs) => Promise.all(logs.map((log) => transformLog(log))));
541
+ const transformedLogs = await transformBatchFn(filtered);
542
+ const transformedPair = filtered.map((log, idx) => ({ raw: log, transformed: transformedLogs[idx] }));
543
+ const decodeTime = Date.now() - t0;
544
+ if (onDecodeStats && filtered.length > 0)
545
+ onDecodeStats({ batchSize: filtered.length, decodeTime, itemsDecoded: filtered.length });
295
546
  if (processor)
296
- await processor(transformed.map((i) => i.transformed));
297
- allLogs.push(...transformed);
547
+ await processor(transformedPair.map((i) => i.transformed));
548
+ // Safe push helper to avoid stack overflow on large arrays
549
+ // Only accumulate if collect is enabled (default true for backward compatibility)
550
+ const shouldCollect = (options.collect !== false);
551
+ if (shouldCollect) {
552
+ safePush(allLogsPairs, transformedPair);
553
+ }
298
554
  logCount += _logs.length;
299
555
  chunkOffset += limit;
300
- if (_logs.length < limit || totalCount <= logCount || _logs.length === 0)
556
+ if (_logs.length === 0) {
557
+ hasMore = false;
558
+ }
559
+ else if (_logs.length < limit) {
560
+ hasMore = false;
561
+ }
562
+ else if (typeof totalCount === 'number' && totalCount <= logCount) {
301
563
  hasMore = false;
564
+ }
565
+ else {
566
+ hasMore = true;
567
+ }
302
568
  } while (all && hasMore);
303
569
  }
304
570
  if (debugMode) {
305
571
  console.timeEnd(debugTimeKey);
306
- (0, debugLog_1.debugLog)("Logs pulled " + chain, address, allLogs.length);
572
+ (0, debugLog_1.debugLog)("Logs pulled " + chain, address, allLogsPairs.length);
307
573
  }
308
- // -------------------------------------------------------------------------
309
- // Map / transform result
310
- // -------------------------------------------------------------------------
311
574
  const splitByAddress = (targets === null || targets === void 0 ? void 0 : targets.length) && !flatten;
312
575
  if (splitByAddress) {
313
576
  const mapped = targets.map(() => []);
314
577
  const indexMap = {};
315
578
  targets.forEach((t, i) => (indexMap[t.toLowerCase()] = i));
316
- allLogs.forEach(({ raw, transformed }) => {
317
- const idx = indexMap[raw.source.toLowerCase()];
579
+ allLogsPairs.forEach(({ raw, transformed }) => {
580
+ var _a, _b, _c;
581
+ const idx = indexMap[(_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)];
318
582
  if (idx === undefined)
319
583
  return; // ignore unknown sources
320
584
  mapped[idx].push(transformed);
321
585
  });
322
586
  return mapped;
323
587
  }
324
- return allLogs.map(i => i.transformed);
588
+ // Return empty array if collect=false (processor handles results)
589
+ const shouldCollect = (options.collect !== false);
590
+ return shouldCollect ? allLogsPairs.map(i => i.transformed) : [];
325
591
  }
326
- // ---------------------------------------------------------------------------
327
- // TOKEN TRANSFERS (v2 only)
328
- // ---------------------------------------------------------------------------
329
592
  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, }) {
330
593
  var _a, _b;
331
594
  if (!debugMode)
332
595
  debugMode = debugLog_1.DEBUG_LEVEL2 && !!env_1.ENV_CONSTANTS.GET_LOGS_INDEXER;
333
596
  checkIndexerConfig("v2");
334
597
  const chainId = getChainId(chain, "v2");
335
- // -----------------------------------------------------------------------
336
- // Input validation & pre‑processing
337
- // -----------------------------------------------------------------------
338
598
  const fromFilterEnabled = !!(fromAddressFilter === null || fromAddressFilter === void 0 ? void 0 : fromAddressFilter.length);
339
599
  if (typeof fromAddressFilter === "string")
340
600
  fromAddressFilter = [fromAddressFilter];
@@ -364,15 +624,12 @@ async function getTokenTransfers({ chain = "ethereum", fromAddressFilter, fromBl
364
624
  throw new Error("target|targets is required");
365
625
  targets = targets.map((t) => t.toLowerCase());
366
626
  const addresses = targets.join(",");
367
- // Ensure v2 indexer is up‑to‑date
627
+ // Ensure v2 indexer is up-to-date
368
628
  const chainIndexStatus = await getChainIndexStatus("v2");
369
629
  const lastIndexedBlock = (_b = (_a = chainIndexStatus[chain]) === null || _a === void 0 ? void 0 : _a.block) !== null && _b !== void 0 ? _b : 0;
370
630
  if (lastIndexedBlock < toBlock) {
371
631
  throw new Error(`Indexer not up to date for ${chain}. Last indexed block: ${lastIndexedBlock}, requested block: ${toBlock}`);
372
632
  }
373
- // -----------------------------------------------------------------------
374
- // Fetch transfers
375
- // -----------------------------------------------------------------------
376
633
  const rawTransfers = [];
377
634
  const debugTimeKey = `Indexer-tokenTransfers-${chain}-${addresses}_${Math.random()}`;
378
635
  if (debugMode) {
@@ -407,11 +664,10 @@ async function getTokenTransfers({ chain = "ethereum", fromAddressFilter, fromBl
407
664
  default:
408
665
  throw new Error("Invalid transferType");
409
666
  }
410
- const { data: { transfers: _logs, totalCount }, } = await axiosInstances.v2(`/token-transfers`, { params }).catch(e => { throw (0, common_1.formError)(e); });
667
+ const { data: { transfers: _logs }, } = await axiosInstances.v2(`/token-transfers`, { params }).catch(e => { throw (0, common_1.formError)(e); });
411
668
  rawTransfers.push(..._logs);
412
669
  currentOffset += limit;
413
- if (_logs.length < limit || totalCount <= rawTransfers.length || _logs.length === 0)
414
- hasMore = false;
670
+ hasMore = _logs.length === limit;
415
671
  } while (all && hasMore);
416
672
  const filteredTransfers = rawTransfers.filter((l) => {
417
673
  if (!fromFilterEnabled)
@@ -422,9 +678,6 @@ async function getTokenTransfers({ chain = "ethereum", fromAddressFilter, fromBl
422
678
  console.timeEnd(debugTimeKey);
423
679
  (0, debugLog_1.debugLog)("Token Transfers pulled " + chain, addresses, filteredTransfers.length);
424
680
  }
425
- // -----------------------------------------------------------------------
426
- // Split result if requested
427
- // -----------------------------------------------------------------------
428
681
  const splitByAddress = (targets === null || targets === void 0 ? void 0 : targets.length) && !flatten;
429
682
  if (splitByAddress) {
430
683
  const mapped = targets.map(() => []);
@@ -440,9 +693,6 @@ async function getTokenTransfers({ chain = "ethereum", fromAddressFilter, fromBl
440
693
  }
441
694
  return filteredTransfers;
442
695
  }
443
- // ---------------------------------------------------------------------------
444
- // TRANSACTIONS (v2 only)
445
- // ---------------------------------------------------------------------------
446
696
  async function getTransactions({ chain = "ethereum", addresses, transaction_hashes, from_block, to_block, all = true, limit = 1000, offset = 0, debugMode = false, transactionType = "from", }) {
447
697
  var _a, _b;
448
698
  if (!debugMode)
@@ -515,11 +765,7 @@ async function getTransactions({ chain = "ethereum", addresses, transaction_hash
515
765
  timestamp: t.timestamp,
516
766
  }));
517
767
  }
518
- // ---------------------------------------------------------------------------
519
- // Public helpers
520
- // ---------------------------------------------------------------------------
521
768
  function isIndexerEnabled(chain) {
522
- // Legacy function kept for backward‑compat: return true if v2 is enabled
523
769
  if (!indexerConfigs.v2.endpoint)
524
770
  return false;
525
771
  if (chain && !exports.supportedChainSet2.has(chain))
@@ -527,6 +773,5 @@ function isIndexerEnabled(chain) {
527
773
  return true;
528
774
  }
529
775
  function isIndexer2Enabled(chain) {
530
- // Alias – retained so external calls do not break
531
776
  return isIndexerEnabled(chain);
532
777
  }
@@ -0,0 +1,8 @@
1
+ import { ethers, Interface } from "ethers";
2
+ export declare function padTopic32(t: string): `0x${string}`;
3
+ export declare function normalizeLog(log: any, isIndexerCall?: boolean): boolean;
4
+ export declare function normalizeValueByType(value: any, type: string): any;
5
+ export declare function convertTopicsToViemFormat(topics: any[]): [`0x${string}`, ...(`0x${string}`[] | [])] | [];
6
+ export declare function enrichEthersArgsWithNamedProperties(ethersArgs: any, event: ethers.EventFragment | null, iface: Interface): any;
7
+ export declare function buildEthersCompatibleArgs(decodedArgs: any, eventInputs: readonly ethers.ParamType[], normalizeFn?: (value: any, type: string) => any): any;
8
+ export declare function createViemFastPathBatchDecoder(eventAbi: string | any): ((logs: any[]) => Promise<any[]>) | null;