Overview
Compressed accounts store state in Merkle trees instead of traditional Solana accounts, eliminating rent costs while maintaining full composability and security through zero-knowledge proofs.
Compressed Account Structure
A compressed account contains:
interface CompressedAccountWithMerkleContext {
hash : BN254 ; // Account hash (unique identifier)
treeInfo : TreeInfo ; // Merkle tree location
leafIndex : number ; // Position in tree
owner : PublicKey ; // Account owner
lamports : BN ; // SOL balance
address : Uint8Array | null ; // Optional: account address
data : CompressedAccountData | null ; // Optional: account data
}
Account Data
Compressed accounts can store arbitrary data:
interface CompressedAccountData {
discriminator : number []; // 8-byte program discriminator
data : Buffer ; // Serialized account data
dataHash : number []; // Hash of the data (32 bytes)
}
Querying Compressed Accounts
By Owner
Fetch all compressed accounts owned by a public key:
import { createRpc } from '@lightprotocol/stateless.js' ;
import { PublicKey } from '@solana/web3.js' ;
const rpc = createRpc ();
const owner = new PublicKey ( '...' );
// Get all accounts
const response = await rpc . getCompressedAccountsByOwner ( owner );
console . log ( 'Found accounts:' , response . items . length );
response . items . forEach ( account => {
console . log ( 'Account:' , {
hash: account . hash . toString (),
lamports: account . lamports . toString (),
leafIndex: account . leafIndex ,
});
});
Handle large result sets with cursor-based pagination:
import { bn } from '@lightprotocol/stateless.js' ;
let cursor : string | undefined ;
const allAccounts = [];
do {
const response = await rpc . getCompressedAccountsByOwner ( owner , {
cursor ,
limit: bn ( 1000 ),
});
allAccounts . push ( ... response . items );
cursor = response . cursor ?? undefined ;
} while ( cursor );
console . log ( 'Total accounts:' , allAccounts . length );
By Hash
Fetch a specific account by its hash:
import { bn } from '@lightprotocol/stateless.js' ;
const accountHash = bn ( '1234567890...' );
const account = await rpc . getCompressedAccount (
undefined , // address
accountHash // hash
);
if ( account ) {
console . log ( 'Found account:' , {
owner: account . owner . toBase58 (),
lamports: account . lamports . toString (),
hasData: account . data !== null ,
});
}
By Address
Query by account address (if the account has one):
const address = new Uint8Array ([ /* 32 bytes */ ]);
const account = await rpc . getCompressedAccount (
address , // address
undefined // hash
);
Multiple Accounts
Batch fetch multiple accounts:
const hashes = [
bn ( 'hash1...' ),
bn ( 'hash2...' ),
bn ( 'hash3...' ),
];
const accounts = await rpc . getMultipleCompressedAccounts ( hashes );
accounts . forEach (( account , i ) => {
console . log ( `Account ${ i } :` , account . lamports . toString ());
});
Creating Compressed Accounts
Compress SOL
Create a compressed account by compressing regular SOL:
import { compress } from '@lightprotocol/stateless.js' ;
import { Keypair } from '@solana/web3.js' ;
const payer = Keypair . generate ();
const recipient = Keypair . generate (). publicKey ;
// Compress 0.01 SOL into a compressed account
const signature = await compress (
rpc ,
payer , // Fee payer
10_000_000 , // 0.01 SOL in lamports
recipient // Owner of compressed account
);
// Query the new account
const accounts = await rpc . getCompressedAccountsByOwner ( recipient );
console . log ( 'New account lamports:' , accounts . items [ 0 ]. lamports . toString ());
With Custom State Tree
Specify which state tree to use:
import { selectStateTreeInfo } from '@lightprotocol/stateless.js' ;
// Get available trees
const treeInfos = await rpc . getStateTreeInfos ();
const selectedTree = selectStateTreeInfo ( treeInfos );
// Compress to specific tree
await compress (
rpc ,
payer ,
10_000_000 ,
recipient ,
selectedTree // Optional: specify output tree
);
Create Account with Address (V1 Only)
This method is only available in V1 and is deprecated. Use program-specific account creation methods instead.
import { createAccount , LightSystemProgram } from '@lightprotocol/stateless.js' ;
const address = new Uint8Array ([ /* 32 bytes */ ]);
await createAccount (
rpc ,
payer ,
[ address ], // Array of addresses to create
LightSystemProgram . programId , // Owner program
undefined , // No data
stateTree // Output state tree
);
Transferring Compressed Accounts
Basic Transfer
Transfer compressed SOL from one owner to another:
import { transfer } from '@lightprotocol/stateless.js' ;
const owner = Keypair . generate ();
const recipient = Keypair . generate (). publicKey ;
// Transfer 0.001 SOL
const signature = await transfer (
rpc ,
payer , // Fee payer
1_000_000 , // Amount in lamports
owner , // Current owner (signer)
recipient // Destination
);
How Transfers Work
Input Selection : SDK automatically selects compressed accounts to cover the amount
Proof Generation : Requests validity proof from the RPC
Account Creation : Creates output accounts (recipient + change)
Nullification : Marks input accounts as spent
// The SDK does this automatically:
const accounts = await rpc . getCompressedAccountsByOwner ( owner . publicKey );
// Select minimum accounts needed
const [ inputAccounts ] = selectMinCompressedSolAccountsForTransfer (
accounts . items ,
amount
);
// Get validity proof
const proof = await rpc . getValidityProof (
inputAccounts . map ( acc => bn ( acc . hash ))
);
// Build instruction
const ix = await LightSystemProgram . transfer ({
payer: payer . publicKey ,
inputCompressedAccounts: inputAccounts ,
toAddress: recipient ,
lamports: amount ,
recentInputStateRootIndices: proof . rootIndices ,
recentValidityProof: proof . compressedProof ,
});
Decompressing Accounts
Back to Regular SOL
Convert compressed SOL back to regular Solana accounts:
import { decompress } from '@lightprotocol/stateless.js' ;
const owner = Keypair . generate ();
const recipient = Keypair . generate (). publicKey ;
// Decompress 0.001 SOL to recipient's regular account
const signature = await decompress (
rpc ,
payer , // Fee payer
1_000_000 , // Amount to decompress
recipient // Destination (regular Solana account)
);
// Check regular balance
const balance = await rpc . getBalance ( recipient );
console . log ( 'Decompressed balance:' , balance );
Querying Balances
Total Balance by Owner
Get total compressed SOL balance:
const balance = await rpc . getCompressedBalanceByOwner ( owner . publicKey );
console . log ( 'Total compressed SOL:' , balance . toString (), 'lamports' );
Single Account Balance
Get balance of a specific compressed account:
const balance = await rpc . getCompressedBalance (
undefined , // address
accountHash // hash
);
console . log ( 'Account balance:' , balance . toString (), 'lamports' );
Merkle Proofs
Get Account Proof
Fetch the Merkle proof for an account:
const proof = await rpc . getCompressedAccountProof ( accountHash );
console . log ( 'Proof:' , {
hash: proof . hash . toString (),
leafIndex: proof . leafIndex ,
root: proof . root . toString (),
merkleProof: proof . merkleProof . map ( p => p . toString ()),
});
Batch Proof Requests
Get proofs for multiple accounts:
const hashes = [
bn ( 'hash1...' ),
bn ( 'hash2...' ),
bn ( 'hash3...' ),
];
const proofs = await rpc . getMultipleCompressedAccountProofs ( hashes );
proofs . forEach (( proof , i ) => {
console . log ( `Proof ${ i } root:` , proof . root . toString ());
});
Account Filtering
With Filters
Filter accounts by data pattern:
const response = await rpc . getCompressedAccountsByOwner ( owner , {
filters: [
{
memcmp: {
offset: 0 ,
bytes: 'base58string...' ,
},
},
],
});
With Data Slice
Limit the data returned:
const response = await rpc . getCompressedAccountsByOwner ( owner , {
dataSlice: {
offset: 0 ,
length: 32 , // Only return first 32 bytes
},
});
Transaction Signatures
Get Signatures for Account
Find all transactions involving a compressed account:
const signatures = await rpc . getCompressionSignaturesForAccount (
accountHash
);
signatures . forEach ( sig => {
console . log ( 'Transaction:' , {
signature: sig . signature ,
slot: sig . slot ,
blockTime: new Date ( sig . blockTime * 1000 ),
});
});
Get Signatures by Owner
Find all compression transactions for an owner:
const signatures = await rpc . getCompressionSignaturesForOwner (
owner . publicKey ,
{
cursor: undefined ,
limit: bn ( 100 ),
}
);
console . log ( 'Found' , signatures . items . length , 'transactions' );
Best Practices
Account Selection
When selecting accounts for transfers:
import { selectMinCompressedSolAccountsForTransfer } from '@lightprotocol/stateless.js' ;
const accounts = await rpc . getCompressedAccountsByOwner ( owner . publicKey );
// Select minimum accounts to cover the amount
const [ selectedAccounts , totalAmount ] =
selectMinCompressedSolAccountsForTransfer (
accounts . items ,
requiredAmount
);
if ( totalAmount . lt ( requiredAmount )) {
throw new Error ( 'Insufficient balance' );
}
Error Handling
Handle common errors:
try {
const account = await rpc . getCompressedAccount ( undefined , hash );
if ( ! account ) {
console . log ( 'Account not found or already spent' );
}
} catch ( error ) {
if ( error . message . includes ( 'failed to get info' )) {
console . error ( 'RPC error:' , error );
} else {
throw error ;
}
}
Transaction Confirmation
Wait for transaction confirmation:
import type { ConfirmOptions } from '@solana/web3.js' ;
const confirmOptions : ConfirmOptions = {
commitment: 'confirmed' ,
skipPreflight: false ,
};
const signature = await transfer (
rpc ,
payer ,
amount ,
owner ,
recipient ,
confirmOptions
);
// Wait for finalization
await rpc . confirmTransaction ( signature , 'finalized' );
Next Steps
RPC Methods Complete RPC method reference
Compressed Tokens Work with compressed SPL tokens