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 🙏

© 2025 – Pkg Stats / Ryan Hefner

@cloaklabz/sdk

v0.1.4

Published

TypeScript SDK for Cloak Protocol - Private transactions on Solana

Readme

Cloak SDK

TypeScript SDK for the Cloak Protocol - Private transactions on Solana using zero-knowledge proofs.

Features

  • 🔒 Private Transfers: Send SOL privately using zero-knowledge proofs
  • 👥 Multi-Recipient: Support for 1-5 recipients in a single transaction
  • 🔐 Type-Safe: Full TypeScript support with comprehensive types
  • 🌐 Cross-Platform: Works in browser and Node.js
  • 📦 Zero Dependencies: Minimal external dependencies (only Solana + crypto)
  • Simple API: Easy-to-use high-level client

Installation

npm install @cloaklabz/sdk @solana/web3.js
# or
yarn add @cloaklabz/sdk @solana/web3.js
# or
pnpm add @cloaklabz/sdk @solana/web3.js

Note: For swap functionality, you'll also need @solana/spl-token:

npm install @solana/spl-token

Quick Start

import { CloakSDK, generateNote } from "@cloaklabz/sdk";
import { Connection, Keypair } from "@solana/web3.js";

// Initialize connection and keypair
const connection = new Connection("https://api.devnet.solana.com");
const keypair = Keypair.fromSecretKey(/* your secret key */);

// Initialize client - only need network and keypair!
const client = new CloakSDK({
  keypairBytes: keypair.secretKey,
  network: "devnet", // Optional, defaults to "devnet"
});

// ==================================================================
// OPTION 1: Deposit only (save note for later)
// ==================================================================
const depositResult = await client.deposit(
  connection,
  keypair,
  1_000_000_000, // 1 SOL in lamports
  {
    onProgress: (status) => console.log(status),
  }
);
console.log("Note saved:", depositResult.note);
// Save this note securely! You can withdraw later.

// ==================================================================
// OPTION 2: Private Transfer (complete flow: deposit + withdraw)
// ==================================================================
// Generate a note (not deposited yet)
const note = generateNote(1_000_000_000); // 1 SOL

// privateTransfer handles EVERYTHING:
// - Deposits the note if not already deposited
// - Waits for confirmation
// - Generates ZK proof using artifact-based flow (private inputs never pass through backend)
// - Transfers to recipients
const transferResult = await client.privateTransfer(
  connection,
  note,
  [
    { recipient: new PublicKey("ADDR1"), amount: 500_000_000 },
    { recipient: new PublicKey("ADDR2"), amount: 492_500_000 }, // After fees
  ],
  {
    onProgress: (status) => console.log(status),
    onProofProgress: (progress) => console.log(`Proof: ${progress}%`),
  }
);

console.log("Transfer complete:", transferResult.signature);

API Configuration:

  • The SDK uses environment variables or defaults for API URLs
  • Set CLOAK_API_URL environment variable to override the default
  • Defaults to http://localhost for local development

Core Concepts

Notes

A Cloak Note is a cryptographic commitment representing a private amount of SOL. It contains:

  • commitment: Blake3 hash that commits to the amount and secrets
  • sk_spend: Spending secret key (keep this safe!)
  • r: Randomness value
  • amount: Amount in lamports
  • Metadata: signature, leafIndex, merkleProof (after deposit)

Important: Treat notes like cash - anyone with the note can withdraw the funds!

Private Transfer Flow

The privateTransfer method handles the complete flow automatically:

  1. Check: Is the note already deposited?
  2. Deposit (if needed): Deposit SOL and create commitment
  3. Wait: Transaction confirms and indexer processes it
  4. Prove: Generate zero-knowledge proof
  5. Transfer: Submit to relay and send funds to recipients
  6. Complete: Funds are sent to recipients privately

The proof ensures that:

  • You own the note (know the spending key)
  • The note exists in the Merkle tree
  • You haven't spent it before (nullifier check)
  • Outputs sum correctly (amount conservation)

