2

I want to create a counter PDA that counts the number of times a token has been transferred. I understand how to create the counter PDA using the Solana Playground tutorial as the basis and I know how to create and mint token-22 tokens using the CLI. I assume the transfer hook would need to know the correct entrypoint to execute upon transfer but I cannot see where this would be defined. If this is currently not possible in Anchor please let me know as well.

Here is the program for reference:

use anchor_lang::prelude::*;

declare_id!("8f9DYpQbCkCvKgWkafcNBjBxS5aH1dTtEXcdqTDGr3Q1");

#[program]
// Smart contract functions
pub mod counter {
    use super::*;

    pub fn create_counter(ctx: Context<CreateCounter>) -> Result<()> {
        msg!("Creating a Counter!!");

        // The creation of the counter must be here
        let counter = &mut ctx.accounts.counter;
        counter.authority = ctx.accounts.authority.key();
        counter.count = 0;
        msg!("Current count is {}", counter.count);
        msg!("The Admin PubKey is: {} ", counter.authority);

        Ok(())
    }

    pub fn update_counter(ctx: Context<UpdateCounter>) -> Result<()> {
        msg!("Adding 1 to the counter!!");

        // Updating the counter must be here 
        let counter = &mut ctx.accounts.counter;
        counter.count += 1 ;
        
        msg!("Current count is {}", counter.count);
        msg!("{} remaining to reach 1000 ", 1000 - counter.count);

        Ok(())
    }

}

// Data validators
#[derive(Accounts)]
pub struct CreateCounter<'info> {
    #[account(mut)]
    authority: Signer<'info>,
    #[account(
        init,
        seeds = [authority.key().as_ref()],
        bump,
        payer = authority,
        space = 100
    )]
    counter: Account<'info, Counter>,
    system_program: Program<'info, System>,
}

#[derive(Accounts)]
pub struct UpdateCounter<'info> {
    authority: Signer<'info>,
    #[account(mut, has_one = authority)]
    counter: Account<'info, Counter>,
}

// Data structures
#[account]
pub struct Counter {
    authority: Pubkey,
    count: u64,
}

2 Answers 2

5

Here are two references of transfer hook programs built using Anchor!

https://github.com/0ximalice/spl-token-2022-transfer-hook-anchor

https://github.com/TeamRaccoons/raccoons-program-library/tree/main/permissioned-token

It looks like the solution is to use a fallback instruction in Anchor to work around the instruction discriminator.

Here's the fallback instructions for the two references:

First reference:


// Sha256(spl-transfer-hook-interface:execute)[..8]
pub const EXECUTE_IX_TAG_LE: [u8; 8] = [105, 37, 101, 197, 75, 251, 102, 26];

...

    pub fn fallback(program_id: &Pubkey, accounts: &[AccountInfo], ix_data: &[u8]) -> Result<()> {
        let mut ix_data: &[u8] = ix_data;
        let sighash: [u8; 8] = {
            let mut sighash: [u8; 8] = [0; 8];
            sighash.copy_from_slice(&ix_data[..8]);
            ix_data = &ix_data[8..];
            sighash
        };
        match sighash {
            EXECUTE_IX_TAG_LE => {__private::__global::transfer_hook(program_id, accounts, ix_data)},
            _ => Err(ProgramError::InvalidInstructionData.into()),
        }
    }

Second reference:

/// The fallback allows routing methods to match the transfer hook interface
    pub fn fallback(
        program_id: &Pubkey,
        accounts: &[AccountInfo],
        instruction_data: &[u8],
    ) -> Result<()> {
        if let Err(error) = processor::process(program_id, accounts, instruction_data) {
            // catch the error so we can print it
            error.print::<TransferHookError>();
            return Err(error.into());
        }

        Ok(())
    }
}
2

Unfortunately, this isn't possible at the moment. Anchor always generates the instruction discriminator the same way, by appending global: to the instruction name, taking the sha256 hash, and then taking the first 8 bytes [1].

To implement a transfer hook, your program needs to accept instruction data fitting the predefined discriminator for the transfer hook program, which is the first 8 bytes of the sha256 hash of spl-transfer-hook-interface:execute [2].

You can start with the example program and adapt it to your needs [3].

[1] https://github.com/coral-xyz/anchor/blob/243ab7573865a7296dd2af1153f44bffbf488458/lang/syn/src/codegen/program/instruction.rs#L25

[2] https://github.com/solana-labs/solana-program-library/blob/88547d85c1cbe63feb2f79160d3070075a9e156a/token/transfer-hook-interface/src/instruction.rs#L54

[3] https://github.com/solana-labs/solana-program-library/tree/master/token/transfer-hook/example

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.