npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

veilo-sdk-core

v0.3.2

Published

TypeScript SDK for the Veilo Privacy Pool - UTXO-based privacy protocol on Solana with ZK-SNARKs

Readme

veilo-sdk-core

TypeScript SDK for the Veilo Privacy Pool Anchor program.

This package provides a complete UTXO-based privacy protocol implementation on Solana with:

  • UTXO Model: Full support for unspent transaction outputs with Poseidon commitments
  • Multi-Tree Support: Multiple concurrent Merkle trees for improved scalability
  • ZK Proofs: Integration with Circom circuits for private transactions
  • Private Swaps: Atomic cross-pool swaps via Jupiter (native SOL + SPL tokens)
  • Note Encryption: NaCl-based encrypted UTXO notes and blind mailbox delivery
  • Event Scanning: Reconstruct Merkle trees from on-chain events
  • Poseidon Hashing: BN254-curve compatible hashing using circomlibjs
  • Relayer Support: Built-in relayer infrastructure for private withdrawals and swaps

Status: Active development. The SDK supports full transaction privacy with ZK-SNARK proofs. Proofs are generated off-chain and verified on-chain using Groth16.


1. Installation

npm install veilo-sdk-core

2. Prerequisites

  • A running Solana validator (localnet/devnet/mainnet)
  • The privacy-pool program deployed to the network
  • A funded keypair
export ANCHOR_PROVIDER_URL=https://api.devnet.solana.com
export ANCHOR_WALLET=$HOME/.config/solana/id.json

3. Build

npm run build

4. SDK API

4.1 PDA Helpers

import {
  getPoolPdas,
  getNoteTreePda,
  getGlobalConfigPda,
  getNullifierMarkerPda,
  getSwapExecutorPda,
} from "veilo-sdk-core";
import { PublicKey } from "@solana/web3.js";

const programId = new PublicKey(
  "YourProgram1111111111111111111111111111111111",
);
const mintAddress = new PublicKey(
  "So11111111111111111111111111111111111111112",
);

// Pool PDAs (config, vault, nullifiers)
const { config, vault, nullifiers } = getPoolPdas(programId, mintAddress);

// Note tree PDA for tree ID 0
const noteTree = getNoteTreePda(programId, mintAddress, 0);

// Global config PDA (one per program)
const globalConfig = getGlobalConfigPda(programId);

// Nullifier marker PDA (prevents double-spend)
const nullifier = new Uint8Array(32);
const marker = getNullifierMarkerPda(programId, mintAddress, nullifier);

// Swap executor PDA (for cross-pool swaps)
const relayerPubkey = new PublicKey("...");
const executor = getSwapExecutorPda(
  programId,
  sourceMint,
  destMint,
  inputNullifier0, // Uint8Array[32]
  relayerPubkey,
);

PDA seeds (v3):

| Account | Seeds | | ---------------- | ------------------------------------------------------------------- | | Config | ["privacy_config_v3", mint] | | Vault | ["privacy_vault_v3", mint] | | Note Tree | ["privacy_note_tree_v3", mint, tree_id] | | Nullifiers | ["privacy_nullifiers_v3", mint] | | Nullifier Marker | ["nullifier_v3", mint, nullifier] | | Global Config | ["global_config_v1"] | | Swap Executor | ["swap_executor_v1", source_mint, dest_mint, nullifier0, relayer] |


4.2 Pool Initialization

import {
  initializeGlobalConfig,
  initializePool,
  updatePoolConfig,
  addMerkleTree,
  getPoolConfig,
  updateGlobalConfig,
} from "veilo-sdk-core";
import { NATIVE_SOL_MINT, sol } from "veilo-sdk-core/config";

// Initialize global config (once per program)
await initializeGlobalConfig({ program, admin: adminKeypair });

// Update global config
await updateGlobalConfig({
  program,
  admin: adminKeypair,
  newAdmin: newAdminPubkey, // optional
  paused: false, // optional
});

// Initialize a pool for native SOL
await initializePool({
  program,
  payer: adminKeypair,
  admin: adminKeypair,
  mintAddress: NATIVE_SOL_MINT,
  minDepositAmount: sol(0.1),
  maxDepositAmount: sol(100),
  minWithdrawAmount: sol(0.1),
  maxWithdrawAmount: sol(100),
  feeBps: 50, // 0.5%
  feeErrorMarginBps: 10, // 0.1% margin
  minWithdrawalFee: 1_000_000n,
});

// Add Merkle tree (tree ID 0)
await addMerkleTree({
  program,
  payer: adminKeypair,
  mintAddress: NATIVE_SOL_MINT,
  treeId: 0,
});

// Update pool fee
await updatePoolConfig({
  program,
  admin: adminKeypair,
  mintAddress: NATIVE_SOL_MINT,
  feeBps: 30,
});

