如何使用Codama创建Anchor程序客户端

  • QuickNode
  • 发布于 2024-07-15 18:28
  • 阅读 20

文章介绍了如何使用Codama库为Solana上的Anchor程序生成客户端,包括创建Anchor程序、生成客户端代码以及测试客户端的完整流程。

概述

Codama 是一组库,提供了为 Solana 程序生成客户端的工具。它是一个强大的工具,可以用于为现有的 Solana 程序生成 JavaScript、Umi(JavaScript)和 Rust 客户端。Codama 最近增加了从 Anchor IDL 生成客户端的功能,因此我们现在可以使用 Codama 为 Anchor 程序创建客户端。这可以在使用 Anchor 构建和测试新程序时节省时间。本指南将展示如何使用 Codama 为你的 Anchor 程序生成客户端。

你将完成的任务

  • 创建一个简单的 Anchor 程序
  • 编写一个脚本,使用 Codama 为该程序生成客户端
  • 测试客户端

你需要准备的内容

本指南假设你对 Solana 编程和 Anchor 有基本的了解:

在开始之前,请确保你已经安装了以下内容:

检查你的 Solana 和 Anchor 版本

本指南适用于 Solana CLI 版本 1.18.16 或更高版本,Anchor 版本 0.30.0 或更高版本。

  • 在终端中运行 solana --version 检查你的 Solana 版本。如果需要更新,请按照此处的说明进行操作。
  • 在终端中运行 anchor --version 检查你的 Anchor 版本。如果需要更新,请按照此处的说明进行操作。

让我们开始吧!

什么是 Codama?

Codama 是由 Metaplex Foundation 从 Kinobi 项目中分离出来的一个库,旨在为 Solana 程序生成客户端。Codama 通过传递一个或多个程序的 IDL 来生成一个节点树,这些节点可以由访问者更新。这些访问者可以根据需要更新指令或账户。与语言无关的渲染访问者可以生成各种语言的客户端,以便你可以管理客户端堆栈/依赖项。

Codama 的工作原理

  1. 程序定义:你定义你的 Solana 程序及其对应的 IDL。
  2. 抽象:Codama 创建一个与语言无关的节点树(或客户端表示),访问者可以对其进行更新。
  3. 客户端生成:Codama 的访问者处理树并生成特定语言的客户端。

Codama 图示来源:Metaplex Developers

关键组件

  • 程序:带有相关 IDL 的 Solana 程序。
  • IDL:描述 Solana 程序的接口和功能。
  • Codama 树:组织 IDL 并促进客户端生成。
  • 访问者:为特定语言定制客户端生成过程的模块。
  • 依赖项:包括必要的库和工具,如 HTTP 接口、RPC 接口和加密工具。

最近,Codama 增加了从 Anchor IDL 生成客户端的功能。这意味着你现在可以使用 Codama 为 Anchor 程序生成客户端。让我们看看如何做到这一点。

活跃开发

Codama 对 Anchor 的支持仍然非常新,并且正在积极开发中。代码可能会发生变化,可能会添加新功能。如果你遇到任何问题,请在 Discord 上告诉我们。

创建一个 Anchor 程序

首先,让我们创建一个简单的 Anchor 程序。我们将创建一个包含两条指令的程序:

  1. initialize:用 u64 值初始化一个数据账户。
  2. set_data:设置数据账户的值。

初始化项目

创建一个新的项目目录并运行以下命令来创建一个新的 Anchor 程序:

anchor init codama-test

进入项目目录:

cd codama-test

安装依赖项

项目初始化后,你可以运行 npm install 以确保依赖项已安装。然后我们将安装一些额外的依赖项。在终端中运行以下命令:

npm install @codama/nodes-from-anchor @codama/renderers @codama/visitors-core @metaplex-foundation/umi @metaplex-foundation/umi-bundle-defaults

这将安装一些 Codama 包,包括 nodes-from-anchor 包,它将帮助我们从 Anchor IDL 生成 Codama 树。

更新 TSConfig

在你的 tsconfg.json 中添加 resolveJsonModule,以确保我们可以加载 IDL JSON 对象来生成客户端,并将 "DOM" 添加到你的 lib 数组中,以便我们可以在 Node.js 中运行脚本。更新项目目录中的 tsconfig.json 文件,使其如下所示:

