@fatsolutions/privacy-pools-core-starknet-sdk 0.0.40 → 0.0.42

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.
@@ -0,0 +1,42 @@
1
+ import { AccountService, PoolAccount } from "@0xbow/privacy-pools-core-sdk";
2
+ import { StarknetDataService } from "./data.service.js";
3
+ import { AuditorData, deriveKeyFromMnemonic,packAuditorData, computeTag, encrypt } from "./auditor.js";
4
+ import { base64 } from "@scure/base"
5
+
6
+ type AccountServiceConfig = Exclude<ConstructorParameters<typeof AccountService>[1], {account:any}>
7
+
8
+ export class StarknetAccountService extends AccountService {
9
+ private mnemonic: string;
10
+ constructor(dataService: StarknetDataService, config: AccountServiceConfig){
11
+ super(dataService as never, config);
12
+ this.mnemonic = config.mnemonic;
13
+ }
14
+
15
+ createAuditorData(
16
+ { label, deposit, children }: PoolAccount,
17
+ withdrawalValue?: bigint
18
+ ): {
19
+ auditorData: AuditorData;
20
+ withdrawalSecrets: ReturnType<StarknetAccountService['createWithdrawalSecrets']>;
21
+ } {
22
+ const viewKey = deriveKeyFromMnemonic(this.mnemonic, label);
23
+ const accountCommitment = children.at(-1) || deposit;
24
+ const withdrawalIndex = children.length;
25
+
26
+ const withdrawalSecrets = this.createWithdrawalSecrets(accountCommitment);
27
+
28
+ const packedData = packAuditorData({
29
+ value: withdrawalValue || accountCommitment.value,
30
+ label,
31
+ ...withdrawalSecrets,
32
+ });
33
+
34
+ const cipherArray = encrypt(packedData, viewKey);
35
+ const ciphertext = base64.encode(cipherArray);
36
+
37
+ const tag = computeTag(viewKey, withdrawalIndex);
38
+ const auditorData: AuditorData = { tag, ciphertext };
39
+ return { auditorData, withdrawalSecrets };
40
+ }
41
+ }
42
+
package/src/auditor.ts ADDED
@@ -0,0 +1,223 @@
1
+ import { xchacha20poly1305 } from "@noble/ciphers/chacha.js";
2
+ import { hkdf } from "@noble/hashes/hkdf.js";
3
+ import { sha3_256 as sha256 } from "@noble/hashes/sha3.js";
4
+ import { bytesToNumberBE, numberToBytesBE } from "@noble/ciphers/utils.js";
5
+ import { Secret, getCommitment, AccountCommitment, Hash, PoolInfo } from "@0xbow/privacy-pools-core-sdk";
6
+ import { CairoOption, CairoOptionVariant } from "starknet";
7
+ import { poseidonHashMany } from "@scure/starknet";
8
+ import { StarknetDataService } from "./data.service.js";
9
+ import { base64 } from "@scure/base";
10
+ import { mnemonicToEntropy } from "@scure/bip39";
11
+ import { wordlist } from '@scure/bip39/wordlists/english';
12
+
13
+ export interface AuditEvent {
14
+ tag: Hash;
15
+ ciphertext: string;
16
+ prevNullifierHash: Hash;
17
+ newCommitment: Hash;
18
+ blockNumber: bigint;
19
+ transactionHash: string;
20
+ }
21
+
22
+ //TODO: The first time you will need to generate a viewing key is in the first withdraw
23
+ // of a deposited note. At that point you allready have a label. I suggest for the viewKey to be derivable
24
+ // from the account secret + deposit label. There is no need to store another secret for each deposit.
25
+ // viewKey =~ Hash(AccountSecret, Label);
26
+ export function deriveKey(secret: Uint8Array, label: bigint): Uint8Array {
27
+ const info = new TextEncoder().encode("xchacha-key");
28
+ const salt = new TextEncoder().encode(String(label));
29
+ const viewKey = hkdf(sha256, secret, salt, info, 32);
30
+ return viewKey;
31
+ }
32
+
33
+ export function deriveKeyFromMnemonic(mnemonic: string, label: bigint): Uint8Array {
34
+ const secret = mnemonicToEntropy(mnemonic, wordlist);
35
+ return deriveKey(secret, label);
36
+ }
37
+
38
+ export function encrypt(plaintext: Uint8Array, viewKey: Uint8Array) {
39
+ const nonce = crypto.getRandomValues(new Uint8Array(24));
40
+
41
+ const chacha = xchacha20poly1305(viewKey, nonce);
42
+ const ciphertext = chacha.encrypt(plaintext);
43
+
44
+ // Prepend the nonce
45
+ const output = new Uint8Array(24 + ciphertext.length);
46
+ output.set(nonce, 0);
47
+ output.set(ciphertext, 24);
48
+
49
+ return output;
50
+ }
51
+
52
+ export function decrypt(input: Uint8Array, viewKey: Uint8Array): Uint8Array {
53
+ const nonce = input.slice(0, 24);
54
+ const chacha = xchacha20poly1305(viewKey, nonce);
55
+
56
+ const ciphertext = input.slice(24);
57
+ //TODO: What happens if AE fails?
58
+ const decrypted = chacha.decrypt(ciphertext);
59
+
60
+ return decrypted;
61
+ }
62
+
63
+ export function packAuditorData({
64
+ label: declaredLabel,
65
+ value: declaredValue,
66
+ nullifier: declaredNullifier,
67
+ secret: declaredSecret
68
+ }: Pick<AccountCommitment, 'label' | 'value' | 'nullifier' | 'secret'>,
69
+ ): Uint8Array {
70
+
71
+ //TODO: Add assertions about lenght of bigint
72
+ const output = new Uint8Array(128);
73
+ output.set(numberToBytesBE(declaredLabel, 32), 0);
74
+ output.set(numberToBytesBE(declaredValue, 32), 32);
75
+ output.set(numberToBytesBE(declaredNullifier, 32), 64);
76
+ output.set(numberToBytesBE(declaredSecret, 32), 96);
77
+
78
+ return output;
79
+ }
80
+
81
+ export function unpackAuditorData(packedData: Uint8Array)
82
+ : {
83
+ declaredLabel: bigint,
84
+ declaredValue: bigint,
85
+ declaredNullifier: bigint,
86
+ declaredSecret: bigint;
87
+ } {
88
+ const declaredLabel = bytesToNumberBE(packedData.slice(0, 32));
89
+ const declaredValue = bytesToNumberBE(packedData.slice(32, 64));
90
+ const declaredNullifier = bytesToNumberBE(packedData.slice(64, 96));
91
+ const declaredSecret = bytesToNumberBE(packedData.slice(96, 128));
92
+
93
+ return { declaredLabel, declaredValue, declaredNullifier, declaredSecret };
94
+ }
95
+
96
+
97
+ export function reconstructAccountCommitment(event: AuditEvent, viewKey: Uint8Array): AccountCommitment {
98
+ const { ciphertext, blockNumber, transactionHash, prevNullifierHash } = event;
99
+
100
+ const auditorData = decrypt(base64.decode(ciphertext), viewKey);
101
+ const { declaredLabel, declaredValue, declaredNullifier, declaredSecret } = unpackAuditorData(auditorData);
102
+
103
+ const {
104
+ hash,
105
+ nullifierHash,
106
+ preimage: { value: _value, label, precommitment: _precommitment }
107
+ } = getCommitment(declaredValue, declaredLabel, declaredNullifier as Secret, declaredSecret as Secret);
108
+
109
+ if (nullifierHash != prevNullifierHash) {
110
+ throw new Error("Inconsistency in emitted nullifier hash");
111
+ }
112
+
113
+ return {
114
+ hash,
115
+ value: declaredValue,
116
+ label: label as Hash,
117
+ nullifier: declaredNullifier as Secret,
118
+ secret: declaredSecret as Secret,
119
+ blockNumber,
120
+ txHash: transactionHash as `0x{string}`,
121
+ };
122
+ }
123
+
124
+ export function computeTag(viewKey: Uint8Array, withdraw_number: number): bigint {
125
+ const viewKeyBigInt = bytesToNumberBE(viewKey);
126
+ const tag = poseidonHashMany([viewKeyBigInt, BigInt(withdraw_number)]);
127
+ return tag;
128
+ };
129
+
130
+
131
+ async function fetchAuditEventsForViewKey(
132
+ viewKey: Uint8Array,
133
+ serviceData: StarknetDataService,
134
+ pool: PoolInfo,
135
+ ): Promise<[AuditEvent, number][]> {
136
+ let out: [AuditEvent, number][] = [];
137
+ let withdraw_number = 0;
138
+ while (true) {
139
+ let tag = computeTag(viewKey, withdraw_number);
140
+ let events = await serviceData.getAuditEventForTag(tag, pool);
141
+ if (!events) { break; };
142
+
143
+ //It should be only ONE event with this tag. But it is not eforced. A troll could make a withdraw with
144
+ //the tag of a previous withraw from another label.
145
+ events.forEach(event => out.push([event, withdraw_number]));
146
+ withdraw_number++;
147
+ }
148
+ return out;
149
+ }
150
+
151
+ export async function constructSequence(
152
+ viewKey: Uint8Array,
153
+ serviceData: StarknetDataService,
154
+ pool: PoolInfo
155
+ ): Promise<AccountCommitment[]> {
156
+ const commitments: AccountCommitment[] = [];
157
+ const events = await fetchAuditEventsForViewKey(viewKey, serviceData, pool);
158
+
159
+ const events_for_index = events.filter(([_, b]) => b === 0);
160
+ if (events_for_index.length === 0) {
161
+ throw new Error("No withdraws for this key");
162
+ }
163
+
164
+ let declaredCommitment: AccountCommitment | undefined;
165
+
166
+ for (const [event, _withdraw_number] of events_for_index) {
167
+ try {
168
+ declaredCommitment = reconstructAccountCommitment(event, viewKey);
169
+ } catch {
170
+ //What should we do here?
171
+ }
172
+ }
173
+
174
+ if (!declaredCommitment) {
175
+ throw new Error("No event could be decrypted");
176
+ }
177
+
178
+
179
+ let initialLabel = declaredCommitment.label;
180
+
181
+ let deposit = await serviceData.getDeposits(pool, pool.deploymentBlock, initialLabel);
182
+ if (deposit.length != 1) {
183
+ throw new Error("No deposit found for this label");
184
+ }
185
+
186
+ let initialCommitment = deposit[0]!.commitment;
187
+ if (initialCommitment != declaredCommitment.hash) {
188
+ throw new Error("mismatch");
189
+ }
190
+ commitments.push(declaredCommitment);
191
+
192
+ for (let i = 1; i < events.length; i++) {
193
+ let events_for_index = events.filter(([_, b]) => b === i);
194
+
195
+ for (const [event, _withdraw_number] of events_for_index) {
196
+ try {
197
+ declaredCommitment = reconstructAccountCommitment(event, viewKey);
198
+ } catch {
199
+ //What should we do here?
200
+ }
201
+ }
202
+
203
+ if (initialLabel != declaredCommitment.label) {
204
+ throw new Error("mismatch");
205
+ }
206
+
207
+ if (commitments[i - 1]!.hash != declaredCommitment.hash) {
208
+ throw new Error("mismatch");
209
+ }
210
+
211
+ commitments.push(declaredCommitment);
212
+ }
213
+
214
+ return commitments;
215
+ }
216
+
217
+ /**
218
+ * Represents a AuditEvent event from a privacy pool
219
+ */
220
+ export interface AuditorData {
221
+ tag: bigint;
222
+ ciphertext: string;
223
+ }
@@ -8,7 +8,7 @@ import { ETH_ADDRESS } from "../constants.js";
8
8
  import { SNContractError, SNPoolError, StarknetSDKError } from "../errors/index.js";
