Transfer Hook -laajennus ja Transfer Hook Interface mahdollistavat sellaisten Mint Account -tilien luomisen, jotka suorittavat mukautettua instruktiologiikkaa jokaisen token-siirron yhteydessä.
Tämä avaa monia uusia käyttötapauksia token-siirroille, kuten:
- NFT-rojaltien valvominen
- Token-vastaanottajien walletien mustalistaus tai sallittujen listaus
- Mukautettujen maksujen toteuttaminen token-siirtoihin
- Mukautettujen token-siirtotapahtumien luominen
- Token-siirtojen tilastojen seuranta
- Ja monia muita
Tämän toteuttamiseksi kehittäjien on rakennettava ohjelma, joka toteuttaa Transfer Hook Interfacen ja alustettava Mint Account, jossa Transfer Hook -laajennus on otettu käyttöön.
Jokaisen token-siirron yhteydessä, johon liittyy Mint Account -tilin tokeneita, Token Extensions program tekee Cross Program Invocation -kutsun (CPI) suorittaakseen instruktion Transfer Hook -ohjelmassa.
Kun Token Extensions program tekee CPI-kutsun Transfer Hook -ohjelmaan, kaikki alkuperäisen siirron tilit muunnetaan vain luku -tileiksi. Tämä tarkoittaa, että lähettäjän allekirjoitusoikeudet eivät ulotu Transfer Hook -ohjelmaan.
Tämä suunnitteluratkaisu on tehty Transfer Hook -ohjelmien väärinkäytön estämiseksi.
Tässä oppaassa luomme Transfer Hook -ohjelman Anchor-kehyksellä, mutta Transfer Hook Interfacen voi toteuttaa myös natiivilla ohjelmalla. Lisätietoja Anchor-kehyksestä: Anchor Framework
Transfer Hook Interfacen yleiskatsaus
Transfer Hook Interface tarjoaa kehittäjille tavan toteuttaa mukautettua instruktiologiikkaa, joka suoritetaan jokaisen tietyn Mint Account -tilin token-siirron yhteydessä.
Transfer Hook Interface määrittelee seuraavat instruktiot:
Execute: Instruktio, jonka Token Extensions program kutsuu jokaisen token-siirron yhteydessä.InitializeExtraAccountMetaList(valinnainen): Luo tilin, joka tallentaa listan mukautetunExecute-instruktion vaatimista lisätileistä.UpdateExtraAccountMetaList(valinnainen): Päivittää lisätilien listan korvaamalla nykyisen listan.
Teknisesti InitializeExtraAccountMetaList-instruktion toteuttaminen
rajapinnan kautta ei ole pakollista. Tilin voi luoda mikä tahansa Transfer Hook
-ohjelman instruktio.
Tilin Program Derived Address (PDA) on kuitenkin johdettava käyttämällä seuraavia seed-arvoja:
- Kovakoodattu merkkijono "extra-account-metas"
- Mint Account -tilin osoite
- Transfer Hook -ohjelman ID
const [pda] = await PublicKey.findProgramAddress([Buffer.from("extra-account-metas"), mint.publicKey.toBytes()],program.programId // transfer hook program ID);
Tallentamalla Execute-instruktion vaatimat lisätilit ennalta määritettyyn
PDA-osoitteeseen nämä tilit voidaan lisätä automaattisesti token-siirron
instruktioon asiakasohjelmassa.
Hello World -Transfer Hook
Tämä esimerkki on transfer hookien Hello World. Se on yksinkertainen transfer hook, joka tulostaa viestin jokaisen token-siirron yhteydessä. Aloitamme avaamalla esimerkin Solana Playgroundissa, verkkotyökalussa Solana-ohjelmien rakentamiseen ja julkaisemiseen: linkki
Esimerkki koostuu Anchor-ohjelmasta, joka toteuttaa transfer hook -rajapinnan, sekä testitiedostosta ohjelman testaamiseen.
Tämä ohjelma sisältää vain 3 instruktiota:
initialize_extra_account_meta_list: Luo tilin, joka tallentaa listantransfer_hook-instruktion vaatimista lisätileistä. Hello World -esimerkissä jätämme sen tyhjäksi.transfer_hook: Tämä instruktio kutsutaan CPI:n kautta jokaisen token-siirron yhteydessä wrapped SOL -token-siirron suorittamiseksi.fallback: Koska käytämme Anchoria ja Token Program on natiivi ohjelma, meidän on lisättävä fallback-instruktio, joka täsmää instruktion discriminatorin käsin ja kutsuu mukautettuatransfer_hook-instruktiotamme. Tätä funktiota ei tarvitse muuttaa.
Aina kun tokenia siirretään, Token Program kutsuu tätä transfer_hook-funktiota.
pub fn transfer_hook(ctx: Context<TransferHook>, amount: u64) -> Result<()> {msg!("Hello Transfer Hook!");Ok(())}
Tähän funktioon voit lisätä oman lisälogiikkasi. Voit esimerkiksi antaa siirron epäonnistua aina, kun siirrettävä määrä on suurempi kuin 50, näin:
#[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(())}
Voit ajaa esimerkin Solana Playgroundissa tästä linkistä: linkki
Suorita Playgroundin terminaalissa build-komento, joka päivittää
declare_id-arvon lib.rs-tiedostossa uudella, generoidulla ohjelma-ID:llä.
Suorita sitten deploy-komento julkaistaksesi ohjelmasi devnetiin. Kun ohjelma
on julkaistu, voit ajaa testitiedoston käyttämällä terminaalissa test-komentoa.
Tämän jälkeen saat samankaltaisen tulosteen kuin tässä:
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)
Jos et halua käyttää javascriptiä tokenin luomiseen, voit myös käyttää Solana
CLI:n spl-token-komentoa ohjelmasi julkaisemisen jälkeen:
spl-token --program-id TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb create-token --transfer-hook yourTransferHookProgramId
Counter Transfer Hook
Seuraava esimerkki näyttää, miten voit kasvattaa laskuria aina, kun tokenia on siirretty. linkki
Jos haluat lisätä transfer hookiisi logiikkaa, joka tarvitsee lisätilejä, sinun on lisättävä ne ExtraAccountMetaList-tiliin. Tässä tapauksessa haluamme PDA:n, joka tallentaa, kuinka monta kertaa tokenia on siirretty.
Tämä onnistuu lisäämällä seuraava koodi
initialize_extra_account_meta_list-instruktioon:
let account_metas = vec![ExtraAccountMeta::new_with_seeds(&[Seed::Literal {bytes: "counter".as_bytes().to_vec(),}],false, // is_signertrue, // is_writable)?,];
Meidän on myös luotava tämä tili, kun alustamme uuden mint accountin, ja välitettävä se jokaisen token-siirron yhteydessä.
#[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>,}
Ja tili sisältää u64-laskurimuuttujan:
#[account]pub struct CounterAccount {counter: u64,}
Nyt transfer hook -funktiossamme voimme yksinkertaisesti kasvattaa tätä laskuria yhdellä aina, kun funktiota kutsutaan:
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(())}
Asiakasohjelmassa nämä lisätilit lisätään automaattisesti apufunktiolla:
getTransferCheckedWithTransferHookInstructionAsync kirjastosta
@solana-program/token-2022 web3.js v3:ssa tai
createTransferCheckedWithTransferHookInstruction kirjastosta
@solana/spl-token vanhassa web3.js:ssä. V3-apufunktio ottaa parametrina Kit
RPC:n, joten rakenna sellainen samalle endpointille kuin Connection käyttää,
funktiolla createSolanaRpc kirjastosta @solana/kit.
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});
Voit ajaa esimerkin Solana Playgroundissa tästä linkistä: linkki
Kirjoita siellä build, joka päivittää declare_id-arvon lib.rs-tiedostossa
uudella, generoidulla ohjelma-ID:llä. Kirjoita sitten deploy julkaistaksesi
ohjelmasi devnetiin. Kun ohjelma on julkaistu, voit ajaa testitiedoston
kirjoittamalla terminaaliin test.
Tämän jälkeen saat seuraavan tulosteen. Viimeisestä transaktiosta näet, kuinka monta kertaa tokenia on siirretty:
"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)
Koska kasvatamme tässä laskuria aina, kun tokenia siirretään, meidän on varmistettava, että transfer hook -instruktiota voi kutsua vain siirron aikana. Muuten kuka tahansa voisi kutsua transfer hook -instruktiota suoraan ja sotkea laskurimme. Tämä on tarkistus, joka kannattaa lisätä kaikkiin transfer hookeihisi.
Voit lisätä tarkistuksen näin:
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(())}
Ja kutsu sitä transfer_hook-funktiosi alussa:
#[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(())}
Transfer Hook wSOl-siirtomaksulla (edistynyt esimerkki)
Oppaan seuraavassa osassa rakennamme edistyneemmän Transfer Hook -ohjelman Anchor-kehyksellä. Tämä ohjelma edellyttää, että lähettäjä maksaa wSOL-maksun jokaisesta token-siirrosta.
wSOL-siirrot suoritetaan delegaatilla, joka on Transfer Hook -ohjelmasta johdettu PDA. Tämä on tarpeen, koska token-siirtoinstruktion alkuperäisen lähettäjän allekirjoitus ei ole käytettävissä Transfer Hook -ohjelmassa.
Tämä ohjelma sisältää vain 3 instruktiota:
initialize_extra_account_meta_list: Luo tilin, joka tallentaa listantransfer_hook-instruktion vaatimista lisätileistä.transfer_hook: Tämä instruktio kutsutaan CPI:n kautta jokaisen token-siirron yhteydessä wrapped SOL -token-siirron suorittamiseksi.fallback: Transfer hook -rajapinnan instruktioilla on tietyt discriminatorit (instruktioiden tunnisteet). Anchor-ohjelmassa voimme käyttää fallback-instruktiota täsmäämään instruktion discriminatorin käsin ja kutsumaan mukautettuatransfer_hook-instruktiotamme.
Tämä ohjelma edellyttää, että lähettäjä maksaa maksun wrapped SOL:ssa (wSOL) jokaisesta token-siirrosta. Tässä on valmis ohjelma.
Aloitus
Aloita avaamalla tämä Solana Playground -linkki ja kopioi projekti napsauttamalla "Import"-painiketta.
Aloituskoodi sisältää lib.rs- ja transfer-hook.test.ts-tiedostot, jotka on
runkoistettu luomaamme ohjelmaa varten. lib.rs-tiedostossa pitäisi olla
seuraava koodi:
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 {}
Kun olet tuonut projektin, rakenna ohjelma käyttämällä build-komentoa
Playgroundin terminaalissa.
build
Tämä päivittää declare_id-arvon lib.rs-tiedostossa uudella, generoidulla
ohjelma-ID:llä.
ExtraAccountMetas-tilin alustusinstruktio
Tässä vaiheessa toteutamme Transfer Hook -ohjelmamme
initialize_extra_account_meta_list-instruktion. Tämä instruktio luo
ExtraAccountMetas-tilin, joka tallentaa transfer_hook-instruktiomme
vaatimat lisätilit.
Tässä esimerkissä initialize_extra_account_meta_list-instruktio vaatii 7
tiliä:
payer: Tili, jolla maksetaan ExtraAccountMetas-tilin luominen.extra_account_meta_list: ExtraAccountMetas-tili, joka luodaan tallentamaantransfer_hook-instruktiomme vaatimien tilien lista.mint: Mint Account, joka osoittaa tähän Transfer Hook -ohjelmaan. Mint-osoite on pakollinen seed-arvoextra_account_meta_list-PDA:n johtamisessa.wsol_mint: Wrapped SOL -mint.token_program: Alkuperäisen Token Programin IDassociated_token_program: Associated Token Programin ID.system_program: System Program, joka on pakollinen tili uusien tilien luomisessa.
Osoitteita mint, wsol_mint ja associated_token_program käytetään
johtamaan wSOL associated token account -tilien osoitteet. Nämä tilit ovat
transfer_hook-instruktion vaatimia, ja ne tallennetaan
ExtraAccountMetas-tilille.
Päivitä InitializeExtraAccountMetaList-struct korvaamalla seuraava
aloituskoodi:
#[derive(Accounts)]pub struct InitializeExtraAccountMetaList {}
Alla annetulla koodilla:
#[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>,}
Päivitä seuraavaksi initialize_extra_account_meta_list-instruktio korvaamalla
seuraava aloituskoodi:
pub fn initialize_extra_account_meta_list(ctx: Context<InitializeExtraAccountMetaList>,) -> Result<()> {Ok(())}
Alla olevalla koodilla:
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(())}
Käydään läpi päivitetty instruktion logiikka. Aloitamme luettelemalla lisätilit, jotka täytyy tallentaa ExtraAccountMetas-tilille.
// 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)?,];
Näiden tilien tallentamiseen on kolme tapaa:
- Tallenna tilin osoite suoraan:
- Wrapped SOL mint -osoite
- 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,)?,
- Tallenna seed-arvot, joilla johdetaan PDA Transfer Hook -ohjelmalle:
- Delegate-PDA
// index 8, delegate PDAExtraAccountMeta::new_with_seeds(&[Seed::Literal {bytes: "delegate".as_bytes().to_vec(),}],false, // is_signerfalse, // is_writable)?,
- Tallenna seed-arvot, joilla johdetaan PDA muulle ohjelmalle kuin Transfer Hook
-ohjelmalle:
- Delegaatin wSOL Associated Token Account
- Lähettäjän wSOL Associated Token Account
// 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)?,
Seuraavaksi laskemme ExtraAccountMetas-luettelon tallentamiseen tarvittavan koon ja rent-maksun.
// 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);
Seuraavaksi teemme CPI-kutsun System Program -ohjelmaan luodaksemme tilin ja asettaaksemme Transfer Hook -ohjelman omistajaksi. PDA:n seed-arvot sisällytetään CPI-kutsuun allekirjoittajan seed-arvoina, koska käytämme PDA:ta uuden tilin osoitteena.
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,)?;
Kun tili on luotu, alustamme tilin datan tallentamaan ExtraAccountMetas-luettelon.
// initialize ExtraAccountMetaList account with extra accountsExtraAccountMetaList::init::<ExecuteInstruction>(&mut ctx.accounts.extra_account_meta_list.try_borrow_mut_data()?,&account_metas,)?;
Tässä esimerkissä emme käytä Transfer Hook -rajapintaa ExtraAccountMetas-tilin luomiseen.
Mukautettu Transfer Hook -instruktio
Toteutetaan seuraavaksi mukautettu transfer_hook-instruktio. Token Extension
-ohjelma kutsuu tätä instruktiota jokaisen token-siirron yhteydessä.
Tässä esimerkissä jokaisesta token-siirrosta vaaditaan wSOL-muodossa maksettava maksu. Yksinkertaisuuden vuoksi maksun määrä on sama kuin siirrettävien tokenien määrä.
Päivitä TransferHook-struct korvaamalla seuraava aloituskoodi:
#[derive(Accounts)]pub struct TransferHook {}
Alla olevalla päivitetyllä koodilla:
Huomaa, että tämän structin tilien järjestys on tärkeä. Token Extensions program antaa nämä tilit tässä järjestyksessä, kun se tekee CPI-kutsun tähän Transfer Hook -ohjelmaan.
// 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>,}
Neljä ensimmäistä tiliä ovat alkuperäisen token-siirron vaatimat tilit.
#[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>,
Viides tili on ExtraAccountMeta-tilin osoite, johon on tallennettu
transfer_hook-instruktiomme vaatimien lisätilien luettelo.
/// CHECK: ExtraAccountMetaList Account#[account(seeds = [b"extra-account-metas", mint.key().as_ref()],bump)]pub extra_account_meta_list: UncheckedAccount<'info>,
Loput tilit ovat ExtraAccountMetas-tilillä lueteltuja tilejä siinä järjestyksessä,
jossa ne määriteltiin initialize_extra_account_meta_list-instruktiossa.
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>,
Päivitä seuraavaksi transfer_hook-instruktio korvaamalla seuraava
aloituskoodi:
pub fn transfer_hook(ctx: Context<TransferHook>, amount: u64) -> Result<()> {Ok(())}
Alla olevalla päivitetyllä koodilla:
// 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(())}
Instruktion logiikassa teemme CPI-kutsun, jolla siirretään wSOL lähettäjän wSOL token account -tililtä. Siirto allekirjoitetaan delegate-PDA:lla. Jokaisessa token-siirrossa lähettäjän on ensin hyväksyttävä delegate siirtomäärälle.
Fallback-instruktio
Lopuksi meidän täytyy lisätä Anchor-ohjelmaan fallback-instruktio, joka käsittelee Token Extensions -ohjelman tekemän CPI-kutsun.
Tämä vaihe on tarpeen, koska Anchor generoi instruktioiden discriminatorit eri
tavalla kuin Transfer Hook -rajapinnan instruktioissa käytetään.
transfer_hook-instruktion discriminator ei vastaa Transfer Hook -rajapinnan
discriminatoria.
Päivitä fallback-instruktio korvaamalla seuraava aloituskoodi:
pub fn fallback<'info>(program_id: &Pubkey,accounts: &'info [AccountInfo<'info>],data: &[u8],) -> Result<()> {Ok(())}
Alla olevalla päivitetyllä koodilla:
// 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-instruktio tarkistaa, vastaako saapuvan instruktion discriminator
Transfer Hook -rajapinnan Execute-instruktiota. Jos vastaavuus löytyy, se
kutsuu Anchor-ohjelmamme transfer_hook-instruktiota.
Anchor-kehyksessä on tällä hetkellä julkaisematon ominaisuus, joka yksinkertaistaa tätä prosessia. Se poistaisi tarpeen fallback-instruktiolle.
Rakenna ja julkaise ohjelma
Transfer Hook -ohjelma on nyt valmis. Varmista, että Playground-lompakossasi on riittävästi Devnet SOL -varoja ohjelman julkaisua varten.
Rakenna ohjelma seuraavalla komennolla:
build
Julkaise seuraavaksi ohjelma komennolla:
deploy
Testitiedoston yleiskatsaus
Testataan seuraavaksi ohjelma. Avaa transfer-hook.test.ts-tiedosto, jossa
pitäisi näkyä seuraava aloituskoodi:
Alla oleva testitiedosto käyttää legacy web3.js -kirjastoa (@solana/web3.js v1)
ja @solana/spl-token-pakettia, koska TypeScript-asiakasohjelma, jonka Anchor
tarjoaa (AnchorProvider, program.methods ja sen tarjoama Wallet),
perustuu legacy web3.js -tyyppeihin. Esimerkin client-puolen lähetyspolusta,
joka on kirjoitettu web3.js v3 -kirjastolla ja
@solana-program/token-2022-paketilla, löydät oppaasta Transfer Hook
-integrointiopas.
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,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 () => {});});
Ensin generoimme keypair-avainparin, jota käytetään uuden Mint Account -tilin osoitteena. Mint-osoitteen avulla johdamme Associated Token Account (ATA) -osoitteet, joita käytämme token-siirrossa.
// 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);
Seuraavaksi johdamme ExtraAccountMetas-tilin PDA:n. Tämä tili luodaan tallentamaan mukautetun transfer hook -instruktion vaatimat lisätilit.
// 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);
Johdamme myös PDA:n, jota käytetään delegatena. Lähettäjän on hyväksyttävä tämä osoite wSOL token account -tilinsä delegatena. Tätä delegate-PDA:ta käytetään "allekirjoittamaan" wSOL-siirto mukautetussa transfer hook -instruktiossa.
// PDA delegate to transfer wSOL tokens from senderconst [delegatePDA] = PublicKey.findProgramAddressSync([Buffer.from("delegate")],program.programId);
Lisäksi johdamme wSOL token account -tilien osoitteet. Ensimmäinen osoite on lähettäjän wSOL token account -tilille, joka täytyy rahoittaa, jotta transfer hook -instruktion vaatima siirtomaksu voidaan maksaa. Toinen osoite on delegate-PDA:n omistamalle wSOL token account -tilille. Tässä esimerkissä kaikki wSOL-maksut lähetetään tälle tilille.
// 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);
Lopuksi osana valmistelua luomme wSOL token account -tilit.
// 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);});
Luo Mint Account
Aloita rakentamalla transaktio, joka luo uuden Mint Account -tilin Transfer Hook -laajennus käytössä. Varmista tässä transaktiossa, että määrität ohjelmamme laajennukseen tallennettavaksi Transfer Hook -ohjelmaksi.
Transfer Hook -laajennuksen käyttöönotto mahdollistaa sen, että Transfer Extension -ohjelma voi määrittää, mitä ohjelmaa kutsutaan jokaisen token-siirron yhteydessä.
Korvaa paikkamerkkitesti:
it("Create Mint Account with Transfer Hook Extension", async () => {});
Alla olevalla päivitetyllä testillä:
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 -tilien luominen
Luo seuraavaksi osana valmistelua Associated Token Account -tilit sekä lähettäjälle että vastaanottajalle. Rahoita lisäksi lähettäjän tili joillakin tokeneilla.
Korvaa paikkamerkkitesti:
it("Create Token Accounts and Mint Tokens", async () => {});
Alla olevalla päivitetyllä testillä:
// 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}`);});
Luo ExtraAccountMeta-tili
Ennen token-siirron lähettämistä meidän täytyy luoda ExtraAccountMetas-tili, johon tallennetaan kaikki transfer hook -instruktion vaatimat lisätilit.
Tämän tilin luomiseksi kutsumme ohjelmamme instruktiota.
Korvaa paikkamerkkitesti:
it("Create ExtraAccountMetaList Account", async () => {});
Alla olevalla päivitetyllä testillä:
// 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);});
Siirrä tokeneita
Nyt olemme valmiita lähettämään token-siirron. Siirtoinstruktion lisäksi mukaan täytyy sisällyttää muutama muu instruktio.
- Lähettäjän on siirrettävä SOL-varoja wSOL token account -tililleen kattamaan transfer hook -instruktion vaatima maksu.
- Lähettäjän on hyväksyttävä delegate-PDA wSOL-maksun määrälle.
- Sisällytä instruktio, joka synkronoi wSOL-saldon.
- Token-siirtoinstruktioon on sisällytettävä kaikki transfer hook -instruktion vaatimat lisätilit.
Korvaa paikkamerkkitesti:
it("Transfer Hook with Extra Account Meta", async () => {});
Alla olevalla päivitetyllä testillä:
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);});
Siirtoinstruktioon on sisällytettävä kaikki ylimääräiset AccountMetat, ExtraAccountMetas-tilin osoite ja Transfer Hook -ohjelman osoite.
Suorita testitiedosto
Kun olet päivittänyt kaikki testit, viimeinen vaihe on testin suorittaminen.
Suorita testitiedosto antamalla päätteessä seuraava komento:
test
Sinun pitäisi nähdä seuraavanlainen tuloste:
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)
Token account -datan käyttäminen transfer hookissa
Joskus tilin dataa halutaan käyttää lisätilien johtamiseen extra account metas -tiedoissa. Tämä on hyödyllistä esimerkiksi silloin, kun token account -tilin omistajaa halutaan käyttää PDA:n seed-arvona.
ExtraAccountMetaa luodessasi voit käyttää minkä tahansa tilin dataa ylimääräisenä seed-arvona. Tässä tapauksessa haluamme johtaa counter-tilin token account -tilin omistajasta ja merkkijonosta 'counter'. Näin voimme aina nähdä, kuinka usein kyseisen token account -tilin omistaja on siirtänyt tokeneita.
Näin se määritetään extra_account_metas()-funktiossa.
// 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)?])}}
Tarkastellaan token account -structia ymmärtääksemme, miten tilin data tallennetaan. Alla on esimerkki token account -rakenteesta. Voimme siis ottaa 32 tavua kohdasta 32–64 token account -tilin omistajaksi, joka on kohdassa 'account_index: 0'. 'account_index' viittaa tilin indeksiin accounts-taulukossa. Transfer hookin tapauksessa omistajan token account on accounts-taulukon ensimmäinen alkio. Toinen tili on aina mint ja kolmas tili on kohde-token account. Tämä tilien järjestys on sama kuin vanhassa Token Program -ohjelmassa.
/// 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>,}
Meidän tapauksessamme haluamme johtaa counter-tilin lähettäjän token account
-tilin omistajasta, joten kun luomme ExtraAccountMeta-tilit, alustamme (init)
tämän PDA-counter-tilin, joka johdetaan lähettäjän token account -tilin
omistajasta ja merkkijonosta 'counter'. Kun PDA-counter-tili on alustettu,
voimme käyttää sitä transfer hookissa arvon kasvattamiseen jokaisessa siirrossa.
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>,}
Meidän on myös määriteltävä tämä ylimääräinen counter-tili TransferHook-structissa. Nämä ovat tilit, jotka välitetään TransferHook-ohjelmallemme aina, kun siirto tehdään. Asiakas hakee nämä lisätilit ExtraAccountsMetaList-PDA:sta ja sisällyttää ne token-siirtoinstruktioon, mutta ohjelmassa se täytyy silti määrittää.
#[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>,}
Asiakkaassa tämä tili generoidaan automaattisesti, ja voit käyttää sitä seuraavasti.
const transferInstructionWithHelper =await createTransferCheckedWithTransferHookInstruction(connection,sourceTokenAccount,mint.publicKey,destinationTokenAccount,wallet.publicKey,amountBigInt,decimals,[],"confirmed",TOKEN_2022_PROGRAM_ID);
Apufunktio ratkaisee tilin automaattisesti ExtraAccounts-datatililtä. Tili ratkaistaisiin asiakkaassa seuraavasti:
const [counterPDA] = await PublicKey.findProgramAddress([Buffer.from("counter"), wallet.publicKey.toBytes()],program.programId);
Huomaa, että counter-tili johdetaan token account -tilin omistajasta, ja se täytyy alustaa ennen siirron tekemistä. Tässä esimerkissä alustamme counter-tilin samalla, kun alustamme extra account metas -tilin. Näin counter-PDA on vain sen token account -tilin omistajalle, joka kutsui kyseistä funktiota. Jos haluat counter-tilin jokaiselle tokenillesi (mint) kuuluvalle token account -tilille, tarvitset toiminnallisuuden, jolla nämä PDA:t luodaan etukäteen. Dappisi voisi sisältää painikkeen, jolla rekisteröidytään counteriin ja joka luo tämän PDA-tilin, ja siitä lähtien käyttäjät voivat käyttää tätä counter-tokenia.
Yhteenveto
Transfer Hook -laajennus ja Transfer Hook Interface -rajapinta mahdollistavat sellaisten Mint Account -tilien luomisen, jotka suorittavat mukautettua ohjelmalogiikkaa jokaisen tokensiirron yhteydessä. Tämä opas toimii apuna omien Transfer Hook -ohjelmiesi luomisessa. Käytä rohkeasti luovuuttasi ja tutustu tämän uuden toiminnallisuuden mahdollisuuksiin!
Is this page helpful?