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

Published

Tiny TypeScript SDK for the `privacy-pool` Anchor program.

Readme

@zkprivacysol/sdk-core

Tiny TypeScript SDK for the privacy-pool Anchor program.

This package wraps the on-chain program with a small set of ergonomic helpers for:

  • Deriving PDAs (config, vault, note_tree, nullifiers)
  • Initializing the pool with fixed SOL denominations
  • Depositing using a note commitment + off-chain Merkle root
  • Withdrawing via an authorized relayer (with fee + TVL accounting)
  • Building simple Merkle roots off-chain (for demo / testing)
  • Handling note commitments (createRandomNote, commitNote, etc.)

Status: internal/dev SDK. No production guarantees.
ZK verification is expected to happen off-chain in a relayer service.
On-chain, proof: Vec<u8> is treated as opaque bytes (hook for a future verifier).


1. Install

From the monorepo root (or inside the package folder):

cd packages/sdk-core
npm install

If you publish it somewhere later:

npm install @zkprivacysol/sdk-core

2. Prerequisites

You need:

  • A running Solana validator (localnet recommended):

    solana-test-validator
  • The privacy-pool program built and deployed to that validator.

  • The privacy-pool Anchor IDL available (the SDK tests load it from):

    ../../privacy-pool/target/idl/privacy_pool.json
  • A funded keypair on that validator:

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

Environment variables for tests:

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

3. Build & Test

From packages/sdk-core:

# Typecheck & build to dist/
npm run build

# Run unit + integration tests
npm test

What the tests do:

  • Unit tests (note.test.ts)

    • createRandomNote / encodeNoteToBytes / commitNote / createNoteWithCommitment
    • Ensures 32-byte commitments, deterministic encoding, etc.
  • Integration test (sdk.integration.test.ts)

    • Loads the privacy-pool IDL from privacy-pool/target/idl/privacy_pool.json
    • Constructs an Anchor Program with a provider from ANCHOR_PROVIDER_URL / ANCHOR_WALLET
    • Runs end-to-end flow:
      1. initializePool (configures denoms + fee)
      2. createNoteAndDeposit (creates note, commits it, calls on-chain depositFixed and updates the Merkle root)
      3. addRelayer
      4. withdrawViaRelayer (using a Merkle root that actually contains the note + a demo nullifier, empty proof)
      5. Asserts vault TVL decreased, recipient gained funds, relayer received fee.

The integration test uses an empty proof for now; in a real deployment your relayer would generate and verify a Groth16/Plonk proof off-chain, then pass the proof bytes into withdraw.


4. SDK Surface

4.1 PDA helpers

import { getPoolPdas } from "@zkprivacysol/sdk-core";
import { PublicKey } from "@solana/web3.js";

const programId = new PublicKey("YourProgram1111111111111111111111111111111111");
const { config, vault, noteTree, nullifiers } = getPoolPdas(programId);

These must match the on-chain seeds (v3 layout):

  • ["privacy_config_v3"]
  • ["privacy_vault_v3"]
  • ["privacy_note_tree_v3"]
  • ["privacy_nullifiers_v3"]

4.2 Pool init / configuration

import * as anchor from "@coral-xyz/anchor";
import { initializePool } from "@zkprivacysol/sdk-core";
import { sol } from "@zkprivacysol/sdk-core/config";
import type { Program, Idl } from "@coral-xyz/anchor";

async function initPool(program: Program<Idl>, adminWallet: anchor.Wallet) {
  await initializePool({
    program,
    admin: adminWallet,
    denomsLamports: [sol(1), sol(5)], // 1 SOL & 5 SOL
    feeBps: 50,                       // 0.5% fee
  });
}

This calls the on-chain initialize instruction and sets:

  • fixed denominations (in lamports)
  • vault + note tree + nullifier set PDAs
  • fee in basis points
  • initial TVL = 0

4.3 Notes & commitments

