做网站代理拉不到人,5个网站建设,开发工具和开发平台,烟台网站建设设计从零到英雄的 Solana 代币 2022 — Transfer Hook Token 2022 计划引入了几项令人兴奋的扩展#xff0c;增强了铸造和代币账户的功能。在这些功能中#xff0c;我个人最喜欢的是Transfer Hook #xff08;转账钩子#xff09; 。
想象时间
让我们戴上想象的帽子#xf…从零到英雄的 Solana 代币 2022 — Transfer Hook Token 2022 计划引入了几项令人兴奋的扩展增强了铸造和代币账户的功能。在这些功能中我个人最喜欢的是Transfer Hook 转账钩子 。
想象时间
让我们戴上想象的帽子想象一下这个美好的场景你是一个 NFT 项目的所有者。你向持有者发放代币以奖励他们将 NFT 抵押给你。你将你的项目视为一个“封闭社区”这意味着只有你的 NFT 持有者才能持有你的代币。在没有转账钩子之前你必须构建自己的程序来处理转账以强制执行这种行为。但有了转账钩子这就非常简单了你只需要构建一个转账钩子程序验证转账的目标地址是否确实是白名单地址之一如果不是则停止交易。另一个例子假设你希望用户在每次进行代币转账时支付额外费用……没问题这也可以通过构建一个处理转账的钩子来实现
现在在 NFT 项目的背景下这些例子可能看起来有些愚蠢但想象一下更大的图景这样的钩子可以帮助遵守监管要求例如强制执行持有期或设置代币的最大持有量。
Transfer Hook — 它到底是什么
顾名思义Transfer Hook 扩展与代币转账密切相关。每当一个铸造配置了 Transfer Hook 时每次在该铸造上执行转账指令时都会自动触发一个指令。如果这个概念看起来很熟悉那是因为它确实如此——这个流程与 web2 中的 webhooks 工作方式非常相似。
转账钩子指令可以访问一个变量即转账金额但我们可以提供一个额外的账户extra-account-meta-list其中包含指令可以使用的其他自定义账户。我们很快会详细讨论这个账户。
虽然转账钩子确实可以访问初始转账账户但需要注意的是它们作为只读账户传递这意味着发送者的签名权限不会扩展到Transfer Hook程序。
最后当使用 Anchor 编写Transfer Hook程序时需要一个回退指令来手动匹配原生程序指令鉴别器并调用Transfer Hook指令。这是因为 Token 2022 计划是一个原生程序因此我们需要“桥接”其原生接口与 Anchor。
是时候动手了 好了话不多说——让我们构建一些东西我们将构建一个“鲸鱼警报”转账钩子 对于每次代币转账转账钩子指令将比较转账金额与预定义的值例如10,000 代币。如果等于或大于该值我们将更新一个账户记录最新的鲸鱼详情并发出一个事件供客户端处理。 ⚒️ 你将需要安装 Solana CLI 工具和 Anchor。如果你还没有安装请查看 Anchor 文档 以获取安装说明。 第一步转账钩子程序
让我们设计我们的转账钩子程序。我们的转账钩子程序首先需要一个指令可以在转账完成后调用。我们将使用 Anchor 构建我们的程序然而Token 2022 计划是一个原生 Solana 程序——因此我们需要另一个指令一个指令来匹配指令鉴别器到转账钩子的 execute 接口并在匹配时调用 transfer_hook 指令。所以目前有两个指令。
当我们的钩子被调用时它会将转账金额与一个值进行比较——但这个值来自哪里当然我们可以在程序中硬编码这个值——但如果明天我们决定要增加/减少这个值呢我们需要重新部署程序。更好的处理方式是将比较值保存在一个账户中。要将额外的账户传递给转账钩子我们使用 extra_account_meta_list 账户。这是一个 PDA 账户存储转账钩子在调用时可以使用的账户。extra_account_meta_list 账户将始终使用硬编码字符串 extra-account-metas 和代币的铸造地址作为种子。
现在你可能会问自己我们如何创建和初始化这个 extra_account_meta_list 账户这就是我们程序的第三个也是最后一个指令——initialize_extra_account_meta_list 指令的作用。
在完成我们程序的高层设计后让我们开始实际编写转账钩子程序 。
创建一个新的 Anchor 项目并在你喜欢的 IDE 中打开它
anchor init transfer-hook-whale安装 anchor-spl, spl-transfer-hook-interface 和 spl_tlv_account_resolution crates。这些 crates 包含帮助函数和接口使我们在处理 SPL 代币和扩展指令时更加轻松。
cd programs/transfer-hook-whale
cargo add anchor-spl spl-transfer-hook-interface spl_tlv_account_resolution从 Anchor 0.30 开始我们需要告诉 Anchor 为我们使用的 crates 生成类型定义因此我们需要告诉它为 anchor-spl crate 生成类型定义。打开 Cargo.toml 文件位于 programs/transfer-hook-whale 文件夹内并按如下方式更新 idl-build
[features]
...
idl-build [anchor-lang/idl-build, anchor-spl/idl-build]打开 lib.rs 并添加以下 use 语句
use anchor_lang::system_program::{create_account, CreateAccount};
use anchor_spl::{associated_token::AssociatedToken,token_interface::{Mint, TokenInterface},
};
use spl_transfer_hook_interface::instruction::TransferHookInstruction;use spl_tlv_account_resolution::{account::ExtraAccountMeta, seeds::Seed, state::ExtraAccountMetaList,
};
use spl_transfer_hook_interface::instruction::ExecuteInstruction;一个转账钩子程序通常由三个指令组成
initialize_extra_account_meta — 一个创建账户extra_account_meta_list的指令该账户保存转账钩子可以使用的额外账户列表。 在我们的例子中这个账户将保存最新“鲸鱼”地址和金额的详细信息。transfer_hook — 钩子本身。这是每次发生代币转账时实际调用的指令。fallback — Token 2022 计划是一个原生程序但我们使用 Anchor 构建我们的程序因此我们需要添加一个回退指令该指令将指令鉴别器匹配到转账钩子的 execute 接口并在匹配时调用我们的 transfer_hook 指令。
实现 initialize_extra_account_meta 指令
在你的 lib.rs 底部添加以下账户。此账户将保存最新“鲸鱼”转账的详细信息
#[account]
pub struct WhaleAccount {pub whale_address: Pubkey,pub transfer_amount: u64
}接下来我们定义 initialize_extra_account_meta 指令执行所需的所有账户。在 LatestWhaleAccount 上方添加以下代码片段
#[derive(Accounts)]
pub struct InitializeExtraAccountMetainfo {#[account(mut)]pub payer: Signerinfo,/// CHECK: ExtraAccountMetaList Account, must use these exact seeds#[account(mut, seeds[bextra-account-metas, mint.key().as_ref()], bump)]pub extra_account_meta_list: AccountInfoinfo,pub mint: InterfaceAccountinfo, Mint,#[account(init, seeds[bwhale_account], bump, payerpayer, space8328)]pub latest_whale_account: Accountinfo, WhaleAccount,pub token_program: Interfaceinfo, TokenInterface,pub associated_token_program: Programinfo, AssociatedToken,pub system_program: Programinfo, System,
} 提示
将保存额外账户的账户 (extra_account_meta_list) 必须有一个非常特定的种子用于其 PDA字节串 extra-account-metas 和代币的 mint 账户的公钥。将保存鲸鱼详细信息的账户 (latest_whale_account) 将有一个简单的种子用于其 PDA字符串 whale_account 的字节。这意味着我们铸造的所有代币将共享同一个账户。我们必须为指令提供代币程序、关联代币程序和系统程序因为它在运行时需要使用它们。
接下来让我们添加 initialize_extra_account_meta 指令。用以下内容替换默认的 initialize 指令
pub fn initialize_extra_account(ctx: ContextInitializeExtraAccountMeta) - Result() {// 这是我们需要的额外账户的向量。在我们的例子中// 只有一个账户 - 鲸鱼详细信息账户。let account_metas vec![ExtraAccountMeta::new_with_seeds([Seed::Literal {bytes: whale_account.as_bytes().to_vec(),}],false,true,)?];// 计算账户大小和租金let account_size ExtraAccountMetaList::size_of(account_metas.len())? as u64;let lamports Rent::get()?.minimum_balance(account_size as usize);// 从上下文中获取铸造账户公钥。let mint ctx.accounts.mint.key();// ExtraAccountMetaList PDA 的种子。let signer_seeds: [[[u8]]] [[bextra-account-metas,mint.as_ref(),[ctx.bumps.extra_account_meta_list],]];// 创建 ExtraAccountMetaList 账户create_account(CpiContext::new(ctx.accounts.system_program.to_account_info(),CreateAccount {from: ctx.accounts.payer.to_account_info(),to: ctx.accounts.extra_account_meta_list.to_account_info(),},).with_signer(signer_seeds),lamports,account_size,ctx.program_id,)?;// 使用额外账户初始化 ExtraAccountMetaList 账户ExtraAccountMetaList::init::ExecuteInstruction(mut ctx.accounts.extra_account_meta_list.try_borrow_mut_data()?,account_metas,)?;Ok(())
}这里有很多内容让我们逐步理解
首先我们声明一个 ExtraAccountMeta 账户的向量 (account_metas)指定我们将使用的不同额外账户。这些可以从种子、公钥等指定。在我们的例子中我们只需要一个额外账户——鲸鱼详细信息账户。我们计算账户大小并基于此计算租金所需的 lamports 数量。我们指定需要签名的种子为 extra_account_meta_list PDA。我们调用 create_account CPI 实际创建 extra_account_meta_list 账户提供所有需要的数据付款人、签名者种子、账户大小等。最后我们用在步骤 1 中声明的额外账户向量初始化新创建的账户。
实现 transfer_hook 指令
我们达到了程序的核心——transfer_hook 指令这是实际操作发生的地方因为每当进行交易时都会调用此指令。
我们首先为指令添加传入账户。在 lib.rs 中 InitializeExtraAccountMeta 下方添加以下内容
#[derive(Accounts)]
pub struct TransferHookinfo {#[account(token::mint mint, token::authority owner)]pub source_token: InterfaceAccountinfo, TokenAccount,pub mint: InterfaceAccountinfo, Mint,#[account(token::mint mint)]pub destination_token: InterfaceAccountinfo, TokenAccount,/// CHECK: source token account owner, /// can be SystemAccount or PDA owned by another program pub owner: UncheckedAccountinfo,/// CHECK: ExtraAccountMetaList Account,#[account(seeds [bextra-account-metas, mint.key().as_ref()],bump)]pub extra_account_meta_list: UncheckedAccountinfo,#[account(mut, seeds[bwhale_account], bump)]pub latest_whale_account: Accountinfo, WhaleAccount,
} 提示
这里指定的账户顺序很重要 前四个账户是代币转账所需的账户源、铸造、目标和所有者——按此顺序第五个账户是 ExtraAccountMetaList 账户的地址。剩余的账户是 ExtraAccountMetaList 账户所需的额外账户。注意我们在这里传递的约束——它们是为了帮助我们确保传递的账户确实是我们期望的账户即正确的铸造账户正确的所有者等。
现在我们可以继续进行实际指令。在 initialize_extra_account 指令下方添加以下代码片段
pub fn transfer_hook(ctx: ContextTransferHook, amount: u64) - Result() {msg!(format!(Transfer hook fired for an amount of {}, amount));if amount 1000 * (u64::pow(10, ctx.accounts.mint.decimals as u32)) {// 我们有一个鲸鱼ctx.accounts.latest_whale_account.whale_address ctx.accounts.owner.key();ctx.accounts.latest_whale_account.transfer_amount amount;emit!(WhaleTransferEvent {whale_address: ctx.accounts.owner.key(),transfer_amount: amount});}Ok(())
}这里没有太多疯狂的事情
我们检查转账金额是否大于或等于 1000 代币这是我选择的任意金额你可以选择其他金额。如果金额确实是 1000 或更多我们更新鲸鱼详细信息账户的新地址和金额。最后我们发出一个名为 WhaleTransferEvent 的事件供客户端例如 Discord 机器人、Web 应用程序等监听。
在 lib.rs 底部添加实际的事件声明
#[event]
pub struct WhaleTransferEvent {pub whale_address: Pubkey,pub transfer_amount: u64,
}实现 fallback 指令
最后但同样重要的是fallback 指令。在 transfer_hook 指令之后添加以下代码片段
pub fn fallbackinfo(program_id: Pubkey,accounts: info [AccountInfoinfo],data: [u8],
) - Result() {let instruction TransferHookInstruction::unpack(data)?;// 匹配指令识别符以执行 transfer hook 接口指令// token2022 程序在代币转移时调用此指令match instruction {TransferHookInstruction::Execute { amount } {let amount_bytes amount.to_le_bytes();// 在我们的程序中调用自定义 transfer hook 指令__private::__global::transfer_hook(program_id, accounts, amount_bytes)}_ return Err(ProgramError::InvalidInstructionData.into()),}
}fallback 指令取自 Solana 的hello-world transfer hook 示例 。 虽然看起来很复杂但这个指令其实相当简单
解包指令数据并将其转换为 TransferHookInstruction。匹配 TransferHookInstruction 枚举。如果是 Execute 指令获取转移的金额并调用 transfer_hook 指令。如果不是 Execute 指令则返回无效指令数据的错误。 继续将程序部署到 Devnet
第 2 步铸造
好的我们的程序已经部署现在我们可以将注意力转向创建一个实际使用它的代币铸造。
使用以下命令创建一个铸造
spl-token --program-id TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb create-token --transfer-hook your transfer hook program id 提示
注意命令中的程序 id那是 Token 2022 程序的地址。我们必须指定它以便铸造实际使用新标准。为铸造注册 transfer hook 就像在 spl-token 命令中添加 --transfer-hook address 子命令一样简单。
为你配置的钱包创建代币账户
spl-token create-account the token address from previous step向你的钱包铸造一些代币
spl-token mint the token address from previous step 3000第 3 步初始化 ExtraAccountMeta 账户
如果你尝试转移代币例如使用 spl-token transfer 命令你将遇到 AccountNotFound 错误。这是因为我们从未初始化我们的老朋友 ExtraAccountMetaList
我们在程序中创建了一个指令来初始化账户initialize_extra_account所以现在是调用它的最佳时机。
我创建了这个小的 TypeScript 代码来调用它但你可以随意使用任何你喜欢的方法
import { readFile } from fs/promises;
import * as anchor from coral-xyz/anchor;
// 这两个文件是从构建的 Anchor 程序 Target/idl 和 Target/types 文件夹中复制的。
import { TransferHookWhale } from ./program/transfer_hook_whale;
import idl from ./program/transfer_hook_whale.json;import { TOKEN_2022_PROGRAM_ID, ASSOCIATED_TOKEN_PROGRAM_ID } from solana/spl-token;
import dotenv/config;const kpFile .your keypair file;
const mint new anchor.web3.PublicKey(your mint public address)const main async () {if (!process.env.SOLANA_RPC) {console.log(Missing required env variables);return;}console.log( Reading wallet...);const keyFile await readFile(kpFile);const keypair: anchor.web3.Keypair anchor.web3.Keypair.fromSecretKey(new Uint8Array(JSON.parse(keyFile.toString())));const wallet new anchor.Wallet(keypair);console.log(☕️ Setting provider and program...);const connection new anchor.web3.Connection(process.env.SOLANA_RPC);const provider new anchor.AnchorProvider(connection, wallet, {});anchor.setProvider(provider);const program new anchor.ProgramTransferHookWhale(idl as TransferHookWhale, provider);console.log( Initializing transfer hook accounts);const [extraAccountMetaListPDA] anchor.web3.PublicKey.findProgramAddressSync([Buffer.from(extra-account-metas), mint.toBuffer()],program.programId);const [whalePDA] anchor.web3.PublicKey.findProgramAddressSync([Buffer.from(whale_account)], program.programId);const initializeExtraAccountMetaListInstruction await program.methods.initializeExtraAccount().accounts({mint,extraAccountMetaList: extraAccountMetaListPDA,latestWhaleAccount: whalePDA,systemProgram: anchor.web3.SystemProgram.programId,tokenProgram: TOKEN_2022_PROGRAM_ID,associatedTokenProgram: ASSOCIATED_TOKEN_PROGRAM_ID,}).instruction();const transaction new anchor.web3.Transaction().add(initializeExtraAccountMetaListInstruction);const tx await anchor.web3.sendAndConfirmTransaction(connection, transaction, [wallet.payer], {commitment: confirmed,});console.log(Transaction Signature:, tx);
}main().then(() {console.log(done!);process.exit(0);
}).catch((e) {console.log(Error: , e);process.exit(1);
});完整项目在GitHub 第 4 步让转移开始
就是这样朋友们随着 transfer hook 程序的部署配置使用它的铸造以及 ExtraAccountMetaList 账户的初始化我们终于可以开始使用我们的 hook 了 transfer hook 正在运行更多相关信息https://t.me/gtokentool