@0xbow/privacy-pools-core-sdk 1.0.3 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (33) hide show
  1. package/dist/esm/{fetchArtifacts.esm-aHubC6Nb.js → fetchArtifacts.esm-B6qveiM8.js} +2 -2
  2. package/dist/esm/{fetchArtifacts.esm-aHubC6Nb.js.map → fetchArtifacts.esm-B6qveiM8.js.map} +1 -1
  3. package/dist/esm/{fetchArtifacts.node-DUdhNM9Z.js → fetchArtifacts.node-BPQQPsnb.js} +2 -2
  4. package/dist/esm/{fetchArtifacts.node-DUdhNM9Z.js.map → fetchArtifacts.node-BPQQPsnb.js.map} +1 -1
  5. package/dist/esm/{index-BQLhaPQL.js → index-CRtEyHEf.js} +2880 -78
  6. package/dist/esm/index-CRtEyHEf.js.map +1 -0
  7. package/dist/esm/index.mjs +1 -1
  8. package/dist/index.d.mts +83 -8
  9. package/dist/node/{fetchArtifacts.esm-CRX34p8g.js → fetchArtifacts.esm-z-KXbilc.js} +2 -2
  10. package/dist/node/{fetchArtifacts.esm-CRX34p8g.js.map → fetchArtifacts.esm-z-KXbilc.js.map} +1 -1
  11. package/dist/node/{fetchArtifacts.node-CuPs4jZ1.js → fetchArtifacts.node-DvqhqpW9.js} +2 -2
  12. package/dist/node/{fetchArtifacts.node-CuPs4jZ1.js.map → fetchArtifacts.node-DvqhqpW9.js.map} +1 -1
  13. package/dist/node/{index-DB1uYJPF.js → index-BsmEKESv.js} +2880 -78
  14. package/dist/node/index-BsmEKESv.js.map +1 -0
  15. package/dist/node/index.mjs +1 -1
  16. package/dist/types/core/account.service.d.ts +4 -0
  17. package/dist/types/core/data.service.d.ts +28 -8
  18. package/dist/types/{fetchArtifacts.esm-CzBDJOwj.js → fetchArtifacts.esm-DF01Zpo3.js} +1 -1
  19. package/dist/types/{fetchArtifacts.node-CtMsXhR9.js → fetchArtifacts.node-BO6FBCAw.js} +1 -1
  20. package/dist/types/{index-BL6xN-P5.js → index-CH7gk4sK.js} +2879 -77
  21. package/dist/types/index.js +1 -1
  22. package/dist/types/types/index.d.ts +1 -0
  23. package/dist/types/types/rateLimit.d.ts +51 -0
  24. package/package.json +4 -1
  25. package/src/core/account.service.ts +46 -24
  26. package/src/core/contracts.service.ts +2 -6
  27. package/src/core/data.service.ts +324 -95
  28. package/src/providers/blockchainProvider.ts +1 -3
  29. package/src/types/index.ts +1 -0
  30. package/src/types/rateLimit.ts +66 -0
  31. package/dist/esm/index-BQLhaPQL.js.map +0 -1
  32. package/dist/node/index-DB1uYJPF.js.map +0 -1
  33. package/dist/types/core/test.d.ts +0 -1
@@ -1,20 +1,12 @@
1
- import {
2
- type PublicClient,
3
- createPublicClient,
4
- http,
5
- parseAbiItem,
6
- } from "viem";
7
- import {
8
- ChainConfig,
9
- DepositEvent,
10
- WithdrawalEvent,
11
- RagequitEvent,
12
- } from "../types/events.js";
13
- import { PoolInfo } from "../types/account.js";
14
- import { Hash } from "../types/commitment.js";
15
- import { Logger } from "../utils/logger.js";
16
- import { DataError } from "../errors/data.error.js";
17
- import { ErrorCode } from "../errors/base.error.js";
1
+ import {createPublicClient, type Hex, http, parseAbiItem, type PublicClient,} from "viem";
2
+ import {mapLimit} from "async";
3
+ import {ChainConfig, DepositEvent, RagequitEvent, WithdrawalEvent,} from "../types/events.js";
4
+ import {PoolInfo} from "../types/account.js";
5
+ import {Hash} from "../types/commitment.js";
6
+ import {BlockRange, ChainLogFetchConfig, DEFAULT_LOG_FETCH_CONFIG, LogFetchConfig,} from "../types/rateLimit.js";
7
+ import {Logger, LogLevel} from "../utils/logger.js";
8
+ import {DataError} from "../errors/data.error.js";
9
+ import {ErrorCode} from "../errors/base.error.js";
18
10
 
