文章介绍了如何使用Codama库为Solana上的Anchor程序生成客户端,包括创建Anchor程序、生成客户端代码以及测试客户端的完整流程。
Codama 是一组库,提供了为 Solana 程序生成客户端的工具。它是一个强大的工具,可以用于为现有的 Solana 程序生成 JavaScript、Umi(JavaScript)和 Rust 客户端。Codama 最近增加了从 Anchor IDL 生成客户端的功能,因此我们现在可以使用 Codama 为 Anchor 程序创建客户端。这可以在使用 Anchor 构建和测试新程序时节省时间。本指南将展示如何使用 Codama 为你的 Anchor 程序生成客户端。
本指南假设你对 Solana 编程和 Anchor 有基本的了解:
在开始之前,请确保你已经安装了以下内容:
检查你的 Solana 和 Anchor 版本
本指南适用于 Solana CLI 版本 1.18.16 或更高版本,Anchor 版本 0.30.0 或更高版本。
solana --version
检查你的 Solana 版本。如果需要更新,请按照此处的说明进行操作。anchor --version
检查你的 Anchor 版本。如果需要更新,请按照此处的说明进行操作。让我们开始吧!
Codama 是由 Metaplex Foundation 从 Kinobi 项目中分离出来的一个库,旨在为 Solana 程序生成客户端。Codama 通过传递一个或多个程序的 IDL 来生成一个节点树,这些节点可以由访问者更新。这些访问者可以根据需要更新指令或账户。与语言无关的渲染访问者可以生成各种语言的客户端,以便你可以管理客户端堆栈/依赖项。
最近,Codama 增加了从 Anchor IDL 生成客户端的功能。这意味着你现在可以使用 Codama 为 Anchor 程序生成客户端。让我们看看如何做到这一点。
活跃开发
Codama 对 Anchor 的支持仍然非常新,并且正在积极开发中。代码可能会发生变化,可能会添加新功能。如果你遇到任何问题,请在 Discord 上告诉我们。
首先,让我们创建一个简单的 Anchor 程序。我们将创建一个包含两条指令的程序:
initialize
:用 u64 值初始化一个数据账户。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 树。
在你的 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 程序。打开你的 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.
干得漂亮。
由于你的测试已成功运行,Anchor 应该已经为你自动生成了一个 IDL,位于 target/idl/codama_test.json
。找到这个文件——我们将在下一节中使用它(注意:如果你为你的 Anchor 项目使用了不同的名称,此文件路径可能会略有不同)。我们现在可以使用 Codama 为这个程序生成客户端。
在你的根目录中创建一个新文件夹 clients
,并创建两个新文件:
generate-client.ts
用于客户端生成脚本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();
让我们分解一下这个脚本的作用:
rootNodeFromAnchorWithoutDefaultVisitor
函数)。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);
});
这个脚本将:
initialize
和 setData
函数。它们接受 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 与之进行了交互。你现在可以在你的应用程序中使用这个客户端与你的程序进行交互。
干得好,你已经完成了这些步骤。以下是你在本指南中完成的内容的快速回顾:
通过利用 Codama,你简化了客户端生成过程,使你的开发工作流程更加高效。如果你正在构建一个复杂的程序,为你的客户构建许多程序,或者只是想节省时间,Codama 可以成为你工具箱中的一个宝贵工具。
随着 Codama 的发展,请随时关注其最新功能和改进。不要犹豫,通过报告问题或提交 PR 来贡献 到这个项目。
如果你遇到困难或有任何问题,请在我们的 Discord 上发布。你也可以通过
- 原文链接: quicknode.com/guides/sol...
- 登链社区 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!