// Read pool configuration
const poolConfig = await getPoolConfig(program, NATIVE_SOL_MINT);
console.log("TVL:", poolConfig.totalTvl, "  Fee:", poolConfig.feeBps, "bps");

4.3 On-Chain Account Queries

import {
  fetchPoolConfig,
  checkNullifierSpent,
  getTreeInfo,
  getAllTreeInfo,
  getBestTreeForDeposit,
} from "veilo-sdk-core";

// Fetch decoded PrivacyConfig account
const config = await fetchPoolConfig(program, NATIVE_SOL_MINT);

// Check if a nullifier has been spent
const spent = await checkNullifierSpent(program, NATIVE_SOL_MINT, nullifier);

// Info for a single tree
const info = await getTreeInfo(program, NATIVE_SOL_MINT, 0);
// info = { treeId, leafCount, root, isFull }

// Info for all trees in a pool
const allInfo = await getAllTreeInfo(program, NATIVE_SOL_MINT);

// Pick the best tree for depositing (least full with capacity)
const best = await getBestTreeForDeposit(program, NATIVE_SOL_MINT);
// best = { treeId, leafCount, root }

4.4 UTXO Management

import {
  generateKeypair,
  keypairFromPrivateKey,
  createUTXO,
  createOwnedUTXO,
  createOwnedZeroUTXO,
  deriveNullifier,
  type Keypair,
  type UTXO,
  type SerializedUTXO,
  type InputUTXO,
} from "veilo-sdk-core";
import { NATIVE_SOL_MINT } from "veilo-sdk-core/config";
import { pubkeyToField } from "veilo-sdk-core";

// Generate a random UTXO keypair
const keypair: Keypair = generateKeypair();
// keypair = { privateKey: bigint, publicKey: bigint }

// Restore from private key
const restored = keypairFromPrivateKey(privateKeyBigInt);

// Create an owned UTXO (includes private key)
const ownedUtxo: SerializedUTXO = createOwnedUTXO({
  amount: 1_000_000_000n,
  privateKey: keypair.privateKey,
  mintAddress: NATIVE_SOL_MINT,
});

// Create a zero-value UTXO (for unused inputs/outputs)
const zeroUtxo = createOwnedZeroUTXO(pubkeyToField(NATIVE_SOL_MINT), keypair);

// Derive nullifier for spending
const pathIndex = 0; // leaf index in Merkle tree
const treeId = 0;
const nullifier = deriveNullifier(
  ownedUtxo.privateKey,
  ownedUtxo.commitment,
  pathIndex,
  treeId,
);

UTXO commitment: Poseidon(amount, pubkey, blinding, mintAddress)

Nullifier: Poseidon(privateKey, commitment, pathIndex, treeId)


4.5 UTXO Encryption

The SDK provides NaCl-based encryption for UTXO notes (relayer storage) and blind mailbox delivery (wallet-to-wallet).

import {
  deriveEncryptionKeypair,
  encryptUTXONote,
  decryptUTXONote,
  encryptBlindMailboxNote,
  decryptBlindMailboxNote,
  fetchAndDecryptNotes,
  computeSignature,
  inputUTXOToCircuitFormat,
  type EncryptedNote,
  type BlindMailboxNote,
  type BlindMailboxNoteData,
  type DecryptedNote,
} from "veilo-sdk-core";
import { Keypair as SolanaKeypair } from "@solana/web3.js";

// Derive a NaCl encryption keypair from a UTXO private key
const encKeypair = deriveEncryptionKeypair(utxoKeypair.privateKey);
// encKeypair = { publicKey: Uint8Array[32], secretKey: Uint8Array[32] }

// Encrypt a UTXO note for relay storage
const encryptedNote: EncryptedNote = encryptUTXONote(
  serializedUtxo, // SerializedUTXO
  encKeypair.publicKey, // NaCl public key (Uint8Array[32])
);

// Decrypt a UTXO note
const decrypted: SerializedUTXO | null = decryptUTXONote(
  encryptedNote,
  encKeypair.secretKey, // NaCl secret key (Uint8Array[32])
);

// Blind mailbox: encrypt for a Solana wallet keypair
const recipientKeypair = SolanaKeypair.generate();

const mailboxNote: BlindMailboxNote = encryptBlindMailboxNote(
  serializedUtxo,
  recipientKeypair.publicKey.toBytes(), // ed25519 public key → X25519 DH
);

// Recipient decrypts using their Solana secret key (64-byte)
const mailboxDecrypted: DecryptedNote | null = decryptBlindMailboxNote(
  mailboxNote,
  recipientKeypair.secretKey, // 64-byte Solana secret key
);

// Compute UTXO signature (used in circuit)
const sig = computeSignature(utxoKeypair.privateKey, utxo.commitment);

// Format an InputUTXO for snarkjs circuit
const circuitFormat = inputUTXOToCircuitFormat(inputUtxo);

4.6 Merkle Tree Operations

