Skip to main content
// DOCUMENTATIONVERSION: 1.0.0文書

NixProtocol Documentation

LOADING_DOCS_MODULE

Add UTXO-based private payments to your application. NixProtocol provides a privacy pool protocol for EVM chains using Noir ZK circuits and Grumpkin curve cryptography. Testnet live on Avalanche Fuji & Base Sepolia.

Testnet LiveAvalanche FujiBase SepoliaAvalancheEthereum

Architecture Overview

ARCH_001

NixProtocol provides privacy through a layered architecture combining encryption, zero-knowledge proofs, and smart contracts.

System Architecture
Your Application
@nixprotocol/nix-wallet
Key Gen
(Grumpkin)
ECIES
Encrypt
ZK Proof Generator
(UltraHonk/Noir)
Smart Contracts
NixPool
(Privacy Pool)
Merkle Trees
(Note Storage)
UltraHonk Verifier
(Proof Verification)
Avalanche Fuji|Base Sepolia|Avalanche|Ethereum

Data Flow

01
Client Side
  • Derive Grumpkin keypair via Poseidon2
  • Encrypt notes with ECIES
  • Generate UltraHonk proofs locally
  • Authorize via ZK proofs (no signatures)
02
On-Chain
  • Verify ZK proofs
  • Store note commitments in Merkle tree
  • Verify nullifiers & update roots
  • Emit privacy-preserving events
03
Auditor Access
  • Selective disclosure keys
  • View-only access grants
  • Compliance reporting
  • Audit trail generation

Key Components

GRP
Grumpkin Curve

Embedded elliptic curve for ZK-friendly operations. Used for keypairs, ECIES encryption, and NixAddress encoding. Native to Noir circuits via Barretenberg.

UH
UltraHonk Proofs

Compiled from Noir circuits via Barretenberg. No trusted setup required. 3 circuits: deposit, registration, and transact.

PSN
Poseidon2 Hash

ZK-optimized hash function used for note commitments, nullifiers, and Merkle trees. Poseidon2 variant for improved performance in Noir circuits.

Quickstart

QUICK_START

Get up and running in 5 minutes. Generate keys, encrypt data, and create proofs.

1

Install the SDK

bash
npm install @nixprotocol/nix-wallet viem
2

Generate Keypair

typescript
import { deriveNixKeyFromSignature } from '@nixprotocol/nix-wallet';

// Deterministic from wallet signature (recommended)
// Signs "NixProtocol key derivation v3", then Poseidon2-hashes the signature
const keyPair = await deriveNixKeyFromSignature(walletClient);

console.log('Public Key:', keyPair.publicKey);    // { x: bigint, y: bigint }
console.log('Commitment:', keyPair.commitment);   // Poseidon2(pk.x, pk.y)
console.log('Private Key:', keyPair.privateKey);  // Grumpkin scalar
3

Encrypt & Create Notes

typescript
import { ecies, poseidon2Hash } from '@nixprotocol/nix-wallet';

// Encrypt a note value to recipient's Grumpkin public key
const randomness = BigInt('0x' + crypto.randomUUID().replace(/-/g, ''));
const ciphertext = ecies.encrypt(1000n, randomness, recipientPublicKey);

// Decrypt (only recipient can do this)
const decrypted = ecies.decrypt(privateKey, ciphertext.authKeyX, ciphertext.authKeyY, ciphertext.encrypted);

// Create note commitment
const commitment = poseidon2Hash([value, ownerPkX, ownerPkY, salt]);

Installation

PKG_INSTALL

NixProtocol is available as a set of NPM packages for different integration needs.

PKG.01
@nixprotocol/nix-walletBUSL-1.1

Core wallet library - key derivation, encryption, proofs, NixAddress, and UTXO management

PKG.02
@nixprotocol/contractsBUSL-1.1

NixPool Solidity contracts and TypeScript bindings

PKG.03
@nixprotocol/relayerBUSL-1.1

Gas abstraction with ZK proof authorization

Encryption

NixProtocol uses ECIES encryption on the Grumpkin curve for encrypting UTXO note data and auditor-accessible transaction records.