src/note.ts is a small “note” helper module. It does not implement real zk-friendly Poseidon hashing yet; it’s just using SHA-256 to get 32-byte commitments that the on-chain program treats as opaque.

import {
  createRandomNote,
  encodeNoteToBytes,
  commitNote,
  createNoteWithCommitment,
} from "@zkprivacysol/sdk-core/note";
import { Keypair } from "@solana/web3.js";

const owner = Keypair.generate().publicKey;

// 1. Create a note
const note = createRandomNote({
  value: 1_000_000n, // lamports
  owner,
});

// 2. Encode deterministically
const bytes = encodeNoteToBytes(note);

// 3. Hash to a 32-byte commitment (placeholder)
const commitment = commitNote(note);

// 4. Convenience combo
const full = createNoteWithCommitment({
  value: 1_000_000n,
  owner,
});
// full.commitment is 32 bytes

Encoding layout:

value (u64 LE, 8 bytes)
|| owner pubkey (32 bytes)
|| rho (32 bytes random)
|| r   (32 bytes random)

Hash:

commitment = sha256(encodedBytes);

Later, a real implementation should swap this out for the exact hash function used inside the zk circuit (Poseidon/Rescue/etc.). The on-chain program just sees [u8; 32].


4.4 Merkle helpers (demo-only)

src/merkle.ts provides very basic Merkle helpers so callers can build roots off-chain. This is meant for demos/tests, not production.

Key functions:

import {
  merkleLeafFromCommitment,
  merkleHashPair,
  merkleRootFromLeaves,
  MerkleTree,
} from "@zkprivacysol/sdk-core/merkle";

// Stateless helpers
const leaf = merkleLeafFromCommitment(commitment);
const parent = merkleHashPair(left, right);
const root = merkleRootFromLeaves([leaf1, leaf2, leaf3]);

// Simple incremental tree (toy)
const tree = new MerkleTree();
const { index, root: newRoot } = tree.insert(commitment);
const path = tree.getPath(index); // Merkle path for proofs

Notes:

  • Uses SHA-256 under the hood, returning Uint8Array of length 32.
  • Pads with a “zero node” derived from hashing the all-zero leaf repeatedly up the tree.
  • This is intentionally “toy” to keep the SDK usable while the real circuit/Merkle design is still in flux.
  • On-chain, the program only stores the latest Merkle root in the NoteTree account (v3 layout exposes a currentRoot field).

4.5 Deposits

Low-level helper (you supply both commitment + Merkle root):

import * as anchor from "@coral-xyz/anchor";
import { depositFixedSol } from "@zkprivacysol/sdk-core";

const provider = anchor.getProvider() as anchor.AnchorProvider;
const wallet = provider.wallet as anchor.Wallet;

await depositFixedSol({
  program,
  depositor: wallet,
  denomIndex: 0,        // index into cfg.denoms
  commitment,           // 32-byte note commitment
  newRoot,              // 32-byte Merkle root (caller computed off-chain)
});

High-level helper (with note creation, but you still feed a root):

import * as anchor from "@coral-xyz/anchor";
import { createNoteAndDeposit } from "@zkprivacysol/sdk-core";
import { sol } from "@zkprivacysol/sdk-core/config";

const provider = anchor.getProvider() as anchor.AnchorProvider;
const wallet = provider.wallet as anchor.Wallet;

const dummyRoot = new Uint8Array(32).fill(7); // replace with real Merkle root

const note = await createNoteAndDeposit({
  program,
  depositor: wallet,
  denomIndex: 0,
  valueLamports: sol(1),
  newRoot: dummyRoot,
});

// note.commitment can later be used in your off-chain tree

There’s also a higher-level helper that integrates directly with an in-memory MerkleTree:

import { createNoteDepositWithMerkle } from "@zkprivacysol/sdk-core";
import { MerkleTree } from "@zkprivacysol/sdk-core/merkle";

const tree = new MerkleTree();

