Skip to main content

V2 Improvements

v1v2
CU consumptionBaselineUp to 70% less
Merkle tree typeConcurrentBatched
State tree depth26 (~67M leaves)32 (~4B leaves)
Address tree depth2640
Address tree accountsSeparate tree + queueSingle batch tree

Update Cargo.toml

In 0.17.x onward, v2 is a default feature of light-sdk. Make sure it stays enabled.
When you specify features = [...] in Cargo.toml, Cargo disables default features. If you need extra features like keccak or anchor, either keep defaults explicitly or add v2 to your features list:
# This DISABLES v2 (default features are off):
light-sdk = { version = "0.17.1", features = ["keccak"] }

# Either of these keeps v2 enabled:
light-sdk = { version = "0.17.1", features = ["keccak", "v2"] }
light-sdk = { version = "0.17.1", default-features = true, features = ["keccak"] }
Without v2, add_system_accounts_v2 and cpi::v2 are not available.
On-chain program:
[dependencies]
light-sdk = "0.17.1"
light-hasher = "5.0.0"
For Anchor programs, add the anchor feature:
[dependencies]
light-sdk = { version = "0.17.1", features = ["anchor"] }
Off-chain client:
[dependencies]
light-client = { version = "0.17.2", features = ["v2"] }

Update imports

// v1
use light_sdk::{
    address::v1::derive_address,
    constants::ADDRESS_TREE_V1,
    cpi::v1::{CpiAccounts, LightSystemProgramCpi},
};
// v2
use light_sdk::{
    address::v2::derive_address,
    cpi::v2::{CpiAccounts, LightSystemProgramCpi},
    constants::ADDRESS_TREE_V2,
};

Update address derivation

The derive_address function signature remains the same, but the internal derivation logic differs. No code changes needed beyond updating the import path.

Update address params

Replace into_new_address_params_packed() with into_new_address_params_assigned_packed():
// v1
let new_address_params = instruction_data
    .address_tree_info
    .into_new_address_params_packed(address_seed);
// v2
let new_address_params = instruction_data
    .address_tree_info
    .into_new_address_params_assigned_packed(address_seed, Some(0));
The second parameter specifies the output queue index. Use Some(0) to assign the address to the first available queue.

Update tree validation

If your program validates the address tree pubkey, update the constant:
// v1
if address_tree_pubkey.to_bytes() != ADDRESS_TREE_V1 {
    return Err(ProgramError::InvalidAccountData);
}
// v2
if address_tree_pubkey.to_bytes() != ADDRESS_TREE_V2 {
    return Err(ProgramError::InvalidAccountData);
}

Update client-side account packing

use light_sdk::instruction::{PackedAccounts, SystemAccountMetaConfig};

let config = SystemAccountMetaConfig::new(YOUR_PROGRAM_ID);
let mut packed = PackedAccounts::default();
// v1
packed.add_system_accounts(config)?;
// v2
packed.add_system_accounts_v2(config)?;
Full v2 client example
use light_client::rpc::{LightClient, LightClientConfig, Rpc, Indexer};
use light_sdk::{
    address::v2::derive_address,
    instruction::{PackedAccounts, SystemAccountMetaConfig},
};

// 1. Build PackedAccounts with v2 system accounts
let config = SystemAccountMetaConfig::new(YOUR_PROGRAM_ID);
let mut packed = PackedAccounts::default();
packed.add_system_accounts_v2(config)?;

// 2. Fetch validity proof
let address_tree = rpc.get_address_tree_v2();
let (address, _) = derive_address(&[b"my_seed", &id], &address_tree.tree, &YOUR_PROGRAM_ID);

let rpc_result = rpc
    .get_validity_proof(
        vec![],
        vec![AddressWithTree { address, tree: address_tree.tree }],
        None,
    )
    .await?
    .value;

// 3. Pack tree infos into remaining accounts
let tree_infos = rpc_result.pack_tree_infos(&mut packed);
let address_tree_info = tree_infos.address_trees[0];

// 4. Pack output state tree
let state_tree = rpc.get_random_state_tree_info()?;
let output_state_tree_index = state_tree.pack_output_tree_index(&mut packed)?;

// 5. Convert to account metas for the instruction
let (remaining_accounts, _system_offset, _packed_offset) = packed.to_account_metas();

Update client code (light-client)

use light_client::rpc::{LightClient, LightClientConfig, Rpc};

let rpc_url = "https://mainnet.helius-rpc.com/?api-key=YOUR_KEY".to_string();
let photon_url = "https://mainnet.helius-rpc.com".to_string();
let api_key = "YOUR_KEY".to_string();

let config = LightClientConfig::new(rpc_url, Some(photon_url), Some(api_key));
let mut rpc = LightClient::new(config).await?;
Tree info methods:
// v1
let address_tree = rpc.get_address_tree_v1();
let state_tree = rpc.get_random_state_tree_info_v1()?;
// v2
let address_tree = rpc.get_address_tree_v2();
let state_tree = rpc.get_random_state_tree_info()?; // returns v2 when feature enabled
Validity proofs:
// get_validity_proof automatically uses v2 endpoint (default)
let proof_result = rpc
    .get_validity_proof(hashes, addresses_with_trees, None)
    .await?;

View Program Examples

Program examples