@fatsolutions/privacy-pools-core-starknet-sdk 0.0.41 → 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.
- package/dist/abis/EntryPoint.abi.d.ts +23 -0
- package/dist/abis/EntryPoint.abi.js +32 -0
- package/dist/abis/EntryPoint.abi.js.map +1 -1
- package/dist/abis/PrivacyPool.abi.d.ts +66 -5
- package/dist/abis/PrivacyPool.abi.js +87 -5
- package/dist/abis/PrivacyPool.abi.js.map +1 -1
- package/dist/account.service.d.ts +15 -0
- package/dist/account.service.js +27 -0
- package/dist/account.service.js.map +1 -0
- package/dist/auditor.d.ts +31 -0
- package/dist/auditor.js +148 -0
- package/dist/auditor.js.map +1 -0
- package/dist/contracts/contracts.service.js +5 -3
- package/dist/contracts/contracts.service.js.map +1 -1
- package/dist/data.service.d.ts +15 -2
- package/dist/data.service.js +41 -2
- package/dist/data.service.js.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -1
- package/dist/types/entrypoint.d.ts +2 -0
- package/dist/types/entrypoint.js +26 -11
- package/dist/types/entrypoint.js.map +1 -1
- package/dist/utils.d.ts +4 -1
- package/dist/utils.js +10 -1
- package/dist/utils.js.map +1 -1
- package/package.json +7 -3
- package/src/abis/EntryPoint.abi.ts +925 -893
- package/src/abis/PrivacyPool.abi.ts +607 -525
- package/src/account.service.ts +42 -0
- package/src/auditor.ts +223 -0
- package/src/contracts/contracts.service.ts +5 -3
- package/src/data.service.ts +79 -2
- package/src/index.ts +5 -0
- package/src/types/entrypoint.ts +46 -13
- package/src/utils.ts +13 -1
|
@@ -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", [
|
|
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
|
|
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
|
|
package/src/data.service.ts
CHANGED
|
@@ -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
|
|
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
package/src/types/entrypoint.ts
CHANGED
|
@@ -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
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
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
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
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
|
+
};
|