Skip to main content
The transfer() function moves compressed tokens between accounts.
  • SPL token transfers that update the existing account
  • Compressed token transfers consume input accounts from the sender and create new output accounts for sender and recipient with updated balances.
SPL token accounts can be compressed in the same transaction with compress_or_decompress_amount, if needed.
function-transfer-compressed-tokens.ts
// Transfer compressed tokens
const transactionSignature = await transfer(
    rpc,
    payer,
    mint, // SPL mint with token pool for compression
    amount,
    payer,
    recipient, // destination address (toAddress parameter)
)

Get Started

1

Transfer Compressed 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.
import "dotenv/config";
import { Keypair } from "@solana/web3.js";
import { createRpc } from "@lightprotocol/stateless.js";
import { createMint, mintTo, transfer } from "@lightprotocol/compressed-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: Create mint and mint tokens
    const { mint } = await createMint(rpc, payer, payer.publicKey, 9);
    const sender = Keypair.generate();
    await mintTo(rpc, payer, mint, sender.publicKey, payer, 1_000_000_000);

    // Transfer compressed tokens
    const recipient = Keypair.generate();
    const tx = await transfer(rpc, payer, mint, 500_000_000, sender, recipient.publicKey);

    console.log("Mint:", mint.toBase58());
    console.log("Recipient:", recipient.publicKey.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

The sender doesn’t have enough compressed tokens for the requested transfer amount.
// Check current balance first
const tokenAccounts = await rpc.getCompressedTokenAccountsByOwner(
    owner.publicKey,
    { mint }
);

if (tokenAccounts.items.length === 0) {
    throw new Error("No compressed token accounts found");
}

// Calculate total balance across all accounts
const totalBalance = tokenAccounts.items.reduce(
    (sum, account) => sum.add(account.parsed.amount),
    new BN(0)
);

console.log("Available balance:", totalBalance.toString());

// Ensure transfer amount doesn't exceed balance
if (new BN(transferAmount).gt(totalBalance)) {
    throw new Error(`Transfer amount ${transferAmount} exceeds balance ${totalBalance.toString()}`);
}
The transfer requires more than 4 compressed accounts, which exceeds the transaction limit.
// Error message: "Account limit exceeded: max X (4 accounts) per transaction.
// Total balance: Y (Z accounts). Consider multiple transfers to spend full balance."

// Split into multiple smaller transfers
const maxTransferPerTx = 1_000_000_000; // Adjust based on your account sizes

if (transferAmount > maxTransferPerTx) {
    console.log("Large transfer detected, splitting into multiple transactions...");

    let remainingAmount = transferAmount;
    while (remainingAmount > 0) {
        const currentTransfer = Math.min(remainingAmount, maxTransferPerTx);

        await transfer(
            rpc,
            payer,
            mint,
            currentTransfer,
            owner,
            recipient
        );

        remainingAmount -= currentTransfer;
        console.log(`Transferred ${currentTransfer}, remaining: ${remainingAmount}`);
    }
}

Advanced Configuration

Transfer to multiple recipients in separate transactions:
const recipients = [
    Keypair.generate().publicKey,
    Keypair.generate().publicKey,
    Keypair.generate().publicKey,
];

const amounts = [
    100_000_000, // 0.1 tokens
    200_000_000, // 0.2 tokens
    150_000_000, // 0.15 tokens
];

for (let i = 0; i < recipients.length; i++) {
    const transactionSignature = await transfer(
        rpc,
        payer,
        mint,
        amounts[i],
        owner,
        recipients[i],
    );

    console.log(`Transfer ${i + 1} completed:`, transactionSignature);
}
Transfer tokens using delegate authority:
import { approve, transferDelegated } from '@lightprotocol/compressed-token';

// 1. Owner approves delegate
await approve(
    rpc,
    payer,
    mint,
    amount,
    owner, // Signer
    delegate.publicKey, // PublicKey
);

// 2. Delegate transfers tokens
await transferDelegated(
    rpc,
    payer,
    mint,
    transferAmount,
    delegate, // Signer - named "owner" in SDK
    recipient,
);

Next Steps

How to Compress and Decompress SPL Tokens