Transfer Hook -laajennus ja Transfer Hook Interface mahdollistavat sellaisten Mint Account -tilien luomisen, jotka suorittavat mukautettua käskylogiikkaa jokaisen token-siirron yhteydessä.
Tämä avaa monia uusia käyttötapauksia token-siirroille, kuten:
- NFT-rojaltien pakottaminen
- Tokeneita vastaanottavien lompakoiden estolistat tai sallittujen lista
- Mukautettujen maksujen toteuttaminen token-siirroille
- Mukautettujen token-siirtotapahtumien luominen
- Token-siirtojen tilastojen seuraaminen
- Ja monia muita
Tämän toteuttamiseksi kehittäjien täytyy rakentaa ohjelma, joka toteuttaa Transfer Hook Interfacen ja alustaa Mint Account, jossa Transfer Hook -laajennus on käytössä.
Jokaisen sellaisen token-siirron yhteydessä, jossa siirretään Mint Accountin tokeneita, Token Extensions program tekee Cross Program Invocation (CPI) -kutsun suorittaakseen käskyn 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ä suunnittelupäätös on tehty Transfer Hook -ohjelmien väärinkäytön estämiseksi.
Tässä oppaassa luomme Transfer Hook -ohjelman Anchor-kehyksellä. Transfer Hook Interface on kuitenkin mahdollista toteuttaa myös natiiviohjelmalla. Lue lisää Anchor-kehyksestä täältä: Anchor Framework
Transfer Hook Interfacen yleiskatsaus
Transfer Hook Interface tarjoaa kehittäjille tavan toteuttaa mukautettua käskylogiikkaa, joka suoritetaan jokaisen tietyn Mint Accountin token-siirron yhteydessä.
Transfer Hook Interface määrittelee seuraavat käskyt:
Execute: Käsky, jonka Token Extensions program kutsuu jokaisen token-siirron yhteydessä.InitializeExtraAccountMetaList(valinnainen): Luo tilin, joka tallentaa luettelon mukautetunExecute-käskyn tarvitsemista lisätileistä.UpdateExtraAccountMetaList(valinnainen): Päivittää lisätilien luettelon korvaamalla olemassa olevan luettelon.
InitializeExtraAccountMetaList-käskyn toteuttaminen rajapinnan avulla ei ole
tekninen vaatimus. Tilin voi luoda mikä tahansa Transfer Hook -ohjelman käsky.
Tilin Program Derived Address (PDA) täytyy kuitenkin johtaa käyttäen seuraavia seed-arvoja:
- Kovakoodattu merkkijono "extra-account-metas"
- Mint Accountin 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-käskyn tarvitsemat lisätilit ennalta määritettyyn PDA:han
nämä tilit voidaan lisätä automaattisesti token-siirtokäskyyn 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 Interfacen, sekä testitiedostosta ohjelman testaamiseen.
Tämä ohjelma sisältää vain 3 käskyä:
initialize_extra_account_meta_list: Luo tilin, joka tallentaa luettelontransfer_hook-käskyn tarvitsemista lisätileistä. Hello world -esimerkissä jätämme sen tyhjäksi.transfer_hook: Tämä käsky kutsutaan CPI:n kautta jokaisen token-siirron yhteydessä wrapped SOL -token-siirron suorittamiseksi.fallback: Koska käytämme Anchoria ja Token Program on natiiviohjelma, meidän täytyy lisätä fallback-käsky, joka täsmää käskyn discriminatorin manuaalisesti ja kutsuu mukautettuatransfer_hook-käskyämme. 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 nyt lisätä oman lisälogiikkasi. Voisit 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ämän linkin kautta: linkki
Aja Playgroundin terminaalissa build-komento, joka päivittää declare_id-arvon
lib.rs-tiedostossa uudella, generoidulla ohjelma-ID:llä. Aja 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 tulosteen, joka on samankaltainen kuin tämä:
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ä tokenisi luomiseen, voit ohjelmasi
julkaisemisen jälkeen käyttää myös Solana CLI:n spl-token-komentoa:
spl-token --program-id TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb create-token --transfer-hook yourTransferHookProgramId
Counter Transfer Hook
Seuraava esimerkki näyttää, miten voit kasvattaa laskuria aina, kun tokenisi on siirretty. linkki
Jos haluat lisätä transfer hookiisi logiikkaa, joka tarvitsee lisätilejä, sinun täytyy lisätä ne ExtraAccountMetaList-tilille. Tässä tapauksessa haluamme PDA:n, joka tallentaa, kuinka monta kertaa tokenia on siirretty.
Tämä voidaan tehdä lisäämällä seuraava koodi
initialize_extra_account_meta_list-käskyyn:
let account_metas = vec![ExtraAccountMeta::new_with_seeds(&[Seed::Literal {bytes: "counter".as_bytes().to_vec(),}],false, // is_signertrue, // is_writable)?,];
Meidän täytyy myös luoda tämä tili, kun alustamme uuden Mint Accountin, ja välittää se mukaan joka kerta, kun tokenia siirretään.
#[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-tyyppisen laskurimuuttujan:
#[account]pub struct CounterAccount {counter: u64,}
Nyt transfer hook -funktiossamme voimme kasvattaa tätä laskuria yhdellä joka kerta, 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 paketista
@solana-program/token-2022 web3.js v3:lla tai
createTransferCheckedWithTransferHookInstruction paketista @solana/spl-token
vanhalla web3.js:llä. V3-apufunktio ottaa parametrikseen Kit RPC:n, joten luo
sellainen samalle päätepisteelle kuin Connection käyttämällä
createSolanaRpc-funktiota paketista @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ämän linkin kautta: 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 tokeniasi 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 täytyy varmistaa, että transfer hook -käskyä voi kutsua vain siirron aikana. Muuten joku voisi kutsua transfer hook -käskyä suoraan ja sotkea laskurimme. Tämä on tarkistus, joka sinun 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 vaatii lähettäjää maksamaan wSOL-maksun jokaisesta token-siirrosta.
wSOL-siirrot suoritetaan delegaatilla, joka on Transfer Hook -ohjelmasta johdettu PDA. Tämä on tarpeen, koska token-siirtokäskyn alkuperäisen lähettäjän allekirjoitus ei ole käytettävissä Transfer Hook -ohjelmassa.
Tämä ohjelma sisältää vain 3 käskyä:
initialize_extra_account_meta_list: Luo tilin, joka tallentaa luettelontransfer_hook-käskyn tarvitsemista lisätileistä.transfer_hook: Tämä käsky kutsutaan CPI:n kautta jokaisen token-siirron yhteydessä wrapped SOL -token-siirron suorittamiseksi.fallback: Transfer hook interfacen käskyillä on omat discriminatorinsa (käskytunnisteet). Anchor-ohjelmassa voimme käyttää fallback-käskyä täsmäämään käskyn discriminatorin manuaalisesti ja kutsumaan mukautettuatransfer_hook-käskyämme.
Tämä ohjelma vaatii lähettäjää maksamaan maksun wrapped SOL (wSOL) -tokenina jokaisesta token-siirrosta. Tässä on valmis ohjelma.
Aloittaminen
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
pohjustettu 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 Playgroundin terminaalissa
build-komennolla.
build
Tämä päivittää declare_id-arvon lib.rs-tiedostossa uudella, generoidulla
ohjelma-ID:llä.
ExtraAccountMetas-tilin alustuskäsky
Tässä vaiheessa toteutamme Transfer Hook -ohjelmamme
initialize_extra_account_meta_list-käskyn. Tämä käsky luo ExtraAccountMetas-tilin,
joka tallentaa transfer_hook-käskymme tarvitsemat lisätilit.
Tässä esimerkissä initialize_extra_account_meta_list-käsky vaatii 7 tiliä:
payer: Tili, jolla maksetaan ExtraAccountMetas-tilin luominen.extra_account_meta_list: ExtraAccountMetas-tili, joka luodaan tallentamaantransfer_hook-käskymme tarvitsemien tilien luettelo.mint: Mint Account, joka osoittaa tähän Transfer Hook -ohjelmaan. Mint-osoite on pakollinen seed-arvoextra_account_meta_list-PDA:n johtamiseen.wsol_mint: Wrapped SOL -mint.token_program: Alkuperäisen Token Program -ohjelman ID.associated_token_program: Associated Token Program -ohjelman ID.system_program: System Program, joka on pakollinen tili uusia tilejä luotaessa.
Osoitteita mint, wsol_mint ja associated_token_program käytetään
wSOL-tilien (associated token account) osoitteiden johtamiseen. Nämä tilit ovat
transfer_hook-käskyn vaatimia, ja ne tallennetaan ExtraAccountMetas-tilille.
Päivitä InitializeExtraAccountMetaList-struct korvaamalla seuraava
aloituskoodi:
#[derive(Accounts)]pub struct InitializeExtraAccountMetaList {}
Alla olevalla 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-käsky korvaamalla
seuraava aloituskoodi:
pub fn initialize_extra_account_meta_list(ctx: Context<InitializeExtraAccountMetaList>,) -> Result<()> {Ok(())}
Seuraavalla 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 instruction-logiikka. Aloitamme listaamalla 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 -mintin 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 seeds, 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 seeds, joilla johdetaan PDA muulle ohjelmalle kuin Transfer Hook
-ohjelmalle:
- Delegaten 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-listan tallentamiseen tarvittavan koon ja rent-määrän.
// 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 Programiin, jolla luodaan tili ja asetetaan Transfer Hook -ohjelma omistajaksi. PDA seeds sisällytetään CPI-kutsuun signer seeds -arvoina, koska PDA:ta käytetään 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-listan.
// 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 -instruction
Toteutetaan seuraavaksi mukautettu transfer_hook-instruction. Token Extensions
Program kutsuu tätä instructionia jokaisen token-siirron yhteydessä.
Tässä esimerkissä jokaisesta token-siirrosta vaaditaan wSOL-muodossa maksettava maksu. Yksinkertaisuuden vuoksi maksun suuruus on sama kuin siirrettävä tokenmäärä.
Päivitä TransferHook-struct korvaamalla seuraava aloituskoodi:
#[derive(Accounts)]pub struct TransferHook {}
Alla olevalla päivitetyllä koodilla:
Huomaa, että tilien järjestyksellä tässä structissa on merkitystä. Tässä järjestyksessä Token Extensions Program antaa nämä tilit, 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, joka tallentaa
transfer_hook-instructionimme vaatimien lisätilien listan.
/// 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ä listatut tilit siinä järjestyksessä,
jossa ne määriteltiin initialize_extra_account_meta_list-instructionissa.
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-instruction 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(())}
Instructionin logiikassa teemme CPI-kutsun, jolla siirretään wSOL:ia lähettäjän wSOL token account -tililtä. Siirto allekirjoitetaan delegate PDA:lla. Jokaisessa token-siirrossa lähettäjän täytyy ensin hyväksyä delegate siirrettävälle määrälle.
Fallback-instruction
Lopuksi Anchor-ohjelmaan täytyy lisätä fallback-instruction, joka käsittelee Token Extensions Programin tekemän CPI-kutsun.
Tämä vaihe on tarpeen, koska Anchor generoi instruction discriminatorit eri
tavalla kuin Transfer Hook -rajapinnan instructioneissa käytetään.
transfer_hook-instructionin discriminator ei vastaa Transfer Hook -rajapinnan
discriminatoria.
Päivitä fallback-instruction 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-instruction tarkistaa, vastaako saapuvan instructionin discriminator
Transfer Hook -rajapinnan Execute-instructionia. Jos vastaavuus löytyy, se
kutsuu Anchor-ohjelmamme transfer_hook-instructionia.
Anchorissa on tällä hetkellä julkaisematon ominaisuus, joka yksinkertaistaa tätä prosessia. Se poistaisi tarpeen fallback-instructionille.
Rakenna ja julkaise ohjelma
Transfer Hook -ohjelma on nyt valmis. Varmista, että Playground-lompakossasi on tarpeeksi Devnet-SOL:ia ohjelman julkaisemiseen.
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:ää (@solana/web3.js v1) ja
@solana/spl-token-pakettia, koska Anchorin TypeScript-client
(AnchorProvider, program.methods ja sen tarjoama Wallet) perustuu legacy
web3.js -tyyppeihin. Client-puolen lähetyspolusta, joka on kirjoitettu web3.js
v3:lla ja @solana-program/token-2022:lla, katso 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,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 () => {});});
Ensin luomme keypairin, jota käytetään uuden Mint Accountin 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 -instructionin 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 täytyy hyväksyä tämä osoite wSOL token accountinsa delegateksi. Tätä delegate PDA:ta käytetään "allekirjoittamaan" wSOL-siirto mukautetussa transfer hook -instructionissa.
// 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, jolle täytyy lisätä varoja, jotta transfer hook -instructionin vaatima siirtomaksu voidaan maksaa. Toinen osoite on delegate PDA:n omistama wSOL token account. 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);});
Mint Accountin luominen
Aloita rakentamalla transaktio, joka luo uuden Mint Accountin 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 sallii Token Extensions Programin selvittää, 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. Lisää lisäksi lähettäjän tilille tokeneita.
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}`);});
ExtraAccountMeta-tilin luominen
Ennen token-siirron lähettämistä meidän täytyy luoda ExtraAccountMetas-tili, johon tallennetaan kaikki transfer hook -instructionin vaatimat lisätilit.
Tämän tilin luomiseksi kutsumme ohjelmamme instructionia.
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);});
Tokenien siirto
Nyt olemme valmiita lähettämään token-siirron. Siirto-instructionin lisäksi mukaan täytyy sisällyttää muutama muu instruction.
- Lähettäjän täytyy siirtää SOL:ia wSOL token accountilleen kattaakseen transfer hook -instructionin vaatiman maksun.
- Lähettäjän täytyy hyväksyä delegate PDA wSOL-maksun määrälle.
- Sisällytä instruction wSOL-saldon synkronoimiseksi.
- Token-siirto-instructionin täytyy sisältää kaikki transfer hook -instructionin 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);});
Siirto-instructionin täytyy sisältää kaikki lisä-AccountMetat, ExtraAccountMetas-tilin osoite ja Transfer Hook -ohjelman osoite.
Testitiedoston ajaminen
Kun olet päivittänyt kaikki testit, viimeinen vaihe on testin ajaminen.
Aja testitiedosto antamalla terminaalissa seuraava komento:
test
Sinun pitäisi nähdä seuraavankaltainen 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 voi haluta käyttää lisätilien johtamiseen extra account metoissa. Tästä on hyötyä esimerkiksi silloin, kun haluat käyttää token account -tilin omistajaa PDA:n seedinä.
Kun luot ExtraAccountMetan, voit käyttää minkä tahansa tilin dataa ylimääräisenä seedinä. Tässä tapauksessa haluamme johtaa counter-tilin token account -tilin omistajasta ja merkkijonosta 'counter'. Näin näemme aina, kuinka usein kyseisen token accountin omistaja on siirtänyt tokeneita.
Näin tämä 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 -tilin rakenteesta. Voimme 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. Tilien järjestys on sama kuin vanhassa Token Programissa.
/// 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, teemme init-toiminnon
tälle PDA counter -tilille, 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 täytyy määritellä tämä ylimääräinen counter-tili myös TransferHook-structissa. Nämä ovat tilit, jotka välitetään TransferHook-ohjelmallemme aina siirron yhteydessä. Client hakee nämä lisätilit ExtraAccountsMetaList PDA:sta ja sisällyttää ne token-siirto-instructioniin, mutta ohjelmassa ne täytyy silti määritellä.
#[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>,}
Clientissa tämä tili generoituu automaattisesti, ja sitä voi käyttää 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 clientissa 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, kun alustamme extra account metat. Counter-PDA on siis vain sen token account -tilin omistajalle, joka kutsui kyseistä funktiota. Jos haluat counter-tilin jokaiselle mintillesi kuuluvalle token account -tilille, tarvitset toiminnon näiden PDA:iden luomiseen etukäteen. Dapp-sovelluksessasi voisi olla painike, 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 mahdollistavat sellaisten Mint Account -tilien luomisen, jotka suorittavat mukautettua ohjelmalogiikkaa jokaisen token-siirron yhteydessä. Tämä opas toimii apuna ja viitteenä, kun luot omia Transfer Hook -ohjelmiasi. Anna luovuutesi lentää ja tutustu rohkeasti tämän uuden toiminnallisuuden mahdollisuuksiin!
Is this page helpful?