@defillama/sdk 5.0.235 → 5.0.236

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 (60) hide show
  1. package/build/ChainApi.d.ts +23 -1
  2. package/build/chains/algorand.d.ts +277 -0
  3. package/build/chains/algorand.js +618 -0
  4. package/build/chains/aptos.d.ts +230 -0
  5. package/build/chains/aptos.js +612 -0
  6. package/build/chains/cardano.d.ts +242 -0
  7. package/build/chains/cardano.js +407 -0
  8. package/build/chains/cosmos.d.ts +252 -0
  9. package/build/chains/cosmos.js +618 -0
  10. package/build/chains/icp.d.ts +235 -0
  11. package/build/chains/icp.js +1040 -0
  12. package/build/chains/index.d.ts +23 -0
  13. package/build/chains/index.js +59 -0
  14. package/build/chains/near.d.ts +192 -0
  15. package/build/chains/near.js +446 -0
  16. package/build/chains/rpc.d.ts +125 -0
  17. package/build/chains/rpc.js +326 -0
  18. package/build/chains/starknet.d.ts +205 -0
  19. package/build/chains/starknet.js +884 -0
  20. package/build/chains/stellar.d.ts +410 -0
  21. package/build/chains/stellar.js +1082 -0
  22. package/build/chains/substrate.d.ts +333 -0
  23. package/build/chains/substrate.js +1042 -0
  24. package/build/chains/sui.d.ts +365 -0
  25. package/build/chains/sui.js +1135 -0
  26. package/build/chains/svm.d.ts +340 -0
  27. package/build/chains/svm.js +760 -0
  28. package/build/chains/tezos.d.ts +225 -0
  29. package/build/chains/tezos.js +412 -0
  30. package/build/chains/ton.d.ts +353 -0
  31. package/build/chains/ton.js +788 -0
  32. package/build/chains/tron.d.ts +45 -0
  33. package/build/chains/tron.js +110 -0
  34. package/build/chains/utxo.d.ts +202 -0
  35. package/build/chains/utxo.js +592 -0
  36. package/build/chains/xrpl.d.ts +179 -0
  37. package/build/chains/xrpl.js +467 -0
  38. package/build/index.d.ts +2 -1
  39. package/build/index.js +3 -1
  40. package/build/providers.json +1 -1
  41. package/build/util/chainUtils/data.json +11 -0
  42. package/build/util/chainUtils/index.d.ts +1 -0
  43. package/build/util/chainUtils/index.js +23 -0
  44. package/build/util/common.js +2 -3
  45. package/build/util/cosmos.d.ts +1 -11
  46. package/build/util/cosmos.js +6 -66
  47. package/build/util/env.js +1 -0
  48. package/build/util/indexer.compatibility.d.ts +6 -0
  49. package/build/util/indexer.compatibility.js +64 -0
  50. package/build/util/indexer.d.ts +24 -2
  51. package/build/util/indexer.js +215 -282
  52. package/build/util/indexerSql.d.ts +8 -1
  53. package/build/util/indexerSql.js +53 -22
  54. package/build/util/logs.decode.shared.js +5 -4
  55. package/build/util/transactions.d.ts +23 -1
  56. package/build/util/updateChainLabels.d.ts +1 -0
  57. package/build/util/updateChainLabels.js +5 -0
  58. package/build/util/updateProviderList.js +38 -17
  59. package/package.json +20 -7
  60. 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,28 @@ 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
  };
98
97
  function checkIndexerConfig(version) {
99
98
  const { endpoint, apiKey } = indexerConfigs[version];
100
99
  if (!endpoint || !apiKey)
101
100
  throw new Error(`Llama Indexer (${version}) URL/api key is not set`);
102
101
  }