Key Properties

  • Asymmetric Encryption: Encrypt data to any Grumpkin public key
  • Probabilistic: Same plaintext encrypts differently each time
  • ZK-Friendly: Grumpkin curve is native to Noir circuits via Barretenberg

Mathematical Foundation

Grumpkin Curve

Embedded elliptic curve native to Barretenberg/Noir:

Short Weierstrass curve: y² = x³ - 17 (embedded curve of BN254)
ECIES Encryption

For message m, recipient public key P = sG:

Encrypt: Generate ephemeral keypair (r, R=rG), shared secret S=rP, encrypt m with symmetric key derived from S
Decrypt: Compute S=sR, derive symmetric key, decrypt m
Note Commitments

Note commitments use Poseidon2 hash: commitment = Poseidon2(value, ownerPubKey, salt). Nullifiers prevent double-spending: nullifier = Poseidon2(commitment, privateKey).

Security Parameters

Curve Security

~128-bit security level (Grumpkin curve, embedded in BN254)

Ephemeral Keys

Fresh random ephemeral key per encryption

Note Size

Commitment (1 field element) + ECIES-encrypted data

Merkle Depth

20 levels supporting ~1M notes

Zero-Knowledge Proofs

NixProtocol uses UltraHonk proofs compiled from Noir circuits via Barretenberg. No trusted setup required.

Registration Circuit

Proves knowledge of Grumpkin private key corresponding to registered public key

Deposit Circuit

Creates a new UTXO note commitment from an ERC-20 deposit with ECIES-encrypted data

Transact Circuit

Consumes 2 input notes, creates 2 output notes. Proves Merkle membership, correct nullifiers, and value conservation

NixAddress & UTXO Notes

NixAddress aliases your real EVM address with a compact Grumpkin public key. Share it to receive UTXO notes in the privacy pool without ever exposing your on-chain identity.

typescript
import { deriveNixKeyFromSignature, nixAddressFromKeyPair,
  encodeNixAddress, decodeNixAddress } from '@nixprotocol/nix-wallet';

// Derive keypair from wallet signature
const keyPair = await deriveNixKeyFromSignature(walletClient);
const nixAddress = nixAddressFromKeyPair(keyPair);
console.log('NixAddress:', nixAddress);
// nix:<commitment>:<pk.x>:<pk.y>

// Decode to get commitment + public key
const { commitment, publicKey } = decodeNixAddress(nixAddress);

Compliance & Auditor Access

Privacy with accountability. Each NixPool has a designated auditor who holds a single Grumpkin private key, set at deployment time. This key can decrypt ECIES-encrypted note data for compliance reporting.

How Auditor Access Works

1
Auditor Key Assignment

A single Grumpkin private key is designated as the auditor key when the NixPool is deployed. The corresponding public key is stored on-chain.

2
ECIES Encryption

Transaction data is ECIES-encrypted to the auditor's Grumpkin public key during each deposit and transfer.

3
Compliance Decryption

The auditor uses their Grumpkin private key to decrypt note data, revealing amounts and ownership for compliance reporting.

Implementation

typescript
import { NixAuditorClient } from '@nixprotocol/nix-wallet';

// Initialize auditor client with the designated Grumpkin private key
const auditorClient = new NixAuditorClient({
  auditorPrivateKey: process.env.AUDITOR_GRUMPKIN_KEY,
  poolAddress: '0x94e5f37c557dA9B2C0F0636BbCDB3743B30Ba6B1', // NixPool on Fuji
});

// Decrypt ECIES-encrypted note data from on-chain events
const decryptedNotes = await auditorClient.decryptPoolNotes({
  fromBlock: 1000000,
  toBlock: 'latest',
});

// Generate compliance report from decrypted data
const report = await auditorClient.generateComplianceReport({
  notes: decryptedNotes,
  fromDate: '2025-01-01',
  toDate: '2025-12-31',
});

What the Auditor Can See

Note Amounts

Decrypt UTXO note values from ECIES-encrypted on-chain data

Note Ownership

Identify note owners via decrypted Grumpkin public keys

Transaction Flow

Trace deposit, transfer, and withdrawal activity across the pool

Regulatory Compatible: Designed for institutions requiring audit trails, tax reporting, and regulatory compliance while maintaining user privacy from public view.

