如何使用Codama在Solana Kit中创建自定义程序客户端
本文介绍了如何使用Codama为Solana Kit(原Solana Web3.js 2.0)生成自定义程序客户端。通过create-solana-program库或Anchor项目,结合IDL和Codama的JavaScript渲染器,可以自动生成与Solana Kit兼容的客户端代码。文章提供了详细的步骤和代码示例,包括环境搭建、客户端生成和测试,展示了如何高效地管理和更新Solana程序客户端。
🛠️ 更新公告
本指南已更新,以反映 Solana Web3.js 2.0 的新名称——Solana Kit。我们遵循最新的最佳实践,助你保持与时俱进。在此了解更多关于 Solana Kit 的信息。
概述
Solana 近期推出了 Solana Kit(原名 Solana Web3.js 2.0),这是其与 Solana 区块链交互的 JavaScript 库的一次重大更新。新版 SDK 的一个激动人心的特性是,它已配备一个 Codama JavaScript 渲染器,只需一个 IDL 文件,就能为全新的 Solana Kit SDK 生成客户端。
Kinboi 是一组库,提供了为 Solana 程序生成客户端的工具。它是一个强大的工具,可用于为现有的 Solana 程序生成 JavaScript、Rust、Umi(Metaplex 维护的 JavaScript 库)以及(未来)其他语言的客户端。Codama 通过传入一个或多个程序的 IDL 来生成一个节点树,该节点树可以由访问者(Visitors)更新。这些访问者可以根据需要更新指令或账户。语言无关的渲染访问者可以生成各种语言的客户端,方便你管理客户端堆栈和依赖项。
我们试试看吧!
你将做什么
在本指南中,你将:
- 使用
create-solana-program创建一个程序和客户端 - 使用 Anchor 创建一个程序和客户端
你需要什么
先决知识
- Solana 基础知识(指南:Solana 入门)
- 使用 Anchor 进行 Solana 编程经验
- 了解 Solana IDL
本指南使用的依赖项
| 依赖项 | 版本 |
|---|---|
| create-solana-program | v0.3.12 |
| anchor cli | 0.30.1 |
| @solana/kit | ^2.0.0 |
| @codama/nodes-from-anchor | ^0.21.2 |
| @codama/renderers-js | ^0.21.8 |
| codama | ^0.21.4 |
Create-Solana-Program
可以说,为 Solana 程序创建客户端最简单的方法是使用 create-solana-program 库。该库允许你使用 Shank 或 Anchor 创建 Solana 程序。它预装了 Codama 配置。你只需创建一个新项目,构建程序,然后生成客户端:
创建一个新项目:
npm create solana-program@latest
系统会提示你选择一个模板。选择你喜欢的模板(本例中我们将使用 Anchor,但这并不影响),并确保选择 JavaScript 客户端(你也可以包含 Rust,但 JavaScript 渲染器与新的 Solana Kit 兼容)。

