@defillama/sdk 5.0.146 → 5.0.148

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.
@@ -1,5 +1,6 @@
1
- import { EventLog } from "ethers";
1
+ import { EventLog, Interface } from "ethers";
2
2
  import { Address } from "../types";
3
+ import { IndexerGetLogsOptions } from "./indexer";
3
4
  export type GetLogsOptions = {
4
5
  target?: Address;
5
6
  topic?: string;
@@ -24,6 +25,9 @@ export type GetLogsOptions = {
24
25
  debugMode?: boolean;
25
26
  noTarget?: boolean;
26
27
  parseLog?: boolean;
28
+ processor?: (logs: any[]) => Promise<void> | void;
29
+ maxBlockRange?: number;
30
+ allowParseFailure?: boolean;
27
31
  };
28
32
  export declare function getLogs(options: GetLogsOptions): Promise<EventLog[] | EventLog[][] | any[]>;
29
33
  export declare function toFilterTopic(topic: string): string;
@@ -35,3 +39,17 @@ export type logCache = {
35
39
  };
36
40
  };
37
41
  export default getLogs;
42
+ type GetLogsParamsResponse = {
43
+ chain: string;
44
+ fromBlock: number;
45
+ toBlock: number;
46
+ target?: Address;
47
+ topic: string;
48
+ iface?: Interface;
49
+ transformLog: (log: any) => any;
50
+ topics?: (string | null)[];
51
+ topic1?: string;
52
+ topic2?: string;
53
+ topic3?: string;
54
+ };
55
+ export declare function getLogParams(options: GetLogsOptions | IndexerGetLogsOptions, isIndexerCall?: boolean): Promise<GetLogsParamsResponse>;
@@ -1,198 +1,334 @@
1
1
  "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
2
5
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.toFilterTopic = exports.getLogs = void 0;
4
- const _1 = require(".");
6
+ exports.getLogParams = exports.toFilterTopic = exports.getLogs = void 0;
5
7
  const ethers_1 = require("ethers");
8
+ const _1 = require(".");
9
+ const tron_1 = require("../abi/tron");
6
10
  const blocks_1 = require("./blocks");
7
11
  const cache_1 = require("./cache");
8
12
  const debugLog_1 = require("./debugLog");
9
13
  const indexer_1 = require("./indexer");
