@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.
- package/build/ChainApi.d.ts +23 -1
- package/build/ChainApi.js +1 -0
- package/build/abi/multicall3.js +4 -0
- 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 +16 -1
- 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 -285
- 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/logs.js +1 -2
- 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 +40 -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,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
|
-
|
|
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
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
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
|
-
//
|
|
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
|
-
|
|
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
|
|
149
|
-
if (!
|
|
138
|
+
function getTransactionCursor(transaction) {
|
|
139
|
+
if (!transaction)
|
|
150
140
|
return;
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
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
|
-
|
|
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:
|
|
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
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
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
|
-
|
|
236
|
-
|
|
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 (
|
|
239
|
-
|
|
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
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
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
|
-
|
|
265
|
-
|
|
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
|
|
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 =
|
|
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
|
|
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 =
|
|
356
|
-
const
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
474
|
+
await flushBatch(batch);
|
|
482
475
|
}
|
|
483
476
|
},
|
|
484
477
|
shouldStop: () => stopNow,
|
|
485
|
-
onChunkStats:
|
|
478
|
+
onChunkStats: onWireStats,
|
|
486
479
|
});
|
|
487
480
|
if (transformBatch.length > 0) {
|
|
488
|
-
|
|
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: ${
|
|
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
|
|
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
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
const
|
|
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);
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
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
|
-
|
|
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);
|