接下来,安装依赖项:
npm install
该包附带了一个预写的计数器程序。你可以随时在 programs/ 目录下修改它。准备就绪后,构建程序:
npm run programs:build
程序构建完成后,你可以运行 generate 脚本来为程序创建 IDL 和客户端:
npm run generate
如果你有兴趣,可以查看 scripts/generate-clients.mjs 文件中的 generate-clients 脚本。该脚本只是将 IDL 加载到一个 Kinboi 树中,然后使用 JavaScript 渲染器为程序生成客户端。很简单,对吧?接下来,我们将在 Anchor 项目中重建一个类似的脚本。不过首先,让我们先看看生成的客户端!
导航到 clients/js/src/index.ts 并浏览生成的客户端。你会看到指令、账户、错误等,全部由你的 IDL 生成!
而且无需编写任何代码,你就拥有了一个随时可以部署到 Solana 主网的程序和客户端!如果你在使用 Anchor 构建时不想使用 create-solana-program 库怎么办?没问题!下一节我们将展示如何操作。
Anchor
如果你使用 Anchor 编写 Solana 程序,也可以使用 Codama 生成与 Solana Kit 兼容的客户端。让我们使用与 create-solana-program 库中相同的计数器程序,通过一个简单示例来演示。
创建一个新的 Anchor 项目
首先,退出 program-client-demo-anchor 目录并创建一个新项目:
anchor init program-client-demo-anchor
然后,安装依赖项:
cd program-client-demo-anchor
将你的 programs/program-client-demo-anchor/src/lib.rs 文件替换为以下代码:
use anchor_lang::prelude::*;
declare_id!("YOUR_PROGRAM_ID_HERE");
#[program]
pub mod program_client_demo_anchor {
use super::*;
pub fn create(ctx: Context<Create>, authority: Pubkey) -> Result<()> {
let counter = &mut ctx.accounts.counter;
counter.authority = authority;
counter.count = 0;
Ok(())
}
pub fn increment(ctx: Context<Increment>) -> Result<()> {
let counter = &mut ctx.accounts.counter;
counter.count += 1;
Ok(())
}
}
#[derive(Accounts)]
pub struct Create<'info> {
#[account(init, payer = payer, space = 8 + 40)]
pub counter: Account<'info, Counter>,
#[account(mut)]
pub payer: Signer<'info>,
pub system_program: Program<'info, System>,
}
#[derive(Accounts)]
pub struct Increment<'info> {
#[account(mut, has_one = authority @ ProgramClientDemoError::InvalidAuthority)]
pub counter: Account<'info, Counter>,
pub authority: Signer<'info>,
}
#[account]
pub struct Counter {
pub authority: Pubkey,
pub count: u64,
}
#[error_code]
pub enum ProgramClientDemoError {
#[msg("The provided authority doesn't match the counter account's authority")]
InvalidAuthority,
}
该程序与 create-solana-program 库中的默认程序完全相同。你可以随时修改以满足自己的需求(不过,我们将使用它来编写下一节中的示例测试)。
确保更新 declare_id! 宏中的程序 ID,或者运行 anchor keys sync 来更新程序 ID。准备就绪后,构建程序:
anchor build
设置环境
我们需要安装几个包才能开始。首先,安装 Codama、我们将用到的渲染器以及 Solana Kit:
yarn add @solana/kit codama @codama/renderers-js @codama/nodes-from-anchor @types/node
接着,更新你的 tsconfig.json 文件,添加以下编译器选项:
{
"compilerOptions": {
"types": ["mocha", "chai", "node"],
"typeRoots": ["./node_modules/@types"],
"lib": ["es2015"],
"module": "commonjs",
"target": "ESNext",
"esModuleInterop": true,
"resolveJsonModule": true
}
}
最后,将以下脚本添加到你的 Anchor.toml 文件中的 [scripts] 部分:
generate-clients = "yarn ts-node ./scripts/generate-clients.ts"
生成客户端
环境已设置好,现在我们来创建客户端。创建一个名为 scripts 的新目录,并在其中创建一个名为 generate-clients.ts 的新文件。该文件将包含生成客户端的代码。
将以下代码添加到 scripts/generate-clients.ts:
import { createFromRoot } from 'codama';
import { rootNodeFromAnchor, AnchorIdl } from '@codama/nodes-from-anchor';
import { renderVisitor as renderJavaScriptVisitor } from "@codama/renderers-js";
import anchorIdl from '../target/idl/program_client_demo_anchor.json';
import path from 'path';
const codama = createFromRoot(rootNodeFromAnchor(anchorIdl as AnchorIdl));
const jsClient = path.join(__dirname, "..", "clients", "js");
codama.accept(
renderJavaScriptVisitor(path.join(jsClient, "src", "generated"))
);
这个脚本与 create-solana-program 库中的 generate-clients 脚本非常相似。主要区别在于,我们使用 rootNodeFromAnchor 函数从 Anchor IDL(格式略有不同)创建 Codama 树。然后,我们使用 JavaScript 渲染器为该树生成客户端。
由于我们已在 Anchor.toml 中设置好了,你可以通过运行以下命令来执行脚本并生成程序客户端:
anchor run generate-clients
你会看到一个新的 clients/js/src/generated 目录,里面包含了生成的客户端:

