privacycash 1.0.17 → 1.0.19

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 (43) hide show
  1. package/README.md +5 -1
  2. package/__tests__/e2e.test.ts +5 -1
  3. package/__tests__/e2espl.test.ts +73 -0
  4. package/dist/config.d.ts +1 -0
  5. package/dist/config.js +6 -10
  6. package/dist/deposit.js +4 -11
  7. package/dist/depositSPL.d.ts +19 -0
  8. package/dist/depositSPL.js +434 -0
  9. package/dist/exportUtils.d.ts +3 -0
  10. package/dist/exportUtils.js +3 -0
  11. package/dist/getUtxos.d.ts +2 -1
  12. package/dist/getUtxos.js +68 -79
  13. package/dist/getUtxosSPL.d.ts +31 -0
  14. package/dist/getUtxosSPL.js +369 -0
  15. package/dist/index.d.ts +31 -1
  16. package/dist/index.js +73 -3
  17. package/dist/models/utxo.js +10 -1
  18. package/dist/utils/address_lookup_table.d.ts +1 -0
  19. package/dist/utils/address_lookup_table.js +23 -0
  20. package/dist/utils/constants.d.ts +3 -2
  21. package/dist/utils/constants.js +5 -4
  22. package/dist/utils/encryption.d.ts +1 -1
  23. package/dist/utils/encryption.js +5 -3
  24. package/dist/utils/utils.d.ts +3 -2
  25. package/dist/utils/utils.js +26 -6
  26. package/dist/withdraw.js +3 -4
  27. package/dist/withdrawSPL.d.ts +22 -0
  28. package/dist/withdrawSPL.js +289 -0
  29. package/package.json +5 -3
  30. package/src/config.ts +7 -14
  31. package/src/deposit.ts +4 -11
  32. package/src/depositSPL.ts +556 -0
  33. package/src/exportUtils.ts +5 -1
  34. package/src/getUtxos.ts +73 -78
  35. package/src/getUtxosSPL.ts +495 -0
  36. package/src/index.ts +84 -3
  37. package/src/models/utxo.ts +10 -1
  38. package/src/utils/address_lookup_table.ts +54 -6
  39. package/src/utils/constants.ts +7 -5
  40. package/src/utils/encryption.ts +6 -3
  41. package/src/utils/utils.ts +29 -6
  42. package/src/withdraw.ts +4 -6
  43. package/src/withdrawSPL.ts +377 -0
package/src/index.ts CHANGED
@@ -1,8 +1,9 @@
1
1
  import { Connection, Keypair, LAMPORTS_PER_SOL, PublicKey, VersionedTransaction } from '@solana/web3.js';
2
2
  import { deposit } from './deposit.js';
3
3
  import { getBalanceFromUtxos, getUtxos, localstorageKey } from './getUtxos.js';
4
+ import { getBalanceFromUtxosSPL, getUtxosSPL } from './getUtxosSPL.js';
4
5
 
5
- import { LSK_ENCRYPTED_OUTPUTS, LSK_FETCH_OFFSET } from './utils/constants.js';
6
+ import { LSK_ENCRYPTED_OUTPUTS, LSK_FETCH_OFFSET, USDC_MINT } from './utils/constants.js';
6
7
  import { logger, type LoggerFn, setLogger } from './utils/logger.js';
7
8
  import { EncryptionService } from './utils/encryption.js';
8
9
  import { WasmFactory } from '@lightprotocol/hasher.rs';
@@ -10,6 +11,9 @@ import bs58 from 'bs58'
10
11
  import { withdraw } from './withdraw.js';
11
12
  import { LocalStorage } from "node-localstorage";
12
13
  import path from 'node:path'
14
+ import { depositSPL } from './depositSPL.js';
15
+ import { withdrawSPL } from './withdrawSPL.js';
16
+ import { getAssociatedTokenAddress } from '@solana/spl-token';
13
17
 
14
18
  let storage = new LocalStorage(path.join(process.cwd(), "cache"));
15
19
 
@@ -65,6 +69,16 @@ export class PrivacyCash {
65
69
  }