All without revealing which note you're spending!

Usage Patterns

Pattern 1: Deposit now, withdraw later

import { generateNote } from "@cloaklabz/sdk";

// Save funds privately
const result = await client.deposit(connection, keypair, 1_000_000_000);
saveNoteSecurely(result.note); // Store for later

// Later: withdraw using the saved note
const withdrawResult = await client.withdraw(
  connection, loadNote(), recipientAddress
);

Pattern 2: Private transfer (deposit + immediate withdraw)

import { generateNote } from "@cloaklabz/sdk";

// Send funds privately to recipients in one call
const note = generateNote(1_000_000_000);
const result = await client.privateTransfer(
  connection,
  note,
  [
    { recipient: addr1, amount: 500_000_000 },
    { recipient: addr2, amount: 492_500_000 }
  ]
);
// Everything handled automatically!

Pattern 3: Swap SOL for tokens privately

import { generateNote } from "@cloaklabz/sdk";

// Swap SOL for tokens privately
const note = generateNote(1_000_000_000);
const result = await client.swap(
  connection,
  note,
  recipientPublicKey,
  {
    outputMint: "BRjpCHtyQLNCo8gqRUr8jtdAj5AjPYQaoqbvcZiHok1k",
    slippageBps: 100,
    getQuote: async (amount, mint, slippage) => {
      // Your swap quote logic
    },
  }
);

API Reference

CloakSDK

Main client for interacting with Cloak Protocol.

Constructor

new CloakSDK(config: {
  keypairBytes: Uint8Array;  // Required: Solana keypair secret key
  network?: Network;          // Optional: "devnet" | "testnet" | "mainnet" (default: "devnet")
  cloakKeys?: CloakKeyPair;   // Optional: Cloak protocol keys (auto-generated if not provided)
  storage?: StorageAdapter;   // Optional: Storage adapter for notes (default: in-memory)
  programId?: PublicKey;      // Optional: Cloak program ID (uses default if not provided)
})

Methods

deposit()

Deposit SOL into the protocol and create a private note.

async deposit(
  connection: Connection,
  payer: Keypair | WalletAdapter,
  amountLamports: number,
  options?: DepositOptions
): Promise<DepositResult>

Example:

const result = await client.deposit(connection, keypair, 1_000_000_000, {
  onProgress: (status) => console.log(status),
});
console.log("Leaf index:", result.leafIndex);
// Save result.note securely!
privateTransfer()

Execute a complete private transfer with 1-5 recipients.

Handles the full flow: If the note isn't deposited yet, it deposits it first, waits for confirmation, then proceeds with the withdrawal.

async privateTransfer(
  connection: Connection,
  note: CloakNote,
  recipients: MaxLengthArray<Transfer, 5>,
  options?: TransferOptions
): Promise<TransferResult>

Type-safe recipient array (1-5 elements):

type Transfer = { recipient: PublicKey; amount: number };
type MaxLengthArray<T, Max extends number, A extends T[] = []> =
  A['length'] extends Max ? A : A | MaxLengthArray<T, Max, [T, ...A]>;

Example:

// Generate note (not deposited)
const note = generateNote(1_000_000_000);

// privateTransfer handles deposit + withdrawal
const result = await client.privateTransfer(
  connection,
  note,
  [
    { recipient: addr1, amount: 500_000_000 },
    { recipient: addr2, amount: 400_000_000 },
    { recipient: addr3, amount: 92_500_000 }, // After protocol fees
  ],
  {
    onProgress: (status) => console.log(status),
    onProofProgress: (progress) => console.log(`Proof: ${progress}%`),
  }
);
send()

Convenience method that wraps privateTransfer() with a simpler API.

async send(
  connection: Connection,
  note: CloakNote,
  recipients: MaxLengthArray<Transfer, 5>,
  options?: TransferOptions
): Promise<TransferResult>

Example:

