Skip to main content

Best Practices

  • Clear UI Indicators — Provide clear visual distinctions between compressed and uncompressed SPL tokens
  • Transaction History — Provide detailed transaction histories for compressed tokens
  • Decompression and Compression — Provide a clear path for users to convert between compressed and uncompressed tokens when needed
Leading Solana Wallets like Phantom and Backpack already support compressed tokens.

Integration Steps

  • Code Snippets
  • End-to-End Guide

Display Compressed Token Balances

import { Rpc, createRpc } from '@lightprotocol/stateless.js';
import { PublicKey } from '@solana/web3.js';

const connection: Rpc = createRpc();
const publicKey = new PublicKey('CLEuMG7pzJX9xAuKCFzBP154uiG1GaNo4Fq7x6KAcAfG');

(async () => {
    const balances = await connection.getCompressedTokenBalancesByOwnerV2(publicKey);
    console.log(balances);
})();

Get Transaction History

import { Rpc, createRpc } from '@lightprotocol/stateless.js';
import { PublicKey } from '@solana/web3.js';

const connection: Rpc = createRpc();
const publicKey = new PublicKey('CLEuMG7pzJX9xAuKCFzBP154uiG1GaNo4Fq7x6KAcAfG');

(async () => {
    const signatures = await connection.getCompressionSignaturesForOwner(publicKey);
    console.log(signatures);

    if (signatures.items.length > 0) {
        const parsedTransaction = await connection.getTransactionWithCompressionInfo(signatures.items[0].signature);
        console.log(parsedTransaction);
    }
})();

Send Compressed Tokens

send-compressed-tokens.ts
import {
  Rpc,
  createRpc,
  bn,
  dedupeSigner,
  sendAndConfirmTx,
  buildAndSignTx,
} from "@lightprotocol/stateless.js";
import {
  CompressedTokenProgram,
  selectMinCompressedTokenAccountsForTransfer,
} from "@lightprotocol/compressed-token";
import { ComputeBudgetProgram, Keypair, PublicKey } from "@solana/web3.js";

const connection: Rpc = createRpc();
const mint = new PublicKey("MINT_ADDRESS");
const payer = PAYER_KEYPAIR;
const owner = payer;
const recipient = Keypair.generate();
const amount = bn(1e8);

(async () => {
  const compressedTokenAccounts =
    await connection.getCompressedTokenAccountsByOwner(owner.publicKey, { mint });

  if (compressedTokenAccounts.items.length === 0) {
    console.log("No compressed token accounts found");
    return;
  }

  const [inputAccounts] = selectMinCompressedTokenAccountsForTransfer(
    compressedTokenAccounts.items,
    amount
  );

  const proof = await connection.getValidityProof(
    inputAccounts.map((account) => account.compressedAccount.hash)
  );

  const ix = await CompressedTokenProgram.transfer({
    payer: payer.publicKey,
    inputCompressedTokenAccounts: inputAccounts,
    toAddress: recipient.publicKey,
    amount,
    recentInputStateRootIndices: proof.rootIndices,
    recentValidityProof: proof.compressedProof,
  });

  const { blockhash } = await connection.getLatestBlockhash();
  const additionalSigners = dedupeSigner(payer, [owner]);
  const signedTx = buildAndSignTx(
    [ComputeBudgetProgram.setComputeUnitLimit({ units: 300_000 }), ix],
    payer,
    blockhash,
    additionalSigners
  );

  const transferTxId = await sendAndConfirmTx(connection, signedTx);
  console.log(`Transaction: ${transferTxId}`);
})();

Advanced Integrations

Use these integrations to let users convert between regular and compressed format as needed.
This example converts compressed tokens to regular SPL format using CompressedTokenProgram.decompress().
import {
  bn,
  buildAndSignTx,
  sendAndConfirmTx,
  dedupeSigner,
  Rpc,
  createRpc,
} from "@lightprotocol/stateless.js";
import { ComputeBudgetProgram } from "@solana/web3.js";
import {
  CompressedTokenProgram,
  getTokenPoolInfos,
  selectMinCompressedTokenAccountsForTransfer,
  selectTokenPoolInfosForDecompression,
} from "@lightprotocol/compressed-token";

// 1. Setup RPC connection and fetch compressed token accounts with getCompressedTokenAccountsByOwner()
// 2. Select accounts and token pool infos using selectMinCompressedTokenAccountsForTransfer() and selectTokenPoolInfosForDecompression()
// 3. Create decompress instruction with CompressedTokenProgram.decompress() and submit transaction

// Step 1: Setup RPC connection and define decompression parameters
const connection: Rpc = createRpc("https://mainnet.helius-rpc.com?api-key=<api_key>";);
const payer = PAYER_KEYPAIR;
const owner = PAYER_KEYPAIR;
const mint = MINT_ADDRESS;
const amount = 1e5; // 100K tokens to decompress

