@defillama/sdk 5.0.234 → 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 (63) hide show
  1. package/build/ChainApi.d.ts +23 -1
  2. package/build/ChainApi.js +1 -0
  3. package/build/abi/multicall3.js +4 -0
  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/chainUtils/data.json +16 -1
  44. package/build/util/chainUtils/index.d.ts +1 -0
  45. package/build/util/chainUtils/index.js +23 -0
  46. package/build/util/common.js +2 -3
  47. package/build/util/cosmos.d.ts +1 -11
  48. package/build/util/cosmos.js +6 -66
  49. package/build/util/env.js +1 -0
  50. package/build/util/indexer.compatibility.d.ts +6 -0
  51. package/build/util/indexer.compatibility.js +64 -0
  52. package/build/util/indexer.d.ts +24 -2
  53. package/build/util/indexer.js +215 -285
  54. package/build/util/indexerSql.d.ts +8 -1
  55. package/build/util/indexerSql.js +53 -22
  56. package/build/util/logs.decode.shared.js +5 -4
  57. package/build/util/logs.js +1 -2
  58. package/build/util/transactions.d.ts +23 -1
  59. package/build/util/updateChainLabels.d.ts +1 -0
  60. package/build/util/updateChainLabels.js +5 -0
  61. package/build/util/updateProviderList.js +40 -17
  62. package/package.json +20 -7
  63. 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,12 +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
- // route every supported chain through v4 (v2 stays available as explicit config)
32
- const LLAMA_INDEXER_PREFER_V4 = (0, env_1.getEnvValue)("LLAMA_INDEXER_PREFER_V4") === "true";
33
+ const LLAMA_INDEXER_PREFER_V4 = (0, env_1.getEnvValue)("LLAMA_INDEXER_PREFER_V4") !== "false"; // explicit false allows rollback
33
34
  const addressChunkSize = +(0, env_1.getEnvValue)("LLAMA_INDEXER_ADDRESS_CHUNK_SIZE") || 100;
34
35
  const INDEXER_REQUEST_TIMEOUT_MS = +(0, env_1.getEnvValue)("LLAMA_INDEXER_TIMEOUT_MS");
35
36
  const indexerChainIdChainMapping = {
@@ -77,33 +78,28 @@ const indexerConfigs = {
77
78
  v2: { endpoint: LLAMA_INDEXER_V2_ENDPOINT, apiKey: LLAMA_INDEXER_V2_API_KEY },
78
79
  v4: { endpoint: LLAMA_INDEXER_V4_ENDPOINT, apiKey: LLAMA_INDEXER_V4_API_KEY },
79
80
  };
80
- // http agents for streaming (keep-alive)
81
81
  const httpAgent = new http_1.default.Agent({ keepAlive: true, maxSockets: 256, maxFreeSockets: 64 });
82
82
  const httpsAgent = new https_1.default.Agent({ keepAlive: true, maxSockets: 256, maxFreeSockets: 64 });
83
- const axiosInstances = {
84
- v2: axios_1.default.create({
85
- headers: { "x-api-key": LLAMA_INDEXER_V2_API_KEY },
86
- baseURL: LLAMA_INDEXER_V2_ENDPOINT,
87
- httpAgent,
88
- httpsAgent,
89
- timeout: INDEXER_REQUEST_TIMEOUT_MS,
90
- }),
91
- v4: axios_1.default.create({
92
- headers: { "x-api-key": LLAMA_INDEXER_V4_API_KEY },
93
- 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,
94
88
  httpAgent,
95
89
  httpsAgent,
96
90
  timeout: INDEXER_REQUEST_TIMEOUT_MS,
97
- }),
91
+ });
92
+ }
93
+ const axiosInstances = {
94
+ v2: createIndexerClient("v2"),
95
+ v4: createIndexerClient("v4"),
98
96
  };
99
97
  function checkIndexerConfig(version) {
100
98
  const { endpoint, apiKey } = indexerConfigs[version];
101
99
  if (!endpoint || !apiKey)
102
100
  throw new Error(`Llama Indexer (${version}) URL/api key is not set`);
103
101
  }