103
- // v4-only chains always go to v4; the rest go to v2 unless PREFER_V4 is set or v2 is not configured
102
+ // Prefer a configured v4 deployment; retain v2-only installations and explicit rollback.
104
103
  function getChainIndexerVersion(chain) {
105
104
  if (v4OnlyChainSet.has(chain))
106
105
  return "v4";
@@ -117,13 +116,7 @@ function getChainId(chain) {
117
116
  throw new Error("Chain not supported");
118
117
  return +chainId;
119
118
  }
120
- function getSupportedChains() {
121
- return new Set(Object.values(allChainIdChainMapping));
122
- }
123
- exports.supportedChainSet2 = getSupportedChains();
124
- function supportsCursorPagination(version) {
125
- return version === "v4";
126
- }
119
+ exports.supportedChainSet2 = new Set(Object.values(allChainIdChainMapping));
127
120
  function getLogCursor(log) {
128
121
  var _a, _b, _c;
129
122
  if (!log)
@@ -142,13 +135,14 @@ function getTransferCursor(transfer) {
142
135
  cursor.after_id = String(transfer.id);
143
136
  return cursor;
144
137
  }
145
- function applyCursorParams(params, cursor) {
146
- if (!cursor)
138
+ function getTransactionCursor(transaction) {
139
+ if (!transaction)
147
140
  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;
141
+ return { after_block: +transaction.block_number, after_index: +transaction.transaction_index };
142
+ }
143
+ function safePush(target, source) {
144
+ for (const row of source)
145
+ target.push(row);
152
146
  }
153
147
  function applyLocalOffset(rows, remainingOffset) {
154
148
  if (remainingOffset <= 0)
@@ -157,6 +151,72 @@ function applyLocalOffset(rows, remainingOffset) {
157
151
  return { rows: [], remainingOffset: remainingOffset - rows.length };
158
152
  return { rows: rows.slice(remainingOffset), remainingOffset: 0 };
159
153
  }
154
+ function assertCursorAdvances(next, previous) {
155
+ if (!next || !Number.isSafeInteger(next.after_block) || !Number.isSafeInteger(next.after_index))
156
+ throw new Error("Indexer v4 returned a page without a valid cursor");
157
+ if (!previous)
158
+ return;
159
+ const advances = next.after_block > previous.after_block ||
160
+ (next.after_block === previous.after_block && (next.after_index > previous.after_index ||
161
+ (next.after_index === previous.after_index && next.after_id !== undefined && previous.after_id !== undefined &&
162
+ BigInt(next.after_id) > BigInt(previous.after_id))));
163
+ if (!advances)
164
+ throw new Error("Indexer v4 pagination cursor did not advance");
165
+ }
166
+ // Translate the public offset/limit contract into keyset pagination. Even all=false
167
+ // may need several requests: the v4 server caps offsets, not the public SDK API.
168
+ async function* getV4Pages(path, key, params, limit, offset, all, getCursor) {
169
+ if (!Number.isSafeInteger(limit) || limit < 0 || !Number.isSafeInteger(offset) || offset < 0)
170
+ throw new Error("Indexer pagination requires a non-negative integer limit and offset");
171
+ if (limit === 0)
172
+ return;
173
+ let cursor;
174
+ let remainingOffset = offset;
175
+ let remainingLimit = all ? Infinity : limit;
176
+ while (remainingLimit > 0) {
177
+ const pageParams = { ...params, limit, offset: 0, includeTotal: false, ...cursor };
178
+ const response = await axiosInstances.v4(path, {
179
+ params: pageParams,
180
+ ...(key === "transfers" ? { responseType: "text", transformResponse: (data) => data } : {}),
181
+ }).catch((e) => { throw (0, common_1.formError)(e); });
182
+ const data = key === "transfers" && typeof response.data === "string"
183
+ ? await (0, indexer_compatibility_1.parseTransferResponse)(response.data) : response.data;
184
+ const page = data[key];
185
+ if (!Array.isArray(page))
186
+ throw new Error(`Indexer v4 returned an invalid ${key} response`);
187
+ // Capture the cursor before consumers normalize/mutate the raw rows.
188
+ const next = getCursor(page[page.length - 1]);
189
+ const result = applyLocalOffset(page, remainingOffset);
190
+ remainingOffset = result.remainingOffset;
191
+ const rows = result.rows.slice(0, remainingLimit);
192
+ remainingLimit -= rows.length;
193
+ const hasMore = page.length >= limit && remainingLimit > 0;
194
+ if (hasMore || (page.length && cursor))
195
+ assertCursorAdvances(next, cursor);
196
+ if (rows.length)
197
+ yield rows.map(row => (0, indexer_compatibility_1.normalizeV4Row)(row, key));
198
+ if (!hasMore)
199
+ return;
200
+ cursor = next;
201
+ }
202
+ }
203
+ async function* getIndexerPages(version, path, key, params, limit, offset, all, getCursor) {
204
+ if (version === "v4") {
205
+ yield* getV4Pages(path, key, params, limit, offset, all, getCursor);
206
+ return;
207
+ }
208
+ let count = 0;
209
+ do {
210
+ const { data } = await axiosInstances.v2(path, { params: { ...params, limit, offset } })
211
+ .catch((e) => { throw (0, common_1.formError)(e); });
212
+ const rows = data[key];
213
+ yield rows;
214
+ count += rows.length;
215
+ if (!rows.length || rows.length < limit || (key === "logs" && typeof data.totalCount === "number" && data.totalCount <= count))
216
+ return;
217
+ offset += limit;
218
+ } while (all);
219
+ }
160
220
  const syncStates = {
161
221
  v2: { chainIndexStatus: {} },
162
222
  v4: { chainIndexStatus: {} },
@@ -181,94 +241,88 @@ async function getChainIndexStatus(version) {
181
241
  }
182
242
  });
183
243
  return (state.chainIndexStatus = info);
184
- })();
244
+ })().catch((error) => {
245
+ state.timestamp = undefined;
246
+ state.chainIndexStatus = {};
247
+ throw error;
248
+ });
185
249
  return state.chainIndexStatus;