(async () => {
  // 1. Fetch compressed token accounts
  const compressedTokenAccounts =
    await connection.getCompressedTokenAccountsByOwner(owner.publicKey, {
      mint,
    });

  // 2. Select
  const [inputAccounts] = selectMinCompressedTokenAccountsForTransfer(
    compressedTokenAccounts.items,
    bn(amount)
  );

  // 3. Fetch validity proof
  const proof = await connection.getValidityProof(
    inputAccounts.map((account) => account.compressedAccount.hash)
  );

  // 4. Fetch & Select tokenPoolInfos
  const tokenPoolInfos = await getTokenPoolInfos(connection, mint);
  const selectedTokenPoolInfos = selectTokenPoolInfosForDecompression(
    tokenPoolInfos,
    amount
  );

  // 5. Build instruction
  const ix = await CompressedTokenProgram.decompress({
    payer: payer.publicKey,
    inputCompressedTokenAccounts: inputAccounts,
    toAddress: owner.publicKey,
    amount,
    tokenPoolInfos: selectedTokenPoolInfos,
    recentInputStateRootIndices: proof.rootIndices,
    recentValidityProof: proof.compressedProof,
  });


  // 6. Sign, send, and confirm.
  // Example with keypair:
  const { blockhash } = await connection.getLatestBlockhash();
  const additionalSigners = dedupeSigner(payer, [owner]);
  const signedTx = buildAndSignTx(
    [ComputeBudgetProgram.setComputeUnitLimit({ units: 300_000 }), ix],
    payer,
    blockhash,
    additionalSigners
  );

  return await sendAndConfirmTx(connection, signedTx);
})();
This example converts regular SPL tokens to compressed format using CompressedTokenProgram.compress().
// 1. Setup RPC connection and get user ATA with getOrCreateAssociatedTokenAccount()
// 2. Fetch state tree and token pool infos using getStateTreeInfos() and getTokenPoolInfos()
// 3. Create compress instruction with CompressedTokenProgram.compress() and submit transaction


import {
  buildAndSignTx,
  sendAndConfirmTx,
  Rpc,
  createRpc,
  selectStateTreeInfo,
} from "@lightprotocol/stateless.js";
import { ComputeBudgetProgram } from "@solana/web3.js";
import {
  CompressedTokenProgram,
  getTokenPoolInfos,
  selectTokenPoolInfo,
} from "@lightprotocol/compressed-token";
import { getOrCreateAssociatedTokenAccount } from "@solana/spl-token";

// Step 1: Setup RPC connection and define compression parameters
const connection: Rpc = createRpc(
  "https://mainnet.helius-rpc.com?api-key=<api_key>"
);
  const payer = <PAYER_KEYPAIR>;
  const mint = <MINT_ADDRESS>;
const amount = 1e5; // 100K tokens to compress

(async () => {
  // Step 2: Get or create associated token account for SPL tokens
  const sourceTokenAccount = await getOrCreateAssociatedTokenAccount(
    connection,
    payer, // fee payer
    mint, // token mint address
    payer.publicKey // token account owner
  );

  // Step 3: Fetch and select state tree info for compression
  const treeInfos = await connection.getStateTreeInfos();
  const treeInfo = selectStateTreeInfo(treeInfos);

  // Step 4: Fetch and select token pool info for compression
  const tokenPoolInfos = await getTokenPoolInfos(connection, mint);
  const tokenPoolInfo = selectTokenPoolInfo(tokenPoolInfos);

  // Step 5: Create compress instruction - transfer SPL tokens to pool and create compressed accounts
  const compressInstruction = await CompressedTokenProgram.compress({
    payer: payer.publicKey, // fee payer
    owner: payer.publicKey, // owner of source SPL tokens
    source: sourceTokenAccount.address, // source ATA address
    toAddress: payer.publicKey, // recipient of compressed tokens (self)
    amount, // amount to compress
    mint, // token mint address
    outputStateTreeInfo: treeInfo, // state tree for compressed accounts
    tokenPoolInfo, // token pool for compression
  });

  // Step 6: Build, sign, and submit compression transaction
  const { blockhash } = await connection.getLatestBlockhash();
  const tx = buildAndSignTx(
    [
      ComputeBudgetProgram.setComputeUnitLimit({ units: 300_000 }),
      compressInstruction,
    ],
    payer, // transaction signer
    blockhash,
    [payer] // additional signers
  );
  await sendAndConfirmTx(connection, tx);
})();

Common Errors

If getCompressedTokenBalancesByOwnerV2 returns empty:
  • Ensure the wallet has compressed tokens (not regular SPL tokens)
  • Verify you’re on the correct network (devnet/mainnet)

Next Steps

Take a look at other compressed token guides.