-
Notifications
You must be signed in to change notification settings - Fork 300
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #71 from solana-developers/transfer_hook_examples
Add 4 transfer hook examples
- Loading branch information
Showing
44 changed files
with
2,200 additions
and
0 deletions.
There are no files selected for viewing
8 changes: 8 additions & 0 deletions
8
tokens/token-2022/transfer-hook/anchor/TransferHookCounter/.gitignore
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
|
||
.anchor | ||
.DS_Store | ||
target | ||
**/*.rs.bk | ||
node_modules | ||
test-ledger | ||
.yarn |
8 changes: 8 additions & 0 deletions
8
tokens/token-2022/transfer-hook/anchor/TransferHookCounter/.prettierignore
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
|
||
.anchor | ||
.DS_Store | ||
target | ||
node_modules | ||
dist | ||
build | ||
test-ledger |
18 changes: 18 additions & 0 deletions
18
tokens/token-2022/transfer-hook/anchor/TransferHookCounter/Anchor.toml
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
[toolchain] | ||
|
||
[features] | ||
seeds = false | ||
skip-lint = false | ||
|
||
[programs.localnet] | ||
transfer_hook = "DrWbQtYJGtsoRwzKqAbHKHKsCJJfpysudF39GBVFSxub" | ||
|
||
[registry] | ||
url = "https://api.apr.dev" | ||
|
||
[provider] | ||
cluster = "Localnet" | ||
wallet = "~/.config/solana/id.json" | ||
|
||
[scripts] | ||
test = "yarn run ts-mocha -p ./tsconfig.json -t 1000000 tests/**/*.ts" |
13 changes: 13 additions & 0 deletions
13
tokens/token-2022/transfer-hook/anchor/TransferHookCounter/Cargo.toml
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
[workspace] | ||
members = [ | ||
"programs/*" | ||
] | ||
|
||
[profile.release] | ||
overflow-checks = true | ||
lto = "fat" | ||
codegen-units = 1 | ||
[profile.release.build-override] | ||
opt-level = 3 | ||
incremental = false | ||
codegen-units = 1 |
12 changes: 12 additions & 0 deletions
12
tokens/token-2022/transfer-hook/anchor/TransferHookCounter/migrations/deploy.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
// Migrations are an early feature. Currently, they're nothing more than this | ||
// single deploy script that's invoked from the CLI, injecting a provider | ||
// configured from the workspace's Anchor.toml. | ||
|
||
const anchor = require("@coral-xyz/anchor"); | ||
|
||
module.exports = async function (provider) { | ||
// Configure client to use the provider. | ||
anchor.setProvider(provider); | ||
|
||
// Add your deploy script here. | ||
}; |
21 changes: 21 additions & 0 deletions
21
tokens/token-2022/transfer-hook/anchor/TransferHookCounter/package.json
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
{ | ||
"scripts": { | ||
"lint:fix": "prettier */*.js \"*/**/*{.js,.ts}\" -w", | ||
"lint": "prettier */*.js \"*/**/*{.js,.ts}\" --check" | ||
}, | ||
"dependencies": { | ||
"@coral-xyz/anchor": "^0.29.0", | ||
"@solana/spl-token": "^0.4.0", | ||
"@solana/web3.js": "^1.89.1" | ||
}, | ||
"devDependencies": { | ||
"@types/bn.js": "^5.1.0", | ||
"@types/chai": "^4.3.0", | ||
"@types/mocha": "^9.0.0", | ||
"chai": "^4.3.4", | ||
"mocha": "^9.0.3", | ||
"prettier": "^2.6.2", | ||
"ts-mocha": "^10.0.0", | ||
"typescript": "^4.3.5" | ||
} | ||
} |
24 changes: 24 additions & 0 deletions
24
tokens/token-2022/transfer-hook/anchor/TransferHookCounter/programs/transfer-hook/Cargo.toml
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
[package] | ||
name = "transfer-hook" | ||
version = "0.1.0" | ||
description = "Created with Anchor" | ||
edition = "2021" | ||
|
||
[lib] | ||
crate-type = ["cdylib", "lib"] | ||
name = "transfer_hook" | ||
|
||
[features] | ||
no-entrypoint = [] | ||
no-idl = [] | ||
no-log-ix-name = [] | ||
cpi = ["no-entrypoint"] | ||
default = [] | ||
|
||
[dependencies] | ||
anchor-lang = {version = "0.29.0", features = ["init-if-needed"]} | ||
anchor-spl = "0.29.0" | ||
solana-program = "=1.17.17" | ||
|
||
spl-transfer-hook-interface = "0.5.0" | ||
spl-tlv-account-resolution = "0.5.0" |
2 changes: 2 additions & 0 deletions
2
tokens/token-2022/transfer-hook/anchor/TransferHookCounter/programs/transfer-hook/Xargo.toml
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
[target.bpfel-unknown-unknown.dependencies.std] | ||
features = [] |
172 changes: 172 additions & 0 deletions
172
tokens/token-2022/transfer-hook/anchor/TransferHookCounter/programs/transfer-hook/src/lib.rs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,172 @@ | ||
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::{ | ||
account::ExtraAccountMeta, seeds::Seed, state::ExtraAccountMetaList, | ||
}; | ||
use spl_transfer_hook_interface::instruction::{ExecuteInstruction, TransferHookInstruction}; | ||
|
||
declare_id!("DrWbQtYJGtsoRwzKqAbHKHKsCJJfpysudF39GBVFSxub"); | ||
|
||
#[error_code] | ||
pub enum MyError { | ||
#[msg("The amount is too big")] | ||
AmountTooBig, | ||
} | ||
|
||
#[program] | ||
pub mod transfer_hook { | ||
use super::*; | ||
|
||
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, | ||
)?; | ||
|
||
// initialize ExtraAccountMetaList account with extra accounts | ||
ExtraAccountMetaList::init::<ExecuteInstruction>( | ||
&mut ctx.accounts.extra_account_meta_list.try_borrow_mut_data()?, | ||
&account_metas, | ||
)?; | ||
|
||
Ok(()) | ||
} | ||
|
||
pub fn transfer_hook(ctx: Context<TransferHook>, amount: u64) -> Result<()> { | ||
|
||
if amount > 50 { | ||
msg!("The amount is too big {0}", amount); | ||
//return err!(MyError::AmountTooBig); | ||
} | ||
|
||
ctx.accounts.counter_account.counter.checked_add(1).unwrap(); | ||
|
||
msg!("This token has been transferred {0} times", ctx.accounts.counter_account.counter); | ||
|
||
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)] | ||
pub struct InitializeExtraAccountMetaList<'info> { | ||
#[account(mut)] | ||
payer: Signer<'info>, | ||
|
||
/// CHECK: ExtraAccountMetaList Account, must use these seeds | ||
#[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 | ||
)] | ||
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>, | ||
} | ||
|
||
// 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 | ||
// These accounts are provided via CPI to this program from the token2022 program | ||
#[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, can be SystemAccount or PDA owned by another program | ||
pub owner: UncheckedAccount<'info>, | ||
/// CHECK: ExtraAccountMetaList Account, | ||
#[account( | ||
seeds = [b"extra-account-metas", mint.key().as_ref()], | ||
bump | ||
)] | ||
pub extra_account_meta_list: UncheckedAccount<'info>, | ||
#[account( | ||
seeds = [b"counter"], | ||
bump | ||
)] | ||
pub counter_account: Account<'info, CounterAccount>, | ||
} | ||
|
||
#[account] | ||
pub struct CounterAccount { | ||
counter: u64, | ||
} |
Oops, something went wrong.