{
    "compilerOptions": {
      "types": ["mocha", "chai"],
      "typeRoots": ["./node_modules/@types"],
      "lib": ["ES2020", "DOM"],
      "module": "commonjs",
      "target": "es6",
      "esModuleInterop": true,
      "resolveJsonModule": true,
 }
}

编写 Anchor 程序

让我们编写我们的 Anchor 程序。打开你的 programs/codama-test/src/lib.rs 文件,并将内容替换为以下代码,注意不要覆盖你的 declare_id! 宏:

use anchor_lang::prelude::*;

declare_id!("YOUR_PROGRAM_ID_HERE"); // 替换为你的程序 ID

#[program]
pub mod codama_test {
    use super::*;

    pub fn initialize(ctx: Context<Initialize>) -> Result<()> {
        ctx.accounts.pda.set_inner(ExampleStruct {
            data: 0,
            authority: *ctx.accounts.payer.key,
 });
        Ok(())
 }

    pub fn set_data(ctx: Context<SetData>, data: u32) -> Result<()> {
        ctx.accounts.pda.data = data;
        Ok(())
 }
}

#[derive(Accounts)]
pub struct Initialize<'info> {
 #[account(mut)]
    payer: Signer<'info>,

 #[account(\
 init,\
 payer = payer,\
 space = 45,\
 seeds = [b"example".as_ref(), payer.key().as_ref()],\
        bump\
 )]
    pda: Account<'info, ExampleStruct>,

    system_program: Program<'info, System>,
}

#[derive(Accounts)]
pub struct SetData<'info> {
 #[account(mut)]
    authority: Signer<'info>,

 #[account(\
        mut,\
 has_one = authority,\
 seeds = [b"example".as_ref(), authority.key().as_ref()],\
        bump\
 )]
    pda: Account<'info, ExampleStruct>,
}

#[account]
pub struct ExampleStruct {
    pub data: u32,
    pub authority: Pubkey,
}

这是一个基本的 Anchor 程序,它将允许用户初始化一个 ExampleStruct(一个包含 u32 数据和 authority PublicKey 的 PDA)并设置数据值。PDA 使用支付者的密钥和字符串 "example" 进行种子生成。你可以自由使用不同的程序,或根据需要修改此程序——它仅用于演示目的。

构建和测试程序

现在我们有了程序,我们可以构建并测试它。在终端中运行以下命令:

anchor build

这可能需要几分钟,但应该会运行而不出现任何错误。在它运行时,让我们编写一个简单的测试脚本。打开你由 anchor 生成的测试文件 tests/codama-test.ts,并将内容替换为以下代码:

import * as anchor from "@coral-xyz/anchor";
import { Program } from "@coral-xyz/anchor";
import { CodamaTest } from "../target/types/codama_test";

describe("codama-test", () => {
  anchor.setProvider(anchor.AnchorProvider.env());
  const program = anchor.workspace.CodamaTest as Program<CodamaTest>;
  const [pda] = anchor.web3.PublicKey.findProgramAddressSync(
 [\
      Buffer.from("example"),\
      program.provider.publicKey.toBuffer()\
 ],
    program.programId
 )
  it("Is initialized!", async () => {
    const tx = await program.methods
 .initialize()
 .accountsStrict({
        payer: program.provider.publicKey,
        pda,
        systemProgram: anchor.web3.SystemProgram.programId
 })
 .rpc();
 });
  it("Can set data!", async () => {
    const tx = await program.methods
 .setData(10)
 .accountsStrict({
        authority: program.provider.publicKey,
        pda
 })
 .rpc({skipPreflight: true});
 });
});

这个脚本将测试我们程序中的两条指令。第一个测试将初始化数据账户,第二个测试将数据值设置为 10。继续运行测试脚本:

anchor test

你应该会看到类似以下的内容:

  codama-test
    ✔ Is initialized! (450ms)
    ✔ Can set data! (463ms)

  2 passing (916ms)

✨  Done in 2.80s.

干得漂亮。

使用 Codama 生成客户端

