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.
- package/README.md +5 -1
- package/__tests__/e2e.test.ts +5 -1
- package/__tests__/e2espl.test.ts +73 -0
- package/dist/config.d.ts +1 -0
- package/dist/config.js +6 -10
- package/dist/deposit.js +4 -11
- package/dist/depositSPL.d.ts +19 -0
- package/dist/depositSPL.js +434 -0
- package/dist/exportUtils.d.ts +3 -0
- package/dist/exportUtils.js +3 -0
- package/dist/getUtxos.d.ts +2 -1
- package/dist/getUtxos.js +68 -79
- package/dist/getUtxosSPL.d.ts +31 -0
- package/dist/getUtxosSPL.js +369 -0
- package/dist/index.d.ts +31 -1
- package/dist/index.js +73 -3
- package/dist/models/utxo.js +10 -1
- package/dist/utils/address_lookup_table.d.ts +1 -0
- package/dist/utils/address_lookup_table.js +23 -0
- package/dist/utils/constants.d.ts +3 -2
- package/dist/utils/constants.js +5 -4
- package/dist/utils/encryption.d.ts +1 -1
- package/dist/utils/encryption.js +5 -3
- package/dist/utils/utils.d.ts +3 -2
- package/dist/utils/utils.js +26 -6
- package/dist/withdraw.js +3 -4
- package/dist/withdrawSPL.d.ts +22 -0
- package/dist/withdrawSPL.js +289 -0
- package/package.json +5 -3
- package/src/config.ts +7 -14
- package/src/deposit.ts +4 -11
- package/src/depositSPL.ts +556 -0
- package/src/exportUtils.ts +5 -1
- package/src/getUtxos.ts +73 -78
- package/src/getUtxosSPL.ts +495 -0
- package/src/index.ts +84 -3
- package/src/models/utxo.ts +10 -1
- package/src/utils/address_lookup_table.ts +54 -6
- package/src/utils/constants.ts +7 -5
- package/src/utils/encryption.ts +6 -3
- package/src/utils/utils.ts +29 -6
- package/src/withdraw.ts +4 -6
- package/src/withdrawSPL.ts +377 -0
|
@@ -0,0 +1,289 @@
|
|
|
1
|
+
import { PublicKey } from '@solana/web3.js';
|
|
2
|
+
import BN from 'bn.js';
|
|
3
|
+
import { Buffer } from 'buffer';
|
|
4
|
+
import { Keypair as UtxoKeypair } from './models/keypair.js';
|
|
5
|
+
import { Utxo } from './models/utxo.js';
|
|
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 } from './utils/constants.js';
|
|
8
|
+
import { serializeProofAndExtData } from './utils/encryption.js';
|
|
9
|
+
import { fetchMerkleProof, findNullifierPDAs, getProgramAccounts, queryRemoteTreeState, findCrossCheckNullifierPDAs, getMintAddressField, getExtDataHash } from './utils/utils.js';
|
|
10
|
+
import { getUtxosSPL } from './getUtxosSPL.js';
|
|
11
|
+
import { logger } from './utils/logger.js';
|
|
12
|
+
import { getConfig } from './config.js';
|
|
13
|
+
import { getAssociatedTokenAddressSync, getMint } from '@solana/spl-token';
|
|
14
|
+
// Indexer API endpoint
|
|
15
|
+
// Function to submit withdraw request to indexer backend
|
|
16
|
+
async function submitWithdrawToIndexer(params) {
|
|
17
|
+
try {
|
|
18
|
+
const response = await fetch(`${RELAYER_API_URL}/withdraw/spl`, {
|
|
19
|
+
method: 'POST',
|
|
20
|
+
headers: {
|
|
21
|
+
'Content-Type': 'application/json',
|
|
22
|
+
},
|
|
23
|
+
body: JSON.stringify(params)
|
|
24
|
+
});
|
|
25
|
+
if (!response.ok) {
|
|
26
|
+
const errorData = await response.json();
|
|
27
|
+
throw new Error(errorData.error);
|
|
28
|
+
}
|
|
29
|
+
const result = await response.json();
|
|
30
|
+
logger.debug('Withdraw request submitted successfully!');
|
|
31
|
+
logger.debug('Response:', result);
|
|
32
|
+
return result.signature;
|
|
33
|
+
}
|
|
34
|
+
catch (error) {
|
|
35
|
+
logger.debug('Failed to submit withdraw request to indexer:', typeof error, error);
|
|
36
|
+
throw error;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
export async function withdrawSPL({ recipient, lightWasm, storage, publicKey, connection, base_units, encryptionService, keyBasePath, mintAddress }) {
|
|
40
|
+
let mintInfo = await getMint(connection, mintAddress);
|
|
41
|
+
let units_per_token = 10 ** mintInfo.decimals;
|
|
42
|
+
let withdraw_fee_rate = await getConfig('withdraw_fee_rate');
|
|
43
|
+
let withdraw_rent_fee = await getConfig('usdc_withdraw_rent_fee');
|
|
44
|
+
let fee_base_units = Math.floor(base_units * withdraw_fee_rate + units_per_token * withdraw_rent_fee);
|
|
45
|
+
base_units -= fee_base_units;
|
|
46
|
+
if (base_units <= 0) {
|
|
47
|
+
throw new Error('withdraw amount too low');
|
|
48
|
+
}
|
|
49
|
+
let isPartial = false;
|
|
50
|
+
let recipient_ata = getAssociatedTokenAddressSync(mintAddress, recipient, true);
|
|
51
|
+
let feeRecipientTokenAccount = getAssociatedTokenAddressSync(mintAddress, FEE_RECIPIENT, true);
|
|
52
|
+
let signerTokenAccount = getAssociatedTokenAddressSync(mintAddress, publicKey);
|
|
53
|
+
logger.debug('Encryption key generated from user keypair');
|
|
54
|
+
// Derive tree account PDA with mint address for SPL (different from SOL version)
|
|
55
|
+
const [treeAccount] = PublicKey.findProgramAddressSync([Buffer.from('merkle_tree'), mintAddress.toBuffer()], PROGRAM_ID);
|
|
56
|
+
const { globalConfigAccount, treeTokenAccount } = getProgramAccounts();
|
|
57
|
+
// Get current tree state
|
|
58
|
+
const { root, nextIndex: currentNextIndex } = await queryRemoteTreeState('usdc');
|
|
59
|
+
logger.debug(`Using tree root: ${root}`);
|
|
60
|
+
logger.debug(`New UTXOs will be inserted at indices: ${currentNextIndex} and ${currentNextIndex + 1}`);
|
|
61
|
+
// Generate a deterministic private key derived from the wallet keypair
|
|
62
|
+
const utxoPrivateKey = encryptionService.deriveUtxoPrivateKey();
|
|
63
|
+
// Create a UTXO keypair that will be used for all inputs and outputs
|
|
64
|
+
const utxoKeypair = new UtxoKeypair(utxoPrivateKey, lightWasm);
|
|
65
|
+
logger.debug('Using wallet-derived UTXO keypair for withdrawal');
|
|
66
|
+
// Generate a deterministic private key derived from the wallet keypair (V2)
|
|
67
|
+
const utxoPrivateKeyV2 = encryptionService.getUtxoPrivateKeyV2();
|
|
68
|
+
const utxoKeypairV2 = new UtxoKeypair(utxoPrivateKeyV2, lightWasm);
|
|
69
|
+
// Fetch existing UTXOs for this user
|
|
70
|
+
logger.debug('\nFetching existing UTXOs...');
|
|
71
|
+
const mintUtxos = await getUtxosSPL({ connection, publicKey, encryptionService, storage, mintAddress });
|
|
72
|
+
logger.debug(`Found ${mintUtxos.length} total UTXOs`);
|
|
73
|
+
// Calculate and log total unspent UTXO balance
|
|
74
|
+
const totalUnspentBalance = mintUtxos.reduce((sum, utxo) => sum.add(utxo.amount), new BN(0));
|
|
75
|
+
logger.debug(`Total unspent UTXO balance before: ${totalUnspentBalance.toString()} lamports (${totalUnspentBalance.toNumber() / 1e9} SOL)`);
|
|
76
|
+
if (mintUtxos.length < 1) {
|
|
77
|
+
throw new Error('Need at least 1 unspent UTXO to perform a withdrawal');
|
|
78
|
+
}
|
|
79
|
+
// Sort UTXOs by amount in descending order to use the largest ones first
|
|
80
|
+
mintUtxos.sort((a, b) => b.amount.cmp(a.amount));
|
|
81
|
+
// Use the largest UTXO as first input, and either second largest UTXO or dummy UTXO as second input
|
|
82
|
+
const firstInput = mintUtxos[0];
|
|
83
|
+
const secondInput = mintUtxos.length > 1 ? mintUtxos[1] : new Utxo({
|
|
84
|
+
lightWasm,
|
|
85
|
+
keypair: utxoKeypair,
|
|
86
|
+
amount: '0',
|
|
87
|
+
mintAddress: mintAddress.toString()
|
|
88
|
+
});
|
|
89
|
+
const inputs = [firstInput, secondInput];
|
|
90
|
+
logger.debug(`firstInput index: ${firstInput.index}, commitment: ${firstInput.getCommitment()}`);
|
|
91
|
+
logger.debug(`secondInput index: ${secondInput.index}, commitment: ${secondInput.getCommitment()}`);
|
|
92
|
+
const totalInputAmount = firstInput.amount.add(secondInput.amount);
|
|
93
|
+
logger.debug(`Using UTXO with amount: ${firstInput.amount.toString()} and ${secondInput.amount.gt(new BN(0)) ? 'second UTXO with amount: ' + secondInput.amount.toString() : 'dummy UTXO'}`);
|
|
94
|
+
if (totalInputAmount.toNumber() === 0) {
|
|
95
|
+
throw new Error('no balance');
|
|
96
|
+
}
|
|
97
|
+
if (totalInputAmount.lt(new BN(base_units + fee_base_units))) {
|
|
98
|
+
isPartial = true;
|
|
99
|
+
base_units = totalInputAmount.toNumber();
|
|
100
|
+
base_units -= fee_base_units;
|
|
101
|
+
}
|
|
102
|
+
// Calculate the change amount (what's left after withdrawal and fee)
|
|
103
|
+
const changeAmount = totalInputAmount.sub(new BN(base_units)).sub(new BN(fee_base_units));
|
|
104
|
+
logger.debug(`Withdrawing ${base_units} lamports with ${fee_base_units} fee, ${changeAmount.toString()} as change`);
|
|
105
|
+
// Get Merkle proofs for both input UTXOs
|
|
106
|
+
const inputMerkleProofs = await Promise.all(inputs.map(async (utxo, index) => {
|
|
107
|
+
// For dummy UTXO (amount is 0), use a zero-filled proof
|
|
108
|
+
if (utxo.amount.eq(new BN(0))) {
|
|
109
|
+
return {
|
|
110
|
+
pathElements: [...new Array(MERKLE_TREE_DEPTH).fill("0")],
|
|
111
|
+
pathIndices: Array(MERKLE_TREE_DEPTH).fill(0)
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
// For real UTXOs, fetch the proof from API
|
|
115
|
+
const commitment = await utxo.getCommitment();
|
|
116
|
+
return fetchMerkleProof(commitment, 'usdc');
|
|
117
|
+
}));
|
|
118
|
+
// Extract path elements and indices
|
|
119
|
+
const inputMerklePathElements = inputMerkleProofs.map(proof => proof.pathElements);
|
|
120
|
+
const inputMerklePathIndices = inputs.map(utxo => utxo.index || 0);
|
|
121
|
+
// Create outputs: first output is change, second is dummy (required by protocol)
|
|
122
|
+
const outputs = [
|
|
123
|
+
new Utxo({
|
|
124
|
+
lightWasm,
|
|
125
|
+
amount: changeAmount.toString(),
|
|
126
|
+
keypair: utxoKeypairV2,
|
|
127
|
+
index: currentNextIndex,
|
|
128
|
+
mintAddress: mintAddress.toString()
|
|
129
|
+
}), // Change output
|
|
130
|
+
new Utxo({
|
|
131
|
+
lightWasm,
|
|
132
|
+
amount: '0',
|
|
133
|
+
keypair: utxoKeypairV2,
|
|
134
|
+
index: currentNextIndex + 1,
|
|
135
|
+
mintAddress: mintAddress.toString()
|
|
136
|
+
}) // Empty UTXO
|
|
137
|
+
];
|
|
138
|
+
// For withdrawals, extAmount is negative (funds leaving the system)
|
|
139
|
+
const extAmount = -base_units;
|
|
140
|
+
const publicAmountForCircuit = new BN(extAmount).sub(new BN(fee_base_units)).add(FIELD_SIZE).mod(FIELD_SIZE);
|
|
141
|
+
logger.debug(`Public amount calculation: (${extAmount} - ${fee_base_units} + FIELD_SIZE) % FIELD_SIZE = ${publicAmountForCircuit.toString()}`);
|
|
142
|
+
// Verify this matches the circuit balance equation: sumIns + publicAmount = sumOuts
|
|
143
|
+
const sumIns = inputs.reduce((sum, input) => sum.add(input.amount), new BN(0));
|
|
144
|
+
const sumOuts = outputs.reduce((sum, output) => sum.add(output.amount), new BN(0));
|
|
145
|
+
logger.debug(`Circuit balance check: sumIns(${sumIns.toString()}) + publicAmount(${publicAmountForCircuit.toString()}) should equal sumOuts(${sumOuts.toString()})`);
|
|
146
|
+
// Convert to circuit-compatible format
|
|
147
|
+
const publicAmountCircuitResult = sumIns.add(publicAmountForCircuit).mod(FIELD_SIZE);
|
|
148
|
+
logger.debug(`Balance verification: ${sumIns.toString()} + ${publicAmountForCircuit.toString()} (mod FIELD_SIZE) = ${publicAmountCircuitResult.toString()}`);
|
|
149
|
+
logger.debug(`Expected sum of outputs: ${sumOuts.toString()}`);
|
|
150
|
+
logger.debug(`Balance equation satisfied: ${publicAmountCircuitResult.eq(sumOuts)}`);
|
|
151
|
+
// Generate nullifiers and commitments
|
|
152
|
+
const inputNullifiers = await Promise.all(inputs.map(x => x.getNullifier()));
|
|
153
|
+
const outputCommitments = await Promise.all(outputs.map(x => x.getCommitment()));
|
|
154
|
+
// Save original commitment and nullifier values for verification
|
|
155
|
+
logger.debug('\n=== UTXO VALIDATION ===');
|
|
156
|
+
logger.debug('Output 0 Commitment:', outputCommitments[0]);
|
|
157
|
+
logger.debug('Output 1 Commitment:', outputCommitments[1]);
|
|
158
|
+
// Encrypt the UTXO data using a compact format that includes the keypair
|
|
159
|
+
logger.debug('\nEncrypting UTXOs with keypair data...');
|
|
160
|
+
const encryptedOutput1 = encryptionService.encryptUtxo(outputs[0]);
|
|
161
|
+
const encryptedOutput2 = encryptionService.encryptUtxo(outputs[1]);
|
|
162
|
+
logger.debug(`\nOutput[0] (change):`);
|
|
163
|
+
await outputs[0].log();
|
|
164
|
+
logger.debug(`\nOutput[1] (empty):`);
|
|
165
|
+
await outputs[1].log();
|
|
166
|
+
logger.debug(`Encrypted output 1: ${encryptedOutput1.toString('hex')}`);
|
|
167
|
+
logger.debug(`Encrypted output 2: ${encryptedOutput2.toString('hex')}`);
|
|
168
|
+
logger.debug(`\nEncrypted output 1 size: ${encryptedOutput1.length} bytes`);
|
|
169
|
+
logger.debug(`Encrypted output 2 size: ${encryptedOutput2.length} bytes`);
|
|
170
|
+
logger.debug(`Total encrypted outputs size: ${encryptedOutput1.length + encryptedOutput2.length} bytes`);
|
|
171
|
+
// Test decryption to verify commitment values match
|
|
172
|
+
logger.debug('\n=== TESTING DECRYPTION ===');
|
|
173
|
+
logger.debug('Decrypting output 1 to verify commitment matches...');
|
|
174
|
+
const decryptedUtxo1 = await encryptionService.decryptUtxo(encryptedOutput1, lightWasm);
|
|
175
|
+
const decryptedCommitment1 = await decryptedUtxo1.getCommitment();
|
|
176
|
+
logger.debug('Original commitment:', outputCommitments[0]);
|
|
177
|
+
logger.debug('Decrypted commitment:', decryptedCommitment1);
|
|
178
|
+
logger.debug('Commitment matches:', outputCommitments[0] === decryptedCommitment1);
|
|
179
|
+
// Create the withdrawal ExtData with real encrypted outputs
|
|
180
|
+
const extData = {
|
|
181
|
+
// it can be any address
|
|
182
|
+
recipient: recipient_ata,
|
|
183
|
+
extAmount: new BN(extAmount),
|
|
184
|
+
encryptedOutput1: encryptedOutput1,
|
|
185
|
+
encryptedOutput2: encryptedOutput2,
|
|
186
|
+
fee: new BN(fee_base_units),
|
|
187
|
+
feeRecipient: feeRecipientTokenAccount,
|
|
188
|
+
mintAddress: mintAddress.toString()
|
|
189
|
+
};
|
|
190
|
+
// Calculate the extDataHash with the encrypted outputs
|
|
191
|
+
const calculatedExtDataHash = getExtDataHash(extData);
|
|
192
|
+
// Create the input for the proof generation
|
|
193
|
+
const input = {
|
|
194
|
+
// Common transaction data
|
|
195
|
+
root: root,
|
|
196
|
+
mintAddress: getMintAddressField(mintAddress), // new mint address
|
|
197
|
+
publicAmount: publicAmountForCircuit.toString(), // Use proper field arithmetic result
|
|
198
|
+
extDataHash: calculatedExtDataHash,
|
|
199
|
+
// Input UTXO data (UTXOs being spent) - ensure all values are in decimal format
|
|
200
|
+
inAmount: inputs.map(x => x.amount.toString(10)),
|
|
201
|
+
inPrivateKey: inputs.map(x => x.keypair.privkey),
|
|
202
|
+
inBlinding: inputs.map(x => x.blinding.toString(10)),
|
|
203
|
+
inPathIndices: inputMerklePathIndices,
|
|
204
|
+
inPathElements: inputMerklePathElements,
|
|
205
|
+
inputNullifier: inputNullifiers, // Use resolved values instead of Promise objects
|
|
206
|
+
// Output UTXO data (UTXOs being created) - ensure all values are in decimal format
|
|
207
|
+
outAmount: outputs.map(x => x.amount.toString(10)),
|
|
208
|
+
outBlinding: outputs.map(x => x.blinding.toString(10)),
|
|
209
|
+
outPubkey: outputs.map(x => x.keypair.pubkey),
|
|
210
|
+
outputCommitment: outputCommitments,
|
|
211
|
+
};
|
|
212
|
+
logger.info('generating ZK proof...');
|
|
213
|
+
// Generate the zero-knowledge proof
|
|
214
|
+
const { proof, publicSignals } = await prove(input, keyBasePath);
|
|
215
|
+
// Parse the proof and public signals into byte arrays
|
|
216
|
+
const proofInBytes = parseProofToBytesArray(proof);
|
|
217
|
+
const inputsInBytes = parseToBytesArray(publicSignals);
|
|
218
|
+
// Create the proof object to submit to the program
|
|
219
|
+
const proofToSubmit = {
|
|
220
|
+
proofA: proofInBytes.proofA,
|
|
221
|
+
proofB: proofInBytes.proofB.flat(),
|
|
222
|
+
proofC: proofInBytes.proofC,
|
|
223
|
+
root: inputsInBytes[0],
|
|
224
|
+
publicAmount: inputsInBytes[1],
|
|
225
|
+
extDataHash: inputsInBytes[2],
|
|
226
|
+
inputNullifiers: [
|
|
227
|
+
inputsInBytes[3],
|
|
228
|
+
inputsInBytes[4]
|
|
229
|
+
],
|
|
230
|
+
outputCommitments: [
|
|
231
|
+
inputsInBytes[5],
|
|
232
|
+
inputsInBytes[6]
|
|
233
|
+
],
|
|
234
|
+
};
|
|
235
|
+
// Find PDAs for nullifiers and commitments
|
|
236
|
+
const { nullifier0PDA, nullifier1PDA } = findNullifierPDAs(proofToSubmit);
|
|
237
|
+
const { nullifier2PDA, nullifier3PDA } = findCrossCheckNullifierPDAs(proofToSubmit);
|
|
238
|
+
// Serialize the proof and extData
|
|
239
|
+
const serializedProof = serializeProofAndExtData(proofToSubmit, extData, true);
|
|
240
|
+
logger.debug(`Total instruction data size: ${serializedProof.length} bytes`);
|
|
241
|
+
const [globalConfigPda, globalConfigPdaBump] = await PublicKey.findProgramAddressSync([Buffer.from("global_config")], PROGRAM_ID);
|
|
242
|
+
const treeAta = getAssociatedTokenAddressSync(mintAddress, globalConfigPda, true);
|
|
243
|
+
// Prepare withdraw parameters for indexer backend
|
|
244
|
+
const withdrawParams = {
|
|
245
|
+
serializedProof: serializedProof.toString('base64'),
|
|
246
|
+
treeAccount: treeAccount.toString(),
|
|
247
|
+
nullifier0PDA: nullifier0PDA.toString(),
|
|
248
|
+
nullifier1PDA: nullifier1PDA.toString(),
|
|
249
|
+
nullifier2PDA: nullifier2PDA.toString(),
|
|
250
|
+
nullifier3PDA: nullifier3PDA.toString(),
|
|
251
|
+
treeTokenAccount: treeTokenAccount.toString(),
|
|
252
|
+
globalConfigAccount: globalConfigAccount.toString(),
|
|
253
|
+
recipient: recipient.toString(),
|
|
254
|
+
feeRecipientAccount: FEE_RECIPIENT.toString(),
|
|
255
|
+
extAmount: extAmount,
|
|
256
|
+
fee: fee_base_units,
|
|
257
|
+
lookupTableAddress: ALT_ADDRESS.toString(),
|
|
258
|
+
senderAddress: publicKey.toString(),
|
|
259
|
+
treeAta: treeAta.toString(),
|
|
260
|
+
recipientAta: recipient_ata.toString(),
|
|
261
|
+
mintAddress: mintAddress.toString(),
|
|
262
|
+
feeRecipientTokenAccount: feeRecipientTokenAccount.toString()
|
|
263
|
+
};
|
|
264
|
+
logger.debug('Prepared withdraw parameters for indexer backend');
|
|
265
|
+
// Submit to indexer backend instead of directly to Solana
|
|
266
|
+
logger.info('submitting transaction to relayer...');
|
|
267
|
+
const signature = await submitWithdrawToIndexer(withdrawParams);
|
|
268
|
+
// Wait a moment for the transaction to be confirmed
|
|
269
|
+
logger.info('waiting for transaction confirmation...');
|
|
270
|
+
let retryTimes = 0;
|
|
271
|
+
let itv = 2;
|
|
272
|
+
const encryptedOutputStr = Buffer.from(encryptedOutput1).toString('hex');
|
|
273
|
+
let start = Date.now();
|
|
274
|
+
while (true) {
|
|
275
|
+
console.log(`retryTimes: ${retryTimes}`);
|
|
276
|
+
await new Promise(resolve => setTimeout(resolve, itv * 1000));
|
|
277
|
+
console.log('Fetching updated tree state...');
|
|
278
|
+
let res = await fetch(RELAYER_API_URL + '/utxos/check/' + encryptedOutputStr + '?token=usdc');
|
|
279
|
+
let resJson = await res.json();
|
|
280
|
+
console.log('resJson:', resJson);
|
|
281
|
+
if (resJson.exists) {
|
|
282
|
+
return { isPartial, tx: signature, recipient: recipient.toString(), base_units, fee_base_units };
|
|
283
|
+
}
|
|
284
|
+
if (retryTimes >= 10) {
|
|
285
|
+
throw new Error('Refresh the page to see latest balance.');
|
|
286
|
+
}
|
|
287
|
+
retryTimes++;
|
|
288
|
+
}
|
|
289
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "privacycash",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.19",
|
|
4
4
|
"description": "",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"repository": "https://github.com/Privacy-Cash/privacy-cash-sdk",
|
|
@@ -12,8 +12,9 @@
|
|
|
12
12
|
"type": "module",
|
|
13
13
|
"scripts": {
|
|
14
14
|
"build": "tsc",
|
|
15
|
-
"test": "vitest run --exclude \"**/__tests__/e2e.test.ts\"",
|
|
16
|
-
"teste2e": "vitest run e2e.test.ts"
|
|
15
|
+
"test": "vitest run --exclude \"**/__tests__/e2e.test.ts,**/__tests__/e2espl.test.ts\"",
|
|
16
|
+
"teste2e": "vitest run e2e.test.ts",
|
|
17
|
+
"teste2espl": "vitest run e2espl.test.ts"
|
|
17
18
|
},
|
|
18
19
|
"keywords": [],
|
|
19
20
|
"author": "",
|
|
@@ -23,6 +24,7 @@
|
|
|
23
24
|
"@ethersproject/keccak256": "^5.8.0",
|
|
24
25
|
"@ethersproject/sha2": "^5.8.0",
|
|
25
26
|
"@lightprotocol/hasher.rs": "^0.2.1",
|
|
27
|
+
"@solana/spl-token": "^0.4.14",
|
|
26
28
|
"@solana/web3.js": "^1.98.4",
|
|
27
29
|
"@types/bn.js": "^5.2.0",
|
|
28
30
|
"@types/snarkjs": "^0.7.9",
|
package/src/config.ts
CHANGED
|
@@ -1,28 +1,21 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { RELAYER_API_URL } from "./utils/constants.js";
|
|
2
2
|
|
|
3
3
|
type Config = {
|
|
4
4
|
withdraw_fee_rate: number
|
|
5
5
|
withdraw_rent_fee: number
|
|
6
6
|
deposit_fee_rate: number
|
|
7
|
+
usdc_withdraw_rent_fee: number
|
|
7
8
|
}
|
|
8
9
|
|
|
9
10
|
let config: Config | undefined
|
|
10
11
|
|
|
11
12
|
export async function getConfig<K extends keyof Config>(key: K): Promise<Config[K]> {
|
|
12
13
|
if (!config) {
|
|
13
|
-
const res = await fetch(
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
typeof data.withdraw_fee_rate !== 'number' ||
|
|
19
|
-
typeof data.withdraw_rent_fee !== 'number' ||
|
|
20
|
-
typeof data.deposit_fee_rate !== 'number'
|
|
21
|
-
) {
|
|
22
|
-
throw new Error("Invalid config received from server")
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
config = data
|
|
14
|
+
const res = await fetch(RELAYER_API_URL + '/config')
|
|
15
|
+
config = await res.json()
|
|
16
|
+
}
|
|
17
|
+
if (typeof config![key] != 'number') {
|
|
18
|
+
throw new Error(`can not get ${key} from ${RELAYER_API_URL}/config`)
|
|
26
19
|
}
|
|
27
20
|
return config![key]
|
|
28
21
|
}
|
package/src/deposit.ts
CHANGED
|
@@ -8,7 +8,7 @@ import { MerkleTree } from './utils/merkle_tree.js';
|
|
|
8
8
|
import { EncryptionService, serializeProofAndExtData } from './utils/encryption.js';
|
|
9
9
|
import { Keypair as UtxoKeypair } from './models/keypair.js';
|
|
10
10
|
import { getUtxos, isUtxoSpent } from './getUtxos.js';
|
|
11
|
-
import { FIELD_SIZE, FEE_RECIPIENT, MERKLE_TREE_DEPTH,
|
|
11
|
+
import { FIELD_SIZE, FEE_RECIPIENT, MERKLE_TREE_DEPTH, RELAYER_API_URL, PROGRAM_ID, ALT_ADDRESS } from './utils/constants.js';
|
|
12
12
|
import { useExistingALT } from './utils/address_lookup_table.js';
|
|
13
13
|
import { logger } from './utils/logger.js';
|
|
14
14
|
|
|
@@ -27,7 +27,7 @@ async function relayDepositToIndexer(signedTransaction: string, publicKey: Publi
|
|
|
27
27
|
params.referralWalletAddress = referrer
|
|
28
28
|
}
|
|
29
29
|
|
|
30
|
-
const response = await fetch(`${
|
|
30
|
+
const response = await fetch(`${RELAYER_API_URL}/deposit`, {
|
|
31
31
|
method: 'POST',
|
|
32
32
|
headers: {
|
|
33
33
|
'Content-Type': 'application/json',
|
|
@@ -83,7 +83,7 @@ export async function deposit({ lightWasm, storage, keyBasePath, publicKey, conn
|
|
|
83
83
|
logger.debug(`Wallet balance: ${balance / 1e9} SOL`);
|
|
84
84
|
|
|
85
85
|
if (balance < amount_in_lamports + fee_amount_in_lamports) {
|
|
86
|
-
new Error(`Insufficient balance: ${balance / 1e9} SOL. Need at least ${(amount_in_lamports + fee_amount_in_lamports) / LAMPORTS_PER_SOL} SOL.`);
|
|
86
|
+
throw new Error(`Insufficient balance: ${balance / 1e9} SOL. Need at least ${(amount_in_lamports + fee_amount_in_lamports) / LAMPORTS_PER_SOL} SOL.`);
|
|
87
87
|
}
|
|
88
88
|
|
|
89
89
|
const { treeAccount, treeTokenAccount, globalConfigAccount } = getProgramAccounts()
|
|
@@ -348,7 +348,6 @@ export async function deposit({ lightWasm, storage, keyBasePath, publicKey, conn
|
|
|
348
348
|
// Address Lookup Table for transaction size optimization
|
|
349
349
|
logger.debug('Setting up Address Lookup Table...');
|
|
350
350
|
|
|
351
|
-
const ALT_ADDRESS = new PublicKey('72bpRay17JKp4k8H87p7ieU9C6aRDy5yCqwvtpTN2wuU');
|
|
352
351
|
const lookupTableAccount = await useExistingALT(connection, ALT_ADDRESS);
|
|
353
352
|
|
|
354
353
|
if (!lookupTableAccount?.value) {
|
|
@@ -423,7 +422,7 @@ export async function deposit({ lightWasm, storage, keyBasePath, publicKey, conn
|
|
|
423
422
|
logger.debug(`retryTimes: ${retryTimes}`)
|
|
424
423
|
await new Promise(resolve => setTimeout(resolve, itv * 1000));
|
|
425
424
|
logger.debug('Fetching updated tree state...');
|
|
426
|
-
let res = await fetch(
|
|
425
|
+
let res = await fetch(RELAYER_API_URL + '/utxos/check/' + encryptedOutputStr)
|
|
427
426
|
let resJson = await res.json()
|
|
428
427
|
if (resJson.exists) {
|
|
429
428
|
logger.debug(`Top up successfully in ${((Date.now() - start) / 1000).toFixed(2)} seconds!`);
|
|
@@ -463,11 +462,6 @@ async function checkDepositLimit(connection: Connection) {
|
|
|
463
462
|
const maxDepositAmount = new BN(accountInfo.data.slice(4120, 4128), 'le');
|
|
464
463
|
const bump = accountInfo.data[4128];
|
|
465
464
|
|
|
466
|
-
console.log('\nš MerkleTreeAccount Details:');
|
|
467
|
-
console.log(`āā Authority: ${authority.toString()}`);
|
|
468
|
-
console.log(`āā Next Index: ${nextIndex.toString()}`);
|
|
469
|
-
console.log(`āā Root Index: ${rootIndex.toString()}`);
|
|
470
|
-
console.log(`āā Max Deposit Amount: ${maxDepositAmount.toString()} lamports`);
|
|
471
465
|
|
|
472
466
|
// Convert to SOL using BN division to handle large numbers
|
|
473
467
|
const lamportsPerSol = new BN(1_000_000_000);
|
|
@@ -483,7 +477,6 @@ async function checkDepositLimit(connection: Connection) {
|
|
|
483
477
|
const fractional = remainder.toNumber() / 1e9;
|
|
484
478
|
solFormatted = `${maxDepositSol.toString()}${fractional.toFixed(9).substring(1)}`;
|
|
485
479
|
}
|
|
486
|
-
console.log('solFormatted', solFormatted)
|
|
487
480
|
return Number(solFormatted)
|
|
488
481
|
|
|
489
482
|
} catch (error) {
|