Skip to main content
// Compress SPL tokens to compressed tokens
const compressionSignature = await compress(
    rpc,
    payer,
    mint, // SPL mint with token pool for compression
    amount,
    payer, // owner of SPL tokens
    tokenAccount.address, // source SPL token account (sourceTokenAccount parameter)
    recipient, // recipient owner address (toAddress parameter)
);
Function Difference and Best Practice:
  • compress(amount, sourceTokenAccount, toAddress) compresses specific amounts from source to a specified recipient. Use for transfers and precise amounts.
  • compressSplTokenAccount(tokenAccount, remainingAmount) compresses the entire SPL token account balance minus optional remaining amount only to the same owner. Use to migrate complete token accounts with optional partial retention. Here is how.

Get Started

1

Compress / Decompress Tokens

Install packages in your working directory:
npm install @lightprotocol/stateless.js@alpha \
            @lightprotocol/compressed-token@alpha
Install the CLI globally:
npm install -g @lightprotocol/zk-compression-cli@alpha
# start local test-validator in a separate terminal
light test-validator
In the code examples, use createRpc() without arguments for localnet.
Before we can compress or decompresss, we need:
  • An SPL mint with a token pool for compression. This token pool can be created for new SPL mints via createMint() or added to existing SPL mints via createTokenPool().
  • For compress() SPL tokens in an Associated Token Account, or
  • For decompress() compressed token accounts with sufficient balance.
import "dotenv/config";
import { Keypair } from "@solana/web3.js";
import { createRpc } from "@lightprotocol/stateless.js";
import { createMint, mintTo, decompress, compress } from "@lightprotocol/compressed-token";
import { createAssociatedTokenAccount } from "@solana/spl-token";
import { homedir } from "os";
import { readFileSync } from "fs";

// devnet:
const RPC_URL = `https://devnet.helius-rpc.com?api-key=${process.env.API_KEY!}`;
// localnet:
// const RPC_URL = undefined;
const payer = Keypair.fromSecretKey(
    new Uint8Array(
        JSON.parse(readFileSync(`${homedir()}/.config/solana/id.json`, "utf8"))
    )
);

(async function () {
    // devnet:
    const rpc = createRpc(RPC_URL);
    // localnet:
    // const rpc = createRpc();

    // Setup: Get SPL tokens (needed to compress)
    const { mint } = await createMint(rpc, payer, payer.publicKey, 9);
    const splAta = await createAssociatedTokenAccount(rpc, payer, mint, payer.publicKey);
    await mintTo(rpc, payer, mint, payer.publicKey, payer, 1_000_000_000);
    await decompress(rpc, payer, mint, 1_000_000_000, payer, splAta);

    // Compress SPL tokens
    const recipient = Keypair.generate();
    const tx = await compress(rpc, payer, mint, 500_000_000, payer, splAta, recipient.publicKey);

    console.log("Mint:", mint.toBase58());
    console.log("Tx:", tx);
})();
Make sure the SPL mint has a token pool for compression.
The script creates this token pool for you.
For development, you can create a new mint with token pool via createMint() or add a token pool to an existing mint via createTokenPool().

Troubleshooting

Check your balances before operations:
// For decompression - check compressed balance
const compressedAccounts = await rpc.getCompressedTokenAccountsByOwner(
    owner.publicKey,
    { mint }
);
const compressedBalance = compressedAccounts.items.reduce(
    (sum, account) => sum.add(account.parsed.amount),
    new BN(0)
);

// For compression - check SPL token balance
const tokenBalance = await rpc.getTokenAccountBalance(tokenAccount);
const splBalance = new BN(tokenBalance.value.amount);

console.log("Can decompress up to:", compressedBalance.toString());
console.log("Can compress up to:", splBalance.toString());
Ensure the signer owns the tokens being decompressed/compressed:
// The owner parameter must be the actual owner
const decompressTx = await decompress(
    rpc,
    payer, // can be different (pays fees)
    mint,
    amount,
    actualOwner, // must own compressed tokens
    destinationAta,
);

const compressTx = await compress(
    rpc,
    payer, // can be different (pays fees)
    mint,
    amount,
    actualOwner, // must own SPL tokens
    sourceAta,
    recipient,
);

Advanced Configuration

Compress tokens directly to someone else:
const recipientWallet = new PublicKey("RECIPIENT_WALLET_ADDRESS");

// Compress your SPL tokens to recipient
const compressTx = await compress(
    rpc,
    payer,
    mint,
    amount,
    tokenOwner, // current owner signs
    tokenAccount, // your token account
    recipientWallet, // recipient gets compressed tokens
);
Compress multiple token accounts:
// Compress to multiple recipients at once
const recipients = [recipient1.publicKey, recipient2.publicKey, recipient3.publicKey];
const amounts = [1_000_000_000, 2_000_000_000, 500_000_000]; // Different amounts

const batchCompressTx = await compress(
    rpc,
    payer,
    mint,
    amounts, // Array of amounts
    owner,
    tokenAccount,
    recipients, // Array of recipients
);

console.log("Batch compression completed:", batchCompressTx);
Decompress tokens using delegate authority:
import { decompressDelegated } from '@lightprotocol/compressed-token';
import { getAssociatedTokenAddress, TOKEN_PROGRAM_ID } from '@solana/spl-token';

// Get ATA for decompressed tokens
const ataAddress = await getAssociatedTokenAddress(
    mint,
    recipient,
    false,
    TOKEN_PROGRAM_ID
);

// Delegate decompresses tokens
await decompressDelegated(
    rpc,
    payer,
    mint,
    amount,
    delegate, // Signer - owner of compressed tokens
    ataAddress, // Uncompressed token account (ATA)
);

Next Steps

How to Compress Complete SPL Token Accounts