Skip to main content
Light Token -> Light Token Account
  • Transfers tokens between Light Token accounts
Token 2022 / SPL token -> Light Token Account
  • Transfers SPL tokens to Light Token accounts
  • SPL / Token 2022 tokens are locked in interface PDA
  • Tokens are minted to Light Token account
Light Token -> SPL / Token 2022 Account
  • Releases SPL / Token 2022 tokens from interface PDA to SPL account
  • Burns tokens in source Light Token account
Install the agent skill:
npx skills add https://zkcompression.com
See the AI tools guide for dedicated skills.
The transferInterface function transfers tokens between token accounts (SPL, Token 2022, or Light) in a single call and checks decimals.Compare to SPL:
Find the source code here.
1

Transfer Interface

Install packages in your working directory:
npm install @lightprotocol/stateless.js@beta \
            @lightprotocol/compressed-token@beta
Install the CLI globally:
npm install -g @lightprotocol/zk-compression-cli@beta
# 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 {
    createMintInterface,
    createAtaInterface,
    mintToInterface,
    transferInterface,
    getAssociatedTokenAddressInterface,
} 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!}`;
// const rpc = createRpc(RPC_URL);
// localnet:
const rpc = createRpc();

const payer = Keypair.fromSecretKey(
    new Uint8Array(
        JSON.parse(readFileSync(`${homedir()}/.config/solana/id.json`, "utf8"))
    )
);

(async function () {
    const { mint } = await createMintInterface(rpc, payer, payer, null, 9);

    const sender = Keypair.generate();
    await createAtaInterface(rpc, payer, mint, sender.publicKey);
    const senderAta = getAssociatedTokenAddressInterface(
        mint,
        sender.publicKey
    );
    await mintToInterface(rpc, payer, mint, senderAta, payer, 1_000_000_000);

    const recipient = Keypair.generate();
    await createAtaInterface(rpc, payer, mint, recipient.publicKey);
    const recipientAta = getAssociatedTokenAddressInterface(
        mint,
        recipient.publicKey
    );

    // Transfer tokens between light-token associated token accounts
    // destination is recipient wallet; transferInterface creates recipient ATA idempotently
    const tx = await transferInterface(
        rpc,
        payer,
        senderAta,
        mint,
        recipient.publicKey,
        sender,
        500_000_000
    );

    console.log("Tx:", tx);
})();

Advanced: Explicit Destination Account

For PDA destinations or program-owned accounts where you need to specify the exact destination token account, use transferToAccountInterface. This is an edge case; most transfers should use transferInterface with a wallet address.
import {
    transferToAccountInterface,
    getAssociatedTokenAddressInterface,
} from "@lightprotocol/compressed-token";

// The destination must be an existing token account (not a wallet address).
await transferToAccountInterface(
    rpc,
    payer,
    sourceAta,
    mint,
    destinationTokenAccount,
    owner,
    amount,
);
Both transferInterface and transferToAccountInterface reject off-curve owners. For PDA-owned source accounts (smart wallets), use createLightTokenTransferInstruction.

Wrap and unwrap

Payments overview


Didn’t find what you were looking for?

Reach out! Telegram | email | Discord