> ## Documentation Index
> Fetch the complete documentation index at: https://www.zkcompression.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Guide to Migrate from v1 to v2 Merkle trees

> V2 reduces CU consumption by up to 70%. V1 remains supported for existing deployments.

## 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 |

<Tabs>
  <Tab title="Rust">
    ### Update Cargo.toml

    In 0.17.x onward, `v2` is a default feature of `light-sdk`. Make sure it stays enabled.

    <Info>
      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:

      ```toml theme={null}
      # 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.
    </Info>

    **On-chain program:**

    ```toml theme={null}
    [dependencies]
    light-sdk = "0.17.1"
    light-hasher = "5.0.0"
    ```

    For Anchor programs, add the `anchor` feature:

    ```toml theme={null}
    [dependencies]
    light-sdk = { version = "0.17.1", features = ["anchor"] }
    ```

    **Off-chain client:**

    ```toml theme={null}
    [dependencies]
    light-client = { version = "0.17.2", features = ["v2"] }
    ```

    ### Update imports

    ```rust theme={null}
    // v1
    use light_sdk::{
        address::v1::derive_address,
        constants::ADDRESS_TREE_V1,
        cpi::v1::{CpiAccounts, LightSystemProgramCpi},
    };
    ```

    ```rust theme={null}
    // 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()`:

    ```rust theme={null}
    // v1
    let new_address_params = instruction_data
        .address_tree_info
        .into_new_address_params_packed(address_seed);
    ```

    ```rust theme={null}
    // 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:

    ```rust theme={null}
    // v1
    if address_tree_pubkey.to_bytes() != ADDRESS_TREE_V1 {
        return Err(ProgramError::InvalidAccountData);
    }
    ```

    ```rust theme={null}
    // v2
    if address_tree_pubkey.to_bytes() != ADDRESS_TREE_V2 {
        return Err(ProgramError::InvalidAccountData);
    }
    ```

    ### Update client-side account packing

    ```rust theme={null}
    use light_sdk::instruction::{PackedAccounts, SystemAccountMetaConfig};

    let config = SystemAccountMetaConfig::new(YOUR_PROGRAM_ID);
    let mut packed = PackedAccounts::default();
    ```

    ```rust theme={null}
    // v1
    packed.add_system_accounts(config)?;
    ```

    ```rust theme={null}
    // v2
    packed.add_system_accounts_v2(config)?;
    ```

    **Full v2 client example**

    ```rust theme={null}
    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)

    ```rust theme={null}
    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:**

    ```rust theme={null}
    // v1
    let address_tree = rpc.get_address_tree_v1();
    let state_tree = rpc.get_random_state_tree_info_v1()?;
    ```

    ```rust theme={null}
    // 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:**

    ```rust theme={null}
    // get_validity_proof automatically uses v2 endpoint (default)
    let proof_result = rpc
        .get_validity_proof(hashes, addresses_with_trees, None)
        .await?;
    ```
  </Tab>

  <Tab title="TypeScript">
    ### Update imports

    ```typescript theme={null}
    // v1
    import {
        deriveAddress,
        deriveAddressSeed,
        defaultTestStateTreeAccounts,
        PackedAccounts,
    } from "@lightprotocol/stateless.js";
    ```

    ```typescript theme={null}
    // v2
    import {
        deriveAddressV2,
        deriveAddressSeedV2,
        batchAddressTree,
        PackedAccounts,
    } from "@lightprotocol/stateless.js";
    ```

    ### V2 mode (TypeScript)

    Published **`@lightprotocol/stateless.js@^0.23.0`** defaults to **V2** — you do **not** need to set **`LIGHT_PROTOCOL_VERSION=V2`** in application code.

    If you **build `stateless.js` from source** and need V1 artifacts, set **`LIGHT_PROTOCOL_VERSION=V1`** for that build. For rare runtime overrides in custom bundles, you can still assign **`featureFlags.version`** before other imports run (advanced; prefer matching published versions instead).

    ### Update address derivation

    The seed and address derivation functions have different signatures in v2:

    ```typescript theme={null}
    // v1 - Program ID passed to seed derivation
    const seed = deriveAddressSeed(
        [counterSeed, signer.publicKey.toBytes()],
        new PublicKey(program.idl.address)
    );
    const address = deriveAddress(seed, addressTree);
    ```

    ```typescript theme={null}
    // 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`:

    ```typescript theme={null}
    // v1
    const addressTree = defaultTestStateTreeAccounts().addressTree;
    const addressQueue = defaultTestStateTreeAccounts().addressQueue;

    // In proof request
    {
        tree: addressTree,
        queue: addressQueue,
        address: bn(address.toBytes()),
    }
    ```

    ```typescript theme={null}
    // 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:

    ```typescript theme={null}
    // v1
    const remainingAccounts = PackedAccounts.newWithSystemAccounts(systemAccountConfig);
    ```

    ```typescript theme={null}
    // v2
    const remainingAccounts = PackedAccounts.newWithSystemAccountsV2(systemAccountConfig);
    ```
  </Tab>

  <Tab title="AI Prompt">
    <Prompt description="Migrate Light Protocol program from v1 to v2 Merkle trees" actions={["copy", "cursor"]}>
      {`---
            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?")`}
    </Prompt>

    ```text theme={null}
    ---
    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?")
    ```
  </Tab>
</Tabs>

## View Program Examples

<Card title="Program examples" icon="chevron-right" color="#0066ff" href="/pda/compressed-pdas/program-examples" horizontal />

***

## Didn't find what you were looking for?

<Callout type="info">
  Reach out! [Telegram](https://t.me/swen_light) | [email](mailto:support@lightprotocol.com) | [Discord](https://discord.com/invite/7cJ8BhAXhu)
</Callout>
