@permissionless-technologies/upp-sdk 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (140) hide show
  1. package/LICENSE +661 -0
  2. package/README.md +194 -0
  3. package/dist/asp-TXSAFFD3.cjs +53 -0
  4. package/dist/asp-TXSAFFD3.cjs.map +1 -0
  5. package/dist/asp-ZA3RGN7G.js +4 -0
  6. package/dist/asp-ZA3RGN7G.js.map +1 -0
  7. package/dist/babyjubjub-2MGQVCKB.js +5 -0
  8. package/dist/babyjubjub-2MGQVCKB.js.map +1 -0
  9. package/dist/babyjubjub-MWZLJOVZ.cjs +66 -0
  10. package/dist/babyjubjub-MWZLJOVZ.cjs.map +1 -0
  11. package/dist/chunk-2JQISXBD.js +150 -0
  12. package/dist/chunk-2JQISXBD.js.map +1 -0
  13. package/dist/chunk-3HQ7A6ZM.cjs +61 -0
  14. package/dist/chunk-3HQ7A6ZM.cjs.map +1 -0
  15. package/dist/chunk-5AKBSMEQ.cjs +1008 -0
  16. package/dist/chunk-5AKBSMEQ.cjs.map +1 -0
  17. package/dist/chunk-5V5HSN6Y.js +81 -0
  18. package/dist/chunk-5V5HSN6Y.js.map +1 -0
  19. package/dist/chunk-BH24DZ5S.cjs +91 -0
  20. package/dist/chunk-BH24DZ5S.cjs.map +1 -0
  21. package/dist/chunk-C7QQOJ7T.cjs +67 -0
  22. package/dist/chunk-C7QQOJ7T.cjs.map +1 -0
  23. package/dist/chunk-ERQE57IA.cjs +404 -0
  24. package/dist/chunk-ERQE57IA.cjs.map +1 -0
  25. package/dist/chunk-EUP7MBAH.cjs +165 -0
  26. package/dist/chunk-EUP7MBAH.cjs.map +1 -0
  27. package/dist/chunk-G7VZBCD6.cjs +35 -0
  28. package/dist/chunk-G7VZBCD6.cjs.map +1 -0
  29. package/dist/chunk-GQV47S3N.cjs +10 -0
  30. package/dist/chunk-GQV47S3N.cjs.map +1 -0
  31. package/dist/chunk-GXZ3MTCQ.cjs +527 -0
  32. package/dist/chunk-GXZ3MTCQ.cjs.map +1 -0
  33. package/dist/chunk-JWNXBALH.cjs +57 -0
  34. package/dist/chunk-JWNXBALH.cjs.map +1 -0
  35. package/dist/chunk-KIKBPJXJ.cjs +348 -0
  36. package/dist/chunk-KIKBPJXJ.cjs.map +1 -0
  37. package/dist/chunk-NCW4AE7L.js +8 -0
  38. package/dist/chunk-NCW4AE7L.js.map +1 -0
  39. package/dist/chunk-NDM5EJEV.cjs +70 -0
  40. package/dist/chunk-NDM5EJEV.cjs.map +1 -0
  41. package/dist/chunk-NUIQHTSA.js +489 -0
  42. package/dist/chunk-NUIQHTSA.js.map +1 -0
  43. package/dist/chunk-OQDSHMXU.js +1002 -0
  44. package/dist/chunk-OQDSHMXU.js.map +1 -0
  45. package/dist/chunk-P37MRZ73.js +58 -0
  46. package/dist/chunk-P37MRZ73.js.map +1 -0
  47. package/dist/chunk-PWHOUQOZ.js +335 -0
  48. package/dist/chunk-PWHOUQOZ.js.map +1 -0
  49. package/dist/chunk-S4B7GYLN.js +112 -0
  50. package/dist/chunk-S4B7GYLN.js.map +1 -0
  51. package/dist/chunk-SGZZL5AC.js +59 -0
  52. package/dist/chunk-SGZZL5AC.js.map +1 -0
  53. package/dist/chunk-SQKBT2SH.cjs +122 -0
  54. package/dist/chunk-SQKBT2SH.cjs.map +1 -0
  55. package/dist/chunk-TSF6HEVS.cjs +201 -0
  56. package/dist/chunk-TSF6HEVS.cjs.map +1 -0
  57. package/dist/chunk-V23OSL25.js +48 -0
  58. package/dist/chunk-V23OSL25.js.map +1 -0
  59. package/dist/chunk-W77GRBO4.js +53 -0
  60. package/dist/chunk-W77GRBO4.js.map +1 -0
  61. package/dist/chunk-XV72HNHN.js +399 -0
  62. package/dist/chunk-XV72HNHN.js.map +1 -0
  63. package/dist/chunk-YOWDERVC.js +186 -0
  64. package/dist/chunk-YOWDERVC.js.map +1 -0
  65. package/dist/chunk-Z6ZWNWWR.js +30 -0
  66. package/dist/chunk-Z6ZWNWWR.js.map +1 -0
  67. package/dist/chunk-ZKZV6OI3.cjs +165 -0
  68. package/dist/chunk-ZKZV6OI3.cjs.map +1 -0
  69. package/dist/chunk-ZU6J7KMY.js +159 -0
  70. package/dist/chunk-ZU6J7KMY.js.map +1 -0
  71. package/dist/core/index.cjs +300 -0
  72. package/dist/core/index.cjs.map +1 -0
  73. package/dist/core/index.d.cts +9 -0
  74. package/dist/core/index.d.ts +9 -0
  75. package/dist/core/index.js +11 -0
  76. package/dist/core/index.js.map +1 -0
  77. package/dist/index-BBzvvrhG.d.ts +757 -0
  78. package/dist/index-BGvapsJy.d.cts +2811 -0
  79. package/dist/index-C-jSNw6j.d.cts +757 -0
  80. package/dist/index-ChGaGPzP.d.ts +2811 -0
  81. package/dist/index.cjs +3652 -0
  82. package/dist/index.cjs.map +1 -0
  83. package/dist/index.d.cts +12 -0
  84. package/dist/index.d.ts +12 -0
  85. package/dist/index.js +3112 -0
  86. package/dist/index.js.map +1 -0
  87. package/dist/indexer/index.cjs +58 -0
  88. package/dist/indexer/index.cjs.map +1 -0
  89. package/dist/indexer/index.d.cts +206 -0
  90. package/dist/indexer/index.d.ts +206 -0
  91. package/dist/indexer/index.js +5 -0
  92. package/dist/indexer/index.js.map +1 -0
  93. package/dist/keccak-m31-B_AqBbRF.d.cts +70 -0
  94. package/dist/keccak-m31-B_AqBbRF.d.ts +70 -0
  95. package/dist/keys/index.cjs +68 -0
  96. package/dist/keys/index.cjs.map +1 -0
  97. package/dist/keys/index.d.cts +158 -0
  98. package/dist/keys/index.d.ts +158 -0
  99. package/dist/keys/index.js +7 -0
  100. package/dist/keys/index.js.map +1 -0
  101. package/dist/merkle-7KS2EHRF.js +5 -0
  102. package/dist/merkle-7KS2EHRF.js.map +1 -0
  103. package/dist/merkle-HGDC6OB4.cjs +30 -0
  104. package/dist/merkle-HGDC6OB4.cjs.map +1 -0
  105. package/dist/merkle-mteVOlDf.d.cts +188 -0
  106. package/dist/merkle-mteVOlDf.d.ts +188 -0
  107. package/dist/poseidon-UHTJLWQM.js +7 -0
  108. package/dist/poseidon-UHTJLWQM.js.map +1 -0
  109. package/dist/poseidon-WHJSZSNP.cjs +45 -0
  110. package/dist/poseidon-WHJSZSNP.cjs.map +1 -0
  111. package/dist/proof-5OECB3RQ.cjs +45 -0
  112. package/dist/proof-5OECB3RQ.cjs.map +1 -0
  113. package/dist/proof-C4YBP6RY.js +4 -0
  114. package/dist/proof-C4YBP6RY.js.map +1 -0
  115. package/dist/react/index.cjs +2641 -0
  116. package/dist/react/index.cjs.map +1 -0
  117. package/dist/react/index.d.cts +757 -0
  118. package/dist/react/index.d.ts +757 -0
  119. package/dist/react/index.js +2598 -0
  120. package/dist/react/index.js.map +1 -0
  121. package/dist/transfer-2UDHDS7Q.cjs +37 -0
  122. package/dist/transfer-2UDHDS7Q.cjs.map +1 -0
  123. package/dist/transfer-BlmbO-Rd.d.ts +1270 -0
  124. package/dist/transfer-DKZuJnRM.d.cts +1270 -0
  125. package/dist/transfer-KTCXKHS4.js +8 -0
  126. package/dist/transfer-KTCXKHS4.js.map +1 -0
  127. package/dist/types-CJSbxv4q.d.cts +143 -0
  128. package/dist/types-mLybMxNR.d.ts +143 -0
  129. package/dist/utils/index.cjs +178 -0
  130. package/dist/utils/index.cjs.map +1 -0
  131. package/dist/utils/index.d.cts +88 -0
  132. package/dist/utils/index.d.ts +88 -0
  133. package/dist/utils/index.js +9 -0
  134. package/dist/utils/index.js.map +1 -0
  135. package/package.json +119 -0
  136. package/src/contracts/interfaces/IASPRegistry.sol +36 -0
  137. package/src/contracts/interfaces/IUniversalPrivatePool.sol +260 -0
  138. package/src/contracts/interfaces/IVerifiers.sol +68 -0
  139. package/src/deployments/11155111.json +19 -0
  140. package/src/deployments/31337.json +19 -0
