指令结构

摘要

一个指令包含 3 个字段:program_id(要调用的程序)、 accounts(带有 is_signer/is_writable 标志的 AccountMeta 列表)和 data (程序解析的数据字节数组)。

指令结构

一个 Instruction 包含三个字段:

  • program_id:被调用程序的 ID
  • accounts:一个 账户元数据 数组
  • data:包含额外 数据 的字节数组,供指令使用。
Instruction struct
pub struct Instruction {
/// Pubkey of the program that executes this instruction.
pub program_id: Pubkey,
/// Metadata describing accounts that should be passed to the program.
pub accounts: Vec<AccountMeta>,
/// Opaque data passed to the program for its own interpretation.
pub data: Vec<u8>,
}

程序 ID

指令的 program_id 是包含该指令执行逻辑的程序的公钥地址。运行时会使用此字段将指令路由到正确的程序进行处理。

账户元数据

指令的 accounts 数组是一个有序的 AccountMeta 结构体列表。每个指令交互的账户都必须提供元数据。validator 会利用这些元数据判断哪些交易可以并行运行。写入不同账户的交易可以并行执行。

下图展示了包含单个指令的交易。该指令的 accounts 数组包含了两个账户的元数据。

包含一个指令的交易。该指令的 accounts 数组中有两个 AccountMeta 结构体。包含一个指令的交易。该指令的 accounts 数组中有两个 AccountMeta 结构体。

每个 AccountMeta 有三个字段:

  • pubkey:账户的公钥地址
  • is_signer:如果账户需要签名交易,则设置为 true
  • is_writable:如果指令会修改账户数据,则设置为 true

要了解某个指令需要哪些账户,包括哪些账户必须是可写、只读或需要签署交易,必须参考该指令的具体实现,也就是由程序定义的实现。

AccountMeta
pub struct AccountMeta {
/// An account's public key.
pub pubkey: Pubkey,
/// True if an `Instruction` requires a `Transaction` signature matching `pubkey`.
pub is_signer: bool,
/// True if the account data or metadata may be mutated during program execution.
pub is_writable: bool,
}

数据

指令的 data 字段是一个字节数组,用于告知程序要调用哪个函数,并为该函数提供参数。数据通常以一个判别符或索引字节开头,用于标识目标函数,后面跟着序列化的参数。编码格式由每个程序自行定义(例如 Borsh 序列化或自定义布局)。

常见的编码约定:

  • 核心程序(System、Stake、Vote):使用 Bincode 序列化的枚举变体索引,后跟序列化参数。
  • Anchor 程序:使用 8 字节判别符(即 SHA-256 哈希值的前 8 个字节,基于 "global:<function_name>")后跟 Borsh 序列化参数。

运行时不会解析 data 字段。它会原样传递给程序的 process_instruction 入口点。

已编译指令

当指令被序列化到交易消息中时,它们会变成 CompiledInstruction 结构体,将所有公钥替换为消息中 account_keys 数组中的紧凑整数索引。

示例:SOL 转账指令

下面的示例展示了一个 SOL 转账指令的结构。

import { generateKeyPairSigner, lamports } from "@solana/kit";
import { getTransferSolInstruction } from "@solana-program/system";
// Generate sender and recipient keypairs
const sender = await generateKeyPairSigner();
const recipient = await generateKeyPairSigner();
// Define the amount to transfer
const LAMPORTS_PER_SOL = 1_000_000_000n;
const transferAmount = lamports(LAMPORTS_PER_SOL / 100n); // 0.01 SOL
// Create a transfer instruction for transferring SOL from sender to recipient
const transferInstruction = getTransferSolInstruction({
source: sender,
destination: recipient.address,
amount: transferAmount
});
console.log(JSON.stringify(transferInstruction, null, 2));
Console
Click to execute the code.

下面的代码展示了前面代码片段的输出。不同 SDK 的格式可能有所不同,但请注意,每个指令都包含相同的三项必要信息:program_idaccountsdata

{
"accounts": [
{
"address": "Hu28vRMGWpQXN56eaE7jRiDDRRz3vCXEs7EKHRfL6bC",
"role": 3,
"signer": {
"address": "Hu28vRMGWpQXN56eaE7jRiDDRRz3vCXEs7EKHRfL6bC",
"keyPair": {
"privateKey": {},
"publicKey": {}
}
}
},
{
"address": "2mBY6CTgeyJNJDzo6d2Umipw2aGUquUA7hLdFttNEj7p",
"role": 1
}
],
"programAddress": "11111111111111111111111111111111",
"data": {
"0": 2,
"1": 0,
"2": 0,
"3": 0,
"4": 128,
"5": 150,
"6": 152,
"7": 0,
"8": 0,
"9": 0,
"10": 0,
"11": 0
}
}

下面的示例展示了如何手动构建转账指令。( Expanded Instruction 选项卡在功能上等同于 Instruction 选项卡。)

实际开发中,通常无需手动构建 Instruction。 大多数程序都提供带有辅助函数的客户端库,能够帮你自动生成指令。如果没有可用的库,你也可以手动构建指令。

const transferAmount = 0.01; // 0.01 SOL
const transferInstruction = getTransferSolInstruction({
source: sender,
destination: recipient.address,
amount: transferAmount * LAMPORTS_PER_SOL
});

Is this page helpful?

Table of Contents

Edit Page

管理者

©️ 2026 Solana 基金会版权所有
取得联系