Jelajahi Sumber

update transferhook examples

John 1 tahun lalu
induk
melakukan
9fb957c271

+ 3 - 5
tokens/token-2022/transfer-hook/anchor/TransferHookCounter/programs/transfer-hook/Cargo.toml

@@ -17,9 +17,7 @@ no-log-ix-name = []
 idl-build = ["anchor-lang/idl-build", "anchor-spl/idl-build"]
 
 [dependencies]
-anchor-lang = {version = "0.30.0", features = ["init-if-needed"]}
+anchor-lang = {version = "0.30.0", features = ["interface-instructions"]}
 anchor-spl = "0.30.0"
-solana-program = "=1.17.17"
-
-spl-transfer-hook-interface = "0.5.0"
-spl-tlv-account-resolution = "0.5.0"
+spl-tlv-account-resolution = "0.6.3"
+spl-transfer-hook-interface = "0.6.3"

+ 29 - 72
tokens/token-2022/transfer-hook/anchor/TransferHookCounter/programs/transfer-hook/src/lib.rs

@@ -1,15 +1,11 @@
-use anchor_lang::{
-    prelude::*,
-    system_program::{create_account, CreateAccount},
-};
+use anchor_lang::prelude::*;
 use anchor_spl::{
-    associated_token::AssociatedToken,
-    token_interface::{Mint, TokenAccount, TokenInterface},
+    associated_token::AssociatedToken, token_2022::Token2022, token_interface::{Mint, TokenAccount}
 };
 use spl_tlv_account_resolution::{
     account::ExtraAccountMeta, seeds::Seed, state::ExtraAccountMetaList,
 };
-use spl_transfer_hook_interface::instruction::{ExecuteInstruction, TransferHookInstruction};
+use spl_transfer_hook_interface::instruction::ExecuteInstruction;
 
 declare_id!("DrWbQtYJGtsoRwzKqAbHKHKsCJJfpysudF39GBVFSxub");
 
@@ -23,56 +19,22 @@ pub enum MyError {
 pub mod transfer_hook {
     use super::*;
 
+    #[interface(spl_transfer_hook_interface::initialize_extra_account_meta_list)]
     pub fn initialize_extra_account_meta_list(
         ctx: Context<InitializeExtraAccountMetaList>,
     ) -> Result<()> {
-
-        let account_metas = vec![
-            ExtraAccountMeta::new_with_seeds(
-                &[Seed::Literal {
-                    bytes: "counter".as_bytes().to_vec(),
-                }],
-                false, // is_signer
-                true,  // is_writable
-            )?,
-        ];
-
-        // calculate account size
-        let account_size = ExtraAccountMetaList::size_of(account_metas.len())? as u64;
-        // calculate minimum required lamports
-        let lamports = Rent::get()?.minimum_balance(account_size as usize);
-
-        let mint = ctx.accounts.mint.key();
-        let signer_seeds: &[&[&[u8]]] = &[&[
-            b"extra-account-metas",
-            &mint.as_ref(),
-            &[ctx.bumps.extra_account_meta_list],
-        ]];
-
-        // create ExtraAccountMetaList account
-        create_account(
-            CpiContext::new(
-                ctx.accounts.system_program.to_account_info(),
-                CreateAccount {
-                    from: ctx.accounts.payer.to_account_info(),
-                    to: ctx.accounts.extra_account_meta_list.to_account_info(),
-                },
-            )
-            .with_signer(signer_seeds),
-            lamports,
-            account_size,
-            ctx.program_id,
-        )?;
+        let extra_account_metas = InitializeExtraAccountMetaList::extra_account_metas()?;
 
         // initialize ExtraAccountMetaList account with extra accounts
         ExtraAccountMetaList::init::<ExecuteInstruction>(
             &mut ctx.accounts.extra_account_meta_list.try_borrow_mut_data()?,
-            &account_metas,
+            &extra_account_metas,
         )?;
 
         Ok(())
     }
 
+    #[interface(spl_transfer_hook_interface::execute)]
     pub fn transfer_hook(ctx: Context<TransferHook>, amount: u64) -> Result<()> {
 
         if amount > 50 {
@@ -80,33 +42,12 @@ pub mod transfer_hook {
             //return err!(MyError::AmountTooBig);
         }
 
-        ctx.accounts.counter_account.counter.checked_add(1).unwrap();
+        let count = ctx.accounts.counter_account.counter.checked_add(1).unwrap();
 
-        msg!("This token has been transferred {0} times", ctx.accounts.counter_account.counter);
+        msg!("This token has been transferred {} times", count);
        
         Ok(())
     }
-
-    // fallback instruction handler as workaround to anchor instruction discriminator check
-    pub fn fallback<'info>(
-        program_id: &Pubkey,
-        accounts: &'info [AccountInfo<'info>],
-        data: &[u8],
-    ) -> Result<()> {
-        let instruction = TransferHookInstruction::unpack(data)?;
-
-        // match instruction discriminator to transfer hook interface execute instruction  
-        // token2022 program CPIs this instruction on token transfer
-        match instruction {
-            TransferHookInstruction::Execute { amount } => {
-                let amount_bytes = amount.to_le_bytes();
-
-                // invoke custom transfer hook instruction on our program
-                __private::__global::transfer_hook(program_id, accounts, &amount_bytes)
-            }
-            _ => return Err(ProgramError::InvalidInstructionData.into()),
-        }
-    }
 }
 
 #[derive(Accounts)]