SDK Overview

The @nixprotocol/nix-wallet package is the main entry point for most integrations.

Exports

deriveNixKeyFromSignature(walletClient) - Deterministic keypair from wallet signature via Poseidon2
generateNixKeyPair() - Random Grumpkin keypair
ecies.encrypt(data, pubKey) - ECIES encrypt to Grumpkin key
ecies.decrypt(ciphertext, privKey) - Decrypt ECIES ciphertext
poseidon2.hash(inputs) - Poseidon2 hash function
createNoteCommitment(value, pubKey, salt) - Create UTXO note commitment

Key Generation

Generate Grumpkin keypairs for encryption and proof generation. Keys are derived deterministically from EVM wallet signatures using Poseidon2 hashing, ensuring the same wallet always produces the same NixProtocol private key.

Important: Use deterministic key derivation from wallet signatures in production. Random keypairs require secure private key storage.

ECIES Encryption

ECIES encryption on Grumpkin curve for encrypting note data and auditor records.

typescript
import { ecies } from '@nixprotocol/nix-wallet';

// ECIES ciphertext structure
interface ECIESCiphertext {
  authKeyX: bigint;    // Ephemeral Grumpkin point x
  authKeyY: bigint;    // Ephemeral Grumpkin point y
  encrypted: bigint;   // Encrypted value
}

// Encrypt note data to recipient's Grumpkin public key
const randomness = BigInt('0x' + crypto.randomUUID().replace(/-/g, ''));
const encrypted = ecies.encrypt(value, randomness, recipientPublicKey);

// Decrypt (only recipient can do this)
const decrypted = ecies.decrypt(privateKey, encrypted.authKeyX, encrypted.authKeyY, encrypted.encrypted);

// Auditor can also decrypt with auditor key
const auditValue = ecies.decrypt(auditorPrivateKey, auditCt.authKeyX, auditCt.authKeyY, auditCt.encrypted);

Nix ID

Nix ID is a shareable payment identifier that encodes your Grumpkin public key commitment and coordinates for receiving UTXO notes in the privacy pool.

NixAddress format:

nix:<commitment>:<pk.x>:<pk.y>

Commitment is Poseidon2(pk.x, pk.y), ensuring address integrity is self-verifiable.

Smart Contracts Overview

NixProtocol smart contracts handle on-chain privacy operations including UTXO note management and ZK proof verification.

NixPool

Main privacy pool contract: deposits, transfers, withdrawals with UTXO notes

Merkle Trees

Poseidon2-based Merkle trees (depth 20) for note commitment storage

UltraHonk Verifier

On-chain proof verification for all 3 circuits

NixPool Contract

Main privacy pool contract for UTXO-based private payments.

solidity
// Register Grumpkin public key with ZK proof
function register(
    bytes calldata proof,
    bytes32 publicKeyHash
) external;

// Deposit ERC-20 tokens, creating a UTXO note
function deposit(
    address token,
    uint256 amount,
    bytes calldata proof,
    bytes32 noteCommitment
) external;

// Transact: consume 2 input notes, create 2 output notes
function transact(
    bytes calldata proof,
    bytes32[2] calldata nullifiers,
    bytes32[2] calldata newCommitments,
    bytes calldata encryptedNotes
) external;

NixPool

Merkle tree storage for UTXO note commitments.

solidity
// Merkle tree stores note commitments
function getMerkleRoot() external view returns (bytes32);

// Check root history (last 100 roots valid)
function isKnownRoot(bytes32 root) external view returns (bool);

// Check if nullifier has been used
function isSpent(bytes32 nullifier) external view returns (bool);

// Get current note index
function nextIndex() external view returns (uint256);

Deployed Addresses

Contract deployment coming soon.

Reference implementation. Security audit in progress. Do not deploy with significant value until audit complete.

Private ERC-20 Guide

Add UTXO-based private payments to any ERC-20 token in 5 steps.

  1. 1Deploy NixPool contract for your ERC-20 token
  2. 2Users register with Grumpkin public key
  3. 3Deposit converts public tokens to UTXO notes
  4. 4Transact consumes input notes, creates output notes
  5. 5Withdraw burns notes and releases public tokens