由于你的测试已成功运行,Anchor 应该已经为你自动生成了一个 IDL,位于 target/idl/codama_test.json。找到这个文件——我们将在下一节中使用它(注意:如果你为你的 Anchor 项目使用了不同的名称,此文件路径可能会略有不同)。我们现在可以使用 Codama 为这个程序生成客户端。

在你的根目录中创建一个新文件夹 clients,并创建两个新文件:

  1. generate-client.ts 用于客户端生成脚本
  2. example.ts 用于尝试生成的客户端

生成客户端

打开 generate-client.ts,并在文件中添加以下代码:

import { AnchorIdl, rootNodeFromAnchorWithoutDefaultVisitor } from "@codama/nodes-from-anchor";
import { renderJavaScriptUmiVisitor, renderJavaScriptVisitor, renderRustVisitor } from "@codama/renderers";
import { visit } from "@codama/visitors-core";
import anchorIdl from "../target/idl/codama_test.json"; // 注意:如果你用不同的名称初始化了你的项目,你可能需要更改此路径

async function generateClients() {
    const node = rootNodeFromAnchorWithoutDefaultVisitor(anchorIdl as AnchorIdl);

    const clients = [\
        { type: "JS", dir: "clients/generated/js/src", renderVisitor: renderJavaScriptVisitor },\
        { type: "Umi", dir: "clients/generated/umi/src", renderVisitor: renderJavaScriptUmiVisitor },\
        { type: "Rust", dir: "clients/generated/rust/src", renderVisitor: renderRustVisitor }\
    ];

    for (const client of clients) {
        try {
            await visit(
                node,
                await client.renderVisitor(client.dir)
            ); console.log(`✅ Successfully generated ${client.type} client for directory: ${client.dir}!`);
        } catch (e) {
            console.error(`Error in ${client.renderVisitor.name}:`, e);
            throw e;
        }
    }
}

generateClients();

让我们分解一下这个脚本的作用:

  1. 导入必要的 Codama 包。
  2. 导入由 Anchor 生成的 IDL 文件,并从中创建一个 Codama 树(使用 rootNodeFromAnchorWithoutDefaultVisitor 函数)。
  3. 定义要生成的客户端(JavaScript、Umi 和 Rust)——你可以根据需要注释掉任何你不需要的客户端,并调整目录。
  4. 遍历客户端,并使用适当的渲染访问者使用 visit 函数生成客户端。

运行脚本

现在我们有了脚本,我们可以运行它来生成客户端。在终端中运行以下命令:

ts-node clients/generate-client.ts

你应该会看到类似以下的输出:

ts-node clients/generate-client.ts
✅ Successfully generated JS client for directory: clients/generated/js/src!
✅ Successfully generated Umi client for directory: clients/generated/umi/src!
✅ Successfully generated Rust client for directory: clients/generated/rust/src!

你现在应该在 clients 目录中生成了你的程序的客户端。干得漂亮!

你现在可以使用这些客户端与你的程序进行交互。

测试客户端

打开你之前创建的 example.ts 文件,并添加以下代码:

import { createUmi } from '@metaplex-foundation/umi-bundle-defaults';
import { TransactionBuilderSendAndConfirmOptions, generateSigner, keypairIdentity, sol } from '@metaplex-foundation/umi';
import { publicKey as publicKeySerializer, string } from '@metaplex-foundation/umi/serializers';
import { getCodamaTestProgramId } from './generated/umi/src/programs/codamaTest';
import { initialize, setData } from './generated/umi/src/instructions';

const umi = createUmi('http://127.0.0.1:8899', { commitment: 'processed' });
const creator = generateSigner(umi);
umi.use(keypairIdentity(creator));

const options: TransactionBuilderSendAndConfirmOptions = {
    confirm: { commitment: 'processed' }
};

const pda = umi.eddsa.findPda(getCodamaTestProgramId(umi), [\
    string({ size: 'variable' }).serialize('example'),\
    publicKeySerializer().serialize(creator.publicKey),\
]);

async function logPda() {
    console.log(`PDA: ${pda.toString()}`);
}