9
9
  import { AssetConfig, Withdrawal } from "../types/entrypoint.js";
10
10
  import { StarknetAddress } from "../types/starknet.js";
11
- import { castBigInt, toAddress } from "../utils.js";
11
+ import { castBigInt, toAddress, toCairoOption } from "../utils.js";
12
12
  import { executeIntent, simulateIntent } from "./transactionHandler.js";
13
13
 
14
14
  /**
@@ -251,8 +251,9 @@ export class SNContractInteractionsService {
251
251
  */
252
252
  withdraw(withdrawal: Withdrawal, withdrawalProofGaraga: bigint[], scope: Hash, options: OptionModeExecute): Promise<InvokeFunctionResponse>;
253
253
  async withdraw(withdrawal: Withdrawal, withdrawalProofGaraga: bigint[], scope: Hash, options?: OptionModes): Promise<CallResult> {
254
+ const _withdrawal = { ...withdrawal, auditorData: toCairoOption(withdrawal.auditorData) };
254
255
  const { poolAddress } = await this.getScopeData(scope);
255
- const call = this.pool(poolAddress).populate("withdraw", [withdrawal, { fullProof: withdrawalProofGaraga }]);
256
+ const call = this.pool(poolAddress).populate("withdraw", [_withdrawal, { fullProof: withdrawalProofGaraga }]);
256
257
  return _intentOptionWrapper(call, options && { ...options, account: this.providerOrAccount });
257
258
  }