import { MerkleTree } from "veilo-sdk-core";

// Create a new Merkle tree (default depth: 22)
const tree = new MerkleTree();

// Insert commitments
const index = tree.insert(ownedUtxo.commitment);

// Current root
const root = tree.root();

// Merkle path for proof generation
const path = tree.path(index);
// path = { pathElements: Uint8Array[], pathIndices: number[] }

// Total leaves inserted
const count = tree.totalLeaves;

4.7 Transaction Operations

All transaction functions return Promise<string> (the transaction signature).

Deposits

import { deposit, type DepositResult } from "veilo-sdk-core";

const result: DepositResult = await deposit({
  program,
  depositor: depositorKeypair,
  mintAddress: NATIVE_SOL_MINT,
  amount: 1_000_000_000n, // 1 SOL
  recipientPubkey: pubkeyToField(depositorKeypair.publicKey), // bigint
  tree,
  proofBuilder,
  treeId: 0,
});
// result = { outputUTXOs, leafIndices, root }

Withdrawals

import { withdraw, type WithdrawResult } from "veilo-sdk-core";

const result: WithdrawResult = await withdraw({
  program,
  relayer: relayerKeypair,
  mintAddress: NATIVE_SOL_MINT,
  amount: 900_000_000n, // 0.9 SOL
  fee: 100_000_000n, // 0.1 SOL to relayer
  inputs: [inputUtxo1, zeroInputUtxo],
  outputs: [changeUtxo, zeroOutputUtxo],
  recipient: recipientKeypair.publicKey,
  tree,
  proofBuilder,
  treeId: 0,
});

Private Transfers

import { privateTransfer, type TransferResult } from "veilo-sdk-core";

const result: TransferResult = await privateTransfer({
  program,
  relayer: relayerKeypair,
  mintAddress: NATIVE_SOL_MINT,
  inputs: [inputUtxo1, zeroInputUtxo],
  outputs: [recipientUtxo, changeUtxo],
  recipient: relayerKeypair.publicKey,
  tree,
  proofBuilder,
  treeId: 0,
});

Low-Level transact

import { transact } from "veilo-sdk-core";

const signature: string = await transact({
  program,
  relayer: relayerKeypair,
  mintAddress: NATIVE_SOL_MINT,
  inputTreeId: 0,
  outputTreeId: 0,
  root: tree.root(),
  publicAmount: 0n,
  inputNullifiers: [nullifier1, nullifier2],
  outputCommitments: [output1.commitment, output2.commitment],
  extData: { recipient, relayer: relayerPubkey, fee: 0n, refund: 0n },
  proof,
});

4.8 Private Swaps

Cross-pool private swaps via Jupiter/Raydium. For native SOL source pools the SDK automatically composes the required fund_native_source + transact_swap instructions into a single atomic transaction.

import {
  transactSwap,
  getSwapExecutorPda,
  fundNativeSource,
  type SwapParams,
  type SwapProofStruct,
} from "veilo-sdk-core";

// Execute a private swap (returns transaction signature)
const signature: string = await transactSwap({
  program,
  relayer: relayerKeypair,
  sourceMint, // NATIVE_SOL_MINT or SPL token mint
  destMint,
  sourceRoot: tree.root(),
  sourceTreeId: 0,
  destTreeId: 0,
  inputNullifiers: [nullifier0, nullifier1],
  outputCommitments: [changeCommitment, destCommitment],
  proof, // SwapProofStruct
  swapParams: {
    minAmountOut: 950_000_000n,
    deadline: BigInt(Math.floor(Date.now() / 1000) + 60),
    sourceMint,
    destMint,
    destAmount: 950_000_000n,
    swapDataHash: new Uint8Array(32), // SHA-256 of DEX ix bytes, or zeros
  },
  swapAmount: 1_000_000_000n,
  swapData: jupiterInstructionBytes, // Buffer
  extData: { recipient, relayer: relayerPubkey, fee, refund: 0n },
  sourceVaultTokenAccount,
  sourceMintAccount,
  destVaultTokenAccount,
  destMintAccount,
  relayerTokenAccount,
  swapProgram: JUPITER_PROGRAM_ID,
  jupiterEventAuthority,
});

// Build the fund_native_source instruction standalone (advanced)
const fundIx = await fundNativeSource({
  program,
  relayer: relayerKeypair,
  sourceMint: NATIVE_SOL_MINT,
  destMint,
  inputNullifier0: nullifier0,
  swapAmount: 1_000_000_000n,
});
// Returns TransactionInstruction — must be first ix in same tx as transact_swap

Note: transactSwap handles the atomicity requirement automatically. Only use fundNativeSource directly if you are building transactions manually.


4.9 Event Scanning & Tree Reconstruction

import {
  scanCommitmentEvents,
  scanNullifierEvents,
  buildTreeFromEvents,
  type CommitmentEvent,
  type NullifierSpentEvent,
} from "veilo-sdk-core";