const result = await client.send(
  connection,
  note,
  [
    { recipient: addr1, amount: 500_000_000 },
    { recipient: addr2, amount: 492_500_000 },
  ]
);
withdraw()

Convenience method for withdrawing to a single recipient.

Handles the full flow: Deposits if needed, then withdraws to one recipient.

async withdraw(
  connection: Connection,
  note: CloakNote,
  recipient: PublicKey,
  options?: WithdrawOptions
): Promise<TransferResult>

Example:

const note = generateNote(1_000_000_000);
const result = await client.withdraw(
  connection,
  note,
  recipientAddress,
  { withdrawAll: true } // Withdraw full amount minus fees
);
swap()

Swap SOL for tokens privately using zero-knowledge proofs.

Handles the full flow: Deposits if needed, fetches swap quote, generates proof, and executes swap.

async swap(
  connection: Connection,
  note: CloakNote,
  recipient: PublicKey,
  options: SwapOptions
): Promise<SwapResult>

SwapOptions:

interface SwapOptions extends TransferOptions {
  outputMint: string;                    // Output token mint address
  slippageBps?: number;                  // Slippage tolerance in basis points (default: 100)
  minOutputAmount?: number;               // Minimum output amount (if not using getQuote)
  getQuote?: (amountLamports: number, outputMint: string, slippageBps: number) => Promise<{
    outAmount: number;
    minOutputAmount: number;
  }>;
}

Example:

const note = generateNote(1_000_000_000);
const result = await client.swap(
  connection,
  note,
  recipientPublicKey,
  {
    outputMint: "BRjpCHtyQLNCo8gqRUr8jtdAj5AjPYQaoqbvcZiHok1k",
    slippageBps: 100, // 1% slippage
    getQuote: async (amount, mint, slippage) => {
      // Fetch quote from swap API (e.g., Orca, Jupiter)
      const response = await fetch(
        `https://pools-api.devnet.orca.so/swap-quote?from=So11111111111111111111111111111111111111112&to=${mint}&amount=${amount}&slippageBps=${slippage}`
      );
      const data = await response.json();
      return {
        outAmount: parseInt(data.data.swap.outputAmount),
        minOutputAmount: Math.floor(parseInt(data.data.swap.outputAmount) * (10000 - slippage) / 10000),
      };
    },
    onProgress: (status) => console.log(status),
  }
);
generateNote()

Generate a new note without depositing (for testing or pre-generation).

Note: This is a standalone function, not a method on the client.

import { generateNote } from "@cloaklabz/sdk";

const note = generateNote(amountLamports: number): CloakNote
parseNote()

Parse a note from JSON string.

Note: This is a standalone function, not a method on the client.

import { parseNote } from "@cloaklabz/sdk";

const note = parseNote(jsonString: string): CloakNote
exportNote()

Export a note to JSON string.

Note: This is a standalone function, not a method on the client.

import { exportNote } from "@cloaklabz/sdk";

const json = exportNote(note: CloakNote, pretty?: boolean): string

Fee Calculation

import { calculateFee, getDistributableAmount, formatAmount } from "@cloaklabz/sdk";

const amount = 1_000_000_000; // 1 SOL
const fee = calculateFee(amount); // Protocol fee
const distributable = getDistributableAmount(amount); // Amount after fees

console.log(`Fee: ${formatAmount(fee)} SOL`);
console.log(`Distributable: ${formatAmount(distributable)} SOL`);

Fee Structure:

  • Fixed: 0.0025 SOL (2.5M lamports)
  • Variable: 0.5% of amount
  • Total: FIXED + floor(amount * 0.005)

Crypto Utilities

import {
  generateCommitment,
  computeNullifier,
  computeOutputsHash,
  hexToBytes,
  bytesToHex,
} from "@cloaklabz/sdk";

// Generate commitment
const commitment = generateCommitment(amount, r, skSpend);

// Compute nullifier (prevents double-spending)
const nullifier = computeNullifier(skSpend, leafIndex);