@@ -116,25 +57,41 @@ pub struct InitializeExtraAccountMetaList<'info> {
 
     /// CHECK: ExtraAccountMetaList Account, must use these seeds
     #[account(
-        mut,
+        init,
         seeds = [b"extra-account-metas", mint.key().as_ref()], 
-        bump
+        bump,
+        space = ExtraAccountMetaList::size_of(InitializeExtraAccountMetaList::extra_account_metas()?.len())?,
+        payer = payer,
     )]
     pub extra_account_meta_list: AccountInfo<'info>,
     pub mint: InterfaceAccount<'info, Mint>,
     #[account(
-        init_if_needed,
+        init,
         seeds = [b"counter"], 
         bump,
         payer = payer,
         space = 16
     )]
     pub counter_account: Account<'info, CounterAccount>,
-    pub token_program: Interface<'info, TokenInterface>,
+    pub token_program: Program<'info, Token2022>,
     pub associated_token_program: Program<'info, AssociatedToken>,
     pub system_program: Program<'info, System>,
 }
 
+// Define extra account metas to store on extra_account_meta_list account
+impl<'info> InitializeExtraAccountMetaList<'info> {
+    pub fn extra_account_metas() -> Result<Vec<ExtraAccountMeta>> {
+        Ok(vec![
+            ExtraAccountMeta::new_with_seeds(
+                &[Seed::Literal {
+                    bytes: b"counter".to_vec(),
+                }],
+                false, // is_signer
+                true,  // is_writable
+            )?
+        ])
+    }
+}
 // Order of accounts matters for this struct.
 // The first 4 accounts are the accounts required for token transfer (source, mint, destination, owner)
 // Remaining accounts are the extra accounts required from the ExtraAccountMetaList account

+ 1 - 3
tokens/token-2022/transfer-hook/anchor/TransferHookCounter/tests/transfer-hook.ts

