Skip to main content
The nullifier program utility solves this for you.
  1. It derives PDA from ["nullifier", id] seeds (where id is your unique identifier, e.g. a nonce, uuid, hash of signature, etc.)
  2. Creates an empty rent-free PDA at that address
  3. If the address exists, the whole transaction fails
  4. Prepend or append this instruction to your transaction.
We also deployed a reference implementation to public networks so you can get started quickly:
Cost per nullifier~15,000 lamports (~0.000015 SOL)
Program IDNFLx5WGPrTHHvdRNsidcrNcLxRruMC92E4yv7zhZBoT
NetworksMainnet, Devnet
Source codegithub.com/Lightprotocol/nullifier-program
Example TxSolana Explorer
Example source code: TypeScript | Rust

Dependencies

[dependencies]
light-nullifier-program = "0.1.2"
light-client = "0.23.0"

Using the Helper

use light_nullifier_program::sdk::{create_nullifier_ix, PROGRAM_ID};
use light_client::{LightClient, LightClientConfig};
use solana_sdk::{system_instruction, transaction::Transaction};

let mut rpc = LightClient::new(
    LightClientConfig::new("https://mainnet.helius-rpc.com/?api-key=...")
).await?;

// Create a unique 32-byte ID
let id: [u8; 32] = /* hash of payment inputs or random */;

// Build nullifier instruction
let nullifier_ix = create_nullifier_ix(&mut rpc, payer.pubkey(), id).await?;

// Combine with your transaction
let transfer_ix = system_instruction::transfer(&payer.pubkey(), &recipient, 1_000_000);
let tx = Transaction::new_signed_with_payer(
    &[nullifier_ix, transfer_ix],
    Some(&payer.pubkey()),
    &[&payer],
    recent_blockhash,
);

Manually Fetching Proof

use light_nullifier_program::sdk::{fetch_proof, build_instruction};

// Step 1: Fetch proof
let proof_result = fetch_proof(&mut rpc, &id).await?;

// Step 2: Build instruction
let nullifier_ix = build_instruction(payer.pubkey(), id, proof_result);

// Add to transaction
let tx = Transaction::new_signed_with_payer(
    &[nullifier_ix, transfer_ix],
    Some(&payer.pubkey()),
    &[&payer],
    recent_blockhash,
);

Check If Nullifier Exists

use light_nullifier_program::sdk::derive_nullifier_address;

let address = derive_nullifier_address(&id);
let account = rpc.get_compressed_account(None, Some(address)).await?;
let exists = account.value.is_some();

Note that this is a reference implementation. Feel free to fork the program as you see fit.
Questions or need support? Telegram | email | Discord