The transfer() function moves compressed tokens between accounts. Unlike regular SPL transfers that update existing account balances, compressed transfers consume input accounts from the sender and create new output accounts for sender and recipient with updated balances.
Before we can transfer compressed tokens, 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() ., and
a source compressed token account with sufficient balance for the transfer amount.
Regular 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
Prerequisites
Make sure you have dependencies and developer environment set up!
Dependencies npm install --save-dev typescript tsx @types/node && \
npm install --save \
@lightprotocol/stateless.js \
@lightprotocol/compressed-token \
@solana/web3.js \
@solana/spl-token
Alternatives: yarn add --dev typescript tsx @types/node && \
yarn add \
@lightprotocol/stateless.js \
@lightprotocol/compressed-token \
@solana/web3.js \
@solana/spl-token
pnpm add --save-dev typescript tsx @types/node && \
pnpm add \
@lightprotocol/stateless.js \
@lightprotocol/compressed-token \
@solana/web3.js \
@solana/spl-token
Developer Environment By default, this guide uses Localnet. # Install the development CLI
npm install @lightprotocol/zk-compression-cli
# 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 ();
Alternative: Using Devnet Follow these steps to create an RPC Connection. Replace <your_api_key> with your API key before running. Get your API Key here , if you don’t have one yet. import { createRpc } from "@lightprotocol/stateless.js" ;
// Helius exposes Solana and Photon RPC endpoints through a single URL
const RPC_ENDPOINT = "https://devnet.helius-rpc.com?api-key=<your_api_key>" ;
const connection = createRpc ( RPC_ENDPOINT , RPC_ENDPOINT , RPC_ENDPOINT );
console . log ( "Connection created!" );
console . log ( "RPC Endpoint:" , RPC_ENDPOINT );
Transferring 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 ( " \n Compressed 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 ( " \n Compressed 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 );
See all 83 lines
Troubleshooting
Insufficient balance for transfer
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
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 with Delegate Authority
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 compress and decompress SPL Tokens.