Skip to content

Commit dfeea04

Browse files
Fix nullifier example to work with LightClient on devnet
- Use LightClientConfig with correct photon_url setup - Load keypair from ~/.config/solana/id.json - Remove transfer instruction (caused rent issues) - Improve duplicate detection handling Co-authored-by: Cursor <cursoragent@cursor.com>
1 parent 17a87bb commit dfeea04

3 files changed

Lines changed: 116 additions & 42 deletions

File tree

rust-client/Cargo.lock

Lines changed: 58 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

rust-client/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ tokio = { version = "1", features = ["full"] }
3535
dotenv = "0.15"
3636
rand = "0.8"
3737
bs58 = "0.5"
38+
shellexpand = "3"
3839

3940
# Pin blake3 to avoid constant_time_eq edition2024 issue
4041
blake3 = "=1.5.5"
Lines changed: 57 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -1,54 +1,67 @@
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};
911
use 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]
2719
async 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!("\nFetching 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!("\nAttempting 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!("\nSuccess! Nullifier program works on devnet.");
7388
Ok(())
7489
}

0 commit comments

Comments
 (0)