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)
)

Full Code Example

1

Prerequisites

Make sure you have dependencies and developer environment set up!
Dependencies
npm install @lightprotocol/stateless.js@alpha \
            @lightprotocol/compressed-token@alpha
Developer Environment
By default, all guides use Localnet.
npm install -g @lightprotocol/zk-compression-cli@alpha
# Start a local test validator
light test-validator

## ensure you have the Solana CLI accessible in your system PATH
// createRpc() defaults to local test validator endpoints
import {
  Rpc,
  createRpc,
} from "@lightprotocol/stateless.js";

const connection: Rpc = createRpc();

async function main() {
  let slot = await connection.getSlot();
  console.log(slot);

  let health = await connection.getIndexerHealth(slot);
  console.log(health);
  // "Ok"
}

main();
2

Transfer Compressed Tokens

Run this script to transfer compressed tokens to a recipient!
transfer-compressed-tokens.ts
// 1. Setup funded payer and connect to local validator
// 2. Create SPL mint and token pool for compression with initial tokens
// 3. Call transfer() with mint, amount, owner, recipient
// 4. Verify transferred tokens via getCompressedTokenAccountsByOwner

import { Keypair, PublicKey } from '@solana/web3.js';
import { createRpc } from '@lightprotocol/stateless.js';
import { createMint, mintTo, transfer } from '@lightprotocol/compressed-token';

async function transferCompressedTokens() {
    // Step 1: Setup funded payer and connect to local validator
    const rpc = createRpc(); // defaults to localhost:8899
    const payer = Keypair.generate();
    const airdropSignature = await rpc.requestAirdrop(payer.publicKey, 1000000000); // 1 SOL
    await rpc.confirmTransaction(airdropSignature);

    // Step 2a: Create SPL mint with token pool for compression
    const { mint, transactionSignature: mintCreateTx } = await createMint(
        rpc,
        payer,
        payer.publicKey, // mint authority
        9
    );

    console.log("SPL mint with token pool for compression created");
    console.log("Mint address:", mint.toBase58());
    console.log("Create mint transaction:", mintCreateTx);

    // Step 2b: Create token owner and mint initial tokens
    const tokenOwner = Keypair.generate();
    const initialMintAmount = 1_000_000_000; // 1 token with 9 decimals

    const mintToTx = await mintTo(
        rpc,
        payer,
        mint, // SPL mint with token pool for compression
        tokenOwner.publicKey, // recipient
        payer, // mint authority
        initialMintAmount
    );

    console.log("\nCompressed Tokens minted:", initialMintAmount / 1_000_000_000, "tokens");
    console.log("Mint tokens transaction:", mintToTx);

    // Generate recipient address and define transfer amount
    const recipient = Keypair.generate();
    const transferAmount = 500_000_000; // 0.5 tokens

    // Step 3: Call transfer() with mint, amount, owner, recipient
    const transferTx = await transfer(
        rpc,
        payer,
        mint, // SPL mint with token pool for compression
        transferAmount,
        tokenOwner, // owner keypair
        recipient.publicKey // recipient address
    );

    console.log("\nCompressed tokens transferred!");
    console.log("From:", tokenOwner.publicKey.toBase58());
    console.log("To:", recipient.publicKey.toBase58());
    console.log("Amount transferred:", transferAmount / 1_000_000_000, "tokens");
    console.log("Transfer transaction:", transferTx);

    // Step 4: Verify transferred tokens via getCompressedTokenAccountsByOwner
    const recipientAccounts = await rpc.getCompressedTokenAccountsByOwner(
        recipient.publicKey,
        { mint }
    );

    // Check recipient received the tokens
    if (recipientAccounts.items.length > 0) {
        const receivedBalance = recipientAccounts.items[0].parsed.amount;
    }

    return {
        transferTransaction: transferTx,
        recipient: recipient.publicKey,
        amount: transferAmount
    };
}

transferCompressedTokens().catch(console.error);
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

Learn how to compress and decompress SPL tokens.