@@ -0,0 +1,1002 @@
1
+ import { createAutoAdapter } from './chunk-XV72HNHN.js';
2
+ import { keccak256, toHex } from 'viem';
3
+
4
+ async function computeSearchTag(sdk, masterViewingSecret, masterViewingPubKey, ephemeralPubkey, debug = false) {
5
+ const dvk = await sdk.deriveDecryptionViewingKey(
6
+ masterViewingSecret,
7
+ masterViewingPubKey,
8
+ ephemeralPubkey.x
9
+ );
10
+ const viewingSharedSecret = await sdk.computeSharedSecret(dvk, ephemeralPubkey);
11
+ const searchTagHash = await sdk.poseidon([viewingSharedSecret.x, 0n]);
12
+ const searchTag = searchTagHash & (1n << 64n) - 1n;
13
+ if (debug) {
14
+ console.log(`[computeSearchTag] viewingSharedSecret.x: ${viewingSharedSecret.x}`);
15
+ console.log(`[computeSearchTag] searchTagHash: ${searchTagHash}`);
16
+ console.log(`[computeSearchTag] searchTag (lower 64 bits): ${searchTag}`);
17
+ }
18
+ return searchTag;
19
+ }
20
+ async function matchesSearchTag(eventSearchTag, sdk, masterViewingSecret, masterViewingPubKey, ephemeralPubkey, debug = false) {
21
+ const expectedTag = await computeSearchTag(
22
+ sdk,
23
+ masterViewingSecret,
24
+ masterViewingPubKey,
25
+ ephemeralPubkey,
26
+ debug
27
+ );
28
+ if (debug) {
29
+ console.log(`[matchesSearchTag] eventSearchTag: ${eventSearchTag}, expectedTag: ${expectedTag}, match: ${eventSearchTag === expectedTag}`);
30
+ }
31
+ return eventSearchTag === expectedTag;
32
+ }
33
+ async function tryDecryptNote(sdk, keys, ephemeralPubkey, encryptedNote, expectedCommitment, debug = false) {
34
+ try {
35
+ const dvk = await sdk.deriveDecryptionViewingKey(
36
+ keys.masterViewingSecret,
37
+ keys.masterViewingPubKey,
38
+ ephemeralPubkey.x
39
+ );
40
+ const viewingSharedSecret = await sdk.computeSharedSecret(dvk, ephemeralPubkey);
41
+ const keyMaterial = keccak256(toHex(viewingSharedSecret.x, { size: 32 }));
42
+ const keyBytes = hexToBytes(keyMaterial);
43
+ const cryptoKey = await crypto.subtle.importKey(
44
+ "raw",
45
+ keyBytes.buffer.slice(keyBytes.byteOffset, keyBytes.byteOffset + keyBytes.byteLength),
46
+ { name: "AES-GCM", length: 256 },
47
+ false,
48
+ ["decrypt"]
49
+ );
50
+ const encryptedBytes = hexToBytes(encryptedNote);
51
+ const nonce = encryptedBytes.slice(0, 12);
52
+ const ciphertext = encryptedBytes.slice(12);
53
+ const plaintext = await crypto.subtle.decrypt({ name: "AES-GCM", iv: nonce }, cryptoKey, ciphertext);
54
+ const plaintextBytes = new Uint8Array(plaintext);
55
+ if (debug) {
56
+ console.log(`[tryDecryptNote] Decrypted plaintext length: ${plaintextBytes.length} bytes`);
57
+ }
58
+ const amount = bytesToBigint(plaintextBytes.slice(0, 32));
59
+ const blinding = bytesToBigint(plaintextBytes.slice(32, 64));
60
+ const origin = bytesToBigint(plaintextBytes.slice(64, 84));
61
+ const token = bytesToBigint(plaintextBytes.slice(84, 104));
62
+ const spendingSharedSecret = await sdk.computeSharedSecret(keys.spendingSecret, ephemeralPubkey);
63
+ const stealthScalar = await sdk.poseidon([spendingSharedSecret.x, spendingSharedSecret.y]);
64
+ const stealthOffset = await sdk.privateToPublic(stealthScalar);
65
+ const oneTimePubkey = await sdk.addPoints(keys.spendingPubkey, stealthOffset);
66
+ const subOrder = await sdk.getSubOrder();
67
+ const oneTimeSecret = (keys.spendingSecret + stealthScalar) % subOrder;
68
+ const computedCommitment = await sdk.poseidon([
69
+ amount,
70
+ oneTimePubkey.x,
71
+ oneTimePubkey.y,
72
+ blinding,
73
+ origin,
74
+ token
75
+ ]);
76
+ if (debug) {
77
+ console.log(`[tryDecryptNote] Decrypted values:`);
78
+ console.log(` amount: ${amount}`);
79
+ console.log(` blinding: ${blinding}`);
80
+ console.log(` origin: 0x${origin.toString(16).padStart(40, "0")}`);
81
+ console.log(` token: 0x${token.toString(16).padStart(40, "0")}`);
82
+ console.log(` oneTimePubkey.x: ${oneTimePubkey.x}`);
83
+ console.log(` oneTimePubkey.y: ${oneTimePubkey.y}`);
84
+ console.log(` computedCommitment: ${computedCommitment}`);
85
+ console.log(` expectedCommitment: ${expectedCommitment}`);
86
+ console.log(` match: ${computedCommitment === expectedCommitment}`);
87
+ }
88
+ if (computedCommitment !== expectedCommitment) {
89
+ if (debug) {
90
+ console.log(`[tryDecryptNote] Commitment mismatch - not our note`);
91
+ }
92
+ return null;
93
+ }
94
+ return { amount, blinding, oneTimeSecret, oneTimePubkey, origin, token };
95
+ } catch (e) {
96
+ if (debug) {
97
+ console.log(`[tryDecryptNote] Decryption exception:`, e instanceof Error ? e.message : e);
98
+ console.log(`[tryDecryptNote] encryptedNote hex length: ${encryptedNote.length} chars = ${(encryptedNote.length - 2) / 2} bytes`);
99
+ }
100
+ return null;
101
+ }
102
+ }
103
+ function unpackNoteData(packed, debug = false) {
104
+ try {
105
+ const bytes = hexToBytes(packed);
106
+ if (bytes.length < 100) {
107
+ if (debug) {
108
+ console.log(`[unpackNoteData] Packed data too short: ${bytes.length} bytes`);
109
+ }
110
+ return null;
111
+ }
112
+ const searchTag = bytesToBigint(bytes.slice(0, 8));
113
+ const ephemeralPubkeyX = bytesToBigint(bytes.slice(8, 40));
114
+ const ephemeralPubkeyY = bytesToBigint(bytes.slice(40, 72));
115
+ const encryptedNote = "0x" + Array.from(bytes.slice(72)).map((b) => b.toString(16).padStart(2, "0")).join("");
116
+ if (debug) {
117
+ console.log(`[unpackNoteData] Total packed bytes: ${bytes.length}, header: 72, encryptedNote: ${bytes.length - 72}`);
118
+ console.log(`[unpackNoteData] searchTag=${searchTag}, ephemeralPubkeyX=${ephemeralPubkeyX}`);
119
+ }
120
+ return { searchTag, ephemeralPubkeyX, ephemeralPubkeyY, encryptedNote };
121
+ } catch (e) {
122
+ if (debug) {
123
+ console.log(`[unpackNoteData] Failed to unpack:`, e);
124
+ }
125
+ return null;
126
+ }
127
+ }
128
+ function hexToBytes(hex) {
129
+ const h = hex.startsWith("0x") ? hex.slice(2) : hex;
130
+ const bytes = new Uint8Array(h.length / 2);
131
+ for (let i = 0; i < bytes.length; i++) {
132
+ bytes[i] = parseInt(h.substr(i * 2, 2), 16);
133
+ }
134
+ return bytes;
135
+ }
136
+ function bytesToBigint(bytes) {
137
+ let result = 0n;
138
+ for (let i = 0; i < bytes.length; i++) {
139
+ const byte = bytes[i];
140
+ if (byte !== void 0) {
141
+ result = result << 8n | BigInt(byte);
142
+ }
143
+ }
144
+ return result;
145
+ }
146
+ var DEFAULT_BATCH_SIZE = 200;
147
+ var MIN_BATCH_SIZE = 50;
148
+ var MAX_BATCH_SIZE = 2e3;
149
+ var BATCH_DELAY_MS = 100;
150
+ var DEFAULT_LIVE_SYNC_INTERVAL_MS = 3e4;
151
+ var DEFAULT_REQUESTS_PER_SECOND = 20;
152
+ var DEFAULT_MAX_CONCURRENT = 10;
153
+ var STORAGE_KEY_SYNC = "sync";
154
+ var STORAGE_KEY_NOTES = "notes";
155
+ var STORAGE_KEY_NULLIFIERS = "nullifiers";
156
+ var COMMITMENT_INSERTED_EVENT = {
157
+ type: "event",
158
+ name: "CommitmentInserted",
159
+ inputs: [
160
+ { indexed: true, name: "commitment", type: "bytes32" },
161
+ { indexed: false, name: "leafIndex", type: "uint256" },
162
+ { indexed: false, name: "timestamp", type: "uint256" }
163
+ ]
164
+ };
165
+ var STEALTH_TRANSACT_EVENT = {
166
+ type: "event",
167
+ name: "StealthTransact",
168
+ inputs: [
169
+ { indexed: true, name: "commitment", type: "bytes32" },
170
+ { indexed: true, name: "searchTag", type: "uint64" },
171
+ { indexed: false, name: "ephemeralPubkeyX", type: "uint256" },
172
+ { indexed: false, name: "ephemeralPubkeyY", type: "uint256" },
173
+ { indexed: false, name: "encryptedNote", type: "bytes" }
174
+ ]
175
+ };
176
+ var SHIELDED_EVENT = {
177
+ type: "event",
178
+ name: "Shielded",
179
+ inputs: [
180
+ { indexed: true, name: "token", type: "address" },
181
+ { indexed: true, name: "depositor", type: "address" },
182
+ { indexed: true, name: "commitment", type: "bytes32" },
183
+ { indexed: false, name: "leafIndex", type: "uint256" },
184
+ { indexed: false, name: "encryptedNote", type: "bytes" }
185
+ ]
186
+ };
187
+ var TRANSFERRED_EVENT = {
188
+ type: "event",
189
+ name: "Transferred",
190
+ inputs: [
191
+ { indexed: true, name: "nullifier", type: "bytes32" },
192
+ { indexed: true, name: "outputCommitment1", type: "bytes32" },
193
+ { indexed: true, name: "outputCommitment2", type: "bytes32" },
194
+ { indexed: false, name: "encryptedNote1", type: "bytes" },
195
+ { indexed: false, name: "encryptedNote2", type: "bytes" }
196
+ ]
197
+ };
198
+ var NULLIFIED_EVENT = {
199
+ type: "event",
200
+ name: "Nullified",
201
+ inputs: [{ indexed: true, name: "nullifier", type: "bytes32" }]
202
+ };
203
+ var WITHDRAWN_EVENT = {
204
+ type: "event",
205
+ name: "Withdrawn",
206
+ inputs: [
207
+ { indexed: true, name: "token", type: "address" },
208
+ { indexed: true, name: "recipient", type: "address" },
209
+ { indexed: true, name: "nullifier", type: "bytes32" },
210
+ { indexed: false, name: "amount", type: "uint256" },
211
+ { indexed: false, name: "isRagequit", type: "bool" }
212
+ ]
213
+ };
214
+ var RateLimiter = class {
215
+ tokens;
216
+ maxTokens;
217
+ refillRate;
218
+ // tokens per ms
219
+ lastRefill;
220
+ activeRequests;
221
+ maxConcurrent;
222
+ queue = [];
223
+ constructor(requestsPerSecond, maxConcurrent) {
224
+ this.maxTokens = requestsPerSecond;
225
+ this.tokens = requestsPerSecond;
226
+ this.refillRate = requestsPerSecond / 1e3;
227
+ this.lastRefill = Date.now();
228
+ this.activeRequests = 0;
229
+ this.maxConcurrent = maxConcurrent;
230
+ }
231
+ /**
232
+ * Refill tokens based on time elapsed
233
+ */
234
+ refill() {
235
+ const now = Date.now();
236
+ const elapsed = now - this.lastRefill;
237
+ this.tokens = Math.min(this.maxTokens, this.tokens + elapsed * this.refillRate);
238
+ this.lastRefill = now;
239
+ }
240
+ /**
241
+ * Try to process queued requests
242
+ */
243
+ processQueue() {
244
+ while (this.queue.length > 0 && this.activeRequests < this.maxConcurrent) {
245
+ this.refill();
246
+ if (this.tokens >= 1) {
247
+ this.tokens -= 1;
248
+ this.activeRequests++;
249
+ const resolve = this.queue.shift();
250
+ resolve?.();
251
+ } else {
252
+ break;
253
+ }
254
+ }
255
+ }
256
+ /**
257
+ * Acquire a token (wait if necessary)
258
+ */
259
+ async acquire() {
260
+ return new Promise((resolve) => {
261
+ this.refill();
262
+ if (this.tokens >= 1 && this.activeRequests < this.maxConcurrent) {
263
+ this.tokens -= 1;
264
+ this.activeRequests++;
265
+ resolve();
266
+ return;
267
+ }
268
+ this.queue.push(resolve);
269
+ const waitTime = this.tokens < 1 ? Math.ceil((1 - this.tokens) / this.refillRate) : 10;
270
+ setTimeout(() => this.processQueue(), waitTime);
271
+ });
272
+ }
273
+ /**
274
+ * Release a token (call after request completes)
275
+ */
276
+ release() {
277
+ this.activeRequests = Math.max(0, this.activeRequests - 1);
278
+ this.processQueue();
279
+ }
280
+ /**
281
+ * Execute a function with rate limiting
282
+ */
283
+ async execute(fn) {
284
+ await this.acquire();
285
+ try {
286
+ return await fn();
287
+ } finally {
288
+ this.release();
289
+ }
290
+ }
291
+ };
292
+ var sdkPromise = null;
293
+ async function loadSDK() {
294
+ if (!sdkPromise) {
295
+ sdkPromise = import('./index.js');
296
+ }
297
+ return sdkPromise;
298
+ }
299
+ var RpcIndexer = class {
300
+ client;
301
+ contractAddress;
302
+ chainId;
303
+ storage;
304
+ spendingSecret;
305
+ spendingPubkey;
306
+ masterViewingSecret;
307
+ masterViewingPubKey;
308
+ // Adaptive batching state
309
+ batchSize;
310
+ minBatchSize;
311
+ batchDelayMs;
312
+ // In-memory cache
313
+ notes = [];
314
+ nullifiers = /* @__PURE__ */ new Set();
315
+ lastBlock = 0;
316
+ initialized = false;
317
+ // Initial starting block (fallback if no saved state)
318
+ initialFromBlock;
319
+ // Live sync state
320
+ liveSyncInterval = null;
321
+ liveSyncConfig = null;
322
+ // Rate limiter for RPC requests
323
+ rateLimiter;
324
+ constructor(config) {
325
+ this.client = config.client;
326
+ this.contractAddress = config.contractAddress;
327
+ this.chainId = config.chainId;
328
+ this.spendingSecret = config.spendingSecret;
329
+ this.spendingPubkey = config.spendingPubkey;
330
+ this.masterViewingSecret = config.masterViewingSecret;
331
+ this.masterViewingPubKey = config.masterViewingPubKey;
332
+ this.batchSize = config.batchSize ?? DEFAULT_BATCH_SIZE;
333
+ this.minBatchSize = config.minBatchSize ?? MIN_BATCH_SIZE;
334
+ this.batchDelayMs = config.batchDelayMs ?? BATCH_DELAY_MS;
335
+ this.initialFromBlock = config.fromBlock ?? 0;
336
+ const accountPrefix = `${config.chainId}_${config.contractAddress.slice(2, 10)}_${toHex(config.spendingPubkey.x).slice(2, 12)}`;
337
+ this.storage = config.storage ?? createAutoAdapter(accountPrefix);
338
+ this.rateLimiter = new RateLimiter(DEFAULT_REQUESTS_PER_SECOND, DEFAULT_MAX_CONCURRENT);
339
+ }
340
+ /**
341
+ * Initialize from storage
342
+ */
343
+ async init() {
344
+ if (this.initialized) return;
345
+ const syncState = await this.storage.get(STORAGE_KEY_SYNC);
346
+ if (syncState && syncState.chainId === this.chainId && syncState.contractAddress === this.contractAddress) {
347
+ this.lastBlock = syncState.lastBlock;
348
+ } else {
349
+ this.lastBlock = this.initialFromBlock;
350
+ }
351
+ const serializedNotes = await this.storage.get(STORAGE_KEY_NOTES);
352
+ if (serializedNotes) {
353
+ this.notes = serializedNotes.map(deserializeNote);
354
+ }
355
+ const nullifiers = await this.storage.get(STORAGE_KEY_NULLIFIERS);
356
+ if (nullifiers) {
357
+ this.nullifiers = new Set(nullifiers.map((n) => n.toLowerCase()));
358
+ }
359
+ this.initialized = true;
360
+ if (this.notes.length > 0 && this.nullifiers.size > 0) {
361
+ await this.checkNotesAgainstNullifiers();
362
+ }
363
+ }
364
+ /**
365
+ * Check all notes against known nullifiers and mark spent ones
366
+ */
367
+ async checkNotesAgainstNullifiers() {
368
+ const sdk = await loadSDK();
369
+ let spentCount = 0;
370
+ console.log(`[Indexer] checkNotesAgainstNullifiers: ${this.notes.length} notes, ${this.nullifiers.size} nullifiers in set`);
371
+ if (this.nullifiers.size > 0) {
372
+ console.log(`[Indexer] Known nullifiers:`);
373
+ for (const n of this.nullifiers) {
374
+ console.log(` ${n}`);
375
+ }
376
+ }
377
+ for (const note of this.notes) {
378
+ if (note.status === "spent") continue;
379
+ try {
380
+ const nullifier = await sdk.poseidon([
381
+ BigInt(note.oneTimeSecret),
382
+ BigInt(note.leafIndex),
383
+ BigInt(note.commitment)
384
+ ]);
385
+ const nullifierHex = toHex(nullifier, { size: 32 }).toLowerCase();
386
+ const isSpent = this.nullifiers.has(nullifierHex);
387
+ console.log(`[Indexer] Note ${note.commitment.slice(0, 18)}... leafIndex=${note.leafIndex} \u2192 nullifier=${nullifierHex.slice(0, 18)}... \u2192 ${isSpent ? "SPENT" : "unspent"}`);
388
+ if (isSpent) {
389
+ note.status = "spent";
390
+ spentCount++;
391
+ }
392
+ } catch (e) {
393
+ console.error(`[Indexer] Error computing nullifier for note:`, e);
394
+ }
395
+ }
396
+ if (spentCount > 0) {
397
+ console.log(`[Indexer] Marked ${spentCount} notes as spent based on nullifiers`);
398
+ await this.persist();
399
+ }
400
+ return spentCount;
401
+ }
402
+ /**
403
+ * Persist current state to storage
404
+ */
405
+ async persist() {
406
+ const syncState = {
407
+ lastBlock: this.lastBlock,
408
+ chainId: this.chainId,
409
+ contractAddress: this.contractAddress,
410
+ lastSyncTimestamp: Date.now()
411
+ };
412
+ await Promise.all([
413
+ this.storage.set(STORAGE_KEY_SYNC, syncState),
414
+ this.storage.set(STORAGE_KEY_NOTES, this.notes.map(serializeNote)),
415
+ this.storage.set(STORAGE_KEY_NULLIFIERS, Array.from(this.nullifiers))
416
+ ]);
417
+ }
418
+ /**
419
+ * Sync notes from blockchain
420
+ *
421
+ * @param config - Optional sync configuration
422
+ */
423
+ async sync(config) {
424
+ const startTime = Date.now();
425
+ await this.init();
426
+ const errors = [];
427
+ let newNotesCount = 0;
428
+ let spentNotesCount = 0;
429
+ const debug = config?.debug ?? false;
430
+ const useSearchTagFiltering = config?.useSearchTagFiltering ?? false;
431
+ try {
432
+ const currentBlock = Number(await this.client.getBlockNumber());
433
+ const fromBlock = this.lastBlock > 0 ? this.lastBlock + 1 : this.initialFromBlock;
434
+ if (fromBlock > currentBlock) {
435
+ return {
436
+ newNotesCount: 0,
437
+ spentNotesCount: 0,
438
+ lastBlock: this.lastBlock,
439
+ durationMs: Date.now() - startTime
440
+ };
441
+ }
442
+ const reportProgress = (currentBlock_, eventsProcessed2, notesFound, eventsSkipped2) => {
443
+ if (config?.onProgress) {
444
+ const totalBlocks = currentBlock - fromBlock + 1;
445
+ const processedBlocks = currentBlock_ - fromBlock + 1;
446
+ config.onProgress({
447
+ currentBlock: currentBlock_,
448
+ targetBlock: currentBlock,
449
+ eventsProcessed: eventsProcessed2,
450
+ notesFound,
451
+ percent: Math.round(processedBlocks / totalBlocks * 100),
452
+ eventsSkipped: eventsSkipped2
453
+ });
454
+ }
455
+ };
456
+ const commitmentMap = /* @__PURE__ */ new Map();
457
+ const stealthEvents = [];
458
+ const newNullifiers = [];
459
+ let currentBatchSize = this.batchSize;
460
+ let batchStart = fromBlock;
461
+ while (batchStart <= currentBlock) {
462
+ const batchEnd = Math.min(batchStart + currentBatchSize - 1, currentBlock);
463
+ try {
464
+ const [commitmentLogs, stealthLogs, shieldedLogs, transferredLogs, nullifiedLogs, withdrawnLogs] = await Promise.all([
465
+ this.rateLimiter.execute(
466
+ () => this.client.getLogs({
467
+ address: this.contractAddress,
468
+ event: COMMITMENT_INSERTED_EVENT,
469
+ fromBlock: BigInt(batchStart),
470
+ toBlock: BigInt(batchEnd)
471
+ })
472
+ ),
473
+ this.rateLimiter.execute(
474
+ () => this.client.getLogs({
475
+ address: this.contractAddress,
476
+ event: STEALTH_TRANSACT_EVENT,
477
+ fromBlock: BigInt(batchStart),
478
+ toBlock: BigInt(batchEnd)
479
+ })
480
+ ),
481
+ this.rateLimiter.execute(
482
+ () => this.client.getLogs({
483
+ address: this.contractAddress,
484
+ event: SHIELDED_EVENT,
485
+ fromBlock: BigInt(batchStart),
486
+ toBlock: BigInt(batchEnd)
487
+ })
488
+ ),
489
+ this.rateLimiter.execute(
490
+ () => this.client.getLogs({
491
+ address: this.contractAddress,
492
+ event: TRANSFERRED_EVENT,
493
+ fromBlock: BigInt(batchStart),
494
+ toBlock: BigInt(batchEnd)
495
+ })
496
+ ),
497
+ this.rateLimiter.execute(
498
+ () => this.client.getLogs({
499
+ address: this.contractAddress,
500
+ event: NULLIFIED_EVENT,
501
+ fromBlock: BigInt(batchStart),
502
+ toBlock: BigInt(batchEnd)
503
+ })
504
+ ),
505
+ this.rateLimiter.execute(
506
+ () => this.client.getLogs({
507
+ address: this.contractAddress,
508
+ event: WITHDRAWN_EVENT,
509
+ fromBlock: BigInt(batchStart),
510
+ toBlock: BigInt(batchEnd)
511
+ })
512
+ )
513
+ ]);
514
+ for (const log of commitmentLogs) {
515
+ const commitment = log.args.commitment.toLowerCase();
516
+ commitmentMap.set(commitment, {
517
+ leafIndex: Number(log.args.leafIndex),
518
+ timestamp: Number(log.args.timestamp),
519
+ blockNumber: Number(log.blockNumber)
520
+ });
521
+ }
522
+ for (const log of stealthLogs) {
523
+ const args = log.args;
524
+ stealthEvents.push({
525
+ commitment: args.commitment.toLowerCase(),
526
+ ephemeralPubkeyX: args.ephemeralPubkeyX,
527
+ ephemeralPubkeyY: args.ephemeralPubkeyY,
528
+ encryptedNote: args.encryptedNote,
529
+ txHash: log.transactionHash,
530
+ blockNumber: Number(log.blockNumber),
531
+ searchTag: args.searchTag
532
+ });
533
+ }
534
+ for (const log of shieldedLogs) {
535
+ const args = log.args;
536
+ const unpacked = unpackNoteData(args.encryptedNote);
537
+ if (unpacked) {
538
+ stealthEvents.push({
539
+ commitment: args.commitment.toLowerCase(),
540
+ ephemeralPubkeyX: unpacked.ephemeralPubkeyX,
541
+ ephemeralPubkeyY: unpacked.ephemeralPubkeyY,
542
+ encryptedNote: unpacked.encryptedNote,
543
+ txHash: log.transactionHash,
544
+ blockNumber: Number(log.blockNumber),
545
+ searchTag: unpacked.searchTag
546
+ });
547
+ commitmentMap.set(args.commitment.toLowerCase(), {
548
+ leafIndex: Number(args.leafIndex),
549
+ timestamp: Math.floor(Date.now() / 1e3),
550
+ blockNumber: Number(log.blockNumber)
551
+ });
552
+ }
553
+ }
554
+ for (const log of transferredLogs) {
555
+ const args = log.args;
556
+ const unpacked1 = unpackNoteData(args.encryptedNote1);
557
+ if (unpacked1) {
558
+ stealthEvents.push({
559
+ commitment: args.outputCommitment1.toLowerCase(),
560
+ ephemeralPubkeyX: unpacked1.ephemeralPubkeyX,
561
+ ephemeralPubkeyY: unpacked1.ephemeralPubkeyY,
562
+ encryptedNote: unpacked1.encryptedNote,
563
+ txHash: log.transactionHash,
564
+ blockNumber: Number(log.blockNumber),
565
+ searchTag: unpacked1.searchTag
566
+ });
567
+ }
568
+ const unpacked2 = unpackNoteData(args.encryptedNote2);
569
+ if (unpacked2) {
570
+ stealthEvents.push({
571
+ commitment: args.outputCommitment2.toLowerCase(),
572
+ ephemeralPubkeyX: unpacked2.ephemeralPubkeyX,
573
+ ephemeralPubkeyY: unpacked2.ephemeralPubkeyY,
574
+ encryptedNote: unpacked2.encryptedNote,
575
+ txHash: log.transactionHash,
576
+ blockNumber: Number(log.blockNumber),
577
+ searchTag: unpacked2.searchTag
578
+ });
579
+ }
580
+ }
581
+ for (const log of nullifiedLogs) {
582
+ const nullifier = log.args.nullifier.toLowerCase();
583
+ newNullifiers.push(nullifier);
584
+ }
585
+ for (const log of transferredLogs) {
586
+ const args = log.args;
587
+ if (args.nullifier) {
588
+ newNullifiers.push(args.nullifier.toLowerCase());
589
+ }
590
+ }
591
+ if (withdrawnLogs.length > 0) {
592
+ console.log(`[Indexer] Found ${withdrawnLogs.length} Withdrawn events in batch`);
593
+ }
594
+ for (const log of withdrawnLogs) {
595
+ const args = log.args;
596
+ if (args.nullifier) {
597
+ const nullifierLower = args.nullifier.toLowerCase();
598
+ console.log(`[Indexer] Withdrawn event nullifier: ${nullifierLower}`);
599
+ newNullifiers.push(nullifierLower);
600
+ }
601
+ }
602
+ if (currentBatchSize < MAX_BATCH_SIZE) {
603
+ currentBatchSize = Math.min(currentBatchSize * 2, MAX_BATCH_SIZE);
604
+ }
605
+ reportProgress(batchEnd, stealthEvents.length, 0, 0);
606
+ batchStart = batchEnd + 1;
607
+ if (batchStart <= currentBlock) {
608
+ await sleep(this.batchDelayMs);
609
+ }
610
+ } catch (e) {
611
+ if (isRateLimitError(e)) {
612
+ currentBatchSize = Math.max(Math.floor(currentBatchSize / 2), this.minBatchSize);
613
+ errors.push(`Rate limited at block ${batchStart}, reducing batch size to ${currentBatchSize}`);
614
+ await sleep(1e3);
615
+ continue;
616
+ }
617
+ throw e;
618
+ }
619
+ }
620
+ this.batchSize = currentBatchSize;
621
+ console.log(`[Indexer] Processing ${newNullifiers.length} new nullifiers`);
622
+ for (const nullifier of newNullifiers) {
623
+ if (!this.nullifiers.has(nullifier.toLowerCase())) {
624
+ this.nullifiers.add(nullifier.toLowerCase());
625
+ }
626
+ }
627
+ console.log(`[Indexer] Total nullifiers in set: ${this.nullifiers.size}`);
628
+ const sdk = await loadSDK();
629
+ const existingCommitmentsMap = new Map(this.notes.map((n) => [n.commitment.toLowerCase(), n]));
630
+ console.log(`[Indexer] Sync: blocks ${fromBlock} to ${currentBlock} (${currentBlock - fromBlock + 1} blocks)`);
631
+ console.log(`[Indexer] Found: ${stealthEvents.length} stealth events, ${commitmentMap.size} commitment events`);
632
+ console.log(`[Indexer] Search tag filtering: ${useSearchTagFiltering ? "ENABLED" : "DISABLED"}`);
633
+ if (debug) {
634
+ console.log(`[Indexer] Existing notes in memory: ${this.notes.length}`);
635
+ console.log(`[Indexer] masterViewingPubKey: x=${this.masterViewingPubKey.x}, y=${this.masterViewingPubKey.y}`);
636
+ }
637
+ let eventsSkipped = 0;
638
+ let eventsProcessed = 0;
639
+ for (const event of stealthEvents) {
640
+ const commitmentLower = event.commitment.toLowerCase();
641
+ const existingNote = existingCommitmentsMap.get(commitmentLower);
642
+ if (existingNote && existingNote.status === "confirmed") {
643
+ continue;
644
+ }
645
+ if (existingNote && existingNote.status === "pending") {
646
+ const info = commitmentMap.get(commitmentLower);
647
+ existingNote.status = "confirmed";
648
+ existingNote.leafIndex = info?.leafIndex ?? existingNote.leafIndex;
649
+ existingNote.blockNumber = info?.blockNumber ?? event.blockNumber;
650
+ existingNote.timestamp = info?.timestamp ?? existingNote.timestamp;
651
+ newNotesCount++;
652
+ continue;
653
+ }
654
+ if (useSearchTagFiltering && event.searchTag !== void 0) {
655
+ const ephemeralPubkey = { x: event.ephemeralPubkeyX, y: event.ephemeralPubkeyY };
656
+ const shouldLogDetails = debug && eventsSkipped + eventsProcessed < 3;
657
+ if (shouldLogDetails) {
658
+ console.log(`[Indexer] Checking event searchTag=${event.searchTag}`);
659
+ console.log(`[Indexer] ephemeralPubkey: x=${ephemeralPubkey.x}, y=${ephemeralPubkey.y}`);
660
+ }
661
+ const matches = await matchesSearchTag(
662
+ event.searchTag,
663
+ sdk,
664
+ this.masterViewingSecret,
665
+ this.masterViewingPubKey,
666
+ ephemeralPubkey,
667
+ shouldLogDetails
668
+ // Pass debug flag
669
+ );
670
+ if (!matches) {
671
+ eventsSkipped++;
672
+ continue;
673
+ }
674
+ if (debug) {
675
+ console.log(`[Indexer] \u2713 Search tag MATCHED for commitment ${event.commitment.slice(0, 18)}...`);
676
+ }
677
+ }
678
+ eventsProcessed++;
679
+ try {
680
+ if (debug) {
681
+ console.log(`[Indexer] Trying to decrypt event commitment: ${event.commitment}`);
682
+ console.log(`[Indexer] Encrypted note length: ${(event.encryptedNote.length - 2) / 2} bytes`);
683
+ }
684
+ const decrypted = await tryDecryptNote2(
685
+ sdk,
686
+ this.spendingSecret,
687
+ this.spendingPubkey,
688
+ this.masterViewingSecret,
689
+ this.masterViewingPubKey,
690
+ { x: event.ephemeralPubkeyX, y: event.ephemeralPubkeyY },
691
+ event.encryptedNote,
692
+ BigInt(event.commitment)
693
+ );
694
+ if (decrypted) {
695
+ if (debug) {
696
+ console.log(`[Indexer] \u2713 Decryption SUCCESS for commitment ${event.commitment.slice(0, 18)}...`);
697
+ console.log(`[Indexer] Amount: ${decrypted.amount}, Origin: 0x${decrypted.origin.toString(16)}, Token: 0x${decrypted.token.toString(16)}`);
698
+ }
699
+ const info = commitmentMap.get(event.commitment.toLowerCase());
700
+ const note = {
701
+ amount: decrypted.amount,
702
+ blinding: decrypted.blinding,
703
+ commitment: event.commitment.toLowerCase(),
704
+ oneTimeSecret: toHex(decrypted.oneTimeSecret, { size: 32 }),
705
+ oneTimePubkeyX: toHex(decrypted.oneTimePubkey.x, { size: 32 }),
706
+ oneTimePubkeyY: toHex(decrypted.oneTimePubkey.y, { size: 32 }),
707
+ leafIndex: info?.leafIndex ?? 0,
708
+ txHash: event.txHash,
709
+ status: "confirmed",
710
+ blockNumber: info?.blockNumber ?? event.blockNumber,
711
+ timestamp: info?.timestamp ?? Math.floor(Date.now() / 1e3),
712
+ origin: "0x" + decrypted.origin.toString(16).padStart(40, "0"),
713
+ token: "0x" + decrypted.token.toString(16).padStart(40, "0"),
714
+ ephemeralX: toHex(event.ephemeralPubkeyX, { size: 32 }),
715
+ ephemeralY: toHex(event.ephemeralPubkeyY, { size: 32 })
716
+ };
717
+ this.notes.push(note);
718
+ existingCommitmentsMap.set(note.commitment.toLowerCase(), note);
719
+ newNotesCount++;
720
+ }
721
+ } catch (decryptErr) {
722
+ if (debug) {
723
+ console.log(`[Indexer] \u2717 Decryption FAILED for commitment ${event.commitment.slice(0, 18)}...`, decryptErr);
724
+ }
725
+ }
726
+ }
727
+ if (stealthEvents.length > 0) {
728
+ console.log(`[Indexer] Result: ${eventsSkipped} skipped by search tag, ${eventsProcessed} processed, ${newNotesCount} notes found`);
729
+ }
730
+ reportProgress(currentBlock, eventsProcessed, newNotesCount, eventsSkipped);
731
+ spentNotesCount = await this.checkNotesAgainstNullifiers();
732
+ this.lastBlock = currentBlock;
733
+ await this.persist();
734
+ if (newNotesCount > 0 && this.liveSyncConfig?.onNewNotes) {
735
+ const newNotes = this.notes.slice(-newNotesCount);
736
+ this.liveSyncConfig.onNewNotes(newNotes);
737
+ }
738
+ return {
739
+ newNotesCount,
740
+ spentNotesCount,
741
+ lastBlock: this.lastBlock,
742
+ durationMs: Date.now() - startTime,
743
+ errors: errors.length > 0 ? errors : void 0
744
+ };
745
+ } catch (e) {
746
+ const error = e instanceof Error ? e : new Error(String(e));
747
+ if (this.liveSyncConfig?.onError) {
748
+ this.liveSyncConfig.onError(error);
749
+ }
750
+ throw error;
751
+ }
752
+ }
753
+ /**
754
+ * Get total balance of unspent notes
755
+ */
756
+ getBalance(token) {
757
+ const unspent = this.getUnspentNotes();
758
+ const filtered = token ? unspent.filter((n) => n.token === token) : unspent;
759
+ return filtered.reduce((sum, n) => sum + n.amount, 0n);
760
+ }
761
+ /**
762
+ * Get all notes matching filters
763
+ */
764
+ getNotes(filters) {
765
+ let result = [...this.notes];
766
+ if (filters?.status) {
767
+ const statuses = Array.isArray(filters.status) ? filters.status : [filters.status];
768
+ result = result.filter((n) => statuses.includes(n.status));
769
+ }
770
+ if (filters?.token) {
771
+ result = result.filter((n) => n.token === filters.token);
772
+ }
773
+ if (filters?.minAmount !== void 0) {
774
+ result = result.filter((n) => n.amount >= filters.minAmount);
775
+ }
776
+ if (filters?.maxAmount !== void 0) {
777
+ result = result.filter((n) => n.amount <= filters.maxAmount);
778
+ }
779
+ return result;
780
+ }
781
+ /**
782
+ * Get unspent notes
783
+ */
784
+ getUnspentNotes() {
785
+ return this.notes.filter((n) => n.status !== "spent");
786
+ }
787
+ /**
788
+ * Get the last synced block number
789
+ */
790
+ getLastBlock() {
791
+ return this.lastBlock;
792
+ }
793
+ /**
794
+ * Mark a note as spent
795
+ */
796
+ markSpent(commitment) {
797
+ const note = this.notes.find((n) => n.commitment.toLowerCase() === commitment.toLowerCase());
798
+ if (note) {
799
+ note.status = "spent";
800
+ this.persist().catch(console.error);
801
+ }
802
+ }
803
+ /**
804
+ * Add a note directly
805
+ */
806
+ addNote(note) {
807
+ if (this.notes.some((n) => n.commitment.toLowerCase() === note.commitment.toLowerCase())) {
808
+ return;
809
+ }
810
+ this.notes.push(note);
811
+ this.persist().catch(console.error);
812
+ }
813
+ /**
814
+ * Start live sync
815
+ */
816
+ startLiveSync(config) {
817
+ if (this.liveSyncInterval) {
818
+ this.stopLiveSync();
819
+ }
820
+ this.liveSyncConfig = config ?? {};
821
+ const intervalMs = config?.intervalMs ?? DEFAULT_LIVE_SYNC_INTERVAL_MS;
822
+ this.liveSyncInterval = setInterval(() => {
823
+ this.sync().catch((e) => {
824
+ if (config?.onError) {
825
+ config.onError(e instanceof Error ? e : new Error(String(e)));
826
+ }
827
+ });
828
+ }, intervalMs);
829
+ this.sync().catch((e) => {
830
+ if (config?.onError) {
831
+ config.onError(e instanceof Error ? e : new Error(String(e)));
832
+ }
833
+ });
834
+ }
835
+ /**
836
+ * Stop live sync
837
+ */
838
+ stopLiveSync() {
839
+ if (this.liveSyncInterval) {
840
+ clearInterval(this.liveSyncInterval);
841
+ this.liveSyncInterval = null;
842
+ }
843
+ this.liveSyncConfig = null;
844
+ }
845
+ /**
846
+ * Check if live sync is running
847
+ */
848
+ isLiveSyncing() {
849
+ return this.liveSyncInterval !== null;
850
+ }
851
+ /**
852
+ * Clear all indexed data
853
+ */
854
+ async clear() {
855
+ this.notes = [];
856
+ this.nullifiers.clear();
857
+ this.lastBlock = 0;
858
+ await this.storage.clear();
859
+ }
860
+ /**
861
+ * Export current state
862
+ */
863
+ async exportState() {
864
+ await this.init();
865
+ return {
866
+ sync: {
867
+ lastBlock: this.lastBlock,
868
+ chainId: this.chainId,
869
+ contractAddress: this.contractAddress,
870
+ lastSyncTimestamp: Date.now()
871
+ },
872
+ notes: this.notes.map(serializeNote),
873
+ nullifiers: Array.from(this.nullifiers)
874
+ };
875
+ }
876
+ /**
877
+ * Import state
878
+ */
879
+ async importState(state) {
880
+ if (state.sync.chainId !== this.chainId || state.sync.contractAddress !== this.contractAddress) {
881
+ throw new Error("State does not match indexer configuration");
882
+ }
883
+ this.lastBlock = state.sync.lastBlock;
884
+ this.notes = state.notes.map(deserializeNote);
885
+ this.nullifiers = new Set(state.nullifiers.map((n) => n.toLowerCase()));
886
+ this.initialized = true;
887
+ await this.persist();
888
+ }
889
+ };
890
+ function makeRpcIndexer(config) {
891
+ return new RpcIndexer(config);
892
+ }
893
+ async function tryDecryptNote2(sdk, spendingSecret, spendingPubkey, masterViewingSecret, masterViewingPubKey, ephemeralPubkey, encryptedNote, expectedCommitment) {
894
+ try {
895
+ const dvk = await sdk.deriveDecryptionViewingKey(masterViewingSecret, masterViewingPubKey, ephemeralPubkey.x);
896
+ const viewingSharedSecret = await sdk.computeSharedSecret(dvk, ephemeralPubkey);
897
+ const keyMaterial = keccak256(toHex(viewingSharedSecret.x, { size: 32 }));
898
+ const keyBytes = hexToBytes(keyMaterial);
899
+ const cryptoKey = await crypto.subtle.importKey(
900
+ "raw",
901
+ keyBytes.buffer.slice(keyBytes.byteOffset, keyBytes.byteOffset + keyBytes.byteLength),
902
+ { name: "AES-GCM", length: 256 },
903
+ false,
904
+ ["decrypt"]
905
+ );
906
+ const encryptedBytes = hexToBytes(encryptedNote);
907
+ const nonce = encryptedBytes.slice(0, 12);
908
+ const ciphertext = encryptedBytes.slice(12);
909
+ const plaintext = await crypto.subtle.decrypt({ name: "AES-GCM", iv: nonce }, cryptoKey, ciphertext);
910
+ const plaintextBytes = new Uint8Array(plaintext);
911
+ console.log(`[tryDecryptNote] Decrypted plaintext length: ${plaintextBytes.length} bytes`);
912
+ const amount = bytesToBigint(plaintextBytes.slice(0, 32));
913
+ const blinding = bytesToBigint(plaintextBytes.slice(32, 64));
914
+ const origin = bytesToBigint(plaintextBytes.slice(64, 84));
915
+ const token = bytesToBigint(plaintextBytes.slice(84, 104));
916
+ const spendingSharedSecret = await sdk.computeSharedSecret(spendingSecret, ephemeralPubkey);
917
+ const stealthScalar = await sdk.poseidon([spendingSharedSecret.x, spendingSharedSecret.y]);
918
+ const stealthOffset = await sdk.privateToPublic(stealthScalar);
919
+ const oneTimePubkey = await sdk.addPoints(spendingPubkey, stealthOffset);
920
+ const subOrder = await sdk.getSubOrder();
921
+ const oneTimeSecret = (spendingSecret + stealthScalar) % subOrder;
922
+ const computedCommitment = await sdk.poseidon([
923
+ amount,
924
+ oneTimePubkey.x,
925
+ oneTimePubkey.y,
926
+ blinding,
927
+ origin,
928
+ token
929
+ ]);
930
+ console.log(`[tryDecryptNote] Decrypted values:`);
931
+ console.log(` amount: ${amount}`);
932
+ console.log(` blinding: ${blinding}`);
933
+ console.log(` origin: 0x${origin.toString(16).padStart(40, "0")}`);
934
+ console.log(` token: 0x${token.toString(16).padStart(40, "0")}`);
935
+ console.log(` oneTimePubkey.x: ${oneTimePubkey.x}`);
936
+ console.log(` oneTimePubkey.y: ${oneTimePubkey.y}`);
937
+ console.log(` computedCommitment: ${computedCommitment}`);
938
+ console.log(` expectedCommitment: ${expectedCommitment}`);
939
+ console.log(` match: ${computedCommitment === expectedCommitment}`);
940
+ if (computedCommitment !== expectedCommitment) {
941
+ console.log(`[tryDecryptNote] Commitment mismatch - not our note`);
942
+ return null;
943
+ }
944
+ return { amount, blinding, oneTimeSecret, oneTimePubkey, origin, token };
945
+ } catch (e) {
946
+ console.log(`[tryDecryptNote] Decryption exception:`, e instanceof Error ? e.message : e);
947
+ console.log(`[tryDecryptNote] encryptedNote hex length: ${encryptedNote.length} chars = ${(encryptedNote.length - 2) / 2} bytes`);
948
+ return null;
949
+ }
950
+ }
951
+ function serializeNote(note) {
952
+ return {
953
+ amount: note.amount.toString(),
954
+ blinding: note.blinding.toString(),
955
+ commitment: note.commitment,
956
+ oneTimeSecret: note.oneTimeSecret,
957
+ oneTimePubkeyX: note.oneTimePubkeyX,
958
+ oneTimePubkeyY: note.oneTimePubkeyY,
959
+ leafIndex: note.leafIndex,
960
+ txHash: note.txHash,
961
+ status: note.status,
962
+ blockNumber: note.blockNumber,
963
+ timestamp: note.timestamp,
964
+ origin: note.origin,
965
+ token: note.token,
966
+ ephemeralX: note.ephemeralX,
967
+ ephemeralY: note.ephemeralY
968
+ };
969
+ }
970
+ function deserializeNote(note) {
971
+ return {
972
+ amount: BigInt(note.amount),
973
+ blinding: BigInt(note.blinding),
974
+ commitment: note.commitment,
975
+ oneTimeSecret: note.oneTimeSecret,
976
+ oneTimePubkeyX: note.oneTimePubkeyX,
977
+ oneTimePubkeyY: note.oneTimePubkeyY,
978
+ leafIndex: note.leafIndex,
979
+ txHash: note.txHash,
980
+ status: note.status,
981
+ blockNumber: note.blockNumber,
982
+ timestamp: note.timestamp,
983
+ origin: note.origin,
984
+ token: note.token,
985
+ ephemeralX: note.ephemeralX,
986
+ ephemeralY: note.ephemeralY
987
+ };
988
+ }
989
+ function isRateLimitError(e) {
990
+ if (e instanceof Error) {
991
+ const message = e.message.toLowerCase();
992
+ return message.includes("rate limit") || message.includes("too many requests") || message.includes("429") || message.includes("exceeded");
993
+ }
994
+ return false;
995
+ }
996
+ function sleep(ms) {
997
+ return new Promise((resolve) => setTimeout(resolve, ms));
998
+ }
999
+
1000
+ export { computeSearchTag, makeRpcIndexer, matchesSearchTag, tryDecryptNote, unpackNoteData };
1001
+ //# sourceMappingURL=chunk-OQDSHMXU.js.map
1002
+ //# sourceMappingURL=chunk-OQDSHMXU.js.map