Summary
- The transfer hook extension enables custom logic execution during every token transfer.
- The Token Extensions Program invokes the transfer hook instruction for each transfer.
- Transfer hook programs must implement the
TransferHookinterface. - Extra accounts beyond standard transfers can be specified via
extra-account-metas. - Key accounts (sender, mint, receiver, owner) are de-escalated (read-only) during hook execution.
Overview
The transfer hook extension allows developers to:
- Enforce artist royalties for NFT transfers
- Implement blocklists/allowlists
- Require specific NFT ownership for token receipts
- Conduct token analytics
Use Cases:
- Royalty enforcement for NFT artists
- Token gating via allowlists
- Real-time transfer analytics
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:
- Create PDA to track royalty payments
- Verify payment status during transfer
- Allow/block transfers based on payment verification
Considerations:
- NFT price enforcement
- Approved wallet lists
- Artist involvement in transfers
Lab: Cookie Crumb Trail
Implement a transfer hook that:
- Creates Cookie NFT with transfer hook
- Mints Crumb SFT on each transfer
- 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.