@@ -159,8 +159,6 @@ describe("transfer-hook", () => {
       .initializeExtraAccountMetaList()
       .accounts({
         mint: mint.publicKey,
-        extraAccountMetaList: extraAccountMetaListPDA,
-        counterAccount: counterPDA,
       })
       .instruction();
 
@@ -197,7 +195,7 @@ describe("transfer-hook", () => {
       );
 
     console.log("Extra accounts meta: " + extraAccountMetaListPDA);
-    console.log("Counter PDa: " + counterPDA);
+    console.log("Counter PDA: " + counterPDA);
     console.log(
       "Transfer Instruction: " +
         JSON.stringify(transferInstructionWithHelper, null, 2)

+ 3 - 5
tokens/token-2022/transfer-hook/anchor/TransferHookHelloWorld/programs/transfer-hook/Cargo.toml

@@ -17,9 +17,7 @@ no-log-ix-name = []
 idl-build = ["anchor-lang/idl-build", "anchor-spl/idl-build"]
 
 [dependencies]
-anchor-lang = {version = "0.30.0", features = ["init-if-needed"]}
+anchor-lang = { version = "0.30.0", features = ["interface-instructions"] }
 anchor-spl = "0.30.0"
-solana-program = "1.17.17"
-
-spl-transfer-hook-interface = "0.5.0"
-spl-tlv-account-resolution = "0.5.0"
+spl-tlv-account-resolution = "0.6.3"
+spl-transfer-hook-interface = "0.6.3"

+ 24 - 66
tokens/token-2022/transfer-hook/anchor/TransferHookHelloWorld/programs/transfer-hook/src/lib.rs

@@ -1,15 +1,9 @@
-use anchor_lang::{
-    prelude::*,
-    system_program::{create_account, CreateAccount},
-};
+use anchor_lang::prelude::*;
 use anchor_spl::{
-    associated_token::AssociatedToken,
-    token_interface::{Mint, TokenAccount, TokenInterface},
-};
-use spl_tlv_account_resolution::{
-    state::ExtraAccountMetaList,
+    associated_token::AssociatedToken, token_2022::Token2022, token_interface::{Mint, TokenAccount}
 };
-use spl_transfer_hook_interface::instruction::{ExecuteInstruction, TransferHookInstruction};
+use spl_tlv_account_resolution::{account::ExtraAccountMeta, state::ExtraAccountMetaList};
+use spl_transfer_hook_interface::instruction::ExecuteInstruction;
 
 declare_id!("DrWbQtYJGtsoRwzKqAbHKHKsCJJfpysudF39GBVFSxub");
 
@@ -17,75 +11,29 @@ declare_id!("DrWbQtYJGtsoRwzKqAbHKHKsCJJfpysudF39GBVFSxub");
 pub mod transfer_hook {
     use super::*;
 
+    #[interface(spl_transfer_hook_interface::initialize_extra_account_meta_list)]
     pub fn initialize_extra_account_meta_list(
         ctx: Context<InitializeExtraAccountMetaList>,
     ) -> Result<()> {
-
-        let account_metas = vec![];
-
-        // calculate account size
-        let account_size = ExtraAccountMetaList::size_of(account_metas.len())? as u64;
-        // calculate minimum required lamports
-        let lamports = Rent::get()?.minimum_balance(account_size as usize);
-
-        let mint = ctx.accounts.mint.key();
-        let signer_seeds: &[&[&[u8]]] = &[&[
-            b"extra-account-metas",
-            &mint.as_ref(),
-            &[ctx.bumps.extra_account_meta_list],
-        ]];
-
-        // create ExtraAccountMetaList account
-        create_account(
-            CpiContext::new(
-                ctx.accounts.system_program.to_account_info(),
-                CreateAccount {
-                    from: ctx.accounts.payer.to_account_info(),
-                    to: ctx.accounts.extra_account_meta_list.to_account_info(),
-                },
-            )
-            .with_signer(signer_seeds),
-            lamports,
-            account_size,
-            ctx.program_id,
-        )?;
+        let extra_account_metas = InitializeExtraAccountMetaList::extra_account_metas()?;
 
         // initialize ExtraAccountMetaList account with extra accounts
         ExtraAccountMetaList::init::<ExecuteInstruction>(
             &mut ctx.accounts.extra_account_meta_list.try_borrow_mut_data()?,
-            &account_metas,
+            &extra_account_metas,
         )?;
 
+
         Ok(())
     }
 
-    pub fn transfer_hook(ctx: Context<TransferHook>, amount: u64) -> Result<()> {
+    #[interface(spl_transfer_hook_interface::execute)]
+    pub fn transfer_hook(_ctx: Context<TransferHook>, _amount: u64) -> Result<()> {
 
         msg!("Hello Transfer Hook!");
 
         Ok(())
     }
-
-    // fallback instruction handler as workaround to anchor instruction discriminator check
-    pub fn fallback<'info>(
-        program_id: &Pubkey,
-        accounts: &'info [AccountInfo<'info>],
-        data: &[u8],
-    ) -> Result<()> {
-        let instruction = TransferHookInstruction::unpack(data)?;
-
-        // match instruction discriminator to transfer hook interface execute instruction  
-        // token2022 program CPIs this instruction on token transfer
-        match instruction {
-            TransferHookInstruction::Execute { amount } => {
-                let amount_bytes = amount.to_le_bytes();
-
-                // invoke custom transfer hook instruction on our program
-                __private::__global::transfer_hook(program_id, accounts, &amount_bytes)
-            }
-            _ => return Err(ProgramError::InvalidInstructionData.into()),
-        }
-    }
 }
 
 #[derive(Accounts)]
@@ -95,17 +43,27 @@ pub struct InitializeExtraAccountMetaList<'info> {
 
     /// CHECK: ExtraAccountMetaList Account, must use these seeds
     #[account(
-        mut,
+        init,
         seeds = [b"extra-account-metas", mint.key().as_ref()], 
-        bump
+        bump,
+        space = ExtraAccountMetaList::size_of(InitializeExtraAccountMetaList::extra_account_metas()?.len())?,
+        payer = payer,
     )]
-    pub extra_account_meta_list: AccountInfo<'info>,
+    pub extra_account_meta_list: UncheckedAccount<'info>,
     pub mint: InterfaceAccount<'info, Mint>,
-    pub token_program: Interface<'info, TokenInterface>,
+    pub token_program: Program<'info, Token2022>,
     pub associated_token_program: Program<'info, AssociatedToken>,
     pub system_program: Program<'info, System>,
 }
 
+// Define extra account metas to store on extra_account_meta_list account
+// In this example there are none
+impl<'info> InitializeExtraAccountMetaList<'info> {
+    pub fn extra_account_metas() -> Result<Vec<ExtraAccountMeta>> {
+        Ok(vec![])
+    }
+}
+
 // Order of accounts matters for this struct.
 // The first 4 accounts are the accounts required for token transfer (source, mint, destination, owner)
 // Remaining accounts are the extra accounts required from the ExtraAccountMetaList account

+ 14 - 25
tokens/token-2022/transfer-hook/anchor/TransferHookHelloWorld/tests/transfer-hook.ts

