@hula-privacy/mixer
v2.0.0
Published
Hula Privacy Protocol SDK - Complete toolkit for private transactions on Solana
Downloads
435
Maintainers
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/mixerQuick 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:
./circuits/build/transaction_js/transaction.wasm./circuits/build/keys/transaction_final.zkey./assets/transaction.wasmand./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 typecheckArchitecture
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 abstractionSecurity Considerations
- Spending Key: The spending key is the master secret. Never share it or store it insecurely.
- Wallet Recovery: Using
fromSignature()allows deterministic recovery from a Solana wallet signature. - Local Storage: When storing UTXOs locally, use appropriate encryption.
- Note Encryption: Encrypted notes allow recipients to discover UTXOs sent to them.
License
MIT
