Skip to main content
Encrypt token transfer amounts using ElGamal encryption so they are not visible on-chain, while keeping account addresses public and balances auditable by a designated auditor key.
Light Token supports confidential transfer extensions in their initialized-but-disabled state only. You can create Token-2022 mints with these extensions configured and register them with Light Token, but the confidential transfer features (encrypted amounts, confidential fees, confidential minting/burning) cannot be actively used through Light Token.

Key Parameters

The confidential transfer family includes three extensions:
ExtensionParametersLight Token restriction
ConfidentialTransferMintauthority, autoApproveNewAccounts, auditorElGamalPubkeyInitialized but not enabled. The extension can exist on the mint, but encrypted transfers cannot be performed through Light Token.
ConfidentialTransferFeeConfigauthority, withdrawWithheldAuthorityElGamalPubkey, harvestToMintEnabled, withheldAmountFees must be zero. The extension can be initialized for compatibility, but confidential fee collection is not supported.
ConfidentialMintBurnconfidentialSupply, decryptableSupply, supplyElGamalPubkeyInitialized but not enabled. The extension can exist on the mint, but encrypted minting and burning cannot be performed through Light Token.

Use Confidential Transfer With Light Token

Install the agent skill:
npx skills add https://zkcompression.com
See the AI tools guide for dedicated skills.
npm install @lightprotocol/compressed-token@beta \
            @lightprotocol/stateless.js@beta \
            @solana/spl-token
Snippets below assume rpc, payer, mint, owner, recipient, and amount are defined. See the full examples for runnable setup.
import { createRpc } from "@lightprotocol/stateless.js";

const rpc = createRpc(RPC_ENDPOINT);

Create a Token-2022 Mint With ConfidentialTransferMint

import {
    Keypair,
    PublicKey,
    SystemProgram,
    Transaction,
    TransactionInstruction,
    sendAndConfirmTransaction,
} from "@solana/web3.js";
import { createRpc } from "@lightprotocol/stateless.js";
import { LightTokenProgram } from "@lightprotocol/compressed-token";
import {
    ExtensionType,
    TOKEN_2022_PROGRAM_ID,
    createInitializeMint2Instruction,
    getMintLen,
} from "@solana/spl-token";

const rpc = createRpc(RPC_ENDPOINT);

/**
 * Build the InitializeMint instruction for ConfidentialTransferMint.
 *
 * The @solana/spl-token SDK defines ExtensionType.ConfidentialTransferMint
 * but does not yet export a helper for this instruction, so we construct
 * it manually using the Token-2022 instruction layout.
 */
function createInitializeConfidentialTransferMintIx(
    mint: PublicKey,
    authority: PublicKey | null,
    autoApproveNewAccounts: boolean,
    auditorElGamalPubkey: Uint8Array | null,
): TransactionInstruction {
    // TokenInstruction::ConfidentialTransferExtension = 27
    // ConfidentialTransferInstruction::InitializeMint = 0
    const data = Buffer.alloc(2 + 1 + 32 + 1 + 1 + 32);
    let offset = 0;
    data.writeUInt8(27, offset); offset += 1;  // TokenInstruction
    data.writeUInt8(0, offset);  offset += 1;  // InitializeMint sub-instruction

    // authority (COption<Pubkey>): 1 byte tag + 32 bytes
    if (authority) {
        data.writeUInt8(1, offset); offset += 1;
        authority.toBuffer().copy(data, offset); offset += 32;
    } else {
        data.writeUInt8(0, offset); offset += 1;
        offset += 32;
    }

    // auto_approve_new_accounts: bool (1 byte)
    data.writeUInt8(autoApproveNewAccounts ? 1 : 0, offset); offset += 1;

    // auditor_elgamal_pubkey (COption<ElGamalPubkey>): 1 byte tag + 32 bytes
    if (auditorElGamalPubkey) {
        data.writeUInt8(1, offset); offset += 1;
        Buffer.from(auditorElGamalPubkey).copy(data, offset);
    } else {
        data.writeUInt8(0, offset); offset += 1;
    }

    return new TransactionInstruction({
        keys: [{ pubkey: mint, isSigner: false, isWritable: true }],
        programId: TOKEN_2022_PROGRAM_ID,
        data,
    });
}

const mintKeypair = Keypair.generate();
const decimals = 9;

// Calculate space including ConfidentialTransferMint extension
const mintLen = getMintLen([ExtensionType.ConfidentialTransferMint]);
const rentExemptBalance =
    await rpc.getMinimumBalanceForRentExemption(mintLen);

// Create account
const createAccountIx = SystemProgram.createAccount({
    fromPubkey: payer.publicKey,
    lamports: rentExemptBalance,
    newAccountPubkey: mintKeypair.publicKey,
    programId: TOKEN_2022_PROGRAM_ID,
    space: mintLen,
});

// Initialize ConfidentialTransferMint extension (must come before mint init)
// auto_approve_new_accounts: false — extension is initialized but not enabled
// auditor_elgamal_pubkey: null — no auditor configured
const initConfidentialTransferIx = createInitializeConfidentialTransferMintIx(
    mintKeypair.publicKey,
    payer.publicKey, // authority
    false,           // auto_approve_new_accounts (not enabled)
    null,            // auditor_elgamal_pubkey
);

// Initialize mint
const initMintIx = createInitializeMint2Instruction(
    mintKeypair.publicKey,
    decimals,
    payer.publicKey, // mint authority
    null,            // freeze authority
    TOKEN_2022_PROGRAM_ID,
);

// Register interface PDA with Light Token
const createSplInterfaceIx = await LightTokenProgram.createSplInterface({
    feePayer: payer.publicKey,
    mint: mintKeypair.publicKey,
    tokenProgramId: TOKEN_2022_PROGRAM_ID,
});

const tx = new Transaction().add(
    createAccountIx,
    initConfidentialTransferIx,
    initMintIx,
    createSplInterfaceIx,
);

const signature = await sendAndConfirmTransaction(rpc, tx, [
    payer,
    mintKeypair,
]);

Create Interface PDA for Existing Mint

If you already have a Token-2022 mint with ConfidentialTransferMint, create an interface PDA with Light Token.
import { LightTokenProgram } from "@lightprotocol/compressed-token";
import { TOKEN_2022_PROGRAM_ID } from "@solana/spl-token";

const createSplInterfaceIx = await LightTokenProgram.createSplInterface({
    feePayer: payer.publicKey,
    mint: mintKeypair.publicKey,
    tokenProgramId: TOKEN_2022_PROGRAM_ID,
});

Transfer interface

Wrap and unwrap


Didn’t find what you were looking for?

Reach out! Telegram | email | Discord