@@ -2,7 +2,6 @@ import * as anchor from "@coral-xyz/anchor";
 import { Program } from "@coral-xyz/anchor";
 import { TransferHook } from "../target/types/transfer_hook";
 import {
-  PublicKey,
   SystemProgram,
   Transaction,
   sendAndConfirmTransaction,
@@ -17,7 +16,6 @@ import {
   ASSOCIATED_TOKEN_PROGRAM_ID,
   createAssociatedTokenAccountInstruction,
   createMintToInstruction,
-  createTransferCheckedInstruction,
   getAssociatedTokenAddressSync,
   createTransferCheckedWithTransferHookInstruction,
 } from "@solana/spl-token";
@@ -54,13 +52,6 @@ describe("transfer-hook", () => {
     ASSOCIATED_TOKEN_PROGRAM_ID
   );
 
-  // ExtraAccountMetaList address
-  // Store extra accounts required by the custom transfer hook instruction
-  const [extraAccountMetaListPDA] = PublicKey.findProgramAddressSync(
-    [Buffer.from("extra-account-metas"), mint.publicKey.toBuffer()],
-    program.programId
-  );
-
   it("Create Mint Account with Transfer Hook Extension", async () => {
     const extensions = [ExtensionType.TransferHook];
     const mintLen = getMintLen(extensions);
@@ -147,7 +138,6 @@ describe("transfer-hook", () => {
       .initializeExtraAccountMetaList()
       .accounts({
         mint: mint.publicKey,
-        extraAccountMetaList: extraAccountMetaListPDA,
       })
       .instruction();
 
@@ -170,22 +160,21 @@ describe("transfer-hook", () => {
     const bigIntAmount = BigInt(amount);
 
     // Standard token transfer instruction
-    const transferInstruction = await createTransferCheckedWithTransferHookInstruction(
-      connection,
-      sourceTokenAccount,
-      mint.publicKey,
-      destinationTokenAccount,
-      wallet.publicKey,
-      bigIntAmount,
-      decimals,
-      [],
-      "confirmed",
-      TOKEN_2022_PROGRAM_ID
-    );
+    const transferInstruction =
+      await createTransferCheckedWithTransferHookInstruction(
+        connection,
+        sourceTokenAccount,
+        mint.publicKey,
+        destinationTokenAccount,
+        wallet.publicKey,
+        bigIntAmount,
+        decimals,
+        [],
+        "confirmed",
+        TOKEN_2022_PROGRAM_ID
+      );
 
-    const transaction = new Transaction().add(
-      transferInstruction
-    );
+    const transaction = new Transaction().add(transferInstruction);
 
     const txSig = await sendAndConfirmTransaction(
       connection,

+ 2 - 2
tokens/token-2022/transfer-hook/anchor/TransferHookTransferCost/Anchor.toml

@@ -1,11 +1,11 @@
 [toolchain]
 
 [features]
-seeds = false
+resolution = true
 skip-lint = false
 
 [programs.localnet]
-transfer_hook = "DrWbQtYJGtsoRwzKqAbHKHKsCJJfpysudF39GBVFSxub"
+transfer_hook = "FjcHckEgXcBhFmSGai3FRpDLiT6hbpV893n8iTxVd81g"
 
 [registry]
 url = "https://api.apr.dev"

+ 3 - 5
tokens/token-2022/transfer-hook/anchor/TransferHookTransferCost/programs/transfer-hook/Cargo.toml

@@ -17,9 +17,7 @@ no-log-ix-name = []
 idl-build = ["anchor-lang/idl-build", "anchor-spl/idl-build"]
 
 [dependencies]
-anchor-lang = "0.30.0"
+anchor-lang = {version = "0.30.0", features = ["interface-instructions"]}
 anchor-spl = "0.30.0"
-solana-program = "1.17.13"
-
-spl-transfer-hook-interface = "0.4.1"
-spl-tlv-account-resolution = "0.5.0"
+spl-tlv-account-resolution = "0.6.3"
+spl-transfer-hook-interface = "0.6.3"

+ 73 - 114
tokens/token-2022/transfer-hook/anchor/TransferHookTransferCost/programs/transfer-hook/src/lib.rs

@@ -1,20 +1,19 @@
+use std::str::FromStr;
 use anchor_lang::{
-    prelude::*,
-    system_program::{create_account, CreateAccount},
+    prelude::*, solana_program::pubkey::Pubkey, 
 };
 use anchor_spl::{
-    associated_token::AssociatedToken,
-    token_interface::{transfer_checked, Mint, TokenAccount, TokenInterface, TransferChecked},
+    associated_token::AssociatedToken, token::Token, token_interface::{transfer_checked, Mint, TokenAccount, TransferChecked}
 };
 use spl_tlv_account_resolution::{
     account::ExtraAccountMeta, seeds::Seed, state::ExtraAccountMetaList,
 };
-use spl_transfer_hook_interface::instruction::{ExecuteInstruction, TransferHookInstruction};
+use spl_transfer_hook_interface::instruction::ExecuteInstruction;
 
 // transfer-hook program that charges a SOL fee on token transfer
 // use a delegate and wrapped SOL because signers from initial transfer are not accessible
 
-declare_id!("DrWbQtYJGtsoRwzKqAbHKHKsCJJfpysudF39GBVFSxub");
+declare_id!("FjcHckEgXcBhFmSGai3FRpDLiT6hbpV893n8iTxVd81g");
 
 #[error_code]
 pub enum MyError {
@@ -26,97 +25,22 @@ pub enum MyError {
 pub mod transfer_hook {
     use super::*;
 
+    #[interface(spl_transfer_hook_interface::initialize_extra_account_meta_list)]
     pub fn initialize_extra_account_meta_list(
         ctx: Context<InitializeExtraAccountMetaList>,
     ) -> Result<()> {
-        // index 0-3 are the accounts required for token transfer (source, mint, destination, owner)
-        // index 4 is address of ExtraAccountMetaList account
-        let account_metas = vec![
-            // index 5, wrapped SOL mint
-            ExtraAccountMeta::new_with_pubkey(&ctx.accounts.wsol_mint.key(), false, false)?,
-            // index 6, token program
-            ExtraAccountMeta::new_with_pubkey(&ctx.accounts.token_program.key(), false, false)?,
-            // index 7, associated token program
-            ExtraAccountMeta::new_with_pubkey(
-                &ctx.accounts.associated_token_program.key(),
-                false,
-                false,
-            )?,
-            // index 8, delegate PDA
-            ExtraAccountMeta::new_with_seeds(
-                &[Seed::Literal {
-                    bytes: "delegate".as_bytes().to_vec(),
-                }],
-                false, // is_signer
-                true,  // is_writable
-            )?,
-            // index 9, delegate wrapped SOL token account
-            ExtraAccountMeta::new_external_pda_with_seeds(
-                7, // associated token program index
-                &[
-                    Seed::AccountKey { index: 8 }, // owner index (delegate PDA)
-                    Seed::AccountKey { index: 6 }, // token program index
-                    Seed::AccountKey { index: 5 }, // wsol mint index
-                ],
-                false, // is_signer
-                true,  // is_writable
-            )?,
-            // index 10, sender wrapped SOL token account
-            ExtraAccountMeta::new_external_pda_with_seeds(
-                7, // associated token program index
-                &[
-                    Seed::AccountKey { index: 3 }, // owner index
-                    Seed::AccountKey { index: 6 }, // token program index
-                    Seed::AccountKey { index: 5 }, // wsol mint index
-                ],
-                false, // is_signer
-                true,  // is_writable
-            )?,
-            ExtraAccountMeta::new_with_seeds(
-                &[Seed::Literal {
-                    bytes: "counter".as_bytes().to_vec(),
-                }],
-                false, // is_signer
-                true,  // is_writable
-            )?,
-        ];
-
-        // calculate account size
-        let account_size = ExtraAccountMetaList::size_of(account_metas.len())? as u64;
-        // calculate minimum required lamports
-        let lamports = Rent::get()?.minimum_balance(account_size as usize);
-
-        let mint = ctx.accounts.mint.key();
-        let signer_seeds: &[&[&[u8]]] = &[&[
-            b"extra-account-metas",
-            &mint.as_ref(),
-            &[ctx.bumps.extra_account_meta_list],
-        ]];
-
-        // create ExtraAccountMetaList account
-        create_account(
-            CpiContext::new(
-                ctx.accounts.system_program.to_account_info(),
-                CreateAccount {
-                    from: ctx.accounts.payer.to_account_info(),
-                    to: ctx.accounts.extra_account_meta_list.to_account_info(),
-                },
-            )
-            .with_signer(signer_seeds),
-            lamports,
-            account_size,
-            ctx.program_id,
-        )?;
-
+        let extra_account_metas = InitializeExtraAccountMetaList::extra_account_metas()?;
+      
         // initialize ExtraAccountMetaList account with extra accounts
         ExtraAccountMetaList::init::<ExecuteInstruction>(
             &mut ctx.accounts.extra_account_meta_list.try_borrow_mut_data()?,
-            &account_metas,
+            &extra_account_metas,
         )?;
 
         Ok(())
     }
 
+    #[interface(spl_transfer_hook_interface::execute)]
     pub fn transfer_hook(ctx: Context<TransferHook>, amount: u64) -> Result<()> {
 
         if amount > 50 {
@@ -136,6 +60,7 @@ pub mod transfer_hook {
         let signer_seeds: &[&[&[u8]]] = &[&[b"delegate", &[ctx.bumps.delegate]]];
 
         // Transfer WSOL from sender to delegate token account using delegate PDA
+        // transfer lamports amount equal to token transfer amount
         transfer_checked(
             CpiContext::new(
                 ctx.accounts.token_program.to_account_info(),
@@ -147,32 +72,11 @@ pub mod transfer_hook {
                 },
             )
             .with_signer(signer_seeds),
-            amount / 2,
+            amount,
             ctx.accounts.wsol_mint.decimals,
         )?;
         Ok(())
     }
-
-    // fallback instruction handler as workaround to anchor instruction discriminator check
-    pub fn fallback<'info>(
-        program_id: &Pubkey,
-        accounts: &'info [AccountInfo<'info>],
-        data: &[u8],
-    ) -> Result<()> {
-        let instruction = TransferHookInstruction::unpack(data)?;
-
-        // match instruction discriminator to transfer hook interface execute instruction  
-        // token2022 program CPIs this instruction on token transfer
-        match instruction {
-            TransferHookInstruction::Execute { amount } => {
-                let amount_bytes = amount.to_le_bytes();
-
-                // invoke custom transfer hook instruction on our program
-                __private::__global::transfer_hook(program_id, accounts, &amount_bytes)
-            }
-            _ => Err(ProgramError::InvalidInstructionData.into()),
-        }
-    }
 }
 
 #[derive(Accounts)]
@@ -182,13 +86,14 @@ pub struct InitializeExtraAccountMetaList<'info> {
 
     /// CHECK: ExtraAccountMetaList Account, must use these seeds
     #[account(
-        mut,
+        init,
         seeds = [b"extra-account-metas", mint.key().as_ref()], 
-        bump
+        bump,
+        space = ExtraAccountMetaList::size_of(InitializeExtraAccountMetaList::extra_account_metas()?.len())?,
+        payer = payer,
     )]
     pub extra_account_meta_list: AccountInfo<'info>,
     pub mint: InterfaceAccount<'info, Mint>,
-    pub wsol_mint: InterfaceAccount<'info, Mint>,
     #[account(
         init,
         seeds = [b"counter"],
@@ -197,11 +102,65 @@ pub struct InitializeExtraAccountMetaList<'info> {
         space = 9
     )]
     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>,
 }
 
+// Define extra account metas to store on extra_account_meta_list account
+impl<'info> InitializeExtraAccountMetaList<'info> {
+    pub fn extra_account_metas() -> Result<Vec<ExtraAccountMeta>> {
+        // When the token2022 program CPIs to the transfer_hook instruction on this program,
+        // the accounts are provided in order defined specified the list:
+
+        // index 0-3 are the accounts required for token transfer (source, mint, destination, owner)
+        // index 4 is address of ExtraAccountMetaList account
+        Ok(vec![
+            // index 5, wrapped SOL mint
+            ExtraAccountMeta::new_with_pubkey(&Pubkey::from_str("So11111111111111111111111111111111111111112").unwrap(), false, false)?,
+            // index 6, token program (for wsol token transfer)
+            ExtraAccountMeta::new_with_pubkey(&Token::id(), false, false)?,
+            // index 7, associated token program
+            ExtraAccountMeta::new_with_pubkey(&AssociatedToken::id(), false, false)?,
+            // index 8, delegate PDA
+            ExtraAccountMeta::new_with_seeds(
+                &[Seed::Literal {
+                    bytes: b"delegate".to_vec(),
+                }],
+                false, // is_signer
+                true,  // is_writable
+            )?,
+            // index 9, delegate wrapped SOL token account
+            ExtraAccountMeta::new_external_pda_with_seeds(
+                7, // associated token program index
+                &[
+                    Seed::AccountKey { index: 8 }, // owner index (delegate PDA)
+                    Seed::AccountKey { index: 6 }, // token program index
+                    Seed::AccountKey { index: 5 }, // wsol mint index
+                ],
+                false, // is_signer
+                true,  // is_writable
+            )?,
+            // index 10, sender wrapped SOL token account
+            ExtraAccountMeta::new_external_pda_with_seeds(
+                7, // associated token program index
+                &[
+                    Seed::AccountKey { index: 3 }, // owner index
+                    Seed::AccountKey { index: 6 }, // token program index
+                    Seed::AccountKey { index: 5 }, // wsol mint index
+                ],
+                false, // is_signer
+                true,  // is_writable
+            )?,
+            ExtraAccountMeta::new_with_seeds(
+                &[Seed::Literal {
+                    bytes: b"counter".to_vec(),
+                }],
+                false, // is_signer
+                true,  // is_writable
+            )?,
+        ])
+    }
+}
+
 // Order of accounts matters for this struct.
 // The first 4 accounts are the accounts required for token transfer (source, mint, destination, owner)
 // Remaining accounts are the extra accounts required from the ExtraAccountMetaList account
@@ -227,7 +186,7 @@ pub struct TransferHook<'info> {
     )]
     pub extra_account_meta_list: UncheckedAccount<'info>,
     pub wsol_mint: InterfaceAccount<'info, Mint>,
-    pub token_program: Interface<'info, TokenInterface>,
+    pub token_program:Program<'info, Token>,
     pub associated_token_program: Program<'info, AssociatedToken>,
     #[account(
         mut,

+ 1 - 18
tokens/token-2022/transfer-hook/anchor/TransferHookTransferCost/tests/transfer-hook.ts

@@ -64,18 +64,6 @@ describe("transfer-hook", () => {
     ASSOCIATED_TOKEN_PROGRAM_ID
   );
 
-  // ExtraAccountMetaList address
-  // Store extra accounts required by the custom transfer hook instruction
-  const [extraAccountMetaListPDA] = PublicKey.findProgramAddressSync(
-    [Buffer.from("extra-account-metas"), mint.publicKey.toBuffer()],
-    program.programId
-  );
-
-  const [counterPDA] = PublicKey.findProgramAddressSync(
-    [Buffer.from("counter")],
-    program.programId
-  );
-
   // PDA delegate to transfer wSOL tokens from sender
   const [delegatePDA] = PublicKey.findProgramAddressSync(
     [Buffer.from("delegate")],
@@ -201,12 +189,7 @@ describe("transfer-hook", () => {
       .initializeExtraAccountMetaList()
       .accounts({
         payer: wallet.publicKey,
-        extraAccountMetaList: extraAccountMetaListPDA,
         mint: mint.publicKey,
-        wsolMint: NATIVE_MINT,
-        counterAccount: counterPDA,
-        tokenProgram: TOKEN_PROGRAM_ID,
-        associatedTokenProgram: ASSOCIATED_TOKEN_PROGRAM_ID,
       })
       .instruction();
 
@@ -316,6 +299,6 @@ describe("transfer-hook", () => {
 
     const tokenAccount = await getAccount(connection, delegateWSolTokenAccount);
 
-    assert.equal(Number(tokenAccount.amount), amount / 2);
+    assert.equal(Number(tokenAccount.amount), amount);
   });
 });

+ 3 - 5
tokens/token-2022/transfer-hook/anchor/TransferHookWhitelist/programs/transfer-hook/Cargo.toml

@@ -17,9 +17,7 @@ no-log-ix-name = []
 idl-build = ["anchor-lang/idl-build", "anchor-spl/idl-build"]
 
 [dependencies]
-anchor-lang = {version = "0.30.0", features = ["init-if-needed"]}
+anchor-lang = {version = "0.30.0", features = ["interface-instructions", "init-if-needed"]}
 anchor-spl = "0.30.0"
-solana-program = "1.17.13"
-
-spl-transfer-hook-interface = "0.4.1"
-spl-tlv-account-resolution = "0.5.0"
+spl-tlv-account-resolution = "0.6.3"
+spl-transfer-hook-interface = "0.6.3"

+ 28 - 74
tokens/token-2022/transfer-hook/anchor/TransferHookWhitelist/programs/transfer-hook/src/lib.rs

@@ -1,74 +1,33 @@
-use anchor_lang::{
-    prelude::*,
-    system_program::{create_account, CreateAccount},
-};
-use anchor_spl::{
-    associated_token::AssociatedToken, token_interface::{Mint, TokenAccount, TokenInterface}
-};
-use spl_tlv_account_resolution::state::ExtraAccountMetaList;
-use spl_transfer_hook_interface::instruction::{ExecuteInstruction, TransferHookInstruction};
+use anchor_lang::prelude::*;
+use anchor_spl::token_interface::{Mint, TokenAccount};
+use spl_tlv_account_resolution::{account::ExtraAccountMeta, seeds::Seed, state::ExtraAccountMetaList};
+use spl_transfer_hook_interface::instruction::ExecuteInstruction;
 
 declare_id!("DrWbQtYJGtsoRwzKqAbHKHKsCJJfpysudF39GBVFSxub");
 
 #[program]
 pub mod transfer_hook {
 
-    use spl_tlv_account_resolution::{account::ExtraAccountMeta, seeds::Seed};
-
     use super::*;
 
+    #[interface(spl_transfer_hook_interface::initialize_extra_account_meta_list)]
     pub fn initialize_extra_account_meta_list(
         ctx: Context<InitializeExtraAccountMetaList>,
     ) -> Result<()> {
-
-        let account_metas = vec![
-            ExtraAccountMeta::new_with_seeds(
-                &[Seed::Literal {
-                    bytes: "white_list".as_bytes().to_vec(),
-                }], // owner index (delegate PDA)
-                false, // is_signer
-                true,  // is_writable
-            )?,
-        ];
-
+        // set authority field on white_list account as payer address
         ctx.accounts.white_list.authority = ctx.accounts.payer.key();
 
-        // calculate account size
-        let account_size = ExtraAccountMetaList::size_of(account_metas.len())? as u64;
-        // calculate minimum required lamports
-        let lamports = Rent::get()?.minimum_balance(account_size as usize);
-
-        let mint = ctx.accounts.mint.key();
-        let signer_seeds: &[&[&[u8]]] = &[&[
-            b"extra-account-metas",
-            &mint.as_ref(),
-            &[ctx.bumps.extra_account_meta_list],
-        ]];
-
-        // create ExtraAccountMetaList account
-        create_account(
-            CpiContext::new(
-                ctx.accounts.system_program.to_account_info(),
-                CreateAccount {
-                    from: ctx.accounts.payer.to_account_info(),
-                    to: ctx.accounts.extra_account_meta_list.to_account_info(),
-                },
-            )
-            .with_signer(signer_seeds),
-            lamports,
-            account_size,
-            ctx.program_id,
-        )?;
+        let extra_account_metas = InitializeExtraAccountMetaList::extra_account_metas()?;
 
         // initialize ExtraAccountMetaList account with extra accounts
         ExtraAccountMetaList::init::<ExecuteInstruction>(
             &mut ctx.accounts.extra_account_meta_list.try_borrow_mut_data()?,
-            &account_metas,
+            &extra_account_metas,
         )?;
-
         Ok(())
     }
 
+    #[interface(spl_transfer_hook_interface::execute)]
     pub fn transfer_hook(ctx: Context<TransferHook>, _amount: u64) -> Result<()> {
 
         if !ctx.accounts.white_list.white_list.contains(&ctx.accounts.destination_token.key()) {
@@ -93,26 +52,6 @@ pub mod transfer_hook {
         Ok(())
     }
 
-    // fallback instruction handler as workaround to anchor instruction discriminator check
-    pub fn fallback<'info>(
-        program_id: &Pubkey,
-        accounts: &'info [AccountInfo<'info>],
-        data: &[u8],
-    ) -> Result<()> {
-        let instruction = TransferHookInstruction::unpack(data)?;
-
-        // match instruction discriminator to transfer hook interface execute instruction  
-        // token2022 program CPIs this instruction on token transfer
-        match instruction {
-            TransferHookInstruction::Execute { amount } => {
-                let amount_bytes = amount.to_le_bytes();
-
-                // invoke custom transfer hook instruction on our program
-                __private::__global::transfer_hook(program_id, accounts, &amount_bytes)
-            }
-            _ => return Err(ProgramError::InvalidInstructionData.into()),
-        }
-    }
 }
 
 #[derive(Accounts)]
@@ -122,14 +61,14 @@ pub struct InitializeExtraAccountMetaList<'info> {
 
     /// CHECK: ExtraAccountMetaList Account, must use these seeds
     #[account(
-        mut,
+        init,
         seeds = [b"extra-account-metas", mint.key().as_ref()], 
-        bump
+        bump,
+        space = ExtraAccountMetaList::size_of(InitializeExtraAccountMetaList::extra_account_metas()?.len())?,
+        payer = payer,
     )]
     pub extra_account_meta_list: AccountInfo<'info>,
     pub mint: InterfaceAccount<'info, Mint>,
-    pub token_program: Interface<'info, TokenInterface>,
-    pub associated_token_program: Program<'info, AssociatedToken>,
     pub system_program: Program<'info, System>,
     #[account(
         init_if_needed,
@@ -141,6 +80,21 @@ pub struct InitializeExtraAccountMetaList<'info> {
     pub white_list: Account<'info, WhiteList>,
 }
 
+// Define extra account metas to store on extra_account_meta_list account
+impl<'info> InitializeExtraAccountMetaList<'info> {
+    pub fn extra_account_metas() -> Result<Vec<ExtraAccountMeta>> {
+        Ok(vec![
+            ExtraAccountMeta::new_with_seeds(
+                &[Seed::Literal {
+                    bytes: "white_list".as_bytes().to_vec(),
+                }], 
+                false, // is_signer
+                true,  // is_writable
+            )?,
+        ])
+    }
+}
+
 // Order of accounts matters for this struct.
 // The first 4 accounts are the accounts required for token transfer (source, mint, destination, owner)
 // Remaining accounts are the extra accounts required from the ExtraAccountMetaList account

+ 16 - 37
tokens/token-2022/transfer-hook/anchor/TransferHookWhitelist/tests/transfer-hook.ts

@@ -2,7 +2,6 @@ import * as anchor from "@coral-xyz/anchor";
 import { Program } from "@coral-xyz/anchor";
 import { TransferHook } from "../target/types/transfer_hook";
 import {
-  PublicKey,
   SystemProgram,
   Transaction,
   sendAndConfirmTransaction,
@@ -17,7 +16,6 @@ import {
   ASSOCIATED_TOKEN_PROGRAM_ID,
   createAssociatedTokenAccountInstruction,
   createMintToInstruction,
-  createTransferCheckedInstruction,
   getAssociatedTokenAddressSync,
   createTransferCheckedWithTransferHookInstruction,
 } from "@solana/spl-token";
@@ -54,18 +52,6 @@ describe("transfer-hook", () => {
     ASSOCIATED_TOKEN_PROGRAM_ID
   );
 
-  // ExtraAccountMetaList address
-  // Store extra accounts required by the custom transfer hook instruction
-  const [extraAccountMetaListPDA] = PublicKey.findProgramAddressSync(
-    [Buffer.from("extra-account-metas"), mint.publicKey.toBuffer()],
-    program.programId
-  );
-
-  const [whiteListPDA] = PublicKey.findProgramAddressSync(
-    [Buffer.from("white_list")],
-    program.programId
-  );
-
   it("Create Mint Account with Transfer Hook Extension", async () => {
     const extensions = [ExtensionType.TransferHook];
     const mintLen = getMintLen(extensions);
@@ -152,8 +138,6 @@ describe("transfer-hook", () => {
       .initializeExtraAccountMetaList()
       .accounts({
         mint: mint.publicKey,
-        extraAccountMetaList: extraAccountMetaListPDA,
-        whiteList: whiteListPDA,
       })
       .instruction();
 
@@ -172,19 +156,15 @@ describe("transfer-hook", () => {
   });
 
   it("Add account to white list", async () => {
-
     const addAccountToWhiteListInstruction = await program.methods
       .addToWhitelist()
       .accounts({
         newAccount: destinationTokenAccount,
         signer: wallet.publicKey,
-        whiteList: whiteListPDA
       })
       .instruction();
 
-    const transaction = new Transaction().add(
-      addAccountToWhiteListInstruction
-    );
+    const transaction = new Transaction().add(addAccountToWhiteListInstruction);
 
     const txSig = await sendAndConfirmTransaction(
       connection,
@@ -194,29 +174,28 @@ describe("transfer-hook", () => {
     );
     console.log("White Listed:", txSig);
   });
-  
+
   it("Transfer Hook with Extra Account Meta", async () => {
     // 1 tokens
     const amount = 1 * 10 ** decimals;
     const bigIntAmount = BigInt(amount);
 
     // Standard token transfer instruction
-    const transferInstruction = await createTransferCheckedWithTransferHookInstruction(
-      connection,
-      sourceTokenAccount,
-      mint.publicKey,
-      destinationTokenAccount,
-      wallet.publicKey,
-      bigIntAmount,
-      decimals,
-      [],
-      "confirmed",
-      TOKEN_2022_PROGRAM_ID
-    );
+    const transferInstruction =
+      await createTransferCheckedWithTransferHookInstruction(
+        connection,
+        sourceTokenAccount,
+        mint.publicKey,
+        destinationTokenAccount,
+        wallet.publicKey,
+        bigIntAmount,
+        decimals,
+        [],
+        "confirmed",
+        TOKEN_2022_PROGRAM_ID
+      );
 
-    const transaction = new Transaction().add(
-      transferInstruction
-    );
+    const transaction = new Transaction().add(transferInstruction);
 
     const txSig = await sendAndConfirmTransaction(
       connection,