104
- // pick which indexer serves a given chain:
105
- // - v4-only chains (not indexed on v2) always go to v4
106
- // - everything else goes to v2, unless LLAMA_INDEXER_PREFER_V4 is set or v2 is not configured
102
+ // Prefer a configured v4 deployment; retain v2-only installations and explicit rollback.
107
103
  function getChainIndexerVersion(chain) {
108
104
  if (v4OnlyChainSet.has(chain))
109
105
  return "v4";
@@ -120,13 +116,7 @@ function getChainId(chain) {
120
116
  throw new Error("Chain not supported");
121
117
  return +chainId;
122
118
  }
123
- function getSupportedChains() {
124
- return new Set(Object.values(allChainIdChainMapping));
125
- }
126
- exports.supportedChainSet2 = getSupportedChains();
127
- function supportsCursorPagination(version) {
128
- return version === "v4";
129
- }
119
+ exports.supportedChainSet2 = new Set(Object.values(allChainIdChainMapping));
130
120
  function getLogCursor(log) {
131
121
  var _a, _b, _c;
132
122
  if (!log)
@@ -145,13 +135,14 @@ function getTransferCursor(transfer) {
145
135
  cursor.after_id = String(transfer.id);
146
136
  return cursor;
147
137
  }
148
- function applyCursorParams(params, cursor) {
149
- if (!cursor)
138
+ function getTransactionCursor(transaction) {
139
+ if (!transaction)
150
140
  return;
151
- params.after_block = cursor.after_block;
152
- params.after_index = cursor.after_index;
153
- if (cursor.after_id !== undefined)
154
- 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);
155
146
  }
156
147
  function applyLocalOffset(rows, remainingOffset) {
157
148
  if (remainingOffset <= 0)
@@ -160,6 +151,72 @@ function applyLocalOffset(rows, remainingOffset) {
160
151
  return { rows: [], remainingOffset: remainingOffset - rows.length };
161
152
  return { rows: rows.slice(remainingOffset), remainingOffset: 0 };
162
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
+ }
163
220
  const syncStates = {
164
221
  v2: { chainIndexStatus: {} },
165
222
  v4: { chainIndexStatus: {} },
@@ -184,94 +241,88 @@ async function getChainIndexStatus(version) {
184
241
  }
185
242
  });
186
243
  return (state.chainIndexStatus = info);
187
- })();
244
+ })().catch((error) => {
245
+ state.timestamp = undefined;
246
+ state.chainIndexStatus = {};
247
+ throw error;
248
+ });
188
249
  return state.chainIndexStatus;
189
250
  }
