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

privacy-cash-sdk

v0.1.5

Published

Browser-native client SDK for the Privacy Cash Solana protocol (deposit/withdraw SOL & SPL tokens, ZK proofs, commitment notes).

Readme

privacy-cash-sdk

Browser-native client SDK for Privacy Cash — zero-knowledge private payments on Solana. Client-side Groth16 proofs, a Poseidon Merkle pool, gasless relayer withdrawals, and multi-asset (SOL + SPL) support. The wallet signs everything client-side; private keys never leave the device.

npm version License: MIT

Features

  • 🔒 100% client-side ZK — Groth16 proofs generated in the browser via snarkjs/WASM. No private data leaves the device.
  • 🌲 Poseidon Merkle pool — 26-level tree (~67M commitments), 100-root history, nullifier set, Groth16 over BN254.
  • 💸 Gasless withdrawals — relayer signs + submits on your behalf (default), so a recipient with 0 SOL can still be paid.
  • 🪙 SOL + SPL — native SOL and any SPL token (USDC, USDT, custom mints), each mint in its own tree.
  • 🔑 Self-custodial — the SDK holds no keys; the wallet derives the shielded spending key via a signature.
  • 🧩 Two submit modesrelay: true (gasless, default) or relay: false (you sign + pay gas, censorship-resistant).

Install

npm install privacy-cash-sdk
# or: bun add privacy-cash-sdk / yarn add privacy-cash-sdk

That's it — @solana/web3.js, @solana/spl-token, snarkjs, etc. come along automatically as dependencies. (If your app already uses @solana/web3.js, npm/bun will dedupe to a single copy — just keep the version compatible.)

You also need

  • The Privacy Cash backend running (the Rust privacy-cash-backend) — it indexes the chain (Yellowstone gRPC) and serves the read/relay endpoints (/api/merkle, /api/relayer/*, /api/utxos, …). The SDK talks only to this backend.
  • The Privacy Cash program deployed + the pool initialized on your network (devnet/mainnet/local).
  • Circuit files — the browser fetches /circuit/transaction2.{wasm,zkey} by default (ship them in your app's public/). In Node, preload them with setCircuitBuffers(wasm, zkey).

Quick start

import { PrivacyCashClient, setBackendUrl } from "privacy-cash-sdk"

// Point at your backend (or set VITE_BACKEND_URL and skip this).
setBackendUrl("https://your-backend.example.com")

// Build a Signer from any wallet (example: @solana/wallet-adapter-react).
// Signer = { publicKey, signMessage, signTransaction }
const client = new PrivacyCashClient(
  { publicKey, signMessage, signTransaction },
  // programId is optional — fetched from the backend if omitted
)

// Prompts the wallet to sign a message → derives your shielded spending key.
await client.unlock()

Deposit (shield funds)

const dep = await client.depositSOL(0.5 * 1_000_000_000) // lamports
console.log(dep.signature, dep.leafIndex)

// SPL:
// await client.depositSPL(mintAddress, amountRaw)

Withdraw / Transfer (spend a note)

// Withdraw to an address — defaults to GASLESS (relay: true):
const wd = await client.withdrawSOL(0.5e9, recipientPublicKey)
console.log(wd.recipientLamports, wd.feeLamports)

// Transfer = withdraw to someone ELSE's address (same call, default gasless):
await client.transferSOL(0.3e9, anotherAddress)

// Self-signed + you pay gas (censorship-resistant; you can broadcast via any RPC):
await client.withdrawSOL(0.5e9, recipient, { relay: false })

Send privately to a public address (deposit + gasless withdraw in one call)

// Deposits from your wallet, then the relayer gasless-withdraws to the recipient.
// Your deposit is unlinkable to the recipient via ZK.
await client.sendPrivateSOL(0.5e9, recipientAddress)

Scan + balance

await client.syncNotes("sol")                // decrypt your owned notes from the backend
const bal = client.getPrivateBalanceSOL()    // bigint, lamports
const usdcBal = client.getPrivateBalanceSPL(usdcMint)

Note storage & recovery

Notes (the off-chain material needed to spend a shielded UTXO, including each note's blinding secret) are never persisted to the browser. They live in two places only:

  • In memory — decrypted notes are held in a page-session cache while the wallet is unlocked, and wiped on reload.
  • On the backend, encrypted — after every change the SDK encrypts the whole note set with the wallet-signature-derived AES-256-GCM key and PUTs an opaque blob to the backend (encrypted_notes table). The server stores only ciphertext and has no key.

On unlock() the SDK seeds the in-memory cache from that blob (restoreNotes) and from a scan of the indexer's encrypted outputs (syncNotes). Use refreshNotes() to re-pull + rescan, and getNotes() to read the decrypted set. Import/clear go through importNotesJson / clearAllNotes, which also persist to the backend.

Recovery: if the backend blob is ever lost, syncNotes rebuilds the note set by decrypting the indexer's on-chain encrypted outputs — so a wallet can always recover its funds by re-scanning. No plaintext note material is ever written to localStorage.

relay modes

Every withdraw/transfer/send takes an optional { relay?: boolean } (default true).

| | relay: true (default) | relay: false | |---|---|---| | Signs the tx | the relayer | your wallet | | Pays gas | relayer (takes a fee from the pool) | you | | Needs SOL for gas | no | yes | | Censorship resistance | depends on relayer | yes — you can submit the signed tx via any RPC |

Use relay: true for smooth gasless UX (the common case). Use relay: false when you want maximum self-sovereignty / no trust in the relayer key.

Privacy is identical in both modes (your deposit is unlinked via ZK). relay only changes who signs + who pays gas. Both pay the recipient in public SOL/SPL at a visible address — a fully shielded transfer (ext_amount = 0, recipient gets a private note) is a separate, upcoming mode.

API

| method | description | |---|---| | new PrivacyCashClient(signer, programId?) | construct (signer = { publicKey, signMessage, signTransaction }) | | unlock() | wallet signs → derives shielded key + syncs notes | | depositSOL(lamports) / depositSPL(mint, raw) | shield funds into the pool | | withdrawSOL(lamports, recipient, opts?) / withdrawSPL(...) | spend a note to an address (default gasless) | | transferSOL(lamports, recipient, opts?) / transferSPL(...) | alias: spend a note to someone else's address | | sendPrivateSOL(lamports, recipient, opts?) / sendPrivateSPL(...) | deposit + gasless withdraw-to-recipient | | syncNotes(treeKey, fromScratch?) | scan + decrypt your notes from the backend | | refreshNotes() | unlock + re-scan + persist the in-memory note set | | getPrivateBalanceSOL() / getPrivateBalanceSPL(mint) | private balance (bigint, lamports/raw) | | getNotes() | decrypted notes held in memory for this session | | exportNotesString() / importNotesJson(json, replace?) | export / import notes (and persist to backend) | | clearAllNotes() | wipe notes — in-memory cache + encrypted backend blob | | backupNotes() / restoreNotes() | push / pull the encrypted note blob (the authoritative store) |

Module-level: setBackendUrl(url), getBackendUrl(), setCircuitBuffers(wasm, zkey).

Build & test

bun run build     # tsc → dist/
bun test          # proof unit tests

License

MIT