@velumdotcash/sdk 2.0.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 (49) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +142 -0
  3. package/dist/__tests__/paylink.test.d.ts +9 -0
  4. package/dist/__tests__/paylink.test.js +254 -0
  5. package/dist/config.d.ts +9 -0
  6. package/dist/config.js +12 -0
  7. package/dist/deposit.d.ts +22 -0
  8. package/dist/deposit.js +445 -0
  9. package/dist/depositSPL.d.ts +24 -0
  10. package/dist/depositSPL.js +499 -0
  11. package/dist/errors.d.ts +78 -0
  12. package/dist/errors.js +127 -0
  13. package/dist/exportUtils.d.ts +10 -0
  14. package/dist/exportUtils.js +10 -0
  15. package/dist/getUtxos.d.ts +30 -0
  16. package/dist/getUtxos.js +335 -0
  17. package/dist/getUtxosSPL.d.ts +34 -0
  18. package/dist/getUtxosSPL.js +442 -0
  19. package/dist/index.d.ts +183 -0
  20. package/dist/index.js +436 -0
  21. package/dist/models/keypair.d.ts +26 -0
  22. package/dist/models/keypair.js +43 -0
  23. package/dist/models/utxo.d.ts +51 -0
  24. package/dist/models/utxo.js +99 -0
  25. package/dist/test_paylink_logic.test.d.ts +1 -0
  26. package/dist/test_paylink_logic.test.js +114 -0
  27. package/dist/utils/address_lookup_table.d.ts +9 -0
  28. package/dist/utils/address_lookup_table.js +45 -0
  29. package/dist/utils/constants.d.ts +27 -0
  30. package/dist/utils/constants.js +56 -0
  31. package/dist/utils/debug-logger.d.ts +250 -0
  32. package/dist/utils/debug-logger.js +688 -0
  33. package/dist/utils/encryption.d.ts +152 -0
  34. package/dist/utils/encryption.js +700 -0
  35. package/dist/utils/logger.d.ts +9 -0
  36. package/dist/utils/logger.js +35 -0
  37. package/dist/utils/merkle_tree.d.ts +92 -0
  38. package/dist/utils/merkle_tree.js +186 -0
  39. package/dist/utils/node-shim.d.ts +14 -0
  40. package/dist/utils/node-shim.js +21 -0
  41. package/dist/utils/prover.d.ts +36 -0
  42. package/dist/utils/prover.js +169 -0
  43. package/dist/utils/utils.d.ts +64 -0
  44. package/dist/utils/utils.js +165 -0
  45. package/dist/withdraw.d.ts +22 -0
  46. package/dist/withdraw.js +290 -0
  47. package/dist/withdrawSPL.d.ts +24 -0
  48. package/dist/withdrawSPL.js +329 -0
  49. package/package.json +59 -0