190
- // ---------------------------------------------------------------------------
191
- // Streaming support (disabled by default, opt-in via clientStreaming: true)
192
- // ---------------------------------------------------------------------------
193
- async function streamJsonArrayFromIndexer(opts) {
194
- const { path, version, arrayKey = "logs", onItem, onChunkStats, shouldStop } = opts;
251
+ async function streamLogs(opts) {
252
+ const { path, version, onItem, onChunkStats, shouldStop } = opts;
195
253
  const controller = new AbortController();
196
254
  let bytesReceived = 0;
197
255
  let itemsProcessed = 0;
198
256
  const res = await axiosInstances[version].get(path, {
199
257
  responseType: "stream",
200
- timeout: 0,
258
+ timeout: INDEXER_REQUEST_TIMEOUT_MS,
201
259
  maxContentLength: Infinity,
202
260
  maxBodyLength: Infinity,
203
261
  headers: { Accept: "application/json", "Accept-Encoding": "gzip" },
204
262
  signal: controller.signal,
205
- }).catch((e) => {
206
- if (axios_1.default.isCancel(e) || (e === null || e === void 0 ? void 0 : e.code) === 'ERR_CANCELED')
207
- return { data: null };
208
- throw (0, common_1.formError)(e);
209
- });
210
- if (!(res === null || res === void 0 ? void 0 : res.data))
211
- return;
212
- await new Promise((resolve, reject) => {
213
- const stream = res.data;
214
- const isCancellationError = (err) => {
215
- var _a;
216
- return (err === null || err === void 0 ? void 0 : err.name) === "CanceledError" ||
217
- ((_a = err === null || err === void 0 ? void 0 : err.message) === null || _a === void 0 ? void 0 : _a.includes("aborted")) ||
218
- axios_1.default.isCancel(err) ||
219
- (err === null || err === void 0 ? void 0 : err.code) === 'ERR_CANCELED';
220
- };
221
- const handleError = (err) => isCancellationError(err) ? resolve() : reject(err);
222
- stream.on('data', (chunk) => {
223
- bytesReceived += chunk.length;
224
- onChunkStats === null || onChunkStats === void 0 ? void 0 : onChunkStats({ chunkSize: chunk.length, bytesReceived, itemsProcessed });
225
- });
226
- const jsonParser = (0, stream_json_1.parser)();
227
- const pickFilter = (0, Pick_1.pick)({ filter: arrayKey });
228
- const arrayStream = (0, StreamArray_1.streamArray)();
229
- arrayStream.on('data', (data) => {
230
- if (shouldStop === null || shouldStop === void 0 ? void 0 : shouldStop()) {
231
- controller.abort();
232
- stream.destroy();
233
- 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;
234
272
  }
235
- try {
236
- 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
+ }
237
282
  itemsProcessed++;
238
- if (itemsProcessed % 100000 === 0) {
239
- 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;
240
287
  }
241
288
  }
242
- catch { }
243
289
  });
244
- const finish = () => {
245
- const rem = itemsProcessed % 100000;
246
- if (rem > 0) {
247
- onChunkStats === null || onChunkStats === void 0 ? void 0 : onChunkStats({ chunkSize: rem, bytesReceived, itemsProcessed });
248
- }
249
- resolve();
250
- };
251
- arrayStream.on('end', finish);
252
- arrayStream.on('close', finish);
253
- arrayStream.on('error', handleError);
254
- jsonParser.on('error', handleError);
255
- pickFilter.on('error', handleError);
256
- stream.on('error', handleError);
257
- 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;
258
313
  });
259
314
  }