66
70
  storage.removeItem(LSK_FETCH_OFFSET + localstorageKey(this.publicKey))
67
71
  storage.removeItem(LSK_ENCRYPTED_OUTPUTS + localstorageKey(this.publicKey))
72
+ // spl
73
+ let mintAddresses = [USDC_MINT]
74
+ for (let mintAddress of mintAddresses) {
75
+ let ata = await getAssociatedTokenAddress(
76
+ mintAddress,
77
+ this.publicKey
78
+ );
79
+ storage.removeItem(LSK_FETCH_OFFSET + localstorageKey(ata))
80
+ storage.removeItem(LSK_ENCRYPTED_OUTPUTS + localstorageKey(ata))
81
+ }
68
82
  return this
69
83
  }
70
84
 
@@ -96,6 +110,33 @@ export class PrivacyCash {
96
110
  return res
97
111
  }
98
112
 
113
+ /**
114
+ * Deposit USDC to the Privacy Cash.
115
+ */
116
+ async depositUSDC({ base_units }: {
117
+ base_units: number
118
+ }) {
119
+ this.isRuning = true
120
+ logger.info('start depositting')
121
+ let lightWasm = await WasmFactory.getInstance()
122
+ let res = await depositSPL({
123
+ mintAddress: USDC_MINT,
124
+ lightWasm,
125
+ base_units: base_units,
126
+ connection: this.connection,
127
+ encryptionService: this.encryptionService,
128
+ publicKey: this.publicKey,
129
+ transactionSigner: async (tx: VersionedTransaction) => {
130
+ tx.sign([this.keypair])
131
+ return tx
132
+ },
133
+ keyBasePath: path.join(import.meta.dirname, '..', 'circuit2', 'transaction2'),
134
+ storage
135
+ })
136
+ this.isRuning = false
137
+ return res
138
+ }
139
+
99
140
  /**
100
141
  * Withdraw SOL from the Privacy Cash.
101
142
  *
@@ -124,17 +165,57 @@ export class PrivacyCash {
124
165
  return res
125
166
  }
126
167
 
168
+ /**
169
+ * Withdraw USDC from the Privacy Cash.
170
+ *
171
+ * 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' })
172
+ */
173
+ async withdrawUSDC({ base_units, recipientAddress }: {
174
+ base_units: number,
175
+ recipientAddress?: string
176
+ }) {
177
+ this.isRuning = true
178
+ logger.info('start withdrawing')
179
+ let lightWasm = await WasmFactory.getInstance()
180
+ let recipient = recipientAddress ? new PublicKey(recipientAddress) : this.publicKey
181
+ let res = await withdrawSPL({
182
+ mintAddress: USDC_MINT,
183
+ lightWasm,
184
+ base_units,
185
+ connection: this.connection,
186
+ encryptionService: this.encryptionService,
187
+ publicKey: this.publicKey,
188
+ recipient,
189
+ keyBasePath: path.join(import.meta.dirname, '..', 'circuit2', 'transaction2'),
190
+ storage
191
+ })
192
+ console.log(`Withdraw successful. Recipient ${recipient} received ${base_units} USDC units`)
193
+ this.isRuning = false
194
+ return res
195
+ }
196
+
127
197
  /**
128
198
  * Returns the amount of lamports current wallet has in Privacy Cash.
129
199
  */