const { note, leafIndex, root, merklePath } =
  await createNoteDepositWithMerkle({
    program,
    depositor: wallet,
    denomIndex: 0,
    valueLamports: sol(1),
    tree,
  });

// `root` is what got written to the on-chain NoteTree
// `merklePath` can be used as witness for the zk circuit

On-chain, the depositFixed instruction:

  • moves SOL from depositor to the vault PDA,
  • updates TVL,
  • writes new_root into the on-chain note tree’s current root field.

4.6 Relayers & Withdrawals

Add a relayer (admin-only):

import * as anchor from "@coral-xyz/anchor";
import { addRelayer } from "@zkprivacysol/sdk-core";
import { Keypair } from "@solana/web3.js";

const provider = anchor.getProvider() as anchor.AnchorProvider;
const wallet = provider.wallet as anchor.Wallet;

const relayer = Keypair.generate();

await addRelayer({
  program,
  admin: wallet,
  newRelayer: relayer.publicKey,
});

Withdraw via relayer (SDK-level helper):

import { withdrawViaRelayer } from "@zkprivacysol/sdk-core";
import { Keypair } from "@solana/web3.js";

const relayer = Keypair.generate();
const recipient = Keypair.generate();

const root = /* 32-byte Merkle root containing the note */;
const nullifier = new Uint8Array(32).fill(3); // demo only

// In the real world, `proof` will be zk-proof bytes coming from your prover.
const proofBytes = new Uint8Array([]); // currently ignored by on-chain program

await withdrawViaRelayer({
  program,
  relayer,
  recipient: recipient.publicKey,
  denomIndex: 0,
  root,
  nullifier,
  proof: proofBytes,
});

There is also a higher-level helper (withdrawViaRelayerWithProof) that takes:

  • noteData (serialized note),
  • merklePath,
  • feeBps,
  • a builder: ProofBuilder callback

and lets you plug in your own proof generator. In practice, your relayer service will own that logic.

Production pattern:

  • A backend relayer service (see packages/relayer or similar) owns:
    • the proving key / circuits,
    • a mirror view of the Merkle tree and nullifier set,
    • a funded relayer keypair.
  • The front-end sends a withdraw request to that service:
    • root, nullifier, denomIndex, recipient, plus any private witness data.
  • The relayer:
    1. Builds & verifies the zk proof off-chain.
    2. Packs it into bytes (e.g. via a packProofToBytes helper).
    3. Calls the on-chain withdraw via Anchor, using the same program/PDAs as the SDK.

From the SDK’s perspective, proof: Uint8Array is already-built; this package doesn’t know how you generated it.


5. Environment & Localnet

To run the integration tests successfully, you should:

  1. Start local validator:

    solana-test-validator
  2. Build & deploy the privacy-pool Anchor program in packages/privacy-pool:

    cd packages/privacy-pool
    anchor build
    anchor deploy
  3. Ensure your CLI and wallet match the validator:

    solana config set --url http://127.0.0.1:8899
    solana-keygen new --outfile ~/.config/solana/id.json
    solana airdrop 10
  4. Export env vars (or inject via npm test script):

    export ANCHOR_PROVIDER_URL=http://127.0.0.1:8899
    export ANCHOR_WALLET=$HOME/.config/solana/id.json
  5. Then from packages/sdk-core:

    npm run build
    npm test

6. Limitations & TODOs

  • Merkle tree is minimal.
    • Toy implementation, primarily for demos/tests.
    • No persisted tree; you’re expected to maintain state in your own service.
  • On-chain NoteTree only stores the latest root.
    • Historical roots/nullifiers must be mirrored off-chain.
  • Proofs are relayer-only.
    • SDK does not generate Groth16/Plonk proofs.
    • On-chain program currently only sees Vec<u8> and does not verify it yet.
  • API is still evolving.
    • Types, exports, and function signatures may change as the circuit + relayer design solidifies.

This SDK is meant as a thin, hackable layer around the Anchor program while the core privacy design (circuit, proof system, Merkle layout, relayer flow) is being explored.