@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.
- package/build/ChainApi.d.ts +23 -1
- package/build/chains/algorand.d.ts +277 -0
- package/build/chains/algorand.js +618 -0
- package/build/chains/aptos.d.ts +230 -0
- package/build/chains/aptos.js +612 -0
- package/build/chains/cardano.d.ts +242 -0
- package/build/chains/cardano.js +407 -0
- package/build/chains/cosmos.d.ts +252 -0
- package/build/chains/cosmos.js +618 -0
- package/build/chains/icp.d.ts +235 -0
- package/build/chains/icp.js +1040 -0
- package/build/chains/index.d.ts +23 -0
- package/build/chains/index.js +59 -0
- package/build/chains/near.d.ts +192 -0
- package/build/chains/near.js +446 -0
- package/build/chains/rpc.d.ts +125 -0
- package/build/chains/rpc.js +326 -0
- package/build/chains/starknet.d.ts +205 -0
- package/build/chains/starknet.js +884 -0
- package/build/chains/stellar.d.ts +410 -0
- package/build/chains/stellar.js +1082 -0
- package/build/chains/substrate.d.ts +333 -0
- package/build/chains/substrate.js +1042 -0
- package/build/chains/sui.d.ts +365 -0
- package/build/chains/sui.js +1135 -0
- package/build/chains/svm.d.ts +340 -0
- package/build/chains/svm.js +760 -0
- package/build/chains/tezos.d.ts +225 -0
- package/build/chains/tezos.js +412 -0
- package/build/chains/ton.d.ts +353 -0
- package/build/chains/ton.js +788 -0
- package/build/chains/tron.d.ts +45 -0
- package/build/chains/tron.js +110 -0
- package/build/chains/utxo.d.ts +202 -0
- package/build/chains/utxo.js +592 -0
- package/build/chains/xrpl.d.ts +179 -0
- package/build/chains/xrpl.js +467 -0
- package/build/index.d.ts +2 -1
- package/build/index.js +3 -1
- package/build/providers.json +1 -1
- package/build/util/chainUtils/data.json +11 -0
- package/build/util/chainUtils/index.d.ts +1 -0
- package/build/util/chainUtils/index.js +23 -0
- package/build/util/common.js +2 -3
- package/build/util/cosmos.d.ts +1 -11
- package/build/util/cosmos.js +6 -66
- package/build/util/env.js +1 -0
- package/build/util/indexer.compatibility.d.ts +6 -0
- package/build/util/indexer.compatibility.js +64 -0
- package/build/util/indexer.d.ts +24 -2
- package/build/util/indexer.js +215 -282
- package/build/util/indexerSql.d.ts +8 -1
- package/build/util/indexerSql.js +53 -22
- package/build/util/logs.decode.shared.js +5 -4
- package/build/util/transactions.d.ts +23 -1
- package/build/util/updateChainLabels.d.ts +1 -0
- package/build/util/updateChainLabels.js +5 -0
- package/build/util/updateProviderList.js +38 -17
- package/package.json +20 -7
- package/README.md +0 -41
package/build/util/indexer.js
CHANGED
|
@@ -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")
|
|
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
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
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
|
-
//
|
|
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
|
-
|
|
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
|
|
146
|
-
if (!
|
|
138
|
+
function getTransactionCursor(transaction) {
|
|
139
|
+
if (!transaction)
|
|
147
140
|
return;
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
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
|
-
|
|
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:
|
|
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
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
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
|
-
|
|
233
|
-
|
|
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 (
|
|
236
|
-
|
|
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
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
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
|
-
|
|
262
|
-
|
|
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
|
|
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 =
|
|
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
|
|
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 =
|
|
353
|
-
const
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
474
|
+
await flushBatch(batch);
|
|
479
475
|
}
|
|
480
476
|
},
|
|
481
477
|
shouldStop: () => stopNow,
|
|
482
|
-
onChunkStats:
|
|
478
|
+
onChunkStats: onWireStats,
|
|
483
479
|
});
|
|
484
480
|
if (transformBatch.length > 0) {
|
|
485
|
-
|
|
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: ${
|
|
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
|
|
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
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
const
|
|
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);
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
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
|
-
|
|
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);
|