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/// Derives the nullifier address for a given ID.
25pub fn derive_nullifier_address(id: &[u8; 32]) -> [u8; 32] {
26    let (address, _) = derive_address(&[b"nullifier", id], &address_tree(), &PROGRAM_ID);
27    address
28}
29
30/// Result from fetching validity proof - contains all data needed to build the instruction.
31pub struct ProofResult {
32    pub proof: ValidityProof,
33    pub address_tree_info: PackedAddressTreeInfo,
34    pub output_state_tree_index: u8,
35    pub remaining_accounts: Vec<AccountMeta>,
36}
37
38/// Fetches validity proof and packs accounts for a nullifier creation.
39///
40/// Works with any `Rpc + Indexer` client (LightProgramTest or LightClient).
41pub async fn fetch_proof<R: Rpc + Indexer>(rpc: &mut R, id: &[u8; 32]) -> Result<ProofResult, RpcError> {
42    let address = derive_nullifier_address(id);
43    let tree = address_tree();
44
45    let config = SystemAccountMetaConfig::new(PROGRAM_ID);
46    let mut packed = PackedAccounts::default();
47    packed.add_system_accounts_v2(config)?;
48
49    let rpc_result = rpc
50        .get_validity_proof(vec![], vec![AddressWithTree { address, tree }], None)
51        .await?
52        .value;
53
54    let tree_infos = rpc_result.pack_tree_infos(&mut packed);
55
56    let output_state_tree_index = rpc
57        .get_random_state_tree_info()?
58        .pack_output_tree_index(&mut packed)?;
59
60    let (remaining_accounts, _, _) = packed.to_account_metas();
61
62    Ok(ProofResult {
63        proof: rpc_result.proof,
64        address_tree_info: tree_infos.address_trees[0],
65        output_state_tree_index,
66        remaining_accounts,
67    })
68}
69
70/// Builds the create_nullifier instruction from proof data.
71///
72/// This is sync and requires no RPC calls.
73pub fn build_instruction(payer: Pubkey, id: [u8; 32], proof_result: ProofResult) -> Instruction {
74    use anchor_lang::InstructionData;
75
76    let data = crate::instruction::CreateNullifier {
77        proof: proof_result.proof,
78        address_tree_info: proof_result.address_tree_info,
79        output_state_tree_index: proof_result.output_state_tree_index,
80        id,
81    }
82    .data();
83
84    let mut accounts = vec![AccountMeta::new(payer, true)];
85    accounts.extend(proof_result.remaining_accounts);
86
87    Instruction {
88        program_id: PROGRAM_ID,
89        accounts,
90        data,
91    }
92}
93
94/// Creates a nullifier instruction in one call.
95///
96/// Combines `fetch_proof` and `build_instruction` for convenience.
97/// Works with any `Rpc + Indexer` client (LightProgramTest or LightClient).
98pub async fn create_nullifier_ix<R: Rpc + Indexer>(
99    rpc: &mut R,
100    payer: Pubkey,
101    id: [u8; 32],
102) -> Result<Instruction, RpcError> {
103    let proof_result = fetch_proof(rpc, &id).await?;
104    Ok(build_instruction(payer, id, proof_result))
105}