编写第一个 Anchor 程序 - 第 2 部分

编写第一个 Anchor 程序

概述

程序是 Solana 所称的智能合约——它们是处理信息和请求的引擎:从代币转移和 Candy Machine 铸造到“你好,世界”日志和 DeFi 保管治理。Solana 支持使用 Rust、C 和 C++ 编程语言编写链上程序。Anchor 是一个加速在 Solana 上构建安全 Rust 程序的框架。让我们使用 Anchor 构建你的第一个 Solana 程序吧!

你将要做的事情

这是一个两部分系列的第二个指南。在 第一部分 中,我们讲解了如何在 Anchor 上创建你的第一个 Solana 程序。该程序允许客户端请求发送到 Solana 的开发网络,并获取“你好,世界”的日志。在本指南中,我们将基于该程序,实施一个递增函数来跟踪程序被使用的次数:

  • 创建一个程序指令以初始化新的数据账户
  • 更新 hello_world 函数以在每次调用时递增数据账户
  • 创建一个客户端请求以调用我们更新的函数

你将需要的东西

启动你的项目

我们将基于本系列第一部分的项目进行构建。如果你已经打开该项目,请继续进行下一部分。

如果你还没有“你好,世界”项目,你可以通过在 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:

你准备好开始了!让我们构建吧!

创建Counter账户

创建初始化结构体

为了计算我们的“hello_world”函数被调用的次数,我们需要创建一个由我们的程序拥有的新数据账户。该数据账户将需要两个新的结构体:

  1. 一个账户结构体 Counter,用于跟踪 count(这是一个无符号 64 位整数,u64)。 #[account] 将 Anchor 的部分能力引入其中,帮助我们处理数据的序列化和反序列化。
#[account]
pub struct Counter {
    count: u64
}
  1. 一个新的 Context 将用于创建一个新的Counter账户。上下文 Initialize 将告诉我们的程序在从客户端发送交易指令时需要提供哪些公共密钥。 #[derive(Accounts)] 为我们抽象了很多内容——我们将在以后的指南中涵盖这一点。现在,请知道 Anchor 在大幅简化我们的代码!
#[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 结构体将需要三个公共密钥:

  1. counter,我们创建的新数据账户的公共密钥(类型为上面创建的 Counter)。请注意,我们必须传递几个参数来初始化我们的新账户:
    • init 表示 Anchor 我们正在初始化一个新账户。
    • 定义一个负责支付与新账户相关的租金的公共密钥(payer),我们将其设置为我们的 signer
    • space 用于计算我们的账户需要多少字节(更多字节需要更多租金,因此我们希望准确满足需求)。我们将需要 8 字节作为账户标识符(每个账户所需)和 8 字节作为我们的 u64。请参阅 Anchor 的空间参考指南 以获取所有类型的空间要求。
  2. signer,负责签署 initialize 交易(以及支付上述费用)的钱包。
  3. system_program,将拥有我们的新账户并处理账户之间的 SOL 转移。

创建初始化Counter功能

太好了!现在我们的结构体已定义,我们需要创建一个初始化函数。在 hello_worldsay_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。

从客户端初始化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`); 

让我们逐步解析一下:

  1. 生成一个新的密钥对,用于我们的新Counter账户。

  2. 获取最新的块哈希。

  3. 和之前一样,我们将使用 Anchor 调用我们程序的 initialize_counter(在 TypeScript 中,它格式化为 initializeCounter)。在这个实例中,我们必须传递我们在 Initialize 结构中开发的账户:counter、signer 和系统程序。Anchor 知道我们需要传递系统程序和 signer,因此我们不需要在这里添加它。最后,由于我们希望我们的程序能够在未来更新此账户中的数据,我们必须让Counter将权限赋予我们的程序。我们必须将账户的签名附加到我们的方法中以实现这一点。

好了,让我们施展一点魔法。在左侧边栏点击“▶️ 运行”以初始化你的账户。交易处理后,你应该在终端中看到一个交易 URL。打开它并向下滚动到交易指令。你应该看到如下内容:

你可以看到我们的 Playground 钱包创建了一个新的账户,该账户被分配给我们的程序!你还会注意到该账户已分配 16 字节的数据。最后,如果你向下滚动到日志中,你可以看到我们的账户状态为 0,符合预期。干得不错!现在我们需要将Counter添加到我们的 say_hello 函数中,以便我们可以在 devnet 上永远跟踪 hello!

在继续之前,从控制台复制你的Counter公钥,并替换你的客户端代码中的公钥:

const COUNTER = 'S61VvavzvHtGLEMxxavPdsKydRnwShxfbubGbw1mkPU'; // 替换为你的公钥

实现Counter

我们现在应该有一个可以打招呼的程序和一个可以保存整数值的数据账户。我们需要让这两个账户进行交互。我们需要更新我们的 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 指南。

额外资源

我是 AI 翻译官,为大家转译优秀英文文章,如有翻译不通的地方,在这里修改,还请包涵~

点赞 0
收藏 0
分享
本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

0 条评论

请先 登录 后评论
QuickNode
QuickNode
江湖只有他的大名,没有他的介绍。