// Compute outputs hash
const outputsHash = computeOutputsHash([
  { recipient: addr1, amount: amount1 },
  { recipient: addr2, amount: amount2 },
]);

Advanced Usage

Custom Storage

The SDK doesn't handle persistence - you control where notes are stored:

// Browser localStorage
const noteJson = client.exportNote(note);
localStorage.setItem(`note-${note.commitment}`, noteJson);

// File system (Node.js)
import fs from "fs";
fs.writeFileSync(`note-${note.commitment}.json`, client.exportNote(note, true));

// Database
await db.notes.create({ commitment: note.commitment, data: client.exportNote(note) });

Progress Tracking

await client.privateTransfer(
  note,
  recipients,
  {
    onProgress: (status) => {
      console.log(status);
      // "Fetching Merkle proof..."
      // "Computing cryptographic values..."
      // "Generating zero-knowledge proof..."
      // "Submitting to relay service..."
    },
    onProofProgress: (progress) => {
      console.log(`Proof generation: ${progress}%`);
      // Update progress bar, etc.
    },
  }
);

Direct Service Access

Use service clients directly for lower-level operations:

import { IndexerService, ArtifactProverService, RelayService } from "@cloaklabz/sdk";

const indexer = new IndexerService("https://indexer.example.com");
// Use ArtifactProverService for privacy (private inputs never pass through backend)
const prover = new ArtifactProverService("https://indexer.example.com");
const relay = new RelayService("https://relay.example.com");

// Get Merkle root
const { root, next_index } = await indexer.getMerkleRoot();

// Get Merkle proof
const proof = await indexer.getMerkleProof(leafIndex);

// Generate proof using artifact-based flow (private inputs uploaded directly to TEE)
const proofResult = await prover.generateProof(inputs);

// Submit withdrawal
const signature = await relay.submitWithdraw(params);

TypeScript Types

The SDK is fully typed. Import types as needed:

import type {
  CloakNote,
  CloakConfig,
  Transfer,
  TransferResult,
  DepositResult,
  MerkleProof,
  SwapOptions,
  SwapResult,
} from "@cloaklabz/sdk";

Security Considerations

⚠️ Important Security Notes:

  1. Keep notes secure: Treat them like private keys. Anyone with the note can withdraw funds.

  2. Backup notes: If you lose a note, the funds are unrecoverable.

  3. Verify recipients: Double-check recipient addresses before transfer.

  4. Amount validation: Always validate that outputs sum to expected amounts.

  5. Network isolation: Don't mix notes from different networks (devnet/mainnet).

  6. Proof privacy: This SDK uses artifact-based proof generation where private inputs are uploaded directly to TEE, never passing through the backend in plaintext. This provides true privacy-preserving withdrawals.

Error Handling

try {
  const result = await client.privateTransfer(connection, note, recipients);
  console.log("Success:", result.signature);
} catch (error) {
  if (error.message.includes("Note must be deposited")) {
    console.error("Note not yet deposited");
  } else if (error.message.includes("Proof generation failed")) {
    console.error("ZK proof generation failed");
  } else if (error.message.includes("Relay")) {
    console.error("Relay service error");
  } else if (error.message.includes("@solana/spl-token")) {
    console.error("Swap requires @solana/spl-token package");
  } else {
    console.error("Unknown error:", error);
  }
}

Examples

See the examples directory for complete working examples:

  • deposit-example.ts - Deposit SOL to create a private note
  • transfer-example.ts - Private transfers to multiple recipients
  • send-example.ts - Using the send() convenience method
  • withdraw-example.ts - Withdraw previously deposited notes
  • swap-example.ts - Swap SOL for tokens privately

Run examples:

npm run example:deposit
npm run example:transfer
npm run example:send
npm run example:withdraw
npm run example:swap

Contributing

Contributions are welcome! Please open an issue or PR.

License

MIT

Support

For questions and support: