Transfer Hook: Customizing Token Transfers on Solana

ยท

Summary

Overview

The transfer hook extension allows developers to:

Use Cases:

Implementing Transfer Hooks On-Chain

1. Initialize Extra Account Meta List Instruction

#[derive(Accounts)]
pub struct InitializeExtraAccountMetaList<'info> {
    #[account(mut)]
    payer: Signer<'info>,
    #[account(
        mut,
        seeds = [b"extra-account-metas", mint.key().as_ref()],
        bump
    )]
    pub extra_account_meta_list: AccountInfo<'info>,
    pub mint: InterfaceAccount<'info, Mint>,
    // Additional accounts...
}

pub fn initialize_extra_account_meta_list(
    ctx: Context<InitializeExtraAccountMetaList>
) -> Result<()> {
    // Implementation details...
}

2. Transfer Hook Instruction

#[derive(Accounts)]
pub struct TransferHook<'info> {
    #[account(token::mint = mint, token::authority = owner)]
    pub source_token: InterfaceAccount<'info, TokenAccount>,
    pub mint: InterfaceAccount<'info, Mint>,
    #[account(token::mint = mint)]
    pub destination_token: InterfaceAccount<'info, TokenAccount>,
    /// CHECK: source token account owner
    pub owner: UncheckedAccount<'info>,
    #[account(seeds = [b"extra-account-metas", mint.key().as_ref()], bump)]
    pub extra_account_meta_list: UncheckedAccount<'info>,
    // Additional accounts...
}

pub fn transfer_hook(ctx: Context<TransferHook>, amount: u64) -> Result<()> {
    // Custom transfer logic
}

3. Fallback Instruction

pub fn fallback<'info>(
    program_id: &Pubkey,
    accounts: &'info [AccountInfo<'info>],
    data: &[u8]
) -> Result<()> {
    // Handle CPI from Token Extensions Program
}

Frontend Integration

Creating a Mint with Transfer Hook

const transaction = new Transaction().add(
    SystemProgram.createAccount({/*...*/}),
    createInitializeTransferHookInstruction(
        mint.publicKey,
        wallet.publicKey,
        program.programId,
        TOKEN_2022_PROGRAM_ID
    ),
    createInitializeMintInstruction(/*...*/)
);

Initializing ExtraAccountMetaList

const initializeInstruction = await program.methods
    .initializeExtraAccountMetaList()
    .accounts({
        mint: mint.publicKey,
        extraAccountMetaList: extraAccountMetaListPDA,
        // Additional accounts...
    })
    .instruction();

Executing Transfers

const transferInstruction = await createTransferCheckedWithTransferHookInstruction(
    connection,
    sourceTokenAccount,
    mint.publicKey,
    destinationTokenAccount,
    wallet.publicKey,
    BigInt(1),
    0,
    [],
    "confirmed",
    TOKEN_2022_PROGRAM_ID
);

Theoretical Example: Artist Royalties

Implementation Approach:

  1. Create PDA to track royalty payments
  2. Verify payment status during transfer
  3. Allow/block transfers based on payment verification

Considerations:

Lab: Cookie Crumb Trail

Implement a transfer hook that:

  1. Creates Cookie NFT with transfer hook
  2. Mints Crumb SFT on each transfer
  3. Tracks transfer count via Crumb supply

Challenge

Modify the transfer hook to prevent users with crumbs from receiving cookies back.

๐Ÿ‘‰ Explore Solana Token Extensions

FAQ

Q: Can transfer hooks modify sender/receiver accounts?
A: No, key accounts are de-escalated (read-only) during hook execution.

Q: How many extra accounts can a transfer hook use?
A: There's no hard limit, but keep it reasonable for gas efficiency.

Q: Are transfer hooks recursive?
A: No, Solana prevents reentrancy into the same program during CPIs.

Q: Can I use transfer hooks with existing tokens?
A: Only for new mints created with the transfer hook extension.