19
11
  // Event signatures from the contract
20
12
  const DEPOSIT_EVENT = parseAbiItem('event Deposited(address indexed _depositor, uint256 _commitment, uint256 _label, uint256 _value, uint256 _merkleRoot)');
@@ -33,15 +25,31 @@ const RAGEQUIT_EVENT = parseAbiItem('event Ragequit(address indexed _ragequitter
33
25
  export class DataService {
34
26
  private readonly clients: Map<number, PublicClient> = new Map();
35
27
  private readonly logger: Logger;
28
+ private readonly logFetchConfigs: Map<number, LogFetchConfig>;
36
29
 
37
30
  /**
38
31
  * Initialize the data service with chain configurations
39
32
  *
40
33
  * @param chainConfigs - Array of chain configurations containing chainId, RPC URL, and API key
34
+ * @param logFetchConfig - Per-chain configuration for rate-limited log fetching as a Map<chainId, config>.
35
+ * Each chain can have its own specific settings (e.g., different block chunk sizes).
41
36
  * @throws {DataError} If client initialization fails for any chain
42
37
  */
43
- constructor(private readonly chainConfigs: ChainConfig[]) {
44
- this.logger = new Logger({ prefix: "Data" });
38
+ constructor(
39
+ private readonly chainConfigs: ChainConfig[],
40
+ logFetchConfig: ChainLogFetchConfig = new Map()
41
+ ) {
42
+ this.logger = new Logger({ prefix: "Data", level: LogLevel.DEBUG });
43
+
44
+ // Initialize per-chain configs with defaults merged with chain-specific overrides
45
+ this.logFetchConfigs = new Map();
46
+ for (const config of chainConfigs) {
47
+ const chainSpecificConfig = logFetchConfig.get(config.chainId);
48
+ this.logFetchConfigs.set(
49
+ config.chainId,
50
+ { ...DEFAULT_LOG_FETCH_CONFIG, ...chainSpecificConfig }
51
+ );
52
+ }
45
53
 
46
54
  try {
47
55
  for (const config of chainConfigs) {
@@ -50,9 +58,7 @@ export class DataService {
50
58
  }
51
59
 
52
60
  const client = createPublicClient({
53
- transport: http(config.rpcUrl, {
54
- timeout: 20_000,
55
- }),
61
+ transport: http(config.rpcUrl),
56
62
  });
57
63
  this.clients.set(config.chainId, client);
58
64
  }
@@ -68,33 +74,64 @@ export class DataService {
68
74
  /**
69
75
  * Get deposit events for a specific chain
70
76
  *
71
- * @param chainId - Chain ID to fetch events from
72
- * @param options - Event filter options including fromBlock, toBlock, and other filters
77
+ * @param pool - Pool info containing chainId, address, and deployment block
73
78
  * @returns Array of deposit events with properly typed fields (bigint for numbers, Hash for commitments)
74
79
  * @throws {DataError} If client is not configured, network error occurs, or event data is invalid
75
80
  */
76
- async getDeposits(
77
- pool: PoolInfo
78
- ): Promise<DepositEvent[]> {
81
+ async getDeposits(pool: PoolInfo): Promise<DepositEvent[]> {
79
82
  try {
80
83
  const client = this.getClientForChain(pool.chainId);
81
- const config = this.getConfigForChain(pool.chainId);
82
-
83
- const logs = await client.getLogs({
84
- address: pool.address,
85
- event: DEPOSIT_EVENT,
86
- fromBlock: pool.deploymentBlock ?? config.startBlock
87
- }).catch(error => {
88
- throw new DataError(
89
- "Failed to fetch deposit logs",
90
- ErrorCode.NETWORK_ERROR,
91
- { error: error instanceof Error ? error.message : "Unknown error" },
92
- );
93
- });
84
+ const chainConfig = this.getConfigForChain(pool.chainId);
85
+ const logConfig = this.getLogFetchConfigForChain(pool.chainId);
86
+
87
+ const fromBlock = pool.deploymentBlock ?? chainConfig.startBlock;
88
+ const toBlock = await this.getCurrentBlock(pool.chainId);
89
+ const ranges = this.generateBlockRanges(
90
+ fromBlock,
91
+ toBlock,
92
+ logConfig.blockChunkSize
93
+ );
94
+
95
+ this.logger.info(
96
+ `Fetching deposits in ${ranges.length} chunks for pool ${pool.address}, chunk size is: ${logConfig.blockChunkSize}`
97
+ );
94
98
 
95
- return logs.map((log) => {
99
+ // Use async.mapLimit for controlled concurrency
100
+ const allLogs = await mapLimit<BlockRange, unknown[]>(
101
+ ranges,
102
+ logConfig.concurrency,
103
+ async (range: BlockRange) => {
104
+ if (logConfig.chunkDelayMs > 0) {
105
+ await this.sleep(logConfig.chunkDelayMs);
106
+ }
107
+ return this.fetchLogsWithRetry(
108
+ client,
109
+ pool.address,
110
+ DEPOSIT_EVENT,
111
+ range,
112
+ logConfig
113
+ );
114
+ }
115
+ );
116
+
117
+ // Flatten and parse results
118
+ const flatLogs = allLogs.flat();
119
+
120
+ return flatLogs.map((log: unknown) => {
96
121
  try {
97
- if (!log.args) {
122
+ const typedLog = log as {
123
+ args?: {
124
+ _depositor?: string;
125
+ _commitment?: bigint;
126
+ _label?: bigint;
127
+ _value?: bigint;
128
+ _merkleRoot?: bigint;
129
+ };
130
+ blockNumber?: bigint;
131
+ transactionHash?: Hex;
132
+ };
133
+
134
+ if (!typedLog.args) {
98
135
  throw DataError.invalidLog("deposit", "missing args");
99
136
  }
100
137
 
@@ -104,9 +141,16 @@ export class DataService {
104
141
  _label: label,
105
142
  _value: value,
106
143
  _merkleRoot: precommitment,
107
- } = log.args;
144
+ } = typedLog.args;
108
145
 
109
- if (!depositor || !commitment || !label || !precommitment || !log.blockNumber || !log.transactionHash) {
146
+ if (
147
+ !depositor ||
148
+ !commitment ||
149
+ !label ||
150
+ !precommitment ||
151
+ !typedLog.blockNumber ||
152
+ !typedLog.transactionHash
153
+ ) {
110
154
  throw DataError.invalidLog("deposit", "missing required fields");
111
155
  }
112
156
 
@@ -116,25 +160,31 @@ export class DataService {
116
160
  label: label as Hash,
117
161
  value: value || BigInt(0),
118
162
  precommitment: precommitment as Hash,
119
- blockNumber: BigInt(log.blockNumber),
120
- transactionHash: log.transactionHash,
163
+ blockNumber: BigInt(typedLog.blockNumber),
164
+ transactionHash: typedLog.transactionHash,
121
165
  };
122
166
  } catch (error) {
123
167
  if (error instanceof DataError) throw error;
124
- throw DataError.invalidLog("deposit", error instanceof Error ? error.message : "Unknown error");
168
+ throw DataError.invalidLog(
169
+ "deposit",
170
+ error instanceof Error ? error.message : "Unknown error"
171
+ );
125
172
  }
126
173
  });
127
174
  } catch (error) {
128
175
  if (error instanceof DataError) throw error;
129
- throw DataError.networkError(pool.chainId, error instanceof Error ? error : new Error(String(error)));
176
+ throw DataError.networkError(
177
+ pool.chainId,
178
+ error instanceof Error ? error : new Error(String(error))
179
+ );
130
180
  }
131
181
  }
132
182
 
133
183
  /**
134
184
  * Get withdrawal events for a specific chain
135
185
  *
136
- * @param chainId - Chain ID to fetch events from
137
- * @param options - Event filter options including fromBlock, toBlock, and other filters
186
+ * @param pool - Pool info containing chainId, address, and deployment block
187
+ * @param fromBlock - Optional starting block (defaults to pool deployment block)
138
188
  * @returns Array of withdrawal events with properly typed fields (bigint for numbers, Hash for commitments)
139
189
  * @throws {DataError} If client is not configured, network error occurs, or event data is invalid
140
190
  */
@@ -144,23 +194,55 @@ export class DataService {
144
194
  ): Promise<WithdrawalEvent[]> {
145
195
  try {
146
196
  const client = this.getClientForChain(pool.chainId);
147
- const config = this.getConfigForChain(pool.chainId);
148
-
149
- const logs = await client.getLogs({
150
- address: pool.address,
151
- event: WITHDRAWAL_EVENT,
152
- fromBlock: fromBlock ?? config.startBlock,
153
- }).catch(error => {
154
- throw new DataError(
155
- "Failed to fetch withdrawal logs",
156
- ErrorCode.NETWORK_ERROR,
157
- { error: error instanceof Error ? error.message : "Unknown error" },
158
- );
159
- });
197
+ const chainConfig = this.getConfigForChain(pool.chainId);
198
+ const logConfig = this.getLogFetchConfigForChain(pool.chainId);
199
+
200
+ const startBlock = fromBlock ?? chainConfig.startBlock;
201
+ const toBlock = await this.getCurrentBlock(pool.chainId);
202
+ const ranges = this.generateBlockRanges(
203
+ startBlock,
204
+ toBlock,
205
+ logConfig.blockChunkSize
206
+ );
160
207
 
161
- return logs.map((log) => {
208
+ this.logger.debug(
209
+ `Fetching withdrawals in ${ranges.length} chunks for pool ${pool.address}`
210
+ );
211
+
212
+ // Use async.mapLimit for controlled concurrency
213
+ const allLogs = await mapLimit<BlockRange, unknown[]>(
214
+ ranges,
215
+ logConfig.concurrency,
216
+ async (range: BlockRange) => {
217
+ if (logConfig.chunkDelayMs > 0) {
218
+ await this.sleep(logConfig.chunkDelayMs);
219
+ }
220
+ return this.fetchLogsWithRetry(
221
+ client,
222
+ pool.address,
223
+ WITHDRAWAL_EVENT,
224
+ range,
225
+ logConfig
226
+ );
227
+ }
228
+ );
229
+
230
+ // Flatten and parse results
231
+ const flatLogs = allLogs.flat();
232
+
233
+ return flatLogs.map((log: unknown) => {
162
234
  try {
163
- if (!log.args) {
235
+ const typedLog = log as {
236
+ args?: {
237
+ _value?: bigint;
238
+ _spentNullifier?: bigint;
239
+ _newCommitment?: bigint;
240
+ };
241
+ blockNumber?: bigint;
242
+ transactionHash?: Hex;
243
+ };
244
+
245
+ if (!typedLog.args) {
164
246
  throw DataError.invalidLog("withdrawal", "missing args");
165
247
  }
166
248
 
@@ -168,9 +250,15 @@ export class DataService {
168
250
  _value: value,
169
251
  _spentNullifier: spentNullifier,
170
252
  _newCommitment: newCommitment,
171
- } = log.args;
253
+ } = typedLog.args;
172
254
 
173
- if (!value || !spentNullifier || !newCommitment || !log.blockNumber || !log.transactionHash) {
255
+ if (
256
+ !value ||
257
+ !spentNullifier ||
258
+ !newCommitment ||
259
+ !typedLog.blockNumber ||
260
+ !typedLog.transactionHash
261
+ ) {
174
262
  throw DataError.invalidLog("withdrawal", "missing required fields");
175
263
  }
176
264
 
@@ -178,25 +266,31 @@ export class DataService {
178
266
  withdrawn: value,
179
267
  spentNullifier: spentNullifier as Hash,
180
268
  newCommitment: newCommitment as Hash,
181
- blockNumber: BigInt(log.blockNumber),
182
- transactionHash: log.transactionHash,
269
+ blockNumber: BigInt(typedLog.blockNumber),
270
+ transactionHash: typedLog.transactionHash,
183
271
  };
184
272
  } catch (error) {
185
273
  if (error instanceof DataError) throw error;
186
- throw DataError.invalidLog("withdrawal", error instanceof Error ? error.message : "Unknown error");
274
+ throw DataError.invalidLog(
275
+ "withdrawal",
276
+ error instanceof Error ? error.message : "Unknown error"
277
+ );
187
278
  }
188
279
  });
189
280
  } catch (error) {
190
281
  if (error instanceof DataError) throw error;
191
- throw DataError.networkError(pool.chainId, error instanceof Error ? error : new Error(String(error)));
282
+ throw DataError.networkError(
283
+ pool.chainId,
284
+ error instanceof Error ? error : new Error(String(error))
285
+ );
192
286
  }
193
287
  }
194
288
 
195
289
  /**
196
290
  * Get ragequit events for a specific chain
197
291
  *
198
- * @param chainId - Chain ID to fetch events from
199
- * @param options - Event filter options including fromBlock, toBlock, and other filters
292
+ * @param pool - Pool info containing chainId, address, and deployment block
293
+ * @param fromBlock - Optional starting block (defaults to pool deployment block)
200
294
  * @returns Array of ragequit events with properly typed fields (bigint for numbers, Hash for commitments)
201
295
  * @throws {DataError} If client is not configured, network error occurs, or event data is invalid
202
296
  */
@@ -206,23 +300,56 @@ export class DataService {
206
300
  ): Promise<RagequitEvent[]> {
207
301
  try {
208
302
  const client = this.getClientForChain(pool.chainId);
209
- const config = this.getConfigForChain(pool.chainId);
210
-
211
- const logs = await client.getLogs({
212
- address: pool.address,
213
- event: RAGEQUIT_EVENT,
214
- fromBlock: fromBlock ?? config.startBlock,
215
- }).catch(error => {
216
- throw new DataError(
217
- "Failed to fetch ragequit logs",
218
- ErrorCode.NETWORK_ERROR,
219
- { error: error instanceof Error ? error.message : "Unknown error" },
220
- );
221
- });
303
+ const chainConfig = this.getConfigForChain(pool.chainId);
304
+ const logConfig = this.getLogFetchConfigForChain(pool.chainId);
305
+
306
+ const startBlock = fromBlock ?? chainConfig.startBlock;
307
+ const toBlock = await this.getCurrentBlock(pool.chainId);
308
+ const ranges = this.generateBlockRanges(
309
+ startBlock,
310
+ toBlock,
311
+ logConfig.blockChunkSize
312
+ );
313
+
314
+ this.logger.debug(
315
+ `Fetching ragequits in ${ranges.length} chunks for pool ${pool.address}`
316
+ );
317
+
318
+ // Use async.mapLimit for controlled concurrency
319
+ const allLogs = await mapLimit<BlockRange, unknown[]>(
320
+ ranges,
321
+ logConfig.concurrency,
322
+ async (range: BlockRange) => {
323
+ if (logConfig.chunkDelayMs > 0) {
324
+ await this.sleep(logConfig.chunkDelayMs);
325
+ }
326
+ return this.fetchLogsWithRetry(
327
+ client,
328
+ pool.address,
329
+ RAGEQUIT_EVENT,
330
+ range,
331
+ logConfig
332
+ );
333
+ }
334
+ );
335
+
336
+ // Flatten and parse results
337
+ const flatLogs = allLogs.flat();
222
338
 
223
- return logs.map((log) => {
339
+ return flatLogs.map((log: unknown) => {
224
340
  try {
225
- if (!log.args) {
341
+ const typedLog = log as {
342
+ args?: {
343
+ _ragequitter?: string;
344
+ _commitment?: bigint;
345
+ _label?: bigint;
346
+ _value?: bigint;
347
+ };
348
+ blockNumber?: bigint;
349
+ transactionHash?: Hex;
350
+ };
351
+
352
+ if (!typedLog.args) {
226
353
  throw DataError.invalidLog("ragequit", "missing args");
227
354
  }
228
355
 
@@ -231,9 +358,15 @@ export class DataService {
231
358
  _commitment: commitment,
232
359
  _label: label,
233
360
  _value: value,
234
- } = log.args;
361
+ } = typedLog.args;
235
362
 
236
- if (!ragequitter || !commitment || !label || !log.blockNumber || !log.transactionHash) {
363
+ if (
364
+ !ragequitter ||
365
+ !commitment ||
366
+ !label ||
367
+ !typedLog.blockNumber ||
368
+ !typedLog.transactionHash
369
+ ) {
237
370
  throw DataError.invalidLog("ragequit", "missing required fields");
238
371
  }
239
372
 
@@ -242,20 +375,107 @@ export class DataService {
242
375
  commitment: commitment as Hash,
243
376
  label: label as Hash,
244
377
  value: value || BigInt(0),
245
- blockNumber: BigInt(log.blockNumber),
246
- transactionHash: log.transactionHash,
378
+ blockNumber: BigInt(typedLog.blockNumber),
379
+ transactionHash: typedLog.transactionHash,
247
380
  };
248
381
  } catch (error) {
249
382
  if (error instanceof DataError) throw error;
250
- throw DataError.invalidLog("ragequit", error instanceof Error ? error.message : "Unknown error");
383
+ throw DataError.invalidLog(
384
+ "ragequit",
385
+ error instanceof Error ? error.message : "Unknown error"
386
+ );
251
387
  }
252
388
  });
253
389
  } catch (error) {
254
390
  if (error instanceof DataError) throw error;
255
- throw DataError.networkError(pool.chainId, error instanceof Error ? error : new Error(String(error)));
391
+ throw DataError.networkError(
392
+ pool.chainId,
393
+ error instanceof Error ? error : new Error(String(error))
394
+ );
256
395
  }
257
396
  }
258
397
 
398
+ /**
399
+ * Gets the current block number for a chain
400
+ */
401
+ private async getCurrentBlock(chainId: number): Promise<bigint> {
402
+ const client = this.getClientForChain(chainId);
403
+ return client.getBlockNumber();
404
+ }
405
+
406
+ /**
407
+ * Generates block ranges for chunked fetching
408
+ */
409
+ private generateBlockRanges(
410
+ fromBlock: bigint,
411
+ toBlock: bigint,
412
+ chunkSize: number
413
+ ): BlockRange[] {
414
+ const ranges: BlockRange[] = [];
415
+ let current = fromBlock;
416
+
417
+ while (current <= toBlock) {
418
+ const end = current + BigInt(chunkSize) - 1n;
419
+ ranges.push({
420
+ fromBlock: current,
421
+ toBlock: end > toBlock ? toBlock : end,
422
+ });
423
+ current = end + 1n;
424
+ }
425
+
426
+ return ranges;
427
+ }
428
+
429
+ /**
430
+ * Fetches logs for a single block range with retry logic
431
+ */
432
+ private async fetchLogsWithRetry<T>(
433
+ client: PublicClient,
434
+ address: string,
435
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
436
+ event: any,
437
+ range: BlockRange,
438
+ logConfig: LogFetchConfig
439
+ ): Promise<T[]> {
440
+ const maxRetries = logConfig.retryOnFailure
441
+ ? logConfig.maxRetries
442
+ : 0;
443
+ let lastError: Error | undefined;
444
+
445
+ for (let attempt = 0; attempt <= maxRetries; attempt++) {
446
+ try {
447
+ const logs = await client.getLogs({
448
+ address: address as `0x${string}`,
449
+ event,
450
+ fromBlock: range.fromBlock,
451
+ toBlock: range.toBlock,
452
+ });
453
+ return logs as T[];
454
+ } catch (error) {
455
+ lastError = error instanceof Error ? error : new Error(String(error));
456
+
457
+ if (attempt < maxRetries) {
458
+ const delay =
459
+ logConfig.retryBaseDelayMs * Math.pow(2, attempt);
460
+ this.logger.warn(
461
+ `Log fetch failed, retrying in ${delay}ms (attempt ${attempt + 1}/${maxRetries})`,
462
+ { error: lastError.message, range }
463
+ );
464
+ await this.sleep(delay);
465
+ }
466
+ }
467
+ }
468
+
469
+ throw lastError;
470
+ }
471
+
472
+ /**
473
+ * Helper to add delay between requests
474
+ */
475
+ private sleep(ms: number): Promise<void> {
476
+ return new Promise((resolve) => setTimeout(resolve, ms));
477
+ }
478
+
259
479
  private getClientForChain(chainId: number): PublicClient {
260
480
  const client = this.clients.get(chainId);
261
481
  if (!client) {
@@ -265,10 +485,19 @@ export class DataService {
265
485
  }
266
486
 
267
487
  private getConfigForChain(chainId: number): ChainConfig {
268
- const config = this.chainConfigs.find(c => c.chainId === chainId);
488
+ const config = this.chainConfigs.find((c) => c.chainId === chainId);
269
489
  if (!config) {
270
490
  throw DataError.chainNotConfigured(chainId);
271
491
  }
272
492
  return config;
273
493
  }
494
+
495
+ private getLogFetchConfigForChain(chainId: number): LogFetchConfig {
496
+ const config = this.logFetchConfigs.get(chainId);
497
+ if (!config) {
498
+ // Fallback to default if not found (shouldn't happen if constructor is correct)
499
+ return DEFAULT_LOG_FETCH_CONFIG;
500
+ }
501
+ return config;
502
+ }
274
503
  }
@@ -15,9 +15,7 @@ export class BlockchainProvider implements IBlockchainProvider {
15
15
 
16
16
  this.client = createPublicClient({
17
17
  chain: mainnet,
18
- transport: http(rpcUrl, {
19
- timeout: 20_000,
20
- }),
18
+ transport: http(rpcUrl),
21
19
  });
22
20
  }
23
21
 
@@ -1,3 +1,4 @@
1
1
  export * from "./commitment.js";
2
2
  export * from "./withdrawal.js";
3
3
  export * from "./keys.js";
4
+ export * from "./rateLimit.js";
@@ -0,0 +1,66 @@
1
+ /**
2
+ * Configuration options for rate-limited log fetching
3
+ */
4
+ export interface LogFetchConfig {
5
+ /**
6
+ * Maximum number of blocks to fetch in a single RPC call.
7
+ * Default: 10000 (typical limit for most RPC providers)
8
+ */
9
+ blockChunkSize: number;
10
+
11
+ /**
12
+ * Maximum number of concurrent log fetch operations.
13
+ * Default: 3
14
+ */
15
+ concurrency: number;
16
+
17
+ /**
18
+ * Delay in milliseconds between chunk requests (for additional throttling).
19
+ * Default: 0 (no delay)
20
+ */
21
+ chunkDelayMs: number;
22
+
23
+ /**
24
+ * Whether to retry failed chunk fetches.
25
+ * Default: true
26
+ */
27
+ retryOnFailure: boolean;
28
+
29
+ /**
30
+ * Maximum number of retries for a failed chunk.
31
+ * Default: 3
32
+ */
33
+ maxRetries: number;
34
+
35
+ /**
36
+ * Base delay for exponential backoff on retries (ms).
37
+ * Default: 1000
38
+ */
39
+ retryBaseDelayMs: number;
40
+ }
41
+
42
+ /**
43
+ * Default log fetch configuration
44
+ */
45
+ export const DEFAULT_LOG_FETCH_CONFIG: LogFetchConfig = {
46
+ blockChunkSize: 10000,
47
+ concurrency: 3,
48
+ chunkDelayMs: 0,
49
+ retryOnFailure: true,
50
+ maxRetries: 3,
51
+ retryBaseDelayMs: 1000,
52
+ };
53
+
54
+ /**
55
+ * Per-chain log fetch configuration map
56
+ * Maps chainId to its specific LogFetchConfig
57
+ */
58
+ export type ChainLogFetchConfig = Map<number, Partial<LogFetchConfig>>;
59
+
60
+ /**
61
+ * Block range for chunked fetching
62
+ */
63
+ export interface BlockRange {
64
+ fromBlock: bigint;
65
+ toBlock: bigint;
66
+ }