Skip to content
Launch App >

Depositing tokens into the privacy pool is the first step in a private transfer. This guide covers the complete deposit flow.

  1. Generate Secrets

    Generate cryptographically secure random values for your nullifier and secret:

    import { randomBytes } from 'crypto';
    // Generate 32 random bytes for each
    const nullifier = randomBytes(32);
    const secret = randomBytes(32);
  2. Compute Commitment

    Hash the nullifier and secret using Poseidon:

    import { poseidon } from '[PLACEHOLDER: POSEIDON_NPM_PACKAGE]';
    // Commitment = poseidon(nullifier, secret)
    const commitment = poseidon([nullifier, secret]);
  3. Build Deposit Instruction

    Create the Anchor instruction for depositing:

    import { Program, BN } from '@coral-xyz/anchor';
    import { PublicKey, SystemProgram, SYSVAR_RENT_PUBKEY } from '@solana/web3.js';
    import { TOKEN_PROGRAM_ID, getAssociatedTokenAddress } from '@solana/spl-token';
    const PROGRAM_ID = new PublicKey('FGKoWNsvTTDCGW9JyR2DJWNzSXpejWk7yXcKsFFj9GQp');
    const DEPTH = 32;
    async function buildDepositInstruction(
    program: Program,
    payer: PublicKey,
    mint: PublicKey,
    commitment: Uint8Array,
    amount: BN
    ) {
    // Derive PDAs
    const [settings] = PublicKey.findProgramAddressSync(
    [Buffer.from('Settings')],
    PROGRAM_ID
    );
    const [merkle] = PublicKey.findProgramAddressSync(
    [Buffer.from('Merkle'), mint.toBuffer(), Buffer.from([DEPTH])],
    PROGRAM_ID
    );
    const [merkleToken] = PublicKey.findProgramAddressSync(
    [Buffer.from('MerkleToken'), mint.toBuffer(), Buffer.from([DEPTH])],
    PROGRAM_ID
    );
    const payerTokenAccount = await getAssociatedTokenAddress(mint, payer);
    return program.methods
    .deposit({
    commitment: Array.from(commitment),
    amount,
    })
    .accounts({
    signer: payer,
    signerTokenAccount: payerTokenAccount,
    settings,
    merkle,
    merkleTokenAccount: merkleToken,
    mint,
    systemProgram: SystemProgram.programId,
    tokenProgram: TOKEN_PROGRAM_ID,
    rent: SYSVAR_RENT_PUBKEY,
    })
    .instruction();
    }
  4. Sign and Send Transaction

    import { Transaction, sendAndConfirmTransaction } from '@solana/web3.js';
    async function deposit(
    connection: Connection,
    payer: Keypair,
    mint: PublicKey,
    amount: BN
    ) {
    // 1. Generate secrets
    const nullifier = randomBytes(32);
    const secret = randomBytes(32);
    // 2. Compute commitment
    const commitment = poseidon([nullifier, secret]);
    // 3. Build instruction
    const ix = await buildDepositInstruction(
    program,
    payer.publicKey,
    mint,
    commitment,
    amount
    );
    // 4. Send transaction
    const tx = new Transaction().add(ix);
    const signature = await sendAndConfirmTransaction(connection, tx, [payer]);
    return {
    signature,
    nullifier,
    secret,
    commitment,
    };
    }
  5. Store Secrets Securely

    After a successful deposit, you MUST store:

    interface DepositRecord {
    nullifier: Uint8Array;
    secret: Uint8Array;
    commitment: Uint8Array;
    leafIndex: number; // Get from transaction events
    mint: string;
    amount: string;
    txSignature: string;
    timestamp: number;
    }
    // Store securely - this is just an example
    function storeDeposit(record: DepositRecord) {
    const encrypted = encrypt(JSON.stringify({
    nullifier: Array.from(record.nullifier),
    secret: Array.from(record.secret),
    // ... other fields
    }));
    localStorage.setItem(`deposit_${record.txSignature}`, encrypted);
    }
import { Connection, Keypair, PublicKey } from '@solana/web3.js';
import { Program, AnchorProvider, BN } from '@coral-xyz/anchor';
import { randomBytes } from 'crypto';
import { poseidon } from '[PLACEHOLDER: POSEIDON_NPM_PACKAGE]';
const PROGRAM_ID = new PublicKey('FGKoWNsvTTDCGW9JyR2DJWNzSXpejWk7yXcKsFFj9GQp');
const DEPTH = 32;
async function performDeposit(
connection: Connection,
wallet: { publicKey: PublicKey; signTransaction: Function },
mint: PublicKey,
amountInBaseUnits: number
): Promise<DepositResult> {
// Generate secrets (CLIENT-SIDE ONLY)
const nullifier = randomBytes(32);
const secret = randomBytes(32);
const commitment = poseidon([nullifier, secret]);
// Build and send transaction
// ... (use instruction builder from above)
// Get leaf index from transaction logs/events
const leafIndex = await parseLeafIndexFromTx(signature);
// Return secrets for storage
return {
nullifier,
secret,
commitment,
leafIndex,
signature,
};
}

Tokens are deposited into pools based on amount. Check available deposit sizes for each token:

// Common deposit sizes (in base units)
// Actual sizes depend on token and configuration
const depositSizes = [
1_000_000, // 1 USDC
10_000_000, // 10 USDC
100_000_000, // 100 USDC
];

A deposit fee is deducted from your deposit:

  • Fee: [PLACEHOLDER: FEE_DEPOSIT_PCT]

See Fee Structure for details.

The leaf index is emitted in transaction logs. Parse it for later use:

async function parseLeafIndexFromTx(
connection: Connection,
signature: string
): Promise<number> {
const tx = await connection.getTransaction(signature, {
maxSupportedTransactionVersion: 0,
});
// Parse program logs for leaf index
// Implementation depends on how events are emitted
// ...
}
ErrorCauseSolution
InsufficientFundsNot enough tokensCheck balance before deposit
InvalidDepositSizeAmount not in allowed sizesUse a valid deposit size
MerkleTreeFullTree has max depositsWait for new tree or use different size
  • Generate secrets client-side only
  • Use cryptographically secure random
  • Store secrets encrypted
  • Provide backup mechanism to users
  • Never log or transmit secrets
  • Withdraw - Learn to withdraw deposited funds
  • Revert - Recover from failed deposits