npm package: @nullifierlabs/sdk
Description: TypeScript SDK for Nullifier.cash - Solana privacy mixer with anonymous SOL transfers
Author: Nullifier.cash
Latest version: 1.0.0

# @nullifierlabs/sdk

TypeScript SDK for [Nullifier.cash](https://nullifier.cash) - A privacy-preserving mixer for Solana that enables anonymous SOL transfers.

## Features

- 🔒 **Privacy-First**: Zero-knowledge proofs ensure transaction privacy
- 🌳 **Merkle Tree**: Depth-20 tree supports 1M+ deposits per pool
- 🔐 **Poseidon Hashing**: BN254-optimized cryptographic commitments
- 💾 **Encrypted Backup**: On-chain encrypted note storage for recovery
- 🎯 **Multiple Pools**: Support for 0.1, 1, 10, and 100 SOL denominations
- 📱 **Universal**: Works in browser and Node.js environments

## Installation

```bash
npm install @nullifierlabs/sdk @solana/web3.js @coral-xyz/anchor
```

## Quick Start

### Depositing SOL

```typescript
import { Connection, PublicKey } from "@solana/web3.js";
import { NullifierMixer } from "@nullifierlabs/sdk";

// Initialize connection
const connection = new Connection("https://api.devnet.solana.com");
const mixer = new NullifierMixer(connection);

// Your wallet adapter (e.g., from @solana/wallet-adapter-react)
const wallet = useWallet(); // or any wallet implementing WalletAdapter interface

// Deposit 1 SOL into the mixer
const { note, signature, commitment } = await mixer.deposit(
  wallet,
  1.0, // denomination in SOL
  true // save note to storage
);

console.log("Deposit successful!");
console.log("Transaction:", signature);
console.log("Save this note securely:", note);
```

### Withdrawing SOL

```typescript
import { PublicKey } from "@solana/web3.js";

// Load note from storage or file
const storedNotes = await mixer.getStoredNotes();
const note = storedNotes[0]; // or load from file

// Withdraw to a different address for privacy
const recipientAddress = new PublicKey("...");

const { signature, amount } = await mixer.withdraw(
  wallet,
  note,
  recipientAddress,
  true // remove note from storage after withdrawal
);

console.log(`Withdrawn ${amount} SOL`);
console.log("Transaction:", signature);
```

## Core Concepts

### Privacy Model

Nullifier.cash uses a **commitment scheme** and **Merkle proofs** to ensure privacy:

1. **Deposit**: Generate random `secret` and `nullifier`, compute `commitment = Poseidon(secret, nullifier)`, and deposit into pool
2. **Anonymity Set**: Your deposit mixes with all other deposits in the pool
3. **Withdrawal**: Prove knowledge of a valid commitment without revealing which one

**Result**: No on-chain link between your deposit and withdrawal addresses!

### Note Structure

A note contains all information needed to withdraw:

```typescript
interface Note {
  secret: string;        // Random 32-byte hex value
  nullifier: string;     // Random 32-byte hex value
  commitment: string;    // Poseidon(secret, nullifier)
  pool: string;          // Pool address
  leafIndex: number;     // Position in Merkle tree
  denomination: number;  // Amount deposited (SOL)
}
```

**⚠️ IMPORTANT**: Store notes securely! Loss of a note means loss of funds.

## API Reference

### NullifierMixer

Main SDK class for interacting with the mixer.

#### Constructor

```typescript
new NullifierMixer(
  connection: Connection,
  programId?: PublicKey,
  noteStorage?: NoteStorage
)
```

#### Methods

##### `deposit(wallet, denomination, saveToStorage?)`

Deposit SOL into a mixing pool.

```typescript
const result = await mixer.deposit(wallet, 1.0, true);
// Returns: { note, signature, commitment, leafIndex }
```

**Parameters:**
- `wallet`: Wallet adapter for signing transactions
- `denomination`: Amount in SOL (0.1, 1, 10, or 100)
- `saveToStorage`: Save note to storage (default: `true`)

##### `withdraw(wallet, note, recipient, removeFromStorage?)`

Withdraw SOL using a note.

```typescript
const result = await mixer.withdraw(
  wallet,
  note,
  recipientAddress,
  true
);
// Returns: { signature, amount }
```

**Parameters:**
- `wallet`: Wallet adapter for signing
- `note`: Note from deposit
- `recipient`: Recipient public key
- `removeFromStorage`: Remove note after withdrawal (default: `true`)

##### `getPoolStats(poolAddress)`

Get statistics for a pool.

```typescript
const stats = await mixer.getPoolStats(poolAddress);
// Returns: { denomination, totalDeposits, totalWithdrawals, balance, minDelay, merkleRoot, nextLeafIndex }
```

##### `isNullifierUsed(poolAddress, nullifier)`

Check if a nullifier has been used (note already withdrawn).

```typescript
const isUsed = await mixer.isNullifierUsed(poolAddress, note.nullifier);
```

##### `getStoredNotes()`

Get all notes from storage.

```typescript
const notes = await mixer.getStoredNotes();
```

### Configuration

#### Pool Configuration

```typescript
import { POOLS, getPoolByDenomination } from "@nullifierlabs/sdk";

// Get pool by denomination
const pool = getPoolByDenomination(1.0);
console.log(pool.address); // Pool address
console.log(pool.minDelay); // Minimum delay in seconds
```

#### Available Pools

| Pool | Denomination | Min Delay |
|------|-------------|-----------|
| 0.1 SOL | 0.1 SOL | 60 seconds |
| 1 SOL | 1.0 SOL | 60 seconds |
| 10 SOL | 10.0 SOL | 60 seconds |
| 100 SOL | 100.0 SOL | 60 seconds |

### Cryptographic Utilities

#### Poseidon Hashing

```typescript
import {
  createCommitment,
  generateRandomBytes32,
  hexToBytes,
  toHex,
} from "@nullifierlabs/sdk";

// Generate random values
const secret = hexToBytes(generateRandomBytes32());
const nullifier = hexToBytes(generateRandomBytes32());

// Create commitment
const commitment = await createCommitment(secret, nullifier);
console.log("Commitment:", toHex(commitment));
```

#### Merkle Proofs

```typescript
import { buildMerkleProofPoseidon } from "@nullifierlabs/sdk";

// Get all commitments for pool
const commitments = await mixer.getAllCommitments(poolAddress);

// Build Merkle proof for specific commitment
const proof = await buildMerkleProofPoseidon(commitments, leafIndex);
// Returns: { pathElements: string[], pathIndices: boolean[] }
```

### Note Storage

#### Using Default Storage (localStorage)

```typescript
import { defaultNoteStorage } from "@nullifierlabs/sdk";

// Save note
await defaultNoteStorage.saveNote({
  secret: "...",
  nullifier: "...",
  commitment: "...",
  pool: "...",
  poolName: "1 SOL Pool",
  denomination: 1.0,
  leafIndex: 42,
  timestamp: Date.now(),
  depositorWallet: "...",
});

// Get all notes
const notes = await defaultNoteStorage.getStoredNotes();

// Remove note
await defaultNoteStorage.removeNote(commitment);
```

#### Custom Storage Backend

Implement the `StorageBackend` interface for custom storage:

```typescript
import { NoteStorage, StorageBackend } from "@nullifierlabs/sdk";
import fs from "fs/promises";

class FileStorage implements StorageBackend {
  async getItem(key: string): Promise<string | null> {
    try {
      return await fs.readFile(`./data/${key}.json`, "utf-8");
    } catch {
      return null;
    }
  }

  async setItem(key: string, value: string): Promise<void> {
    await fs.writeFile(`./data/${key}.json`, value);
  }

  async removeItem(key: string): Promise<void> {
    await fs.unlink(`./data/${key}.json`);
  }
}

// Use custom storage
const storage = new NoteStorage(new FileStorage());
const mixer = new NullifierMixer(connection, PROGRAM_ID, storage);
```

## Security Best Practices

1. **Store Notes Securely**: Notes are equivalent to private keys. Store them encrypted or in secure storage.

2. **Use Different Wallets**: For maximum privacy, use different wallets for deposit and withdrawal.

3. **Wait Between Transactions**: Allow time for other deposits to increase anonymity set.

4. **Verify Pool Address**: Always verify pool addresses match official configurations.

5. **Check Network**: Ensure you're connected to the correct network (devnet/mainnet).

## Examples

### Full Deposit & Withdrawal Flow

```typescript
import { Connection, Keypair, PublicKey } from "@solana/web3.js";
import { NullifierMixer } from "@nullifierlabs/sdk";

async function privacyTransfer() {
  const connection = new Connection("https://api.devnet.solana.com");
  const mixer = new NullifierMixer(connection);

  // 1. Deposit from Wallet A
  const walletA = {
    publicKey: depositWallet.publicKey,
    signTransaction: (tx) => depositWallet.signTransaction(tx),
  };

  console.log("Depositing 1 SOL...");
  const { note, signature } = await mixer.deposit(walletA, 1.0);
  console.log("Deposit TX:", signature);
  console.log("Note saved to storage");

  // 2. Wait for anonymity (in production, wait longer)
  console.log("Waiting for more deposits to increase anonymity...");
  await new Promise(resolve => setTimeout(resolve, 60000));

  // 3. Withdraw to Wallet B (different wallet!)
  const walletB = {
    publicKey: withdrawWallet.publicKey,
    signTransaction: (tx) => withdrawWallet.signTransaction(tx),
  };

  const recipientAddress = new PublicKey("..."); // Final destination

  console.log("Withdrawing to new address...");
  const { signature: withdrawSig } = await mixer.withdraw(
    walletB,
    note,
    recipientAddress
  );

  console.log("Withdrawal TX:", withdrawSig);
  console.log("Privacy transfer complete! ✅");
}
```

### Check Pool Statistics

```typescript
import { POOLS } from "@nullifierlabs/sdk";

for (const pool of POOLS) {
  const stats = await mixer.getPoolStats(pool.address);

  console.log(`\n${pool.name}:`);
  console.log(`  Total Deposits: ${stats.totalDeposits}`);
  console.log(`  Total Withdrawals: ${stats.totalWithdrawals}`);
  console.log(`  Pool Balance: ${stats.balance} SOL`);
  console.log(`  Anonymity Set Size: ${stats.nextLeafIndex}`);
}
```

### Recover Notes from Blockchain

```typescript
import { decryptNote } from "@nullifierlabs/sdk";

// If you lost your note but have the original deposit wallet
async function recoverNote(depositWallet: WalletAdapter, poolAddress: PublicKey, leafIndex: number) {
  const mixer = new NullifierMixer(connection);

  // Derive encrypted note PDA
  const [encryptedNotePda] = PublicKey.findProgramAddressSync(
    [
      Buffer.from("encrypted_note"),
      poolAddress.toBuffer(),
      new BN(leafIndex).toArrayLike(Buffer, "le", 4),
    ],
    mixer.programId
  );

  // Fetch and decrypt
  const encryptedNoteAccount = await connection.getAccountInfo(encryptedNotePda);
  const { secret, nullifier } = await decryptNote(
    encryptedNoteAccount.data,
    depositWallet.publicKey
  );

  console.log("Note recovered!");
  console.log("Secret:", toHex(secret));
  console.log("Nullifier:", toHex(nullifier));
}
```

## TypeScript Support

This package includes full TypeScript definitions. All types are exported for your convenience:

```typescript
import type {
  Note,
  StoredNote,
  MerkleProof,
  DepositResult,
  WithdrawResult,
  WalletAdapter,
  PoolConfig,
  NetworkConfig,
  StorageBackend,
} from "@nullifierlabs/sdk";
```

## Contributing

Contributions are welcome! Please ensure:

- Code follows existing style conventions
- All tests pass
- New features include tests and documentation

## License

MIT

## Links

- **Website**: https://nullifier.cash
- **Documentation**: https://docs.nullifier.cash
- **GitHub**: https://github.com/NullifierLabs/npm-sdk

## Support

For questions and support:
- Open an issue on GitHub
- Email: support@nullifier.cash

---

**⚠️ Disclaimer**: This software is provided as-is. Always verify contract addresses and use at your own risk. Not audited for mainnet use.