@0xbow/privacy-pools-core-sdk 1.0.4 → 1.1.1
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-DbVRphob.js} +2 -2
- package/dist/esm/{fetchArtifacts.esm-C_DNv-_D.js.map → fetchArtifacts.esm-DbVRphob.js.map} +1 -1
- package/dist/esm/{fetchArtifacts.node-BBPNjgiT.js → fetchArtifacts.node-D-fJGtzV.js} +2 -2
- package/dist/esm/{fetchArtifacts.node-BBPNjgiT.js.map → fetchArtifacts.node-D-fJGtzV.js.map} +1 -1
- package/dist/esm/{index-BxzIe_IR.js → index-DkNRxKxP.js} +2889 -109
- package/dist/esm/index-DkNRxKxP.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-BIT-b_1_.js} +2 -2
- package/dist/node/{fetchArtifacts.esm-l-EDo53-.js.map → fetchArtifacts.esm-BIT-b_1_.js.map} +1 -1
- package/dist/node/{fetchArtifacts.node-DYwQHSF4.js → fetchArtifacts.node-CKwwU50E.js} +2 -2
- package/dist/node/{fetchArtifacts.node-DYwQHSF4.js.map → fetchArtifacts.node-CKwwU50E.js.map} +1 -1
- package/dist/node/{index-DGsIfUGw.js → index-C3RV9Cri.js} +2889 -109
- package/dist/node/index-C3RV9Cri.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-DT5RuODl.js} +1 -1
- package/dist/types/{fetchArtifacts.node-BcXsBNCT.js → fetchArtifacts.node-D_iVIPqW.js} +1 -1
- package/dist/types/{index-DbuAhDci.js → index-CHy3YamH.js} +2888 -108
- 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 +325 -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,16 @@ 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 === undefined ||
|
|
257
|
+
value === null ||
|
|
258
|
+
!spentNullifier ||
|
|
259
|
+
!newCommitment ||
|
|
260
|
+
!typedLog.blockNumber ||
|
|
261
|
+
!typedLog.transactionHash
|
|
262
|
+
) {
|
|
174
263
|
throw DataError.invalidLog("withdrawal", "missing required fields");
|
|
175
264
|
}
|
|
176
265
|
|
|
@@ -178,25 +267,31 @@ export class DataService {
|
|
|
178
267
|
withdrawn: value,
|
|
179
268
|
spentNullifier: spentNullifier as Hash,
|
|
180
269
|
newCommitment: newCommitment as Hash,
|
|
181
|
-
blockNumber: BigInt(
|
|
182
|
-
transactionHash:
|
|
270
|
+
blockNumber: BigInt(typedLog.blockNumber),
|
|
271
|
+
transactionHash: typedLog.transactionHash,
|
|
183
272
|
};
|
|
184
273
|
} catch (error) {
|
|
185
274
|
if (error instanceof DataError) throw error;
|
|
186
|
-
throw DataError.invalidLog(
|
|
275
|
+
throw DataError.invalidLog(
|
|
276
|
+
"withdrawal",
|
|
277
|
+
error instanceof Error ? error.message : "Unknown error"
|
|
278
|
+
);
|
|
187
279
|
}
|
|
188
280
|
});
|
|
189
281
|
} catch (error) {
|
|
190
282
|
if (error instanceof DataError) throw error;
|
|
191
|
-
throw DataError.networkError(
|
|
283
|
+
throw DataError.networkError(
|
|
284
|
+
pool.chainId,
|
|
285
|
+
error instanceof Error ? error : new Error(String(error))
|
|
286
|
+
);
|
|
192
287
|
}
|
|
193
288
|
}
|
|
194
289
|
|
|
195
290
|
/**
|
|
196
291
|
* Get ragequit events for a specific chain
|
|
197
292
|
*
|
|
198
|
-
* @param
|
|
199
|
-
* @param
|
|
293
|
+
* @param pool - Pool info containing chainId, address, and deployment block
|
|
294
|
+
* @param fromBlock - Optional starting block (defaults to pool deployment block)
|
|
200
295
|
* @returns Array of ragequit events with properly typed fields (bigint for numbers, Hash for commitments)
|
|
201
296
|
* @throws {DataError} If client is not configured, network error occurs, or event data is invalid
|
|
202
297
|
*/
|
|
@@ -206,23 +301,56 @@ export class DataService {
|
|
|
206
301
|
): Promise<RagequitEvent[]> {
|
|
207
302
|
try {
|
|
208
303
|
const client = this.getClientForChain(pool.chainId);
|
|
209
|
-
const
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
304
|
+
const chainConfig = this.getConfigForChain(pool.chainId);
|
|
305
|
+
const logConfig = this.getLogFetchConfigForChain(pool.chainId);
|
|
306
|
+
|
|
307
|
+
const startBlock = fromBlock ?? chainConfig.startBlock;
|
|
308
|
+
const toBlock = await this.getCurrentBlock(pool.chainId);
|
|
309
|
+
const ranges = this.generateBlockRanges(
|
|
310
|
+
startBlock,
|
|
311
|
+
toBlock,
|
|
312
|
+
logConfig.blockChunkSize
|
|
313
|
+
);
|
|
314
|
+
|
|
315
|
+
this.logger.debug(
|
|
316
|
+
`Fetching ragequits in ${ranges.length} chunks for pool ${pool.address}`
|
|
317
|
+
);
|
|
318
|
+
|
|
319
|
+
// Use async.mapLimit for controlled concurrency
|
|
320
|
+
const allLogs = await mapLimit<BlockRange, unknown[]>(
|
|
321
|
+
ranges,
|
|
322
|
+
logConfig.concurrency,
|
|
323
|
+
async (range: BlockRange) => {
|
|
324
|
+
if (logConfig.chunkDelayMs > 0) {
|
|
325
|
+
await this.sleep(logConfig.chunkDelayMs);
|
|
326
|
+
}
|
|
327
|
+
return this.fetchLogsWithRetry(
|
|
328
|
+
client,
|
|
329
|
+
pool.address,
|
|
330
|
+
RAGEQUIT_EVENT,
|
|
331
|
+
range,
|
|
332
|
+
logConfig
|
|
333
|
+
);
|
|
334
|
+
}
|
|
335
|
+
);
|
|
336
|
+
|
|
337
|
+
// Flatten and parse results
|
|
338
|
+
const flatLogs = allLogs.flat();
|
|
222
339
|
|
|
223
|
-
return
|
|
340
|
+
return flatLogs.map((log: unknown) => {
|
|
224
341
|
try {
|
|
225
|
-
|
|
342
|
+
const typedLog = log as {
|
|
343
|
+
args?: {
|
|
344
|
+
_ragequitter?: string;
|
|
345
|
+
_commitment?: bigint;
|
|
346
|
+
_label?: bigint;
|
|
347
|
+
_value?: bigint;
|
|
348
|
+
};
|
|
349
|
+
blockNumber?: bigint;
|
|
350
|
+
transactionHash?: Hex;
|
|
351
|
+
};
|
|
352
|
+
|
|
353
|
+
if (!typedLog.args) {
|
|
226
354
|
throw DataError.invalidLog("ragequit", "missing args");
|
|
227
355
|
}
|
|
228
356
|
|
|
@@ -231,9 +359,15 @@ export class DataService {
|
|
|
231
359
|
_commitment: commitment,
|
|
232
360
|
_label: label,
|
|
233
361
|
_value: value,
|
|
234
|
-
} =
|
|
362
|
+
} = typedLog.args;
|
|
235
363
|
|
|
236
|
-
if (
|
|
364
|
+
if (
|
|
365
|
+
!ragequitter ||
|
|
366
|
+
!commitment ||
|
|
367
|
+
!label ||
|
|
368
|
+
!typedLog.blockNumber ||
|
|
369
|
+
!typedLog.transactionHash
|
|
370
|
+
) {
|
|
237
371
|
throw DataError.invalidLog("ragequit", "missing required fields");
|
|
238
372
|
}
|
|
239
373
|
|
|
@@ -242,20 +376,107 @@ export class DataService {
|
|
|
242
376
|
commitment: commitment as Hash,
|
|
243
377
|
label: label as Hash,
|
|
244
378
|
value: value || BigInt(0),
|
|
245
|
-
blockNumber: BigInt(
|
|
246
|
-
transactionHash:
|
|
379
|
+
blockNumber: BigInt(typedLog.blockNumber),
|
|
380
|
+
transactionHash: typedLog.transactionHash,
|
|
247
381
|
};
|
|
248
382
|
} catch (error) {
|
|
249
383
|
if (error instanceof DataError) throw error;
|
|
250
|
-
throw DataError.invalidLog(
|
|
384
|
+
throw DataError.invalidLog(
|
|
385
|
+
"ragequit",
|
|
386
|
+
error instanceof Error ? error.message : "Unknown error"
|
|
387
|
+
);
|
|
251
388
|
}
|
|
252
389
|
});
|
|
253
390
|
} catch (error) {
|
|
254
391
|
if (error instanceof DataError) throw error;
|
|
255
|
-
throw DataError.networkError(
|
|
392
|
+
throw DataError.networkError(
|
|
393
|
+
pool.chainId,
|
|
394
|
+
error instanceof Error ? error : new Error(String(error))
|
|
395
|
+
);
|
|
256
396
|
}
|
|
257
397
|
}
|
|
258
398
|
|
|
399
|
+
/**
|
|
400
|
+
* Gets the current block number for a chain
|
|
401
|
+
*/
|
|
402
|
+
private async getCurrentBlock(chainId: number): Promise<bigint> {
|
|
403
|
+
const client = this.getClientForChain(chainId);
|
|
404
|
+
return client.getBlockNumber();
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
/**
|
|
408
|
+
* Generates block ranges for chunked fetching
|
|
409
|
+
*/
|
|
410
|
+
private generateBlockRanges(
|
|
411
|
+
fromBlock: bigint,
|
|
412
|
+
toBlock: bigint,
|
|
413
|
+
chunkSize: number
|
|
414
|
+
): BlockRange[] {
|
|
415
|
+
const ranges: BlockRange[] = [];
|
|
416
|
+
let current = fromBlock;
|
|
417
|
+
|
|
418
|
+
while (current <= toBlock) {
|
|
419
|
+
const end = current + BigInt(chunkSize) - 1n;
|
|
420
|
+
ranges.push({
|
|
421
|
+
fromBlock: current,
|
|
422
|
+
toBlock: end > toBlock ? toBlock : end,
|
|
423
|
+
});
|
|
424
|
+
current = end + 1n;
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
return ranges;
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
/**
|
|
431
|
+
* Fetches logs for a single block range with retry logic
|
|
432
|
+
*/
|
|
433
|
+
private async fetchLogsWithRetry<T>(
|
|
434
|
+
client: PublicClient,
|
|
435
|
+
address: string,
|
|
436
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
437
|
+
event: any,
|
|
438
|
+
range: BlockRange,
|
|
439
|
+
logConfig: LogFetchConfig
|
|
440
|
+
): Promise<T[]> {
|
|
441
|
+
const maxRetries = logConfig.retryOnFailure
|
|
442
|
+
? logConfig.maxRetries
|
|
443
|
+
: 0;
|
|
444
|
+
let lastError: Error | undefined;
|
|
445
|
+
|
|
446
|
+
for (let attempt = 0; attempt <= maxRetries; attempt++) {
|
|
447
|
+
try {
|
|
448
|
+
const logs = await client.getLogs({
|
|
449
|
+
address: address as `0x${string}`,
|
|
450
|
+
event,
|
|
451
|
+
fromBlock: range.fromBlock,
|
|
452
|
+
toBlock: range.toBlock,
|
|
453
|
+
});
|
|
454
|
+
return logs as T[];
|
|
455
|
+
} catch (error) {
|
|
456
|
+
lastError = error instanceof Error ? error : new Error(String(error));
|
|
457
|
+
|
|
458
|
+
if (attempt < maxRetries) {
|
|
459
|
+
const delay =
|
|
460
|
+
logConfig.retryBaseDelayMs * Math.pow(2, attempt);
|
|
461
|
+
this.logger.warn(
|
|
462
|
+
`Log fetch failed, retrying in ${delay}ms (attempt ${attempt + 1}/${maxRetries})`,
|
|
463
|
+
{ error: lastError.message, range }
|
|
464
|
+
);
|
|
465
|
+
await this.sleep(delay);
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
throw lastError;
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
/**
|
|
474
|
+
* Helper to add delay between requests
|
|
475
|
+
*/
|
|
476
|
+
private sleep(ms: number): Promise<void> {
|
|
477
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
478
|
+
}
|
|
479
|
+
|
|
259
480
|
private getClientForChain(chainId: number): PublicClient {
|
|
260
481
|
const client = this.clients.get(chainId);
|
|
261
482
|
if (!client) {
|
|
@@ -265,10 +486,19 @@ export class DataService {
|
|
|
265
486
|
}
|
|
266
487
|
|
|
267
488
|
private getConfigForChain(chainId: number): ChainConfig {
|
|
268
|
-
const config = this.chainConfigs.find(c => c.chainId === chainId);
|
|
489
|
+
const config = this.chainConfigs.find((c) => c.chainId === chainId);
|
|
269
490
|
if (!config) {
|
|
270
491
|
throw DataError.chainNotConfigured(chainId);
|
|
271
492
|
}
|
|
272
493
|
return config;
|
|
273
494
|
}
|
|
495
|
+
|
|
496
|
+
private getLogFetchConfigForChain(chainId: number): LogFetchConfig {
|
|
497
|
+
const config = this.logFetchConfigs.get(chainId);
|
|
498
|
+
if (!config) {
|
|
499
|
+
// Fallback to default if not found (shouldn't happen if constructor is correct)
|
|
500
|
+
return DEFAULT_LOG_FETCH_CONFIG;
|
|
501
|
+
}
|
|
502
|
+
return config;
|
|
503
|
+
}
|
|
274
504
|
}
|
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
|
+
}
|