@0xbow/privacy-pools-core-sdk 1.0.4 → 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.
- package/dist/esm/{fetchArtifacts.esm-C_DNv-_D.js → fetchArtifacts.esm-B6qveiM8.js} +2 -2
- package/dist/esm/{fetchArtifacts.esm-C_DNv-_D.js.map → fetchArtifacts.esm-B6qveiM8.js.map} +1 -1
- package/dist/esm/{fetchArtifacts.node-BBPNjgiT.js → fetchArtifacts.node-BPQQPsnb.js} +2 -2
- package/dist/esm/{fetchArtifacts.node-BBPNjgiT.js.map → fetchArtifacts.node-BPQQPsnb.js.map} +1 -1
- package/dist/esm/{index-BxzIe_IR.js → index-CRtEyHEf.js} +2884 -107
- package/dist/esm/index-CRtEyHEf.js.map +1 -0
- package/dist/esm/index.mjs +1 -1
- package/dist/index.d.mts +87 -14
- package/dist/node/{fetchArtifacts.esm-l-EDo53-.js → fetchArtifacts.esm-z-KXbilc.js} +2 -2
- package/dist/node/{fetchArtifacts.esm-l-EDo53-.js.map → fetchArtifacts.esm-z-KXbilc.js.map} +1 -1
- package/dist/node/{fetchArtifacts.node-DYwQHSF4.js → fetchArtifacts.node-DvqhqpW9.js} +2 -2
- package/dist/node/{fetchArtifacts.node-DYwQHSF4.js.map → fetchArtifacts.node-DvqhqpW9.js.map} +1 -1
- package/dist/node/{index-DGsIfUGw.js → index-BsmEKESv.js} +2884 -107
- package/dist/node/index-BsmEKESv.js.map +1 -0
- package/dist/node/index.mjs +1 -1
- package/dist/types/core/account.service.d.ts +8 -6
- package/dist/types/core/data.service.d.ts +28 -8
- package/dist/types/{fetchArtifacts.esm-IMTIZwq7.js → fetchArtifacts.esm-DF01Zpo3.js} +1 -1
- package/dist/types/{fetchArtifacts.node-BcXsBNCT.js → fetchArtifacts.node-BO6FBCAw.js} +1 -1
- package/dist/types/{index-DbuAhDci.js → index-CH7gk4sK.js} +2883 -106
- package/dist/types/index.js +1 -1
- package/dist/types/types/index.d.ts +1 -0
- package/dist/types/types/rateLimit.d.ts +51 -0
- package/package.json +4 -2
- package/src/core/account.service.ts +52 -39
- package/src/core/data.service.ts +324 -95
- package/src/types/index.ts +1 -0
- package/src/types/rateLimit.ts +66 -0
- package/dist/esm/index-BxzIe_IR.js.map +0 -1
- package/dist/node/index-DGsIfUGw.js.map +0 -1
- package/dist/types/utils/concurrency.d.ts +0 -15
- package/src/utils/concurrency.ts +0 -32
package/src/core/data.service.ts
CHANGED
|
@@ -1,20 +1,12 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
} from "
|
|
7
|
-
import {
|
|
8
|
-
|
|
9
|
-
|
|
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(
|
|
44
|
-
|
|
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
|
|
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
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
} =
|
|
144
|
+
} = typedLog.args;
|
|
108
145
|
|
|
109
|
-
if (
|
|
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(
|
|
120
|
-
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(
|
|
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(
|
|
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
|
|
137
|
-
* @param
|
|
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
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
} =
|
|
253
|
+
} = typedLog.args;
|
|
172
254
|
|
|
173
|
-
if (
|
|
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(
|
|
182
|
-
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(
|
|
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(
|
|
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
|
|
199
|
-
* @param
|
|
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
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
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
|
|
339
|
+
return flatLogs.map((log: unknown) => {
|
|
224
340
|
try {
|
|
225
|
-
|
|
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
|
-
} =
|
|
361
|
+
} = typedLog.args;
|
|
235
362
|
|
|
236
|
-
if (
|
|
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(
|
|
246
|
-
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(
|
|
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(
|
|
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
|
}
|
package/src/types/index.ts
CHANGED
|
@@ -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
|
+
}
|