// Scan all commitment events for a mint (paginated by signature)
const { events, latestSignature } = await scanCommitmentEvents({
  program,
  mintAddress: NATIVE_SOL_MINT,
  treeId: 0,
  beforeSignature: undefined, // or last known signature for pagination
  limit: 1000,
});
// events: CommitmentEvent[]
// CommitmentEvent = { commitment, leafIndex, newRoot, timestamp, mintAddress, treeId }

// Scan spent nullifier events
const { events: nullEvents } = await scanNullifierEvents({
  program,
  mintAddress: NATIVE_SOL_MINT,
  beforeSignature: undefined,
  limit: 1000,
});
// NullifierSpentEvent = { nullifier, mintAddress, treeId }

// Reconstruct a Merkle tree from on-chain events
const {
  tree,
  events: allEvents,
  latestSignature: sig,
} = await buildTreeFromEvents({
  program,
  mintAddress: NATIVE_SOL_MINT,
  treeId: 0,
});

4.10 Proof Generation

import {
  prepareTransactionInputs,
  formatInputsForSnarkjs,
  computeExtDataHash,
  encodeSnarkjsProofToTransactionProof,
  packProofToBytes,
  computeSwapParamsHash,
  computeSwapDataHash,
  type ExtData,
  type TransactionCircuitInputs,
} from "veilo-sdk-core";

// Prepare inputs for the transaction circuit
const circuitInputs: TransactionCircuitInputs = prepareTransactionInputs({
  root: tree.root(),
  publicAmount: 1_000_000_000n,
  extData: { recipient, relayer: relayerPubkey, fee: 0n, refund: 0n },
  mintAddress: NATIVE_SOL_MINT,
  inputs: [input1, input2],
  outputs: [output1, output2],
  inputTreeId: 0,
  outputTreeId: 0,
});

// Format for snarkjs (converts bigints / Uint8Arrays to strings)
const snarkjsInputs = formatInputsForSnarkjs(circuitInputs);

// After proof generation:
// const { proof } = await snarkjs.groth16.fullProve(snarkjsInputs, wasmPath, zkeyPath);
// const transactionProof = encodeSnarkjsProofToTransactionProof(proof);

// Compute ext data hash (matches on-chain computation)
const extDataHash = computeExtDataHash({
  recipient,
  relayer,
  fee: 0n,
  refund: 0n,
});

// Compute swap param/data hashes (for swap circuit inputs)
const swapParamsHash = computeSwapParamsHash(swapParams);
const swapDataHash = computeSwapDataHash(jupiterInstructionBytes);

4.11 Fee Utilities

import {
  computeWithdrawalFee,
  computeSwapFee,
  DEFAULT_FEE_BPS,
} from "veilo-sdk-core/config";

// Compute protocol fee for a withdrawal
const fee = computeWithdrawalFee(amount, feeBps, minWithdrawalFee);

// Compute protocol fee for a swap
const swapFee = computeSwapFee(swapAmount, feeBps);

4.12 Relayer & Admin Management

import { addRelayer, setPaused } from "veilo-sdk-core";

// Authorise a new relayer for a pool
await addRelayer({
  program,
  admin: adminKeypair,
  mintAddress: NATIVE_SOL_MINT,
  newRelayer: relayerPubkey,
});

// Pause or unpause a pool
await setPaused({
  program,
  admin: adminKeypair,
  mintAddress: NATIVE_SOL_MINT,
  paused: true,
});

4.13 Poseidon Utilities

import {
  initPoseidon,
  poseidon1,
  poseidon2,
  poseidon3,
  poseidon4,
  pubkeyToField,
  bytesToBigIntBE,
  bigIntToBytesBE,
  BN254_FR_MODULUS,
} from "veilo-sdk-core";

// Must be called once before using hash functions
await initPoseidon();

const h1 = poseidon1(12345n);
const h2 = poseidon2(12345n, 67890n);
const h3 = poseidon3(12345n, 67890n, 11111n);
const h4 = poseidon4(12345n, 67890n, 11111n, 22222n);

// Convert a Solana PublicKey to a BN254 field element
const field = pubkeyToField(NATIVE_SOL_MINT);

// Byte ↔ bigint helpers (big-endian)
const n = bytesToBigIntBE(bytes32);
const b = bigIntToBytesBE(someField, 32);

4.14 Error Utilities

import { parseOnChainError } from "veilo-sdk-core";

try {
  await transactSwap({ ... });
} catch (err) {
  // Returns a human-readable error string from Anchor/program errors
  const msg = parseOnChainError(err);
  console.error("Swap failed:", msg);
}

5. Architecture

Transaction Model