10
- const tron_1 = require("../abi/tron");
11
- const currentVersion = 'v3';
14
+ const promisePool_1 = __importDefault(require("./promisePool"));
15
+ const currentVersion = "v3";
16
+ /* -------------------------------------------------------------------------- */
17
+ /* getLogs */
18
+ /* -------------------------------------------------------------------------- */
12
19
  async function getLogs(options) {
13
- let { target, chain = 'ethereum', fromBlock, toBlock, fromTimestamp, toTimestamp, eventAbi, topic, topics, extraTopics, // can be passed as input as extra filter arguments
14
- skipCache = false, skipCacheRead = false, entireLog = false, cacheInCloud = false, onlyArgs = false, keys = [], // [Deprecated] This is just used to select only part of the logs
15
- targets, flatten = true, skipIndexer = false, onlyIndexer = false, debugMode = false, noTarget = false, // we sometimes want to query logs without a target, but it will an be expensive bug if target/targets were not passed by mistake, so this is a safety check
16
- parseLog = false, } = options;
20
+ let { target, skipCache = false, skipCacheRead = false, cacheInCloud = false, keys = [], targets, flatten = true, skipIndexer = false, onlyIndexer = false, debugMode = false, processor, maxBlockRange, } = options;
21
+ const { fromBlock, toBlock, topics, topic, transformLog, chain } = await getLogParams(options, false);
22
+ /* ----------------------------- Indexer path ----------------------------- */
23
+ // If the caller demands indexer only but it is disabled, fail fast
24
+ if (onlyIndexer && !(0, indexer_1.isIndexerEnabled)(chain))
25
+ throw new Error("onlyIndexer is true, but indexer is not enabled");
17
26
  if (!skipIndexer && (0, indexer_1.isIndexerEnabled)(chain)) {
18
27
  try {
19
28
  const response = await (0, indexer_1.getLogs)({
20
29
  ...options,
21
- all: true,
30
+ all: true, // internal service – always fetch full set
31
+ parseLog: options.parseLog,
32
+ eventAbi: options.eventAbi,
33
+ onlyArgs: options.onlyArgs,
22
34
  });
23
35
  return response;
24
36
  }
25
37
  catch (e) {
26
- let message = e === null || e === void 0 ? void 0 : e.message;
27
- (0, debugLog_1.debugLog)('Error in indexer getLogs', message);
38
+ (0, debugLog_1.debugLog)("Error in indexer getLogs", e.message);
39
+ // If caller insisted on indexer only, propagate the error
40
+ if (onlyIndexer)
41
+ throw e;
42
+ // Otherwise continue with RPC fallback
28
43
  }
29
44
  }
30
45
  if (!debugMode)
31
46
  debugMode = debugLog_1.DEBUG_LEVEL2;
32
- if (onlyIndexer)
33
- throw new Error('onlyIndexer is true, but indexer is not enabled or threw an error');
34
- // if (!target && !targets?.length) throw new Error('target|targets is required')
35
- if (!fromBlock && !fromTimestamp)
36
- throw new Error('fromBlock or fromTimestamp is required');
37
- if (!toBlock && !toTimestamp)
38
- throw new Error('toBlock or toTimestamp is required');
39
- if (!fromBlock)
40
- fromBlock = await (0, blocks_1.getBlockNumber)(chain, fromTimestamp);
41
- if (!toBlock)
42
- toBlock = await (0, blocks_1.getBlockNumber)(chain, toTimestamp);
43
- if (!fromBlock || !toBlock)
44
- throw new Error('fromBlock and toBlock must be > 0');
45
- if (!noTarget && !target && !(targets === null || targets === void 0 ? void 0 : targets.length)) {
46
- throw new Error('target|targets is required or set the flag "noTarget" to true');
47
+ /* ----------------------- Split by maxBlockRange ------------------------- */
48
+ const blockRange = toBlock - fromBlock;
49
+ if (maxBlockRange && blockRange > maxBlockRange) {
50
+ const results = [];
51
+ for (let currentFromBlock = fromBlock; currentFromBlock <= toBlock; currentFromBlock += maxBlockRange) {
52
+ const currentToBlock = Math.min(currentFromBlock + maxBlockRange - 1, toBlock);
53
+ const chunk = await getLogs({
54
+ ...options,
55
+ fromBlock: currentFromBlock,
56
+ toBlock: currentToBlock,
57
+ });
58
+ results.push(chunk);
59
+ }
60
+ return flatten ? results.flat() : results;
47
61
  }
62
+ /* ------------------------ Multi-target parallel ------------------------- */
48
63
  if (targets === null || targets === void 0 ? void 0 : targets.length) {
49
- const newOptions = { ...options, fromBlock, toBlock };
50
- delete newOptions.targets;
51
- const res = await Promise.all(targets.map(i => getLogs({ ...newOptions, target: i, skipIndexer: true })));
52
- if (flatten)
53
- return res.flat();
54
- return res;
64
+ const baseOpts = { ...options, fromBlock, toBlock, skipIndexer: true };
65
+ delete baseOpts.targets;
66
+ const jobs = targets.map((address, idx) => ({ idx, address }));
67
+ const proc = async (job) => {
68
+ const logs = await getLogs({ ...baseOpts, target: job.address });
69
+ return { idx: job.idx, logs };
70
+ };
71
+ const results = await (0, promisePool_1.default)({ items: jobs, concurrency: 5, processor: proc });
72
+ const ordered = new Array(targets.length);
73
+ for (const { idx, logs } of results)
74
+ ordered[idx] = logs;
75
+ return flatten ? ordered.flat() : ordered;
55
76
  }
56
- if (chain === 'tron')
77
+ /* ----------------------------- RPC fallback ----------------------------- */
78
+ if (chain === "tron")
57
79
  target = (0, tron_1.hexifyTarget)(target);
58
- let iface;
59
- if (eventAbi)
60
- iface = new ethers_1.Interface([eventAbi]);
61
- if ((!topics || !topics.length)) {
62
- if (topic)
63
- topic = toFilterTopic(topic);
64
- else if (eventAbi)
65
- topic = toFilterTopic(eventAbi);
66
- else {
67
- throw new Error('eventAbi | topic | topics are required');
68
- }
69
- topics = [topic];
70
- if (extraTopics)
71
- topics.push(...extraTopics);
72
- }
73
- let caches = await _getCache();
74
- const isFirstRun = !caches.length;
75
- if (isFirstRun || skipCacheRead) {
80
+ let caches = (await _getCache());
81
+ const firstRun = !caches.length;
82
+ if (firstRun || skipCacheRead) {
76
83
  await addLogsToCache(fromBlock, toBlock);
77
84
  }
78
85
  else {
79
- let _fromBlock = fromBlock; // we need to keep a copy of fromBlock as it will be modified
80
- let _toBlock = toBlock; // we need to keep a copy of toBlock as it will be modified
86
+ // try to fill gaps compared to what is already cached
87
+ let _from = fromBlock;
88
+ let _to = toBlock;
81
89
  const firstCache = caches[0];
82
90
  const lastCache = caches[caches.length - 1];
83
- for (const cacheData of caches) {
84
- const { fromBlock: cFirstBlock, toBlock: cToBlock } = cacheData.metadata;
85
- if (_toBlock < firstCache.metadata.fromBlock || _fromBlock > lastCache.metadata.toBlock) { // no intersection with any cache
86
- await addLogsToCache(_fromBlock, _toBlock);
91
+ for (const c of caches) {
92
+ const { fromBlock: cStart, toBlock: cEnd } = c.metadata;
93
+ if (_to < firstCache.metadata.fromBlock || _from > lastCache.metadata.toBlock) {
94
+ await addLogsToCache(_from, _to);
87
95
  break;
88
96
  }
89
- if (_fromBlock >= cToBlock)
90
- continue; // request is after cache end
91
- if (_fromBlock <= cFirstBlock) { // request is before cache start
92
- await addLogsToCache(_fromBlock, Math.min(_toBlock, cFirstBlock - 1));
97
+ if (_from >= cEnd)
98
+ continue;
99
+ if (_from <= cStart) {
100
+ await addLogsToCache(_from, Math.min(_to, cStart - 1));
93
101
  }
94
- if (_toBlock <= cToBlock) { // request ends before cache end
95
- _fromBlock = cToBlock;
102
+ if (_to <= cEnd) {
103
+ _from = cEnd;
96
104
  break;
97
105
  }
98
- _fromBlock = cToBlock;
106
+ _from = cEnd;
99
107
  }
100
- if (_fromBlock < _toBlock)
101
- await addLogsToCache(_fromBlock, _toBlock);
108
+ if (_from < _to)
109
+ await addLogsToCache(_from, _to);
102
110
  }
103
- const logs = [];
104
- for (const cacheData of caches) {
105
- const { fromBlock: cFirstBlock, toBlock: cToBlock } = cacheData.metadata;
106
- if (fromBlock > cToBlock || toBlock < cFirstBlock)
107
- continue; // no intersection with cache
108
- logs.push(...cacheData.logs.filter((i) => i.blockNumber >= fromBlock && i.blockNumber <= toBlock));
111
+ /* ---------------------------- Return result ----------------------------- */
112
+ const out = [];
113
+ for (const c of caches) {
114
+ const { fromBlock: cStart, toBlock: cEnd } = c.metadata;
115
+ if (fromBlock > cEnd || toBlock < cStart)
116
+ continue;
117
+ const filtered = c.logs
118
+ .filter((i) => i.blockNumber >= fromBlock && i.blockNumber <= toBlock)
119
+ .map(transformLog);
120
+ if (processor)
121
+ await processor(filtered);
122
+ // Always include filtered logs in the return value, even when a processor is provided
123
+ out.push(...filtered);
109
124
  }
110
- if (!eventAbi || entireLog) {
111
- if (iface && parseLog) {
112
- logs.forEach((i) => i.parsedLog = iface === null || iface === void 0 ? void 0 : iface.parseLog(i));
113
- }
114
- return logs;
115
- }
116
- return logs.map((i) => iface.parseLog(i)).map((i) => onlyArgs ? i.args : i);
125
+ return out;
126
+ /* ----------------------------------------------------------------------- */
127
+ /* Internal helper functions */
128
+ /* ----------------------------------------------------------------------- */
117
129
  async function addLogsToCache(fromBlock, toBlock) {
118
- const debugTimeKey = `getLogs-${chain}-${topic}-${target}_${Math.random()}-${fromBlock}-${toBlock}`;
130
+ const dbgKey = `getLogs-${chain}-${topic}-${target}_${Math.random()}-${fromBlock}-${toBlock}`;
119
131
  if (debugMode)
120
- (0, debugLog_1.debugLog)('adding logs to cache: ', fromBlock, toBlock, target, topic, chain);
132
+ (0, debugLog_1.debugLog)("adding logs to cache:", fromBlock, toBlock);
121
133
  if (fromBlock > toBlock)
122
- return; // no data to add
123
- fromBlock = fromBlock - 10;
134
+ return;
135
+ fromBlock = Math.max(fromBlock - 10, 0); // avoid negative block
124
136
  toBlock = toBlock + 10;
125
137
  if (debugMode)
126
- console.time(debugTimeKey);
127
- let { output: logs } = await (0, _1.getLogs)({
128
- chain, target: target, topic: topic, keys, topics, fromBlock, toBlock,
138
+ console.time(dbgKey);
139
+ const { output: newLogs } = await (0, _1.getLogs)({
140
+ chain,
141
+ target: target,
142
+ topic: topic,
143
+ keys,
144
+ topics,
145
+ fromBlock,
146
+ toBlock,
129
147
  });
130
148
  if (debugMode) {
131
- console.timeEnd(debugTimeKey);
132
- (0, debugLog_1.debugLog)('Logs pulled ' + chain, target, logs.length);
149
+ console.timeEnd(dbgKey);
150
+ (0, debugLog_1.debugLog)("Logs pulled", chain, target, newLogs.length);
133
151
  }
134
- caches.push({
135
- logs,
136
- metadata: { fromBlock, toBlock, }
137
- });
152
+ caches.push({ logs: newLogs, metadata: { fromBlock, toBlock } });
138
153
  caches.sort((a, b) => a.metadata.fromBlock - b.metadata.fromBlock);
139
- const mergedCaches = [caches[0]];
140
- caches.slice(1).forEach(i => {
141
- const last = mergedCaches[mergedCaches.length - 1];
142
- if (last.metadata.toBlock + 1 > i.metadata.fromBlock) {
143
- last.metadata.toBlock = i.metadata.toBlock;
144
- last.logs = dedupLogs(last.logs, i.logs);
145
- }
146
- else {
147
- mergedCaches.push(i);
154
+ // merge overlapping ranges
155
+ const merged = [caches[0]];
156
+ caches.slice(1).forEach((c) => {
157
+ const last = merged[merged.length - 1];
158
+ if (last.metadata.toBlock + 1 > c.metadata.fromBlock) {
159
+ last.metadata.toBlock = c.metadata.toBlock;
160
+ last.logs = dedupLogs(last.logs, c.logs);
148
161
  }
162
+ else
163
+ merged.push(c);
149
164
  });
150
- caches = mergedCaches;
151
- if (!skipCache)
152
- await (0, cache_1.writeCache)(getFile(), { caches, version: currentVersion }, { skipR2CacheWrite: !cacheInCloud });
165
+ caches = merged;
166
+ if (!skipCache) // we are skipping compression by default, so reads & writes are faster
167
+ await (0, cache_1.writeCache)(getFile(), { caches, version: currentVersion }, { skipR2CacheWrite: !cacheInCloud, skipCompression: !cacheInCloud });
153
168
  }
154
- function dedupLogs(...logs) {
155
- const logIndices = new Set();
156
- return logs.flat().filter((i) => {
169
+ function dedupLogs(...arr) {
170
+ const seen = new Set();
171
+ return arr.flat().filter((i) => {
157
172
  var _a;
158
- let key = i.transactionHash + ((_a = i.logIndex) !== null && _a !== void 0 ? _a : i.index); // ethers v5 had logIndex, ethers v6 has index
159
- if (!(i.hasOwnProperty('logIndex') || i.hasOwnProperty('index')) || !i.hasOwnProperty('transactionHash')) {
160
- (0, debugLog_1.debugLog)(i, i.logIndex, i.index, i.transactionHash);
161
- throw new Error('Missing crucial field: logIndex/index');
173
+ const key = `${i.transactionHash}-${(_a = i.logIndex) !== null && _a !== void 0 ? _a : i.index}`;
174
+ if (!(i.hasOwnProperty("logIndex") ||
175
+ i.hasOwnProperty("index")) ||
176
+ !i.hasOwnProperty("transactionHash")) {
177
+ (0, debugLog_1.debugLog)("Missing crucial field", i);
178
+ throw new Error("Missing crucial field: logIndex/index");
162
179
  }
163
- if (logIndices.has(key))
180
+ if (seen.has(key))
164
181
  return false;
165
- logIndices.add(key);
182
+ seen.add(key);
166
183
  return true;
167
184
  });
168
185
  }
169
186
  async function _getCache() {
170
187
  const key = getFile();
171
- const defaultRes = [];
188
+ const def = [];
172
189
  if (skipCache)
173
- return defaultRes;
174
- let cache = await (0, cache_1.readCache)(key, { skipR2Cache: !cacheInCloud });
190
+ return def;
191
+ let cache = await (0, cache_1.readCache)(key, { skipR2Cache: !cacheInCloud, skipCompression: !cacheInCloud, });
175
192
  if (!cache.caches || !cache.caches.length || cache.version !== currentVersion)
176
- return defaultRes;
193
+ return def;
177
194
  return cache.caches;
178
195
  }
179
196
  function getFile() {
180
197
  var _a;
181
- let extraKey = topics === null || topics === void 0 ? void 0 : topics.join('-').toLowerCase();
198
+ let extraKey = topics === null || topics === void 0 ? void 0 : topics.join("-").toLowerCase();
182
199
  if (!extraKey)
183
- throw new Error('extraKey is required');
200
+ throw new Error("extraKey is required");
184
201
  if (keys.length)
185
- extraKey += '-' + keys.join('-').toLowerCase();
202
+ extraKey += "-" + keys.join("-").toLowerCase();
186
203
  return `event-logs/${chain}/${(_a = target === null || target === void 0 ? void 0 : target.toLowerCase()) !== null && _a !== void 0 ? _a : null}-${extraKey}`;
187
204
  }
188
205
  }
189
206
  exports.getLogs = getLogs;
207
+ /* -------------------------------------------------------------------------- */
208
+ /* Utility helpers exported */
209
+ /* -------------------------------------------------------------------------- */
190
210
  function toFilterTopic(topic) {
191
- if (typeof topic === 'string' && topic.startsWith('0x')) {
211
+ if (topic.startsWith("0x"))
192
212
  return topic;
193
- }
194
213
  const fragment = ethers_1.EventFragment.from(topic);
195
214
  return (0, ethers_1.id)(fragment.format());
196
215
  }
197
216
  exports.toFilterTopic = toFilterTopic;
198
217
  exports.default = getLogs;
218
+ async function getLogParams(options, isIndexerCall = false) {
219
+ let { target, chain = "ethereum", fromBlock, toBlock, fromTimestamp, toTimestamp, eventAbi, topic, topics, extraTopics, entireLog = false, onlyArgs = false, targets = [], noTarget = false, parseLog = false, allowParseFailure = false, processor, flatten = false, } = options;
220
+ if (eventAbi && !Object.prototype.hasOwnProperty.call(options, "parseLog"))
221
+ parseLog = true;
222
+ // keep raw log if entireLog=true
223
+ if (entireLog)
224
+ onlyArgs = false;
225
+ if (processor && targets.length > 1 && !flatten)
226
+ throw new Error("processor is not supported with multiple targets when flatten=false");
227
+ if (!fromBlock && !fromTimestamp)
228
+ throw new Error("fromBlock or fromTimestamp is required");
229
+ if (!toBlock && !toTimestamp)
230
+ throw new Error("toBlock or toTimestamp is required");
231
+ if (!fromBlock)
232
+ fromBlock = await (0, blocks_1.getBlockNumber)(chain, fromTimestamp);
233
+ if (!toBlock)
234
+ toBlock = await (0, blocks_1.getBlockNumber)(chain, toTimestamp);
235
+ if (!fromBlock || !toBlock)
236
+ throw new Error("fromBlock and toBlock must be > 0");
237
+ if (!noTarget && !target && !targets.length)
238
+ throw new Error('target|targets is required or set the flag "noTarget" to true');
239
+ let iface;
240
+ if (eventAbi)
241
+ iface = new ethers_1.Interface([eventAbi]);
242
+ if (!(topics === null || topics === void 0 ? void 0 : topics.length)) {
243
+ if (topic)
244
+ topic = toFilterTopic(topic);
245
+ else if (eventAbi)
246
+ topic = toFilterTopic(eventAbi);
247
+ else
248
+ throw new Error("eventAbi | topic | topics are required");
249
+ topics = [topic];
250
+ if (extraTopics)
251
+ topics.push(...extraTopics);
252
+ }
253
+ if (topics === null || topics === void 0 ? void 0 : topics.length)
254
+ topic = topics[0];
255
+ function transformLog(log) {
256
+ // normalize indexer payload
257
+ if (isIndexerCall) {
258
+ log.address = log.source;
259
+ log.logIndex = log.log_index;
260
+ log.index = log.log_index;
261
+ log.transactionHash = log.transaction_hash;
262
+ log.blockNumber = parseInt(log.block_number);
263
+ // Ensure topics are properly formatted
264
+ const topics = [log.topic0, log.topic1, log.topic2, log.topic3]
265
+ .filter(Boolean)
266
+ .map((t) => {
267
+ if (!t)
268
+ return null;
269
+ const topic = t.startsWith("0x") ? t : `0x${t}`;
270
+ return ethers_1.ethers.zeroPadValue(topic, 32);
271
+ });
272
+ // Ensure first topic (event signature) is present
273
+ if (!topics[0]) {
274
+ return log;
275
+ }
276
+ log.topics = topics;
277
+ // Store original topics for later use
278
+ log._originalTopics = {
279
+ topic0: log.topic0,
280
+ topic1: log.topic1,
281
+ topic2: log.topic2,
282
+ topic3: log.topic3
283
+ };
284
+ // Store original transaction hash
285
+ log._originalTransactionHash = log.transaction_hash;
286
+ // Only delete fields that are not needed for parsing
287
+ [
288
+ "chain",
289
+ "log_index",
290
+ ].forEach((k) => delete log[k]);
291
+ }
292
+ if (log.blockNumber === undefined && log.block_number !== undefined)
293
+ log.blockNumber = parseInt(log.block_number);
294
+ if ((entireLog && !parseLog) || !iface) {
295
+ return log;
296
+ }
297
+ // Restore topics if they were lost
298
+ if (!log.topics && log._originalTopics) {
299
+ log.topics = [log._originalTopics.topic0, log._originalTopics.topic1, log._originalTopics.topic2, log._originalTopics.topic3]
300
+ .filter(Boolean)
301
+ .map((t) => {
302
+ if (!t)
303
+ return null;
304
+ const topic = t.startsWith("0x") ? t : `0x${t}`;
305
+ return ethers_1.ethers.zeroPadValue(topic, 32);
306
+ });
307
+ }
308
+ // Restore transaction hash if it was lost
309
+ if (!log.transactionHash && log._originalTransactionHash) {
310
+ log.transactionHash = log._originalTransactionHash;
311
+ }
312
+ const parsed = iface.parseLog(log);
313
+ if (!parsed && !allowParseFailure)
314
+ throw new Error(`Failed to parse log: ${JSON.stringify(log.transactionHash)}`);
315
+ log.args = parsed === null || parsed === void 0 ? void 0 : parsed.args;
316
+ if (entireLog)
317
+ log.parsedLog = parsed;
318
+ return onlyArgs ? log.args : log;
319
+ }
320
+ return {
321
+ chain,
322
+ fromBlock,
323
+ toBlock,
324
+ target,
325
+ topic: topic,
326
+ topic1: topics[1],
327
+ topic2: topics[2],
328
+ topic3: topics[3],
329
+ iface,
330
+ transformLog,
331
+ topics,
332
+ };
333
+ }
334
+ exports.getLogParams = getLogParams;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@defillama/sdk",
3
- "version": "5.0.146",
3
+ "version": "5.0.148",
4
4
  "description": "SDK for calculating TVL",
5
5
  "main": "build/index.js",
6
6
  "scripts": {