privacycash 1.1.19 → 1.1.21

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/deposit.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { Connection, PublicKey, VersionedTransaction } from '@solana/web3.js';
2
1
  import * as hasher from '@lightprotocol/hasher.rs';
2
+ import { Connection, PublicKey, VersionedTransaction } from '@solana/web3.js';
3
3
  import { EncryptionService } from './utils/encryption.js';
4
4
  type DepositParams = {
5
5
  publicKey: PublicKey;
package/dist/deposit.js CHANGED
@@ -1,15 +1,15 @@
1
- import { PublicKey, TransactionInstruction, SystemProgram, ComputeBudgetProgram, VersionedTransaction, TransactionMessage, LAMPORTS_PER_SOL } from '@solana/web3.js';
1
+ import { ComputeBudgetProgram, LAMPORTS_PER_SOL, PublicKey, SystemProgram, TransactionInstruction, TransactionMessage, VersionedTransaction } from '@solana/web3.js';
2
2
  import BN from 'bn.js';
3
- import { Utxo } from './models/utxo.js';
4
- import { fetchMerkleProof, findNullifierPDAs, getExtDataHash, getProgramAccounts, queryRemoteTreeState, findCrossCheckNullifierPDAs } from './utils/utils.js';
5
- import { prove, parseProofToBytesArray, parseToBytesArray } from './utils/prover.js';
6
- import { MerkleTree } from './utils/merkle_tree.js';
7
- import { serializeProofAndExtData } from './utils/encryption.js';
8
- import { Keypair as UtxoKeypair } from './models/keypair.js';
9
3
  import { getUtxos } from './getUtxos.js';
10
- import { FIELD_SIZE, FEE_RECIPIENT, MERKLE_TREE_DEPTH, RELAYER_API_URL, PROGRAM_ID, ALT_ADDRESS } from './utils/constants.js';
4
+ import { Keypair as UtxoKeypair } from './models/keypair.js';
5
+ import { Utxo } from './models/utxo.js';
11
6
  import { useExistingALT } from './utils/address_lookup_table.js';
7
+ import { ALT_ADDRESS, FEE_RECIPIENT, FIELD_SIZE, MERKLE_TREE_DEPTH, PROGRAM_ID, RELAYER_API_URL } from './utils/constants.js';
8
+ import { serializeProofAndExtData } from './utils/encryption.js';
12
9
  import { logger } from './utils/logger.js';
10
+ import { MerkleTree } from './utils/merkle_tree.js';
11
+ import { parseProofToBytesArray, parseToBytesArray, prove } from './utils/prover.js';
12
+ import { fetchMerkleProof, findCrossCheckNullifierPDAs, findNullifierPDAs, getExtDataHash, getProgramAccounts, queryRemoteTreeState } from './utils/utils.js';
13
13
  // Function to relay pre-signed deposit transaction to indexer backend
14
14
  async function relayDepositToIndexer(signedTransaction, publicKey, referrer) {
15
15
  try {
@@ -68,10 +68,6 @@ export async function deposit({ lightWasm, storage, keyBasePath, publicKey, conn
68
68
  const { treeAccount, treeTokenAccount, globalConfigAccount } = getProgramAccounts();
69
69
  // Create the merkle tree with the pre-initialized poseidon hash
70
70
  const tree = new MerkleTree(MERKLE_TREE_DEPTH, lightWasm);
71
- // Initialize root and nextIndex variables
72
- const { root, nextIndex: currentNextIndex } = await queryRemoteTreeState();
73
- logger.debug(`Using tree root: ${root}`);
74
- logger.debug(`New UTXOs will be inserted at indices: ${currentNextIndex} and ${currentNextIndex + 1}`);
75
71
  // Generate a deterministic private key derived from the wallet keypair
76
72
  // const utxoPrivateKey = encryptionService.deriveUtxoPrivateKey();
77
73
  const utxoPrivateKey = encryptionService.getUtxoPrivateKeyV2();
@@ -88,7 +84,12 @@ export async function deposit({ lightWasm, storage, keyBasePath, publicKey, conn
88
84
  let inputs;
89
85
  let inputMerklePathIndices;
90
86
  let inputMerklePathElements;
87
+ let root;
88
+ let nextIndex;
91
89
  if (existingUnspentUtxos.length === 0) {
90
+ const treeState = await queryRemoteTreeState();
91
+ root = treeState.root;
92
+ nextIndex = treeState.nextIndex;
92
93
  // Scenario 1: Fresh deposit with dummy inputs - add new funds to the system
93
94
  extAmount = amount_in_lamports;
94
95
  outputAmount = new BN(amount_in_lamports).sub(new BN(fee_amount_in_lamports)).toString();
@@ -144,15 +145,18 @@ export async function deposit({ lightWasm, storage, keyBasePath, publicKey, conn
144
145
  ];
145
146
  // Fetch Merkle proofs for real UTXOs
146
147
  const firstUtxoCommitment = await firstUtxo.getCommitment();
147
- const firstUtxoMerkleProof = await fetchMerkleProof(firstUtxoCommitment);
148
- let secondUtxoMerkleProof;
148
+ let commitmentsToFetch = [firstUtxoCommitment];
149
149
  if (secondUtxo.amount.gt(new BN(0))) {
150
150
  // Second UTXO is real, fetch its proof
151
151
  const secondUtxoCommitment = await secondUtxo.getCommitment();
152
- secondUtxoMerkleProof = await fetchMerkleProof(secondUtxoCommitment);
152
+ commitmentsToFetch.push(secondUtxoCommitment);
153
153
  logger.debug('\nSecond UTXO to be consolidated:');
154
154
  await secondUtxo.log();
155
155
  }
156
+ let data = await fetchMerkleProof(commitmentsToFetch);
157
+ root = data.root;
158
+ nextIndex = data.nextIndex;
159
+ let [firstUtxoMerkleProof, secondUtxoMerkleProof] = data.proofs;
156
160
  // Use the real pathIndices from API for real inputs, mock index for dummy input
157
161
  inputMerklePathIndices = [
158
162
  firstUtxo.index || 0, // Use the real UTXO's index
@@ -178,13 +182,13 @@ export async function deposit({ lightWasm, storage, keyBasePath, publicKey, conn
178
182
  lightWasm,
179
183
  amount: outputAmount,
180
184
  keypair: utxoKeypair,
181
- index: currentNextIndex // This UTXO will be inserted at currentNextIndex
185
+ index: nextIndex // This UTXO will be inserted at currentNextIndex
182
186
  }), // Output with value (either deposit amount minus fee, or input amount minus fee)
183
187
  new Utxo({
184
188
  lightWasm,
185
189
  amount: '0',
186
190
  keypair: utxoKeypair,
187
- index: currentNextIndex + 1 // This UTXO will be inserted at currentNextIndex + 1
191
+ index: nextIndex + 1 // This UTXO will be inserted at currentNextIndex + 1
188
192
  }) // Empty UTXO
189
193
  ];
190
194
  // Verify this matches the circuit balance equation: sumIns + publicAmount = sumOuts
@@ -225,7 +229,7 @@ export async function deposit({ lightWasm, storage, keyBasePath, publicKey, conn
225
229
  // Create the deposit ExtData with real encrypted outputs
226
230
  const extData = {
227
231
  // recipient - just a placeholder, not actually used for deposits.
228
- recipient: new PublicKey('AWexibGxNFKTa1b5R5MN4PJr9HWnWRwf8EW9g8cLx3dM'),
232
+ recipient: new PublicKey(FEE_RECIPIENT).toString(), // Using fee recipient as dummy recipient for deposit
229
233
  extAmount: new BN(extAmount),
230
234
  encryptedOutput1: encryptedOutput1,
231
235
  encryptedOutput2: encryptedOutput2,
@@ -302,7 +306,7 @@ export async function deposit({ lightWasm, storage, keyBasePath, publicKey, conn
302
306
  { pubkey: treeTokenAccount, isSigner: false, isWritable: true },
303
307
  { pubkey: globalConfigAccount, isSigner: false, isWritable: false },
304
308
  // recipient - just a placeholder, not actually used for deposits. using an ALT address to save bytes
305
- { pubkey: new PublicKey('AWexibGxNFKTa1b5R5MN4PJr9HWnWRwf8EW9g8cLx3dM'), isSigner: false, isWritable: true },
309
+ { pubkey: new PublicKey(FEE_RECIPIENT.toString()), isSigner: false, isWritable: true },
306
310
  // fee recipient
307
311
  { pubkey: FEE_RECIPIENT, isSigner: false, isWritable: true },
308
312
  // signer
@@ -1,5 +1,5 @@
1
- import { Connection, PublicKey, VersionedTransaction } from '@solana/web3.js';
2
1
  import * as hasher from '@lightprotocol/hasher.rs';
2
+ import { Connection, PublicKey, VersionedTransaction } from '@solana/web3.js';
3
3
  import { EncryptionService } from './utils/encryption.js';
4
4
  type DepositParams = {
5
5
  mintAddress: PublicKey | string;
@@ -1,16 +1,16 @@
1
- import { PublicKey, TransactionInstruction, SystemProgram, ComputeBudgetProgram, VersionedTransaction, TransactionMessage } from '@solana/web3.js';
1
+ import { ASSOCIATED_TOKEN_PROGRAM_ID, TOKEN_PROGRAM_ID, getAccount, getAssociatedTokenAddressSync } from '@solana/spl-token';
2
+ import { ComputeBudgetProgram, PublicKey, SystemProgram, TransactionInstruction, TransactionMessage, VersionedTransaction } from '@solana/web3.js';
2
3
  import BN from 'bn.js';
3
- import { Utxo } from './models/utxo.js';
4
- import { fetchMerkleProof, findNullifierPDAs, getProgramAccounts, queryRemoteTreeState, findCrossCheckNullifierPDAs, getExtDataHash, getMintAddressField } from './utils/utils.js';
5
- import { prove, parseProofToBytesArray, parseToBytesArray } from './utils/prover.js';
6
- import { MerkleTree } from './utils/merkle_tree.js';
7
- import { serializeProofAndExtData } from './utils/encryption.js';
8
- import { Keypair as UtxoKeypair } from './models/keypair.js';
9
4
  import { getUtxosSPL } from './getUtxosSPL.js';
10
- import { FIELD_SIZE, FEE_RECIPIENT, MERKLE_TREE_DEPTH, RELAYER_API_URL, PROGRAM_ID, ALT_ADDRESS, tokens } from './utils/constants.js';
5
+ import { Keypair as UtxoKeypair } from './models/keypair.js';
6
+ import { Utxo } from './models/utxo.js';
11
7
  import { useExistingALT } from './utils/address_lookup_table.js';
8
+ import { ALT_ADDRESS, FEE_RECIPIENT, FIELD_SIZE, MERKLE_TREE_DEPTH, PROGRAM_ID, RELAYER_API_URL, tokens } from './utils/constants.js';
9
+ import { serializeProofAndExtData } from './utils/encryption.js';
12
10
  import { logger } from './utils/logger.js';
13
- import { ASSOCIATED_TOKEN_PROGRAM_ID, TOKEN_PROGRAM_ID, getAssociatedTokenAddressSync, getAccount } from '@solana/spl-token';
11
+ import { MerkleTree } from './utils/merkle_tree.js';
12
+ import { parseProofToBytesArray, parseToBytesArray, prove } from './utils/prover.js';
13
+ import { fetchMerkleProof, findCrossCheckNullifierPDAs, findNullifierPDAs, getExtDataHash, getMintAddressField, getProgramAccounts, queryRemoteTreeState } from './utils/utils.js';
14
14
  // Function to relay pre-signed deposit transaction to indexer backend
15
15
  async function relayDepositToIndexer({ signedTransaction, publicKey, referrer, mintAddress }) {
16
16
  try {
@@ -72,7 +72,7 @@ export async function depositSPL({ lightWasm, storage, keyBasePath, publicKey, c
72
72
  }
73
73
  // let mintInfo = await getMint(connection, token.pubkey)
74
74
  // let units_per_token = 10 ** mintInfo.decimals
75
- let recipient = new PublicKey('AWexibGxNFKTa1b5R5MN4PJr9HWnWRwf8EW9g8cLx3dM');
75
+ let recipient = new PublicKey(FEE_RECIPIENT.toString()); // Using fee recipient as dummy recipient for deposit
76
76
  let recipient_ata = getAssociatedTokenAddressSync(token.pubkey, recipient, true);
77
77
  let feeRecipientTokenAccount = getAssociatedTokenAddressSync(token.pubkey, FEE_RECIPIENT, true);
78
78
  let signerTokenAccount = getAssociatedTokenAddressSync(token.pubkey, signer);
@@ -106,10 +106,6 @@ export async function depositSPL({ lightWasm, storage, keyBasePath, publicKey, c
106
106
  const { globalConfigAccount } = getProgramAccounts();
107
107
  // Create the merkle tree with the pre-initialized poseidon hash
108
108
  const tree = new MerkleTree(MERKLE_TREE_DEPTH, lightWasm);
109
- // Initialize root and nextIndex variables
110
- const { root, nextIndex: currentNextIndex } = await queryRemoteTreeState(token.name);
111
- logger.debug(`Using tree root: ${root}`);
112
- logger.debug(`New UTXOs will be inserted at indices: ${currentNextIndex} and ${currentNextIndex + 1}`);
113
109
  // Generate a deterministic private key derived from the wallet keypair
114
110
  // const utxoPrivateKey = encryptionService.deriveUtxoPrivateKey();
115
111
  const utxoPrivateKey = encryptionService.getUtxoPrivateKeyV2();
@@ -126,7 +122,12 @@ export async function depositSPL({ lightWasm, storage, keyBasePath, publicKey, c
126
122
  let inputs;
127
123
  let inputMerklePathIndices;
128
124
  let inputMerklePathElements;
125
+ let root;
126
+ let nextIndex;
129
127
  if (mintUtxos.length === 0) {
128
+ const treeState = await queryRemoteTreeState(token.name);
129
+ root = treeState.root;
130
+ nextIndex = treeState.nextIndex;
130
131
  // Scenario 1: Fresh deposit with dummy inputs - add new funds to the system
131
132
  extAmount = base_units;
132
133
  outputAmount = new BN(base_units).sub(new BN(fee_base_units)).toString();
@@ -184,15 +185,18 @@ export async function depositSPL({ lightWasm, storage, keyBasePath, publicKey, c
184
185
  ];
185
186
  // Fetch Merkle proofs for real UTXOs
186
187
  const firstUtxoCommitment = await firstUtxo.getCommitment();
187
- const firstUtxoMerkleProof = await fetchMerkleProof(firstUtxoCommitment, token.name);
188
- let secondUtxoMerkleProof;
188
+ let commitmentsToFetch = [firstUtxoCommitment];
189
189
  if (secondUtxo.amount.gt(new BN(0))) {
190
190
  // Second UTXO is real, fetch its proof
191
191
  const secondUtxoCommitment = await secondUtxo.getCommitment();
192
- secondUtxoMerkleProof = await fetchMerkleProof(secondUtxoCommitment, token.name);
192
+ commitmentsToFetch.push(secondUtxoCommitment);
193
193
  logger.debug('\nSecond UTXO to be consolidated:');
194
194
  await secondUtxo.log();
195
195
  }
196
+ let data = await fetchMerkleProof(commitmentsToFetch, token.name);
197
+ root = data.root;
198
+ nextIndex = data.nextIndex;
199
+ let [firstUtxoMerkleProof, secondUtxoMerkleProof] = data.proofs;
196
200
  // Use the real pathIndices from API for real inputs, mock index for dummy input
197
201
  inputMerklePathIndices = [
198
202
  firstUtxo.index || 0, // Use the real UTXO's index
@@ -218,14 +222,14 @@ export async function depositSPL({ lightWasm, storage, keyBasePath, publicKey, c
218
222
  lightWasm,
219
223
  amount: outputAmount,
220
224
  keypair: utxoKeypair,
221
- index: currentNextIndex, // This UTXO will be inserted at currentNextIndex
225
+ index: nextIndex, // This UTXO will be inserted at currentNextIndex
222
226
  mintAddress: token.pubkey.toString()
223
227
  }), // Output with value (either deposit amount minus fee, or input amount minus fee)
224
228
  new Utxo({
225
229
  lightWasm,
226
230
  amount: '0',
227
231
  keypair: utxoKeypair,
228
- index: currentNextIndex + 1, // This UTXO will be inserted at currentNextIndex
232
+ index: nextIndex + 1, // This UTXO will be inserted at currentNextIndex
229
233
  mintAddress: token.pubkey.toString()
230
234
  }) // Empty UTXO
231
235
  ];
@@ -4,9 +4,9 @@
4
4
  * Provides UTXO functionality for the ZK Cash system
5
5
  * Based on: https://github.com/tornadocash/tornado-nova
6
6
  */
7
+ import * as hasher from '@lightprotocol/hasher.rs';
7
8
  import BN from 'bn.js';
8
9
  import { Keypair } from './keypair.js';
9
- import * as hasher from '@lightprotocol/hasher.rs';
10
10
  /**
11
11
  * Simplified Utxo class inspired by Tornado Cash Nova
12
12
  * Based on: https://github.com/tornadocash/tornado-nova/blob/f9264eeffe48bf5e04e19d8086ee6ec58cdf0d9e/src/utxo.js
@@ -39,7 +39,7 @@ export declare class Utxo {
39
39
  mintAddress?: string;
40
40
  version?: 'v1' | 'v2';
41
41
  });
42
- getCommitment(): Promise<string>;
42
+ getCommitment(): string;
43
43
  getNullifier(): Promise<string>;
44
44
  /**
45
45
  * Log all the UTXO's public properties and derived values in JSON format
@@ -4,11 +4,11 @@
4
4
  * Provides UTXO functionality for the ZK Cash system
5
5
  * Based on: https://github.com/tornadocash/tornado-nova
6
6
  */
7
+ import { PublicKey } from '@solana/web3.js';
7
8
  import BN from 'bn.js';
8
- import { Keypair } from './keypair.js';
9
9
  import { ethers } from 'ethers';
10
10
  import { getMintAddressField } from '../utils/utils.js';
11
- import { PublicKey } from '@solana/web3.js';
11
+ import { Keypair } from './keypair.js';
12
12
  /**
13
13
  * Simplified Utxo class inspired by Tornado Cash Nova
14
14
  * Based on: https://github.com/tornadocash/tornado-nova/blob/f9264eeffe48bf5e04e19d8086ee6ec58cdf0d9e/src/utxo.js
@@ -41,7 +41,7 @@ export class Utxo {
41
41
  this.mintAddress = mintAddress;
42
42
  this.version = version;
43
43
  }
44
- async getCommitment() {
44
+ getCommitment() {
45
45
  // return this.lightWasm.poseidonHashString([this.amount.toString(), this.keypair.pubkey.toString(), this.blinding.toString(), this.mintAddress]);
46
46
  const mintAddressField = getMintAddressField(new PublicKey(this.mintAddress));
47
47
  return this.lightWasm.poseidonHashString([
@@ -2,7 +2,7 @@ import { PublicKey } from '@solana/web3.js';
2
2
  import BN from 'bn.js';
3
3
  export const FIELD_SIZE = new BN('21888242871839275222246405745257275088548364400416034343698204186575808495617');
4
4
  export const PROGRAM_ID = process.env.NEXT_PUBLIC_PROGRAM_ID ? new PublicKey(process.env.NEXT_PUBLIC_PROGRAM_ID) : new PublicKey('9fhQBbumKEFuXtMBDw8AaQyAjCorLGJQiS3skWZdQyQD');
5
- export const FEE_RECIPIENT = new PublicKey('AWexibGxNFKTa1b5R5MN4PJr9HWnWRwf8EW9g8cLx3dM');
5
+ export const FEE_RECIPIENT = new PublicKey('97rSMQUukMDjA7PYErccyx7ZxbHvSDaeXp2ig5BwSrTf');
6
6
  export const FETCH_UTXOS_GROUP_SIZE = 20_000;
7
7
  export const TRANSACT_IX_DISCRIMINATOR = Buffer.from([217, 149, 130, 143, 221, 52, 252, 119]);
8
8
  export const TRANSACT_SPL_IX_DISCRIMINATOR = Buffer.from([154, 66, 244, 204, 78, 225, 163, 151]);
@@ -4,9 +4,9 @@
4
4
  * Provides common utility functions for the ZK Cash system
5
5
  * Based on: https://github.com/tornadocash/tornado-nova
6
6
  */
7
+ import { PublicKey } from '@solana/web3.js';
7
8
  import BN from 'bn.js';
8
9
  import { Utxo } from '../models/utxo.js';
9
- import { PublicKey } from '@solana/web3.js';
10
10
  /**
11
11
  * Calculate deposit fee based on deposit amount and fee rate
12
12
  * @param depositAmount Amount being deposited in lamports
@@ -40,9 +40,14 @@ export declare function getExtDataHash(extData: {
40
40
  feeRecipient: string | PublicKey;
41
41
  mintAddress: string | PublicKey;
42
42
  }): Uint8Array;
43
- export declare function fetchMerkleProof(commitment: string, tokenName?: string): Promise<{
43
+ type Proof = {
44
44
  pathElements: string[];
45
45
  pathIndices: number[];
46
+ };
47
+ export declare function fetchMerkleProof(commitments: string[], tokenName?: string): Promise<{
48
+ proofs: Proof[];
49
+ root: string;
50
+ nextIndex: number;
46
51
  }>;
47
52
  export declare function findNullifierPDAs(proof: any): {
48
53
  nullifier0PDA: PublicKey;
@@ -62,3 +67,4 @@ export declare function findCrossCheckNullifierPDAs(proof: any): {
62
67
  nullifier3PDA: PublicKey;
63
68
  };
64
69
  export declare function getMintAddressField(mint: PublicKey): string;
70
+ export {};
@@ -4,13 +4,13 @@
4
4
  * Provides common utility functions for the ZK Cash system
5
5
  * Based on: https://github.com/tornadocash/tornado-nova
6
6
  */
7
- import BN from 'bn.js';
8
- import * as borsh from 'borsh';
9
7
  import { sha256 } from '@ethersproject/sha2';
10
8
  import { PublicKey } from '@solana/web3.js';
11
- import { RELAYER_API_URL, PROGRAM_ID } from './constants.js';
12
- import { logger } from './logger.js';
9
+ import BN from 'bn.js';
10
+ import * as borsh from 'borsh';
13
11
  import { getConfig } from '../config.js';
12
+ import { PROGRAM_ID, RELAYER_API_URL } from './constants.js';
13
+ import { logger } from './logger.js';
14
14
  /**
15
15
  * Calculate deposit fee based on deposit amount and fee rate
16
16
  * @param depositAmount Amount being deposited in lamports
@@ -91,23 +91,23 @@ export function getExtDataHash(extData) {
91
91
  return Buffer.from(hashHex.slice(2), 'hex');
92
92
  }
93
93
  // Function to fetch Merkle proof from API for a given commitment
94
- export async function fetchMerkleProof(commitment, tokenName) {
94
+ export async function fetchMerkleProof(commitments, tokenName) {
95
95
  try {
96
- logger.debug(`Fetching Merkle proof for commitment: ${commitment}`);
97
- let url = `${RELAYER_API_URL}/merkle/proof/${commitment}`;
96
+ logger.debug(`Fetching Merkle proof for commitment: ${commitments.join(', ')} with token: ${tokenName || 'N/A'}`);
97
+ let url = `${RELAYER_API_URL}/merkle/proofv2/?commitments=${commitments.join(',')}`;
98
98
  if (tokenName) {
99
- url += '?token=' + tokenName;
99
+ url += '&token=' + tokenName;
100
100
  }
101
+ logger.debug(`Fetching Merkle proof from URL: ${url}`);
101
102
  const response = await fetch(url);
102
103
  if (!response.ok) {
103
104
  throw new Error(`Failed to fetch Merkle proof: ${url}`);
104
105
  }
105
106
  const data = await response.json();
106
- logger.debug(`✓ Fetched Merkle proof with ${data.pathElements.length} elements`);
107
107
  return data;
108
108
  }
109
109
  catch (error) {
110
- console.error(`Failed to fetch Merkle proof for commitment ${commitment}:`, error);
110
+ console.error(`Failed to fetch Merkle proof for commitments ${commitments.join(', ')}:`, error);
111
111
  throw error;
112
112
  }
113
113
  }
@@ -1,5 +1,5 @@
1
- import { Connection, PublicKey } from '@solana/web3.js';
2
1
  import * as hasher from '@lightprotocol/hasher.rs';
2
+ import { Connection, PublicKey } from '@solana/web3.js';
3
3
  import { EncryptionService } from './utils/encryption.js';
4
4
  type WithdrawParams = {
5
5
  publicKey: PublicKey;
package/dist/withdraw.js CHANGED
@@ -4,12 +4,12 @@ import { Buffer } from 'buffer';
4
4
  import { Keypair as UtxoKeypair } from './models/keypair.js';
5
5
  import { Utxo } from './models/utxo.js';
6
6
  import { parseProofToBytesArray, parseToBytesArray, prove } from './utils/prover.js';
7
- import { ALT_ADDRESS, FEE_RECIPIENT, FIELD_SIZE, RELAYER_API_URL, MERKLE_TREE_DEPTH } from './utils/constants.js';
7
+ import { ALT_ADDRESS, FEE_RECIPIENT, FIELD_SIZE, RELAYER_API_URL } from './utils/constants.js';
8
8
  import { serializeProofAndExtData } from './utils/encryption.js';
9
- import { fetchMerkleProof, findNullifierPDAs, getExtDataHash, getProgramAccounts, queryRemoteTreeState, findCrossCheckNullifierPDAs } from './utils/utils.js';
9
+ import { fetchMerkleProof, findCrossCheckNullifierPDAs, findNullifierPDAs, getExtDataHash, getProgramAccounts } from './utils/utils.js';
10
+ import { getConfig } from './config.js';
10
11
  import { getUtxos } from './getUtxos.js';
11
12
  import { logger } from './utils/logger.js';
12
- import { getConfig } from './config.js';
13
13
  // Indexer API endpoint
14
14
  // Function to submit withdraw request to indexer backend
15
15
  async function submitWithdrawToIndexer(params) {
@@ -41,10 +41,6 @@ export async function withdraw({ recipient, lightWasm, storage, publicKey, conne
41
41
  let isPartial = false;
42
42
  logger.debug('Encryption key generated from user keypair');
43
43
  const { treeAccount, treeTokenAccount, globalConfigAccount } = getProgramAccounts();
44
- // Get current tree state
45
- const { root, nextIndex: currentNextIndex } = await queryRemoteTreeState();
46
- logger.debug(`Using tree root: ${root}`);
47
- logger.debug(`New UTXOs will be inserted at indices: ${currentNextIndex} and ${currentNextIndex + 1}`);
48
44
  // Generate a deterministic private key derived from the wallet keypair
49
45
  const utxoPrivateKey = encryptionService.deriveUtxoPrivateKey();
50
46
  // Create a UTXO keypair that will be used for all inputs and outputs
@@ -89,20 +85,25 @@ export async function withdraw({ recipient, lightWasm, storage, publicKey, conne
89
85
  const changeAmount = totalInputAmount.sub(new BN(amount_in_lamports)).sub(new BN(fee_in_lamports));
90
86
  logger.debug(`Withdrawing ${amount_in_lamports} lamports with ${fee_in_lamports} fee, ${changeAmount.toString()} as change`);
91
87
  // Get Merkle proofs for both input UTXOs
92
- const inputMerkleProofs = await Promise.all(inputs.map(async (utxo, index) => {
93
- // For dummy UTXO (amount is 0), use a zero-filled proof
94
- if (utxo.amount.eq(new BN(0))) {
95
- return {
96
- pathElements: [...new Array(MERKLE_TREE_DEPTH).fill("0")],
97
- pathIndices: Array(MERKLE_TREE_DEPTH).fill(0)
98
- };
99
- }
100
- // For real UTXOs, fetch the proof from API
101
- const commitment = await utxo.getCommitment();
102
- return fetchMerkleProof(commitment);
103
- }));
88
+ let commitmentsToFetch = inputs.map(utxo => utxo.getCommitment());
89
+ console.log('commitmentsToFetch', commitmentsToFetch);
90
+ const { root, nextIndex, proofs } = await fetchMerkleProof(commitmentsToFetch);
91
+ // const inputMerkleProofs = await Promise.all(
92
+ // inputs.map(async (utxo, index) => {
93
+ // // For dummy UTXO (amount is 0), use a zero-filled proof
94
+ // if (utxo.amount.eq(new BN(0))) {
95
+ // return {
96
+ // pathElements: [...new Array(MERKLE_TREE_DEPTH).fill("0")],
97
+ // pathIndices: Array(MERKLE_TREE_DEPTH).fill(0)
98
+ // };
99
+ // }
100
+ // // For real UTXOs, fetch the proof from API
101
+ // const commitment = await utxo.getCommitment();
102
+ // return fetchMerkleProof(commitment);
103
+ // })
104
+ // );
104
105
  // Extract path elements and indices
105
- const inputMerklePathElements = inputMerkleProofs.map(proof => proof.pathElements);
106
+ const inputMerklePathElements = proofs.map(proof => proof.pathElements);
106
107
  const inputMerklePathIndices = inputs.map(utxo => utxo.index || 0);
107
108
  // Create outputs: first output is change, second is dummy (required by protocol)
108
109
  const outputs = [
@@ -110,13 +111,13 @@ export async function withdraw({ recipient, lightWasm, storage, publicKey, conne
110
111
  lightWasm,
111
112
  amount: changeAmount.toString(),
112
113
  keypair: utxoKeypairV2,
113
- index: currentNextIndex
114
+ index: nextIndex
114
115
  }), // Change output
115
116
  new Utxo({
116
117
  lightWasm,
117
118
  amount: '0',
118
119
  keypair: utxoKeypairV2,
119
- index: currentNextIndex + 1
120
+ index: nextIndex + 1
120
121
  }) // Empty UTXO
121
122
  ];
122
123
  // For withdrawals, extAmount is negative (funds leaving the system)
@@ -1,5 +1,5 @@
1
- import { Connection, PublicKey } from '@solana/web3.js';
2
1
  import * as hasher from '@lightprotocol/hasher.rs';
2
+ import { Connection, PublicKey } from '@solana/web3.js';
3
3
  import { EncryptionService } from './utils/encryption.js';
4
4
  type WithdrawParams = {
5
5
  publicKey: PublicKey;
@@ -4,13 +4,13 @@ import { Buffer } from 'buffer';
4
4
  import { Keypair as UtxoKeypair } from './models/keypair.js';
5
5
  import { Utxo } from './models/utxo.js';
6
6
  import { parseProofToBytesArray, parseToBytesArray, prove } from './utils/prover.js';
7
- import { ALT_ADDRESS, FEE_RECIPIENT, FIELD_SIZE, RELAYER_API_URL, MERKLE_TREE_DEPTH, PROGRAM_ID, tokens } from './utils/constants.js';
7
+ import { ALT_ADDRESS, FEE_RECIPIENT, FIELD_SIZE, PROGRAM_ID, RELAYER_API_URL, tokens } from './utils/constants.js';
8
8
  import { serializeProofAndExtData } from './utils/encryption.js';
9
- import { fetchMerkleProof, findNullifierPDAs, getProgramAccounts, queryRemoteTreeState, findCrossCheckNullifierPDAs, getMintAddressField, getExtDataHash } from './utils/utils.js';
9
+ import { fetchMerkleProof, findCrossCheckNullifierPDAs, findNullifierPDAs, getExtDataHash, getMintAddressField, getProgramAccounts } from './utils/utils.js';
10
+ import { getAssociatedTokenAddressSync, getMint } from '@solana/spl-token';
11
+ import { getConfig } from './config.js';
10
12
  import { getUtxosSPL } from './getUtxosSPL.js';
11
13
  import { logger } from './utils/logger.js';
12
- import { getConfig } from './config.js';
13
- import { getAssociatedTokenAddressSync, getMint } from '@solana/spl-token';
14
14
  // Indexer API endpoint
15
15
  // Function to submit withdraw request to indexer backend
16
16
  async function submitWithdrawToIndexer(params) {
@@ -71,10 +71,6 @@ export async function withdrawSPL({ recipient, lightWasm, storage, publicKey, co
71
71
  // Derive tree account PDA with mint address for SPL (different from SOL version)
72
72
  const [treeAccount] = PublicKey.findProgramAddressSync([Buffer.from('merkle_tree'), token.pubkey.toBuffer()], PROGRAM_ID);
73
73
  const { globalConfigAccount, treeTokenAccount } = getProgramAccounts();
74
- // Get current tree state
75
- const { root, nextIndex: currentNextIndex } = await queryRemoteTreeState(token.name);
76
- logger.debug(`Using tree root: ${root}`);
77
- logger.debug(`New UTXOs will be inserted at indices: ${currentNextIndex} and ${currentNextIndex + 1}`);
78
74
  // Generate a deterministic private key derived from the wallet keypair
79
75
  const utxoPrivateKey = encryptionService.deriveUtxoPrivateKey();
80
76
  // Create a UTXO keypair that will be used for all inputs and outputs
@@ -119,21 +115,10 @@ export async function withdrawSPL({ recipient, lightWasm, storage, publicKey, co
119
115
  // Calculate the change amount (what's left after withdrawal and fee)
120
116
  const changeAmount = totalInputAmount.sub(new BN(base_units)).sub(new BN(fee_base_units));
121
117
  logger.debug(`Withdrawing ${base_units} lamports with ${fee_base_units} fee, ${changeAmount.toString()} as change`);
122
- // Get Merkle proofs for both input UTXOs
123
- const inputMerkleProofs = await Promise.all(inputs.map(async (utxo, index) => {
124
- // For dummy UTXO (amount is 0), use a zero-filled proof
125
- if (utxo.amount.eq(new BN(0))) {
126
- return {
127
- pathElements: [...new Array(MERKLE_TREE_DEPTH).fill("0")],
128
- pathIndices: Array(MERKLE_TREE_DEPTH).fill(0)
129
- };
130
- }
131
- // For real UTXOs, fetch the proof from API
132
- const commitment = await utxo.getCommitment();
133
- return fetchMerkleProof(commitment, token.name);
134
- }));
118
+ let commitmentsToFetch = inputs.map(utxo => utxo.getCommitment());
119
+ const { root, nextIndex, proofs } = await fetchMerkleProof(commitmentsToFetch, token.name);
135
120
  // Extract path elements and indices
136
- const inputMerklePathElements = inputMerkleProofs.map(proof => proof.pathElements);
121
+ const inputMerklePathElements = proofs.map(proof => proof.pathElements);
137
122
  const inputMerklePathIndices = inputs.map(utxo => utxo.index || 0);
138
123
  // Create outputs: first output is change, second is dummy (required by protocol)
139
124
  const outputs = [
@@ -141,14 +126,14 @@ export async function withdrawSPL({ recipient, lightWasm, storage, publicKey, co
141
126
  lightWasm,
142
127
  amount: changeAmount.toString(),
143
128
  keypair: utxoKeypairV2,
144
- index: currentNextIndex,
129
+ index: nextIndex,
145
130
  mintAddress: token.pubkey.toString()
146
131
  }), // Change output
147
132
  new Utxo({
148
133
  lightWasm,
149
134
  amount: '0',
150
135
  keypair: utxoKeypairV2,
151
- index: currentNextIndex + 1,
136
+ index: nextIndex + 1,
152
137
  mintAddress: token.pubkey.toString()
153
138
  }) // Empty UTXO
154
139
  ];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "privacycash",
3
- "version": "1.1.19",
3
+ "version": "1.1.21",
4
4
  "description": "",
5
5
  "main": "dist/index.js",
6
6
  "repository": "https://github.com/Privacy-Cash/privacy-cash-sdk",
package/src/deposit.ts CHANGED
@@ -1,16 +1,16 @@
1
- import { Connection, Keypair, PublicKey, TransactionInstruction, SystemProgram, ComputeBudgetProgram, VersionedTransaction, TransactionMessage, LAMPORTS_PER_SOL } from '@solana/web3.js';
2
- import BN from 'bn.js';
3
- import { Utxo } from './models/utxo.js';
4
- import { fetchMerkleProof, findNullifierPDAs, getExtDataHash, getProgramAccounts, queryRemoteTreeState, findCrossCheckNullifierPDAs } from './utils/utils.js';
5
- import { prove, parseProofToBytesArray, parseToBytesArray } from './utils/prover.js';
6
1
  import * as hasher from '@lightprotocol/hasher.rs';
7
- import { MerkleTree } from './utils/merkle_tree.js';
8
- import { EncryptionService, serializeProofAndExtData } from './utils/encryption.js';
2
+ import { ComputeBudgetProgram, Connection, LAMPORTS_PER_SOL, PublicKey, SystemProgram, TransactionInstruction, TransactionMessage, VersionedTransaction } from '@solana/web3.js';
3
+ import BN from 'bn.js';
4
+ import { getUtxos } from './getUtxos.js';
9
5
  import { Keypair as UtxoKeypair } from './models/keypair.js';
10
- import { getUtxos, isUtxoSpent } from './getUtxos.js';
11
- import { FIELD_SIZE, FEE_RECIPIENT, MERKLE_TREE_DEPTH, RELAYER_API_URL, PROGRAM_ID, ALT_ADDRESS } from './utils/constants.js';
6
+ import { Utxo } from './models/utxo.js';
12
7
  import { useExistingALT } from './utils/address_lookup_table.js';
8
+ import { ALT_ADDRESS, FEE_RECIPIENT, FIELD_SIZE, MERKLE_TREE_DEPTH, PROGRAM_ID, RELAYER_API_URL } from './utils/constants.js';
9
+ import { EncryptionService, serializeProofAndExtData } from './utils/encryption.js';
13
10
  import { logger } from './utils/logger.js';
11
+ import { MerkleTree } from './utils/merkle_tree.js';
12
+ import { parseProofToBytesArray, parseToBytesArray, prove } from './utils/prover.js';
13
+ import { fetchMerkleProof, findCrossCheckNullifierPDAs, findNullifierPDAs, getExtDataHash, getProgramAccounts, queryRemoteTreeState } from './utils/utils.js';
14
14
 
15
15
 
16
16
  // Function to relay pre-signed deposit transaction to indexer backend
@@ -97,12 +97,6 @@ export async function deposit({ lightWasm, storage, keyBasePath, publicKey, conn
97
97
  // Create the merkle tree with the pre-initialized poseidon hash
98
98
  const tree = new MerkleTree(MERKLE_TREE_DEPTH, lightWasm);
99
99
 
100
- // Initialize root and nextIndex variables
101
- const { root, nextIndex: currentNextIndex } = await queryRemoteTreeState();
102
-
103
- logger.debug(`Using tree root: ${root}`);
104
- logger.debug(`New UTXOs will be inserted at indices: ${currentNextIndex} and ${currentNextIndex + 1}`);
105
-
106
100
  // Generate a deterministic private key derived from the wallet keypair
107
101
  // const utxoPrivateKey = encryptionService.deriveUtxoPrivateKey();
108
102
  const utxoPrivateKey = encryptionService.getUtxoPrivateKeyV2();
@@ -123,8 +117,12 @@ export async function deposit({ lightWasm, storage, keyBasePath, publicKey, conn
123
117
  let inputs: Utxo[];
124
118
  let inputMerklePathIndices: number[];
125
119
  let inputMerklePathElements: string[][];
126
-
120
+ let root: string;
121
+ let nextIndex: number;
127
122
  if (existingUnspentUtxos.length === 0) {
123
+ const treeState = await queryRemoteTreeState();
124
+ root = treeState.root;
125
+ nextIndex = treeState.nextIndex;
128
126
  // Scenario 1: Fresh deposit with dummy inputs - add new funds to the system
129
127
  extAmount = amount_in_lamports;
130
128
  outputAmount = new BN(amount_in_lamports).sub(new BN(fee_amount_in_lamports)).toString();
@@ -188,16 +186,18 @@ export async function deposit({ lightWasm, storage, keyBasePath, publicKey, conn
188
186
 
189
187
  // Fetch Merkle proofs for real UTXOs
190
188
  const firstUtxoCommitment = await firstUtxo.getCommitment();
191
- const firstUtxoMerkleProof = await fetchMerkleProof(firstUtxoCommitment);
192
-
193
- let secondUtxoMerkleProof;
189
+ let commitmentsToFetch = [firstUtxoCommitment];
194
190
  if (secondUtxo.amount.gt(new BN(0))) {
195
191
  // Second UTXO is real, fetch its proof
196
192
  const secondUtxoCommitment = await secondUtxo.getCommitment();
197
- secondUtxoMerkleProof = await fetchMerkleProof(secondUtxoCommitment);
193
+ commitmentsToFetch.push(secondUtxoCommitment);
198
194
  logger.debug('\nSecond UTXO to be consolidated:');
199
195
  await secondUtxo.log();
200
196
  }
197
+ let data = await fetchMerkleProof(commitmentsToFetch)
198
+ root = data.root
199
+ nextIndex = data.nextIndex
200
+ let [firstUtxoMerkleProof, secondUtxoMerkleProof] = data.proofs
201
201
 
202
202
  // Use the real pathIndices from API for real inputs, mock index for dummy input
203
203
  inputMerklePathIndices = [
@@ -228,13 +228,13 @@ export async function deposit({ lightWasm, storage, keyBasePath, publicKey, conn
228
228
  lightWasm,
229
229
  amount: outputAmount,
230
230
  keypair: utxoKeypair,
231
- index: currentNextIndex // This UTXO will be inserted at currentNextIndex
231
+ index: nextIndex // This UTXO will be inserted at currentNextIndex
232
232
  }), // Output with value (either deposit amount minus fee, or input amount minus fee)
233
233
  new Utxo({
234
234
  lightWasm,
235
235
  amount: '0',
236
236
  keypair: utxoKeypair,
237
- index: currentNextIndex + 1 // This UTXO will be inserted at currentNextIndex + 1
237
+ index: nextIndex + 1 // This UTXO will be inserted at currentNextIndex + 1
238
238
  }) // Empty UTXO
239
239
  ];
240
240
 
@@ -284,7 +284,7 @@ export async function deposit({ lightWasm, storage, keyBasePath, publicKey, conn
284
284
  // Create the deposit ExtData with real encrypted outputs
285
285
  const extData = {
286
286
  // recipient - just a placeholder, not actually used for deposits.
287
- recipient: new PublicKey('AWexibGxNFKTa1b5R5MN4PJr9HWnWRwf8EW9g8cLx3dM'),
287
+ recipient: new PublicKey(FEE_RECIPIENT).toString(), // Using fee recipient as dummy recipient for deposit
288
288
  extAmount: new BN(extAmount),
289
289
  encryptedOutput1: encryptedOutput1,
290
290
  encryptedOutput2: encryptedOutput2,
@@ -375,7 +375,7 @@ export async function deposit({ lightWasm, storage, keyBasePath, publicKey, conn
375
375
  { pubkey: treeTokenAccount, isSigner: false, isWritable: true },
376
376
  { pubkey: globalConfigAccount, isSigner: false, isWritable: false },
377
377
  // recipient - just a placeholder, not actually used for deposits. using an ALT address to save bytes
378
- { pubkey: new PublicKey('AWexibGxNFKTa1b5R5MN4PJr9HWnWRwf8EW9g8cLx3dM'), isSigner: false, isWritable: true },
378
+ { pubkey: new PublicKey(FEE_RECIPIENT.toString()), isSigner: false, isWritable: true },
379
379
  // fee recipient
380
380
  { pubkey: FEE_RECIPIENT, isSigner: false, isWritable: true },
381
381
  // signer
package/src/depositSPL.ts CHANGED
@@ -1,17 +1,17 @@
1
- import { Connection, Keypair, PublicKey, TransactionInstruction, SystemProgram, ComputeBudgetProgram, VersionedTransaction, TransactionMessage, AddressLookupTableProgram } from '@solana/web3.js';
1
+ import * as hasher from '@lightprotocol/hasher.rs';
2
+ import { ASSOCIATED_TOKEN_PROGRAM_ID, TOKEN_PROGRAM_ID, getAccount, getAssociatedTokenAddressSync } from '@solana/spl-token';
3
+ import { ComputeBudgetProgram, Connection, PublicKey, SystemProgram, TransactionInstruction, TransactionMessage, VersionedTransaction } from '@solana/web3.js';
2
4
  import BN from 'bn.js';
5
+ import { getUtxosSPL } from './getUtxosSPL.js';
6
+ import { Keypair as UtxoKeypair } from './models/keypair.js';
3
7
  import { Utxo } from './models/utxo.js';
4
- import { fetchMerkleProof, findNullifierPDAs, getProgramAccounts, queryRemoteTreeState, findCrossCheckNullifierPDAs, getExtDataHash, getMintAddressField } from './utils/utils.js';
5
- import { prove, parseProofToBytesArray, parseToBytesArray } from './utils/prover.js';
6
- import * as hasher from '@lightprotocol/hasher.rs';
7
- import { MerkleTree } from './utils/merkle_tree.js';
8
+ import { useExistingALT } from './utils/address_lookup_table.js';
9
+ import { ALT_ADDRESS, FEE_RECIPIENT, FIELD_SIZE, MERKLE_TREE_DEPTH, PROGRAM_ID, RELAYER_API_URL, Token, tokens } from './utils/constants.js';
8
10
  import { EncryptionService, serializeProofAndExtData } from './utils/encryption.js';
9
- import { Keypair as UtxoKeypair } from './models/keypair.js';
10
- import { getUtxosSPL, isUtxoSpent } from './getUtxosSPL.js';
11
- import { FIELD_SIZE, FEE_RECIPIENT, MERKLE_TREE_DEPTH, RELAYER_API_URL, PROGRAM_ID, ALT_ADDRESS, tokens, SplList, Token } from './utils/constants.js';
12
- import { getProtocolAddressesWithMint, useExistingALT } from './utils/address_lookup_table.js';
13
11
  import { logger } from './utils/logger.js';
14
- import { getAssociatedTokenAddress, ASSOCIATED_TOKEN_PROGRAM_ID, TOKEN_PROGRAM_ID, getAssociatedTokenAddressSync, getMint, getAccount } from '@solana/spl-token';
12
+ import { MerkleTree } from './utils/merkle_tree.js';
13
+ import { parseProofToBytesArray, parseToBytesArray, prove } from './utils/prover.js';
14
+ import { fetchMerkleProof, findCrossCheckNullifierPDAs, findNullifierPDAs, getExtDataHash, getMintAddressField, getProgramAccounts, queryRemoteTreeState } from './utils/utils.js';
15
15
 
16
16
 
17
17
  // Function to relay pre-signed deposit transaction to indexer backend
@@ -104,7 +104,7 @@ export async function depositSPL({ lightWasm, storage, keyBasePath, publicKey, c
104
104
  // let mintInfo = await getMint(connection, token.pubkey)
105
105
  // let units_per_token = 10 ** mintInfo.decimals
106
106
 
107
- let recipient = new PublicKey('AWexibGxNFKTa1b5R5MN4PJr9HWnWRwf8EW9g8cLx3dM')
107
+ let recipient = new PublicKey(FEE_RECIPIENT.toString()) // Using fee recipient as dummy recipient for deposit
108
108
  let recipient_ata = getAssociatedTokenAddressSync(
109
109
  token.pubkey,
110
110
  recipient,
@@ -162,12 +162,6 @@ export async function depositSPL({ lightWasm, storage, keyBasePath, publicKey, c
162
162
  // Create the merkle tree with the pre-initialized poseidon hash
163
163
  const tree = new MerkleTree(MERKLE_TREE_DEPTH, lightWasm);
164
164
 
165
- // Initialize root and nextIndex variables
166
- const { root, nextIndex: currentNextIndex } = await queryRemoteTreeState(token.name);
167
-
168
- logger.debug(`Using tree root: ${root}`);
169
- logger.debug(`New UTXOs will be inserted at indices: ${currentNextIndex} and ${currentNextIndex + 1}`);
170
-
171
165
  // Generate a deterministic private key derived from the wallet keypair
172
166
  // const utxoPrivateKey = encryptionService.deriveUtxoPrivateKey();
173
167
  const utxoPrivateKey = encryptionService.getUtxoPrivateKeyV2();
@@ -188,8 +182,12 @@ export async function depositSPL({ lightWasm, storage, keyBasePath, publicKey, c
188
182
  let inputs: Utxo[];
189
183
  let inputMerklePathIndices: number[];
190
184
  let inputMerklePathElements: string[][];
191
-
185
+ let root: string;
186
+ let nextIndex: number;
192
187
  if (mintUtxos.length === 0) {
188
+ const treeState = await queryRemoteTreeState(token.name);
189
+ root = treeState.root
190
+ nextIndex = treeState.nextIndex
193
191
  // Scenario 1: Fresh deposit with dummy inputs - add new funds to the system
194
192
  extAmount = base_units;
195
193
  outputAmount = new BN(base_units).sub(new BN(fee_base_units)).toString();
@@ -241,6 +239,7 @@ export async function depositSPL({ lightWasm, storage, keyBasePath, publicKey, c
241
239
  logger.debug('\nFirst UTXO to be consolidated:');
242
240
 
243
241
  // Use first existing UTXO as first input, and either second UTXO or dummy UTXO as second input
242
+
244
243
  const secondUtxo = mintUtxos.length > 1 ? mintUtxos[1] : new Utxo({
245
244
  lightWasm,
246
245
  keypair: utxoKeypair,
@@ -255,17 +254,22 @@ export async function depositSPL({ lightWasm, storage, keyBasePath, publicKey, c
255
254
 
256
255
  // Fetch Merkle proofs for real UTXOs
257
256
  const firstUtxoCommitment = await firstUtxo.getCommitment();
258
- const firstUtxoMerkleProof = await fetchMerkleProof(firstUtxoCommitment, token.name);
259
257
 
260
- let secondUtxoMerkleProof;
258
+ let commitmentsToFetch = [firstUtxoCommitment];
261
259
  if (secondUtxo.amount.gt(new BN(0))) {
262
260
  // Second UTXO is real, fetch its proof
263
261
  const secondUtxoCommitment = await secondUtxo.getCommitment();
264
- secondUtxoMerkleProof = await fetchMerkleProof(secondUtxoCommitment, token.name);
262
+ commitmentsToFetch.push(secondUtxoCommitment);
265
263
  logger.debug('\nSecond UTXO to be consolidated:');
266
264
  await secondUtxo.log();
267
265
  }
268
266
 
267
+ let data = await fetchMerkleProof(commitmentsToFetch, token.name)
268
+ root = data.root
269
+ nextIndex = data.nextIndex
270
+ let [firstUtxoMerkleProof, secondUtxoMerkleProof] = data.proofs
271
+
272
+
269
273
  // Use the real pathIndices from API for real inputs, mock index for dummy input
270
274
  inputMerklePathIndices = [
271
275
  firstUtxo.index || 0, // Use the real UTXO's index
@@ -295,14 +299,14 @@ export async function depositSPL({ lightWasm, storage, keyBasePath, publicKey, c
295
299
  lightWasm,
296
300
  amount: outputAmount,
297
301
  keypair: utxoKeypair,
298
- index: currentNextIndex, // This UTXO will be inserted at currentNextIndex
302
+ index: nextIndex, // This UTXO will be inserted at currentNextIndex
299
303
  mintAddress: token.pubkey.toString()
300
304
  }), // Output with value (either deposit amount minus fee, or input amount minus fee)
301
305
  new Utxo({
302
306
  lightWasm,
303
307
  amount: '0',
304
308
  keypair: utxoKeypair,
305
- index: currentNextIndex + 1, // This UTXO will be inserted at currentNextIndex
309
+ index: nextIndex + 1, // This UTXO will be inserted at currentNextIndex
306
310
  mintAddress: token.pubkey.toString()
307
311
  }) // Empty UTXO
308
312
  ];
@@ -5,12 +5,12 @@
5
5
  * Based on: https://github.com/tornadocash/tornado-nova
6
6
  */
7
7
 
8
- import BN from 'bn.js';
9
- import { Keypair } from './keypair.js';
10
8
  import * as hasher from '@lightprotocol/hasher.rs';
9
+ import { PublicKey } from '@solana/web3.js';
10
+ import BN from 'bn.js';
11
11
  import { ethers } from 'ethers';
12
12
  import { getMintAddressField } from '../utils/utils.js';
13
- import { PublicKey } from '@solana/web3.js';
13
+ import { Keypair } from './keypair.js';
14
14
  /**
15
15
  * Simplified Utxo class inspired by Tornado Cash Nova
16
16
  * Based on: https://github.com/tornadocash/tornado-nova/blob/f9264eeffe48bf5e04e19d8086ee6ec58cdf0d9e/src/utxo.js
@@ -58,7 +58,7 @@ export class Utxo {
58
58
  this.version = version;
59
59
  }
60
60
 
61
- async getCommitment(): Promise<string> {
61
+ getCommitment(): string {
62
62
  // return this.lightWasm.poseidonHashString([this.amount.toString(), this.keypair.pubkey.toString(), this.blinding.toString(), this.mintAddress]);
63
63
  const mintAddressField = getMintAddressField(new PublicKey(this.mintAddress));
64
64
  return this.lightWasm.poseidonHashString([
@@ -5,7 +5,7 @@ export const FIELD_SIZE = new BN('2188824287183927522224640574525727508854836440
5
5
 
6
6
  export const PROGRAM_ID = process.env.NEXT_PUBLIC_PROGRAM_ID ? new PublicKey(process.env.NEXT_PUBLIC_PROGRAM_ID) : new PublicKey('9fhQBbumKEFuXtMBDw8AaQyAjCorLGJQiS3skWZdQyQD');
7
7
 
8
- export const FEE_RECIPIENT = new PublicKey('AWexibGxNFKTa1b5R5MN4PJr9HWnWRwf8EW9g8cLx3dM')
8
+ export const FEE_RECIPIENT = new PublicKey('97rSMQUukMDjA7PYErccyx7ZxbHvSDaeXp2ig5BwSrTf')
9
9
 
10
10
  export const FETCH_UTXOS_GROUP_SIZE = 20_000
11
11
 
@@ -5,14 +5,14 @@
5
5
  * Based on: https://github.com/tornadocash/tornado-nova
6
6
  */
7
7
 
8
- import BN from 'bn.js';
9
- import { Utxo } from '../models/utxo.js';
10
- import * as borsh from 'borsh';
11
8
  import { sha256 } from '@ethersproject/sha2';
12
9
  import { PublicKey } from '@solana/web3.js';
13
- import { RELAYER_API_URL, PROGRAM_ID } from './constants.js';
14
- import { logger } from './logger.js';
10
+ import BN from 'bn.js';
11
+ import * as borsh from 'borsh';
15
12
  import { getConfig } from '../config.js';
13
+ import { Utxo } from '../models/utxo.js';
14
+ import { PROGRAM_ID, RELAYER_API_URL } from './constants.js';
15
+ import { logger } from './logger.js';
16
16
 
17
17
  /**
18
18
  * Calculate deposit fee based on deposit amount and fee rate
@@ -113,24 +113,29 @@ export function getExtDataHash(extData: {
113
113
  return Buffer.from(hashHex.slice(2), 'hex');
114
114
  }
115
115
 
116
+ type Proof = { pathElements: string[], pathIndices: number[] }
116
117
 
117
118
  // Function to fetch Merkle proof from API for a given commitment
118
- export async function fetchMerkleProof(commitment: string, tokenName?: string): Promise<{ pathElements: string[], pathIndices: number[] }> {
119
+ export async function fetchMerkleProof(commitments: string[], tokenName?: string): Promise<{
120
+ proofs: Proof[],
121
+ root: string,
122
+ nextIndex: number
123
+ }> {
119
124
  try {
120
- logger.debug(`Fetching Merkle proof for commitment: ${commitment}`);
121
- let url = `${RELAYER_API_URL}/merkle/proof/${commitment}`
125
+ logger.debug(`Fetching Merkle proof for commitment: ${commitments.join(', ')} with token: ${tokenName || 'N/A'}`);
126
+ let url = `${RELAYER_API_URL}/merkle/proofv2/?commitments=${commitments.join(',')}`
122
127
  if (tokenName) {
123
- url += '?token=' + tokenName
128
+ url += '&token=' + tokenName
124
129
  }
130
+ logger.debug(`Fetching Merkle proof from URL: ${url}`);
125
131
  const response = await fetch(url);
126
132
  if (!response.ok) {
127
133
  throw new Error(`Failed to fetch Merkle proof: ${url}`);
128
134
  }
129
- const data = await response.json() as { pathElements: string[], pathIndices: number[] };
130
- logger.debug(`✓ Fetched Merkle proof with ${data.pathElements.length} elements`);
135
+ const data = await response.json();
131
136
  return data;
132
137
  } catch (error) {
133
- console.error(`Failed to fetch Merkle proof for commitment ${commitment}:`, error);
138
+ console.error(`Failed to fetch Merkle proof for commitments ${commitments.join(', ')}:`, error);
134
139
  throw error;
135
140
  }
136
141
  }
package/src/withdraw.ts CHANGED
@@ -1,18 +1,18 @@
1
- import { Connection, Keypair, LAMPORTS_PER_SOL, PublicKey, Transaction, TransactionInstruction, VersionedTransaction } from '@solana/web3.js';
1
+ import * as hasher from '@lightprotocol/hasher.rs';
2
+ import { Connection, LAMPORTS_PER_SOL, PublicKey } from '@solana/web3.js';
2
3
  import BN from 'bn.js';
3
4
  import { Buffer } from 'buffer';
4
5
  import { Keypair as UtxoKeypair } from './models/keypair.js';
5
- 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, FEE_RECIPIENT, FIELD_SIZE, RELAYER_API_URL, MERKLE_TREE_DEPTH, PROGRAM_ID } from './utils/constants.js';
9
+ import { ALT_ADDRESS, FEE_RECIPIENT, FIELD_SIZE, RELAYER_API_URL } from './utils/constants.js';
10
10
  import { EncryptionService, serializeProofAndExtData } from './utils/encryption.js';
11
- import { fetchMerkleProof, findNullifierPDAs, getExtDataHash, getProgramAccounts, queryRemoteTreeState, findCrossCheckNullifierPDAs } from './utils/utils.js';
11
+ import { fetchMerkleProof, findCrossCheckNullifierPDAs, findNullifierPDAs, getExtDataHash, getProgramAccounts } from './utils/utils.js';
12
12
 
13
+ import { getConfig } from './config.js';
13
14
  import { getUtxos } from './getUtxos.js';
14
15
  import { logger } from './utils/logger.js';
15
- import { getConfig } from './config.js';
16
16
  // Indexer API endpoint
17
17
 
18
18
 
@@ -65,11 +65,6 @@ export async function withdraw({ recipient, lightWasm, storage, publicKey, conne
65
65
 
66
66
  const { treeAccount, treeTokenAccount, globalConfigAccount } = getProgramAccounts()
67
67
 
68
- // Get current tree state
69
- const { root, nextIndex: currentNextIndex } = await queryRemoteTreeState();
70
- logger.debug(`Using tree root: ${root}`);
71
- logger.debug(`New UTXOs will be inserted at indices: ${currentNextIndex} and ${currentNextIndex + 1}`);
72
-
73
68
  // Generate a deterministic private key derived from the wallet keypair
74
69
  const utxoPrivateKey = encryptionService.deriveUtxoPrivateKey();
75
70
 
@@ -124,23 +119,26 @@ export async function withdraw({ recipient, lightWasm, storage, publicKey, conne
124
119
  logger.debug(`Withdrawing ${amount_in_lamports} lamports with ${fee_in_lamports} fee, ${changeAmount.toString()} as change`);
125
120
 
126
121
  // Get Merkle proofs for both input UTXOs
127
- const inputMerkleProofs = await Promise.all(
128
- inputs.map(async (utxo, index) => {
129
- // For dummy UTXO (amount is 0), use a zero-filled proof
130
- if (utxo.amount.eq(new BN(0))) {
131
- return {
132
- pathElements: [...new Array(MERKLE_TREE_DEPTH).fill("0")],
133
- pathIndices: Array(MERKLE_TREE_DEPTH).fill(0)
134
- };
135
- }
136
- // For real UTXOs, fetch the proof from API
137
- const commitment = await utxo.getCommitment();
138
- return fetchMerkleProof(commitment);
139
- })
140
- );
122
+ let commitmentsToFetch = inputs.map(utxo => utxo.getCommitment());
123
+ console.log('commitmentsToFetch', commitmentsToFetch)
124
+ const { root, nextIndex, proofs } = await fetchMerkleProof(commitmentsToFetch);
125
+ // const inputMerkleProofs = await Promise.all(
126
+ // inputs.map(async (utxo, index) => {
127
+ // // For dummy UTXO (amount is 0), use a zero-filled proof
128
+ // if (utxo.amount.eq(new BN(0))) {
129
+ // return {
130
+ // pathElements: [...new Array(MERKLE_TREE_DEPTH).fill("0")],
131
+ // pathIndices: Array(MERKLE_TREE_DEPTH).fill(0)
132
+ // };
133
+ // }
134
+ // // For real UTXOs, fetch the proof from API
135
+ // const commitment = await utxo.getCommitment();
136
+ // return fetchMerkleProof(commitment);
137
+ // })
138
+ // );
141
139
 
142
140
  // Extract path elements and indices
143
- const inputMerklePathElements = inputMerkleProofs.map(proof => proof.pathElements);
141
+ const inputMerklePathElements = proofs.map(proof => proof.pathElements);
144
142
  const inputMerklePathIndices = inputs.map(utxo => utxo.index || 0);
145
143
 
146
144
  // Create outputs: first output is change, second is dummy (required by protocol)
@@ -149,13 +147,13 @@ export async function withdraw({ recipient, lightWasm, storage, publicKey, conne
149
147
  lightWasm,
150
148
  amount: changeAmount.toString(),
151
149
  keypair: utxoKeypairV2,
152
- index: currentNextIndex
150
+ index: nextIndex
153
151
  }), // Change output
154
152
  new Utxo({
155
153
  lightWasm,
156
154
  amount: '0',
157
155
  keypair: utxoKeypairV2,
158
- index: currentNextIndex + 1
156
+ index: nextIndex + 1
159
157
  }) // Empty UTXO
160
158
  ];
161
159
 
@@ -1,19 +1,19 @@
1
- import { Connection, Keypair, PublicKey, Transaction, TransactionInstruction, VersionedTransaction } from '@solana/web3.js';
1
+ import * as hasher from '@lightprotocol/hasher.rs';
2
+ import { Connection, PublicKey } from '@solana/web3.js';
2
3
  import BN from 'bn.js';
3
4
  import { Buffer } from 'buffer';
4
5
  import { Keypair as UtxoKeypair } from './models/keypair.js';
5
- 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, FEE_RECIPIENT, FIELD_SIZE, RELAYER_API_URL, MERKLE_TREE_DEPTH, PROGRAM_ID, SplList, tokens } from './utils/constants.js';
9
+ import { ALT_ADDRESS, FEE_RECIPIENT, FIELD_SIZE, PROGRAM_ID, RELAYER_API_URL, tokens } from './utils/constants.js';
10
10
  import { EncryptionService, serializeProofAndExtData } from './utils/encryption.js';
11
- import { fetchMerkleProof, findNullifierPDAs, getProgramAccounts, queryRemoteTreeState, findCrossCheckNullifierPDAs, getMintAddressField, getExtDataHash } from './utils/utils.js';
11
+ import { fetchMerkleProof, findCrossCheckNullifierPDAs, findNullifierPDAs, getExtDataHash, getMintAddressField, getProgramAccounts } from './utils/utils.js';
12
12
 
13
- import { getUtxosSPL, isUtxoSpent } from './getUtxosSPL.js';
14
- import { logger } from './utils/logger.js';
15
- import { getConfig } from './config.js';
16
13
  import { getAssociatedTokenAddressSync, getMint } from '@solana/spl-token';
14
+ import { getConfig } from './config.js';
15
+ import { getUtxosSPL } from './getUtxosSPL.js';
16
+ import { logger } from './utils/logger.js';
17
17
  // Indexer API endpoint
18
18
 
19
19
 
@@ -120,11 +120,6 @@ export async function withdrawSPL({ recipient, lightWasm, storage, publicKey, co
120
120
 
121
121
  const { globalConfigAccount, treeTokenAccount } = getProgramAccounts()
122
122
 
123
- // Get current tree state
124
- const { root, nextIndex: currentNextIndex } = await queryRemoteTreeState(token.name);
125
- logger.debug(`Using tree root: ${root}`);
126
- logger.debug(`New UTXOs will be inserted at indices: ${currentNextIndex} and ${currentNextIndex + 1}`);
127
-
128
123
  // Generate a deterministic private key derived from the wallet keypair
129
124
  const utxoPrivateKey = encryptionService.deriveUtxoPrivateKey();
130
125
 
@@ -180,24 +175,11 @@ export async function withdrawSPL({ recipient, lightWasm, storage, publicKey, co
180
175
  const changeAmount = totalInputAmount.sub(new BN(base_units)).sub(new BN(fee_base_units));
181
176
  logger.debug(`Withdrawing ${base_units} lamports with ${fee_base_units} fee, ${changeAmount.toString()} as change`);
182
177
 
183
- // Get Merkle proofs for both input UTXOs
184
- const inputMerkleProofs = await Promise.all(
185
- inputs.map(async (utxo, index) => {
186
- // For dummy UTXO (amount is 0), use a zero-filled proof
187
- if (utxo.amount.eq(new BN(0))) {
188
- return {
189
- pathElements: [...new Array(MERKLE_TREE_DEPTH).fill("0")],
190
- pathIndices: Array(MERKLE_TREE_DEPTH).fill(0)
191
- };
192
- }
193
- // For real UTXOs, fetch the proof from API
194
- const commitment = await utxo.getCommitment();
195
- return fetchMerkleProof(commitment, token.name);
196
- })
197
- );
178
+ let commitmentsToFetch = inputs.map(utxo => utxo.getCommitment());
179
+ const { root, nextIndex, proofs } = await fetchMerkleProof(commitmentsToFetch, token.name);
198
180
 
199
181
  // Extract path elements and indices
200
- const inputMerklePathElements = inputMerkleProofs.map(proof => proof.pathElements);
182
+ const inputMerklePathElements = proofs.map(proof => proof.pathElements);
201
183
  const inputMerklePathIndices = inputs.map(utxo => utxo.index || 0);
202
184
 
203
185
  // Create outputs: first output is change, second is dummy (required by protocol)
@@ -206,14 +188,14 @@ export async function withdrawSPL({ recipient, lightWasm, storage, publicKey, co
206
188
  lightWasm,
207
189
  amount: changeAmount.toString(),
208
190
  keypair: utxoKeypairV2,
209
- index: currentNextIndex,
191
+ index: nextIndex,
210
192
  mintAddress: token.pubkey.toString()
211
193
  }), // Change output
212
194
  new Utxo({
213
195
  lightWasm,
214
196
  amount: '0',
215
197
  keypair: utxoKeypairV2,
216
- index: currentNextIndex + 1,
198
+ index: nextIndex + 1,
217
199
  mintAddress: token.pubkey.toString()
218
200
  }) // Empty UTXO
219
201
  ];