Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/Lightprotocol/light-protocol/llms.txt

Use this file to discover all available pages before exploring further.

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,
  });
});

With Pagination

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

  1. Input Selection: SDK automatically selects compressed accounts to cover the amount
  2. Proof Generation: Requests validity proof from the RPC
  3. Account Creation: Creates output accounts (recipient + change)
  4. 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