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

@hula-privacy/mixer

v2.0.0

Published

Hula Privacy Protocol SDK - Complete toolkit for private transactions on Solana

Downloads

435

Readme

Hula Privacy SDK

Complete toolkit for privacy transactions on Solana using ZK proofs and UTXOs.

Installation

npm install @hula-privacy/mixer
# or
bun add @hula-privacy/mixer

Quick Start

import { HulaWallet, initHulaSDK, getKeyDerivationMessage } from '@hula-privacy/mixer';

// Initialize SDK (loads Poseidon hasher)
await initHulaSDK();

// Create wallet from Phantom signature (deterministic derivation)
const message = getKeyDerivationMessage();
const signature = await wallet.signMessage(message);
const hulaWallet = await HulaWallet.fromSignature(signature, {
  rpcUrl: 'https://api.devnet.solana.com',
  relayerUrl: 'http://localhost:3001',
});

// Or create a new wallet with random keys
const newWallet = await HulaWallet.create({
  rpcUrl: 'https://api.devnet.solana.com',
  relayerUrl: 'http://localhost:3001',
});

// Get wallet public identifier
console.log('Owner hash:', hulaWallet.ownerHex);
console.log('Encryption pubkey:', Buffer.from(hulaWallet.encryptionPublicKey).toString('hex'));

Syncing UTXOs

The wallet syncs with the relayer to discover your UTXOs from encrypted notes:

// Sync wallet to find your UTXOs
const result = await hulaWallet.sync((progress) => {
  console.log(`Syncing: ${progress.stage} ${progress.current}/${progress.total}`);
});

console.log(`Found ${result.newUtxos} new UTXOs`);
console.log(`Marked ${result.spentUtxos} UTXOs as spent`);

// Check balance
const balance = hulaWallet.getBalance(mintAddress);
console.log('Private balance:', balance);

Transactions

Deposit (Public → Private)

const { transaction } = await hulaWallet.deposit(mintAddress, 1_000_000n);

// Submit transaction using your preferred method
// The transaction object contains proof, public inputs, etc.

Transfer (Private → Private)

// Get recipient's public info
const recipientOwner = BigInt('0x...');  // Recipient's owner hash
const recipientEncPubKey = new Uint8Array([...]); // Recipient's encryption pubkey

const { transaction } = await hulaWallet.transfer(
  mintAddress,
  500_000n,
  recipientOwner,
  recipientEncPubKey
);

Withdraw (Private → Public)

const recipientPubkey = new PublicKey('...');

const { transaction } = await hulaWallet.withdraw(
  mintAddress,
  200_000n,
  recipientPubkey
);

Low-Level API

For more control, use the low-level functions directly:

Key Derivation

import { generateSpendingKey, deriveKeys, initPoseidon } from '@hula-privacy/mixer';

await initPoseidon();

const spendingKey = generateSpendingKey();
const keys = deriveKeys(spendingKey);

console.log('Owner:', keys.owner.toString(16));
console.log('Viewing key:', keys.viewingKey.toString(16));
console.log('Encryption pubkey:', Buffer.from(keys.encryptionKeyPair.publicKey).toString('hex'));

UTXO Management

import { createUTXO, computeCommitment, computeNullifier } from '@hula-privacy/mixer';

// Create a UTXO
const utxo = createUTXO(
  1_000_000n,        // value
  mintBigInt,        // mint address as bigint
  keys.owner,        // owner hash
  0,                 // leaf index
  0                  // tree index
);

// Compute commitment manually
const commitment = computeCommitment(
  utxo.value,
  utxo.mintTokenAddress,
  utxo.owner,
  utxo.secret
);

// Compute nullifier for spending
const nullifier = computeNullifier(
  keys.spendingKey,
  utxo.commitment,
  utxo.leafIndex
);

Merkle Tree Operations

import { 
  fetchMerklePath, 
  fetchMerkleRoot,
  computeMerklePathFromLeaves,
  getCurrentTreeIndex 
} from '@hula-privacy/mixer';

// Get current tree index
const treeIndex = await getCurrentTreeIndex('http://localhost:3001');

// Fetch merkle root
const root = await fetchMerkleRoot(treeIndex);

// Fetch merkle path for a specific leaf
const path = await fetchMerklePath(treeIndex, leafIndex);

// Or compute locally from leaves
const leaves = [commitment1, commitment2, ...];
const localPath = computeMerklePathFromLeaves(leafIndex, leaves);

Relayer API

import { RelayerClient, getRelayerClient } from '@hula-privacy/mixer';

const client = getRelayerClient('http://localhost:3001');

// Get pool state
const pool = await client.getPool();
console.log('Current tree:', pool.currentTreeIndex);
console.log('Total commitments:', pool.commitmentCount);

// Get leaves for a tree
const leaves = await client.getAllLeavesForTree(0);

// Check if nullifier is spent
const { spent } = await client.checkNullifier(nullifierHex);

// Get encrypted notes
const notes = await client.getAllNotes();

Proof Generation

import { generateProof, setCircuitPaths } from '@hula-privacy/mixer';

// Set circuit paths (if not in default locations)
setCircuitPaths(
  '/path/to/transaction.wasm',
  '/path/to/transaction_final.zkey'
);

// Generate proof
const { proof, publicSignals } = await generateProof(circuitInputs);

Configuration

Circuit Files

The SDK looks for circuit files in these locations:

  1. ./circuits/build/transaction_js/transaction.wasm
  2. ./circuits/build/keys/transaction_final.zkey
  3. ./assets/transaction.wasm and ./assets/transaction_final.zkey

You can also set custom paths:

import { setCircuitPaths } from '@hula-privacy/mixer';

setCircuitPaths('/custom/path/transaction.wasm', '/custom/path/transaction.zkey');

Default Relayer URL

import { setDefaultRelayerUrl } from '@hula-privacy/mixer';

setDefaultRelayerUrl('https://relayer.hulaprivacy.io');

Types

All types are exported for TypeScript users:

import type {
  UTXO,
  SerializableUTXO,
  WalletKeys,
  EncryptedNote,
  MerklePath,
  CircuitInputs,
  TransactionRequest,
  BuiltTransaction,
  HulaSDKConfig,
} from '@hula-privacy/mixer';

Building

# Install dependencies
bun install

# Build
bun run build

# Type check
bun run typecheck

Architecture

sdk/src/
├── index.ts        # Main exports
├── types.ts        # Type definitions
├── constants.ts    # Program IDs, seeds, circuit params
├── api.ts          # Relayer API client
├── crypto.ts       # Poseidon, key derivation, encryption
├── merkle.ts       # Merkle tree operations
├── utxo.ts         # UTXO management
├── proof.ts        # ZK proof generation
├── transaction.ts  # Transaction building
└── wallet.ts       # High-level wallet abstraction

Security Considerations

  1. Spending Key: The spending key is the master secret. Never share it or store it insecurely.
  2. Wallet Recovery: Using fromSignature() allows deterministic recovery from a Solana wallet signature.
  3. Local Storage: When storing UTXOs locally, use appropriate encryption.
  4. Note Encryption: Encrypted notes allow recipients to discover UTXOs sent to them.

License

MIT