编写第一个 Anchor 程序
- 原文链接:www.quicknode.com/guides...
- 译者:AI翻译官,校对:翻译小组
- 本文链接:learnblockchain.cn/article…
程序是 Solana 所称的智能合约——它们是处理信息和请求的引擎:从代币转移和 Candy Machine 铸造到“你好,世界”日志和 DeFi 保管治理。Solana 支持使用 Rust、C 和 C++ 编程语言编写链上程序。Anchor 是一个加速在 Solana 上构建安全 Rust 程序的框架。让我们使用 Anchor 构建你的第一个 Solana 程序吧!
这是一个两部分系列的第二个指南。在 第一部分 中,我们讲解了如何在 Anchor 上创建你的第一个 Solana 程序。该程序允许客户端请求发送到 Solana 的开发网络,并获取“你好,世界”的日志。在本指南中,我们将基于该程序,实施一个递增函数来跟踪程序被使用的次数:
我们将基于本系列第一部分的项目进行构建。如果你已经打开该项目,请继续进行下一部分。
如果你还没有“你好,世界”项目,你可以通过在 Solana Playground 上创建一个新的 Anchor 项目来赶上。打开 lib.rs。删除现有内容,并粘贴以下代码:
use anchor_lang::prelude::*;
declare_id!("11111111111111111111111111111111");
#[program]
mod hello_world {
use super::*;
pub fn say_hello(_ctx: Context<SayHello>) -> Result<()> {
msg!("Hello World!");
Ok(())
}
}
#[derive(Accounts)]
pub struct SayHello {}
由于该项目只是用于演示 purposes ,我们可以使用一个“临时”钱包。Solana Playground 使你能够轻松创建一个。你应该在浏览器窗口左下角看到一个红点“未连接”。点击它:
Solana Playground 会为你生成一个钱包(或你可以导入你自己的)。如果你愿意,可以保存以备后用,并在准备好时点击继续。一个新钱包将被初始化并连接到 Solana 开发网络。Solana Playground 会自动为空账户空投一些 SOL,但是我们需要请求一些额外的,以确保能够足够部署我们的程序。在浏览器终端中,你可以使用 Solana CLI 命令。输入 solana airdrop 2 以向你的钱包中投放 2 SOL。你的钱包现在应该连接到开发网络,余额为 6 SOL:
你准备好开始了!让我们构建吧!
为了计算我们的“hello_world”函数被调用的次数,我们需要创建一个由我们的程序拥有的新数据账户。该数据账户将需要两个新的结构体:
#[account]
pub struct Counter {
count: u64
}
#[derive(Accounts)]
pub struct Initialize<'info> {
#[account(init, payer = signer, space = 8 + 8)]
pub counter: Account<'info, Counter>,
#[account(mut)]
pub signer: Signer<'info>,
pub system_program: Program<'info, System>,
}
我们的 Initialize 结构体将需要三个公共密钥:
太好了!现在我们的结构体已定义,我们需要创建一个初始化函数。在 hello_world 的 say_hello 函数之后,创建一个新函数 initialize_counter:
pub fn initialize_counter(ctx: Context<Initialize>) -> Result<()> {
let counter = &mut ctx.accounts.counter;
counter.count = 0;
msg!("Initialized new count. Current value: {}!", counter.count);
Ok(())
}
我们的函数将传递一个上下文 ctx,其类型为 Initialize(来自我们的前一步)。由于我们在 Initialize 结构体中包含了所有必要的账户信息,Anchor 能够帮助我们完成大部分的重任。然而,我们需要调用我们的 counter 账户并将其初始值设置为 0。因为我们正在修改 counter,我们必须将变量标记为可变,使用 &mut。最后,我们将添加一个日志以显示我们的Counter已以正确的初始余额初始化。Anchor 将确保我们的数据正确序列化!
做得好!让我们创建我们的Counter。
首先,我们需要构建和更新我们的程序:
点击屏幕左侧的 🔧 Build 以编译代码并检查错误。你应该在控制台中看到如下日志。
点击页面左侧的工具图标 🛠,然后点击“升级”。这将升级我们已经部署到 devnet 的程序。
注意:Solana Playground 仍然处于测试版。在为我的客户端类型部署后,我必须刷新才能更新我的新函数。你可能也需要这样做。
在程序部署时,返回到你的客户端代码 client.ts。你可以删除之前的代码并创建一个新的调用 initializeCounter():
// 1 - Generate a new Keypair for the Counter Account
const counter = anchor.web3.Keypair.generate();
console.log('creating counter: ', counter.publicKey.toString());
// 2 - Fetch latest blockhash
let latestBlockhash = await pg.connection.getLatestBlockhash('finalized');
// 3 - Call initialize_counter and send the transaction to the network
const tx = await pg.program.methods
.initializeCounter()
// 3a - Pass the counter public key into our accounts context
.accounts({counter: counter.publicKey})
// 3b - Append the counter keypair's signature to transfer authority to our program
.signers([counter])
.transaction();
// 4 - Confirm the transaction
const txId = await web3.sendAndConfirmTransaction(pg.connection, tx, [pg.wallet.keypair, counter]);
console.log(`https://explorer.solana.com/tx/${txId}?cluster=devnet`);
让我们逐步解析一下:
生成一个新的密钥对,用于我们的新Counter账户。
获取最新的块哈希。
和之前一样,我们将使用 Anchor 调用我们程序的 initialize_counter(在 TypeScript 中,它格式化为 initializeCounter)。在这个实例中,我们必须传递我们在 Initialize 结构中开发的账户:counter、signer 和系统程序。Anchor 知道我们需要传递系统程序和 signer,因此我们不需要在这里添加它。最后,由于我们希望我们的程序能够在未来更新此账户中的数据,我们必须让Counter将权限赋予我们的程序。我们必须将账户的签名附加到我们的方法中以实现这一点。
好了,让我们施展一点魔法。在左侧边栏点击“▶️ 运行”以初始化你的账户。交易处理后,你应该在终端中看到一个交易 URL。打开它并向下滚动到交易指令。你应该看到如下内容:
你可以看到我们的 Playground 钱包创建了一个新的账户,该账户被分配给我们的程序!你还会注意到该账户已分配 16 字节的数据。最后,如果你向下滚动到日志中,你可以看到我们的账户状态为 0,符合预期。干得不错!现在我们需要将Counter添加到我们的 say_hello 函数中,以便我们可以在 devnet 上永远跟踪 hello!
在继续之前,从控制台复制你的Counter公钥,并替换你的客户端代码中的公钥:
const COUNTER = 'S61VvavzvHtGLEMxxavPdsKydRnwShxfbubGbw1mkPU'; // 替换为你的公钥
我们现在应该有一个可以打招呼的程序和一个可以保存整数值的数据账户。我们需要让这两个账户进行交互。我们需要更新我们的 say_hello 函数和传递给 SayHello 的上下文。将你的 say_hello 函数替换为:
pub fn say_hello(ctx: Context<SayHello>) -> Result<()> {
let counter = &mut ctx.accounts.counter;
counter.count += 1;
msg!("Hello World! - Greeting # {}", counter.count);
Ok(())
}
我们更新的函数首先从上下文中的 accounts 对象获取Counter。由于我们将要修改它,因此必须将其标记为可变,使用 &mut。然后我们将Counter增加 1,并使用 msg! 记录我们的新值和问候。我们几乎准备好了,但首先,我们需要更新我们的 SayHello 结构以传递我们的Counter账户。将 SayHello 更新为包含 counter 作为可变账户:
#[derive(Accounts)]
pub struct SayHello<'info> {
#[account(mut)]
pub counter: Account<'info, Counter>,
}
打开 client.ts。在你的 COUNTER 地址和助手函数后,添加以下代码:
console.log(pg.wallet.publicKey.toString(), "saying hello:");
//1. Fetch latest blockhash
let latestBlockhash = await pg.connection.getLatestBlockhash('finalized');
//2. Call say_hello and send the transaction to the network
const counter = new anchor.web3.PublicKey(COUNTER);
const tx = await pg.program.methods
.sayHello()
.accounts({counter})
.transaction();
//3. Confirm the transaction and log the tx URL
const txId = await web3.sendAndConfirmTransaction(pg.connection, tx, [pg.wallet.keypair]);
console.log(`https://explorer.solana.com/tx/${txId}?cluster=devnet`);
//4. Query the chain for our data account and log its value
const data = await pg.program.account.counter.fetch(counter);
console.log('greeted', data.count.toNumber(),' times');
这应该看起来非常熟悉,就像第一部分调用 sayHello 一样,只是有几个小而重要的区别:
在步骤 2 中,我们现在必须添加 .accounts() 方法并传递我们的 counter 公钥,以便我们的程序知道要更新哪个Counter(理论上,我们的程序可以处理任意数量的不同Counter)。
记得当我们初始化 counter 时,我们需要用Counter的密钥对签名交易吗?我们在这里不需要这样做,因为我们之前已将Counter账户的权限分配给我们的程序,这意味着程序有权限更改数据!
最后,步骤 4 是新的。现在我们正在向集群写入数据,我们也必须读取它。Anchor 通过允许我们在数据账户上调用 .fetch() 来简化这个过程。然后,我们记录我们的结果。
随意点击“▶️ 运行”几次。你应该看到你的程序现在正在跟踪它说了多少次“Hello World”:
干得好!你的程序开始变得更加令人兴奋!理解如何通过你的程序读取和写入数据账户是你作为 Solana 开发者在前进过程中将频繁遇到的重要概念。
想继续构建吗?查看我们 其他 Solana 指南。
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!