Veilo uses a UTXO privacy model inspired by Zcash and Tornado Cash Nova:

  • Inputs: 2 UTXOs (zero-value for deposits)
  • Outputs: 2 UTXOs (zero-value for withdrawals)
  • Public Amount: net pool change — positive = deposit, negative = withdrawal, zero = private transfer or swap

Each transaction consumes 2 input UTXOs (Merkle proofs), creates 2 output UTXOs (commitments inserted to tree), and generates 2 nullifiers to prevent double-spend.

Swap Architecture

Private swaps require two instructions in a single atomic transaction:

  1. fund_native_source — pre-funds the swap executor with SOL from the vault (native SOL pools only)
  2. transact_swap — verifies the ZK proof, spends input UTXOs, creates output UTXOs, and executes the DEX swap

transactSwap() handles this automatically. The on-chain program validates atomicity via the instructions sysvar.

Privacy Guarantees

  • Commitment hiding: amount, owner, blinding are hidden via Poseidon
  • Nullifier uniqueness: each UTXO can only be spent once
  • Unlinkability: no public link between inputs and outputs
  • ZK proofs: Groth16 verified on-chain

Constants

import {
  NATIVE_SOL_MINT, // PublicKey.default — native SOL pools
  MERKLE_TREE_DEPTH, // 22
  ROOT_HISTORY_SIZE, // 256
  DEFAULT_FEE_BPS, // 50 (0.5%)
  sol, // sol(1) === 1_000_000_000n
} from "veilo-sdk-core/config";

Type Exports

import type {
  // UTXO
  Keypair,
  UTXO,
  SerializedUTXO,
  InputUTXO,
  // Encryption
  EncryptedNote,
  BlindMailboxNote,
  BlindMailboxNoteData,
  DecryptedNote,
  // Proof
  ExtData,
  TransactionCircuitInputs,
  TransactionProofStruct,
  RawProof,
  TransactionProofBuilder,
  // Swap
  SwapProofStruct,
  SwapParams,
  // Tree / events
  MerklePath,
  CircuitMerklePath,
  TreeInfo,
  CommitmentEvent,
  NullifierSpentEvent,
  // Results
  DepositResult,
  WithdrawResult,
  TransferResult,
  // Config
  PoolInitConfig,
  PrivacyConfigAccount,
  GlobalConfigAccount,
} from "veilo-sdk-core";

6. Development

# Run tests (requires devnet or local validator)
npm test

7. Resources

  • Repository: https://github.com/VeiloSolana/veilo-sdk
  • Circomlibjs: https://github.com/iden3/circomlibjs
  • Poseidon Hash: https://www.poseidon-hash.info/

License

ISC

This package provides a complete UTXO-based privacy protocol implementation on Solana with:

  • UTXO Model: Full support for unspent transaction outputs with Poseidon commitments
  • Multi-Tree Support: Multiple concurrent Merkle trees for improved scalability
  • ZK Proofs: Integration with Circom circuits for private transactions
  • Flexible Operations: Deposits, withdrawals, and private transfers
  • Poseidon Hashing: BN254-curve compatible hashing using circomlibjs
  • Merkle Trees: Off-chain Merkle tree management with proof generation
  • Relayer Support: Built-in relayer infrastructure for private withdrawals

Status: Active development. The SDK supports full transaction privacy with ZK-SNARK proofs.
Proofs are generated off-chain and verified on-chain using Groth16.


1. Installation

npm install veilo-sdk-core

Or from source:

cd core-sdk
npm install

2. Prerequisites

You need:

  • A running Solana validator (localnet/devnet/mainnet):

    solana-test-validator
  • The privacy-pool program deployed to the network

  • The privacy-pool Anchor IDL available:

    idl/idl/privacy_pool.json
  • A funded keypair:

    solana config set --url http://127.0.0.1:8899
    solana-keygen new --outfile ~/.config/solana/id.json
    solana airdrop 10

Environment variables:

export ANCHOR_PROVIDER_URL=http://127.0.0.1:8899
export ANCHOR_WALLET=$HOME/.config/solana/id.json

3. Build

npm run build

4. SDK API

4.1 PDA Helpers

import {
  getPoolPdas,
  getNoteTreePda,
  getGlobalConfigPda,
  getNullifierMarkerPda,
} from "veilo-sdk-core";
import { PublicKey } from "@solana/web3.js";

const programId = new PublicKey(
  "YourProgram1111111111111111111111111111111111",
);
const mintAddress = new PublicKey(
  "So11111111111111111111111111111111111111112",
); // Native SOL

// Get pool PDAs
const { config, vault, nullifiers } = getPoolPdas(programId, mintAddress);

// Get note tree PDA for tree ID 0
const noteTree = getNoteTreePda(programId, mintAddress, 0);

// Get global config
const globalConfig = getGlobalConfigPda(programId);

// Get nullifier marker PDA
const nullifier = new Uint8Array(32);
const marker = getNullifierMarkerPda(programId, mintAddress, 0, nullifier);

