How to Reinitialize Compressed Accounts
Guide to reinitialize compressed accounts in Solana programs with full code examples.
Overview
Compressed accounts are reinitialized via CPI to the Light System Program.
An empty compressed account can be reinitialized
with an account hash marked as empty with zero values and zero discriminator
to create a new account hash at the same address with new values.
Find full code examples at the end for Anchor and native Rust.
Implementation Guide
This guide will cover the components of a Solana program that reinitializes compressed accounts. Here is the complete flow to reinitialize compressed accounts:


Instruction Data
Define the instruction data with the following parameters:
pub fn reinit_account<'info>(
ctx: Context<'_, '_, '_, 'info, GenericAnchorAccounts<'info>>,
proof: ValidityProof,
account_meta: CompressedAccountMeta,
) -> Result<()>pub struct ReinitInstructionData {
pub proof: ValidityProof,
pub account_meta: CompressedAccountMeta,
}Validity Proof
Define
proofto include the proof that the closed account with zero values exists in the state tree.Clients fetch a validity proof with
getValidityProof()from an RPC provider that supports ZK Compression (Helius, Triton, ...).
Specify input state and output state tree (stores new account hash)
Define
account_meta: CompressedAccountMetato reference the closed account and specify the state tree to store the new account hash:tree_info: PackedStateTreeInfo: References the existing account hash in the state tree.address: The account's derived address.output_state_tree_index: References the state tree account that will store the new compressed account hash.
Reinitialize Closed Account
Reinitialize the closed account with LightAccount::new_empty().
new_empty()
reconstructs the closed account hash with zero values as input, and
creates output state with default-initialized values.
You can set custom values in the same transaction:
Reinitialize with
new_empty(), andUpdate with
new_mut()to set custom values.
let my_compressed_account = LightAccount::<MyCompressedAccount>::new_empty(
&crate::ID,
&account_meta,
)?;let my_compressed_account = LightAccount::<MyCompressedAccount>::new_empty(
&ID,
&instruction_data.account_meta,
)?;Pass these parameters to new_empty():
&program_id: The program's ID that owns the compressed account.&account_meta: TheCompressedAccountMetafrom instruction data (Step 2) that identifies the existing account and specifies the output state tree.Anchor: Pass
account_metaby reference. It is automatically deserialized as a function parameter.Native Rust: Manually deserialize the instruction data struct, then pass the field by reference
&instruction_data.account_meta.
The SDK creates:
A
LightAccountwrapper with account data automatically initialized to default values using theDefaulttrait.This creates a zero-initialized instance:
Pubkeyas all zeros,u64as0,Stringas empty.
Light System Program CPI
Invoke the Light System Program to reinitialize the compressed account.
The Light System Program
validates the closed account hash exists in state tree,
nullifies the closed account hash, and
appends the new account hash with provided values to the state tree.
let light_cpi_accounts = CpiAccounts::new(
ctx.accounts.signer.as_ref(),
ctx.remaining_accounts,
crate::LIGHT_CPI_SIGNER,
);
LightSystemProgramCpi::new_cpi(LIGHT_CPI_SIGNER, proof)
.with_light_account(my_compressed_account)?
.invoke(light_cpi_accounts)?;Set up CpiAccounts::new():
CpiAccounts::new() parses accounts for the CPI call to Light System Program.
Pass these parameters:
ctx.accounts.signer.as_ref(): the transaction signerctx.remaining_accounts: Slice with[system_accounts, ...packed_tree_accounts]. The client builds this withPackedAccountsand passes it to the instruction.&LIGHT_CPI_SIGNER: Your program's CPI signer PDA defined in Constants.
let (signer, remaining_accounts) = accounts
.split_first();
let cpi_accounts = CpiAccounts::new(
signer,
remaining_accounts,
LIGHT_CPI_SIGNER
);
LightSystemProgramCpi::new_cpi(LIGHT_CPI_SIGNER, instruction_data.proof)
.with_light_account(my_compressed_account)?
.invoke(cpi_accounts)?;Set up CpiAccounts::new():
CpiAccounts::new() parses accounts for the CPI call to Light System Program.
Pass these parameters:
signer: account that signs and pays for the transactionremaining_accounts: Slice with[system_accounts, ...packed_tree_accounts]. The client builds this withPackedAccounts.split_first()extracts the fee payer from the accounts array to separate it from the Light System Program accounts needed for the CPI.
&LIGHT_CPI_SIGNER: Your program's CPI signer PDA defined in Constants.
Build the CPI instruction:
new_cpi()initializes the CPI instruction with theproofto prove the closed account hash exists in the state tree - defined in the Instruction Data (Step 2).with_light_accountadds theLightAccountconfigured with the closed account hash as input and provided values as output - defined in Step 3.invoke(light_cpi_accounts)calls the Light System Program withCpiAccounts.
Full Code Example
The counter programs below implement all steps from this guide. Make sure you have your developer environment set up first.
npm -g i @lightprotocol/[email protected]
light init testprogramFor help with debugging, see the Error Cheatsheet.
#![allow(unexpected_cfgs)]
#![allow(deprecated)]
use anchor_lang::{prelude::*, AnchorDeserialize, AnchorSerialize};
use light_sdk::{
account::LightAccount,
address::v1::derive_address,
cpi::{v1::CpiAccounts, CpiSigner},
derive_light_cpi_signer,
instruction::{account_meta::CompressedAccountMeta, PackedAddressTreeInfo, ValidityProof},
LightDiscriminator,
};
declare_id!("DeSUZ4to3qN7mQimoTgvEnBXiBWeTqMVcMz3ynLaWx1t");
pub const LIGHT_CPI_SIGNER: CpiSigner =
derive_light_cpi_signer!("DeSUZ4to3qN7mQimoTgvEnBXiBWeTqMVcMz3ynLaWx1t");
#[program]
pub mod reinit {
use super::*;
use light_sdk::cpi::{
v1::LightSystemProgramCpi, InvokeLightSystemProgram, LightCpiInstruction,
};
/// Setup: Create a compressed account
pub fn create_account<'info>(
ctx: Context<'_, '_, '_, 'info, GenericAnchorAccounts<'info>>,
proof: ValidityProof,
address_tree_info: PackedAddressTreeInfo,
output_state_tree_index: u8,
message: String,
) -> Result<()> {
let light_cpi_accounts = CpiAccounts::new(
ctx.accounts.signer.as_ref(),
ctx.remaining_accounts,
crate::LIGHT_CPI_SIGNER,
);
let (address, address_seed) = derive_address(
&[b"message", ctx.accounts.signer.key().as_ref()],
&address_tree_info
.get_tree_pubkey(&light_cpi_accounts)
.map_err(|_| ErrorCode::AccountNotEnoughKeys)?,
&crate::ID,
);
let mut my_compressed_account = LightAccount::<MyCompressedAccount>::new_init(
&crate::ID,
Some(address),
output_state_tree_index,
);
my_compressed_account.owner = ctx.accounts.signer.key();
my_compressed_account.message = message.clone();
msg!(
"Created compressed account with message: {}",
my_compressed_account.message
);
LightSystemProgramCpi::new_cpi(LIGHT_CPI_SIGNER, proof)
.with_light_account(my_compressed_account)?
.with_new_addresses(&[address_tree_info.into_new_address_params_packed(address_seed)])
.invoke(light_cpi_accounts)?;
Ok(())
}
/// Setup: Close compressed account
pub fn close_account<'info>(
ctx: Context<'_, '_, '_, 'info, GenericAnchorAccounts<'info>>,
proof: ValidityProof,
account_meta: CompressedAccountMeta,
current_message: String,
) -> Result<()> {
let light_cpi_accounts = CpiAccounts::new(
ctx.accounts.signer.as_ref(),
ctx.remaining_accounts,
crate::LIGHT_CPI_SIGNER,
);
let my_compressed_account = LightAccount::<MyCompressedAccount>::new_close(
&crate::ID,
&account_meta,
MyCompressedAccount {
owner: ctx.accounts.signer.key(),
message: current_message,
},
)?;
msg!("Close compressed account.");
LightSystemProgramCpi::new_cpi(LIGHT_CPI_SIGNER, proof)
.with_light_account(my_compressed_account)?
.invoke(light_cpi_accounts)?;
Ok(())
}
/// Reinitialize closed compressed account
pub fn reinit_account<'info>(
ctx: Context<'_, '_, '_, 'info, GenericAnchorAccounts<'info>>,
proof: ValidityProof,
account_meta: CompressedAccountMeta,
) -> Result<()> {
let light_cpi_accounts = CpiAccounts::new(
ctx.accounts.signer.as_ref(),
ctx.remaining_accounts,
crate::LIGHT_CPI_SIGNER,
);
let my_compressed_account = LightAccount::<MyCompressedAccount>::new_empty(
&crate::ID,
&account_meta,
)?;
msg!("Reinitializing closed compressed account");
LightSystemProgramCpi::new_cpi(LIGHT_CPI_SIGNER, proof)
.with_light_account(my_compressed_account)?
.invoke(light_cpi_accounts)?;
Ok(())
}
}
#[derive(Accounts)]
pub struct GenericAnchorAccounts<'info> {
#[account(mut)]
pub signer: Signer<'info>,
}
#[event]
#[derive(Clone, Debug, Default, LightDiscriminator)]
pub struct MyCompressedAccount {
pub owner: Pubkey,
pub message: String,
}#![allow(unexpected_cfgs)]
#[cfg(any(test, feature = "test-helpers"))]
pub mod test_helpers;
use borsh::{BorshDeserialize, BorshSerialize};
use light_macros::pubkey;
use light_sdk::{
account::sha::LightAccount,
address::v1::derive_address,
cpi::{
v1::{CpiAccounts, LightSystemProgramCpi},
CpiSigner, InvokeLightSystemProgram, LightCpiInstruction,
},
derive_light_cpi_signer,
error::LightSdkError,
instruction::{account_meta::CompressedAccountMeta, PackedAddressTreeInfo, ValidityProof},
LightDiscriminator,
};
use solana_program::{
account_info::AccountInfo, entrypoint, program_error::ProgramError, pubkey::Pubkey,
};
pub const ID: Pubkey = pubkey!("C9WiPUaQ5PRjEWg7vUmgekfuQtAgFZFhn12ytXEMDr8y");
pub const LIGHT_CPI_SIGNER: CpiSigner = derive_light_cpi_signer!("C9WiPUaQ5PRjEWg7vUmgekfuQtAgFZFhn12ytXEMDr8y");
#[cfg(not(feature = "no-entrypoint"))]
entrypoint!(process_instruction);
#[derive(Debug, BorshSerialize, BorshDeserialize)]
pub enum InstructionType {
Create,
Close,
Reinit,
}
#[derive(Debug, BorshSerialize, BorshDeserialize)]
pub struct CreateInstructionData {
pub proof: ValidityProof,
pub address_tree_info: PackedAddressTreeInfo,
pub output_state_tree_index: u8,
pub message: String,
}
#[derive(Debug, BorshSerialize, BorshDeserialize)]
pub struct CloseInstructionData {
pub proof: ValidityProof,
pub account_meta: CompressedAccountMeta,
pub current_message: String,
}
#[derive(Debug, BorshSerialize, BorshDeserialize)]
pub struct ReinitInstructionData {
pub proof: ValidityProof,
pub account_meta: CompressedAccountMeta,
}
#[derive(Debug, Default, Clone, BorshSerialize, BorshDeserialize, LightDiscriminator)]
pub struct MyCompressedAccount {
pub owner: Pubkey,
pub message: String,
}
pub fn process_instruction(
_program_id: &Pubkey,
accounts: &[AccountInfo],
instruction_data: &[u8],
) -> Result<(), ProgramError> {
let (instruction_type, rest) = instruction_data
.split_first()
.ok_or(ProgramError::InvalidInstructionData)?;
match InstructionType::try_from_slice(&[*instruction_type])
.map_err(|_| ProgramError::InvalidInstructionData)?
{
InstructionType::Create => create(accounts, rest)?,
InstructionType::Close => close(accounts, rest)?,
InstructionType::Reinit => reinit(accounts, rest)?,
}
Ok(())
}
fn create(accounts: &[AccountInfo], instruction_data: &[u8]) -> Result<(), LightSdkError> {
let instruction_data =
CreateInstructionData::try_from_slice(instruction_data).map_err(|_| LightSdkError::Borsh)?;
let signer = accounts.first().ok_or(ProgramError::NotEnoughAccountKeys)?;
let light_cpi_accounts = CpiAccounts::new(
signer,
&accounts[1..],
LIGHT_CPI_SIGNER
);
let (address, address_seed) = derive_address(
&[b"message", signer.key.as_ref()],
&instruction_data
.address_tree_info
.get_tree_pubkey(&light_cpi_accounts)
.map_err(|_| ProgramError::NotEnoughAccountKeys)?,
&ID,
);
let new_address_params = instruction_data
.address_tree_info
.into_new_address_params_packed(address_seed);
let mut my_compressed_account = LightAccount::<MyCompressedAccount>::new_init(
&ID,
Some(address),
instruction_data.output_state_tree_index,
);
my_compressed_account.owner = *signer.key;
my_compressed_account.message = instruction_data.message;
LightSystemProgramCpi::new_cpi(LIGHT_CPI_SIGNER, instruction_data.proof)
.with_light_account(my_compressed_account)?
.with_new_addresses(&[new_address_params])
.invoke(light_cpi_accounts)?;
Ok(())
}
fn close(accounts: &[AccountInfo], instruction_data: &[u8]) -> Result<(), LightSdkError> {
let instruction_data =
CloseInstructionData::try_from_slice(instruction_data).map_err(|_| LightSdkError::Borsh)?;
let (signer, remaining_accounts) = accounts
.split_first()
.ok_or(ProgramError::InvalidAccountData)?;
let cpi_accounts = CpiAccounts::new(
signer,
remaining_accounts,
LIGHT_CPI_SIGNER
);
let my_compressed_account = LightAccount::<MyCompressedAccount>::new_close(
&ID,
&instruction_data.account_meta,
MyCompressedAccount {
owner: *signer.key,
message: instruction_data.current_message,
},
)?;
LightSystemProgramCpi::new_cpi(LIGHT_CPI_SIGNER, instruction_data.proof)
.with_light_account(my_compressed_account)?
.invoke(cpi_accounts)?;
Ok(())
}
fn reinit(accounts: &[AccountInfo], instruction_data: &[u8]) -> Result<(), LightSdkError> {
let instruction_data =
ReinitInstructionData::try_from_slice(instruction_data).map_err(|_| LightSdkError::Borsh)?;
let (signer, remaining_accounts) = accounts
.split_first()
.ok_or(ProgramError::InvalidAccountData)?;
let cpi_accounts = CpiAccounts::new(
signer,
remaining_accounts,
LIGHT_CPI_SIGNER
);
let my_compressed_account = LightAccount::<MyCompressedAccount>::new_empty(
&ID,
&instruction_data.account_meta,
)?;
LightSystemProgramCpi::new_cpi(LIGHT_CPI_SIGNER, instruction_data.proof)
.with_light_account(my_compressed_account)?
.invoke(cpi_accounts)?;
Ok(())
}Next Steps
Build a client for your program or learn how to burn compressed accounts.
Last updated
Was this helpful?