- Compressed and regular Solana accounts share the same functionality and are fully composable.
- A compressed account has two identifiers: the account hash and its address (optional). In comparison, regular Solana accounts are identified by their address.
- The account hash is not persistent and changes with every write to the account.
- For Solana PDA like behavior your compressed account needs an address as persistent identifier.
Fungible state like compressed token accounts do not need addresses.
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 creates compressed accounts.Here is the complete flow:


1
Dependencies
Add dependencies to your program.
- The
light-sdkprovides macros, wrappers and CPI interface to create and interact with compressed accounts. - Add the serialization library (
borshfor native Rust, or useAnchorSerialize).
2
Constants
Set program address and derive the CPI authority PDA to call the Light System program.
CPISigner is the configuration struct for CPIs to the Light System Program.- CPI to the Light System program must be signed with a PDA derived by your program with the seed
b"authority" derive_light_cpi_signer!derives the CPI signer PDA for you at compile time.
3
Compressed Account
- the standard traits (
Clone,Debug,Default), borshorAnchorSerializeto serialize account data, andLightDiscriminatorto implements a unique type ID (8 bytes) to distinguish account types. The default compressed account layout enforces a discriminator in its own field, .
The traits listed above are required for
LightAccount. LightAccount wraps my-compressed-account in Step 7 to set the discriminator and create the compressed account’s data.4
Instruction Data
Define the instruction data with the following parameters:
- Anchor
- Native Rust
Anchor handles instruction deserialization automatically. Pass the parameters directly to the instruction function:
- Validity Proof
- Define
proofto include the proof that the address does not exist yet in the specified address tree. - Clients fetch a validity proof with
getValidityProof()from an RPC provider that supports ZK Compression (Helius, Triton, …).
- Specify Merkle trees to store address and account hash
- Define
address_tree_info: PackedAddressTreeInfoto reference the address tree account used to derive the address in the next step. - Define
output_state_tree_indexto reference the state tree account that stores the compressed account hash.
Clients pack accounts into the accounts array to reduce transaction size. Packed structs like
PackedAddressTreeInfo contain account indices (u8) instead of 32 byte pubkeys. The indices point to the account in the accounts array to retrieve the public key and other metadata.- Initial account data
- Define fields for your program logic. Clients pass the initial values.
- This example includes the
messagefield to define the initial state of the account.
5
Derive Address
Derive the address as a persistent unique identifier for the compressed account.Pass these parameters to
derive_address():&custom_seeds: Predefined inputs, such as strings, numbers or other account addresses. This example usesb"message"and the signer’s pubkey.&address_tree_pubkey: The pubkey of the address tree where the address will be created.- Retrieved by calling
get_tree_pubkey()onaddress_tree_info, which unpacks the index from the accounts array. - This parameter ensures an address is unique to an address tree. Different trees produce different addresses from identical seeds.
- Retrieved by calling
&program_id: Your program’s ID.
address: The derived address for the compressed account.address_seed: Pass this to the Light System Program CPI in Step 8 to create the address.
6
Address Tree Check (optional)
Ensure global uniqueness of an address by verifying that the address tree pubkey matches the program’s tree constant.
Every address is unique, but the same seeds can be used to create different addresses in different address trees. To enforce that a compressed PDA can only be created once with the same seed, you must check the address tree pubkey.
7
Initialize Compressed Account
Initialize the compressed account struct with Pass these parameters to
LightAccount::new_init().new_init() creates a LightAccount instance similar to anchor Account and lets your program define the initial account data.new_init():&owner: The program’s ID that owns the compressed account.Some(address): The derived address from Step 5. PassNonefor accounts without addresses.output_state_tree_index: References the state tree account that will store the updated account hash, defined in instruction data (Step 4)
- A
LightAccountwrapper similar to Anchor’sAccount. new_init()lets the program set the initial data. This example sets:ownerto the signer’s pubkeymessageto an arbitrary string
8
Light System Program CPI
Invoke the Light System Program to create the compressed account and its address.Set up
Build the CPI instruction:
The Light System Program
- verifies the validity proof against the address tree’s Merkle root,
- inserts the address into the address tree, and
- appends the new account hash to the state tree.
- Anchor
- Native Rust
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.
System Accounts List
System Accounts List
| Name | Description | |
|---|---|---|
| 1 | Verifies validity proofs, compressed account ownership checks, cpis the account compression program to update tree accounts | |
| 2 | CPI Signer | - PDA to sign CPI calls from your program to Light System Program - Verified by Light System Program during CPI - Derived from your program ID |
| 3 | Registered Program PDA | - Access control to the Account Compression Program |
| 4 | - Logs compressed account state to Solana ledger. Only used in v1. - Indexers parse transaction logs to reconstruct compressed account state | |
| 5 | Signs CPI calls from Light System Program to Account Compression Program | |
| 6 | - Writes to state and address tree accounts - Client and the account compression program do not interact directly. | |
| 7 | Invoking Program | Your program’s ID, used by Light System Program to: - Derive the CPI Signer PDA - Verify the CPI Signer matches your program ID - Set the owner of created compressed accounts |
| 8 | Solana System Program to transfer lamports |
new_cpi() initializes the CPI instruction with the proof to prove that an address does not exist yet in the specified address tree - defined in the Instruction Data (Step 4).with_light_accountadds theLightAccountwith the initial compressed account data to the CPI instruction - defined in Step 7.with_new_addressesadds theaddress_seedand metadata to the CPI instruction data - returned byderive_addressin Step 5.invoke(light_cpi_accounts)calls the Light System Program withCpiAccounts.
Full Code Example
The example programs below implement all steps from this guide. Make sure you have your developer environment set up first, or simply run:- Anchor
- Native Rust
Find the source code here.