PDA seeds (v3):

  • Config: ["privacy_config_v3", mint_address]
  • Vault: ["privacy_vault_v3", mint_address]
  • Note Tree: ["privacy_note_tree_v3", mint_address, tree_id]
  • Nullifiers: ["privacy_nullifiers_v3", mint_address]
  • Nullifier Marker: ["privacy_nullifier_v3", mint_address, tree_id, nullifier]
  • Global Config: ["global_config_v1"]

4.2 Pool Initialization

import * as anchor from "@coral-xyz/anchor";
import {
  initializeGlobalConfig,
  initializePool,
  updatePoolConfig,
  addMerkleTree,
  getPoolConfig,
} from "veilo-sdk-core";
import { NATIVE_SOL_MINT, sol } from "veilo-sdk-core/config";

// Initialize global config (once per program)
await initializeGlobalConfig({
  program,
  admin: adminKeypair,
});

// Initialize a pool for native SOL
await initializePool({
  program,
  payer: adminKeypair,
  admin: adminKeypair,
  mintAddress: NATIVE_SOL_MINT,
  minDepositAmount: sol(0.1), // 0.1 SOL
  maxDepositAmount: sol(100), // 100 SOL
  minWithdrawAmount: sol(0.1),
  maxWithdrawAmount: sol(100),
  feeBps: 50, // 0.5%
  feeErrorMarginBps: 10, // 0.1% margin
  minWithdrawalFee: 1_000_000n, // 0.001 SOL minimum
});

// Add first Merkle tree (tree ID 0)
await addMerkleTree({
  program,
  payer: adminKeypair,
  mintAddress: NATIVE_SOL_MINT,
  treeId: 0,
});

// Update pool configuration
await updatePoolConfig({
  program,
  admin: adminKeypair,
  mintAddress: NATIVE_SOL_MINT,
  feeBps: 30, // Change to 0.3%
});

// Read pool configuration
const poolConfig = await getPoolConfig(program, NATIVE_SOL_MINT);
console.log("TVL:", poolConfig.totalTvl);
console.log("Fee:", poolConfig.feeBps, "bps");
console.log("Num Trees:", poolConfig.numTrees);

4.3 UTXO Management

The SDK uses a UTXO (Unspent Transaction Output) model with Poseidon commitments:

import {
  generateKeypair,
  keypairFromPrivateKey,
  createUTXO,
  createOwnedUTXO,
  createOwnedZeroUTXO,
  deriveNullifier,
  type Keypair,
  type UTXO,
  type SerializedUTXO,
  type InputUTXO,
} from "veilo-sdk-core";
import { NATIVE_SOL_MINT } from "veilo-sdk-core/config";
import { pubkeyToField } from "veilo-sdk-core";

// Generate a random keypair
const keypair: Keypair = generateKeypair();
// keypair = { privateKey: bigint, publicKey: bigint }

// Restore keypair from private key
const restored = keypairFromPrivateKey(privateKeyBigInt);

// Create an owned UTXO (includes private key)
const ownedUtxo: SerializedUTXO = createOwnedUTXO({
  amount: 1_000_000_000n,
  privateKey: keypair.privateKey,
  mintAddress: NATIVE_SOL_MINT,
});

// Create a zero UTXO (for unused inputs/outputs)
const zeroUtxo = createOwnedZeroUTXO(pubkeyToField(NATIVE_SOL_MINT), keypair);

// Derive nullifier for spending
const nullifier = deriveNullifier(
  ownedUtxo.privateKey,
  ownedUtxo.commitment,
  0, // pathIndex in Merkle tree
  0, // treeId
);

UTXO commitment formula:

commitment = Poseidon(amount, pubkey, blinding, mintAddress)

Nullifier formula:

nullifier = Poseidon(privateKey, commitment, pathIndex, treeId)

4.4 Merkle Tree Operations

import { MerkleTree } from "veilo-sdk-core";

// Create a new Merkle tree (default depth: 20)
const tree = new MerkleTree();

// Insert commitments
const index1 = tree.insert(ownedUtxo1.commitment);
const index2 = tree.insert(ownedUtxo2.commitment);

// Get current root
const root = tree.root();

// Get Merkle path for proof generation
const path = tree.path(index1);
// path = { pathElements: Uint8Array[], pathIndices: number[] }

// Get number of leaves
const numLeaves = tree.totalLeaves;

// Custom tree depth
const deepTree = new MerkleTree(25); // 25 levels

The Merkle tree uses Poseidon hash for all internal nodes.


4.5 Transaction Operations

The SDK supports three types of transactions:

Deposits (publicAmount > 0)

import { deposit } from "veilo-sdk-core";

// Create output UTXO
const outputUtxo = createOwnedUTXO({
  amount: 1_000_000_000n, // 1 SOL
  mintAddress: pubkeyToField(NATIVE_SOL_MINT),
  keypair: utxoKeypair,
});

