Skip to main content

light_nullifier_program/
sdk.rs

1//! SDK for building nullifier instructions.
2//!
3//! Provides helpers for fetching validity proofs and building instructions
4//! that work with any `Rpc`-compatible client (LightProgramTest or LightClient).
5
6use light_program_test::{AddressWithTree, Indexer, Rpc, RpcError};
7use light_sdk::{
8    address::v2::derive_address,
9    instruction::{PackedAccounts, PackedAddressTreeInfo, SystemAccountMetaConfig, ValidityProof},
10};
11use solana_sdk::{
12    instruction::{AccountMeta, Instruction},
13    pubkey::Pubkey,
14};
15
16/// The deployed program ID.
17pub const PROGRAM_ID: Pubkey = crate::ID;
18
19/// Returns the address tree pubkey (static for v2).
20pub fn address_tree() -> Pubkey {
21    Pubkey::new_from_array(light_sdk::constants::ADDRESS_TREE_V2)
22}
23
24/// Output queue pubkey (V2 batch queue 5: oq5oh5ZR3yGomuQgFduNDzjtGvVWfDRGLuDVjv9a96P).
25pub fn output_queue() -> Pubkey {
26    solana_sdk::pubkey!("oq5oh5ZR3yGomuQgFduNDzjtGvVWfDRGLuDVjv9a96P")
27}
28
29/// Derives the nullifier address for a given ID.
30pub fn derive_nullifier_address(id: &[u8; 32]) -> [u8; 32] {
31    let (address, _) = derive_address(&[b"nullifier", id], &address_tree(), &PROGRAM_ID);
32    address
33}
34
35/// Result from fetching validity proof - contains all data needed to build the instruction.
36pub struct ProofResult {
37    pub proof: ValidityProof,
38    pub address_tree_info: PackedAddressTreeInfo,
39    pub output_state_tree_index: u8,
40    pub remaining_accounts: Vec<AccountMeta>,
41}
42
43/// Fetches validity proof and packs accounts for a nullifier creation.
44///
45/// Works with any `Rpc + Indexer` client (LightProgramTest or LightClient).
46pub async fn fetch_proof<R: Rpc + Indexer>(rpc: &mut R, id: &[u8; 32]) -> Result<ProofResult, RpcError> {
47    let address = derive_nullifier_address(id);
48    let tree = address_tree();
49
50    let config = SystemAccountMetaConfig::new(PROGRAM_ID);
51    let mut packed = PackedAccounts::default();
52    packed.add_system_accounts_v2(config)?;
53
54    let rpc_result = rpc
55        .get_validity_proof(vec![], vec![AddressWithTree { address, tree }], None)
56        .await?
57        .value;
58
59    let tree_infos = rpc_result.pack_tree_infos(&mut packed);
60
61    // Hardcode output queue (oq5) - index 1 in packed accounts after address tree
62    let output_state_tree_index = packed.insert_or_get(output_queue());
63
64    let (remaining_accounts, _, _) = packed.to_account_metas();
65
66    Ok(ProofResult {
67        proof: rpc_result.proof,
68        address_tree_info: tree_infos.address_trees[0],
69        output_state_tree_index,
70        remaining_accounts,
71    })
72}
73
74/// Builds the create_nullifier instruction from proof data.
75///
76/// This is sync and requires no RPC calls.
77pub fn build_instruction(payer: Pubkey, id: [u8; 32], proof_result: ProofResult) -> Instruction {
78    use anchor_lang::InstructionData;
79
80    let data = crate::instruction::CreateNullifier {
81        proof: proof_result.proof,
82        address_tree_info: proof_result.address_tree_info,
83        output_state_tree_index: proof_result.output_state_tree_index,
84        id,
85    }
86    .data();
87
88    let mut accounts = vec![AccountMeta::new(payer, true)];
89    accounts.extend(proof_result.remaining_accounts);
90
91    Instruction {
92        program_id: PROGRAM_ID,
93        accounts,
94        data,
95    }
96}
97
98/// Creates a nullifier instruction in one call.
99///
100/// Combines `fetch_proof` and `build_instruction` for convenience.
101/// Works with any `Rpc + Indexer` client (LightProgramTest or LightClient).
102pub async fn create_nullifier_ix<R: Rpc + Indexer>(
103    rpc: &mut R,
104    payer: Pubkey,
105    id: [u8; 32],
106) -> Result<Instruction, RpcError> {
107    let proof_result = fetch_proof(rpc, &id).await?;
108    Ok(build_instruction(payer, id, proof_result))
109}