SDK Quickstart
Deposit and withdraw in 10 lines of code
SDK Quickstart
This guide demonstrates how to deposit funds into a ZKMix pool and withdraw them to a fresh address using the SDK. By the end, you will have a working deposit-and-withdraw flow in under 10 lines of meaningful code.
Prerequisites
Before starting, ensure you have:
- The SDK installed:
npm install @zkmix/sdk @solana/web3.js @coral-xyz/anchor - A Solana wallet with funds (devnet SOL for testing)
- Node.js v18 or later
Minimal Deposit Example
A deposit generates a random commitment, inserts it into the Merkle tree, and transfers the denomination amount to the pool vault. The function returns a DepositNote that you must save securely -- it contains the secret and nullifier needed to withdraw later.
import { Connection, Keypair } from "@solana/web3.js";
import { ZKMix } from "@zkmix/sdk";
// Setup
const connection = new Connection("https://api.devnet.solana.com", "confirmed");
const wallet = Keypair.fromSecretKey(/* your secret key bytes */);
const zkmix = new ZKMix({ connection, cluster: "devnet" });
// Deposit 1 SOL into the mixer
const depositNote = await zkmix.deposit({
wallet,
token: "SOL",
denomination: 1_000_000_000, // 1 SOL in lamports
});
// IMPORTANT: Save this note securely! You need it to withdraw.
console.log("Deposit successful!");
console.log("Note:", depositNote.serialize());That is the entire deposit flow. The deposit() method handles:
- Generating a cryptographically random secret and nullifier
- Computing the Poseidon commitment hash
- Building the deposit transaction
- Signing and submitting the transaction
- Waiting for confirmation
- Returning a
DepositNotecontaining everything needed for withdrawal
Saving the Deposit Note
The DepositNote is the only way to recover your funds. If you lose it, the deposited funds are permanently locked in the pool. Save it securely:
// Serialize to a string for storage
const noteString = depositNote.serialize();
// Example output: "zkmix-1.0-devnet-SOL-1000000000-0x3a4b...7c8d-0x9e0f...2a3b-42"
// Save to a file (Node.js)
import fs from "fs";
fs.writeFileSync("my-deposit-note.txt", noteString);
// Or save to localStorage (browser)
localStorage.setItem("zkmix-deposit", noteString);
// Later, deserialize to use for withdrawal
const restoredNote = zkmix.parseNote(noteString);Minimal Withdrawal Example
A withdrawal generates a zero-knowledge proof that you know the secret and nullifier for a deposit in the pool, then submits it to the on-chain program (optionally through a relayer) to transfer funds to a fresh address.
import { Connection, Keypair, PublicKey } from "@solana/web3.js";
import { ZKMix } from "@zkmix/sdk";
// Setup
const connection = new Connection("https://api.devnet.solana.com", "confirmed");
const zkmix = new ZKMix({ connection, cluster: "devnet" });
// Load your saved deposit note
const noteString = "zkmix-1.0-devnet-SOL-1000000000-0x3a4b...7c8d-0x9e0f...2a3b-42";
const depositNote = zkmix.parseNote(noteString);
// Withdraw to a fresh address using a relayer
const recipient = new PublicKey("FreshAddress111111111111111111111111111111111");
const result = await zkmix.withdraw({
deposit: depositNote,
recipient,
useRelayer: true,
});
console.log("Withdrawal successful!");
console.log("Tx:", result.txSignature);The withdraw() method handles:
- Fetching the current Merkle tree state from on-chain
- Computing the Merkle proof path for your deposit's leaf
- Generating the Groth16 zero-knowledge proof (this takes a few seconds)
- Selecting a relayer and submitting the proof via the relayer API
- Waiting for on-chain confirmation
- Returning the transaction signature
Direct Withdrawal (Without Relayer)
If the recipient address already has SOL to pay for gas, you can skip the relayer and submit the withdrawal transaction directly:
const result = await zkmix.withdraw({
deposit: depositNote,
recipient,
wallet, // The wallet that will sign and pay for the transaction
useRelayer: false,
});This saves the relayer fee but requires the wallet to have SOL for the transaction fee. The wallet does not need to be the same account that made the deposit -- any account with SOL can pay for the transaction, though using an unrelated account could create a linkability concern.
Full Working Example
Here is a complete, self-contained example that deposits and withdraws on devnet:
import { Connection, Keypair, LAMPORTS_PER_SOL } from "@solana/web3.js";
import { ZKMix } from "@zkmix/sdk";
async function main() {
// 1. Connect to devnet
const connection = new Connection("https://api.devnet.solana.com", "confirmed");
const wallet = Keypair.generate();
const zkmix = new ZKMix({ connection, cluster: "devnet" });
// 2. Airdrop SOL for testing
const sig = await connection.requestAirdrop(wallet.publicKey, 2 * LAMPORTS_PER_SOL);
await connection.confirmTransaction(sig);
console.log(`Funded wallet: ${wallet.publicKey.toBase58()}`);
// 3. Deposit 1 SOL
console.log("Depositing 1 SOL...");
const depositNote = await zkmix.deposit({
wallet,
token: "SOL",
denomination: LAMPORTS_PER_SOL,
});
console.log(`Deposit confirmed! Leaf index: ${depositNote.leafIndex}`);
// 4. Save the note
const savedNote = depositNote.serialize();
console.log(`Note: ${savedNote}`);
// 5. Wait a moment (in production, you would wait longer for anonymity)
console.log("Waiting 10 seconds before withdrawal...");
await new Promise((resolve) => setTimeout(resolve, 10_000));
// 6. Withdraw to a fresh address via relayer
const freshWallet = Keypair.generate();
console.log(`Withdrawing to fresh address: ${freshWallet.publicKey.toBase58()}`);
const result = await zkmix.withdraw({
deposit: zkmix.parseNote(savedNote),
recipient: freshWallet.publicKey,
useRelayer: true,
});
console.log(`Withdrawal confirmed! Tx: ${result.txSignature}`);
// 7. Check the fresh wallet balance
const balance = await connection.getBalance(freshWallet.publicKey);
console.log(`Fresh wallet balance: ${balance / LAMPORTS_PER_SOL} SOL`);
// Should be ~0.997 SOL (1 SOL minus relayer fee)
}
main().catch(console.error);Using with @solana/web3.js
The SDK is designed to integrate seamlessly with the standard Solana web3.js library. It accepts standard Connection objects, Keypair and PublicKey types, and returns standard transaction signatures.
Wallet Adapter Integration
For browser-based applications using the Solana Wallet Adapter, pass the wallet adapter's signTransaction and publicKey instead of a Keypair:
import { useWallet, useConnection } from "@solana/wallet-adapter-react";
import { ZKMix } from "@zkmix/sdk";
function DepositButton() {
const { connection } = useConnection();
const wallet = useWallet();
const handleDeposit = async () => {
const zkmix = new ZKMix({ connection, cluster: "mainnet-beta" });
const depositNote = await zkmix.deposit({
wallet: {
publicKey: wallet.publicKey!,
signTransaction: wallet.signTransaction!,
},
token: "SOL",
denomination: 1_000_000_000,
});
// Save the note
const noteString = depositNote.serialize();
localStorage.setItem("zkmix-note", noteString);
alert("Deposit successful! Note saved.");
};
return <button onClick={handleDeposit}>Deposit 1 SOL</button>;
}Custom Transaction Options
You can pass additional transaction options to control confirmation behavior:
const depositNote = await zkmix.deposit({
wallet,
token: "SOL",
denomination: 1_000_000_000,
options: {
commitment: "finalized", // Wait for finalization (slower but safer)
maxRetries: 3, // Retry on failure
skipPreflight: false, // Run simulation before sending
computeUnits: 300_000, // Custom compute unit limit for deposit
priorityFee: 50_000, // Priority fee in microlamports
},
});Next Steps
- Read the full API Reference for all available methods and options.
- See the Examples page for React integration, batch operations, and more.
- Learn about Relayers to understand how withdrawal privacy works.
- Review the Threat Model to understand what ZKMix protects against and its limitations.