// Zero inputs for deposit
const input1 = createOwnedZeroUTXO(pubkeyToField(NATIVE_SOL_MINT), utxoKeypair);
const input2 = createOwnedZeroUTXO(pubkeyToField(NATIVE_SOL_MINT), utxoKeypair);
const output2 = createOwnedZeroUTXO(
  pubkeyToField(NATIVE_SOL_MINT),
  utxoKeypair,
);

await deposit({
  program,
  depositor: depositorKeypair,
  mintAddress: NATIVE_SOL_MINT,
  inputTreeId: 0,
  outputTreeId: 0,
  root: tree.root(),
  publicAmount: 1_000_000_000n, // Positive = deposit
  inputs: [input1, input2],
  outputs: [outputUtxo, output2],
  recipient: depositorKeypair.publicKey,
  fee: 0n,
  refund: 0n,
  proof: mockProof,
});

// Insert outputs into tree
tree.insert(outputUtxo.commitment);
tree.insert(output2.commitment);

Withdrawals (publicAmount < 0)

import { withdraw } from "veilo-sdk-core";

// Prepare input with Merkle path
const input1: InputUTXO = {
  ...ownedUtxo1,
  pathIndex: 0,
  pathElements: tree.path(0).pathElements,
};

const zeroInput2: InputUTXO = {
  ...createOwnedZeroUTXO(pubkeyToField(NATIVE_SOL_MINT), utxoKeypair),
  pathIndex: 0,
  pathElements: tree.path(0).pathElements,
};

// Zero outputs
const output1 = createOwnedZeroUTXO(
  pubkeyToField(NATIVE_SOL_MINT),
  utxoKeypair,
);
const output2 = createOwnedZeroUTXO(
  pubkeyToField(NATIVE_SOL_MINT),
  utxoKeypair,
);

await withdraw({
  program,
  relayer: relayerKeypair,
  mintAddress: NATIVE_SOL_MINT,
  inputTreeId: 0,
  outputTreeId: 0,
  root: tree.root(),
  publicAmount: -900_000_000n, // Negative = withdrawal (0.9 SOL)
  inputs: [input1, zeroInput2],
  outputs: [output1, output2],
  recipient: recipientKeypair.publicKey,
  fee: 100_000_000n, // 0.1 SOL to relayer
  refund: 0n,
  proof,
});

Private Transfers (publicAmount = 0)

import {
  privateTransfer,
  generateKeypair,
  createOwnedUTXO,
} from "veilo-sdk-core";

// Create new outputs for recipient
const recipientKeypair = generateKeypair();
const output1 = createOwnedUTXO({
  amount: 1_000_000_000n,
  privateKey: recipientKeypair.privateKey,
  mintAddress: NATIVE_SOL_MINT,
});

await privateTransfer({
  program,
  relayer: relayerKeypair,
  mintAddress: NATIVE_SOL_MINT,
  inputTreeId: 0,
  outputTreeId: 0,
  root: tree.root(),
  publicAmount: 0n, // Zero = private transfer
  inputs: [input1, zeroInput2],
  outputs: [output1, output2],
  recipient: relayerKeypair.publicKey,
  fee: 0n,
  refund: 0n,
  proof,
});

// Insert new outputs
tree.insert(output1.commitment);
tree.insert(output2.commitment);

Low-Level Transaction Function

For advanced use cases, use the unified transact function directly:

import { transact } from "veilo-sdk-core";

await transact({
  program,
  relayer: relayerKeypair,
  mintAddress: NATIVE_SOL_MINT,
  inputTreeId: 0,
  outputTreeId: 0,
  root: tree.root(),
  publicAmount: 0n,
  inputNullifiers: [nullifier1, nullifier2],
  outputCommitments: [output1.commitment, output2.commitment],
  extData: {
    recipient: recipientPubkey,
    relayer: relayerPubkey,
    fee: 0n,
    refund: 0n,
  },
  proof,
});

4.6 Proof Generation

The SDK provides utilities for preparing circuit inputs:

import {
  prepareTransactionInputs,
  formatInputsForSnarkjs,
  computeExtDataHash,
  encodeSnarkjsProofToTransactionProof,
  packProofToBytes,
  type ExtData,
  type TransactionCircuitInputs,
} from "veilo-sdk-core";

// Prepare inputs for the circuit
const circuitInputs: TransactionCircuitInputs = prepareTransactionInputs({
  root: tree.root(),
  publicAmount: 1_000_000_000n,
  extData: {
    recipient: recipientPubkey,
    relayer: relayerPubkey,
    fee: 0n,
    refund: 0n,
  },
  mintAddress: NATIVE_SOL_MINT,
  inputs: [input1, input2],
  outputs: [output1, output2],
  inputTreeId: 0,
  outputTreeId: 0,
});