Complete Integration Example

typescript
import { createWalletClient, custom } from 'viem';
import {
  deriveNixKeyFromSignature,
  generateRegistrationProof,
  generateDepositProof,
  generateTransactProof,
  ecies,
  poseidon2,
  createNoteCommitment
} from '@nixprotocol/nix-wallet';
import { NixPool__factory } from '@nixprotocol/contracts';

// Setup
const walletClient = createWalletClient({
  transport: custom(window.ethereum),
  account: userAddress,
});

// Step 1: Derive keypair from wallet signature (deterministic via Poseidon2)
// Signs "NixProtocol key derivation v3", splits sig into 3 chunks,
// then Poseidon2-hashes them to produce a valid Grumpkin scalar
const keyPair = await deriveNixKeyFromSignature(walletClient);

// Step 2: Register (one-time per address)
const isRegistered = await nixPool.isRegistered(userAddress);
if (!isRegistered) {
  const regProof = await generateRegistrationProof(keyPair);
  const regTx = await nixPool.register(regProof, keyPair.commitment);
  await regTx.wait();
}

// Step 3: Deposit - convert public tokens to UTXO note
const USDC_ADDRESS = '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48';
const amount = 100_000000n; // 100 USDC (6 decimals)

// First approve
await usdc.approve(NIX_POOL_ADDRESS, amount);

// Create note commitment and deposit proof
const salt = crypto.getRandomValues(new Uint8Array(32));
const noteCommitment = createNoteCommitment(amount, keyPair.publicKey, salt);
const depositProof = await generateDepositProof({ keyPair, amount, salt });
const depositTx = await nixPool.deposit(USDC_ADDRESS, amount, depositProof, noteCommitment);
await depositTx.wait();

// Step 4: Transact - consume input notes, create output notes
const transactProof = await generateTransactProof({
  inputNotes: [myNote1, myNote2],
  outputNotes: [recipientNote, changeNote],
  merkleRoot: await nixPool.getMerkleRoot(),
  senderKeyPair: keyPair,
});

const encryptedNotes = ecies.encrypt(outputNoteData, recipientPublicKey);
const transactTx = await nixPool.transact(
  transactProof.proof,
  transactProof.nullifiers,
  transactProof.newCommitments,
  encryptedNotes
);
await transactTx.wait();
console.log('Private transaction complete!');

Relayer Transactions

Customize how gas is handled for your users. Users generate ZK proofs, the relayer submits transactions on-chain with configurable gas fees. No signatures needed for authorization. Supports ERC-2612 Permit for gasless token approvals.

How It Works

1
User creates transaction
2
Generates ZK proof
3
Relayer submits proof on-chain
4
Fee deducted from output note

Basic Usage

typescript
import { NixRelayer } from '@nixprotocol/relayer';

const relayer = new NixRelayer({
  url: 'https://relayer.nixprotocol.com',
  chainId: 43113, // Avalanche Fuji testnet
});

// User signs, relayer pays gas
const result = await relayer.sendPrivateTransfer({
  signer: userSigner,           // User's wallet (MetaMask, etc.)
  token: USDC_ADDRESS,
  recipient: recipientAddress,
  proof: transferProof,
  feeToken: USDC_ADDRESS,       // Pay fee in USDC
  maxFee: ethers.parseUnits('1', 6), // Max 1 USDC fee
});

console.log('Tx hash:', result.txHash);

Self-Hosted Relayer

typescript
// server.ts - Your own relayer backend
import { NixRelayerServer } from '@nixprotocol/relayer/server';

const relayer = new NixRelayerServer({
  privateKey: process.env.RELAYER_PRIVATE_KEY,
  rpcUrl: process.env.RPC_URL,
  supportedTokens: [USDC, USDT, DAI],
  feePercentage: 0.1, // 0.1% fee
});

// Validate and execute meta-transactions
app.post('/relay', async (req, res) => {
  const { signedRequest } = req.body;

  // Verify signature and fee payment
  const isValid = await relayer.verifyRequest(signedRequest);
  if (!isValid) return res.status(400).json({ error: 'Invalid request' });

  // Execute on-chain
  const result = await relayer.execute(signedRequest);
  res.json({ txHash: result.hash });
});

