Skip to content
This repository has been archived by the owner on Nov 26, 2024. It is now read-only.

Latest commit

 

History

History

07_transfer_hooks_counter

🛹 Demo 7: Transfer Hooks with a Counter


tl; dr


  • This demo demonstrates how to use transfer_hook to increase a counter every time a token is transferred.

pub fn transfer_hook(ctx: Context<TransferHook>, _amount: u64) -> Result<()> {

    ctx.accounts.counter_account.counter.checked_add(1).unwrap();
    msg!("Token has been transferred {0} times", ctx.accounts.counter_account.counter);
       
    Ok(())
}


.
├── Anchor.toml
├── Cargo.toml
├── README.md
├── package.json
├── programs
│   └── transfer_hooks_counter
│       ├── Cargo.toml
│       ├── Xargo.toml
│       └── src
│           ├── errors.rs
│           ├── instructions
│           │   ├── metalist.rs
│           │   ├── mod.rs
│           │   └── transfer_hook.rs
│           ├── lib.rs
│           └── state
│               ├── global.rs
│               └── mod.rs
├── tests
│   └── transfer_hooks_counter.ts
└── tsconfig.json


Extra Accounts at ExtraAccountMetaList


  • Any additional logic to a transfer hook needs additional accounts, that should be added to the ExtraAccountMetaList account.

  • In this demo, we create a PDA saving how often the token has been transferred by this code, which should be added to the initialize_extra_account_meta_list instruction:


let account_metas = vec![
    ExtraAccountMeta::new_with_seeds(
        &[Seed::Literal {
            bytes: "counter".as_bytes().to_vec(),
        }],
        false, // is_signer
        true,  // is_writable
    )?,
];

  • This account needs to be created when we initialize the new Mint Account, and we need to pass it in every time we transfer the token.

#[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>,
    #[account(
        init_if_needed,
        seeds = [b"counter"],
        bump,
        payer = payer,
        space = 16 // u64
    )]
    
    // initialize the accounts
    pub counter_account: Account<'info, CounterAccount>,
    pub token_program: Interface<'info, TokenInterface>,
    pub associated_token_program: Program<'info, AssociatedToken>,
    pub system_program: Program<'info, System>,
}

#[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>,

    pub owner: UncheckedAccount<'info>,
    #[account(
        seeds = [b"extra-account-metas", mint.key().as_ref()],
        bump
    )]
    pub extra_account_meta_list: UncheckedAccount<'info>,
    #[account(
        mut,
        seeds = [b"counter"],
        bump
    )]
    
    // this is the extra account for the counter
    pub counter_account: Account<'info, CounterAccount>,
}

  • Note that this is the account holding the u64 counter variable:

#[account]
pub struct CounterAccount {
    counter: u64,
}


The Transfer Hook


  • The transfer hook function increases the counter by one every time it gets called:

pub fn transfer_hook(
    ctx: Context<TransferHook>, 
    amount: u64
    ) -> Result<()> {

    ctx.accounts.counter_account.counter.checked_add(1).unwrap();
    msg!(
        "This token has been transferred {0} times", 
        ctx.accounts.counter_account.counter
    );

    Ok(())
}


The Client


  • In the client, these accounts are added automatically by the helper function createTransferCheckedWithTransferHookInstructio():

let transferInstructionWithHelper =
  await createTransferCheckedWithTransferHookInstruction(
    connection,
    sourceTokenAccount,
    mint.publicKey,
    destinationTokenAccount,
    wallet.publicKey,
    amountBigInt,
    decimals,
    [],
    "confirmed",
    TOKEN_2022_PROGRAM_ID,
  );


Running this Demo


  • Build and run the tests:

anchor build
anchor test --detach

  • Check the Solana Explorer (localhost) to see the log for the message ("This token has been transferred X times").


References