Add Wallet Support for Compressed Tokens

Guide to add Compressed Token Support to Your Wallet Application

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

Integration Steps

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('FWwR2s4TwpWN3nkCzVfhuPrpePG8kNzBXAxEbNsaDFNu');

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

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

    for (const item of balances.value.items) {
        const balanceValue = typeof item.balance === 'string'
            ? parseInt(item.balance, 16)
            : item.balance;

        const mintInfo = await connection.getAccountInfo(new PublicKey(item.mint));
        const decimals = mintInfo.data[44];
        const formattedBalance = balanceValue / Math.pow(10, decimals);

        console.log(`Mint: ${item.mint}`);
        console.log(`Balance: ${formattedBalance} tokens\n`);
    }
})();

Get Transaction History

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

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

(async () => {
    const signatures = await connection.getCompressionSignaturesForTokenOwner(publicKey);

    if (signatures.items.length > 0) {
        console.log(`Signatures:`);
        signatures.items.forEach((sig, index) => {
            console.log(`${index + 1}. ${sig.signature}`);
            console.log(`   Slot: ${sig.slot}`);
            console.log(`   Time: ${new Date(sig.blockTime * 1000).toISOString()}`);
        });
    } else {
        console.log("No transactions found");
    }
})();
Get full transaction history on devnet or mainnet with getTransactionWithCompressionInfo
import { Rpc, createRpc } from '@lightprotocol/stateless.js';
import { PublicKey } from '@solana/web3.js';

const RPC_ENDPOINT = process.env.RPC_ENDPOINT || 'https://devnet.helius-rpc.com?api-key=<your-api-key>';

const connection: Rpc = createRpc(RPC_ENDPOINT, RPC_ENDPOINT);
const publicKey = new PublicKey('FWwR2s4TwpWN3nkCzVfhuPrpePG8kNzBXAxEbNsaDFNu');

(async () => {
    try {
        const signatures = await connection.getCompressionSignaturesForTokenOwner(publicKey);

        if (signatures.items.length === 0) {
            console.log("No transactions found");
            return;
        }

        console.log(`Latest Transaction:`);
        console.log(`Signature: ${signatures.items[0].signature}`);
        console.log(`Slot: ${signatures.items[0].slot}`);

        const txInfo = await connection.getTransactionWithCompressionInfo(signatures.items[0].signature);

        if (!txInfo) {
            console.log('Transaction not found or has no compression info');
            return;
        }

        if (txInfo.compressionInfo) {
            console.log(`\nClosed Accounts: ${txInfo.compressionInfo.closedAccounts.length}`);
            console.log(`Opened Accounts: ${txInfo.compressionInfo.openedAccounts.length}`);
        }
    } catch (error) {
        console.error('Error fetching transaction history:', error);
    }
})();

Send Compressed Tokens

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.

Decompress to Regular SPL

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);
})();
Compress Regular SPL Tokens

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

No compressed tokens found

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.

Guides

Last updated

Was this helpful?