186
250
  }
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;
251
+ async function streamLogs(opts) {
252
+ const { path, version, onItem, onChunkStats, shouldStop } = opts;
192
253
  const controller = new AbortController();
193
254
  let bytesReceived = 0;
194
255
  let itemsProcessed = 0;
195
256
  const res = await axiosInstances[version].get(path, {
196
257
  responseType: "stream",
197
- timeout: 0,
258
+ timeout: INDEXER_REQUEST_TIMEOUT_MS,
198
259
  maxContentLength: Infinity,
199
260
  maxBodyLength: Infinity,
200
261
  headers: { Accept: "application/json", "Accept-Encoding": "gzip" },
201
262
  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;
263
+ }).catch((e) => { throw (0, common_1.formError)(e); });
264
+ let stopped = false;
265
+ let processingError;
266
+ try {
267
+ await (0, promises_1.pipeline)(res.data, async function* (source) {
268
+ for await (const chunk of source) {
269
+ bytesReceived += chunk.length;
270
+ onChunkStats === null || onChunkStats === void 0 ? void 0 : onChunkStats({ chunkSize: chunk.length, bytesReceived, itemsProcessed });
271
+ yield chunk;
231
272
  }
232
- try {
233
- onItem(data.value);
273
+ }, (0, stream_json_1.parser)(), (0, Pick_1.pick)({ filter: "logs" }), (0, StreamArray_1.streamArray)(), async (source) => {
274
+ for await (const { value } of source) {
275
+ try {
276
+ await onItem(value);
277
+ }
278
+ catch (e) {
279
+ processingError = e;
280
+ throw e;
281
+ }
234
282
  itemsProcessed++;
235
- if (itemsProcessed % 100000 === 0) {
236
- onChunkStats === null || onChunkStats === void 0 ? void 0 : onChunkStats({ chunkSize: 100000, bytesReceived, itemsProcessed });
283
+ if (shouldStop === null || shouldStop === void 0 ? void 0 : shouldStop()) {
284
+ stopped = true;
285
+ controller.abort();
286
+ break;
237
287
  }
238
288
  }
239
- catch { }
240
289
  });
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);
290
+ }
291
+ catch (e) {
292
+ // Only our requested early stop is successful; remote truncation and decode
293
+ // or processor errors must reject instead of returning incomplete results.
294
+ if (!stopped)
295
+ throw processingError !== null && processingError !== void 0 ? processingError : e;
296
+ }
297
+ onChunkStats === null || onChunkStats === void 0 ? void 0 : onChunkStats({ chunkSize: 0, bytesReceived, itemsProcessed });
298
+ }
299
+ async function decodeStreamingLogs(logs, transformLog, batchDecoder, onlyArgs) {
300
+ for (const log of logs)
301
+ (0, logs_decode_shared_1.normalizeLog)(log, true);
302
+ if (!batchDecoder)
303
+ return Promise.all(logs.map(log => transformLog(log)));
304
+ // The historical streaming fast path returns args without buffered parsedLog metadata.
305
+ const args = await batchDecoder(logs);
306
+ return logs.map((log, i) => {
307
+ if (onlyArgs)
308
+ return args[i];
309
+ const decoded = { ...log, args: args[i] };
310
+ if (!decoded.transactionHash && decoded._originalTransactionHash)
311
+ decoded.transactionHash = decoded._originalTransactionHash;
312
+ return decoded;
255
313
  });
256
314
  }
257
- // ---------------------------------------------------------------------------
258
- // LOGS (v2) – streaming opt-in, legacy default
259
- // ---------------------------------------------------------------------------
260
315
  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;
316
+ var _a, _b, _c, _d, _e;
317
+ 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
318
  if (processor && typeof options.collect === "undefined" && clientStreaming) {
268
- options.collect = false;
319
+ options = { ...options, collect: false };
269
320
  }
270
321
  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 viemFastPath =
323
+ // Preserve the historical streaming shape, including entireLog: the viem
324
+ // fast path exposes args without adding the buffered decoder's parsedLog.
325
+ decoderType === "viem" && options.eventAbi && options.parseLog !== false && !options.allowParseFailure
272
326
  ? (0, logs_decode_shared_1.createViemFastPathBatchDecoder)(options.eventAbi)
273
327
  : null;
274
328
  if (!debugMode)
@@ -278,6 +332,8 @@ async function getLogs(options) {
278
332
  throw new Error("processor is not supported with multiple targets when flatten=false");
279
333
  const blockRange = toBlock - fromBlock;
280
334
  const effectiveMaxBlockRange = maxBlockRange !== null && maxBlockRange !== void 0 ? maxBlockRange : (noTarget ? 10000 : Infinity);
335
+ if (!(effectiveMaxBlockRange > 0))
336
+ throw new Error("maxBlockRange must be greater than zero");
281
337
  if (noTarget && blockRange > 500000) {
282
338
  throw new Error("When noTarget is true, block range must be less than 500k blocks. Please narrow down your block range.");
283
339
  }
@@ -305,7 +361,7 @@ async function getLogs(options) {
305
361
  // Re-curse if the requested range is too large
306
362
  if (blockRange > effectiveMaxBlockRange) {
307
363
  const results = [];
308
- for (let currentFromBlock = fromBlock; currentFromBlock < toBlock; currentFromBlock += effectiveMaxBlockRange) {
364
+ for (let currentFromBlock = fromBlock; currentFromBlock <= toBlock; currentFromBlock += effectiveMaxBlockRange) {
309
365
  const currentToBlock = Math.min(currentFromBlock + effectiveMaxBlockRange - 1, toBlock);
310
366
  const chunk = await getLogs({ ...options, fromBlock: currentFromBlock, toBlock: currentToBlock });
311
367
  results.push(chunk);
@@ -326,36 +382,15 @@ async function getLogs(options) {
326
382
  const addressChunks = (0, _1.sliceIntoChunks)((_c = address === null || address === void 0 ? void 0 : address.split(",")) !== null && _c !== void 0 ? _c : [], addressChunkSize);
327
383
  if (noTarget && addressChunks.length === 0)
328
384
  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
385
  if (clientStreaming) {
349
386
  const useAll = all === true;
350
387
  const effectiveLimit = useAll ? Number.POSITIVE_INFINITY : ((_d = options.limit) !== null && _d !== void 0 ? _d : limit);
351
388
  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 = [];
389
+ const effectiveOffset = (_e = options.offset) !== null && _e !== void 0 ? _e : initialOffset;
390
+ const collected = [];
354
391
  const start = debugMode ? Date.now() : 0;
355
392
  const MICRO_BATCH_SIZE = +(process.env.LLAMA_INDEXER_MICRO_BATCH || 10000);
356
- // Check if we need splitByAddress (mapping by target address)
357
393
  const splitByAddress = (targets === null || targets === void 0 ? void 0 : targets.length) && !flatten;
358
- // If splitByAddress, build buckets directly during flushBatch
359
394
  const addressBuckets = splitByAddress ? targets.map(() => []) : [];
360
395
  const addressIndexMap = splitByAddress
361
396
  ? Object.fromEntries(targets.map((t, i) => [t.toLowerCase(), i]))
@@ -367,42 +402,12 @@ async function getLogs(options) {
367
402
  if (!batch.length)
368
403
  return;
369
404
  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
- }
405
+ const transformedLogs = await decodeStreamingLogs(batch, transformLog, viemFastPath !== null && viemFastPath !== void 0 ? viemFastPath : transformLog.batch, options.onlyArgs);
400
406
  const decodeTime = Date.now() - t0;
401
407
  onDecodeStats === null || onDecodeStats === void 0 ? void 0 : onDecodeStats({ batchSize: batch.length, decodeTime, itemsDecoded: batch.length });
402
408
  if (processor)
403
409
  await processor(transformedLogs);
404
- if (splitByAddress) {
405
- // Map directly to address buckets without storing pairs
410
+ if (splitByAddress && options.collect !== false) {
406
411
  for (let i = 0; i < batch.length; i++) {
407
412
  const raw = batch[i];
408
413
  const transformed = transformedLogs[i];
@@ -413,14 +418,8 @@ async function getLogs(options) {
413
418
  }
414
419
  }
415
420
  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);
421
+ safePush(collected, transformedLogs);
422
422
  }
423
- // If collect=false and not splitByAddress, nothing to store (processor already handled it)
424
423
  };
425
424
  for (const chunk of addressChunks) {
426
425
  if (Array.isArray(chunk) && chunk.length === 0)
@@ -446,19 +445,15 @@ async function getLogs(options) {
446
445
  qs.set("offset", "0");
447
446
  const transformBatch = [];
448
447
  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({
448
+ await streamLogs({
455
449
  path: `/logs?${qs.toString()}`,
456
450
  version: indexerVersion,
457
- arrayKey: "logs",
458
- onItem: (raw) => {
451
+ onItem: async (raw) => {
459
452
  var _a, _b, _c;
460
453
  if (stopNow)
461
454
  return;
455
+ if (indexerVersion === "v4")
456
+ (0, indexer_compatibility_1.normalizeV4Row)(raw, "logs");
462
457
  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
458
  if (!okAddress)
464
459
  return;
@@ -472,30 +467,30 @@ async function getLogs(options) {
472
467
  }
473
468
  transformBatch.push(raw);
474
469
  remainingLimit--;
470
+ if (remainingLimit <= 0)
471
+ stopNow = true;
475
472
  if (transformBatch.length >= MICRO_BATCH_SIZE) {
476
- // Sequential flush: chain to avoid concurrent decoding
477
473
  const batch = transformBatch.splice(0, MICRO_BATCH_SIZE);
478
- scheduleFlush(batch);
474
+ await flushBatch(batch);
479
475
  }
480
476
  },
481
477
  shouldStop: () => stopNow,
482
- onChunkStats: (s) => onWireStats === null || onWireStats === void 0 ? void 0 : onWireStats(s),
478
+ onChunkStats: onWireStats,
483
479
  });
484
480
  if (transformBatch.length > 0) {
485
- scheduleFlush(transformBatch.splice(0, transformBatch.length));
481
+ await flushBatch(transformBatch.splice(0, transformBatch.length));
486
482
  }
487
- await flushPromise;
488
483
  if (remainingLimit <= 0)
489
484
  break;
490
485
  }
491
486
  if (debugMode) {
492
487
  const ms = Date.now() - start;
493
- (0, debugLog_1.debugLog)(`[Indexer] stream finished: ${outPairs.length} items in ${ms}ms`);
488
+ (0, debugLog_1.debugLog)(`[Indexer] stream finished: ${collected.length} items in ${ms}ms`);
494
489
  }
495
490
  if (splitByAddress) {
496
491
  return addressBuckets;
497
492
  }
498
- return (options.collect !== false) ? outPairs.map(i => i.transformed) : [];
493
+ return collected;
499
494
  }
500
495
  const allLogsPairs = [];
501
496
  const debugTimeKey = `Indexer-getLogs-${chain}-${topic}-${address}_${Math.random()}`;
@@ -503,48 +498,24 @@ async function getLogs(options) {
503
498
  (0, debugLog_1.debugLog)("[Indexer] Pulling logs " + debugTimeKey);
504
499
  console.time(debugTimeKey);
505
500
  }
506
- const useCursorPagination = supportsCursorPagination(indexerVersion) && all;
507
501
  for (const chunk of addressChunks) {
508
502
  if (Array.isArray(chunk) && chunk.length === 0)
509
503
  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) => {
504
+ const params = {
505
+ addresses: hasAddressFilter ? chunk === null || chunk === void 0 ? void 0 : chunk.join(",") : undefined,
506
+ chainId, topic0: topic, from_block: fromBlock, to_block: toBlock,
507
+ topic1, topic2, topic3, noTarget,
508
+ };
509
+ for await (const rows of getIndexerPages(indexerVersion, "/logs", "logs", params, limit, initialOffset, all, getLogCursor)) {
510
+ const filtered = rows.filter((l) => {
538
511
  var _a, _b, _c;
539
512
  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
513
  return !!isWhitelisted;
541
514
  });
542
515
  const t0 = Date.now();
543
- // Normalize logs before decoding (required for consistency)
544
516
  for (const log of filtered) {
545
- (0, logs_decode_shared_1.normalizeLog)(log, true); // isIndexerCall = true
517
+ (0, logs_decode_shared_1.normalizeLog)(log, true);
546
518
  }
547
- // Legacy path: use transformLog batch if available, otherwise transform individually
548
519
  const transformBatchFn = transformLog.batch || ((logs) => Promise.all(logs.map((log) => transformLog(log))));
549
520
  const transformedLogs = await transformBatchFn(filtered);
550
521
  const transformedPair = filtered.map((log, idx) => ({ raw: log, transformed: transformedLogs[idx] }));
@@ -553,33 +524,10 @@ async function getLogs(options) {
553
524
  onDecodeStats({ batchSize: filtered.length, decodeTime, itemsDecoded: filtered.length });
554
525
  if (processor)
555
526
  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) {
527
+ if (options.collect !== false) {
560
528
  safePush(allLogsPairs, transformedPair);
561
529
  }
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);
530
+ }
583
531
  }
584
532
  if (debugMode) {
585
533
  console.timeEnd(debugTimeKey);
@@ -599,9 +547,7 @@ async function getLogs(options) {
599
547
  });
600
548
  return mapped;
601
549
  }
602
- // Return empty array if collect=false (processor handles results)
603
- const shouldCollect = (options.collect !== false);
604
- return shouldCollect ? allLogsPairs.map(i => i.transformed) : [];
550
+ return allLogsPairs.map(i => i.transformed);
605
551
  }
606
552
  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
553
  var _a, _b;
@@ -651,55 +597,27 @@ async function getTokenTransfers({ chain = "ethereum", fromAddressFilter, fromBl
651
597
  (0, debugLog_1.debugLog)("[Indexer] Pulling token transfers " + debugTimeKey);
652
598
  console.time(debugTimeKey);
653
599
  }
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);
600
+ const params = {
601
+ addresses, chainId, from_block: fromBlock, to_block: toBlock, tokens,
602
+ from_address: false, to_address: false,
603
+ };
604
+ switch (transferType) {
605
+ case "in":
606
+ params.to_address = true;
607
+ break;
608
+ case "out":
609
+ params.from_address = true;
610
+ break;
611
+ case "all":
612
+ params.from_address = true;
613
+ params.to_address = true;
614
+ break;
615
+ default:
616
+ throw new Error("Invalid transferType");
617
+ }
618
+ for await (const rows of getIndexerPages(indexerVersion, "/token-transfers", "transfers", params, limit, offset, all, getTransferCursor)) {
619
+ safePush(rawTransfers, rows);
620
+ }
703
621
  const filteredTransfers = rawTransfers.filter((l) => {
704
622
  if (!fromFilterEnabled)
705
623
  return true;
@@ -766,7 +684,22 @@ async function getTransactions({ chain = "ethereum", addresses, transaction_hash
766
684
  (0, debugLog_1.debugLog)("[Indexer] Pulling transactions " + debugTimeKey);
767
685
  console.time(debugTimeKey);
768
686
  }
769
- const { data: { transactions } } = await axiosInstances[indexerVersion](`/transactions`, { params }).catch((e) => { throw (0, common_1.formError)(e); });
687
+ let transactions;
688
+ if (indexerVersion === "v4") {
689
+ transactions = [];
690
+ // all=false with limit=0 historically omitted limit (server default 1000).
691
+ const requestedLimit = typeof limit === "number" && limit > 0 ? limit : 1000;
692
+ // v2 ignores limit when all=true. Preserve that public contract without
693
+ // turning a small caller limit into hundreds of v4 network round trips.
694
+ const pageLimit = all ? Math.max(1000, requestedLimit) : requestedLimit;
695
+ for await (const rows of getV4Pages("/transactions", "transactions", params, pageLimit, offset, all, getTransactionCursor)) {
696
+ safePush(transactions, rows);
697
+ }
698
+ }
699
+ else {
700
+ const { data } = await axiosInstances.v2('/transactions', { params }).catch((e) => { throw (0, common_1.formError)(e); });
701
+ transactions = data.transactions;
702
+ }
770
703
  if (debugMode) {
771
704
  console.timeEnd(debugTimeKey);
772
705
  (0, debugLog_1.debugLog)("Transactions pulled " + chain, addresses || transaction_hashes, (transactions === null || transactions === void 0 ? void 0 : transactions.length) || 0);