async function airdropFunds() {
    try {
        await umi.rpc.airdrop(creator.publicKey, sol(100), options.confirm);
        console.log(`1. ✅ - Airdropped 100 SOL to the ${creator.publicKey.toString()}`);
    } catch (error) {
        console.error('1. ❌ - Error airdropping SOL to the wallet.', error);
    }
}

async function initializeAccount() {
    try {
        await initialize(umi, { pda, payer: creator }).sendAndConfirm(umi, options);
        console.log('2. ✅ - Initialized the account.');
    } catch (error) {
        console.error('2. ❌ - Error initializing the account.', error);
    }
}

async function setDataAccount(num: number, value: number) {
    try {
        await setData(umi, { authority: creator, pda, data: value }).sendAndConfirm(umi, options);
        console.log(`${num}. ✅ - Set data to ${value}.`);
    } catch (error) {
        console.error(num, '. ❌ - Error setting data.', error);
    }
}

async function main() {
    await logPda();
    await airdropFunds();
    await initializeAccount();
    await setDataAccount(3, 10);
    await setDataAccount(4, 20);
    await setDataAccount(5, 30);
    await setDataAccount(6, 40);
}

main().then(() => {
    console.log('🚀 - Done!');
}).catch((error) => {
    console.error('❌ - Error:', error);
});

这个脚本将:

  • 导入必要的 Umi 包和助手
  • 导入生成的客户端函数
  • 创建一个 Umi 实例
  • 根据我们程序的种子为我们的签名者获取 PDA
  • 定义函数来记录 PDA、空投资金、初始化账户和设置数据
    • 注意,Codama 生成了我们的 initializesetData 函数。它们接受 Umi 实例和必要的参数,并返回一个可用于发送和确认交易的函数。简单吧?
  • 最后,在我们的 main 函数中按顺序运行这些函数。我们包含了几个 setData 调用来演示功能。

运行脚本

现在我们有了脚本,我们可以运行它来与我们的程序进行交互。在终端中运行以下命令:

ts-node clients/example.ts

我猜你遇到了错误,对吧?这是因为我们的本地验证器没有运行。让我们用 --detach 标志重新运行我们的测试,以保持它在后台运行:

anchor test --detach

现在,在另一个终端中,再次运行 example.ts 脚本:

ts-node clients/example.ts

这次运气好点了吗?你应该会看到类似以下的输出:

PDA: 5GawRMyhgw8uDxanKeZd89AMeteuHmuAhyb2NSN7YEgJ,255
1. ✅ - Airdropped 100 SOL to the 5L8siRBhjiAE4GJKZmZSSkfCYBSRZXMv44SVuoD888Yt
2. ✅ - Initialized the account.
3. ✅ - Set data to 10.
4. ✅ - Set data to 20.
5. ✅ - Set data to 30.
6. ✅ - Set data to 40.
🚀 - Done!

恭喜!你已经成功地使用 Codama 为你的 Anchor 程序生成了客户端,并使用 Umi 与之进行了交互。你现在可以在你的应用程序中使用这个客户端与你的程序进行交互。

总结

干得好,你已经完成了这些步骤。以下是你在本指南中完成的内容的快速回顾:

  1. 程序创建:你创建了一个包含基本指令的简单 Anchor 程序。
  2. 测试:你编写并执行了测试,以确保你的程序正常工作。
  3. 客户端生成:你使用 Codama 从你的 Anchor IDL 生成了 JavaScript、Umi 和 Rust 客户端。
  4. 客户端交互:你编写了一个脚本来使用生成的客户端与你的程序进行交互,并确认了其功能。

通过利用 Codama,你简化了客户端生成过程,使你的开发工作流程更加高效。如果你正在构建一个复杂的程序,为你的客户构建许多程序,或者只是想节省时间,Codama 可以成为你工具箱中的一个宝贵工具。

随着 Codama 的发展,请随时关注其最新功能和改进。不要犹豫,通过报告问题或提交 PR 来贡献 到这个项目。

如果你遇到困难或有任何问题,请在我们的 Discord 上发布。你也可以通过

  • 原文链接: quicknode.com/guides/sol...
  • 登链社区 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~
点赞 0
收藏 0
分享
本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

0 条评论

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