// Format for snarkjs (converts Uint8Array to string representations)
const snarkjsInputs = formatInputsForSnarkjs(circuitInputs);

// Use with snarkjs to generate proof
// const { proof, publicSignals } = await snarkjs.groth16.fullProve(
//   snarkjsInputs,
//   wasmPath,
//   zkeyPath
// );

// Convert snarkjs proof to on-chain format
// const transactionProof = encodeSnarkjsProofToTransactionProof(proof);

4.7 Relayer Management

import { addRelayer, setPaused } from "veilo-sdk-core";

// Add a relayer
await addRelayer({
  program,
  admin: adminKeypair,
  mintAddress: NATIVE_SOL_MINT,
  newRelayer: relayerPubkey,
});

// Pause/unpause the pool
await setPaused({
  program,
  admin: adminKeypair,
  mintAddress: NATIVE_SOL_MINT,
  paused: true,
});

4.8 Poseidon Utilities

import {
  initPoseidon,
  poseidon1,
  poseidon2,
  poseidon3,
  poseidon4,
  pubkeyToField,
} from "veilo-sdk-core";

// Initialize Poseidon (required once before using hash functions)
await initPoseidon();

// Hash functions
const hash1 = poseidon1(12345n);
const hash2 = poseidon2(12345n, 67890n);
const hash3 = poseidon3(12345n, 67890n, 11111n);
const hash4 = poseidon4(12345n, 67890n, 11111n, 22222n);

// Convert Solana PublicKey to field element
const fieldElement = pubkeyToField(NATIVE_SOL_MINT);

5. Architecture

Transaction Model

Veilo uses a UTXO-based privacy model inspired by Zcash and Tornado Cash Nova:

  1. Inputs: 2 UTXOs (can be zero for deposits)
  2. Outputs: 2 UTXOs (can be zero for withdrawals)
  3. Public Amount: Net change (positive = deposit, negative = withdrawal, zero = private transfer)

Each transaction:

  • Consumes 2 input UTXOs (proven via Merkle paths)
  • Creates 2 output UTXOs (commitments added to tree)
  • Generates 2 nullifiers (prevents double-spending)
  • Optionally transfers funds in/out of the pool

Privacy Guarantees

  • Commitment hiding: Amount, owner, and blinding factor are hidden via Poseidon hash
  • Nullifier uniqueness: Each UTXO can only be spent once
  • Unlinkability: No public link between inputs and outputs
  • Zero-knowledge proofs: Transactions proven valid without revealing private data

Multi-Tree Support

The protocol supports multiple concurrent Merkle trees per pool:

  • Improves scalability by reducing tree depth
  • Allows parallel insertions
  • Each tree has independent state

Constants

import {
  NATIVE_SOL_MINT, // PublicKey.default (all zeros) for native SOL
  MERKLE_TREE_DEPTH, // 22 levels
  ROOT_HISTORY_SIZE, // 256 historical roots
  DEFAULT_FEE_BPS, // 50 (0.5%)
  sol, // Helper: sol(1) = 1_000_000_000n lamports
} from "veilo-sdk-core/config";

import { BN254_FR_MODULUS } from "veilo-sdk-core";

Type Exports

The SDK exports the following types for TypeScript users:

import type {
  // UTXO types
  Keypair,
  UTXO,
  SerializedUTXO,
  InputUTXO,

  // Proof types
  ExtData,
  TransactionCircuitInputs,
  TransactionProofStruct,
  RawProof,
  TransactionProofBuilder,

  // Merkle types
  MerklePath,
  CircuitMerklePath,

  // Config types
  PoolInitConfig,
  PrivacyConfigAccount,
  GlobalConfigAccount,
} from "veilo-sdk-core";

SPL Token Support

The SDK supports both native SOL and SPL tokens:

import { PublicKey } from "@solana/web3.js";

// For native SOL
const solMint = NATIVE_SOL_MINT; // PublicKey.default

// For SPL tokens
const usdcMint = new PublicKey("EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v");

// Initialize pool for SPL token
await initializePool({
  program,
  payer: adminKeypair,
  admin: adminKeypair,
  mintAddress: usdcMint, // Use SPL token mint
  minDepositAmount: 1_000_000n, // 1 USDC (6 decimals)
  // ... other params
});

When using SPL tokens, the SDK automatically handles associated token accounts.


6. Development

Environment Variables

export ANCHOR_PROVIDER_URL=http://127.0.0.1:8899
export ANCHOR_WALLET=$HOME/.config/solana/id.json

7. Limitations

  • Off-chain tree management: Merkle trees must be maintained by relayers/clients
  • Proof generation not included: You must integrate your own circuit/prover
  • Development status: Active development, APIs may change

8. Resources

  • Repository: https://github.com/VeiloSolana/veilo-sdk
  • Circomlibjs: https://github.com/iden3/circomlibjs
  • Poseidon Hash: https://www.poseidon-hash.info/

License

ISC