Solana Anchor 程序接口定义语言(IDL)
本文将带你探索 Anchor 框架中的 IDL(接口定义语言),这是一个自动生成的 JSON 文件,用于描述 Solana 程序的接口。我们将通过示例展示 IDL 的作用,解释 TypeScript 测试如何调用程序函数。
什么是 IDL?
IDL(Interface Definition Language)是 Anchor 生成的程序接口定义,存储在 target/idl/<program_name>.json 中,类似于 Solidity 的 ABI。它列出了程序的公共函数(instructions)、参数(args)和账户要求(accounts),为客户端(如 TypeScript)提供与链上程序交互的蓝图。
示例 1:函数调用与 IDL 映射
初始化项目
创建一个新项目:
anchor init anchor-function-tutorial
cd anchor-function-tutorial
启动本地验证器:
solana-test-validator
修改函数名
将默认的 initialize 函数改为 boaty_mc_boatface。编辑 programs/anchor-function-tutorial/src/lib.rs:
use anchor_lang::prelude::*;
declare_id!("3ytdGXdSqfQ5Z9NB9c5bGbkNqkzVPkpijs4hj4BeoAa7");
#[program]
pub mod anchor_function_tutorial {
use super::*;
pub fn boaty_mc_boatface(ctx: Context<Initialize>) -> Result<()> {
msg!("Boaty says hi!");
Ok(())
}
}
#[derive(Accounts)]
pub struct Initialize {}
更新测试
编辑 tests/anchor-function-tutorial.ts:
it("Call boaty mcboatface", async () => {
const tx = await program.methods.boatyMcBoatface().rpc();
console.log("Your transaction signature", tx);
});
运行测试:
anchor test --skip-local-validator
测试如何定位函数?
Anchor 在构建时生成 IDL(target/idl/anchor_function_tutorial.json):
{
"version": "0.1.0",
"name": "anchor_function_tutorial",
"instructions": [
{
"name": "boatyMcBoatface",
"accounts": [],
"args": []
}
],
"metadata": {
"address": "3ytdGXdSqfQ5Z9NB9c5bGbkNqkzVPkpijs4hj4BeoAa7"
}
}
- 解析:
- "instructions":列出公共函数,类似 Solidity 的外部函数。
- TypeScript 的 program.methods 根据 IDL 映射函数名,生成调用逻辑。
Anchor 0.30.x 后的命名规则
如果在 Anchor 0.30.0 及以上版本(如 0.30.1):
- 函数名:保留 Rust 的蛇形命名(如 boaty_mc_boatface),不再转换为驼峰式(如 boatyMcBoatface)。
- 原因:提升 Rust 代码与 IDL 的一致性,响应社区反馈。
- 参数和账户名:仍使用驼峰式(如 firstArg、anotherSigner),适配 JavaScript/TypeScript 惯例。
测试调用注意:
await program.methods.boatyMcBoatface(); // 正确,客户端仍需驼峰式
await program.methods.boaty_mc_boatface(); // 可行但不推荐
- 经验建议:尽管 IDL 使用蛇形命名,建议在前端保持驼峰式调用,以符合生态习惯。
示例 2:带参数的算术函数
添加加减法函数
更新 lib.rs,实现加法和减法(Solana 不支持直接返回值,需用 msg! 输出):
use anchor_lang::prelude::*;
declare_id!("3ytdGXdSqfQ5Z9NB9c5bGbkNqkzVPkpijs4hj4BeoAa7");
#[program]
pub mod anchor_function_tutorial {
use super::*;
pub fn add(ctx: Context<Empty>, a: u64, b: u64) -> Result<()> {
let sum = a + b;
msg!("Sum is {}", sum);
Ok(())
}
pub fn sub(ctx: Context<Empty>, a: u64, b: u64) -> Result<()> {
let difference = a - b;
msg!("Difference is {}", difference);
Ok(())
}
}
#[derive(Accounts)]
pub struct Empty {}
更新测试
编辑 tests/anchor-function-tutorial.ts:
it("Should add", async () => {
const tx = await program.methods.add(new anchor.BN(1), new anchor.BN(2)).rpc();
console.log("Your transaction signature", tx);
});
it("Should subtract", async () => {
const tx = await program.methods.sub(new anchor.BN(10), new anchor.BN(3)).rpc();
console.log("Your transaction signature", tx);
});
运行测试:
anchor test --skip-local-validator
生成的 IDL
"instructions": [
{
"name": "add",
"accounts": [],
"args": [
{
"name": "a",
"type": "u64"
},
{
"name": "b",
"type": "u64"
}
]
},
{
"name": "sub",
"accounts": [],
"args": [
{
"name": "a",
"type": "u64"
},
{
"name": "b",
"type": "u64"
}
]
}
],
账户结构体详解
Context 与结构体命名
函数中的 ctx: Context 指定账户上下文,T 是自定义结构体,名称(如 Initialize 或 Empty)任意,只要与函数签名一致。
#[derive(Accounts)] 的作用
这是 Anchor 的 Rust 属性宏,解析结构体字段并映射到 IDL 的 accounts。空结构体(如 Empty)生成空的 accounts 数组。
非空账户示例
更新 lib.rs:
use anchor_lang::prelude::*;
declare_id!("3ytdGXdSqfQ5Z9NB9c5bGbkNqkzVPkpijs4hj4BeoAa7");
#[program]
pub mod anchor_function_tutorial {
use super::*;
pub fn non_empty_account_example(ctx: Context<NonEmptyAccountExample>) -> Result<()> {
msg!("Signers present");
Ok(())
}
}
#[derive(Accounts)]
pub struct NonEmptyAccountExample<'info> {
signer: Signer<'info>,
another_signer: Signer<'info>,
}
构建:
anchor build
生成的 IDL 中的 instructions 部分为:
"instructions": [
{
"name": "nonEmptyAccountExample",
"accounts": [
{
"name": "signer",
"isMut": false,
"isSigner": true
},
{
"name": "anotherSigner",
"isMut": false,
"isSigner": true
}
],
"args": []
}
]
- 变化:
- 函数名:nonEmptyAccountExample(驼峰式)。
- 账户名:signer 和 anotherSigner(another_signer 转为驼峰式)。
- Signer:表示交易签名者,类似 Ethereum 的 tx.origin。
示例 3:综合应用
完整代码
use anchor_lang::prelude::*;
declare_id!("3ytdGXdSqfQ5Z9NB9c5bGbkNqkzVPkpijs4hj4BeoAa7");
#[program]
pub mod anchor_function_tutorial {
use super::*;
pub fn function_a(ctx: Context<NonEmptyAccountExample>) -> Result<()> {
msg!("Function A called");
Ok(())
}
pub fn function_b(ctx: Context<Empty>, first_arg: u64) -> Result<()> {
msg!("Function B with arg {}", first_arg);
Ok(())
}
}
#[derive(Accounts)]
pub struct NonEmptyAccountExample<'info> {
signer: Signer<'info>,
another_signer: Signer<'info>,
}
#[derive(Accounts)]
pub struct Empty {}
生成的 IDL
{
"version": "0.1.0",
"name": "anchor_function_tutorial",
"instructions": [
{
"name": "functionA",
"accounts": [
{
"name": "signer",
"isMut": false,
"isSigner": true
},
{
"name": "anotherSigner",
"isMut": false,
"isSigner": true
}
],
"args": []
},
{
"name": "functionB",
"accounts": [],
"args": [
{
"name": "firstArg",
"type": "u64"
}
]
}
]
}
- 映射:
- functionA:无参数,账户来自 NonEmptyAccountExample。
- functionB:参数 first_arg 转为 firstArg,账户为空。
教程到此结束,更多,请,,https://t.me/+_QibemQqIIg1OTY1