现在你可以使用这些客户端与你的程序进行交互了!
测试客户端
既然我们有了与新的 Solana JS SDK 兼容的 JS 客户端,我们就可以编写测试而无需使用 Anchor 库了。
导航到你的测试文件(tests/program-client-demo-anchor.ts),并清除现有文件的内容。
首先,我们需要从 Solana Kit 和我们的新程序客户端导入必要的依赖项:
import {
appendTransactionMessageInstruction,
Commitment,
CompilableTransactionMessage,
TransactionMessageWithBlockhashLifetime,
Rpc,
RpcSubscriptions,
SolanaRpcApi,
SolanaRpcSubscriptionsApi,
TransactionSigner,
airdropFactory,
createSolanaRpc,
createSolanaRpcSubscriptions,
createTransactionMessage,
generateKeyPairSigner,
getSignatureFromTransaction,
lamports,
pipe,
sendAndConfirmTransactionFactory,
setTransactionMessageFeePayerSigner,
setTransactionMessageLifetimeUsingBlockhash,
signTransactionMessageWithSigners,
KeyPairSigner,
} from '@solana/kit';
import { assert } from 'chai';
import * as programClient from "../clients/js/src/generated";
接下来,我们将创建一些辅助函数,帮助我们快速设置测试环境和处理交易。这里不详细解释它们的工作原理。如果你需要复习新 Solana Kit 中的交易,请查看 转账指南 或 代币指南。
type TestEnvironment = {
rpcClient: RpcClient;
authority: TransactionSigner;
counter: KeyPairSigner;
programClient: typeof programClient;
};
const createTestEnvironment = async (): Promise<TestEnvironment> => {
const rpcClient = createDefaultSolanaClient();
const authority = await generateKeyPairSignerWithSol(rpcClient);
const counter = await generateKeyPairSigner();
return { rpcClient, authority, counter, programClient };
};
type RpcClient = {
rpc: Rpc<SolanaRpcApi>;
rpcSubscriptions: RpcSubscriptions<SolanaRpcSubscriptionsApi>;
};
const createDefaultSolanaClient = (): RpcClient => {
const rpc = createSolanaRpc('http://127.0.0.1:8899');
const rpcSubscriptions = createSolanaRpcSubscriptions('ws://127.0.0.1:8900');
return { rpc, rpcSubscriptions };
};
const generateKeyPairSignerWithSol = async (
rpcClient: RpcClient,
putativeLamports: bigint = 1_000_000_000n
) => {
const signer = await generateKeyPairSigner();
await airdropFactory(rpcClient)({
recipientAddress: signer.address,
lamports: lamports(putativeLamports),
commitment: 'confirmed',
});
return signer;
};
const createDefaultTransaction = async (
testEnv: TestEnvironment
) => {
const { rpcClient, authority: feePayer } = testEnv;
const { value: latestBlockhash } = await rpcClient.rpc
.getLatestBlockhash()
.send();
return pipe(
createTransactionMessage({ version: 0 }),
(tx) => setTransactionMessageFeePayerSigner(feePayer, tx),
(tx) => setTransactionMessageLifetimeUsingBlockhash(latestBlockhash, tx)
);
};
const signAndSendTransaction = async (
rpcClient: RpcClient,
transactionMessage: CompilableTransactionMessage &
TransactionMessageWithBlockhashLifetime,
commitment: Commitment = 'confirmed'
) => {
const signedTransaction =
await signTransactionMessageWithSigners(transactionMessage);
const signature = getSignatureFromTransaction(signedTransaction);
await sendAndConfirmTransactionFactory(rpcClient)(signedTransaction, {
commitment,
});
return signature;
};
请注意,我们将 programClient 定义为 TestEnvironment 类型的一个属性。我们将在下面的测试中使用它。
继续添加两个测试:一个用于初始化新的计数器 PDA,另一个用于递增它:
describe('it creates a new counter account', () => {
let testEnv: TestEnvironment;
before(async () => {
testEnv = await createTestEnvironment();
})
it("Is created!", async () => {
const createIx = testEnv.programClient.getCreateInstruction({
authority: testEnv.authority.address,
counter: testEnv.counter,
payer: testEnv.authority,
});
await pipe(
await createDefaultTransaction(testEnv),
(tx) => appendTransactionMessageInstruction(createIx, tx),
(tx) => signAndSendTransaction(testEnv.rpcClient, tx)
);
let counterAccount = await testEnv.programClient.fetchCounter(testEnv.rpcClient.rpc, testEnv.counter.address);
assert.strictEqual(counterAccount.data.authority, testEnv.authority.address);
assert.strictEqual(counterAccount.data.count, 0n);
});
it("Is incremented!", async () => {
const incrementIx = testEnv.programClient.getIncrementInstruction({
authority: testEnv.authority,
counter: testEnv.counter.address
});
await pipe(
await createDefaultTransaction(testEnv),
(tx) => appendTransactionMessageInstruction(incrementIx, tx),
(tx) => signAndSendTransaction(testEnv.rpcClient, tx)
);
let counterAccount = await testEnv.programClient.fetchCounter(testEnv.rpcClient.rpc, testEnv.counter.address);
assert.strictEqual(counterAccount.data.authority, testEnv.authority.address);
assert.strictEqual(counterAccount.data.count, 1n);
});
});
如你所见,programClient 包含创建指令和获取与程序关联的账户的方法!非常方便。浏览智能感知或浏览生成的客户端,查看还可用的其他内容,并扩展你的测试!
最后,使用以下命令运行测试:
anchor test
你应该会看到类似这样的输出:
it creates a new counter account
✔ Is created! (470ms)
✔ Is incremented! (487ms)
2 passing (1s)
做得好!
自定义 Codama
尽管 Codama 在生成客户端方面非常强大且方便,但可以想象,针对特定需求你可能会遇到一些限制。Codama 使用 Kinobi 类。Kinobi 类 包含一个 update 函数,允许你在生成客户端之前对访问者进行修改。这是 Metaplex 在 Metadata 程序客户端生成器中实现该功能的一个示例。
总结
在本指南中,你创建了使用新 Solana Kit 的自定义程序客户端。这应该能让使用新库进行构建更加高效且易于管理。如果你正在构建复杂的程序、为客户构建多个程序,或者只是想节省时间,Codama 都可以成为你工具箱中的宝贵工具。
这种方法允许高度定制,可以无缝集成各种 Quicknode 插件和 API。我们希望你已经看到这个过程提供了很大的灵活性,所以尽情发挥你的创造力,构建一些了不起的东西吧!我们期待你的成果——在 Quicknode Discord 或 Twitter 上联系我们,告诉我们你构建了什么!
资源
- Solana Web3.js 2.0 博客
- Solana Kit GitHub
- Solana Web3.js 重写原则
- 🎥 新 Solana Web3.js 2.0 SDK 介绍
- create-solana-program 库
- Codama 文档
- Anchor 文档
我们 ❤️ 反馈!
告诉我们 如果你有任何反馈或对新的主题有请求。我们期待你的来信。
- 原文链接: quicknode.com/guides/sol...
- 登链社区 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~