Fee Structure

OperationGas Cost~Fee (L2)
Private Transfer~3M gas~$0.15-1.50
Withdraw~3M gas~$0.15-1.50
Register~2.8M gas~$0.14-1.40

* Estimated fees on L2 chains (Avalanche, Base, Arbitrum). Currently deployed on Avalanche Fuji and Base Sepolia testnets.

Integration Paths

Choose the right approach based on your privacy requirements.

Basic1-2 hours

Add Privacy to ERC-20

Deposit any ERC-20 into the UTXO privacy pool. Amounts hidden with unlinkable transfers.

Intermediate2-4 hours

Relayer UX

Users generate proofs, relayer submits with customizable gas fees. Better UX with flexible gas management.

Advanced1-2 days

Full Privacy Payments

Complete anonymity with unlinkable withdrawals using NixPool dark pool pattern.

Error Handling

Common errors and how to handle them in your integration.

Error CodeCauseSolution
INVALID_PROOFZK proof verification failedRegenerate proof with correct inputs
INSUFFICIENT_BALANCEUTXO note value less than transfer amountCheck decrypted balance before transfer
NOT_REGISTEREDUser hasn't registered their Grumpkin public keyCall register() with valid proof first
NULLIFIER_USEDAttempting to spend already-spent noteUse fresh commitment for new transaction
INVALID_MERKLE_ROOTMerkle root doesn't match on-chain stateFetch latest root before proof generation
DECRYPTION_FAILEDWrong private key or corrupted ciphertextVerify keypair matches registered public key

Handling Errors

typescript
import { NixError, NixErrorCode } from '@nixprotocol/nix-wallet';

try {
  const tx = await nixPool.privateTransfer(recipient, proof);
  await tx.wait();
} catch (error) {
  if (error instanceof NixError) {
    switch (error.code) {
      case NixErrorCode.INVALID_PROOF:
        // Regenerate proof with fresh randomness
        const newProof = await generateTransferProof(/*...*/);
        break;
      case NixErrorCode.INSUFFICIENT_BALANCE:
        // Show user their actual balance
        const balance = await getDecryptedBalance();
        throw new Error(`Insufficient balance: ${balance}`);
      default:
        console.error('Nix error:', error.message);
    }
  }
  throw error;
}

Debugging Tips

Enable Debug Mode

Set NIX_DEBUG=true to log proof inputs and circuit constraints.

Verify Keys Match

Most errors stem from keypair mismatch. Always verify public key matches on-chain registration.

Check Gas Limits

UltraHonk proof verification is gas-heavy (~3M gas). Ensure transactions have sufficient gas limit.

Security Best Practices

Critical security considerations for production deployments.

Key Management

Never Store Private Keys in LocalStorage

Use deterministic key derivation from wallet signatures. Keys should be derived on-demand, not persisted.

Do: Derive Keys from Signatures

Use deriveNixKeyFromSignature(walletClient) to deterministically derive a Grumpkin keypair from a wallet signature via Poseidon2 hashing.

Proof Generation

Use Fresh Randomness

Every proof must use cryptographically random values. Reusing randomness leaks private information.

Client-Side Only

Generate proofs in browser/client. Never send private keys or amounts to backend servers.

Verify Before Submit

Always verify proofs locally before submitting to chain to save gas on invalid proofs.

Timing Attacks

Add random delays between operations to prevent transaction graph analysis.

Smart Contract Security

  • Verify Circuit Hash: Ensure deployed UltraHonk verifier matches compiled Noir circuit
  • Check Nullifier Storage: Nullifiers must be stored permanently to prevent double-spend
  • Merkle Tree Depth: Use sufficient depth (20+) to support expected user base
  • Reentrancy Guards: All state-changing functions must be protected
Security Audit Status

Reference implementation is undergoing security audit. Do not deploy with significant value until audit is complete. Contact us for enterprise deployment security review.

Full API Reference

Complete function signatures and type definitions.

@nixprotocol/nix-wallet

typescript
// Key Generation
function deriveNixKeyFromSignature(walletClient: WalletClient): Promise<NixKeyPair>;
function generateNixKeyPair(): NixKeyPair;
function importNixKey(privateKeyHex: string): NixKeyPair;
function exportNixKey(keyPair: NixKeyPair): string;