130
- async getPrivateBalance() {
200
+ async getPrivateBalance(abortSignal?: AbortSignal) {
131
201
  logger.info('getting private balance')
132
202
  this.isRuning = true
133
- let utxos = await getUtxos({ publicKey: this.publicKey, connection: this.connection, encryptionService: this.encryptionService, storage })
203
+ let utxos = await getUtxos({ publicKey: this.publicKey, connection: this.connection, encryptionService: this.encryptionService, storage, abortSignal })
134
204
  this.isRuning = false
135
205
  return getBalanceFromUtxos(utxos)
136
206
  }
137
207
 
208
+ /**
209
+ * Returns the amount of lamports current wallet has in Privacy Cash.
210
+ */
211
+ async getPrivateBalanceUSDC() {
212
+ logger.info('getting private balance')
213
+ this.isRuning = true
214
+ let utxos = await getUtxosSPL({ publicKey: this.publicKey, connection: this.connection, encryptionService: this.encryptionService, storage, mintAddress: USDC_MINT })
215
+ this.isRuning = false
216
+ return getBalanceFromUtxosSPL(utxos)
217
+ }
218
+
138
219
  /**
139
220
  * Returns true if the code is running in a browser.
140
221
  */
@@ -9,6 +9,8 @@ import BN from 'bn.js';
9
9
  import { Keypair } from './keypair.js';
10
10
  import * as hasher from '@lightprotocol/hasher.rs';
11
11
  import { ethers } from 'ethers';
12
+ import { getMintAddressField } from '../utils/utils.js';
13
+ import { PublicKey } from '@solana/web3.js';
12
14
  /**
13
15
  * Simplified Utxo class inspired by Tornado Cash Nova
14
16
  * Based on: https://github.com/tornadocash/tornado-nova/blob/f9264eeffe48bf5e04e19d8086ee6ec58cdf0d9e/src/utxo.js
@@ -57,7 +59,14 @@ export class Utxo {
57
59
  }
58
60
 
59
61
  async getCommitment(): Promise<string> {
60
- return this.lightWasm.poseidonHashString([this.amount.toString(), this.keypair.pubkey.toString(), this.blinding.toString(), this.mintAddress]);
62
+ // return this.lightWasm.poseidonHashString([this.amount.toString(), this.keypair.pubkey.toString(), this.blinding.toString(), this.mintAddress]);
63
+ const mintAddressField = getMintAddressField(new PublicKey(this.mintAddress));
64
+ return this.lightWasm.poseidonHashString([
65
+ this.amount.toString(),
66
+ this.keypair.pubkey.toString(),
67
+ this.blinding.toString(),
68
+ mintAddressField
69
+ ]);
61
70
  }
62
71
 
63
72
  async getNullifier(): Promise<string> {
@@ -1,7 +1,17 @@
1
- import {
2
- Connection,
3
- PublicKey
1
+ import {
2
+ Connection,
3
+ Keypair,
4
+ PublicKey,
5
+ SystemProgram,
6
+ AddressLookupTableProgram,
7
+ Transaction,
8
+ sendAndConfirmTransaction,
9
+ ComputeBudgetProgram,
10
+ VersionedTransaction,
11
+ TransactionMessage
4
12
  } from '@solana/web3.js';
13
+ import { ASSOCIATED_TOKEN_PROGRAM_ID, TOKEN_PROGRAM_ID } from '@solana/spl-token';
14
+
5
15
 
6
16
  /**
7
17
  * Helper function to use an existing ALT (recommended for production)
@@ -14,16 +24,54 @@ export async function useExistingALT(
14
24
  try {
15
25
  console.log(`Using existing ALT: ${altAddress.toString()}`);
16
26
  const altAccount = await connection.getAddressLookupTable(altAddress);
17
-
27
+
18
28
  if (altAccount.value) {
19
29
  console.log(`✅ ALT found with ${altAccount.value.state.addresses.length} addresses`);
20
30
  } else {
21
31
  console.log('❌ ALT not found');
22
32
  }
23
-
33
+
24
34
  return altAccount;
25
35
  } catch (error) {
26
36
  console.error('Error getting existing ALT:', error);
27
37
  return null;
28
38
  }
29
- }
39
+ }
40
+
41
+
42
+ export function getProtocolAddressesWithMint(
43
+ programId: PublicKey,
44
+ authority: PublicKey,
45
+ treeAta: PublicKey,
46
+ feeRecipient: PublicKey,
47
+ feeRecipientAta: PublicKey
48
+ ): PublicKey[] {
49
+ // Derive global config PDA
50
+ const [globalConfigAccount] = PublicKey.findProgramAddressSync(
51
+ [Buffer.from('global_config')],
52
+ programId
53
+ );
54
+
55
+ // Derive tree accounts
56
+ const [treeAccount] = PublicKey.findProgramAddressSync(
57
+ [Buffer.from('merkle_tree')],
58
+ programId
59
+ );
60
+
61
+ return [
62
+ // Core program accounts (constant)
63
+ programId,
64
+ treeAccount,
65
+ treeAta,
66
+ globalConfigAccount,
67
+ authority,
68
+ feeRecipient,
69
+ feeRecipientAta,
70
+
71
+ // System programs (constant)
72
+ SystemProgram.programId,
73
+ ComputeBudgetProgram.programId,
74
+ ASSOCIATED_TOKEN_PROGRAM_ID,
75
+ TOKEN_PROGRAM_ID,
76
+ ];
77
+ }
@@ -3,9 +3,7 @@ import BN from 'bn.js';
3
3
 
4
4
  export const FIELD_SIZE = new BN('21888242871839275222246405745257275088548364400416034343698204186575808495617')
5
5
 
6
- export const PROGRAM_ID = new PublicKey('9fhQBbumKEFuXtMBDw8AaQyAjCorLGJQiS3skWZdQyQD');
7
-
8
- export const DEPLOYER_ID = new PublicKey('AWexibGxNFKTa1b5R5MN4PJr9HWnWRwf8EW9g8cLx3dM')
6
+ export const PROGRAM_ID = process.env.NEXT_PUBLIC_PROGRAM_ID ? new PublicKey(process.env.NEXT_PUBLIC_PROGRAM_ID) : new PublicKey('9fhQBbumKEFuXtMBDw8AaQyAjCorLGJQiS3skWZdQyQD');
9
7
 
10
8
  export const FEE_RECIPIENT = new PublicKey('AWexibGxNFKTa1b5R5MN4PJr9HWnWRwf8EW9g8cLx3dM')
11
9
 
@@ -13,14 +11,18 @@ export const FETCH_UTXOS_GROUP_SIZE = 10_000
13
11
 
14
12
  export const TRANSACT_IX_DISCRIMINATOR = Buffer.from([217, 149, 130, 143, 221, 52, 252, 119]);
15
13
 
14
+ export const TRANSACT_SPL_IX_DISCRIMINATOR = Buffer.from([154, 66, 244, 204, 78, 225, 163, 151]);
15
+
16
16
  export const MERKLE_TREE_DEPTH = 26;
17
17
 
18
- export const ALT_ADDRESS = new PublicKey('72bpRay17JKp4k8H87p7ieU9C6aRDy5yCqwvtpTN2wuU');
18
+ export const ALT_ADDRESS = process.env.NEXT_PUBLIC_ALT_ADDRESS ? new PublicKey(process.env.NEXT_PUBLIC_ALT_ADDRESS) : new PublicKey('HEN49U2ySJ85Vc78qprSW9y6mFDhs1NczRxyppNHjofe');
19
19
 
20
- export const INDEXER_API_URL = process.env.NEXT_PUBLIC_INDEXER_API_URL ?? 'https://api3.privacycash.org';
20
+ export const RELAYER_API_URL = process.env.NEXT_PUBLIC_RELAYER_API_URL ?? 'https://api3.privacycash.org';
21
21
 
22
22
  export const SIGN_MESSAGE = `Privacy Money account sign in`
23
23
 
24
24
  // localStorage cache keys
25
25
  export const LSK_FETCH_OFFSET = 'fetch_offset'
26
26
  export const LSK_ENCRYPTED_OUTPUTS = 'encrypted_outputs'
27
+
28
+ export const USDC_MINT = process.env.NEXT_PUBLIC_USDC_MINT ? new PublicKey(process.env.NEXT_PUBLIC_USDC_MINT) : new PublicKey('EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v')
@@ -5,7 +5,7 @@ import { Utxo } from '../models/utxo.js';
5
5
  import { WasmFactory } from '@lightprotocol/hasher.rs';
6
6
  import { Keypair as UtxoKeypair } from '../models/keypair.js';
7
7
  import { keccak256 } from '@ethersproject/keccak256';
8
- import { PROGRAM_ID, TRANSACT_IX_DISCRIMINATOR } from './constants.js';
8
+ import { PROGRAM_ID, TRANSACT_IX_DISCRIMINATOR, TRANSACT_SPL_IX_DISCRIMINATOR } from './constants.js';
9
9
  import BN from 'bn.js';
10
10
 
11
11
 
@@ -426,16 +426,19 @@ export class EncryptionService {// Version identifier for encryption scheme (8-b
426
426
  }
427
427
  }
428
428
 
429
- export function serializeProofAndExtData(proof: any, extData: any) {
429
+ export function serializeProofAndExtData(proof: any, extData: any, isSpl: boolean = false) {
430
430
  // Create the ExtDataMinified object for the program call (only extAmount and fee)
431
431
  const extDataMinified = {
432
432
  extAmount: extData.extAmount,
433
433
  fee: extData.fee
434
434
  };
435
435
 
436
+ // Use the appropriate discriminator based on whether this is SPL or native SOL
437
+ const discriminator = isSpl ? TRANSACT_SPL_IX_DISCRIMINATOR : TRANSACT_IX_DISCRIMINATOR;
438
+
436
439
  // Use the same serialization approach as deposit script
437
440
  const instructionData = Buffer.concat([
438
- TRANSACT_IX_DISCRIMINATOR,
441
+ discriminator,
439
442
  // Serialize proof
440
443
  Buffer.from(proof.proofA),
441
444
  Buffer.from(proof.proofB),
@@ -10,7 +10,7 @@ import { Utxo } from '../models/utxo.js';
10
10
  import * as borsh from 'borsh';
11
11
  import { sha256 } from '@ethersproject/sha2';
12
12
  import { PublicKey } from '@solana/web3.js';
13
- import { INDEXER_API_URL, PROGRAM_ID } from './constants.js';
13
+ import { RELAYER_API_URL, PROGRAM_ID } from './constants.js';
14
14
  import { logger } from './logger.js';
15
15
  import { getConfig } from '../config.js';
16
16
 
@@ -115,12 +115,16 @@ export function getExtDataHash(extData: {
115
115
 
116
116
 
117
117
  // Function to fetch Merkle proof from API for a given commitment
118
- export async function fetchMerkleProof(commitment: string): Promise<{ pathElements: string[], pathIndices: number[] }> {
118
+ export async function fetchMerkleProof(commitment: string, tokenName?: string): Promise<{ pathElements: string[], pathIndices: number[] }> {
119
119
  try {
120
120
  logger.debug(`Fetching Merkle proof for commitment: ${commitment}`);
121
- const response = await fetch(`${INDEXER_API_URL}/merkle/proof/${commitment}`);
121
+ let url = `${RELAYER_API_URL}/merkle/proof/${commitment}`
122
+ if (tokenName) {
123
+ url += '?token=' + tokenName
124
+ }
125
+ const response = await fetch(url);
122
126
  if (!response.ok) {
123
- throw new Error(`Failed to fetch Merkle proof: ${response.status} ${response.statusText}`);
127
+ throw new Error(`Failed to fetch Merkle proof: ${url}`);
124
128
  }
125
129
  const data = await response.json() as { pathElements: string[], pathIndices: number[] };
126
130
  logger.debug(`✓ Fetched Merkle proof with ${data.pathElements.length} elements`);
@@ -147,10 +151,14 @@ export function findNullifierPDAs(proof: any) {
147
151
  }
148
152
 
149
153
  // Function to query remote tree state from indexer API
150
- export async function queryRemoteTreeState(): Promise<{ root: string, nextIndex: number }> {
154
+ export async function queryRemoteTreeState(tokenName?: string): Promise<{ root: string, nextIndex: number }> {
151
155
  try {
152
156
  logger.debug('Fetching Merkle root and nextIndex from API...');
153
- const response = await fetch(`${INDEXER_API_URL}/merkle/root`);
157
+ let url = `${RELAYER_API_URL}/merkle/root`
158
+ if (tokenName) {
159
+ url += '?token=' + tokenName
160
+ }
161
+ const response = await fetch(url);
154
162
  if (!response.ok) {
155
163
  throw new Error(`Failed to fetch Merkle root and nextIndex: ${response.status} ${response.statusText}`);
156
164
  }
@@ -196,4 +204,19 @@ export function findCrossCheckNullifierPDAs(proof: any) {
196
204
  );
197
205
 
198
206
  return { nullifier2PDA, nullifier3PDA };
207
+ }
208
+
209
+ export function getMintAddressField(mint: PublicKey): string {
210
+ const mintStr = mint.toString();
211
+
212
+ // Special case for SOL (system program)
213
+ if (mintStr === '11111111111111111111111111111112') {
214
+ return mintStr;
215
+ }
216
+
217
+ // For SPL tokens (USDC, USDT, etc): use first 31 bytes (248 bits)
218
+ // This provides better collision resistance than 8 bytes while still fitting in the field
219
+ // We will only suppport private SOL, USDC and USDT send, so there won't be any collision.
220
+ const mintBytes = mint.toBytes();
221
+ return new BN(mintBytes.slice(0, 31), 'be').toString();
199
222
  }
package/src/withdraw.ts CHANGED
@@ -6,7 +6,7 @@ import * as hasher from '@lightprotocol/hasher.rs';
6
6
  import { Utxo } from './models/utxo.js';
7
7
  import { parseProofToBytesArray, parseToBytesArray, prove } from './utils/prover.js';
8
8
 
9
- import { ALT_ADDRESS, DEPLOYER_ID, FEE_RECIPIENT, FIELD_SIZE, INDEXER_API_URL, MERKLE_TREE_DEPTH, PROGRAM_ID } from './utils/constants.js';
9
+ import { ALT_ADDRESS, FEE_RECIPIENT, FIELD_SIZE, RELAYER_API_URL, MERKLE_TREE_DEPTH, PROGRAM_ID } from './utils/constants.js';
10
10
  import { EncryptionService, serializeProofAndExtData } from './utils/encryption.js';
11
11
  import { fetchMerkleProof, findNullifierPDAs, getExtDataHash, getProgramAccounts, queryRemoteTreeState, findCrossCheckNullifierPDAs } from './utils/utils.js';
12
12
 
@@ -20,7 +20,7 @@ import { getConfig } from './config.js';
20
20
  async function submitWithdrawToIndexer(params: any): Promise<string> {
21
21
  try {
22
22
 
23
- const response = await fetch(`${INDEXER_API_URL}/withdraw`, {
23
+ const response = await fetch(`${RELAYER_API_URL}/withdraw`, {
24
24
  method: 'POST',
25
25
  headers: {
26
26
  'Content-Type': 'application/json',
@@ -62,8 +62,6 @@ export async function withdraw({ recipient, lightWasm, storage, publicKey, conne
62
62
 
63
63
  logger.debug('Encryption key generated from user keypair');
64
64
 
65
- logger.debug(`Deployer wallet: ${DEPLOYER_ID.toString()}`);
66
-
67
65
  const { treeAccount, treeTokenAccount, globalConfigAccount } = getProgramAccounts()
68
66
 
69
67
  // Get current tree state
@@ -319,7 +317,7 @@ export async function withdraw({ recipient, lightWasm, storage, publicKey, conne
319
317
  console.log(`retryTimes: ${retryTimes}`)
320
318
  await new Promise(resolve => setTimeout(resolve, itv * 1000));
321
319
  console.log('Fetching updated tree state...');
322
- let res = await fetch(INDEXER_API_URL + '/utxos/check/' + encryptedOutputStr)
320
+ let res = await fetch(RELAYER_API_URL + '/utxos/check/' + encryptedOutputStr)
323
321
  let resJson = await res.json()
324
322
  console.log('resJson:', resJson)
325
323
  if (resJson.exists) {
@@ -331,4 +329,4 @@ export async function withdraw({ recipient, lightWasm, storage, publicKey, conne
331
329
  retryTimes++
332
330
  }
333
331
 
334
- }
332
+ }