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 Without
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:Report incorrect code
Copy
Ask AI
# 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"] }
v2, add_system_accounts_v2 and cpi::v2 are not available.Report incorrect code
Copy
Ask AI
[dependencies]
light-sdk = "0.17.1"
light-hasher = "5.0.0"
anchor feature:Report incorrect code
Copy
Ask AI
[dependencies]
light-sdk = { version = "0.17.1", features = ["anchor"] }
Report incorrect code
Copy
Ask AI
[dependencies]
light-client = { version = "0.17.2", features = ["v2"] }
Update imports
Report incorrect code
Copy
Ask AI
// v1
use light_sdk::{
address::v1::derive_address,
constants::ADDRESS_TREE_V1,
cpi::v1::{CpiAccounts, LightSystemProgramCpi},
};
Report incorrect code
Copy
Ask AI
// v2
use light_sdk::{
address::v2::derive_address,
cpi::v2::{CpiAccounts, LightSystemProgramCpi},
constants::ADDRESS_TREE_V2,
};
Update address derivation
Thederive_address function signature remains the same, but the internal derivation logic differs. No code changes needed beyond updating the import path.Update address params
Replaceinto_new_address_params_packed() with into_new_address_params_assigned_packed():Report incorrect code
Copy
Ask AI
// v1
let new_address_params = instruction_data
.address_tree_info
.into_new_address_params_packed(address_seed);
Report incorrect code
Copy
Ask AI
// v2
let new_address_params = instruction_data
.address_tree_info
.into_new_address_params_assigned_packed(address_seed, Some(0));
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:Report incorrect code
Copy
Ask AI
// v1
if address_tree_pubkey.to_bytes() != ADDRESS_TREE_V1 {
return Err(ProgramError::InvalidAccountData);
}
Report incorrect code
Copy
Ask AI
// v2
if address_tree_pubkey.to_bytes() != ADDRESS_TREE_V2 {
return Err(ProgramError::InvalidAccountData);
}
Update client-side account packing
Report incorrect code
Copy
Ask AI
use light_sdk::instruction::{PackedAccounts, SystemAccountMetaConfig};
let config = SystemAccountMetaConfig::new(YOUR_PROGRAM_ID);
let mut packed = PackedAccounts::default();
Report incorrect code
Copy
Ask AI
// v1
packed.add_system_accounts(config)?;
Report incorrect code
Copy
Ask AI
// v2
packed.add_system_accounts_v2(config)?;
Report incorrect code
Copy
Ask AI
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)
Report incorrect code
Copy
Ask AI
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?;
Report incorrect code
Copy
Ask AI
// v1
let address_tree = rpc.get_address_tree_v1();
let state_tree = rpc.get_random_state_tree_info_v1()?;
Report incorrect code
Copy
Ask AI
// v2
let address_tree = rpc.get_address_tree_v2();
let state_tree = rpc.get_random_state_tree_info()?; // returns v2 when feature enabled
Report incorrect code
Copy
Ask AI
// get_validity_proof automatically uses v2 endpoint (default)
let proof_result = rpc
.get_validity_proof(hashes, addresses_with_trees, None)
.await?;
Update imports
Report incorrect code
Copy
Ask AI
// v1
import {
deriveAddress,
deriveAddressSeed,
defaultTestStateTreeAccounts,
PackedAccounts,
} from "@lightprotocol/stateless.js";
Report incorrect code
Copy
Ask AI
// v2
import {
deriveAddressV2,
deriveAddressSeedV2,
batchAddressTree,
PackedAccounts,
featureFlags,
VERSION,
} from "@lightprotocol/stateless.js";
Enable v2 mode
Set the feature flag before making any calls:Report incorrect code
Copy
Ask AI
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:Report incorrect code
Copy
Ask AI
// v1 - Program ID passed to seed derivation
const seed = deriveAddressSeed(
[counterSeed, signer.publicKey.toBytes()],
new PublicKey(program.idl.address)
);
const address = deriveAddress(seed, addressTree);
Report incorrect code
Copy
Ask AI
// 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
UsebatchAddressTree instead of defaultTestStateTreeAccounts().addressTree:Report incorrect code
Copy
Ask AI
// v1
const addressTree = defaultTestStateTreeAccounts().addressTree;
const addressQueue = defaultTestStateTreeAccounts().addressQueue;
// In proof request
{
tree: addressTree,
queue: addressQueue,
address: bn(address.toBytes()),
}
Report incorrect code
Copy
Ask AI
// 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:Report incorrect code
Copy
Ask AI
// v1
const remainingAccounts = PackedAccounts.newWithSystemAccounts(systemAccountConfig);
Report incorrect code
Copy
Ask AI
// v2
const remainingAccounts = PackedAccounts.newWithSystemAccountsV2(systemAccountConfig);
Report incorrect code
Copy
Ask AI
---
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?")