Skip to main content

Key Parameters

A program address stored on the mint is invoked via CPI on every token transfer to execute custom logic such as transfer restrictions, royalty enforcement, or event emission. TransferHook is configured with two parameters:
ParameterDescription
authorityAccount that can update the transfer hook program address after initialization.
programIdAddress of the program invoked via CPI on every transfer. Must be PublicKey.default (nil) for Light Token.
Light Token requires the TransferHook program_id to be set to PublicKey.default (all zeros). You can initialize the extension for compatibility, but active hooks are not supported. Registration fails if the program_id points to a real program.

Use TransferHook 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 Transfer Hook

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

const rpc = createRpc(RPC_ENDPOINT);

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

// Calculate space for mint + TransferHook extension
const mintLen = getMintLen([ExtensionType.TransferHook]);
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 TransferHook with nil program_id (hook disabled)
const initTransferHookIx = createInitializeTransferHookInstruction(
    mintKeypair.publicKey,
    payer.publicKey,    // authority
    PublicKey.default,  // program_id must be nil
    TOKEN_2022_PROGRAM_ID
);

// Initialize mint
const initMintIx = createInitializeMint2Instruction(
    mintKeypair.publicKey,
    decimals,
    payer.publicKey,
    null,
    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,
    initTransferHookIx,
    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 TransferHook set to a nil program_id, 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