258
259
 
@@ -286,7 +287,8 @@ export class SNContractInteractionsService {
286
287
  */
287
288
  relay(withdrawal: Withdrawal, withdrawalProofGaraga: bigint[], scope: Hash, options: OptionModeExecute): Promise<InvokeFunctionResponse>;
288
289
  relay(withdrawal: Withdrawal, withdrawalProofGaraga: bigint[], scope: Hash, options?: OptionModes): Promise<CallResult> {
289
- const call = this.entrypoint.populate("relay", [withdrawal, scope, { fullProof: withdrawalProofGaraga }]);
290
+ const _withdrawal = { ...withdrawal, auditorData: toCairoOption(withdrawal.auditorData) };
291
+ const call = this.entrypoint.populate("relay", [_withdrawal, scope, { fullProof: withdrawalProofGaraga }]);
290
292
  return _intentOptionWrapper(call, options && { ...options, account: this.providerOrAccount });
291
293
  }
292
294
 
@@ -5,6 +5,7 @@ import {
5
5
  RagequitEvent,
6
6
  WithdrawalEvent,
7
7
  } from "@0xbow/privacy-pools-core-sdk";
8
+ import { AuditEvent } from "./auditor.js"
8
9
  import {
9
10
  AbiParser2,
10
11
  EmittedEvent,
@@ -21,6 +22,7 @@ import {
21
22
  StarknetSDKError,
22
23
  } from "./errors/index.js";
23
24
 
25
+
24
26
  // Calculate event selectors using Starknet keccak
25
27
  export const DEPOSIT_EVENT_SELECTOR = num.toHex(
26
28
  hash.starknetKeccak("Deposited")
@@ -32,15 +34,21 @@ export const RAGEQUIT_EVENT_SELECTOR = num.toHex(
32
34
  hash.starknetKeccak("RageQuit")
33
35
  ); // 0x2a2469e4569da9d3c3df9bc1a2ddece1e640a89b8ecd537da96f064d64c8183
34
36
 
37
+ export const AUDIT_EVENT_SELECTOR = num.toHex(
38
+ hash.starknetKeccak("AuditEvent")
39
+ );
40
+
35
41
  // ABI event names for direct selection
36
42
  export const DEPOSIT_EVENT_ABI_NAME = "privacy_pools::interfaces::IPool::Deposited";
37
43
  export const WITHDRAWAL_EVENT_ABI_NAME = "privacy_pools::interfaces::IPool::Withdrawn";
38
44
  export const RAGEQUIT_EVENT_ABI_NAME = "privacy_pools::interfaces::IPool::RageQuit";
45
+ export const AUDIT_EVENT_ABI_NAME = "privacy_pools::interfaces::IPool::AuditEvent";
39
46
 
40
47
  export const AbiEventName = {
41
48
  Deposit: DEPOSIT_EVENT_ABI_NAME,
42
49
  Withdraw: WITHDRAWAL_EVENT_ABI_NAME,
43
50
  Ragequit: RAGEQUIT_EVENT_ABI_NAME,
51
+ Audit: AUDIT_EVENT_ABI_NAME,
44
52
  } as const;
45
53
 
46
54
  type AbiEventName = (typeof AbiEventName)[keyof typeof AbiEventName];
@@ -59,6 +67,7 @@ export interface StarknetWithdrawnEventData {
59
67
  newCommitmentHash: bigint;
60
68
  withdrawnValue: bigint;
61
69
  existingNullifierHash: bigint;
70
+ auditorData: string;
62
71
  }
63
72
 
64
73
  export interface StarknetRageQuitEventData {
@@ -68,15 +77,24 @@ export interface StarknetRageQuitEventData {
68
77
  value: bigint;
69
78
  }
70
79
 
80
+ export interface StarknetAuditEventData {
81
+ tag: bigint;
82
+ ciphertext:string;
83
+ prevNullifierHash: bigint,
84
+ newCommitment: bigint,
85
+ }
86
+
71
87
  type AbiToEvent = {
72
88
  [AbiEventName.Deposit]: StarknetDepositedEventData;
73
89
  [AbiEventName.Withdraw]: StarknetWithdrawnEventData;
74
90
  [AbiEventName.Ragequit]: StarknetRageQuitEventData;
91
+ [AbiEventName.Audit]: StarknetAuditEventData;
75
92
  };
76
93
 
77
94
  export type StarknetEvent =
78
95
  | StarknetDepositedEventData
79
96
  | StarknetWithdrawnEventData
97
+ | StarknetAuditEventData
80
98
  | StarknetRageQuitEventData;
81
99
 
82
100
  /**
@@ -123,15 +141,17 @@ export class StarknetDataService {
123
141
  */
124
142
  async getDeposits(
125
143
  pool: PoolInfo,
126
- fromBlock: bigint = pool.deploymentBlock
144
+ fromBlock: bigint = pool.deploymentBlock,
145
+ label?: bigint,
127
146
  ): Promise<DepositEvent[]> {
128
147
  try {
148
+ const keys = label? [[DEPOSIT_EVENT_SELECTOR],[],[String(label)]] : [[DEPOSIT_EVENT_SELECTOR]];
129
149
  // Fetch events using Starknet provider
130
150
  const eventsResult = await this.getEvents({
131
151
  from_block: { block_number: Number(fromBlock) },
132
152
  to_block: "latest",
133
153
  address: pool.address,
134
- keys: [[DEPOSIT_EVENT_SELECTOR]], // Filter by deposit event selector
154
+ keys, // Filter by deposit event selector
135
155
  chunk_size: 1000, // Starknet pagination
136
156
  });
137
157
 
@@ -293,6 +313,63 @@ export class StarknetDataService {
293
313
  transactionHash: transactionHash || "0x0",
294
314
  } as RagequitEvent)
295
315
  );
316
+ // PARSEAR
317
+ } catch (error) {
318
+ if (error instanceof StarknetSDKError) throw error;
319
+ throw new StarknetSDKError(
320
+ `Failed to fetch ragequits: ${error instanceof Error ? error.message : "Unknown error"
321
+ }`,
322
+ SNBaseErrorCode.UNKNOWN
323
+ );
324
+ }
325
+ }
326
+
327
+ async getAuditEventForTag(
328
+ tag: bigint,
329
+ pool: PoolInfo,
330
+ fromBlock: bigint = pool.deploymentBlock
331
+ ) {
332
+ try {
333
+ const eventsResult = await this.getEvents({
334
+ from_block: { block_number: Number(fromBlock) },
335
+ to_block: "latest",
336
+ address: pool.address,
337
+ keys: [[AUDIT_EVENT_SELECTOR], [String(tag)]],
338
+ chunk_size: 1000,
339
+ });
340
+
341
+ // Parse all events at once using starknet.js event parser
342
+ const abiEvents = events.getAbiEvents(PrivacyPoolABI);
343
+ const parsedEvents = events.parseEvents(
344
+ eventsResult,
345
+ abiEvents,
346
+ {},
347
+ {},
348
+ this.abiParser
349
+ );
350
+
351
+ return parsedEvents
352
+ .filter(
353
+ (parsedEvent) => parsedEvent[AUDIT_EVENT_ABI_NAME] !== undefined
354
+ )
355
+ .map((parsedEvent) => ({
356
+ eventData: parsedEvent[
357
+ AUDIT_EVENT_ABI_NAME
358
+ ] as unknown as StarknetAuditEventData,
359
+ blockNumber: parsedEvent.block_number,
360
+ transactionHash: parsedEvent.transaction_hash,
361
+ }))
362
+ .map(
363
+ ({ eventData, blockNumber, transactionHash }) =>
364
+ ({
365
+ tag: eventData.tag as Hash,
366
+ ciphertext: eventData.ciphertext,
367
+ prevNullifierHash: eventData.prevNullifierHash as Hash,
368
+ newCommitment: eventData.newCommitment as Hash,
369
+ blockNumber: BigInt(blockNumber || 0),
370
+ transactionHash: transactionHash || "0x0",
371
+ } as AuditEvent)
372
+ );
296
373
  } catch (error) {
297
374
  if (error instanceof StarknetSDKError) throw error;
298
375
  throw new StarknetSDKError(
package/src/index.ts CHANGED
@@ -85,3 +85,8 @@ export {
85
85
  } from "./data.service.js";
86
86
 
87
87
  export { type StarknetAddress } from "./types/starknet.js";
88
+
89
+ // Auditor
90
+ export * from "./auditor.js";
91
+
92
+ export * from './account.service.js';
@@ -1,8 +1,9 @@
1
- import { BigNumberish, cairo, CallData, num } from "starknet";
1
+ import { BigNumberish, cairo, CallData, num, CairoOption, byteArray } from "starknet";
2
2
 
3
3
  import { EntryPointABI } from "../abis/index.js";
4
4
  import { SNFormatError } from "../errors/index.js";
5
5
  import { StarknetAddress } from "./starknet.js";
6
+ import { AuditorData } from "../auditor.js";
6
7
 
7
8
  const RelayDataAbi = [
8
9
  {
@@ -46,6 +47,7 @@ const entrypointCd = new CallData(EntryPointABI);
46
47
  export interface Withdrawal {
47
48
  /** The address of the processor handling this withdrawal */
48
49
  processor: BigNumberish,
50
+ auditorData?: AuditorData;
49
51
  /** Additional data required for processing the withdrawal */
50
52
  data: BigNumberish[];
51
53
  }
@@ -113,18 +115,48 @@ export function parseRelayData(rawData: string[]): RelayData {
113
115
  * @returns Array of string representations of the withdrawal fields
114
116
  */
115
117
  export function serializeWithdrawal(withdrawal: Withdrawal): `0x${string}`[] {
116
- const { processor, data } = withdrawal;
117
- return [
118
- num.toHex64(processor) as `0x${string}`,
119
- num.toHex64(data.length) as `0x${string}`,
120
- ...data.map(x => num.toHex64(x) as `0x${string}`)
121
- ];
118
+ let result: `0x${string}`[] = [];
119
+ const { processor, auditorData, data } = withdrawal;
120
+
121
+ result.push(num.toHex64(processor) as `0x${string}`);
122
+
123
+ if (auditorData === undefined) {
124
+ result.push(num.toHex64(1) as `0x${string}`);
125
+ } else {
126
+ result.push(num.toHex64(0) as `0x${string}`);
127
+ const { tag, ciphertext } = auditorData;
128
+ const { low, high } = cairo.uint256(tag);
129
+ result.push(num.toHex64(low) as `0x${string}`);
130
+ result.push(num.toHex64(high) as `0x${string}`);
131
+
132
+ const array = CallData.compile([byteArray.byteArrayFromString(ciphertext)]);
133
+ array.forEach((x) => result.push(num.toHex64(x) as `0x${string}`));
134
+ }
135
+
136
+ result.push(num.toHex64(data.length) as `0x${string}`);
137
+ data.forEach((x) => result.push(num.toHex64(x) as `0x${string}`));
138
+ return result;
122
139
  }
123
140
 
124
- function isWithdrawal(data: unknown): data is Withdrawal {
125
- const _data = data as Withdrawal;
126
- return (_data.processor !== undefined && num.isBigNumberish(_data.processor))
127
- && (_data.data !== undefined && _data.data.every(num.isBigNumberish));
141
+ type CairoWithdrawal = {
142
+ processor: BigNumberish;
143
+ data: BigNumberish[];
144
+ auditorData: CairoOption<AuditorData>;
145
+ };
146
+ function isWithdrawal(data: unknown): data is CairoWithdrawal {
147
+ const _data = data as CairoWithdrawal;
148
+ return (
149
+ _data.processor !== undefined &&
150
+ num.isBigNumberish(_data.processor)
151
+ ) && (
152
+ _data.data !== undefined &&
153
+ _data.data.every(num.isBigNumberish)
154
+ ) && (
155
+ (
156
+ _data.auditorData.Some?.ciphertext !== undefined &&
157
+ _data.auditorData.Some?.tag !== undefined
158
+ ) || _data.auditorData.None !== undefined
159
+ );
128
160
  }
129
161
 
130
162
  /**
@@ -140,7 +172,8 @@ export function parseWithdrawal(rawData: string[]): Withdrawal {
140
172
  }
141
173
  const structName = "privacy_pools::interfaces::Structs::Withdrawal";
142
174
  const result = entrypointCd.decodeParameters(structName, rawData);
143
- if (isWithdrawal(result))
144
- return result;
175
+ if (isWithdrawal(result)) {
176
+ return { ...result, auditorData: result.auditorData.unwrap() };
177
+ }
145
178
  throw SNFormatError.parse(`Can't parse Withdrawal from ${rawData}`);
146
179
  }
package/src/utils.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { WithdrawalProof } from "@0xbow/privacy-pools-core-sdk";
2
2
  import { poseidonHashMany } from "@scure/starknet";
3
- import { BigNumberish, cairo, num, uint256, Uint256, validateAndParseAddress, validateChecksumAddress } from "starknet";
3
+ import { BigNumberish, cairo, CairoOption, CairoOptionVariant, num, uint256, Uint256, validateAndParseAddress, validateChecksumAddress } from "starknet";
4
4
 
5
5
  import { getGroth16CallData } from "./garaga.js";
6
6
  import { snarkJsKeyIntoGaraga, snarkJsProofIntoGaraga } from "./types/conversions.js";
@@ -94,3 +94,15 @@ export function castBigInt(x: number | bigint | Uint256) {
94
94
  export function toAddress(x: BigNumberish): StarknetAddress {
95
95
  return validateAndParseAddress(x) as StarknetAddress;
96
96
  }
97
+
98
+ export const Some = <T>(t: T): CairoOption<T> => {
99
+ return new CairoOption<T>(CairoOptionVariant.Some, t);
100
+ };
101
+
102
+ export const None = <T>(): CairoOption<T> => {
103
+ return new CairoOption<T>(CairoOptionVariant.None);
104
+ };
105
+
106
+ export const toCairoOption = <T>(t?: T): CairoOption<T> => {
107
+ return t === undefined ? None() : Some(t);
108
+ };