|
@@ -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,
|