@@ -0,0 +1,442 @@
1
+ import { PublicKey, } from "@solana/web3.js";
2
+ import BN from "bn.js";
3
+ import { Keypair as UtxoKeypair } from "./models/keypair.js";
4
+ import { WasmFactory } from "@lightprotocol/hasher.rs";
5
+ //@ts-ignore
6
+ import * as ffjavascript from "ffjavascript";
7
+ import { FETCH_UTXOS_GROUP_SIZE, RELAYER_API_URL, LSK_ENCRYPTED_OUTPUTS, LSK_FETCH_OFFSET, PROGRAM_ID, tokens, } from "./utils/constants.js";
8
+ import { logger } from "./utils/logger.js";
9
+ import { getAssociatedTokenAddress } from "@solana/spl-token";
10
+ import { debugLogger } from "./utils/debug-logger.js";
11
+ // Use type assertion for the utility functions (same pattern as in get_verification_keys.ts)
12
+ const utils = ffjavascript.utils;
13
+ const { unstringifyBigInts, leInt2Buff } = utils;
14
+ function sleep(ms) {
15
+ return new Promise((resolve) => setTimeout(() => {
16
+ resolve("ok");
17
+ }, ms));
18
+ }
19
+ export function localstorageKey(key) {
20
+ return PROGRAM_ID.toString().substring(0, 6) + key.toString();
21
+ }
22
+ let getMyUtxosPromise = null;
23
+ let roundStartIndex = 0;
24
+ let decryptionTaskFinished = 0;
25
+ /**
26
+ * Fetch and decrypt all UTXOs for a user
27
+ * @param signed The user's signature
28
+ * @param connection Solana connection to fetch on-chain commitment accounts
29
+ * @param setStatus A global state updator. Set live status message showing on webpage
30
+ * @returns Array of decrypted UTXOs that belong to the user
31
+ */
32
+ export async function getUtxosSPL({ publicKey, connection, encryptionService, storage, abortSignal, offset, mintAddress, }) {
33
+ let valid_utxos = [];
34
+ let valid_strings = [];
35
+ let history_indexes = [];
36
+ let publicKey_ata;
37
+ if (typeof mintAddress == "string") {
38
+ mintAddress = new PublicKey(mintAddress);
39
+ }
40
+ let token = tokens.find((t) => t.pubkey.toString() == mintAddress.toString());
41
+ if (!token) {
42
+ throw new Error("token not found: " + mintAddress.toString());
43
+ }
44
+ logger.debug("token name: " + token.name + ", token address" + token.pubkey.toString());
45
+ try {
46
+ publicKey_ata = await getAssociatedTokenAddress(token.pubkey, publicKey);
47
+ let offsetStr = storage.getItem(LSK_FETCH_OFFSET + localstorageKey(publicKey_ata));
48
+ if (offsetStr) {
49
+ roundStartIndex = Number(offsetStr);
50
+ }
51
+ else {
52
+ roundStartIndex = 0;
53
+ }
54
+ decryptionTaskFinished = 0;
55
+ if (!offset) {
56
+ offset = 0;
57
+ }
58
+ roundStartIndex = Math.max(offset, roundStartIndex);
59
+ while (true) {
60
+ if (abortSignal?.aborted) {
61
+ throw new Error("aborted");
62
+ }
63
+ let offsetStr = storage.getItem(LSK_FETCH_OFFSET + localstorageKey(publicKey_ata));
64
+ let fetch_utxo_offset = offsetStr ? Number(offsetStr) : 0;
65
+ if (offset) {
66
+ fetch_utxo_offset = Math.max(offset, fetch_utxo_offset);
67
+ }
68
+ logger.debug(" ####fetch_utxo_offset", fetch_utxo_offset);
69
+ let fetch_utxo_end = fetch_utxo_offset + FETCH_UTXOS_GROUP_SIZE;
70
+ let fetch_utxo_url = `${RELAYER_API_URL}/utxos/range?token=${token.name}&start=${fetch_utxo_offset}&end=${fetch_utxo_end}`;
71
+ let fetched = await fetchUserUtxos({
72
+ url: fetch_utxo_url,
73
+ encryptionService,
74
+ storage,
75
+ publicKey_ata,
76
+ tokenName: token.name,
77
+ });
78
+ let am = 0;
79
+ const nonZeroUtxos = [];
80
+ const nonZeroEncrypted = [];
81
+ for (let [k, utxo] of fetched.utxos.entries()) {
82
+ history_indexes.push(utxo.index);
83
+ if (utxo.amount.toNumber() > 0) {
84
+ nonZeroUtxos.push(utxo);
85
+ nonZeroEncrypted.push(fetched.encryptedOutputs[k]);
86
+ }
87
+ }
88
+ if (nonZeroUtxos.length > 0) {
89
+ const spentFlags = await areUtxosSpent(connection, nonZeroUtxos);
90
+ for (let i = 0; i < nonZeroUtxos.length; i++) {
91
+ if (!spentFlags[i]) {
92
+ logger.debug(`found unspent encrypted_output ${nonZeroEncrypted[i]}`);
93
+ am += nonZeroUtxos[i].amount.toNumber();
94
+ valid_utxos.push(nonZeroUtxos[i]);
95
+ valid_strings.push(nonZeroEncrypted[i]);
96
+ }
97
+ }
98
+ }
99
+ storage.setItem(LSK_FETCH_OFFSET + localstorageKey(publicKey_ata), (fetch_utxo_offset + fetched.len).toString());
100
+ if (!fetched.hasMore) {
101
+ break;
102
+ }
103
+ await sleep(100);
104
+ }
105
+ }
106
+ catch (e) {
107
+ throw e;
108
+ }
109
+ finally {
110
+ getMyUtxosPromise = null;
111
+ }
112
+ // get history index
113
+ let historyKey = "tradeHistory" + localstorageKey(publicKey_ata);
114
+ let rec = storage.getItem(historyKey);
115
+ let recIndexes = [];
116
+ if (rec?.length) {
117
+ recIndexes = rec.split(",").map((n) => Number(n));
118
+ }
119
+ if (recIndexes.length) {
120
+ history_indexes = [...history_indexes, ...recIndexes];
121
+ }
122
+ let unique_history_indexes = Array.from(new Set(history_indexes));
123
+ let top20 = unique_history_indexes.sort((a, b) => b - a).slice(0, 20);
124
+ if (top20.length) {
125
+ storage.setItem(historyKey, top20.join(","));
126
+ }
127
+ // store valid strings
128
+ logger.debug(`valid_strings len before set: ${valid_strings.length}`);
129
+ valid_strings = [...new Set(valid_strings)];
130
+ logger.debug(`valid_strings len after set: ${valid_strings.length}`);
131
+ storage.setItem(LSK_ENCRYPTED_OUTPUTS + localstorageKey(publicKey_ata), JSON.stringify(valid_strings));
132
+ return valid_utxos.filter((u) => u.mintAddress == token.pubkey.toString());
133
+ }
134
+ async function fetchUserUtxos({ url, storage, encryptionService, publicKey_ata, tokenName, }) {
135
+ const lightWasm = await WasmFactory.getInstance();
136
+ // Derive the UTXO keypair from the wallet keypair
137
+ const utxoPrivateKey = encryptionService.deriveUtxoPrivateKey();
138
+ const utxoKeypair = new UtxoKeypair(utxoPrivateKey, lightWasm);
139
+ // Fetch all UTXOs from the API
140
+ let encryptedOutputs = [];
141
+ logger.debug("fetching utxo data", url);
142
+ let res = await fetch(url);
143
+ if (!res.ok)
144
+ throw new Error(`HTTP error! status: ${res.status}`);
145
+ const data = await res.json();
146
+ logger.debug("got utxo data");
147
+ if (!data) {
148
+ throw new Error("API returned empty data");
149
+ }
150
+ else if (Array.isArray(data)) {
151
+ // Handle the case where the API returns an array of UTXOs
152
+ const utxos = data;
153
+ // Extract encrypted outputs from the array of UTXOs
154
+ encryptedOutputs = utxos
155
+ .filter((utxo) => utxo.encrypted_output)
156
+ .map((utxo) => utxo.encrypted_output);
157
+ }
158
+ else if (typeof data === "object" && data.encrypted_outputs) {
159
+ // Handle the case where the API returns an object with encrypted_outputs array
160
+ const apiResponse = data;
161
+ encryptedOutputs = apiResponse.encrypted_outputs;
162
+ }
163
+ else {
164
+ throw new Error(`API returned unexpected data format: ${JSON.stringify(data).substring(0, 100)}...`);
165
+ }
166
+ // Try to decrypt each encrypted output
167
+ const myUtxos = [];
168
+ const myEncryptedOutputs = [];
169
+ let decryptionAttempts = 0;
170
+ let successfulDecryptions = 0;
171
+ let cachedStringNum = 0;
172
+ let cachedString = storage.getItem(LSK_ENCRYPTED_OUTPUTS + localstorageKey(publicKey_ata));
173
+ if (cachedString) {
174
+ cachedStringNum = JSON.parse(cachedString).length;
175
+ }
176
+ let decryptionTaskTotal = data.total + cachedStringNum - roundStartIndex;
177
+ let batchRes = await decrypt_outputs(encryptedOutputs, encryptionService, utxoKeypair, lightWasm, tokenName);
178
+ decryptionTaskFinished += encryptedOutputs.length;
179
+ logger.debug("batchReslen", batchRes.length);
180
+ for (let i = 0; i < batchRes.length; i++) {
181
+ let dres = batchRes[i];
182
+ if (dres.status == "decrypted" && dres.utxo) {
183
+ myUtxos.push(dres.utxo);
184
+ myEncryptedOutputs.push(dres.encryptedOutput);
185
+ }
186
+ }
187
+ logger.info(`(decrypting cached utxo: ${decryptionTaskFinished + 1}/${decryptionTaskTotal}...)`);
188
+ // check cached string when no more fetching tasks
189
+ if (!data.hasMore) {
190
+ if (cachedString) {
191
+ let cachedEncryptedOutputs = JSON.parse(cachedString);
192
+ if (decryptionTaskFinished % 100 == 0) {
193
+ logger.info(`(decrypting cached utxo: ${decryptionTaskFinished + 1}/${decryptionTaskTotal}...)`);
194
+ }
195
+ let batchRes = await decrypt_outputs(cachedEncryptedOutputs, encryptionService, utxoKeypair, lightWasm, tokenName);
196
+ decryptionTaskFinished += cachedEncryptedOutputs.length;
197
+ logger.debug("cachedbatchReslen", batchRes.length, " source", cachedEncryptedOutputs.length);
198
+ for (let i = 0; i < batchRes.length; i++) {
199
+ let dres = batchRes[i];
200
+ if (dres.status == "decrypted" && dres.utxo) {
201
+ myUtxos.push(dres.utxo);
202
+ myEncryptedOutputs.push(dres.encryptedOutput);
203
+ }
204
+ }
205
+ }
206
+ }
207
+ return {
208
+ encryptedOutputs: myEncryptedOutputs,
209
+ utxos: myUtxos,
210
+ hasMore: data.hasMore,
211
+ len: encryptedOutputs.length,
212
+ };
213
+ }
214
+ /**
215
+ * Check if a UTXO has been spent
216
+ * @param connection Solana connection
217
+ * @param utxo The UTXO to check
218
+ * @param retries Number of retries remaining (default 3)
219
+ * @returns Promise<boolean> true if spent, false if unspent
220
+ */
221
+ export async function isUtxoSpent(connection, utxo, retries = 3) {
222
+ try {
223
+ // Get the nullifier for this UTXO
224
+ const nullifier = await utxo.getNullifier();
225
+ logger.debug(`Checking if UTXO with nullifier ${nullifier} is spent`);
226
+ // Convert decimal nullifier string to byte array (same format as in proofs)
227
+ // This matches how commitments are handled and how the Rust code expects the seeds
228
+ const nullifierBytes = Array.from(leInt2Buff(unstringifyBigInts(nullifier), 32)).reverse();
229
+ // Try nullifier0 seed
230
+ const [nullifier0PDA] = PublicKey.findProgramAddressSync([Buffer.from("nullifier0"), Buffer.from(nullifierBytes)], PROGRAM_ID);
231
+ logger.debug(`Derived nullifier0 PDA: ${nullifier0PDA.toBase58()}`);
232
+ const nullifier0Account = await connection.getAccountInfo(nullifier0PDA);
233
+ if (nullifier0Account !== null) {
234
+ logger.debug(`UTXO is spent (nullifier0 account exists)`);
235
+ return true;
236
+ }
237
+ const [nullifier1PDA] = PublicKey.findProgramAddressSync([Buffer.from("nullifier1"), Buffer.from(nullifierBytes)], PROGRAM_ID);
238
+ logger.debug(`Derived nullifier1 PDA: ${nullifier1PDA.toBase58()}`);
239
+ const nullifier1Account = await connection.getAccountInfo(nullifier1PDA);
240
+ if (nullifier1Account !== null) {
241
+ logger.debug(`UTXO is spent (nullifier1 account exists)`);
242
+ return true;
243
+ }
244
+ return false;
245
+ }
246
+ catch (error) {
247
+ console.error("Error checking if UTXO is spent:", error);
248
+ if (retries <= 0) {
249
+ console.error("Max retries reached for isUtxoSpent, returning false");
250
+ return false; // Assume unspent if we can't verify
251
+ }
252
+ await new Promise((resolve) => setTimeout(resolve, 3000));
253
+ return await isUtxoSpent(connection, utxo, retries - 1);
254
+ }
255
+ }
256
+ async function areUtxosSpent(connection, utxos) {
257
+ try {
258
+ const allPDAs = [];
259
+ for (let i = 0; i < utxos.length; i++) {
260
+ const utxo = utxos[i];
261
+ const nullifier = await utxo.getNullifier();
262
+ const nullifierBytes = Array.from(leInt2Buff(unstringifyBigInts(nullifier), 32)).reverse();
263
+ const [nullifier0PDA] = PublicKey.findProgramAddressSync([Buffer.from("nullifier0"), Buffer.from(nullifierBytes)], PROGRAM_ID);
264
+ const [nullifier1PDA] = PublicKey.findProgramAddressSync([Buffer.from("nullifier1"), Buffer.from(nullifierBytes)], PROGRAM_ID);
265
+ allPDAs.push({ utxoIndex: i, pda: nullifier0PDA });
266
+ allPDAs.push({ utxoIndex: i, pda: nullifier1PDA });
267
+ }
268
+ const results = await connection.getMultipleAccountsInfo(allPDAs.map((x) => x.pda));
269
+ const spentFlags = new Array(utxos.length).fill(false);
270
+ for (let i = 0; i < allPDAs.length; i++) {
271
+ if (results[i] !== null) {
272
+ spentFlags[allPDAs[i].utxoIndex] = true;
273
+ }
274
+ }
275
+ return spentFlags;
276
+ }
277
+ catch (error) {
278
+ console.error("Error checking if UTXOs are spent:", error);
279
+ await new Promise((resolve) => setTimeout(resolve, 3000));
280
+ return await areUtxosSpent(connection, utxos);
281
+ }
282
+ }
283
+ // Calculate total balance
284
+ export function getBalanceFromUtxosSPL(utxos) {
285
+ if (!utxos.length) {
286
+ return { base_units: 0, amount: 0, lamports: 0 };
287
+ }
288
+ let token = tokens.find((t) => t.pubkey.toString() == utxos[0].mintAddress.toString());
289
+ if (!token) {
290
+ throw new Error("token not found for " + utxos[0].mintAddress.toString());
291
+ }
292
+ const totalBalance = utxos.reduce((sum, utxo) => sum.add(utxo.amount), new BN(0));
293
+ return {
294
+ base_units: totalBalance.toNumber(),
295
+ lamports: totalBalance.toNumber(),
296
+ amount: totalBalance.toNumber() / token.units_per_token,
297
+ };
298
+ }
299
+ async function decrypt_output(encryptedOutput, encryptionService, utxoKeypair, lightWasm, connection) {
300
+ let res = { status: "unDecrypted" };
301
+ try {
302
+ if (!encryptedOutput) {
303
+ return { status: "skipped" };
304
+ }
305
+ // Try to decrypt the UTXO
306
+ const decryptedUtxo = await encryptionService.decryptUtxo(encryptedOutput, lightWasm);
307
+ // decryptUtxo returns null for schema version mismatch (early termination)
308
+ if (decryptedUtxo === null) {
309
+ return { status: "skipped" };
310
+ }
311
+ res.utxo = decryptedUtxo;
312
+ // If we got here, decryption succeeded, so this UTXO belongs to the user
313
+ res.status = "decrypted";
314
+ // Get the real index from the on-chain commitment account
315
+ try {
316
+ if (!res.utxo) {
317
+ throw new Error("res.utxo undefined");
318
+ }
319
+ const commitment = await res.utxo.getCommitment();
320
+ // Convert decimal commitment string to byte array (same format as in proofs)
321
+ const commitmentBytes = Array.from(leInt2Buff(unstringifyBigInts(commitment), 32)).reverse();
322
+ // Derive the commitment PDA (could be either commitment0 or commitment1)
323
+ // We'll try both seeds since we don't know which one it is
324
+ let commitmentAccount = null;
325
+ let realIndex = null;
326
+ // Try commitment0 seed
327
+ try {
328
+ const [commitment0PDA] = PublicKey.findProgramAddressSync([Buffer.from("commitment0"), Buffer.from(commitmentBytes)], PROGRAM_ID);
329
+ const account0Info = await connection.getAccountInfo(commitment0PDA);
330
+ if (account0Info) {
331
+ // Parse the index from the account data according to CommitmentAccount structure:
332
+ // 0-8: Anchor discriminator
333
+ // 8-40: commitment (32 bytes)
334
+ // 40-44: encrypted_output length (4 bytes)
335
+ // 44-44+len: encrypted_output data
336
+ // 44+len-52+len: index (8 bytes)
337
+ const encryptedOutputLength = account0Info.data.readUInt32LE(40);
338
+ const indexOffset = 44 + encryptedOutputLength;
339
+ const indexBytes = account0Info.data.slice(indexOffset, indexOffset + 8);
340
+ realIndex = new BN(indexBytes, "le").toNumber();
341
+ }
342
+ }
343
+ catch (e) {
344
+ // Try commitment1 seed if commitment0 fails
345
+ try {
346
+ const [commitment1PDA] = PublicKey.findProgramAddressSync([Buffer.from("commitment1"), Buffer.from(commitmentBytes)], PROGRAM_ID);
347
+ const account1Info = await connection.getAccountInfo(commitment1PDA);
348
+ if (account1Info) {
349
+ // Parse the index from the account data according to CommitmentAccount structure
350
+ const encryptedOutputLength = account1Info.data.readUInt32LE(40);
351
+ const indexOffset = 44 + encryptedOutputLength;
352
+ const indexBytes = account1Info.data.slice(indexOffset, indexOffset + 8);
353
+ realIndex = new BN(indexBytes, "le").toNumber();
354
+ logger.debug(`Found commitment1 account with index: ${realIndex}`);
355
+ }
356
+ }
357
+ catch (e2) {
358
+ logger.debug(`Could not find commitment account for ${commitment}, using encrypted index: ${res.utxo.index}`);
359
+ }
360
+ }
361
+ // Update the UTXO with the real index if we found it
362
+ if (realIndex !== null) {
363
+ const oldIndex = res.utxo.index;
364
+ res.utxo.index = realIndex;
365
+ }
366
+ }
367
+ catch (error) {
368
+ logger.debug(`Failed to get real index for UTXO: ${error.message}`);
369
+ }
370
+ }
371
+ catch (error) {
372
+ // Record the failure with full context - this UTXO may not belong to the user
373
+ // or there may be a legitimate error we need to diagnose
374
+ debugLogger.recordDecryptionFailure(error, encryptedOutput);
375
+ }
376
+ return res;
377
+ }
378
+ async function decrypt_outputs(encryptedOutputs, encryptionService, utxoKeypair, lightWasm, tokenName) {
379
+ let results = [];
380
+ let skippedCount = 0;
381
+ let failedCount = 0;
382
+ let successCount = 0;
383
+ // decrypt all UTXO
384
+ for (const encryptedOutput of encryptedOutputs) {
385
+ if (!encryptedOutput) {
386
+ results.push({ status: "skipped" });
387
+ skippedCount++;
388
+ debugLogger.recordDecryptionSkipped();
389
+ continue;
390
+ }
391
+ debugLogger.recordDecryptionAttempt();
392
+ try {
393
+ const utxo = await encryptionService.decryptUtxo(encryptedOutput, lightWasm);
394
+ // decryptUtxo returns null for schema version mismatch (early termination)
395
+ if (utxo === null) {
396
+ results.push({ status: "skipped" });
397
+ skippedCount++;
398
+ debugLogger.recordDecryptionSkipped();
399
+ continue;
400
+ }
401
+ results.push({ status: "decrypted", utxo, encryptedOutput });
402
+ successCount++;
403
+ debugLogger.recordDecryptionSuccess();
404
+ }
405
+ catch (error) {
406
+ // Record the failure with full context instead of silently swallowing
407
+ debugLogger.recordDecryptionFailure(error, encryptedOutput);
408
+ results.push({ status: "unDecrypted" });
409
+ failedCount++;
410
+ }
411
+ }
412
+ // Log batch summary
413
+ debugLogger.utxoBatchSummary(encryptedOutputs.length, successCount, skippedCount, failedCount);
414
+ results = results.filter((r) => r.status == "decrypted");
415
+ if (!results.length) {
416
+ return [];
417
+ }
418
+ // update utxo index
419
+ if (results.length > 0) {
420
+ let encrypted_outputs = results.map((r) => r.encryptedOutput);
421
+ let url = RELAYER_API_URL + `/utxos/indices`;
422
+ let res = await fetch(url, {
423
+ method: "POST",
424
+ headers: { "Content-Type": "application/json" },
425
+ body: JSON.stringify({ encrypted_outputs, token: tokenName }),
426
+ });
427
+ let j = await res.json();
428
+ if (!j.indices ||
429
+ !Array.isArray(j.indices) ||
430
+ j.indices.length != encrypted_outputs.length) {
431
+ throw new Error("failed fetching /utxos/indices");
432
+ }
433
+ for (let i = 0; i < results.length; i++) {
434
+ let utxo = results[i].utxo;
435
+ if (utxo.index !== j.indices[i] && typeof j.indices[i] == "number") {
436
+ logger.debug(`Updated UTXO index from ${utxo.index} to ${j.indices[i]}`);
437
+ utxo.index = j.indices[i];
438
+ }
439
+ }
440
+ }
441
+ return results;
442
+ }
@@ -0,0 +1,183 @@
1
+ import { Keypair, PublicKey, VersionedTransaction } from "@solana/web3.js";
2
+ export { PrivacyCashError, ZKProofError, NetworkError, InsufficientBalanceError, DepositLimitError, TransactionTimeoutError, } from "./errors.js";
3
+ export { enableDebugLogging, disableDebugLogging, isDebugEnabled, setDebugLogger, enableVerboseLogging, disableVerboseLogging, isVerboseEnabled, type DebugLoggerFn, type DebugLogEntry, type DecryptionErrorCategory, type DecryptionFailureRecord, type DecryptionFailureSummary, } from "./utils/debug-logger.js";
4
+ import { type LoggerFn } from "./utils/logger.js";
5
+ import { type DebugLoggerFn, type DecryptionFailureSummary } from "./utils/debug-logger.js";
6
+ export declare class PrivacyCash {
7
+ private connection;
8
+ publicKey: PublicKey;
9
+ private encryptionService;
10
+ private keypair?;
11
+ private transactionSigner?;
12
+ private isRuning?;
13
+ private status;
14
+ private storage;
15
+ private circuitPath;
16
+ private debugMode;
17
+ constructor({ RPC_url, owner, publicKey, signature, transactionSigner, enableDebug, debugLogger: customDebugLogger, storage, circuitPath, }: {
18
+ RPC_url: string;
19
+ owner?: string | number[] | Uint8Array | Keypair;
20
+ publicKey?: PublicKey;
21
+ signature?: Uint8Array;
22
+ transactionSigner?: (tx: VersionedTransaction) => Promise<VersionedTransaction>;
23
+ /** Enable verbose console logging for general SDK operations */
24
+ enableDebug?: boolean;
25
+ /** Enable detailed debug logging for V3 decryption diagnostics.
26
+ * Can be a boolean (true to enable with default console logger)
27
+ * or a custom DebugLoggerFn for custom logging integration.
28
+ * Also checks PRIVACY_CASH_DEBUG environment variable. */
29
+ debugLogger?: boolean | DebugLoggerFn;
30
+ storage?: Storage;
31
+ circuitPath?: string;
32
+ });
33
+ /**
34
+ * Enable debug logging for V3 decryption diagnostics.
35
+ * Useful for troubleshooting paylink balance issues.
36
+ * @param customLogger Optional custom logger function
37
+ */
38
+ enableDecryptionDebug(customLogger?: DebugLoggerFn): this;
39
+ /**
40
+ * Disable debug logging
41
+ */
42
+ disableDecryptionDebug(): this;
43
+ /**
44
+ * Check if debug logging is currently enabled
45
+ */
46
+ isDecryptionDebugEnabled(): boolean;
47
+ setLogger(loger: LoggerFn): this;
48
+ private getSigner;
49
+ /**
50
+ * Clears the cache of utxos.
51
+ *
52
+ * By default, downloaded utxos will be cached in the local storage. Thus the next time when you makes another
53
+ * deposit or withdraw or getPrivateBalance, the SDK only fetches the utxos that are not in the cache.
54
+ *
55
+ * This method clears the cache of utxos.
56
+ */
57
+ clearCache(): Promise<this>;
58
+ /**
59
+ * Deposit SOL to the Privacy Cash.
60
+ *
61
+ * Lamports is the amount of SOL in lamports. e.g. if you want to deposit 0.01 SOL (10000000 lamports), call deposit({ lamports: 10000000 })
62
+ */
63
+ deposit({ lamports, recipientUtxoPublicKey, recipientEncryptionKey, }: {
64
+ lamports: number;
65
+ recipientUtxoPublicKey?: any;
66
+ recipientEncryptionKey?: Uint8Array;
67
+ }): Promise<{
68
+ tx: string;
69
+ }>;
70
+ /**
71
+ * Deposit USDC to the Privacy Cash.
72
+ */
73
+ depositUSDC({ base_units, recipientUtxoPublicKey, recipientEncryptionKey, }: {
74
+ base_units: number;
75
+ recipientUtxoPublicKey?: any;
76
+ recipientEncryptionKey?: Uint8Array;
77
+ }): Promise<{
78
+ tx: string;
79
+ }>;
80
+ /**
81
+ * Withdraw SOL from the Privacy Cash.
82
+ *
83
+ * Lamports is the amount of SOL in lamports. e.g. if you want to withdraw 0.01 SOL (10000000 lamports), call withdraw({ lamports: 10000000 })
84
+ */
85
+ withdraw({ lamports, recipientAddress, referrer, }: {
86
+ lamports: number;
87
+ recipientAddress?: string;
88
+ referrer?: string;
89
+ }): Promise<{
90
+ isPartial: boolean;
91
+ tx: string;
92
+ recipient: string;
93
+ amount_in_lamports: number;
94
+ fee_in_lamports: number;
95
+ }>;
96
+ /**
97
+ * Withdraw USDC from the Privacy Cash.
98
+ *
99
+ * base_units is the amount of USDC in base unit. e.g. if you want to withdraw 1 USDC (1,000,000 base unit), call withdraw({ base_units: 1000000, recipientAddress: 'some_address' })
100
+ */
101
+ withdrawUSDC({ base_units, recipientAddress, referrer, }: {
102
+ base_units: number;
103
+ recipientAddress?: string;
104
+ referrer?: string;
105
+ }): Promise<{
106
+ isPartial: boolean;
107
+ tx: string;
108
+ recipient: string;
109
+ base_units: number;
110
+ fee_base_units: number;
111
+ }>;
112
+ /**
113
+ * Returns the amount of lamports current wallet has in Privacy Cash.
114
+ * Also tracks and summarizes any decryption failures for debugging.
115
+ */
116
+ getPrivateBalance(abortSignal?: AbortSignal): Promise<{
117
+ lamports: number;
118
+ failureSummary?: DecryptionFailureSummary | null;
119
+ }>;
120
+ /**
121
+ * Returns the amount of base units current wallet has in Privacy Cash.
122
+ * Also tracks and summarizes any decryption failures for debugging.
123
+ */
124
+ getPrivateBalanceUSDC(): Promise<{
125
+ base_units: number;
126
+ amount: number;
127
+ lamports: number;
128
+ failureSummary?: DecryptionFailureSummary | null;
129
+ }>;
130
+ /**
131
+ * Returns the amount of base units current wallet has in Privacy Cash.
132
+ * Also tracks and summarizes any decryption failures for debugging.
133
+ */
134
+ getPrivateBalanceSpl(mintAddress: PublicKey | string): Promise<{
135
+ base_units: number;
136
+ amount: number;
137
+ lamports: number;
138
+ failureSummary?: DecryptionFailureSummary | null;
139
+ }>;
140
+ /**
141
+ * Returns true if the code is running in a browser.
142
+ */
143
+ isBrowser(): boolean;
144
+ startStatusRender(): Promise<void>;
145
+ /**
146
+ * Deposit SPL to the Privacy Cash.
147
+ */
148
+ depositSPL({ base_units, mintAddress, amount, recipientUtxoPublicKey, recipientEncryptionKey, }: {
149
+ base_units?: number;
150
+ amount?: number;
151
+ mintAddress: PublicKey | string;
152
+ recipientUtxoPublicKey?: any;
153
+ recipientEncryptionKey?: Uint8Array;
154
+ }): Promise<{
155
+ tx: string;
156
+ }>;
157
+ /**
158
+ * Withdraw SPL from the Privacy Cash.
159
+ */
160
+ withdrawSPL({ base_units, mintAddress, recipientAddress, amount, referrer, }: {
161
+ base_units?: number;
162
+ amount?: number;
163
+ mintAddress: PublicKey | string;
164
+ recipientAddress?: string;
165
+ referrer?: string;
166
+ }): Promise<{
167
+ isPartial: boolean;
168
+ tx: string;
169
+ recipient: string;
170
+ base_units: number;
171
+ fee_base_units: number;
172
+ }>;
173
+ /**
174
+ * Returns the asymmetric encryption public key (X25519) for receiving encrypted paylink data.
175
+ * This key is used by senders to encrypt UTXO data that only this wallet can decrypt.
176
+ */
177
+ getAsymmetricPublicKey(): Uint8Array;
178
+ /**
179
+ * Returns the shielded public key (BN254 curve point) for receiving private payments.
180
+ * This is the UTXO ownership key derived from the wallet signature.
181
+ */
182
+ getShieldedPublicKey(): Promise<string>;
183
+ }