260
- // ---------------------------------------------------------------------------
261
- // LOGS (v2) – streaming opt-in, legacy default
262
- // ---------------------------------------------------------------------------
263
315
  async function getLogs(options) {
264
- // Set collect=false by default when processor is provided AND collect is not explicitly set AND clientStreaming = true
265
- // if you pass processor without collect=true, you won't get results back
266
- var _a, _b, _c, _d, _e, _f;
267
- 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
268
- decoderType = "viem", // default to viem for performance
269
- 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;
270
318
  if (processor && typeof options.collect === "undefined" && clientStreaming) {
271
- options.collect = false;
319
+ options = { ...options, collect: false };
272
320
  }
273
321
  const { chain, fromBlock, toBlock, topic, topic1, topic2, topic3, transformLog, } = await (0, logs_1.getLogParams)(options, true);
274
- 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
275
326
  ? (0, logs_decode_shared_1.createViemFastPathBatchDecoder)(options.eventAbi)
276
327
  : null;
277
328
  if (!debugMode)
@@ -281,6 +332,8 @@ async function getLogs(options) {
281
332
  throw new Error("processor is not supported with multiple targets when flatten=false");
282
333
  const blockRange = toBlock - fromBlock;
283
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");
284
337
  if (noTarget && blockRange > 500000) {
285
338
  throw new Error("When noTarget is true, block range must be less than 500k blocks. Please narrow down your block range.");
286
339
  }
@@ -308,7 +361,7 @@ async function getLogs(options) {
308
361
  // Re-curse if the requested range is too large
309
362
  if (blockRange > effectiveMaxBlockRange) {
310
363
  const results = [];
311
- for (let currentFromBlock = fromBlock; currentFromBlock < toBlock; currentFromBlock += effectiveMaxBlockRange) {
364
+ for (let currentFromBlock = fromBlock; currentFromBlock <= toBlock; currentFromBlock += effectiveMaxBlockRange) {
312
365
  const currentToBlock = Math.min(currentFromBlock + effectiveMaxBlockRange - 1, toBlock);
313
366
  const chunk = await getLogs({ ...options, fromBlock: currentFromBlock, toBlock: currentToBlock });
314
367
  results.push(chunk);
@@ -329,36 +382,15 @@ async function getLogs(options) {
329
382
  const addressChunks = (0, _1.sliceIntoChunks)((_c = address === null || address === void 0 ? void 0 : address.split(",")) !== null && _c !== void 0 ? _c : [], addressChunkSize);
330
383
  if (noTarget && addressChunks.length === 0)
331
384
  addressChunks.push(undefined);
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
385
  if (clientStreaming) {
352
386
  const useAll = all === true;
353
387
  const effectiveLimit = useAll ? Number.POSITIVE_INFINITY : ((_d = options.limit) !== null && _d !== void 0 ? _d : limit);
354
388
  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 = [];
389
+ const effectiveOffset = (_e = options.offset) !== null && _e !== void 0 ? _e : initialOffset;
390
+ const collected = [];
357
391
  const start = debugMode ? Date.now() : 0;
358
392
  const MICRO_BATCH_SIZE = +(process.env.LLAMA_INDEXER_MICRO_BATCH || 10000);
359
- // Check if we need splitByAddress (mapping by target address)
360
393
  const splitByAddress = (targets === null || targets === void 0 ? void 0 : targets.length) && !flatten;
361
- // If splitByAddress, build buckets directly during flushBatch
362
394
  const addressBuckets = splitByAddress ? targets.map(() => []) : [];
363
395
  const addressIndexMap = splitByAddress
364
396
  ? Object.fromEntries(targets.map((t, i) => [t.toLowerCase(), i]))
@@ -370,42 +402,12 @@ async function getLogs(options) {
370
402
  if (!batch.length)
371
403
  return;
372
404
  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
- }
405
+ const transformedLogs = await decodeStreamingLogs(batch, transformLog, viemFastPath !== null && viemFastPath !== void 0 ? viemFastPath : transformLog.batch, options.onlyArgs);
403
406
  const decodeTime = Date.now() - t0;
404
407
  onDecodeStats === null || onDecodeStats === void 0 ? void 0 : onDecodeStats({ batchSize: batch.length, decodeTime, itemsDecoded: batch.length });
405
408
  if (processor)
406
409
  await processor(transformedLogs);
407
- if (splitByAddress) {
408
- // Map directly to address buckets without storing pairs
410
+ if (splitByAddress && options.collect !== false) {
409
411
  for (let i = 0; i < batch.length; i++) {
410
412
  const raw = batch[i];
411
413
  const transformed = transformedLogs[i];
@@ -416,14 +418,8 @@ async function getLogs(options) {
416
418
  }
417
419
  }
418
420
  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);
421
+ safePush(collected, transformedLogs);
425
422
  }
426
- // If collect=false and not splitByAddress, nothing to store (processor already handled it)
427
423
  };
428
424
  for (const chunk of addressChunks) {
429
425
  if (Array.isArray(chunk) && chunk.length === 0)
@@ -449,19 +445,15 @@ async function getLogs(options) {
449
445
  qs.set("offset", "0");
450
446
  const transformBatch = [];
451
447
  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({
448
+ await streamLogs({
458
449
  path: `/logs?${qs.toString()}`,
459
450
  version: indexerVersion,
460
- arrayKey: "logs",
461
- onItem: (raw) => {
451
+ onItem: async (raw) => {
462
452
  var _a, _b, _c;
463
453
  if (stopNow)
464
454
  return;
455
+ if (indexerVersion === "v4")
456
+ (0, indexer_compatibility_1.normalizeV4Row)(raw, "logs");
465
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));
466
458
  if (!okAddress)
467
459
  return;
@@ -475,30 +467,30 @@ async function getLogs(options) {
475
467
  }
476
468
  transformBatch.push(raw);
477
469
  remainingLimit--;
470
+ if (remainingLimit <= 0)
471
+ stopNow = true;
478
472
  if (transformBatch.length >= MICRO_BATCH_SIZE) {
479
- // Sequential flush: chain to avoid concurrent decoding
480
473
  const batch = transformBatch.splice(0, MICRO_BATCH_SIZE);
481
- scheduleFlush(batch);
474
+ await flushBatch(batch);
482
475
  }
483
476
  },
484
477
  shouldStop: () => stopNow,
485
- onChunkStats: (s) => onWireStats === null || onWireStats === void 0 ? void 0 : onWireStats(s),
478
+ onChunkStats: onWireStats,
486
479
  });
487
480
  if (transformBatch.length > 0) {
488
- scheduleFlush(transformBatch.splice(0, transformBatch.length));
481
+ await flushBatch(transformBatch.splice(0, transformBatch.length));
489
482
  }
490
- await flushPromise;
491
483
  if (remainingLimit <= 0)
492
484
  break;
493
485
  }
494
486
  if (debugMode) {
495
487
  const ms = Date.now() - start;
496
- (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`);
497
489
  }
498
490
  if (splitByAddress) {
499
491
  return addressBuckets;
500
492
  }
501
- return (options.collect !== false) ? outPairs.map(i => i.transformed) : [];
493
+ return collected;
502
494
  }
503
495
  const allLogsPairs = [];
504
496
  const debugTimeKey = `Indexer-getLogs-${chain}-${topic}-${address}_${Math.random()}`;
@@ -506,48 +498,24 @@ async function getLogs(options) {
506
498
  (0, debugLog_1.debugLog)("[Indexer] Pulling logs " + debugTimeKey);
507
499
  console.time(debugTimeKey);
508
500
  }
509
- const useCursorPagination = supportsCursorPagination(indexerVersion) && all;
510
501
  for (const chunk of addressChunks) {
511
502
  if (Array.isArray(chunk) && chunk.length === 0)
512
503
  throw new Error("Address chunk cannot be empty");
513
- let chunkOffset = useCursorPagination ? 0 : initialOffset;
514
- let remainingOffset = useCursorPagination ? initialOffset : 0;
515
- let logCount = 0;
516
- let hasMore = true;
517
- let cursor;
518
- do {
519
- const params = {
520
- addresses: hasAddressFilter ? chunk === null || chunk === void 0 ? void 0 : chunk.join(",") : undefined,
521
- chainId,
522
- topic0: topic,
523
- from_block: fromBlock,
524
- to_block: toBlock,
525
- topic1,
526
- topic2,
527
- topic3,
528
- limit,
529
- offset: chunkOffset,
530
- noTarget,
531
- };
532
- if (useCursorPagination) {
533
- params.includeTotal = false;
534
- applyCursorParams(params, cursor);
535
- }
536
- const { data: { logs: _logs, totalCount }, } = await axiosInstances[indexerVersion](`/logs`, { params }).catch((e) => { throw (0, common_1.formError)(e); });
537
- const nextCursor = useCursorPagination ? getLogCursor(_logs[_logs.length - 1]) : undefined;
538
- const offsetResult = useCursorPagination ? applyLocalOffset(_logs, remainingOffset) : { rows: _logs, remainingOffset };
539
- remainingOffset = offsetResult.remainingOffset;
540
- 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) => {
541
511
  var _a, _b, _c;
542
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));
543
513
  return !!isWhitelisted;
544
514
  });
545
515
  const t0 = Date.now();
546
- // Normalize logs before decoding (required for consistency)
547
516
  for (const log of filtered) {
548
- (0, logs_decode_shared_1.normalizeLog)(log, true); // isIndexerCall = true
517
+ (0, logs_decode_shared_1.normalizeLog)(log, true);
549
518
  }
550
- // Legacy path: use transformLog batch if available, otherwise transform individually
551
519
  const transformBatchFn = transformLog.batch || ((logs) => Promise.all(logs.map((log) => transformLog(log))));
552
520
  const transformedLogs = await transformBatchFn(filtered);
553
521
  const transformedPair = filtered.map((log, idx) => ({ raw: log, transformed: transformedLogs[idx] }));
@@ -556,33 +524,10 @@ async function getLogs(options) {
556
524
  onDecodeStats({ batchSize: filtered.length, decodeTime, itemsDecoded: filtered.length });
557
525
  if (processor)
558
526
  await processor(transformedPair.map((i) => i.transformed));
559
- // Safe push helper to avoid stack overflow on large arrays
560
- // Only accumulate if collect is enabled (default true for backward compatibility)
561
- const shouldCollect = (options.collect !== false);
562
- if (shouldCollect) {
527
+ if (options.collect !== false) {
563
528
  safePush(allLogsPairs, transformedPair);
564
529
  }
565
- logCount += _logs.length;
566
- if (useCursorPagination && nextCursor) {
567
- cursor = nextCursor;
568
- chunkOffset = 0;
569
- }
570
- else {
571
- chunkOffset += limit;
572
- }
573
- if (_logs.length === 0) {
574
- hasMore = false;
575
- }
576
- else if (_logs.length < limit) {
577
- hasMore = false;
578
- }
579
- else if (typeof totalCount === 'number' && totalCount <= logCount) {
580
- hasMore = false;
581
- }
582
- else {
583
- hasMore = true;
584
- }
585
- } while (all && hasMore);
530
+ }
586
531
  }
587
532
  if (debugMode) {
588
533
  console.timeEnd(debugTimeKey);
@@ -602,9 +547,7 @@ async function getLogs(options) {
602
547
  });
603
548
  return mapped;
604
549
  }
605
- // Return empty array if collect=false (processor handles results)
606
- const shouldCollect = (options.collect !== false);
607
- return shouldCollect ? allLogsPairs.map(i => i.transformed) : [];
550
+ return allLogsPairs.map(i => i.transformed);
608
551
  }
609
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, }) {
610
553
  var _a, _b;
@@ -654,55 +597,27 @@ async function getTokenTransfers({ chain = "ethereum", fromAddressFilter, fromBl
654
597
  (0, debugLog_1.debugLog)("[Indexer] Pulling token transfers " + debugTimeKey);
655
598
  console.time(debugTimeKey);
656
599
  }
657
- const useCursorPagination = supportsCursorPagination(indexerVersion) && all;
658
- let currentOffset = useCursorPagination ? 0 : offset;
659
- let remainingOffset = useCursorPagination ? offset : 0;
660
- let hasMore = true;
661
- let cursor;
662
- do {
663
- const params = {
664
- addresses,
665
- chainId,
666
- from_block: fromBlock,
667
- to_block: toBlock,
668
- limit,
669
- offset: currentOffset,
670
- tokens,
671
- from_address: false,
672
- to_address: false,
673
- };
674
- if (useCursorPagination) {
675
- params.includeTotal = false;
676
- applyCursorParams(params, cursor);
677
- }
678
- switch (transferType) {
679
- case "in":
680
- params.to_address = true;
681
- break;
682
- case "out":
683
- params.from_address = true;
684
- break;
685
- case "all":
686
- params.from_address = true;
687
- params.to_address = true;
688
- break;
689
- default:
690
- throw new Error("Invalid transferType");
691
- }
692
- const { data: { transfers: _logs }, } = await axiosInstances[indexerVersion](`/token-transfers`, { params }).catch((e) => { throw (0, common_1.formError)(e); });
693
- const nextCursor = useCursorPagination ? getTransferCursor(_logs[_logs.length - 1]) : undefined;
694
- const offsetResult = useCursorPagination ? applyLocalOffset(_logs, remainingOffset) : { rows: _logs, remainingOffset };
695
- remainingOffset = offsetResult.remainingOffset;
696
- rawTransfers.push(...offsetResult.rows);
697
- if (useCursorPagination && nextCursor) {
698
- cursor = nextCursor;
699
- currentOffset = 0;
700
- }
701
- else {
702
- currentOffset += limit;
703
- }
704
- hasMore = _logs.length === limit;
705
- } 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
+ }
706
621
  const filteredTransfers = rawTransfers.filter((l) => {
707
622
  if (!fromFilterEnabled)
708
623
  return true;
@@ -769,7 +684,22 @@ async function getTransactions({ chain = "ethereum", addresses, transaction_hash
769
684
  (0, debugLog_1.debugLog)("[Indexer] Pulling transactions " + debugTimeKey);
770
685
  console.time(debugTimeKey);
771
686
  }
772
- 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
+ }
773
703
  if (debugMode) {
774
704
  console.timeEnd(debugTimeKey);
775
705
  (0, debugLog_1.debugLog)("Transactions pulled " + chain, addresses || transaction_hashes, (transactions === null || transactions === void 0 ? void 0 : transactions.length) || 0);