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
package/README.md
CHANGED
|
@@ -9,7 +9,11 @@ THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR I
|
|
|
9
9
|
### Usage
|
|
10
10
|
This SDK provides APIs for developers to interact with Privacy Cash relayers easily. Developers can easily deposit/withdraw/query balances in Privacy Cash solana program.
|
|
11
11
|
|
|
12
|
-
Main APIs for this SDK are:
|
|
12
|
+
Main APIs for this SDK are:
|
|
13
|
+
a. for SOL:
|
|
14
|
+
deposit(), withdraw(), getPrivateBalance()
|
|
15
|
+
b. for USDC:
|
|
16
|
+
depositUSDC(), withdrawUSDC(), getPrivateBalanceUSDC()
|
|
13
17
|
|
|
14
18
|
Check the example project under /example folder. The code should be fairly self-explanatory.
|
|
15
19
|
|
package/__tests__/e2e.test.ts
CHANGED
|
@@ -9,8 +9,12 @@ describe('e2e test', async () => {
|
|
|
9
9
|
if (!process.env.PRIVATE_KEY) {
|
|
10
10
|
throw new Error('missing PRIVATE_KEY in .env')
|
|
11
11
|
}
|
|
12
|
+
if (!process.env.RPC_URL) {
|
|
13
|
+
throw new Error('missing RPC_URL in .env')
|
|
14
|
+
}
|
|
15
|
+
|
|
12
16
|
let client = new PrivacyCash({
|
|
13
|
-
RPC_url:
|
|
17
|
+
RPC_url: process.env.RPC_URL,
|
|
14
18
|
owner: process.env.PRIVATE_KEY
|
|
15
19
|
})
|
|
16
20
|
let balance_original = await client.getPrivateBalance()
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeAll, beforeEach, type Mock } from "vitest";
|
|
2
|
+
import dotenv from 'dotenv';
|
|
3
|
+
import { PrivacyCash } from "../src";
|
|
4
|
+
import { getAccount, getAssociatedTokenAddress } from "@solana/spl-token";
|
|
5
|
+
import { FEE_RECIPIENT, USDC_MINT } from "../src/utils/constants";
|
|
6
|
+
import { Connection } from "@solana/web3.js";
|
|
7
|
+
dotenv.config();
|
|
8
|
+
const TEST_AMOUNT = 2
|
|
9
|
+
const units_per_token = 1_000_000
|
|
10
|
+
describe('e2e test', async () => {
|
|
11
|
+
if (!process.env.PRIVATE_KEY) {
|
|
12
|
+
throw new Error('missing PRIVATE_KEY in .env')
|
|
13
|
+
}
|
|
14
|
+
if (!process.env.RPC_URL) {
|
|
15
|
+
throw new Error('missing RPC_URL in .env')
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const connection = new Connection(process.env.RPC_URL)
|
|
19
|
+
let fee_recipient_ata = await getAssociatedTokenAddress(USDC_MINT, FEE_RECIPIENT, true)
|
|
20
|
+
let feeRecipientAccount = await getAccount(connection, fee_recipient_ata)
|
|
21
|
+
let feeRecipientBalance_before = feeRecipientAccount.amount
|
|
22
|
+
|
|
23
|
+
let client = new PrivacyCash({
|
|
24
|
+
RPC_url: process.env.RPC_URL,
|
|
25
|
+
owner: process.env.PRIVATE_KEY
|
|
26
|
+
})
|
|
27
|
+
let balance_original = await client.getPrivateBalanceUSDC()
|
|
28
|
+
|
|
29
|
+
// deposit
|
|
30
|
+
await client.depositUSDC({
|
|
31
|
+
base_units: TEST_AMOUNT * units_per_token
|
|
32
|
+
})
|
|
33
|
+
let balance_after_deposit = await client.getPrivateBalanceUSDC()
|
|
34
|
+
|
|
35
|
+
// withdraw wrong amount
|
|
36
|
+
it(`show throw error if withdraw amount less than 1`, async () => {
|
|
37
|
+
await expect(client.withdrawUSDC({
|
|
38
|
+
base_units: 0.9 * units_per_token
|
|
39
|
+
})).rejects.toThrow()
|
|
40
|
+
})
|
|
41
|
+
|
|
42
|
+
// withdraw
|
|
43
|
+
let withdrawRes = await client.withdrawUSDC({
|
|
44
|
+
base_units: TEST_AMOUNT * units_per_token
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
await new Promise(r => setTimeout(r, 10_000));
|
|
48
|
+
|
|
49
|
+
feeRecipientAccount = await getAccount(connection, fee_recipient_ata)
|
|
50
|
+
let feeRecipientBalance_after = feeRecipientAccount.amount
|
|
51
|
+
|
|
52
|
+
let balance_after_withdraw = await client.getPrivateBalanceUSDC()
|
|
53
|
+
|
|
54
|
+
it('balance is a number', () => {
|
|
55
|
+
expect(balance_original.base_units).to.be.a('number')
|
|
56
|
+
})
|
|
57
|
+
|
|
58
|
+
it(`balance should be increased ${TEST_AMOUNT} USDC`, () => {
|
|
59
|
+
expect(balance_after_deposit.base_units).equal(balance_after_withdraw.base_units + TEST_AMOUNT * units_per_token)
|
|
60
|
+
})
|
|
61
|
+
|
|
62
|
+
it('should keep balance unchanged after depositing and withdrawing the same amount', () => {
|
|
63
|
+
expect(balance_original.base_units).equal(balance_after_withdraw.base_units)
|
|
64
|
+
})
|
|
65
|
+
|
|
66
|
+
it(`withdraw real amount plus fee should be ${TEST_AMOUNT * units_per_token}`, () => {
|
|
67
|
+
expect(withdrawRes.base_units + withdrawRes.fee_base_units).equal(TEST_AMOUNT * units_per_token)
|
|
68
|
+
})
|
|
69
|
+
|
|
70
|
+
it('fee recipient amount should be inceased by the withdraw fee', () => {
|
|
71
|
+
expect(withdrawRes.fee_base_units).equal(Number(feeRecipientBalance_after - feeRecipientBalance_before))
|
|
72
|
+
})
|
|
73
|
+
})
|
package/dist/config.d.ts
CHANGED
package/dist/config.js
CHANGED
|
@@ -1,16 +1,12 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { RELAYER_API_URL } from "./utils/constants.js";
|
|
2
2
|
let config;
|
|
3
3
|
export async function getConfig(key) {
|
|
4
4
|
if (!config) {
|
|
5
|
-
const res = await fetch(
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
typeof data.deposit_fee_rate !== 'number') {
|
|
11
|
-
throw new Error("Invalid config received from server");
|
|
12
|
-
}
|
|
13
|
-
config = data;
|
|
5
|
+
const res = await fetch(RELAYER_API_URL + '/config');
|
|
6
|
+
config = await res.json();
|
|
7
|
+
}
|
|
8
|
+
if (typeof config[key] != 'number') {
|
|
9
|
+
throw new Error(`can not get ${key} from ${RELAYER_API_URL}/config`);
|
|
14
10
|
}
|
|
15
11
|
return config[key];
|
|
16
12
|
}
|
package/dist/deposit.js
CHANGED
|
@@ -7,7 +7,7 @@ import { MerkleTree } from './utils/merkle_tree.js';
|
|
|
7
7
|
import { serializeProofAndExtData } from './utils/encryption.js';
|
|
8
8
|
import { Keypair as UtxoKeypair } from './models/keypair.js';
|
|
9
9
|
import { getUtxos } from './getUtxos.js';
|
|
10
|
-
import { FIELD_SIZE, FEE_RECIPIENT, MERKLE_TREE_DEPTH,
|
|
10
|
+
import { FIELD_SIZE, FEE_RECIPIENT, MERKLE_TREE_DEPTH, RELAYER_API_URL, PROGRAM_ID, ALT_ADDRESS } from './utils/constants.js';
|
|
11
11
|
import { useExistingALT } from './utils/address_lookup_table.js';
|
|
12
12
|
import { logger } from './utils/logger.js';
|
|
13
13
|
// Function to relay pre-signed deposit transaction to indexer backend
|
|
@@ -21,7 +21,7 @@ async function relayDepositToIndexer(signedTransaction, publicKey, referrer) {
|
|
|
21
21
|
if (referrer) {
|
|
22
22
|
params.referralWalletAddress = referrer;
|
|
23
23
|
}
|
|
24
|
-
const response = await fetch(`${
|
|
24
|
+
const response = await fetch(`${RELAYER_API_URL}/deposit`, {
|
|
25
25
|
method: 'POST',
|
|
26
26
|
headers: {
|
|
27
27
|
'Content-Type': 'application/json',
|
|
@@ -60,7 +60,7 @@ export async function deposit({ lightWasm, storage, keyBasePath, publicKey, conn
|
|
|
60
60
|
const balance = await connection.getBalance(publicKey);
|
|
61
61
|
logger.debug(`Wallet balance: ${balance / 1e9} SOL`);
|
|
62
62
|
if (balance < amount_in_lamports + fee_amount_in_lamports) {
|
|
63
|
-
new Error(`Insufficient balance: ${balance / 1e9} SOL. Need at least ${(amount_in_lamports + fee_amount_in_lamports) / LAMPORTS_PER_SOL} SOL.`);
|
|
63
|
+
throw new Error(`Insufficient balance: ${balance / 1e9} SOL. Need at least ${(amount_in_lamports + fee_amount_in_lamports) / LAMPORTS_PER_SOL} SOL.`);
|
|
64
64
|
}
|
|
65
65
|
const { treeAccount, treeTokenAccount, globalConfigAccount } = getProgramAccounts();
|
|
66
66
|
// Create the merkle tree with the pre-initialized poseidon hash
|
|
@@ -281,7 +281,6 @@ export async function deposit({ lightWasm, storage, keyBasePath, publicKey, conn
|
|
|
281
281
|
const { nullifier2PDA, nullifier3PDA } = findCrossCheckNullifierPDAs(proofToSubmit);
|
|
282
282
|
// Address Lookup Table for transaction size optimization
|
|
283
283
|
logger.debug('Setting up Address Lookup Table...');
|
|
284
|
-
const ALT_ADDRESS = new PublicKey('72bpRay17JKp4k8H87p7ieU9C6aRDy5yCqwvtpTN2wuU');
|
|
285
284
|
const lookupTableAccount = await useExistingALT(connection, ALT_ADDRESS);
|
|
286
285
|
if (!lookupTableAccount?.value) {
|
|
287
286
|
throw new Error(`ALT not found at address ${ALT_ADDRESS.toString()} `);
|
|
@@ -342,7 +341,7 @@ export async function deposit({ lightWasm, storage, keyBasePath, publicKey, conn
|
|
|
342
341
|
logger.debug(`retryTimes: ${retryTimes}`);
|
|
343
342
|
await new Promise(resolve => setTimeout(resolve, itv * 1000));
|
|
344
343
|
logger.debug('Fetching updated tree state...');
|
|
345
|
-
let res = await fetch(
|
|
344
|
+
let res = await fetch(RELAYER_API_URL + '/utxos/check/' + encryptedOutputStr);
|
|
346
345
|
let resJson = await res.json();
|
|
347
346
|
if (resJson.exists) {
|
|
348
347
|
logger.debug(`Top up successfully in ${((Date.now() - start) / 1000).toFixed(2)} seconds!`);
|
|
@@ -370,11 +369,6 @@ async function checkDepositLimit(connection) {
|
|
|
370
369
|
const rootIndex = new BN(accountInfo.data.slice(4112, 4120), 'le');
|
|
371
370
|
const maxDepositAmount = new BN(accountInfo.data.slice(4120, 4128), 'le');
|
|
372
371
|
const bump = accountInfo.data[4128];
|
|
373
|
-
console.log('\n📋 MerkleTreeAccount Details:');
|
|
374
|
-
console.log(`┌─ Authority: ${authority.toString()}`);
|
|
375
|
-
console.log(`├─ Next Index: ${nextIndex.toString()}`);
|
|
376
|
-
console.log(`├─ Root Index: ${rootIndex.toString()}`);
|
|
377
|
-
console.log(`├─ Max Deposit Amount: ${maxDepositAmount.toString()} lamports`);
|
|
378
372
|
// Convert to SOL using BN division to handle large numbers
|
|
379
373
|
const lamportsPerSol = new BN(1_000_000_000);
|
|
380
374
|
const maxDepositSol = maxDepositAmount.div(lamportsPerSol);
|
|
@@ -389,7 +383,6 @@ async function checkDepositLimit(connection) {
|
|
|
389
383
|
const fractional = remainder.toNumber() / 1e9;
|
|
390
384
|
solFormatted = `${maxDepositSol.toString()}${fractional.toFixed(9).substring(1)}`;
|
|
391
385
|
}
|
|
392
|
-
console.log('solFormatted', solFormatted);
|
|
393
386
|
return Number(solFormatted);
|
|
394
387
|
}
|
|
395
388
|
catch (error) {
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { Connection, PublicKey, VersionedTransaction } from '@solana/web3.js';
|
|
2
|
+
import * as hasher from '@lightprotocol/hasher.rs';
|
|
3
|
+
import { EncryptionService } from './utils/encryption.js';
|
|
4
|
+
type DepositParams = {
|
|
5
|
+
mintAddress: PublicKey;
|
|
6
|
+
publicKey: PublicKey;
|
|
7
|
+
connection: Connection;
|
|
8
|
+
base_units: number;
|
|
9
|
+
storage: Storage;
|
|
10
|
+
encryptionService: EncryptionService;
|
|
11
|
+
keyBasePath: string;
|
|
12
|
+
lightWasm: hasher.LightWasm;
|
|
13
|
+
referrer?: string;
|
|
14
|
+
transactionSigner: (tx: VersionedTransaction) => Promise<VersionedTransaction>;
|
|
15
|
+
};
|
|
16
|
+
export declare function depositSPL({ lightWasm, storage, keyBasePath, publicKey, connection, base_units, encryptionService, transactionSigner, referrer, mintAddress }: DepositParams): Promise<{
|
|
17
|
+
tx: string;
|
|
18
|
+
}>;
|
|
19
|
+
export {};
|
|
@@ -0,0 +1,434 @@
|
|
|
1
|
+
import { PublicKey, TransactionInstruction, SystemProgram, ComputeBudgetProgram, VersionedTransaction, TransactionMessage } from '@solana/web3.js';
|
|
2
|
+
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
|
+
import { getUtxosSPL } from './getUtxosSPL.js';
|
|
10
|
+
import { FIELD_SIZE, FEE_RECIPIENT, MERKLE_TREE_DEPTH, RELAYER_API_URL, PROGRAM_ID, ALT_ADDRESS } from './utils/constants.js';
|
|
11
|
+
import { useExistingALT } from './utils/address_lookup_table.js';
|
|
12
|
+
import { logger } from './utils/logger.js';
|
|
13
|
+
import { ASSOCIATED_TOKEN_PROGRAM_ID, TOKEN_PROGRAM_ID, getAssociatedTokenAddressSync, getMint, getAccount } from '@solana/spl-token';
|
|
14
|
+
// Function to relay pre-signed deposit transaction to indexer backend
|
|
15
|
+
async function relayDepositToIndexer({ signedTransaction, publicKey, referrer, mintAddress }) {
|
|
16
|
+
try {
|
|
17
|
+
logger.debug('Relaying pre-signed deposit transaction to indexer backend...');
|
|
18
|
+
const params = {
|
|
19
|
+
signedTransaction,
|
|
20
|
+
senderAddress: publicKey.toString()
|
|
21
|
+
};
|
|
22
|
+
if (referrer) {
|
|
23
|
+
params.referralWalletAddress = referrer;
|
|
24
|
+
}
|
|
25
|
+
params.mintAddress = mintAddress.toString();
|
|
26
|
+
const response = await fetch(`${RELAYER_API_URL}/deposit/spl`, {
|
|
27
|
+
method: 'POST',
|
|
28
|
+
headers: {
|
|
29
|
+
'Content-Type': 'application/json',
|
|
30
|
+
},
|
|
31
|
+
body: JSON.stringify(params)
|
|
32
|
+
});
|
|
33
|
+
if (!response.ok) {
|
|
34
|
+
console.log('res text:', await response.json());
|
|
35
|
+
throw new Error('response not ok');
|
|
36
|
+
// const errorData = await response.json() as { error?: string };
|
|
37
|
+
// throw new Error(`Deposit relay failed: ${response.status} ${response.statusText} - ${errorData.error || 'Unknown error'}`);
|
|
38
|
+
}
|
|
39
|
+
const result = await response.json();
|
|
40
|
+
logger.debug('Pre-signed deposit transaction relayed successfully!');
|
|
41
|
+
logger.debug('Response:', result);
|
|
42
|
+
return result.signature;
|
|
43
|
+
}
|
|
44
|
+
catch (error) {
|
|
45
|
+
console.error('Failed to relay deposit transaction to indexer:', error);
|
|
46
|
+
throw error;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
export async function depositSPL({ lightWasm, storage, keyBasePath, publicKey, connection, base_units, encryptionService, transactionSigner, referrer, mintAddress }) {
|
|
50
|
+
let mintInfo = await getMint(connection, mintAddress);
|
|
51
|
+
let units_per_token = 10 ** mintInfo.decimals;
|
|
52
|
+
let recipient = new PublicKey('AWexibGxNFKTa1b5R5MN4PJr9HWnWRwf8EW9g8cLx3dM');
|
|
53
|
+
let recipient_ata = getAssociatedTokenAddressSync(mintAddress, recipient, true);
|
|
54
|
+
let feeRecipientTokenAccount = getAssociatedTokenAddressSync(mintAddress, FEE_RECIPIENT, true);
|
|
55
|
+
let signerTokenAccount = getAssociatedTokenAddressSync(mintAddress, publicKey);
|
|
56
|
+
// Derive tree account PDA with mint address for SPL (different from SOL version)
|
|
57
|
+
const [treeAccount] = PublicKey.findProgramAddressSync([Buffer.from('merkle_tree'), mintAddress.toBuffer()], PROGRAM_ID);
|
|
58
|
+
let limitAmount = await checkDepositLimit(connection, treeAccount);
|
|
59
|
+
if (limitAmount && base_units > limitAmount * 1e6) {
|
|
60
|
+
throw new Error(`Don't deposit more than ${limitAmount} USDC`);
|
|
61
|
+
}
|
|
62
|
+
// check limit
|
|
63
|
+
// let limitAmount = await checkDepositLimit(connection)
|
|
64
|
+
// if (limitAmount && base_units > limitAmount * units_per_token) {
|
|
65
|
+
// throw new Error(`Don't deposit more than ${limitAmount} SOL`)
|
|
66
|
+
// }
|
|
67
|
+
// const base_units = amount_in_sol * units_per_token
|
|
68
|
+
const fee_base_units = 0;
|
|
69
|
+
logger.debug('Encryption key generated from user keypair');
|
|
70
|
+
logger.debug(`User wallet: ${publicKey.toString()}`);
|
|
71
|
+
logger.debug(`Deposit amount: ${base_units} base_units (${base_units / units_per_token} USDC)`);
|
|
72
|
+
logger.debug(`Calculated fee: ${fee_base_units} base_units (${fee_base_units / units_per_token} USDC)`);
|
|
73
|
+
// Check SPL balance
|
|
74
|
+
const accountInfo = await getAccount(connection, signerTokenAccount);
|
|
75
|
+
let balance = Number(accountInfo.amount);
|
|
76
|
+
logger.debug(`USDC wallet balance: ${balance / units_per_token} USDC`);
|
|
77
|
+
console.log('balance', balance);
|
|
78
|
+
console.log('base_units + fee_base_units', base_units + fee_base_units);
|
|
79
|
+
if (balance < (base_units + fee_base_units)) {
|
|
80
|
+
throw new Error(`Insufficient balance. Need at least ${(base_units + fee_base_units) / units_per_token} USDC.`);
|
|
81
|
+
}
|
|
82
|
+
// Check SOL balance
|
|
83
|
+
const solBalance = await connection.getBalance(publicKey);
|
|
84
|
+
logger.debug(`SOL Wallet balance: ${solBalance / 1e9} SOL`);
|
|
85
|
+
if (solBalance / 1e9 < 0.01) {
|
|
86
|
+
throw new Error(`Need at least 0.01 SOL for Solana fees.`);
|
|
87
|
+
}
|
|
88
|
+
const { globalConfigAccount } = getProgramAccounts();
|
|
89
|
+
// Create the merkle tree with the pre-initialized poseidon hash
|
|
90
|
+
const tree = new MerkleTree(MERKLE_TREE_DEPTH, lightWasm);
|
|
91
|
+
// Initialize root and nextIndex variables
|
|
92
|
+
const { root, nextIndex: currentNextIndex } = await queryRemoteTreeState('usdc');
|
|
93
|
+
logger.debug(`Using tree root: ${root}`);
|
|
94
|
+
logger.debug(`New UTXOs will be inserted at indices: ${currentNextIndex} and ${currentNextIndex + 1}`);
|
|
95
|
+
// Generate a deterministic private key derived from the wallet keypair
|
|
96
|
+
// const utxoPrivateKey = encryptionService.deriveUtxoPrivateKey();
|
|
97
|
+
const utxoPrivateKey = encryptionService.getUtxoPrivateKeyV2();
|
|
98
|
+
// Create a UTXO keypair that will be used for all inputs and outputs
|
|
99
|
+
const utxoKeypair = new UtxoKeypair(utxoPrivateKey, lightWasm);
|
|
100
|
+
logger.debug('Using wallet-derived UTXO keypair for deposit');
|
|
101
|
+
// Fetch existing UTXOs for this user
|
|
102
|
+
logger.debug('\nFetching existing UTXOs...');
|
|
103
|
+
const mintUtxos = await getUtxosSPL({ connection, publicKey, encryptionService, storage, mintAddress });
|
|
104
|
+
// Calculate output amounts and external amount based on scenario
|
|
105
|
+
let extAmount;
|
|
106
|
+
let outputAmount;
|
|
107
|
+
// Create inputs based on whether we have existing UTXOs
|
|
108
|
+
let inputs;
|
|
109
|
+
let inputMerklePathIndices;
|
|
110
|
+
let inputMerklePathElements;
|
|
111
|
+
if (mintUtxos.length === 0) {
|
|
112
|
+
// Scenario 1: Fresh deposit with dummy inputs - add new funds to the system
|
|
113
|
+
extAmount = base_units;
|
|
114
|
+
outputAmount = new BN(base_units).sub(new BN(fee_base_units)).toString();
|
|
115
|
+
logger.debug(`Fresh deposit scenario (no existing UTXOs):`);
|
|
116
|
+
logger.debug(`External amount (deposit): ${extAmount}`);
|
|
117
|
+
logger.debug(`Fee amount: ${fee_base_units}`);
|
|
118
|
+
logger.debug(`Output amount: ${outputAmount}`);
|
|
119
|
+
// Use two dummy UTXOs as inputs
|
|
120
|
+
inputs = [
|
|
121
|
+
new Utxo({
|
|
122
|
+
lightWasm,
|
|
123
|
+
keypair: utxoKeypair,
|
|
124
|
+
mintAddress: mintAddress.toString()
|
|
125
|
+
}),
|
|
126
|
+
new Utxo({
|
|
127
|
+
lightWasm,
|
|
128
|
+
keypair: utxoKeypair,
|
|
129
|
+
mintAddress: mintAddress.toString()
|
|
130
|
+
})
|
|
131
|
+
];
|
|
132
|
+
// Both inputs are dummy, so use mock indices and zero-filled Merkle paths
|
|
133
|
+
inputMerklePathIndices = inputs.map((input) => input.index || 0);
|
|
134
|
+
inputMerklePathElements = inputs.map(() => {
|
|
135
|
+
return [...new Array(tree.levels).fill("0")];
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
else {
|
|
139
|
+
// Scenario 2: Deposit that consolidates with existing UTXO(s)
|
|
140
|
+
const firstUtxo = mintUtxos[0];
|
|
141
|
+
const firstUtxoAmount = firstUtxo.amount;
|
|
142
|
+
const secondUtxoAmount = mintUtxos.length > 1 ? mintUtxos[1].amount : new BN(0);
|
|
143
|
+
extAmount = base_units; // Still depositing new funds
|
|
144
|
+
// Output combines existing UTXO amounts + new deposit amount - fee
|
|
145
|
+
outputAmount = firstUtxoAmount.add(secondUtxoAmount).add(new BN(base_units)).sub(new BN(fee_base_units)).toString();
|
|
146
|
+
logger.debug(`Deposit with consolidation scenario:`);
|
|
147
|
+
logger.debug(`First existing UTXO amount: ${firstUtxoAmount.toString()}`);
|
|
148
|
+
if (secondUtxoAmount.gt(new BN(0))) {
|
|
149
|
+
logger.debug(`Second existing UTXO amount: ${secondUtxoAmount.toString()}`);
|
|
150
|
+
}
|
|
151
|
+
logger.debug(`New deposit amount: ${base_units}`);
|
|
152
|
+
logger.debug(`Fee amount: ${fee_base_units}`);
|
|
153
|
+
logger.debug(`Output amount (existing UTXOs + deposit - fee): ${outputAmount}`);
|
|
154
|
+
logger.debug(`External amount (deposit): ${extAmount}`);
|
|
155
|
+
logger.debug('\nFirst UTXO to be consolidated:');
|
|
156
|
+
await firstUtxo.log();
|
|
157
|
+
// Use first existing UTXO as first input, and either second UTXO or dummy UTXO as second input
|
|
158
|
+
const secondUtxo = mintUtxos.length > 1 ? mintUtxos[1] : new Utxo({
|
|
159
|
+
lightWasm,
|
|
160
|
+
keypair: utxoKeypair,
|
|
161
|
+
amount: '0', // This UTXO will be inserted at currentNextIndex
|
|
162
|
+
mintAddress: mintAddress.toString()
|
|
163
|
+
});
|
|
164
|
+
inputs = [
|
|
165
|
+
firstUtxo, // Use the first existing UTXO
|
|
166
|
+
secondUtxo // Use second UTXO if available, otherwise dummy
|
|
167
|
+
];
|
|
168
|
+
// Fetch Merkle proofs for real UTXOs
|
|
169
|
+
const firstUtxoCommitment = await firstUtxo.getCommitment();
|
|
170
|
+
const firstUtxoMerkleProof = await fetchMerkleProof(firstUtxoCommitment, 'usdc');
|
|
171
|
+
let secondUtxoMerkleProof;
|
|
172
|
+
if (secondUtxo.amount.gt(new BN(0))) {
|
|
173
|
+
// Second UTXO is real, fetch its proof
|
|
174
|
+
const secondUtxoCommitment = await secondUtxo.getCommitment();
|
|
175
|
+
secondUtxoMerkleProof = await fetchMerkleProof(secondUtxoCommitment, 'usdc');
|
|
176
|
+
logger.debug('\nSecond UTXO to be consolidated:');
|
|
177
|
+
await secondUtxo.log();
|
|
178
|
+
}
|
|
179
|
+
// Use the real pathIndices from API for real inputs, mock index for dummy input
|
|
180
|
+
inputMerklePathIndices = [
|
|
181
|
+
firstUtxo.index || 0, // Use the real UTXO's index
|
|
182
|
+
secondUtxo.amount.gt(new BN(0)) ? (secondUtxo.index || 0) : 0 // Real UTXO index or dummy
|
|
183
|
+
];
|
|
184
|
+
// Create Merkle path elements: real proof for real inputs, zeros for dummy input
|
|
185
|
+
inputMerklePathElements = [
|
|
186
|
+
firstUtxoMerkleProof.pathElements, // Real Merkle proof for first existing UTXO
|
|
187
|
+
secondUtxo.amount.gt(new BN(0)) ? secondUtxoMerkleProof.pathElements : [...new Array(tree.levels).fill("0")] // Real proof or zero-filled for dummy
|
|
188
|
+
];
|
|
189
|
+
logger.debug(`Using first UTXO with amount: ${firstUtxo.amount.toString()} and index: ${firstUtxo.index}`);
|
|
190
|
+
logger.debug(`Using second ${secondUtxo.amount.gt(new BN(0)) ? 'UTXO' : 'dummy UTXO'} with amount: ${secondUtxo.amount.toString()}${secondUtxo.amount.gt(new BN(0)) ? ` and index: ${secondUtxo.index}` : ''}`);
|
|
191
|
+
logger.debug(`First UTXO Merkle proof path indices from API: [${firstUtxoMerkleProof.pathIndices.join(', ')}]`);
|
|
192
|
+
if (secondUtxo.amount.gt(new BN(0))) {
|
|
193
|
+
logger.debug(`Second UTXO Merkle proof path indices from API: [${secondUtxoMerkleProof.pathIndices.join(', ')}]`);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
const publicAmountForCircuit = new BN(extAmount).sub(new BN(fee_base_units)).add(FIELD_SIZE).mod(FIELD_SIZE);
|
|
197
|
+
logger.debug(`Public amount calculation: (${extAmount} - ${fee_base_units} + FIELD_SIZE) % FIELD_SIZE = ${publicAmountForCircuit.toString()}`);
|
|
198
|
+
// Create outputs for the transaction with the same shared keypair
|
|
199
|
+
const outputs = [
|
|
200
|
+
new Utxo({
|
|
201
|
+
lightWasm,
|
|
202
|
+
amount: outputAmount,
|
|
203
|
+
keypair: utxoKeypair,
|
|
204
|
+
index: currentNextIndex, // This UTXO will be inserted at currentNextIndex
|
|
205
|
+
mintAddress: mintAddress.toString()
|
|
206
|
+
}), // Output with value (either deposit amount minus fee, or input amount minus fee)
|
|
207
|
+
new Utxo({
|
|
208
|
+
lightWasm,
|
|
209
|
+
amount: '0',
|
|
210
|
+
keypair: utxoKeypair,
|
|
211
|
+
index: currentNextIndex + 1, // This UTXO will be inserted at currentNextIndex
|
|
212
|
+
mintAddress: mintAddress.toString()
|
|
213
|
+
}) // Empty UTXO
|
|
214
|
+
];
|
|
215
|
+
// Verify this matches the circuit balance equation: sumIns + publicAmount = sumOuts
|
|
216
|
+
const sumIns = inputs.reduce((sum, input) => sum.add(input.amount), new BN(0));
|
|
217
|
+
const sumOuts = outputs.reduce((sum, output) => sum.add(output.amount), new BN(0));
|
|
218
|
+
logger.debug(`Circuit balance check: sumIns(${sumIns.toString()}) + publicAmount(${publicAmountForCircuit.toString()}) should equal sumOuts(${sumOuts.toString()})`);
|
|
219
|
+
// Convert to circuit-compatible format
|
|
220
|
+
const publicAmountCircuitResult = sumIns.add(publicAmountForCircuit).mod(FIELD_SIZE);
|
|
221
|
+
logger.debug(`Balance verification: ${sumIns.toString()} + ${publicAmountForCircuit.toString()} (mod FIELD_SIZE) = ${publicAmountCircuitResult.toString()}`);
|
|
222
|
+
logger.debug(`Expected sum of outputs: ${sumOuts.toString()}`);
|
|
223
|
+
logger.debug(`Balance equation satisfied: ${publicAmountCircuitResult.eq(sumOuts)}`);
|
|
224
|
+
// Generate nullifiers and commitments
|
|
225
|
+
const inputNullifiers = await Promise.all(inputs.map(x => x.getNullifier()));
|
|
226
|
+
const outputCommitments = await Promise.all(outputs.map(x => x.getCommitment()));
|
|
227
|
+
// Save original commitment and nullifier values for verification
|
|
228
|
+
logger.debug('\n=== UTXO VALIDATION ===');
|
|
229
|
+
logger.debug('Output 0 Commitment:', outputCommitments[0]);
|
|
230
|
+
logger.debug('Output 1 Commitment:', outputCommitments[1]);
|
|
231
|
+
// Encrypt the UTXO data using a compact format that includes the keypair
|
|
232
|
+
logger.debug('\nEncrypting UTXOs with keypair data...');
|
|
233
|
+
const encryptedOutput1 = encryptionService.encryptUtxo(outputs[0]);
|
|
234
|
+
const encryptedOutput2 = encryptionService.encryptUtxo(outputs[1]);
|
|
235
|
+
logger.debug(`\nOutput[0] (with value):`);
|
|
236
|
+
await outputs[0].log();
|
|
237
|
+
logger.debug(`\nOutput[1] (empty):`);
|
|
238
|
+
await outputs[1].log();
|
|
239
|
+
logger.debug(`\nEncrypted output 1 size: ${encryptedOutput1.length} bytes`);
|
|
240
|
+
logger.debug(`Encrypted output 2 size: ${encryptedOutput2.length} bytes`);
|
|
241
|
+
logger.debug(`Total encrypted outputs size: ${encryptedOutput1.length + encryptedOutput2.length} bytes`);
|
|
242
|
+
// Test decryption to verify commitment values match
|
|
243
|
+
logger.debug('\n=== TESTING DECRYPTION ===');
|
|
244
|
+
logger.debug('Decrypting output 1 to verify commitment matches...');
|
|
245
|
+
const decryptedUtxo1 = await encryptionService.decryptUtxo(encryptedOutput1, lightWasm);
|
|
246
|
+
const decryptedCommitment1 = await decryptedUtxo1.getCommitment();
|
|
247
|
+
logger.debug('Original commitment:', outputCommitments[0]);
|
|
248
|
+
logger.debug('Decrypted commitment:', decryptedCommitment1);
|
|
249
|
+
logger.debug('Commitment matches:', outputCommitments[0] === decryptedCommitment1);
|
|
250
|
+
// Create the deposit ExtData with real encrypted outputs
|
|
251
|
+
const extData = {
|
|
252
|
+
// recipient - just a placeholder, not actually used for deposits.
|
|
253
|
+
recipient: recipient_ata,
|
|
254
|
+
extAmount: new BN(extAmount),
|
|
255
|
+
encryptedOutput1: encryptedOutput1,
|
|
256
|
+
encryptedOutput2: encryptedOutput2,
|
|
257
|
+
fee: new BN(fee_base_units),
|
|
258
|
+
feeRecipient: feeRecipientTokenAccount,
|
|
259
|
+
mintAddress: mintAddress.toString()
|
|
260
|
+
};
|
|
261
|
+
// Calculate the extDataHash with the encrypted outputs (now includes mintAddress for security)
|
|
262
|
+
const calculatedExtDataHash = getExtDataHash(extData);
|
|
263
|
+
// Create the input for the proof generation (must match circuit input order exactly)
|
|
264
|
+
const input = {
|
|
265
|
+
// Common transaction data
|
|
266
|
+
root: root,
|
|
267
|
+
mintAddress: getMintAddressField(mintAddress), // new mint address
|
|
268
|
+
publicAmount: publicAmountForCircuit.toString(), // Use proper field arithmetic result
|
|
269
|
+
extDataHash: calculatedExtDataHash,
|
|
270
|
+
// Input UTXO data (UTXOs being spent) - ensure all values are in decimal format
|
|
271
|
+
inAmount: inputs.map(x => x.amount.toString(10)),
|
|
272
|
+
inPrivateKey: inputs.map(x => x.keypair.privkey),
|
|
273
|
+
inBlinding: inputs.map(x => x.blinding.toString(10)),
|
|
274
|
+
inPathIndices: inputMerklePathIndices,
|
|
275
|
+
inPathElements: inputMerklePathElements,
|
|
276
|
+
inputNullifier: inputNullifiers, // Use resolved values instead of Promise objects
|
|
277
|
+
// Output UTXO data (UTXOs being created) - ensure all values are in decimal format
|
|
278
|
+
outAmount: outputs.map(x => x.amount.toString(10)),
|
|
279
|
+
outBlinding: outputs.map(x => x.blinding.toString(10)),
|
|
280
|
+
outPubkey: outputs.map(x => x.keypair.pubkey),
|
|
281
|
+
outputCommitment: outputCommitments,
|
|
282
|
+
};
|
|
283
|
+
logger.info('generating ZK proof...');
|
|
284
|
+
// Generate the zero-knowledge proof
|
|
285
|
+
const { proof, publicSignals } = await prove(input, keyBasePath);
|
|
286
|
+
// Parse the proof and public signals into byte arrays
|
|
287
|
+
const proofInBytes = parseProofToBytesArray(proof);
|
|
288
|
+
const inputsInBytes = parseToBytesArray(publicSignals);
|
|
289
|
+
// Create the proof object to submit to the program
|
|
290
|
+
const proofToSubmit = {
|
|
291
|
+
proofA: proofInBytes.proofA,
|
|
292
|
+
proofB: proofInBytes.proofB.flat(),
|
|
293
|
+
proofC: proofInBytes.proofC,
|
|
294
|
+
root: inputsInBytes[0],
|
|
295
|
+
publicAmount: inputsInBytes[1],
|
|
296
|
+
extDataHash: inputsInBytes[2],
|
|
297
|
+
inputNullifiers: [
|
|
298
|
+
inputsInBytes[3],
|
|
299
|
+
inputsInBytes[4]
|
|
300
|
+
],
|
|
301
|
+
outputCommitments: [
|
|
302
|
+
inputsInBytes[5],
|
|
303
|
+
inputsInBytes[6]
|
|
304
|
+
],
|
|
305
|
+
};
|
|
306
|
+
// Find PDAs for nullifiers and commitments
|
|
307
|
+
const { nullifier0PDA, nullifier1PDA } = findNullifierPDAs(proofToSubmit);
|
|
308
|
+
const { nullifier2PDA, nullifier3PDA } = findCrossCheckNullifierPDAs(proofToSubmit);
|
|
309
|
+
const [globalConfigPda, globalConfigPdaBump] = await PublicKey.findProgramAddressSync([Buffer.from("global_config")], PROGRAM_ID);
|
|
310
|
+
const treeAta = getAssociatedTokenAddressSync(mintAddress, globalConfigPda, true);
|
|
311
|
+
const lookupTableAccount = await useExistingALT(connection, ALT_ADDRESS);
|
|
312
|
+
if (!lookupTableAccount?.value) {
|
|
313
|
+
throw new Error(`ALT not found at address ${ALT_ADDRESS.toString()} `);
|
|
314
|
+
}
|
|
315
|
+
// Serialize the proof and extData with SPL discriminator
|
|
316
|
+
const serializedProof = serializeProofAndExtData(proofToSubmit, extData, true);
|
|
317
|
+
logger.debug(`Total instruction data size: ${serializedProof.length} bytes`);
|
|
318
|
+
// Create the deposit instruction (user signs, not relayer)
|
|
319
|
+
const depositInstruction = new TransactionInstruction({
|
|
320
|
+
keys: [
|
|
321
|
+
{ pubkey: treeAccount, isSigner: false, isWritable: true },
|
|
322
|
+
{ pubkey: nullifier0PDA, isSigner: false, isWritable: true },
|
|
323
|
+
{ pubkey: nullifier1PDA, isSigner: false, isWritable: true },
|
|
324
|
+
{ pubkey: nullifier2PDA, isSigner: false, isWritable: false },
|
|
325
|
+
{ pubkey: nullifier3PDA, isSigner: false, isWritable: false },
|
|
326
|
+
{ pubkey: globalConfigAccount, isSigner: false, isWritable: false },
|
|
327
|
+
// signer
|
|
328
|
+
{ pubkey: publicKey, isSigner: true, isWritable: true },
|
|
329
|
+
// SPL token mint
|
|
330
|
+
{ pubkey: mintAddress, isSigner: false, isWritable: false },
|
|
331
|
+
// signer's token account
|
|
332
|
+
{ pubkey: signerTokenAccount, isSigner: false, isWritable: true },
|
|
333
|
+
// recipient (placeholder)
|
|
334
|
+
{ pubkey: recipient, isSigner: false, isWritable: true },
|
|
335
|
+
// recipient's token account (placeholder)
|
|
336
|
+
{ pubkey: recipient_ata, isSigner: false, isWritable: true },
|
|
337
|
+
// tree ATA
|
|
338
|
+
{ pubkey: treeAta, isSigner: false, isWritable: true },
|
|
339
|
+
// fee recipient token account
|
|
340
|
+
{ pubkey: feeRecipientTokenAccount, isSigner: false, isWritable: true },
|
|
341
|
+
// token program id
|
|
342
|
+
{ pubkey: TOKEN_PROGRAM_ID, isSigner: false, isWritable: false },
|
|
343
|
+
// ATA program
|
|
344
|
+
{ pubkey: ASSOCIATED_TOKEN_PROGRAM_ID, isSigner: false, isWritable: false },
|
|
345
|
+
// system protgram
|
|
346
|
+
{ pubkey: SystemProgram.programId, isSigner: false, isWritable: false },
|
|
347
|
+
],
|
|
348
|
+
programId: PROGRAM_ID,
|
|
349
|
+
data: serializedProof,
|
|
350
|
+
});
|
|
351
|
+
// Set compute budget for the transaction
|
|
352
|
+
const modifyComputeUnits = ComputeBudgetProgram.setComputeUnitLimit({
|
|
353
|
+
units: 1_000_000
|
|
354
|
+
});
|
|
355
|
+
// Create versioned transaction with Address Lookup Table
|
|
356
|
+
const recentBlockhash = await connection.getLatestBlockhash();
|
|
357
|
+
const messageV0 = new TransactionMessage({
|
|
358
|
+
payerKey: publicKey, // User pays for their own deposit
|
|
359
|
+
recentBlockhash: recentBlockhash.blockhash,
|
|
360
|
+
instructions: [modifyComputeUnits, depositInstruction],
|
|
361
|
+
}).compileToV0Message([lookupTableAccount.value]);
|
|
362
|
+
let versionedTransaction = new VersionedTransaction(messageV0);
|
|
363
|
+
// sign tx
|
|
364
|
+
versionedTransaction = await transactionSigner(versionedTransaction);
|
|
365
|
+
logger.debug('Transaction signed by user');
|
|
366
|
+
// Serialize the signed transaction for relay
|
|
367
|
+
const serializedTransaction = Buffer.from(versionedTransaction.serialize()).toString('base64');
|
|
368
|
+
logger.debug('Prepared signed transaction for relay to indexer backend');
|
|
369
|
+
// Relay the pre-signed transaction to indexer backend
|
|
370
|
+
logger.info('submitting transaction to relayer...');
|
|
371
|
+
const signature = await relayDepositToIndexer({
|
|
372
|
+
mintAddress: mintAddress.toString(),
|
|
373
|
+
publicKey,
|
|
374
|
+
signedTransaction: serializedTransaction,
|
|
375
|
+
referrer
|
|
376
|
+
});
|
|
377
|
+
logger.debug('Transaction signature:', signature);
|
|
378
|
+
logger.debug(`Transaction link: https://explorer.solana.com/tx/${signature}`);
|
|
379
|
+
logger.info('Waiting for transaction confirmation...');
|
|
380
|
+
let retryTimes = 0;
|
|
381
|
+
let itv = 2;
|
|
382
|
+
const encryptedOutputStr = Buffer.from(encryptedOutput1).toString('hex');
|
|
383
|
+
let start = Date.now();
|
|
384
|
+
while (true) {
|
|
385
|
+
logger.debug(`retryTimes: ${retryTimes}`);
|
|
386
|
+
await new Promise(resolve => setTimeout(resolve, itv * 1000));
|
|
387
|
+
logger.debug('Fetching updated tree state...');
|
|
388
|
+
let url = RELAYER_API_URL + '/utxos/check/' + encryptedOutputStr + '?token=usdc';
|
|
389
|
+
let res = await fetch(url);
|
|
390
|
+
let resJson = await res.json();
|
|
391
|
+
if (resJson.exists) {
|
|
392
|
+
logger.debug(`Top up successfully in ${((Date.now() - start) / 1000).toFixed(2)} seconds!`);
|
|
393
|
+
return { tx: signature };
|
|
394
|
+
}
|
|
395
|
+
if (retryTimes >= 10) {
|
|
396
|
+
throw new Error('Refresh the page to see latest balance.');
|
|
397
|
+
}
|
|
398
|
+
retryTimes++;
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
async function checkDepositLimit(connection, treeAccount) {
|
|
402
|
+
try {
|
|
403
|
+
// Fetch the account data
|
|
404
|
+
const accountInfo = await connection.getAccountInfo(treeAccount);
|
|
405
|
+
if (!accountInfo) {
|
|
406
|
+
console.error('❌ Tree account not found. Make sure the program is initialized.');
|
|
407
|
+
return;
|
|
408
|
+
}
|
|
409
|
+
const authority = new PublicKey(accountInfo.data.slice(8, 40));
|
|
410
|
+
const nextIndex = new BN(accountInfo.data.slice(40, 48), 'le');
|
|
411
|
+
const rootIndex = new BN(accountInfo.data.slice(4112, 4120), 'le');
|
|
412
|
+
const maxDepositAmount = new BN(accountInfo.data.slice(4120, 4128), 'le');
|
|
413
|
+
const bump = accountInfo.data[4128];
|
|
414
|
+
// Convert to SOL using BN division to handle large numbers
|
|
415
|
+
const lamportsPerSol = new BN(1e6);
|
|
416
|
+
const maxDepositSol = maxDepositAmount.div(lamportsPerSol);
|
|
417
|
+
const remainder = maxDepositAmount.mod(lamportsPerSol);
|
|
418
|
+
// Format the SOL amount with decimals
|
|
419
|
+
let solFormatted = '1';
|
|
420
|
+
if (remainder.eq(new BN(0))) {
|
|
421
|
+
solFormatted = maxDepositSol.toString();
|
|
422
|
+
}
|
|
423
|
+
else {
|
|
424
|
+
// Handle fractional SOL by converting remainder to decimal
|
|
425
|
+
const fractional = remainder.toNumber() / 1e6;
|
|
426
|
+
solFormatted = `${maxDepositSol.toString()}${fractional.toFixed(9).substring(1)}`;
|
|
427
|
+
}
|
|
428
|
+
return Number(solFormatted);
|
|
429
|
+
}
|
|
430
|
+
catch (error) {
|
|
431
|
+
console.log('❌ Error reading deposit limit:', error);
|
|
432
|
+
throw error;
|
|
433
|
+
}
|
|
434
|
+
}
|
package/dist/exportUtils.d.ts
CHANGED
|
@@ -4,3 +4,6 @@ export { withdraw } from './withdraw.js';
|
|
|
4
4
|
export { EncryptionService } from './utils/encryption.js';
|
|
5
5
|
export { setLogger } from './utils/logger.js';
|
|
6
6
|
export { getBalanceFromUtxos, getUtxos, localstorageKey } from './getUtxos.js';
|
|
7
|
+
export { depositSPL } from './depositSPL.js';
|
|
8
|
+
export { withdrawSPL } from './withdrawSPL.js';
|
|
9
|
+
export { getBalanceFromUtxosSPL, getUtxosSPL } from './getUtxosSPL.js';
|