interface Point {
  x: bigint;
  y: bigint;
}

interface NixKeyPair {
  privateKey: bigint;       // Grumpkin scalar
  publicKey: Point;         // Grumpkin curve point
  commitment: bigint;       // Poseidon2(pk.x, pk.y)
}

// ECIES Encryption
namespace ecies {
  function encrypt(value: bigint, randomness: bigint, receiverPk: Point): ECIESCiphertext;
  function decrypt(sk: bigint, authKeyX: bigint, authKeyY: bigint, encrypted: bigint): bigint;
}

interface ECIESCiphertext {
  authKeyX: bigint;
  authKeyY: bigint;
  encrypted: bigint;
}

// Poseidon2 Hash
function poseidon2Hash(inputs: bigint[]): bigint;
function computeCommitment(pkx: bigint, pky: bigint): bigint;

// Note Commitments
function createNoteCommitment(value: bigint, pubKey: Point, salt: bigint): bigint;

// Proof Generation
function generateRegistrationProof(keyPair: NixKeyPair): Promise<ProofResult>;
function generateDepositProof(params: DepositParams): Promise<ProofResult>;
function generateTransactProof(params: TransactParams): Promise<ProofResult>;

interface ProofResult {
  proof: `0x${string}`;        // UltraHonk proof bytes
  publicInputs: `0x${string}`[];
}

NixAddress & NixWallet

typescript
// NixAddress encoding/decoding
function encodeNixAddress(data: NixAddressData): string;
function decodeNixAddress(nixAddress: string): NixAddressData;
function nixAddressFromKeyPair(keyPair: NixKeyPair): string;

interface NixAddressData {
  commitment: bigint;           // Poseidon2(pk.x, pk.y)
  publicKey: Point;             // Grumpkin curve point
}
// Format: "nix:<commitment>:<pk.x>:<pk.y>"

// NixWallet class - high-level wallet interface
class NixWallet {
  static create(config: NixWalletConfig): Promise<NixWallet>;
  register(): Promise<void>;
  deposit(token: Address, amount: bigint): Promise<void>;
  transfer(recipientNixAddress: string, amount: bigint): Promise<void>;
  withdraw(token: Address, amount: bigint, to: Address): Promise<void>;
  syncBalances(): Promise<void>;
  exportPrivateKey(): string;
}

Contract ABIs

solidity
interface INixPool {
  function register(bytes calldata proof, bytes32 publicKeyHash) external;
  function deposit(
    address token,
    uint256 amount,
    bytes calldata proof,
    bytes32 noteCommitment
  ) external;
  function transact(
    bytes calldata proof,
    bytes32[2] calldata nullifiers,
    bytes32[2] calldata newCommitments,
    bytes calldata encryptedNotes
  ) external;
  function withdraw(
    bytes calldata proof,
    bytes32 nullifier,
    address token,
    uint256 amount,
    address recipient
  ) external;

  function getMerkleRoot() external view returns (bytes32);
  function isKnownRoot(bytes32 root) external view returns (bool);
  function isSpent(bytes32 nullifier) external view returns (bool);
  function isRegistered(address user) external view returns (bool);
  function nextIndex() external view returns (uint256);
}

poseidon2-go

A pure Go implementation of the Poseidon2 hash function over the BN254 scalar field. Designed for off-chain computation that must match on-chain or in-circuit Poseidon2 outputs.

Installation

bash
go get github.com/nixprotocol/poseidon2-go

Parameters

ParameterValue
FieldBN254 scalar field (Fr)
State width4
Rate3
Capacity1
Full rounds8
Partial rounds56
S-boxx⁵

Exported Functions

go
func Hash(inputs []fr.Element) fr.Element
func Hash2(a, b fr.Element) fr.Element
func HashToBytes(inputs []fr.Element) [32]byte
func Permute(s *[4]fr.Element)
Hash

Variable-length hash. Absorbs inputs in rate-3 chunks via the sponge, returns one field element.

Hash2

Two-input hash. Convenience wrapper equivalent to Hash([]fr.Element{a, b}}).

HashToBytes

