1- //! Example: Create a nullifier PDA to prevent duplicate actions.
2- //!
3- //! This example uses LightProgramTest for local testing.
4- //! For devnet, replace LightProgramTest with LightClient (see comments below).
1+ //! Example: Create a nullifier PDA to prevent duplicate actions on devnet.
52//!
63//! Run with: cargo run --example action_create_nullifier
4+ //!
5+ //! Requires:
6+ //! - API_KEY in .env file (Helius API key)
7+ //! - Funded keypair at ~/.config/solana/id.json
78
8- use light_client:: rpc:: Rpc ;
9+ use dotenv:: dotenv;
10+ use light_client:: rpc:: { LightClient , LightClientConfig , Rpc } ;
911use light_nullifier_program:: sdk:: { create_nullifier_ix, derive_nullifier_address, PROGRAM_ID } ;
10- use light_program_test:: { LightProgramTest , ProgramTestConfig } ;
11- use solana_sdk:: { signature:: Keypair , signer:: Signer } ;
12- use solana_system_interface:: instruction as system_instruction;
13-
14- // For devnet usage with LightClient:
15- // ```rust
16- // use light_client::rpc::{LightClient, LightClientConfig};
17- // let api_key = std::env::var("API_KEY").expect("API_KEY required");
18- // let config = LightClientConfig::new(
19- // format!("https://devnet.helius-rpc.com/?api-key={}", api_key),
20- // None,
21- // Some(api_key),
22- // );
23- // let mut rpc = LightClient::new(config).await?;
24- // ```
12+ use solana_sdk:: {
13+ signature:: read_keypair_file,
14+ signer:: Signer ,
15+ } ;
16+ use std:: env;
2517
2618#[ tokio:: main]
2719async fn main ( ) -> Result < ( ) , Box < dyn std:: error:: Error > > {
20+ dotenv ( ) . ok ( ) ;
21+
2822 println ! ( "Nullifier Program ID: {}" , PROGRAM_ID ) ;
2923
30- // Setup local test environment
31- // For devnet: use LightClient instead (see comments above)
32- let mut rpc = LightProgramTest :: new ( ProgramTestConfig :: new_v2 ( true , None ) ) . await ?;
33- let payer = rpc. get_payer ( ) . insecure_clone ( ) ;
24+ // Load API key from .env
25+ let api_key = env:: var ( "API_KEY" ) . expect ( "API_KEY required in .env" ) ;
26+ let rpc_url = format ! ( "https://devnet.helius-rpc.com/?api-key={}" , api_key) ;
27+ let photon_url = "https://devnet.helius-rpc.com" . to_string ( ) ;
28+
29+ // Setup LightClient for devnet
30+ // RPC URL includes api-key, Photon URL is base URL (api_key added separately)
31+ let config = LightClientConfig :: new ( rpc_url, Some ( photon_url) , Some ( api_key) ) ;
32+ let mut rpc = LightClient :: new ( config) . await ?;
33+
34+ // Load keypair from default Solana CLI location
35+ let keypair_path =
36+ shellexpand:: tilde ( "~/.config/solana/id.json" ) . to_string ( ) ;
37+ let payer = read_keypair_file ( & keypair_path)
38+ . map_err ( |e| format ! ( "Failed to read keypair from {}: {}" , keypair_path, e) ) ?;
39+ rpc. payer = payer. insecure_clone ( ) ;
3440
3541 println ! ( "Payer: {}" , payer. pubkey( ) ) ;
3642
43+ // Check balance
44+ let balance = rpc. get_balance ( & payer. pubkey ( ) ) . await ?;
45+ println ! ( "Balance: {} SOL" , balance as f64 / 1_000_000_000.0 ) ;
46+ if balance < 10_000_000 {
47+ return Err ( "Insufficient balance. Need at least 0.01 SOL on devnet" . into ( ) ) ;
48+ }
49+
3750 // Create a unique 32-byte ID (e.g., hash of payment inputs)
3851 let id: [ u8 ; 32 ] = rand:: random ( ) ;
39- println ! ( "Nullifier ID: {:? }" , & id[ .. 8 ] ) ;
52+ println ! ( "Nullifier ID: {}" , bs58 :: encode ( & id) . into_string ( ) ) ;
4053
4154 // Build nullifier instruction
55+ println ! ( "\n Fetching proof and building instruction..." ) ;
4256 let nullifier_ix = create_nullifier_ix ( & mut rpc, payer. pubkey ( ) , id) . await ?;
4357
44- // Combine with a simple transfer (example of prepending nullifier)
45- let recipient = Keypair :: new ( ) . pubkey ( ) ;
46- let transfer_ix = system_instruction:: transfer ( & payer. pubkey ( ) , & recipient, 1_000 ) ;
47-
48- // Build and send transaction
49- rpc. create_and_send_transaction ( & [ nullifier_ix, transfer_ix] , & payer. pubkey ( ) , & [ & payer] )
58+ // Send transaction with just the nullifier instruction
59+ // (You can add other instructions like transfers here)
60+ println ! ( "Sending transaction..." ) ;
61+ let sig = rpc
62+ . create_and_send_transaction ( & [ nullifier_ix] , & payer. pubkey ( ) , & [ & payer] )
5063 . await ?;
51- println ! ( "Transaction confirmed" ) ;
64+ println ! ( "Transaction confirmed: {}" , sig ) ;
5265
5366 // Verify nullifier was created
5467 let address = derive_nullifier_address ( & id) ;
@@ -57,18 +70,20 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
5770 bs58:: encode( & address) . into_string( )
5871 ) ;
5972
60- // Try to create the same nullifier again (should fail)
73+ // Try to create the same nullifier again (should fail at proof stage )
6174 println ! ( "\n Attempting duplicate nullifier (should fail)..." ) ;
62- let nullifier_ix_2 = create_nullifier_ix ( & mut rpc, payer. pubkey ( ) , id) . await ?;
63- let transfer_ix_2 = system_instruction:: transfer ( & payer. pubkey ( ) , & recipient, 1_000 ) ;
64-
65- match rpc
66- . create_and_send_transaction ( & [ nullifier_ix_2, transfer_ix_2] , & payer. pubkey ( ) , & [ & payer] )
67- . await
68- {
69- Ok ( _) => println ! ( "ERROR: Duplicate nullifier should have failed!" ) ,
70- Err ( e) => println ! ( "Expected failure: {}" , e) ,
75+ match create_nullifier_ix ( & mut rpc, payer. pubkey ( ) , id) . await {
76+ Ok ( _) => println ! ( "ERROR: Duplicate nullifier proof request should have failed!" ) ,
77+ Err ( e) => {
78+ let err_str = e. to_string ( ) ;
79+ if err_str. contains ( "already exists" ) {
80+ println ! ( "Duplicate correctly rejected: address already exists" ) ;
81+ } else {
82+ println ! ( "Expected failure: {}" , e) ;
83+ }
84+ }
7185 }
7286
87+ println ! ( "\n Success! Nullifier program works on devnet." ) ;
7388 Ok ( ( ) )
7489}
0 commit comments