Docs/Sdk/Quickstart

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:

  1. The SDK installed: npm install @zkmix/sdk @solana/web3.js @coral-xyz/anchor
  2. A Solana wallet with funds (devnet SOL for testing)
  3. 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.

typescript
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:

  1. Generating a cryptographically random secret and nullifier
  2. Computing the Poseidon commitment hash
  3. Building the deposit transaction
  4. Signing and submitting the transaction
  5. Waiting for confirmation
  6. Returning a DepositNote containing 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:

typescript
// 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.

typescript
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:

  1. Fetching the current Merkle tree state from on-chain
  2. Computing the Merkle proof path for your deposit's leaf
  3. Generating the Groth16 zero-knowledge proof (this takes a few seconds)
  4. Selecting a relayer and submitting the proof via the relayer API
  5. Waiting for on-chain confirmation
  6. 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:

typescript
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:

typescript
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:

typescript
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:

typescript
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.