Documentation Index
Fetch the complete documentation index at: https://luminouslabs-cc5545c6-bump-sdks.mintlify.app/llms.txt
Use this file to discover all available pages before exploring further.
V2 Improvements
| v1 | v2 |
|---|
| CU consumption | Baseline | Up to 70% less |
| Merkle tree type | Concurrent | Batched |
| State tree depth | 26 (~67M leaves) | 32 (~4B leaves) |
| Address tree depth | 26 | 40 |
| Address tree accounts | Separate tree + queue | Single batch tree |
Rust
TypeScript
AI Prompt
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 exampleuse 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?;
Update imports
// v1
import {
deriveAddress,
deriveAddressSeed,
defaultTestStateTreeAccounts,
PackedAccounts,
} from "@lightprotocol/stateless.js";
// v2
import {
deriveAddressV2,
deriveAddressSeedV2,
batchAddressTree,
PackedAccounts,
featureFlags,
VERSION,
} from "@lightprotocol/stateless.js";
Enable v2 mode
Set the feature flag before making any calls:import { featureFlags, VERSION } from "@lightprotocol/stateless.js";
(featureFlags as any).version = VERSION.V2;
Update address derivation
The seed and address derivation functions have different signatures in v2:// v1 - Program ID passed to seed derivation
const seed = deriveAddressSeed(
[counterSeed, signer.publicKey.toBytes()],
new PublicKey(program.idl.address)
);
const address = deriveAddress(seed, addressTree);
// v2 - Program ID passed to address derivation
const seed = deriveAddressSeedV2([counterSeed, signer.publicKey.toBytes()]);
const address = deriveAddressV2(
seed,
addressTree,
new PublicKey(program.idl.address)
);
Update address tree references
Use batchAddressTree instead of defaultTestStateTreeAccounts().addressTree:// v1
const addressTree = defaultTestStateTreeAccounts().addressTree;
const addressQueue = defaultTestStateTreeAccounts().addressQueue;
// In proof request
{
tree: addressTree,
queue: addressQueue,
address: bn(address.toBytes()),
}
// v2 - queue equals tree for batch trees
const addressTree = new PublicKey(batchAddressTree);
// In proof request
{
tree: addressTree,
queue: addressTree,
address: bn(address.toBytes()),
}
Update PackedAccounts
Use the v2 variant when building remaining accounts:// v1
const remainingAccounts = PackedAccounts.newWithSystemAccounts(systemAccountConfig);
// v2
const remainingAccounts = PackedAccounts.newWithSystemAccountsV2(systemAccountConfig);
Migrate Light Protocol program from v1 to v2 Merkle trees
---
argument-hint: <path_to_program>
description: Migrate Light Protocol program from v1 to v2 Merkle trees
allowed-tools: [Bash, Read, Glob, Grep, Task, WebFetch]
---
Migrate this Light Protocol program from v1 to v2 Merkle trees.
## Goal
Produce a **fully working migration** that builds and tests pass.
## Available commands
Via Bash tool:
- **cargo build-sbf**, **cargo test-sbf**, **cargo fmt**, **cargo clippy**
- **anchor build**, **anchor test**
- **grep**, **sed**
## Documentation
- Migration Guide: https://zkcompression.com/references/migration-v1-to-v2
- Reference PR: https://github.com/Lightprotocol/program-examples/commit/54f0e7f15c2972a078f776cfb40b238d83c7e486
## Reference repos
program-examples/counter/anchor/
├── programs/counter/src/lib.rs # v2 patterns: derive_address, CpiAccounts
├── Cargo.toml # v2 feature flags
└── tests/counter.ts # v2 client patterns
## Workflow
### Phase 1: Index program
Find all v1 patterns:
grep -r "::v1::" src/ tests/
grep -r "ADDRESS_TREE_V1" src/
grep -r "into_new_address_params_packed" src/
grep -r "get_address_tree_v1" tests/
### Phase 2: Update dependencies
Update Cargo.toml. V2 is the default - no feature flag needed:
# On-chain program
[dependencies]
light-sdk = { version = "0.17.1", features = ["anchor"] }
light-hasher = "5.0.0"
# Off-chain client
[dependencies]
light-client = "0.17.2"
Note: V2 is now the default in all crates. Only specify `features = ["v2"]` if you disabled default features.
### Phase 3: Rust SDK replacements
| v1 Pattern | v2 Replacement |
|------------|----------------|
| address::v1::derive_address | address::v2::derive_address |
| cpi::v1::CpiAccounts | cpi::v2::CpiAccounts |
| cpi::v1::LightSystemProgramCpi | cpi::v2::LightSystemProgramCpi |
| constants::ADDRESS_TREE_V1 | constants::ADDRESS_TREE_V2 |
| .into_new_address_params_packed(seed) | .into_new_address_params_assigned_packed(seed, Some(0)) |
| .add_system_accounts(config) | .add_system_accounts_v2(config) |
### Phase 4: TypeScript SDK replacements
| v1 Pattern | v2 Replacement |
|------------|----------------|
| deriveAddress( | deriveAddressV2( |
| deriveAddressSeed( | deriveAddressSeedV2( |
| defaultTestStateTreeAccounts().addressTree | batchAddressTree |
| .newWithSystemAccounts( | .newWithSystemAccountsV2( |
| get_address_tree_v1() | get_address_tree_v2() |
| get_random_state_tree_info_v1() | get_random_state_tree_info() |
### Phase 5: Build and test loop
**Required commands (no shortcuts):**
For Anchor programs: **anchor build && anchor test**
For Native programs: **cargo build-sbf && cargo test-sbf**
**NO shortcuts allowed:**
- Do NOT use **cargo build** (must use **cargo build-sbf**)
- Do NOT use **cargo test** (must use **cargo test-sbf**)
- Tests MUST run against real BPF bytecode
**On failure:** Spawn debugger agent with error context.
**Loop rules:**
1. Each debugger gets fresh context + previous debug reports
2. Each attempt tries something DIFFERENT
3. **NEVER GIVE UP** - keep spawning until fixed
Do NOT proceed until all tests pass.
## DeepWiki fallback
If no matching pattern in reference repos:
mcp__deepwiki__ask_question("Lightprotocol/light-protocol", "How to migrate {pattern} from v1 to v2?")
View Program Examples
Didn’t find what you were looking for?