Same as Hash but returns the result as a 32-byte big-endian array.

Permute

In-place Poseidon2 permutation on a 4-element state. Used internally by Hash; exposed for advanced sponge constructions.

Sponge Construction

Hash and HashToBytes use a sponge that absorbs inputs into state slots [1..3] (rate = 3), leaving slot 0 as capacity. After each rate-sized chunk the full permutation runs. The output is state[0] after final absorption and one more permutation.

Usage

go
package main

import (
    "fmt"
    poseidon2 "github.com/nixprotocol/poseidon2-go"
    "github.com/consensys/gnark-crypto/ecc/bn254/fr"
)

func main() {
    var a, b fr.Element
    a.SetUint64(1)
    b.SetUint64(2)

    h := poseidon2.Hash2(a, b)
    fmt.Println("hash:", h.String())

    raw := poseidon2.HashToBytes([]fr.Element{a, b})
    fmt.Printf("bytes: %x\n", raw)
}

The round constants and MDS matrix match the Noir and Solidity implementations used in NixProtocol, so hashes computed with this library will produce identical outputs for the same inputs.

MCP Server

MCP_SERVER

Model Context Protocol server that wraps the NixProtocol SDK for AI agent integration. Plug it into Claude, or any MCP-compatible agent, to enable private payments through natural language.

Setup

bash
npm install @nixprotocol/mcp-server

# Add to your MCP config (e.g. claude_desktop_config.json)
{
  "mcpServers": {
    "nixprotocol": {
      "command": "npx",
      "args": ["@nixprotocol/mcp-server"],
      "env": {
        "NIX_RPC_URL": "https://api.avax-test.network/ext/bc/C/rpc",
        "NIX_CONTRACT_ADDRESS": "0x94e5...",
        "NIX_CHAIN_ID": "43113",
        "NIX_CIRCUIT_PATH": "./circuits",
        "NIX_RELAYER_URL": "https://relayer.nixprotocol.com"
      }
    }
  }
}

Available Tools

nix_init_wallet - Initialize prover, clients, and optionally import a private key
nix_get_address - Return the NixAddress for the current keypair
nix_get_balance - Scan notes and return token balance
nix_get_history - List all notes with spent/unspent status
nix_register - One-time identity registration with the pool
nix_deposit - Deposit ERC-20 tokens into the privacy pool
nix_transfer - Private transfer to a NixAddress recipient
nix_withdraw - Withdraw from the pool to an EVM address
nix_get_fee - Query the relayer for current fee info
nix_decode_address - Parse and validate a NixAddress

Environment Variables

VariableDescription
NIX_RPC_URLRPC endpoint for the target chain
NIX_CONTRACT_ADDRESSDeployed NixPool address
NIX_CHAIN_IDTarget chain ID
NIX_EVM_PRIVATE_KEYEVM key for gas (deposits only)
NIX_PRIVATE_KEYNix private key (optional, can derive from wallet)
NIX_CIRCUIT_PATHPath to compiled circuit artifacts
NIX_RELAYER_URLRelayer endpoint for gasless transactions

Relayer API Reference

RELAYER_API

The relayer submits transactions on behalf of users so they never need to hold native gas tokens. It calculates fees dynamically using Chainlink price feeds and deducts them from the privacy pool itself.

Endpoints

GET /:chainId/relay/fee-info?token=<address>

Returns the current fee quote for a given token, including gas cost estimate, relayer margin, and the relayer's public key for encrypting notes.

json
// Response
{
  "feeAmount": "500000",
  "feeToken": "0x5425...",
  "gasEstimate": "350000",
  "relayerPublicKey": { "x": "0x...", "y": "0x..." }
}
POST /:chainId/relay/transact

Submit a transact proof (transfer or withdrawal). The relayer verifies the proof, checks the fee output covers gas costs, and submits on-chain.

json
// Request body
{
  "proof": "0x...",
  "publicInputs": ["0x...", ...],
  "encryptedNotes": "0x..."
}
POST /:chainId/relay/register

Submit a registration proof. The relayer submits the identity commitment to the registration Merkle tree.

Fee Calculation

Fees are calculated as: (estimated gas * gas price * token price) + margin. Default margin is 30% with a $0.50 minimum. Token prices are fetched from Chainlink oracles.

