Transfer Hook uzantısı ve Transfer Hook Interface, her token transferinde özel talimat mantığını çalıştıran Mint Account'lar oluşturma imkânı sunar.
Bu, token transferleri için birçok yeni kullanım senaryosunun kapısını açar. Örneğin:
- NFT telif ücretlerinin zorunlu kılınması
- Token alabilecek cüzdanlar için kara veya beyaz liste oluşturulması
- Token transferlerine özel ücretler uygulanması
- Özel token transfer olaylarının oluşturulması
- Token transferlerinizle ilgili istatistiklerin takip edilmesi
- Ve daha fazlası
Bunu başarmak için geliştiricilerin Transfer Hook Interface'i uygulayan bir program geliştirmesi ve Transfer Hook uzantısı etkinleştirilmiş bir Mint Account başlatması gerekir.
Mint Account'a ait tokenları içeren her token transferinde, Token Extensions program, Transfer Hook programı üzerinde bir talimatı çalıştırmak için bir Cross Program Invocation (CPI) gerçekleştirir.
Token Extensions program bir Transfer Hook programına CPI yaptığında, ilk transferdeki tüm hesaplar salt okunur hesaplara dönüştürülür. Bu, göndericinin imzalama yetkilerinin Transfer Hook programına geçmediği anlamına gelir.
Bu tasarım kararı, Transfer Hook programlarının kötü amaçlı kullanımını önlemek için alınmıştır.
Bu rehberde Anchor framework'ünü kullanarak bir Transfer Hook programı oluşturacağız; ancak Transfer Hook Interface'i yerel (native) bir program ile uygulamak da mümkündür. Anchor framework hakkında daha fazla bilgiyi burada bulabilirsiniz: Anchor Framework
Transfer Hook Interface'e Genel Bakış
Transfer Hook Interface, geliştiricilerin belirli bir Mint Account için her token transferinde çalıştırılan özel talimat mantığını uygulamasının bir yolunu sunar.
Transfer Hook Interface aşağıdaki talimatları tanımlar:
Execute: Token Extension programının her token transferinde çağırdığı bir talimat.InitializeExtraAccountMetaList(isteğe bağlı): ÖzelExecutetalimatının ihtiyaç duyduğu ek hesapların listesini saklayan bir hesap oluşturur.UpdateExtraAccountMetaList(isteğe bağlı): Mevcut listenin üzerine yazarak ek hesap listesini günceller.
InitializeExtraAccountMetaList talimatını arayüz üzerinden uygulamak
teknik olarak zorunlu değildir. Hesap, bir Transfer Hook programındaki herhangi bir talimatla
oluşturulabilir.
Ancak bu hesabın Program Derived Address (PDA) adresi, aşağıdaki seed'ler kullanılarak türetilmelidir:
- Koda sabit olarak yazılmış "extra-account-metas" dizesi
- Mint Account adresi
- Transfer Hook programının ID'si
const [pda] = await PublicKey.findProgramAddress([Buffer.from("extra-account-metas"), mint.publicKey.toBytes()],program.programId // transfer hook program ID);
Execute talimatının ihtiyaç duyduğu ek hesaplar önceden tanımlanmış PDA'da
saklandığında, bu hesaplar istemci tarafında token transfer talimatına otomatik olarak
eklenebilir.
Hello World Transfer Hook
Bu örnek, transfer hook'ların "hello world" örneğidir. Her token transferinde yalnızca bir mesaj yazdıran basit bir transfer hook'tur. Başlamak için örneği, Solana programlarını derlemek ve dağıtmak için kullanılan çevrimiçi bir araç olan Solana Playground'da açıyoruz: bağlantı
Örnek, transfer hook arayüzünü uygulayan bir Anchor programından ve programı test etmek için bir test dosyasından oluşur.
Bu program yalnızca 3 talimat içerir:
initialize_extra_account_meta_list:transfer_hooktalimatının ihtiyaç duyduğu ek hesapların listesini saklayan bir hesap oluşturur. Hello world örneğinde bu liste boş bırakılır.transfer_hook: Bu talimat, her token transferinde CPI ile çağrılır ve wrapped SOL token transferi gerçekleştirir.fallback: Anchor kullandığımız ve Token Program yerel (native) bir program olduğu için, talimat ayırıcısını (discriminator) elle eşleştirmek ve özeltransfer_hooktalimatımızı çağırmak üzere bir fallback talimatı eklememiz gerekir. Bu fonksiyonu değiştirmenize gerek yok.
Token her transfer edildiğinde bu transfer_hook fonksiyonu
Token Program tarafından çağrılacaktır.
pub fn transfer_hook(ctx: Context<TransferHook>, amount: u64) -> Result<()> {msg!("Hello Transfer Hook!");Ok(())}
Bu fonksiyona artık ek mantığınızı ekleyebilirsiniz. Örneğin, 50'den büyük bir miktar transfer edildiğinde transferin başarısız olmasını şu şekilde sağlayabilirsiniz:
#[error_code]pub enum MyError {#[msg("The amount is too big")]AmountTooBig,}pub fn transfer_hook(ctx: Context<TransferHook>, amount: u64) -> Result<()> {msg!("Hello Transfer Hook!");if amount > 50 {return err!(MyError::AmountTooBig);}Ok(())}
Örneği Solana Playground'da çalıştırmak için şu bağlantıyı izleyin: bağlantı
Playground terminalinde build komutunu çalıştırın; bu komut lib.rs dosyasındaki
declare_id değerini yeni oluşturulan program ID'si ile güncelleyecektir. Ardından
programınızı devnet'e dağıtmak için deploy komutunu çalıştırın. Program
dağıtıldığında, terminalde test komutunu kullanarak test dosyasını çalıştırabilirsiniz.
Bu size aşağıdakine benzer bir çıktı verecektir:
transfer-hook.test.ts:transfer-hookTransaction Signature: kB8Hkn8NEavK7xztEhQZXKSeidgEK81PZNmgSSodZFVyzM9o18GwNi4bDWD9Q3cbmh75Vn1jqyinYH3YdgJfnuJ✔ Create Mint Account with Transfer Hook Extension (539ms)Transaction Signature: Bf9eYieas6jpV8UxS5upuRv2oMebDdHgDstLMw86ptM7cd4qRpaxRyFYmNZC1WZMcDXP68PoGoApUrrrQKeBbJA✔ Create Token Accounts and Mint Tokens (744ms)Transaction Signature: 3oRtCjM6oSdkxQKUyGF3r6hmZGLUpNefihHoGQT5cftRPeQtimvVukLPvb3PSpvLrUsoCWBnz6nSm6ZbPRUhx7UP✔ Create ExtraAccountMetaList Account (728ms)Transfer Signature: WNAWK2o7wWpVCqPz2uoMtHRe1F5B1jfW8v4kezdQYqaXE3nRAPfqUFkFHg31uYmpZCjncZUwo4g9ZuhgMC9cS1i✔ Transfer Hook with Extra Account Meta (1327ms)4 passing (3s)
Tokenınızı oluşturmak için JavaScript kullanmak istemiyorsanız, programınızı dağıttıktan sonra
Solana CLI'daki spl-token komutunu da kullanabilirsiniz:
spl-token --program-id TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb create-token --transfer-hook yourTransferHookProgramId
Sayaçlı Transfer Hook
Sonraki örnek, tokenınız her transfer edildiğinde bir sayacı nasıl artırabileceğinizi gösterecek. bağlantı
Transfer hook'unuza ek hesaplar gerektiren mantık eklemek istiyorsanız, bu hesapları ExtraAccountMetaList hesabına eklemeniz gerekir. Bizim örneğimizde, tokenın kaç kez transfer edildiğini kaydeden bir PDA istiyoruz.
Bu, initialize_extra_account_meta_list talimatına aşağıdaki kod eklenerek
yapılabilir:
let account_metas = vec![ExtraAccountMeta::new_with_seeds(&[Seed::Literal {bytes: "counter".as_bytes().to_vec(),}],false, // is_signertrue, // is_writable)?,];
Ayrıca yeni mint account'u başlatırken bu hesabı da oluşturmamız ve tokenı her transfer ettiğimizde bu hesabı iletmemiz gerekir.
#[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>,}#[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 programpub 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(mut,seeds = [b"counter"],bump)]pub counter_account: Account<'info, CounterAccount>,}
Hesap, u64 türünde bir sayaç değişkeni tutacak:
#[account]pub struct CounterAccount {counter: u64,}
Artık transfer hook fonksiyonumuzda, her çağrıldığında bu sayacı bir artırabiliriz:
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(())}
İstemcide bu ek hesaplar bir yardımcı fonksiyon tarafından otomatik olarak eklenir:
web3.js v3 için @solana-program/token-2022 içindeki getTransferCheckedWithTransferHookInstructionAsync,
veya eski web3.js için @solana/spl-token içindeki
createTransferCheckedWithTransferHookInstruction. v3 yardımcı fonksiyonu bir Kit RPC alır;
bu nedenle Connection ile aynı uç nokta için @solana/kit içindeki createSolanaRpc ile bir RPC oluşturun.
const rpc = createSolanaRpc(connection.rpcEndpoint);let transferInstructionWithHelper =await getTransferCheckedWithTransferHookInstructionAsync({ rpc },{source: sourceTokenAccount.toBase58(),mint: mint.publicKey.toBase58(),destination: destinationTokenAccount.toBase58(),authority: wallet, // a v3 Keypair, passed as the transfer's signeramount: amountBigInt,decimals});
Örneği Solana Playground'da çalıştırmak için şu bağlantıyı izleyin: bağlantı
Ardından orada build yazın; bu, lib.rs dosyasındaki declare_id değerini
yeni oluşturulan program ID'si ile güncelleyecektir. Sonra programınızı devnet'e
dağıtmak için deploy yazın. Program dağıtıldığında, terminalde test yazarak
test dosyasını çalıştırabilirsiniz.
Bu size aşağıdaki çıktıyı verecektir. Son işlemde, tokenınızın kaç kez transfer edildiğini görebileceksiniz:
"This token has been transferred 1 times"
Running tests...transfer-hook.test.ts:transfer-hookTransaction Signature: 48r6effAA4B9RVh13eBXdGjmcPKcm6QwnvodX2dT5nNfJyzoS3AejqatKXyqcmpzPdcmpTjgALnd1xx7v17ggptV✔ Create Mint Account with Transfer Hook Extension (545ms)Transaction Signature: nfkBH6cbM5c94od3VG4QmxHkXJzm6VEFxogbQKpd7gERJNgESyu1gEjLJnPiUer59sXnx787eB6hYBkhdkFnzdL✔ Create Token Accounts and Mint Tokens (354ms)Extra accounts meta: nullTransaction Signature: 4T6FS3Y95Kjkf9fy5jtCYWo2Wf1SSQKmo6GUK2YqXEcgR4Wrr6aLmnoEBcBNCpEv4ALbJuwu5KtVdxb1S3ynMPJY✔ Create ExtraAccountMetaList Account (695ms)Extra accounts meta: 9mifVeGPh7CHyf1NrcUWzzVKMU7g3AwQ6L3md3fMNqjuCounter PDa: 334HLdMwbhSGYf8QWHHmEkeZf6x6caXGF6oxVnCEmaQdTransfer Signature: 32zoL4oTC3XPVsgeDmT3KsTS4v8U4qe3GPKMF72QX5eSHgAFagKEyvRrGuoP2UEGLpj41Ygm9dSRi5YKghxS24EN✔ Transfer Hook with Extra Account Meta (776ms)4 passing (2s)
Burada token her transfer edildiğinde bir sayacı artırdığımız için, transfer hook talimatının yalnızca bir transfer sırasında çağrılabildiğinden emin olmamız gerekir; aksi takdirde biri transfer hook talimatını doğrudan çağırarak sayacımızı bozabilir. Bu, tüm transfer hook'larınıza eklemeniz gereken bir kontroldür.
Kontrolü şu şekilde ekleyebilirsiniz:
fn assert_is_transferring(ctx: &Context<TransferHook>) -> Result<()> {let source_token_info = ctx.accounts.source_token.to_account_info();let mut account_data_ref: RefMut<&mut [u8]> = source_token_info.try_borrow_mut_data()?;let mut account = PodStateWithExtensionsMut::<PodAccount>::unpack(*account_data_ref)?;let account_extension = account.get_extension_mut::<TransferHookAccount>()?;if !bool::from(account_extension.transferring) {return err!(TransferError::IsNotCurrentlyTransferring);}Ok(())}
Ardından bunu transfer_hook fonksiyonunuzun başında çağırın:
#[error_code]pub enum TransferError {#[msg("The token is not currently transferring")]IsNotCurrentlyTransferring,}#[interface(spl_transfer_hook_interface::execute)]pub fn transfer_hook(ctx: Context<TransferHook>, _amount: u64) -> Result<()> {// Fail this instruction if it is not called from within a transfer hookassert_is_transferring(&ctx)?;ctx.accounts.counter_account.counter.checked_add(1).unwrap();msg!("This token has been transferred {0} times", ctx.accounts.counter_account.counter);Ok(())}
wSOl Transfer Ücretli Transfer Hook (ileri seviye örnek)
Bu rehberin sonraki bölümünde, Anchor framework'ünü kullanarak daha gelişmiş bir Transfer Hook programı oluşturacağız. Bu program, göndericiden her token transferi için wSOL ücreti ödemesini isteyecek.
wSOL transferleri, Transfer Hook programından türetilen bir PDA olan bir delegate kullanılarak gerçekleştirilecek. Bu gereklidir çünkü token transfer talimatının ilk göndericisinin imzasına Transfer Hook programında erişilemez.
Bu program yalnızca 3 talimat içerir:
initialize_extra_account_meta_list:transfer_hooktalimatının ihtiyaç duyduğu ek hesapların listesini saklayan bir hesap oluşturur.transfer_hook: Bu talimat, her token transferinde CPI ile çağrılır ve wrapped SOL token transferi gerçekleştirir.fallback: Transfer hook arayüzü talimatlarının belirli ayırıcıları (talimat tanımlayıcıları) vardır. Bir Anchor programında, talimat ayırıcısını elle eşleştirmek ve özeltransfer_hooktalimatımızı çağırmak için bir fallback talimatı kullanabiliriz.
Bu program, göndericiden her token transferinde wrapped SOL (wSOL) cinsinden bir ücret ödemesini isteyecek. İşte nihai program.
Başlarken
Önce şu Solana Playground bağlantısını açın ve projeyi kopyalamak için "Import" düğmesine tıklayın.
Başlangıç kodu, oluşturacağımız program için iskeleti hazırlanmış bir lib.rs ve
transfer-hook.test.ts dosyası içerir. lib.rs dosyasında
aşağıdaki kodu görmelisiniz:
use anchor_lang::{prelude::*,system_program::{create_account, CreateAccount},};use anchor_spl::{associated_token::AssociatedToken,token_interface::{transfer_checked, Mint, TokenAccount, TokenInterface, TransferChecked},};use spl_tlv_account_resolution::{account::ExtraAccountMeta, seeds::Seed, state::ExtraAccountMetaList,};use spl_transfer_hook_interface::instruction::{ExecuteInstruction, TransferHookInstruction};declare_id!("E6wu6Nykdra8gXs57Zqo7hY6DLaWugTmD3uuuBmX2Vxt");#[program]pub mod transfer_hook {use super::*;pub fn initialize_extra_account_meta_list(ctx: Context<InitializeExtraAccountMetaList>,) -> Result<()> {Ok(())}pub fn transfer_hook(ctx: Context<TransferHook>, amount: u64) -> Result<()> {Ok(())}pub fn fallback<'info>(program_id: &Pubkey,accounts: &'info [AccountInfo<'info>],data: &[u8],) -> Result<()> {Ok(())}}#[derive(Accounts)]pub struct InitializeExtraAccountMetaList {}#[derive(Accounts)]pub struct TransferHook {}
Projeyi içe aktardıktan sonra, Playground terminalinde build komutunu
kullanarak programı derleyin.
build
Bu, lib.rs dosyasındaki declare_id değerini yeni oluşturulan
program ID'si ile güncelleyecektir.
ExtraAccountMetas Hesabını Başlatma Talimatı
Bu adımda, Transfer Hook programımız için initialize_extra_account_meta_list
talimatını uygulayacağız. Bu talimat, transfer_hook talimatımızın ihtiyaç duyduğu
ek hesapları saklayacak bir ExtraAccountMetas hesabı oluşturur.
Bu örnekte initialize_extra_account_meta_list talimatı 7
hesap gerektirir:
payer: ExtraAccountMetas hesabının oluşturulması için ödeme yapan hesap.extra_account_meta_list:transfer_hooktalimatımızın ihtiyaç duyduğu hesapların listesini saklamak için oluşturulan ExtraAccountMetas hesabı.mint: Bu Transfer Hook programına işaret eden Mint Account. Mint adresi,extra_account_meta_listPDA'sını türetmek için gerekli bir seed'dir.wsol_mint: Wrapped SOL mint'i.token_program: Orijinal Token Program ID'siassociated_token_program: Associated Token Program ID'si.system_program: Yeni hesaplar oluşturulurken gerekli olan hesap olan System Program.
mint, wsol_mint ve associated_token_program adresleri,
wSOL associated token account adreslerini türetmek için kullanılacaktır. Bu
hesaplar transfer_hook talimatı için gereklidir ve
ExtraAccountMetas hesabında saklanacaktır.
Aşağıdaki başlangıç kodunu değiştirerek InitializeExtraAccountMetaList
struct'ını güncelleyin:
#[derive(Accounts)]pub struct InitializeExtraAccountMetaList {}
Aşağıda verilen kodla:
#[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>,pub wsol_mint: InterfaceAccount<'info, Mint>,pub token_program: Interface<'info, TokenInterface>,pub associated_token_program: Program<'info, AssociatedToken>,pub system_program: Program<'info, System>,}
Ardından, aşağıdaki başlangıç kodunu değiştirerek
initialize_extra_account_meta_list talimatını güncelleyin:
pub fn initialize_extra_account_meta_list(ctx: Context<InitializeExtraAccountMetaList>,) -> Result<()> {Ok(())}
Aşağıdaki kodla:
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// The `addExtraAccountsToInstruction` JS helper function resolving incorrectlylet account_metas = vec![// index 5, wrapped SOL mintExtraAccountMeta::new_with_pubkey(&ctx.accounts.wsol_mint.key(), false, false)?,// index 6, token programExtraAccountMeta::new_with_pubkey(&ctx.accounts.token_program.key(), false, false)?,// index 7, associated token programExtraAccountMeta::new_with_pubkey(&ctx.accounts.associated_token_program.key(),false,false,)?,// index 8, delegate PDAExtraAccountMeta::new_with_seeds(&[Seed::Literal {bytes: "delegate".as_bytes().to_vec(),}],false, // is_signerfalse, // is_writable)?,// index 9, delegate wrapped SOL token accountExtraAccountMeta::new_external_pda_with_seeds(7, // associated token program index&[Seed::AccountKey { index: 8 }, // owner index (delegate PDA)Seed::AccountKey { index: 6 }, // token program indexSeed::AccountKey { index: 5 }, // wsol mint index],false, // is_signertrue, // is_writable)?,// index 10, sender wrapped SOL token accountExtraAccountMeta::new_external_pda_with_seeds(7, // associated token program index&[Seed::AccountKey { index: 3 }, // owner indexSeed::AccountKey { index: 6 }, // token program indexSeed::AccountKey { index: 5 }, // wsol mint index],false, // is_signertrue, // is_writable)?,];// calculate account sizelet account_size = ExtraAccountMetaList::size_of(account_metas.len())? as u64;// calculate minimum required lamportslet 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 accountcreate_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 accountsExtraAccountMetaList::init::<ExecuteInstruction>(&mut ctx.accounts.extra_account_meta_list.try_borrow_mut_data()?,&account_metas,)?;Ok(())}
Güncellenen talimat mantığını adım adım inceleyelim. ExtraAccountMetas hesabında saklanması gereken ek hesapları listeleyerek başlıyoruz.
// index 0-3 are the accounts required for token transfer (source, mint, destination, owner)// index 4 is address of ExtraAccountMetaList account// The `addExtraAccountsToInstruction` JS helper function resolving incorrectlylet account_metas = vec![// index 5, wrapped SOL mintExtraAccountMeta::new_with_pubkey(&ctx.accounts.wsol_mint.key(), false, false)?,// index 6, token programExtraAccountMeta::new_with_pubkey(&ctx.accounts.token_program.key(), false, false)?,// index 7, associated token programExtraAccountMeta::new_with_pubkey(&ctx.accounts.associated_token_program.key(),false,false,)?,// index 8, delegate PDAExtraAccountMeta::new_with_seeds(&[Seed::Literal {bytes: "delegate".as_bytes().to_vec(),}],false, // is_signertrue, // is_writable)?,// index 9, delegate wrapped SOL token accountExtraAccountMeta::new_external_pda_with_seeds(7, // associated token program index&[Seed::AccountKey { index: 8 }, // owner index (delegate PDA)Seed::AccountKey { index: 6 }, // token program indexSeed::AccountKey { index: 5 }, // wsol mint index],false, // is_signertrue, // is_writable)?,// index 10, sender wrapped SOL token accountExtraAccountMeta::new_external_pda_with_seeds(7, // associated token program index&[Seed::AccountKey { index: 3 }, // owner indexSeed::AccountKey { index: 6 }, // token program indexSeed::AccountKey { index: 5 }, // wsol mint index],false, // is_signertrue, // is_writable)?,];
Bu hesapları saklamanın üç yöntemi vardır:
- Hesap adresini doğrudan saklayın:
- Wrapped SOL mint adresi
- Token Program ID
- Associated Token Program ID
// index 5, wrapped SOL mintExtraAccountMeta::new_with_pubkey(&ctx.accounts.wsol_mint.key(), false, false)?,// index 6, token programExtraAccountMeta::new_with_pubkey(&ctx.accounts.token_program.key(), false, false)?,// index 7, associated token programExtraAccountMeta::new_with_pubkey(&ctx.accounts.associated_token_program.key(),false,false,)?,
- Transfer Hook programı için bir PDA türetmek üzere seed'leri saklayın:
- Delegate PDA
// index 8, delegate PDAExtraAccountMeta::new_with_seeds(&[Seed::Literal {bytes: "delegate".as_bytes().to_vec(),}],false, // is_signerfalse, // is_writable)?,
- Transfer Hook programı dışındaki bir program için PDA türetmek üzere seed'leri saklayın:
- Delegate'in wSOL associated token account'u
- Göndericinin wSOL associated token account'u
// index 9, delegate wrapped SOL token accountExtraAccountMeta::new_external_pda_with_seeds(7, // associated token program index&[Seed::AccountKey { index: 8 }, // owner index (delegate PDA)Seed::AccountKey { index: 6 }, // token program indexSeed::AccountKey { index: 5 }, // wsol mint index],false, // is_signertrue, // is_writable)?,// index 10, sender wrapped SOL token accountExtraAccountMeta::new_external_pda_with_seeds(7, // associated token program index&[Seed::AccountKey { index: 3 }, // owner indexSeed::AccountKey { index: 6 }, // token program indexSeed::AccountKey { index: 5 }, // wsol mint index],false, // is_signertrue, // is_writable)?,
Ardından, ExtraAccountMetas listesini saklamak için gereken boyutu ve rent'i hesaplıyoruz.
// calculate account sizelet account_size = ExtraAccountMetaList::size_of(account_metas.len())? as u64;// calculate minimum required lamportslet lamports = Rent::get()?.minimum_balance(account_size as usize);
Sonra, bir hesap oluşturmak ve sahibi olarak Transfer Hook Programı'nı ayarlamak için System Program'a bir CPI yapıyoruz. Yeni hesabın adresi olarak PDA'yı kullandığımız için PDA seed'leri, CPI üzerinde imzalayan seed'ler olarak dahil edilir.
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 accountcreate_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,)?;
Hesabı oluşturduktan sonra, ExtraAccountMetas listesini saklamak için hesap verilerini başlatıyoruz.
// initialize ExtraAccountMetaList account with extra accountsExtraAccountMetaList::init::<ExecuteInstruction>(&mut ctx.accounts.extra_account_meta_list.try_borrow_mut_data()?,&account_metas,)?;
Bu örnekte ExtraAccountMetas hesabını oluşturmak için Transfer Hook arayüzünü kullanmıyoruz.
Özel Transfer Hook Talimatı
Şimdi özel transfer_hook talimatını uygulayalım. Bu, Token Extensions Program'ın her token transferinde çağıracağı talimattır.
Bu örnekte her token transferi için wSOL ile ödenen bir ücret talep edeceğiz. Basitlik açısından ücret tutarı, token transfer tutarına eşittir.
Şu başlangıç kodunu değiştirerek TransferHook struct'ını güncelleyin:
#[derive(Accounts)]pub struct TransferHook {}
Aşağıdaki güncel kodla:
Bu struct'taki hesapların sırasının önemli olduğunu unutmayın. Token Extensions Program, bu Transfer Hook programına CPI yaparken hesapları bu sırayla sağlar.
// 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 programpub owner: UncheckedAccount<'info>,/// CHECK: ExtraAccountMetaList Account,#[account(seeds = [b"extra-account-metas", mint.key().as_ref()],bump)]pub extra_account_meta_list: UncheckedAccount<'info>,pub wsol_mint: InterfaceAccount<'info, Mint>,pub token_program: Interface<'info, TokenInterface>,pub associated_token_program: Program<'info, AssociatedToken>,#[account(seeds = [b"delegate"],bump)]pub delegate: SystemAccount<'info>,#[account(mut,token::mint = wsol_mint,token::authority = delegate,)]pub delegate_wsol_token_account: InterfaceAccount<'info, TokenAccount>,#[account(mut,token::mint = wsol_mint,token::authority = owner,)]pub sender_wsol_token_account: InterfaceAccount<'info, TokenAccount>,}
İlk 4 hesap, asıl token transferi için gereken hesaplardır.
#[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 programpub owner: UncheckedAccount<'info>,
- hesap,
transfer_hooktalimatımızın ihtiyaç duyduğu ek hesapların listesini saklayan ExtraAccountMeta hesabının adresidir.
/// CHECK: ExtraAccountMetaList Account#[account(seeds = [b"extra-account-metas", mint.key().as_ref()],bump)]pub extra_account_meta_list: UncheckedAccount<'info>,
Kalan hesaplar, ExtraAccountMetas hesabında initialize_extra_account_meta_list talimatında tanımladığımız sırayla listelenen hesaplardır.
pub wsol_mint: InterfaceAccount<'info, Mint>,pub token_program: Interface<'info, TokenInterface>,pub associated_token_program: Program<'info, AssociatedToken>,#[account(mut,seeds = [b"delegate"],bump)]pub delegate: SystemAccount<'info>,#[account(mut,token::mint = wsol_mint,token::authority = delegate,)]pub delegate_wsol_token_account: InterfaceAccount<'info, TokenAccount>,#[account(mut,token::mint = wsol_mint,token::authority = owner,)]pub sender_wsol_token_account: InterfaceAccount<'info, TokenAccount>,
Ardından, şu başlangıç kodunu değiştirerek transfer_hook talimatını güncelleyin:
pub fn transfer_hook(ctx: Context<TransferHook>, amount: u64) -> Result<()> {Ok(())}
Aşağıdaki güncel kodla:
// Require SOL fee on transfer, lamport fee is equal to transfer amount// If this fails, the initial token transfer failspub fn transfer_hook(ctx: Context<TransferHook>, amount: u64) -> Result<()> {msg!("Transfer WSOL using delegate PDA");let signer_seeds: &[&[&[u8]]] = &[&[b"delegate", &[ctx.bumps.delegate]]];// transfer WSOL from sender to delegate token account using delegate PDAtransfer_checked(CpiContext::new(ctx.accounts.token_program.to_account_info(),TransferChecked {from: ctx.accounts.sender_wsol_token_account.to_account_info(),mint: ctx.accounts.wsol_mint.to_account_info(),to: ctx.accounts.delegate_wsol_token_account.to_account_info(),authority: ctx.accounts.delegate.to_account_info(),},).with_signer(signer_seeds),amount,ctx.accounts.wsol_mint.decimals,)?;Ok(())}
Talimat mantığında, göndericinin wSOL token account'undan wSOL aktarmak için bir CPI yapıyoruz. Bu transfer, delegate PDA kullanılarak imzalanır. Her token transferi için gönderen, önce transfer tutarı kadar delegate'i onaylamalıdır.
Fallback Talimatı
Son olarak, Token Extensions Program'dan gelen CPI'ı işlemek için Anchor programına bir fallback talimatı eklememiz gerekiyor.
Bu adım, Anchor'ın talimat discriminator'larını üretme biçimi ile Transfer Hook arayüzü talimatlarında kullanılanlar arasındaki farktan dolayı gereklidir. transfer_hook talimatının discriminator'ı, Transfer Hook arayüzündeki ile eşleşmeyecektir.
Şu başlangıç kodunu değiştirerek fallback talimatını güncelleyin:
pub fn fallback<'info>(program_id: &Pubkey,accounts: &'info [AccountInfo<'info>],data: &[u8],) -> Result<()> {Ok(())}
Aşağıdaki güncel kodla:
// fallback instruction handler as workaround to anchor instruction discriminator checkpub 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 transfermatch 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()),}}
Fallback talimatı, gelen bir talimatın discriminator'ının Transfer Hook arayüzündeki Execute talimatıyla eşleşip eşleşmediğini kontrol eder. Eşleşme başarılıysa Anchor programımızdaki transfer_hook talimatını çağırır.
Şu anda bu süreci basitleştiren, henüz yayımlanmamış bir Anchor özelliği bulunuyor. Bu özellik fallback talimatına duyulan ihtiyacı ortadan kaldıracak.
Programı Derleme ve Dağıtma
Transfer Hook programı artık tamamlandı. Programı dağıtmak için Playground cüzdanınızda yeterli Devnet SOL bulunduğundan emin olun.
Programı derlemek için şu komutu kullanın:
build
Ardından programı şu komutla dağıtın:
deploy
Test Dosyasına Genel Bakış
Şimdi programı test edelim. transfer-hook.test.ts dosyasını açın; aşağıdaki başlangıç kodunu görmelisiniz:
Aşağıdaki test dosyası, Anchor'ın TypeScript istemcisi (AnchorProvider, program.methods ve sunduğu Wallet) eski web3.js türleri üzerine kurulu olduğundan eski web3.js (@solana/web3.js v1) ve @solana/spl-token kullanır. web3.js v3 ve @solana-program/token-2022 ile yazılmış istemci tarafı gönderim yolu için Transfer Hook Entegrasyon Rehberi'ne bakın.
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,Keypair,} from "@solana/web3.js";import {ExtensionType,TOKEN_2022_PROGRAM_ID,getMintLen,createInitializeMintInstruction,createInitializeTransferHookInstruction,addExtraAccountsToInstruction,ASSOCIATED_TOKEN_PROGRAM_ID,createAssociatedTokenAccountInstruction,createMintToInstruction,createTransferCheckedInstruction,getAssociatedTokenAddressSync,createApproveInstruction,createSyncNativeInstruction,NATIVE_MINT,TOKEN_PROGRAM_ID,getAccount,getOrCreateAssociatedTokenAccount,} from "@solana/spl-token";import assert from "assert";describe("transfer-hook", () => {// Configure the client to use the local cluster.const provider = anchor.AnchorProvider.env();anchor.setProvider(provider);const program = anchor.workspace.TransferHook as Program<TransferHook>;const wallet = provider.wallet as anchor.Wallet;const connection = provider.connection;// Generate keypair to use as address for the transfer-hook enabled mintconst mint = new Keypair();const decimals = 9;// Sender token account addressconst sourceTokenAccount = getAssociatedTokenAddressSync(mint.publicKey,wallet.publicKey,false,TOKEN_2022_PROGRAM_ID,ASSOCIATED_TOKEN_PROGRAM_ID);// Recipient token account addressconst recipient = Keypair.generate();const destinationTokenAccount = getAssociatedTokenAddressSync(mint.publicKey,recipient.publicKey,false,TOKEN_2022_PROGRAM_ID,ASSOCIATED_TOKEN_PROGRAM_ID);// ExtraAccountMetaList address// Store extra accounts required by the custom transfer hook instructionconst [extraAccountMetaListPDA] = PublicKey.findProgramAddressSync([Buffer.from("extra-account-metas"), mint.publicKey.toBuffer()],program.programId);// PDA delegate to transfer wSOL tokens from senderconst [delegatePDA] = PublicKey.findProgramAddressSync([Buffer.from("delegate")],program.programId);// Sender wSOL token account addressconst senderWSolTokenAccount = getAssociatedTokenAddressSync(NATIVE_MINT, // mintwallet.publicKey // owner);// Delegate PDA wSOL token account address, to receive wSOL tokens from senderconst delegateWSolTokenAccount = getAssociatedTokenAddressSync(NATIVE_MINT, // mintdelegatePDA, // ownertrue // allowOwnerOffCurve);// Create the two WSol token accounts as part of setupbefore(async () => {// WSol Token Account for senderawait getOrCreateAssociatedTokenAccount(connection,wallet.payer,NATIVE_MINT,wallet.publicKey);// WSol Token Account for delegate PDAawait getOrCreateAssociatedTokenAccount(connection,wallet.payer,NATIVE_MINT,delegatePDA,true);});it("Create Mint Account with Transfer Hook Extension", async () => {});it("Create Token Accounts and Mint Tokens", async () => {});it("Create ExtraAccountMetaList Account", async () => {});it("Transfer Hook with Extra Account Meta", async () => {});});
Önce, yeni bir Mint Account için adres olarak kullanılacak bir keypair oluşturuyoruz. Mint adresini kullanarak, token transferi için kullanacağımız Associated Token Account (ATA) adreslerini türetiyoruz.
// Generate keypair to use as address for the transfer-hook enabled mintconst mint = new Keypair();const decimals = 9;// Sender token account addressconst sourceTokenAccount = getAssociatedTokenAddressSync(mint.publicKey,wallet.publicKey,false,TOKEN_2022_PROGRAM_ID,ASSOCIATED_TOKEN_PROGRAM_ID);// Recipient token account addressconst recipient = Keypair.generate();const destinationTokenAccount = getAssociatedTokenAddressSync(mint.publicKey,recipient.publicKey,false,TOKEN_2022_PROGRAM_ID,ASSOCIATED_TOKEN_PROGRAM_ID);
Ardından ExtraAccountMetas hesabı için PDA'yı türetiyoruz. Bu hesap, özel transfer hook talimatının ihtiyaç duyduğu ek hesapları saklamak için oluşturulur.
// ExtraAccountMetaList address// Store extra accounts required by the custom transfer hook instructionconst [extraAccountMetaListPDA] = PublicKey.findProgramAddressSync([Buffer.from("extra-account-metas"), mint.publicKey.toBuffer()],program.programId);
Ayrıca delegate olarak kullanılacak PDA'yı da türetiyoruz. Gönderen, bu adresi wSOL token account'u için delegate olarak onaylamalıdır. Bu delegate PDA, özel transfer hook talimatındaki wSOL transferi için "imza atmak" amacıyla kullanılır.
// PDA delegate to transfer wSOL tokens from senderconst [delegatePDA] = PublicKey.findProgramAddressSync([Buffer.from("delegate")],program.programId);
Ek olarak wSOL token account'larının adreslerini türetiyoruz. İlk adres, göndericinin wSOL token account'una aittir; transfer hook talimatının gerektirdiği transfer ücretini ödemek için bu hesabın fonlanması gerekir. İkinci adres, delegate PDA'ya ait wSOL token account'udur. Bu örnekte tüm wSOL ücretleri bu hesaba gönderilir.
// Sender wSOL token account addressconst senderWSolTokenAccount = getAssociatedTokenAddressSync(NATIVE_MINT, // mintwallet.publicKey // owner);// Delegate PDA wSOL token account address, to receive wSOL tokens from senderconst delegateWSolTokenAccount = getAssociatedTokenAddressSync(NATIVE_MINT, // mintdelegatePDA, // ownertrue // allowOwnerOffCurve);
Son olarak, kurulumun bir parçası olarak wSOL token account'larını oluşturuyoruz.
// Create the two WSol token accounts as part of setupbefore(async () => {// WSol Token Account for senderawait getOrCreateAssociatedTokenAccount(connection,wallet.payer,NATIVE_MINT,wallet.publicKey);// WSol Token Account for delegate PDAawait getOrCreateAssociatedTokenAccount(connection,wallet.payer,NATIVE_MINT,delegatePDA,true);});
Mint Account Oluşturma
Başlamak için, Transfer Hook uzantısı etkinleştirilmiş yeni bir Mint Account oluşturmak üzere bir işlem hazırlayın. Bu işlemde, programımızı uzantıda saklanan Transfer Hook programı olarak belirttiğinizden emin olun.
Transfer Hook uzantısını etkinleştirmek, Token Extensions Program'ın her token transferinde hangi programı çağıracağını belirlemesini sağlar.
Yer tutucu testi değiştirin:
it("Create Mint Account with Transfer Hook Extension", async () => {});
Aşağıdaki güncel testle:
it("Create Mint Account with Transfer Hook Extension", async () => {const extensions = [ExtensionType.TransferHook];const mintLen = getMintLen(extensions);const lamports =await provider.connection.getMinimumBalanceForRentExemption(mintLen);const transaction = new Transaction().add(SystemProgram.createAccount({fromPubkey: wallet.publicKey,newAccountPubkey: mint.publicKey,space: mintLen,lamports: lamports,programId: TOKEN_2022_PROGRAM_ID}),createInitializeTransferHookInstruction(mint.publicKey,wallet.publicKey,program.programId, // Transfer Hook Program IDTOKEN_2022_PROGRAM_ID),createInitializeMintInstruction(mint.publicKey,decimals,wallet.publicKey,null,TOKEN_2022_PROGRAM_ID));const txSig = await sendAndConfirmTransaction(provider.connection,transaction,[wallet.payer, mint]);console.log(`Transaction Signature: ${txSig}`);});
Token Account'ları Oluşturma
Ardından kurulumun bir parçası olarak hem gönderen hem de alıcı için Associated Token Account'ları oluşturun. Ayrıca göndericinin hesabına bir miktar token yükleyin.
Yer tutucu testi değiştirin:
it("Create Token Accounts and Mint Tokens", async () => {});
Aşağıdaki güncel testle:
// Create the two token accounts for the transfer-hook enabled mint// Fund the sender token account with 100 tokensit("Create Token Accounts and Mint Tokens", async () => {// 100 tokensconst amount = 100 * 10 ** decimals;const transaction = new Transaction().add(createAssociatedTokenAccountInstruction(wallet.publicKey,sourceTokenAccount,wallet.publicKey,mint.publicKey,TOKEN_2022_PROGRAM_ID,ASSOCIATED_TOKEN_PROGRAM_ID),createAssociatedTokenAccountInstruction(wallet.publicKey,destinationTokenAccount,recipient.publicKey,mint.publicKey,TOKEN_2022_PROGRAM_ID,ASSOCIATED_TOKEN_PROGRAM_ID),createMintToInstruction(mint.publicKey,sourceTokenAccount,wallet.publicKey,amount,[],TOKEN_2022_PROGRAM_ID));const txSig = await sendAndConfirmTransaction(connection,transaction,[wallet.payer],{ skipPreflight: true });console.log(`Transaction Signature: ${txSig}`);});
ExtraAccountMeta Hesabı Oluşturma
Bir token transferi göndermeden önce, transfer hook talimatının ihtiyaç duyduğu tüm ek hesapları saklamak için ExtraAccountMetas hesabını oluşturmamız gerekir.
Bu hesabı oluşturmak için programımızdaki talimatı çağırıyoruz.
Yer tutucu testi değiştirin:
it("Create ExtraAccountMetaList Account", async () => {});
Aşağıdaki güncel testle:
// Account to store extra accounts required by the transfer hook instructionit("Create ExtraAccountMetaList Account", async () => {const initializeExtraAccountMetaListInstruction = await program.methods.initializeExtraAccountMetaList().accounts({payer: wallet.publicKey,extraAccountMetaList: extraAccountMetaListPDA,mint: mint.publicKey,wsolMint: NATIVE_MINT,tokenProgram: TOKEN_PROGRAM_ID,associatedTokenProgram: ASSOCIATED_TOKEN_PROGRAM_ID}).instruction();const transaction = new Transaction().add(initializeExtraAccountMetaListInstruction);const txSig = await sendAndConfirmTransaction(provider.connection,transaction,[wallet.payer],{ skipPreflight: true });console.log("Transaction Signature:", txSig);});
Token Transferi
Son olarak token transferi göndermeye hazırız. Transfer talimatına ek olarak dahil edilmesi gereken birkaç talimat daha var.
- Gönderen, transfer hook talimatının gerektirdiği ücreti karşılamak için wSOL token account'una SOL aktarmalıdır.
- Gönderen, delegate PDA'yı wSOL ücreti tutarı kadar onaylamalıdır.
- wSOL bakiyesini senkronize etmek için bir talimat ekleyin.
- Token transfer talimatı, transfer hook talimatının gerektirdiği tüm ek hesapları içermelidir.
Yer tutucu testi değiştirin:
it("Transfer Hook with Extra Account Meta", async () => {});
Aşağıdaki güncel testle:
it("Transfer Hook with Extra Account Meta", async () => {// 1 tokensconst amount = 1 * 10 ** decimals;const amountBigInt = BigInt(amount);// Instruction for sender to fund their WSol token accountconst solTransferInstruction = SystemProgram.transfer({fromPubkey: wallet.publicKey,toPubkey: senderWSolTokenAccount,lamports: amount});// Approve delegate PDA to transfer WSol tokens from sender WSol token accountconst approveInstruction = createApproveInstruction(senderWSolTokenAccount,delegatePDA,wallet.publicKey,amount,[],TOKEN_PROGRAM_ID);// Sync sender WSol token accountconst syncWrappedSolInstruction = createSyncNativeInstruction(senderWSolTokenAccount);// This helper function will automatically derive all the additional accounts that were defined in the ExtraAccountMetas accountlet transferInstructionWithHelper =await createTransferCheckedWithTransferHookInstruction(connection,sourceTokenAccount,mint.publicKey,destinationTokenAccount,wallet.publicKey,amountBigInt,decimals,[],"confirmed",TOKEN_2022_PROGRAM_ID);const transaction = new Transaction().add(solTransferInstruction,syncWrappedSolInstruction,approveInstruction,transferInstructionWithHelper);const txSig = await sendAndConfirmTransaction(connection,transaction,[wallet.payer],{ skipPreflight: true });console.log("Transfer Signature:", txSig);});
Transfer talimatı, tüm ek AccountMeta'ları, ExtraAccountMetas hesabının adresini ve Transfer Hook programının adresini içermelidir.
Test Dosyasını Çalıştırma
Tüm testleri güncelledikten sonra son adım testi çalıştırmaktır.
Test dosyasını çalıştırmak için terminalde şu komutu kullanın:
test
Aşağıdakine benzer bir çıktı görmelisiniz:
Running tests...transfer-hook.test.ts:transfer-hookTransaction Signature: 5o12ZTvcSkV8YNqyeQpzRCq4zFSg9VqguQkT9ZSesioj8uzb8dWRheoknuPaRDDqEGdrUBqmRQ2veSUshUicWsqG✔ Create Mint Account with Transfer Hook Extension (996ms)Transaction Signature: 4F4Vhi8s1h2reDr6jecvuQFF5XpoofWPpshgAMnfg7jtNZj4HtxbsksFTh28ZjYTaKFpjeturYZKxk5Cj4gBZoy✔ Create Token Accounts and Mint Tokens (716ms)Transaction Signature: 3s4Nok6H4qexpGXup3AWC4nGuiqy567rm5rTWLFMXYKxZJensBVVZHCwVDzpwD3XtWjMFHm4TrvQXwKSsp47y5jx✔ Create ExtraAccountMetaList Account (711ms)Transfer Signature: 53j9QV5LYUVgV7T7Z99GfYg1Xvp2qbQnHsJbzDK6BR5TPBo9s622KCf3W3BDEL4ECprkZFs5biDRDedfVj6zuDA6✔ Transfer Hook with Extra Account Meta (925ms)4 passing (5s)
Transfer hook'ta token account verilerini kullanma
Bazen ek hesap meta'larındaki ek hesapları türetmek için hesap verilerini kullanmak isteyebilirsiniz. Bu, örneğin token account'un sahibini bir PDA için seed olarak kullanmak istediğinizde faydalıdır.
ExtraAccountMeta oluştururken herhangi bir hesabın verisini ek seed olarak kullanabilirsiniz. Bu durumda, token account sahibinden ve 'counter' dizesinden bir counter hesabı türetmek istiyoruz. Bu, söz konusu token account sahibinin ne sıklıkla token transfer ettiğini her zaman görebileceğimiz anlamına gelir.
extra_account_metas() fonksiyonunda bunu şöyle ayarlarsınız.
// Define extra account metas to store on extra_account_meta_list accountimpl<'info> InitializeExtraAccountMetaList<'info> {pub fn extra_account_metas() -> Result<Vec<ExtraAccountMeta>> {Ok(vec![ExtraAccountMeta::new_with_seeds(&[Seed::Literal {bytes: b"counter".to_vec(),},Seed::AccountData { account_index: 0, data_index: 32, length: 32 },],false, // is_signertrue // is_writable)?])}}
Hesap verilerinin nasıl saklandığını anlamak için token account struct'ına bakalım. Aşağıda bir token account yapısı örneği var. Buna göre, 32. ile 64. konum arasındaki 32 baytı token account'un sahibi olarak alabiliriz; bu, 'account_index: 0' olan hesaptır. 'account_index', hesabın accounts dizisindeki indeksini ifade eder. Transfer hook durumunda sahip token account'u, accounts dizisindeki ilk girdidir. İkinci hesap her zaman mint, üçüncü hesap ise hedef token account'tur. Bu hesap sırası eski token programındakiyle aynıdır.
/// Account data.#[repr(C)]#[derive(Clone, Copy, Debug, Default, PartialEq)]pub struct Account {/// The mint associated with this accountpub mint: Pubkey,/// The owner of this account.pub owner: Pubkey,/// The amount of tokens this account holds.pub amount: u64,pub delegate: COption<Pubkey>,pub state: AccountState,pub is_native: COption<u64>,pub delegated_amount: u64,pub close_authority: COption<Pubkey>,}
Bizim durumumuzda, gönderen token account'un sahibinden bir counter hesabı türetmek istiyoruz; bu yüzden ExtraAccountMeta hesaplarını oluştururken, gönderen token account sahibinden ve 'counter' dizesinden türetilen bu PDA counter hesabını init ediyoruz. PDA counter hesabı başlatıldığında, her transferde değeri artırmak için bunu transfer hook içinde kullanabileceğiz.
struct.```rust#[derive(Accounts)]pub struct InitializeExtraAccountMetaList<'info> {#[account(mut)]payer: Signer<'info>,/// CHECK: ExtraAccountMetaList Account, must use these seeds#[account(init,seeds = [b"extra-account-metas", mint.key().as_ref()],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, seeds = [b"counter", payer.key().as_ref()], bump, payer = payer, space = 16)]pub counter_account: Account<'info, CounterAccount>,pub token_program: Program<'info, Token2022>,pub associated_token_program: Program<'info, AssociatedToken>,pub system_program: Program<'info, System>,}
Bu ek counter hesabını TransferHook struct'ında da tanımlamamız gerekiyor. Bunlar, her transfer yapıldığında TransferHook programımıza iletilen hesaplardır. İstemci bu ek hesapları ExtraAccountsMetaList PDA'sından alır ve token transfer talimatına dahil eder, ancak program içinde yine de bunu tanımlamamız gerekir.
#[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 programpub 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", owner.key().as_ref()], bump)]pub counter_account: Account<'info, CounterAccount>,}
İstemcide bu hesap otomatik olarak oluşturulur ve şu şekilde kullanabilirsiniz.
const transferInstructionWithHelper =await createTransferCheckedWithTransferHookInstruction(connection,sourceTokenAccount,mint.publicKey,destinationTokenAccount,wallet.publicKey,amountBigInt,decimals,[],"confirmed",TOKEN_2022_PROGRAM_ID);
Yardımcı fonksiyon, hesabı ExtraAccounts veri hesabından otomatik olarak çözümler. Hesabın istemcide nasıl çözümleneceği şöyledir:
const [counterPDA] = await PublicKey.findProgramAddress([Buffer.from("counter"), wallet.publicKey.toBytes()],program.programId);
Counter hesabının token account'un sahibinden türetildiğini ve transfer yapmadan önce başlatılması gerektiğini unutmayın. Bu örnekte counter hesabını, ek hesap meta'larını başlattığımız sırada başlatıyoruz. Dolayısıyla yalnızca o fonksiyonu çağıran token account sahibi için bir counter PDA'sı olacaktır. Mint'iniz için var olan her token account'a ait bir counter hesabı istiyorsanız, bu PDA'ları önceden oluşturacak bir işlevselliğe ihtiyacınız olacaktır. Örneğin dapp'inizde, bu PDA hesabını oluşturan bir counter'a kaydolma düğmesi bulunabilir ve bundan sonra kullanıcılar bu counter token'ını kullanabilir.
Sonuç
Transfer Hook uzantısı ve Transfer Hook Arayüzü, her token transferinde özel talimat mantığını çalıştıran Mint Account'ların oluşturulmasına olanak tanır. Bu kılavuz, kendi Transfer Hook programlarınızı oluşturmanıza yardımcı olacak bir başvuru kaynağı niteliğindedir. Yaratıcı olmaktan ve bu yeni işlevselliğin sunduğu imkânları keşfetmekten çekinmeyin!
Is this page helpful?