@0xbow/privacy-pools-core-sdk 0.0.0-3dpf8pk
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/README.md +73 -0
- package/dist/esm/fetchArtifacts.esm-DTr__iP-.js +18 -0
- package/dist/esm/fetchArtifacts.esm-DTr__iP-.js.map +1 -0
- package/dist/esm/fetchArtifacts.node-BLw8nwbt.js +31 -0
- package/dist/esm/fetchArtifacts.node-BLw8nwbt.js.map +1 -0
- package/dist/esm/index-Derz3sX9.js +73457 -0
- package/dist/esm/index-Derz3sX9.js.map +1 -0
- package/dist/esm/index.mjs +7 -0
- package/dist/esm/index.mjs.map +1 -0
- package/dist/index.d.mts +1326 -0
- package/dist/node/fetchArtifacts.esm-ZwE-hqnx.js +35 -0
- package/dist/node/fetchArtifacts.esm-ZwE-hqnx.js.map +1 -0
- package/dist/node/fetchArtifacts.node-CY8wLnXd.js +48 -0
- package/dist/node/fetchArtifacts.node-CY8wLnXd.js.map +1 -0
- package/dist/node/index-B804ILXn.js +80507 -0
- package/dist/node/index-B804ILXn.js.map +1 -0
- package/dist/node/index.mjs +24 -0
- package/dist/node/index.mjs.map +1 -0
- package/dist/types/abi/ERC20.d.ts +38 -0
- package/dist/types/abi/IEntrypoint.d.ts +823 -0
- package/dist/types/abi/IPrivacyPool.d.ts +51 -0
- package/dist/types/circuits/circuits.impl.d.ts +120 -0
- package/dist/types/circuits/circuits.interface.d.ts +129 -0
- package/dist/types/circuits/fetchArtifacts.d.ts +1 -0
- package/dist/types/circuits/fetchArtifacts.esm.d.ts +1 -0
- package/dist/types/circuits/fetchArtifacts.node.d.ts +1 -0
- package/dist/types/circuits/index.d.ts +2 -0
- package/dist/types/constants.d.ts +2 -0
- package/dist/types/core/account.service.d.ts +355 -0
- package/dist/types/core/bruteForce.service.d.ts +61 -0
- package/dist/types/core/commitment.service.d.ts +30 -0
- package/dist/types/core/contracts.service.d.ts +114 -0
- package/dist/types/core/data.service.d.ts +72 -0
- package/dist/types/core/sdk.d.ts +45 -0
- package/dist/types/core/withdrawal.service.d.ts +32 -0
- package/dist/types/crypto.d.ts +67 -0
- package/dist/types/dirname.helper.d.ts +2 -0
- package/dist/types/errors/account.error.d.ts +10 -0
- package/dist/types/errors/base.error.d.ts +53 -0
- package/dist/types/errors/data.error.d.ts +7 -0
- package/dist/types/errors/events.error.d.ts +9 -0
- package/dist/types/exceptions/circuitInitialization.exception.d.ts +3 -0
- package/dist/types/exceptions/fetchArtifacts.exception.d.ts +3 -0
- package/dist/types/exceptions/index.d.ts +4 -0
- package/dist/types/exceptions/invalidRpcUrl.exception.d.ts +3 -0
- package/dist/types/exceptions/privacyPool.exception.d.ts +13 -0
- package/dist/types/external.d.ts +7 -0
- package/dist/types/fetchArtifacts.esm-m-j0Hu4b.js +34 -0
- package/dist/types/fetchArtifacts.node-CGJMDWIh.js +47 -0
- package/dist/types/filename.helper.d.ts +2 -0
- package/dist/types/index-B6kM6ceO.js +80520 -0
- package/dist/types/index.d.ts +14 -0
- package/dist/types/index.js +23 -0
- package/dist/types/interfaces/blockchainProvider.interface.d.ts +12 -0
- package/dist/types/interfaces/circuits.interface.d.ts +30 -0
- package/dist/types/interfaces/contracts.interface.d.ts +35 -0
- package/dist/types/interfaces/index.d.ts +1 -0
- package/dist/types/internal.d.ts +6 -0
- package/dist/types/keys.d.ts +18 -0
- package/dist/types/providers/blockchainProvider.d.ts +8 -0
- package/dist/types/providers/index.d.ts +1 -0
- package/dist/types/types/account.d.ts +31 -0
- package/dist/types/types/commitment.d.ts +48 -0
- package/dist/types/types/events.d.ts +72 -0
- package/dist/types/types/index.d.ts +4 -0
- package/dist/types/types/keys.d.ts +5 -0
- package/dist/types/types/rateLimit.d.ts +51 -0
- package/dist/types/types/withdrawal.d.ts +30 -0
- package/dist/types/utils/logger.d.ts +22 -0
- package/package.json +84 -0
- package/src/abi/ERC20.ts +222 -0
- package/src/abi/IEntrypoint.ts +1059 -0
- package/src/abi/IPrivacyPool.ts +576 -0
- package/src/circuits/circuits.impl.ts +232 -0
- package/src/circuits/circuits.interface.ts +166 -0
- package/src/circuits/fetchArtifacts.esm.ts +12 -0
- package/src/circuits/fetchArtifacts.node.ts +23 -0
- package/src/circuits/fetchArtifacts.ts +7 -0
- package/src/circuits/index.ts +2 -0
- package/src/constants.ts +3 -0
- package/src/core/account.service.ts +1343 -0
- package/src/core/bruteForce.service.ts +120 -0
- package/src/core/commitment.service.ts +84 -0
- package/src/core/contracts.service.ts +442 -0
- package/src/core/data.service.ts +608 -0
- package/src/core/sdk.ts +92 -0
- package/src/core/withdrawal.service.ts +126 -0
- package/src/crypto.ts +226 -0
- package/src/dirname.helper.ts +4 -0
- package/src/errors/account.error.ts +49 -0
- package/src/errors/base.error.ts +125 -0
- package/src/errors/data.error.ts +34 -0
- package/src/errors/events.error.ts +38 -0
- package/src/exceptions/circuitInitialization.exception.ts +6 -0
- package/src/exceptions/fetchArtifacts.exception.ts +7 -0
- package/src/exceptions/index.ts +4 -0
- package/src/exceptions/invalidRpcUrl.exception.ts +6 -0
- package/src/exceptions/privacyPool.exception.ts +19 -0
- package/src/external.ts +13 -0
- package/src/filename.helper.ts +4 -0
- package/src/index.ts +25 -0
- package/src/interfaces/blockchainProvider.interface.ts +13 -0
- package/src/interfaces/circuits.interface.ts +34 -0
- package/src/interfaces/contracts.interface.ts +66 -0
- package/src/interfaces/index.ts +1 -0
- package/src/internal.ts +6 -0
- package/src/keys.ts +42 -0
- package/src/providers/blockchainProvider.ts +26 -0
- package/src/providers/index.ts +1 -0
- package/src/types/account.ts +35 -0
- package/src/types/commitment.ts +50 -0
- package/src/types/events.ts +82 -0
- package/src/types/index.ts +4 -0
- package/src/types/keys.ts +6 -0
- package/src/types/rateLimit.ts +66 -0
- package/src/types/withdrawal.ts +33 -0
- package/src/utils/logger.ts +56 -0
|
@@ -0,0 +1,608 @@
|
|
|
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";
|
|
10
|
+
|
|
11
|
+
// Event signatures from the contract
|
|
12
|
+
const DEPOSIT_EVENT = parseAbiItem('event Deposited(address indexed _depositor, uint256 _commitment, uint256 _label, uint256 _value, uint256 _merkleRoot)');
|
|
13
|
+
const WITHDRAWAL_EVENT = parseAbiItem('event Withdrawn(address indexed _processooor, uint256 _value, uint256 _spentNullifier, uint256 _newCommitment)');
|
|
14
|
+
const RAGEQUIT_EVENT = parseAbiItem('event Ragequit(address indexed _ragequitter, uint256 _commitment, uint256 _label, uint256 _value)');
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Service responsible for fetching and managing privacy pool events across multiple chains.
|
|
18
|
+
* Handles event retrieval, parsing, and validation for deposits, withdrawals, and ragequits.
|
|
19
|
+
*
|
|
20
|
+
* @remarks
|
|
21
|
+
* This service uses viem's PublicClient to efficiently fetch and process blockchain events.
|
|
22
|
+
* It supports multiple chains and provides robust error handling and validation.
|
|
23
|
+
* All uint256 values from events are handled as bigints, with Hash type assertions for commitment-related fields.
|
|
24
|
+
*/
|
|
25
|
+
export class DataService {
|
|
26
|
+
private readonly clients: Map<number, PublicClient> = new Map();
|
|
27
|
+
private readonly logger: Logger;
|
|
28
|
+
private readonly logFetchConfigs: Map<number, LogFetchConfig>;
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Initialize the data service with chain configurations
|
|
32
|
+
*
|
|
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).
|
|
36
|
+
* @throws {DataError} If client initialization fails for any chain
|
|
37
|
+
*/
|
|
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
|
+
}
|
|
53
|
+
|
|
54
|
+
try {
|
|
55
|
+
for (const config of chainConfigs) {
|
|
56
|
+
if (!config.rpcUrl) {
|
|
57
|
+
throw new Error(`Missing RPC URL for chain ${config.chainId}`);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const client = createPublicClient({
|
|
61
|
+
transport: http(config.rpcUrl),
|
|
62
|
+
});
|
|
63
|
+
this.clients.set(config.chainId, client);
|
|
64
|
+
}
|
|
65
|
+
} catch (error) {
|
|
66
|
+
throw new DataError(
|
|
67
|
+
"Failed to initialize PublicClient",
|
|
68
|
+
ErrorCode.NETWORK_ERROR,
|
|
69
|
+
{ error: error instanceof Error ? error.message : "Unknown error" },
|
|
70
|
+
);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Get deposit events for a specific chain
|
|
76
|
+
*
|
|
77
|
+
* @param pool - Pool info containing chainId, address, and deployment block
|
|
78
|
+
* @returns Array of deposit events with properly typed fields (bigint for numbers, Hash for commitments)
|
|
79
|
+
* @throws {DataError} If client is not configured, network error occurs, or event data is invalid
|
|
80
|
+
*/
|
|
81
|
+
async getDeposits(pool: PoolInfo): Promise<DepositEvent[]> {
|
|
82
|
+
try {
|
|
83
|
+
const client = this.getClientForChain(pool.chainId);
|
|
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
|
+
);
|
|
98
|
+
|
|
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
|
+
this.logger.debug(`[DataService:getDeposits] Raw logs fetched`, {
|
|
121
|
+
poolAddress: pool.address,
|
|
122
|
+
chainId: pool.chainId,
|
|
123
|
+
fromBlock: fromBlock.toString(),
|
|
124
|
+
toBlock: toBlock.toString(),
|
|
125
|
+
chunkCount: ranges.length,
|
|
126
|
+
rawLogCount: flatLogs.length,
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
const parsed = flatLogs.map((log: unknown) => {
|
|
130
|
+
try {
|
|
131
|
+
const typedLog = log as {
|
|
132
|
+
args?: {
|
|
133
|
+
_depositor?: string;
|
|
134
|
+
_commitment?: bigint;
|
|
135
|
+
_label?: bigint;
|
|
136
|
+
_value?: bigint;
|
|
137
|
+
_merkleRoot?: bigint;
|
|
138
|
+
};
|
|
139
|
+
blockNumber?: bigint;
|
|
140
|
+
transactionHash?: Hex;
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
if (!typedLog.args) {
|
|
144
|
+
throw DataError.invalidLog("deposit", "missing args");
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
const {
|
|
148
|
+
_depositor: depositor,
|
|
149
|
+
_commitment: commitment,
|
|
150
|
+
_label: label,
|
|
151
|
+
_value: value,
|
|
152
|
+
_merkleRoot: precommitment,
|
|
153
|
+
} = typedLog.args;
|
|
154
|
+
|
|
155
|
+
if (
|
|
156
|
+
!depositor ||
|
|
157
|
+
!commitment ||
|
|
158
|
+
!label ||
|
|
159
|
+
!precommitment ||
|
|
160
|
+
!typedLog.blockNumber ||
|
|
161
|
+
!typedLog.transactionHash
|
|
162
|
+
) {
|
|
163
|
+
throw DataError.invalidLog("deposit", "missing required fields");
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
const result = {
|
|
167
|
+
depositor: depositor.toLowerCase(),
|
|
168
|
+
commitment: commitment as Hash,
|
|
169
|
+
label: label as Hash,
|
|
170
|
+
value: value || BigInt(0),
|
|
171
|
+
precommitment: precommitment as Hash,
|
|
172
|
+
blockNumber: BigInt(typedLog.blockNumber),
|
|
173
|
+
transactionHash: typedLog.transactionHash,
|
|
174
|
+
};
|
|
175
|
+
|
|
176
|
+
this.logger.debug(`[DataService:getDeposits] Parsed deposit event`, {
|
|
177
|
+
poolAddress: pool.address,
|
|
178
|
+
chainId: pool.chainId,
|
|
179
|
+
depositor: result.depositor,
|
|
180
|
+
commitment: result.commitment,
|
|
181
|
+
label: result.label,
|
|
182
|
+
value: result.value.toString(),
|
|
183
|
+
precommitment: result.precommitment,
|
|
184
|
+
blockNumber: result.blockNumber.toString(),
|
|
185
|
+
transactionHash: result.transactionHash,
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
return result;
|
|
189
|
+
} catch (error) {
|
|
190
|
+
this.logger.debug(`[DataService:getDeposits] Failed to parse deposit log`, {
|
|
191
|
+
poolAddress: pool.address,
|
|
192
|
+
chainId: pool.chainId,
|
|
193
|
+
error: error instanceof Error ? error.message : String(error),
|
|
194
|
+
});
|
|
195
|
+
if (error instanceof DataError) throw error;
|
|
196
|
+
throw DataError.invalidLog(
|
|
197
|
+
"deposit",
|
|
198
|
+
error instanceof Error ? error.message : "Unknown error"
|
|
199
|
+
);
|
|
200
|
+
}
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
this.logger.debug(`[DataService:getDeposits] Deposit fetch complete`, {
|
|
204
|
+
poolAddress: pool.address,
|
|
205
|
+
chainId: pool.chainId,
|
|
206
|
+
totalParsed: parsed.length,
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
return parsed;
|
|
210
|
+
} catch (error) {
|
|
211
|
+
if (error instanceof DataError) throw error;
|
|
212
|
+
throw DataError.networkError(
|
|
213
|
+
pool.chainId,
|
|
214
|
+
error instanceof Error ? error : new Error(String(error))
|
|
215
|
+
);
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* Get withdrawal events for a specific chain
|
|
221
|
+
*
|
|
222
|
+
* @param pool - Pool info containing chainId, address, and deployment block
|
|
223
|
+
* @param fromBlock - Optional starting block (defaults to pool deployment block)
|
|
224
|
+
* @returns Array of withdrawal events with properly typed fields (bigint for numbers, Hash for commitments)
|
|
225
|
+
* @throws {DataError} If client is not configured, network error occurs, or event data is invalid
|
|
226
|
+
*/
|
|
227
|
+
async getWithdrawals(
|
|
228
|
+
pool: PoolInfo,
|
|
229
|
+
fromBlock: bigint = pool.deploymentBlock
|
|
230
|
+
): Promise<WithdrawalEvent[]> {
|
|
231
|
+
try {
|
|
232
|
+
const client = this.getClientForChain(pool.chainId);
|
|
233
|
+
const chainConfig = this.getConfigForChain(pool.chainId);
|
|
234
|
+
const logConfig = this.getLogFetchConfigForChain(pool.chainId);
|
|
235
|
+
|
|
236
|
+
const startBlock = fromBlock ?? chainConfig.startBlock;
|
|
237
|
+
const toBlock = await this.getCurrentBlock(pool.chainId);
|
|
238
|
+
const ranges = this.generateBlockRanges(
|
|
239
|
+
startBlock,
|
|
240
|
+
toBlock,
|
|
241
|
+
logConfig.blockChunkSize
|
|
242
|
+
);
|
|
243
|
+
|
|
244
|
+
this.logger.debug(
|
|
245
|
+
`Fetching withdrawals in ${ranges.length} chunks for pool ${pool.address}`
|
|
246
|
+
);
|
|
247
|
+
|
|
248
|
+
// Use async.mapLimit for controlled concurrency
|
|
249
|
+
const allLogs = await mapLimit<BlockRange, unknown[]>(
|
|
250
|
+
ranges,
|
|
251
|
+
logConfig.concurrency,
|
|
252
|
+
async (range: BlockRange) => {
|
|
253
|
+
if (logConfig.chunkDelayMs > 0) {
|
|
254
|
+
await this.sleep(logConfig.chunkDelayMs);
|
|
255
|
+
}
|
|
256
|
+
return this.fetchLogsWithRetry(
|
|
257
|
+
client,
|
|
258
|
+
pool.address,
|
|
259
|
+
WITHDRAWAL_EVENT,
|
|
260
|
+
range,
|
|
261
|
+
logConfig
|
|
262
|
+
);
|
|
263
|
+
}
|
|
264
|
+
);
|
|
265
|
+
|
|
266
|
+
// Flatten and parse results
|
|
267
|
+
const flatWithdrawalLogs = allLogs.flat();
|
|
268
|
+
|
|
269
|
+
this.logger.debug(`[DataService:getWithdrawals] Raw logs fetched`, {
|
|
270
|
+
poolAddress: pool.address,
|
|
271
|
+
chainId: pool.chainId,
|
|
272
|
+
fromBlock: startBlock.toString(),
|
|
273
|
+
toBlock: toBlock.toString(),
|
|
274
|
+
chunkCount: ranges.length,
|
|
275
|
+
rawLogCount: flatWithdrawalLogs.length,
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
const parsedWithdrawals = flatWithdrawalLogs.map((log: unknown) => {
|
|
279
|
+
try {
|
|
280
|
+
const typedLog = log as {
|
|
281
|
+
args?: {
|
|
282
|
+
_value?: bigint;
|
|
283
|
+
_spentNullifier?: bigint;
|
|
284
|
+
_newCommitment?: bigint;
|
|
285
|
+
};
|
|
286
|
+
blockNumber?: bigint;
|
|
287
|
+
transactionHash?: Hex;
|
|
288
|
+
};
|
|
289
|
+
|
|
290
|
+
if (!typedLog.args) {
|
|
291
|
+
throw DataError.invalidLog("withdrawal", "missing args");
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
const {
|
|
295
|
+
_value: value,
|
|
296
|
+
_spentNullifier: spentNullifier,
|
|
297
|
+
_newCommitment: newCommitment,
|
|
298
|
+
} = typedLog.args;
|
|
299
|
+
|
|
300
|
+
if (
|
|
301
|
+
!value ||
|
|
302
|
+
!spentNullifier ||
|
|
303
|
+
!newCommitment ||
|
|
304
|
+
!typedLog.blockNumber ||
|
|
305
|
+
!typedLog.transactionHash
|
|
306
|
+
) {
|
|
307
|
+
throw DataError.invalidLog("withdrawal", "missing required fields");
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
const result = {
|
|
311
|
+
withdrawn: value,
|
|
312
|
+
spentNullifier: spentNullifier as Hash,
|
|
313
|
+
newCommitment: newCommitment as Hash,
|
|
314
|
+
blockNumber: BigInt(typedLog.blockNumber),
|
|
315
|
+
transactionHash: typedLog.transactionHash,
|
|
316
|
+
};
|
|
317
|
+
|
|
318
|
+
this.logger.debug(`[DataService:getWithdrawals] Parsed withdrawal event`, {
|
|
319
|
+
poolAddress: pool.address,
|
|
320
|
+
chainId: pool.chainId,
|
|
321
|
+
withdrawn: result.withdrawn.toString(),
|
|
322
|
+
spentNullifier: result.spentNullifier,
|
|
323
|
+
newCommitment: result.newCommitment,
|
|
324
|
+
blockNumber: result.blockNumber.toString(),
|
|
325
|
+
transactionHash: result.transactionHash,
|
|
326
|
+
});
|
|
327
|
+
|
|
328
|
+
return result;
|
|
329
|
+
} catch (error) {
|
|
330
|
+
this.logger.debug(`[DataService:getWithdrawals] Failed to parse withdrawal log`, {
|
|
331
|
+
poolAddress: pool.address,
|
|
332
|
+
chainId: pool.chainId,
|
|
333
|
+
error: error instanceof Error ? error.message : String(error),
|
|
334
|
+
});
|
|
335
|
+
if (error instanceof DataError) throw error;
|
|
336
|
+
throw DataError.invalidLog(
|
|
337
|
+
"withdrawal",
|
|
338
|
+
error instanceof Error ? error.message : "Unknown error"
|
|
339
|
+
);
|
|
340
|
+
}
|
|
341
|
+
});
|
|
342
|
+
|
|
343
|
+
this.logger.debug(`[DataService:getWithdrawals] Withdrawal fetch complete`, {
|
|
344
|
+
poolAddress: pool.address,
|
|
345
|
+
chainId: pool.chainId,
|
|
346
|
+
totalParsed: parsedWithdrawals.length,
|
|
347
|
+
});
|
|
348
|
+
|
|
349
|
+
return parsedWithdrawals;
|
|
350
|
+
} catch (error) {
|
|
351
|
+
if (error instanceof DataError) throw error;
|
|
352
|
+
throw DataError.networkError(
|
|
353
|
+
pool.chainId,
|
|
354
|
+
error instanceof Error ? error : new Error(String(error))
|
|
355
|
+
);
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
/**
|
|
360
|
+
* Get ragequit events for a specific chain
|
|
361
|
+
*
|
|
362
|
+
* @param pool - Pool info containing chainId, address, and deployment block
|
|
363
|
+
* @param fromBlock - Optional starting block (defaults to pool deployment block)
|
|
364
|
+
* @returns Array of ragequit events with properly typed fields (bigint for numbers, Hash for commitments)
|
|
365
|
+
* @throws {DataError} If client is not configured, network error occurs, or event data is invalid
|
|
366
|
+
*/
|
|
367
|
+
async getRagequits(
|
|
368
|
+
pool: PoolInfo,
|
|
369
|
+
fromBlock: bigint = pool.deploymentBlock
|
|
370
|
+
): Promise<RagequitEvent[]> {
|
|
371
|
+
try {
|
|
372
|
+
const client = this.getClientForChain(pool.chainId);
|
|
373
|
+
const chainConfig = this.getConfigForChain(pool.chainId);
|
|
374
|
+
const logConfig = this.getLogFetchConfigForChain(pool.chainId);
|
|
375
|
+
|
|
376
|
+
const startBlock = fromBlock ?? chainConfig.startBlock;
|
|
377
|
+
const toBlock = await this.getCurrentBlock(pool.chainId);
|
|
378
|
+
const ranges = this.generateBlockRanges(
|
|
379
|
+
startBlock,
|
|
380
|
+
toBlock,
|
|
381
|
+
logConfig.blockChunkSize
|
|
382
|
+
);
|
|
383
|
+
|
|
384
|
+
this.logger.debug(
|
|
385
|
+
`Fetching ragequits in ${ranges.length} chunks for pool ${pool.address}`
|
|
386
|
+
);
|
|
387
|
+
|
|
388
|
+
// Use async.mapLimit for controlled concurrency
|
|
389
|
+
const allLogs = await mapLimit<BlockRange, unknown[]>(
|
|
390
|
+
ranges,
|
|
391
|
+
logConfig.concurrency,
|
|
392
|
+
async (range: BlockRange) => {
|
|
393
|
+
if (logConfig.chunkDelayMs > 0) {
|
|
394
|
+
await this.sleep(logConfig.chunkDelayMs);
|
|
395
|
+
}
|
|
396
|
+
return this.fetchLogsWithRetry(
|
|
397
|
+
client,
|
|
398
|
+
pool.address,
|
|
399
|
+
RAGEQUIT_EVENT,
|
|
400
|
+
range,
|
|
401
|
+
logConfig
|
|
402
|
+
);
|
|
403
|
+
}
|
|
404
|
+
);
|
|
405
|
+
|
|
406
|
+
// Flatten and parse results
|
|
407
|
+
const flatRagequitLogs = allLogs.flat();
|
|
408
|
+
|
|
409
|
+
this.logger.debug(`[DataService:getRagequits] Raw logs fetched`, {
|
|
410
|
+
poolAddress: pool.address,
|
|
411
|
+
chainId: pool.chainId,
|
|
412
|
+
fromBlock: startBlock.toString(),
|
|
413
|
+
toBlock: toBlock.toString(),
|
|
414
|
+
chunkCount: ranges.length,
|
|
415
|
+
rawLogCount: flatRagequitLogs.length,
|
|
416
|
+
});
|
|
417
|
+
|
|
418
|
+
const parsedRagequits = flatRagequitLogs.map((log: unknown) => {
|
|
419
|
+
try {
|
|
420
|
+
const typedLog = log as {
|
|
421
|
+
args?: {
|
|
422
|
+
_ragequitter?: string;
|
|
423
|
+
_commitment?: bigint;
|
|
424
|
+
_label?: bigint;
|
|
425
|
+
_value?: bigint;
|
|
426
|
+
};
|
|
427
|
+
blockNumber?: bigint;
|
|
428
|
+
transactionHash?: Hex;
|
|
429
|
+
};
|
|
430
|
+
|
|
431
|
+
if (!typedLog.args) {
|
|
432
|
+
throw DataError.invalidLog("ragequit", "missing args");
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
const {
|
|
436
|
+
_ragequitter: ragequitter,
|
|
437
|
+
_commitment: commitment,
|
|
438
|
+
_label: label,
|
|
439
|
+
_value: value,
|
|
440
|
+
} = typedLog.args;
|
|
441
|
+
|
|
442
|
+
if (
|
|
443
|
+
!ragequitter ||
|
|
444
|
+
!commitment ||
|
|
445
|
+
!label ||
|
|
446
|
+
!typedLog.blockNumber ||
|
|
447
|
+
!typedLog.transactionHash
|
|
448
|
+
) {
|
|
449
|
+
throw DataError.invalidLog("ragequit", "missing required fields");
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
const result = {
|
|
453
|
+
ragequitter: ragequitter.toLowerCase(),
|
|
454
|
+
commitment: commitment as Hash,
|
|
455
|
+
label: label as Hash,
|
|
456
|
+
value: value || BigInt(0),
|
|
457
|
+
blockNumber: BigInt(typedLog.blockNumber),
|
|
458
|
+
transactionHash: typedLog.transactionHash,
|
|
459
|
+
};
|
|
460
|
+
|
|
461
|
+
this.logger.debug(`[DataService:getRagequits] Parsed ragequit event`, {
|
|
462
|
+
poolAddress: pool.address,
|
|
463
|
+
chainId: pool.chainId,
|
|
464
|
+
ragequitter: result.ragequitter,
|
|
465
|
+
commitment: result.commitment,
|
|
466
|
+
label: result.label,
|
|
467
|
+
value: result.value.toString(),
|
|
468
|
+
blockNumber: result.blockNumber.toString(),
|
|
469
|
+
transactionHash: result.transactionHash,
|
|
470
|
+
});
|
|
471
|
+
|
|
472
|
+
return result;
|
|
473
|
+
} catch (error) {
|
|
474
|
+
this.logger.debug(`[DataService:getRagequits] Failed to parse ragequit log`, {
|
|
475
|
+
poolAddress: pool.address,
|
|
476
|
+
chainId: pool.chainId,
|
|
477
|
+
error: error instanceof Error ? error.message : String(error),
|
|
478
|
+
});
|
|
479
|
+
if (error instanceof DataError) throw error;
|
|
480
|
+
throw DataError.invalidLog(
|
|
481
|
+
"ragequit",
|
|
482
|
+
error instanceof Error ? error.message : "Unknown error"
|
|
483
|
+
);
|
|
484
|
+
}
|
|
485
|
+
});
|
|
486
|
+
|
|
487
|
+
this.logger.debug(`[DataService:getRagequits] Ragequit fetch complete`, {
|
|
488
|
+
poolAddress: pool.address,
|
|
489
|
+
chainId: pool.chainId,
|
|
490
|
+
totalParsed: parsedRagequits.length,
|
|
491
|
+
});
|
|
492
|
+
|
|
493
|
+
return parsedRagequits;
|
|
494
|
+
} catch (error) {
|
|
495
|
+
if (error instanceof DataError) throw error;
|
|
496
|
+
throw DataError.networkError(
|
|
497
|
+
pool.chainId,
|
|
498
|
+
error instanceof Error ? error : new Error(String(error))
|
|
499
|
+
);
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
/**
|
|
504
|
+
* Gets the current block number for a chain
|
|
505
|
+
*/
|
|
506
|
+
private async getCurrentBlock(chainId: number): Promise<bigint> {
|
|
507
|
+
const client = this.getClientForChain(chainId);
|
|
508
|
+
return client.getBlockNumber();
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
/**
|
|
512
|
+
* Generates block ranges for chunked fetching
|
|
513
|
+
*/
|
|
514
|
+
private generateBlockRanges(
|
|
515
|
+
fromBlock: bigint,
|
|
516
|
+
toBlock: bigint,
|
|
517
|
+
chunkSize: number
|
|
518
|
+
): BlockRange[] {
|
|
519
|
+
const ranges: BlockRange[] = [];
|
|
520
|
+
let current = fromBlock;
|
|
521
|
+
|
|
522
|
+
while (current <= toBlock) {
|
|
523
|
+
const end = current + BigInt(chunkSize) - 1n;
|
|
524
|
+
ranges.push({
|
|
525
|
+
fromBlock: current,
|
|
526
|
+
toBlock: end > toBlock ? toBlock : end,
|
|
527
|
+
});
|
|
528
|
+
current = end + 1n;
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
return ranges;
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
/**
|
|
535
|
+
* Fetches logs for a single block range with retry logic
|
|
536
|
+
*/
|
|
537
|
+
private async fetchLogsWithRetry<T>(
|
|
538
|
+
client: PublicClient,
|
|
539
|
+
address: string,
|
|
540
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
541
|
+
event: any,
|
|
542
|
+
range: BlockRange,
|
|
543
|
+
logConfig: LogFetchConfig
|
|
544
|
+
): Promise<T[]> {
|
|
545
|
+
const maxRetries = logConfig.retryOnFailure
|
|
546
|
+
? logConfig.maxRetries
|
|
547
|
+
: 0;
|
|
548
|
+
let lastError: Error | undefined;
|
|
549
|
+
|
|
550
|
+
for (let attempt = 0; attempt <= maxRetries; attempt++) {
|
|
551
|
+
try {
|
|
552
|
+
const logs = await client.getLogs({
|
|
553
|
+
address: address as `0x${string}`,
|
|
554
|
+
event,
|
|
555
|
+
fromBlock: range.fromBlock,
|
|
556
|
+
toBlock: range.toBlock,
|
|
557
|
+
});
|
|
558
|
+
return logs as T[];
|
|
559
|
+
} catch (error) {
|
|
560
|
+
lastError = error instanceof Error ? error : new Error(String(error));
|
|
561
|
+
|
|
562
|
+
if (attempt < maxRetries) {
|
|
563
|
+
const delay =
|
|
564
|
+
logConfig.retryBaseDelayMs * Math.pow(2, attempt);
|
|
565
|
+
this.logger.warn(
|
|
566
|
+
`Log fetch failed, retrying in ${delay}ms (attempt ${attempt + 1}/${maxRetries})`,
|
|
567
|
+
{ error: lastError.message, range }
|
|
568
|
+
);
|
|
569
|
+
await this.sleep(delay);
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
throw lastError;
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
/**
|
|
578
|
+
* Helper to add delay between requests
|
|
579
|
+
*/
|
|
580
|
+
private sleep(ms: number): Promise<void> {
|
|
581
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
private getClientForChain(chainId: number): PublicClient {
|
|
585
|
+
const client = this.clients.get(chainId);
|
|
586
|
+
if (!client) {
|
|
587
|
+
throw DataError.chainNotConfigured(chainId);
|
|
588
|
+
}
|
|
589
|
+
return client;
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
private getConfigForChain(chainId: number): ChainConfig {
|
|
593
|
+
const config = this.chainConfigs.find((c) => c.chainId === chainId);
|
|
594
|
+
if (!config) {
|
|
595
|
+
throw DataError.chainNotConfigured(chainId);
|
|
596
|
+
}
|
|
597
|
+
return config;
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
private getLogFetchConfigForChain(chainId: number): LogFetchConfig {
|
|
601
|
+
const config = this.logFetchConfigs.get(chainId);
|
|
602
|
+
if (!config) {
|
|
603
|
+
// Fallback to default if not found (shouldn't happen if constructor is correct)
|
|
604
|
+
return DEFAULT_LOG_FETCH_CONFIG;
|
|
605
|
+
}
|
|
606
|
+
return config;
|
|
607
|
+
}
|
|
608
|
+
}
|
package/src/core/sdk.ts
ADDED
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { CommitmentService } from "./commitment.service.js";
|
|
2
|
+
import { WithdrawalService } from "./withdrawal.service.js";
|
|
3
|
+
import { CircuitsInterface } from "../interfaces/circuits.interface.js";
|
|
4
|
+
import { Commitment, CommitmentProof } from "../types/commitment.js";
|
|
5
|
+
import { WithdrawalProof, WithdrawalProofInput } from "../types/withdrawal.js";
|
|
6
|
+
import { ContractInteractionsService } from "./contracts.service.js";
|
|
7
|
+
import { Hex, Address, Chain } from "viem";
|
|
8
|
+
import { AccountCommitment } from "../types/account.js";
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Main SDK class providing access to all privacy pool functionality.
|
|
12
|
+
* Uses Poseidon hash for all commitment operations.
|
|
13
|
+
*/
|
|
14
|
+
export class PrivacyPoolSDK {
|
|
15
|
+
private readonly commitmentService: CommitmentService;
|
|
16
|
+
private readonly withdrawalService: WithdrawalService;
|
|
17
|
+
|
|
18
|
+
constructor(circuits: CircuitsInterface) {
|
|
19
|
+
this.commitmentService = new CommitmentService(circuits);
|
|
20
|
+
this.withdrawalService = new WithdrawalService(circuits);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
public createContractInstance(
|
|
24
|
+
rpcUrl: string,
|
|
25
|
+
chain: Chain,
|
|
26
|
+
entrypointAddress: Address,
|
|
27
|
+
privateKey: Hex,
|
|
28
|
+
): ContractInteractionsService {
|
|
29
|
+
return new ContractInteractionsService(
|
|
30
|
+
rpcUrl,
|
|
31
|
+
chain,
|
|
32
|
+
entrypointAddress,
|
|
33
|
+
privateKey,
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Generates a commitment proof.
|
|
39
|
+
*
|
|
40
|
+
* @param value - Value to commit
|
|
41
|
+
* @param label - Label for the commitment
|
|
42
|
+
* @param nullifier - Nullifier for the commitment
|
|
43
|
+
* @param secret - Secret for the commitment
|
|
44
|
+
*/
|
|
45
|
+
public async proveCommitment(
|
|
46
|
+
value: bigint,
|
|
47
|
+
label: bigint,
|
|
48
|
+
nullifier: bigint,
|
|
49
|
+
secret: bigint,
|
|
50
|
+
): Promise<CommitmentProof> {
|
|
51
|
+
return this.commitmentService.proveCommitment(
|
|
52
|
+
value,
|
|
53
|
+
label,
|
|
54
|
+
nullifier,
|
|
55
|
+
secret,
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Verifies a commitment proof.
|
|
61
|
+
*
|
|
62
|
+
* @param proof - The proof to verify
|
|
63
|
+
*/
|
|
64
|
+
public async verifyCommitment(proof: CommitmentProof): Promise<boolean> {
|
|
65
|
+
return this.commitmentService.verifyCommitment(proof);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Generates a withdrawal proof.
|
|
70
|
+
*
|
|
71
|
+
* @param commitment - Commitment to withdraw
|
|
72
|
+
* @param input - Input parameters for the withdrawal
|
|
73
|
+
* @param withdrawal - Withdrawal details
|
|
74
|
+
*/
|
|
75
|
+
public async proveWithdrawal(
|
|
76
|
+
commitment: Commitment | AccountCommitment ,
|
|
77
|
+
input: WithdrawalProofInput,
|
|
78
|
+
): Promise<WithdrawalProof> {
|
|
79
|
+
return await this.withdrawalService.proveWithdrawal(commitment, input);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Verifies a withdrawal proof.
|
|
84
|
+
*
|
|
85
|
+
* @param withdrawalProof - The withdrawal payload to verify
|
|
86
|
+
*/
|
|
87
|
+
public async verifyWithdrawal(
|
|
88
|
+
withdrawalProof: WithdrawalProof,
|
|
89
|
+
): Promise<boolean> {
|
|
90
|
+
return this.withdrawalService.verifyWithdrawal(withdrawalProof);
|
|
91
|
+
}
|
|
92
|
+
}
|