Rate Limiting

The relayer enforces rate limiting per IP address. Default: 20 requests per minute.

Self-Hosting

bash
# Clone and run your own relayer
npm install @nixprotocol/relayer

# Configure environment
export PRIVATE_KEY="0x..."
export RPC_URL="https://api.avax-test.network/ext/bc/C/rpc"
export PORT=3001

# Start
npx nix-relayer

Auditor Tools

AUDITOR

The auditor holds a single Grumpkin private key set at NixPool deployment. With this key, the auditor can decrypt all transaction data for compliance reporting without affecting user privacy from public view.

What the Auditor Can Decrypt

Deposit amounts and recipient commitments
Transfer amounts for both inputs and outputs
Recipient commitments (who received funds)
Registration data (EVM address and public key of each user)

SDK Functions

decryptAuditorNote(ciphertext, auditorPrivKey) - Decrypt a note's amount and blinding factor
decryptRecipientCommitment(ciphertext, auditorPrivKey) - Decrypt who received a note
parseDepositCalldata(txData) - Extract deposit proof public inputs from calldata
parseTransactCalldata(txData) - Extract transact proof public inputs from calldata
typescript
import { decryptAuditorNote, decryptRecipientCommitment } from '@nixprotocol/sdk';

// Auditor decrypts a deposit note
const note = decryptAuditorNote(
  encryptedCiphertext,
  auditorPrivateKey
);
console.log('Amount:', note.amount);
console.log('Blinding:', note.blinding);

// Auditor decrypts recipient identity
const recipient = decryptRecipientCommitment(
  recipientCiphertext,
  auditorPrivateKey
);

NixPay Auditor Page

NixPay includes a built-in auditor interface at /auditor where the auditor can paste their private key to decrypt and inspect all pool transactions.

Pool Client

POOL_CLIENT

PoolClient wraps the NixPool smart contract for deposits, transfers, withdrawals, and on-chain queries. It handles ABI encoding, Merkle root lookups, and event fetching.

Key Methods

deposit(proof, noteHash, ...) - Submit a deposit transaction
transact(proof, nullifiers, ...) - Submit a transfer or withdrawal
register(proof, commitment) - Register identity commitment
getNotes(fromBlock?) - Fetch NoteCreated events from the pool
getMerkleRoot() - Get current Merkle root
isSpent(nullifier) - Check if a nullifier has been spent

Usage

typescript
import { PoolClient } from '@nixprotocol/sdk';
import { createPublicClient, http } from 'viem';
import { avalancheFuji } from 'viem/chains';

const publicClient = createPublicClient({
  chain: avalancheFuji,
  transport: http(),
});

const pool = new PoolClient({
  contractAddress: '0x94e5...',
  publicClient,
});

// Fetch all notes from the pool
const notes = await pool.getNotes();

// Check if a nullifier has been spent
const spent = await pool.isSpent(nullifierHash);

Note Manager

NOTE_MGR

NoteManager handles off-chain tracking of UTXO notes. It scans NoteCreated events, trial-decrypts each note with the user's private key, and maintains a local cache of owned notes in IndexedDB (browser) or in-memory (Node.js).

Key Methods

scan() - Scan for new notes and update local cache
getUnspentNotes() - Return all unspent notes owned by this key
getBalance() - Sum of all unspent note amounts
markSpent(nullifier) - Mark a note as spent after a successful transaction

Caching

NoteManager caches events in IndexedDB (key: nixpay-v2-events) to avoid re-scanning the full chain on every load. In Node.js environments, it falls back to in-memory storage.

typescript
import { NoteManager } from '@nixprotocol/sdk';

const noteManager = new NoteManager({
  poolClient,
  privateKey: nixPrivateKey,
});

// Scan for new notes (uses IndexedDB cache)
await noteManager.scan();

// Get spendable balance
const balance = noteManager.getBalance();
console.log('Balance:', balance.toString());

// Get unspent notes for building transactions
const notes = noteManager.getUnspentNotes();
支援

Need Help Integrating?

We offer custom development for complex privacy requirements.

暗号化|ZK_PROOFS|PRIVACY|プライバシー