Solana 学习开发之旅

2025年04月03日更新 33 人订阅
原价: ¥ 20 限时优惠
专栏简介 【Solana】使用 CLI 创建 SPL 标准的 Token 以及基础使用 【Solana】完善 SPL Token 名称和 Logo 【Solana】创建 SPL 标准的 NFT 以及完善 metadata 【Solana】一些基本的js脚本 【Solana】Anchor 框架使用笔记 【Solana】Anchor 示例:通过 CPI 实现 Sol 转账与手续费收取 Solana Hello World: 安装与开发指南 Solana 与 Rust 算术入门—从 Solidity 到 Anchor Solana Anchor 程序接口定义语言(IDL) Solana Anchor 框架下的 Require 与自定义错误 Solana 程序:支持升级与无构造函数实现 Solidity 开发者必知的 Rust 语法基础 Rust 的独特语法解析 Rust 类函数宏解析 Rust 结构体、属性宏与自定义派生宏 Rust 与 Solana 中的可见性及模块化复用 Solana 中的时钟与其他区块变量 Solana 系统变量详解 Solana 日志、事件日志与历史交易查询 Solana 中的Tx.origin、msg.sender 和 onlyOwner Solana 计算单元与交易费用概述 Solana 与 Anchor 中的账户初始化 Solana 计数器教程:账户数据的读写 使用 Solana Web3.js 和 Anchor 读取账户数据 在 Solana 中实现映射表与嵌套映射表 Solana 存储成本、最大容量与账户调整 在 Anchor 中读取账户余额:Solana 的 address(account).balance Solana 中的函数修饰符与 Fallback 函数:为何不存在 Solana 中的 SOL 转移与分割:取代 msg.value 的设计 使用不同签名者修改账户:Solana 中的权限控制 PDA 与密钥对账户:Solana 中的地址与权限模型 Anchor 中的 init_if_needed 与重新初始化攻击防范 Solana 中的 Multicall:批处理交易和交易大小的限制 Solana 中的 Owner 和 Authority 删除和关闭 Solana 中的账户和程序 在 Anchor 中的 #[derive(Accounts)] 不同类型的账户 在链上读取另一个 Anchor 程序的账户数据 Anchor 中的跨程序调用

Solana Anchor 程序接口定义语言(IDL)

  • 0xE
  • 发布于 2025-03-21 15:06
  • 阅读 1448

本文将带你探索 Anchor 框架中的 IDL(接口定义语言),这是一个自动生成的 JSON 文件,用于描述 Solana 程序的接口。我们将通过示例展示 IDL 的作用,解释 TypeScript 测试如何调用程序函数。

本文将带你探索 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&lt;Initialize>) -> Result&lt;()> {
        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&lt;Empty>, a: u64, b: u64) -> Result&lt;()> {
        let sum = a + b;
        msg!("Sum is {}", sum);
        Ok(())
    }

    pub fn sub(ctx: Context&lt;Empty>, a: u64, b: u64) -> Result&lt;()> {
        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<T> 与结构体命名

函数中的 ctx: Context<T> 指定账户上下文,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&lt;NonEmptyAccountExample>) -> Result&lt;()> {
        msg!("Signers present");
        Ok(())
    }
}

#[derive(Accounts)]
pub struct NonEmptyAccountExample&lt;'info> {
    signer: Signer&lt;'info>,
    another_signer: Signer&lt;'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&lt;NonEmptyAccountExample>) -> Result&lt;()> {
        msg!("Function A called");
        Ok(())
    }

    pub fn function_b(ctx: Context&lt;Empty>, first_arg: u64) -> Result&lt;()> {
        msg!("Function B with arg {}", first_arg);
        Ok(())
    }
}

#[derive(Accounts)]
pub struct NonEmptyAccountExample&lt;'info> {
    signer: Signer&lt;'info>,
    another_signer: Signer&lt;'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"
        }
      ]
    }
  ]
}
点赞 0
收藏 0
分享
本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

0 条评论

请先 登录 后评论