Solana 生态系统充满活力,但许多程序尚未开源。Syndica在 2024 年 2 月编制的统计数据显示,按计算单元计算,Solana 排名前 100 的程序中,近 50% 发布了其接口定义语言 (IDL)。然而,对于排名前 1000 的程序,这一数字下降到只有 20%。此外,即使已发布的 IDL 也并不总是可靠的。发布的 IDL 过时且与部署的链上程序不匹配的情况并不少见。
作为审计人员和安全研究人员,当我们发现可能导致漏洞的有趣模式时,我们通常会在其他程序中寻找类似的弱点。然而,如果没有源代码或准确的 IDL,这个过程通常仅限于基本的 GitHub 搜索,经常会发现无人维护的项目。
大多数 Solana 程序都是用 Rust 编写的,并编译为 Solana 字节码格式 (sBPF),这是一种基于 eBPF 的格式。对编译后的 Rust 进行逆向工程具有挑战性,而与 sBPF 相关的逆向工程工具链仍在开发中。这种不透明性不仅阻碍了恶意行为者,而且还减缓了白帽黑客和安全研究人员识别和负责任地披露漏洞的工作。
要分析任何闭源 Solana 程序(无论是动态还是静态),基本前提是了解如何与其交互。这意味着了解它的指令、每条指令所需的账户以及这些账户的属性(如签名者或可写状态)。
为了应对这些挑战,我们的安全研究员齐秦领导了这项工作并开发了一个名为IDL Guesser的原型工具。该工具旨在直接从闭源 Solana 程序二进制文件中自动恢复指令定义、所需帐户(包括签名者/可写标志)和参数信息。
该博客概述了 IDL Guesser 背后的方法并讨论了未来改进的潜在领域。
由于 Solana 开发的大部分内容都使用了 Anchor 框架(IDL 的概念也源于此),因此 IDL Guesser 目前专注于基于 Anchor 的程序。Anchor 通过为常见任务和检查提供宏和辅助函数,大大简化了开发。至关重要的是,这会导致编译输出中出现可预测的标准化代码结构,我们可以通过模式匹配将其用于分析。
为了进行调试,Anchor CLI 甚至提供了一个anchor expand
\命令,该命令会显示由其宏生成的代码。检查此扩展代码可以深入了解 Anchor 所采用的模式,从而指导我们的逆向工程工作。
我们来看看一个典型的 Anchor 程序经过宏扩展后的入口点结构:
#[no_mangle]
pub unsafe extern "C" fn entrypoint(input: *mut u8) -> u64 {
let (program_id, accounts, instruction_data) = unsafe {
::solana_program::entrypoint::deserialize(input)
};
match entry(&program_id, &accounts, &instruction_data) {
Ok(()) => ::solana_program::entrypoint::SUCCESS,
Err(error) => error.into(),
}
}
pub fn entry<'info>(
program_id: &Pubkey,
accounts: &'info [AccountInfo<'info>],
data: &[u8],
) -> anchor_lang::solana_program::entrypoint::ProgramResult {
try_entry(program_id, accounts, data)
.map_err(|e| {
e.log();
e.into()
})
}
fn try_entry<'info>(
program_id: &Pubkey,
accounts: &'info [AccountInfo<'info>],
data: &[u8],
) -> anchor_lang::Result<()> {
if *program_id != ID {
return Err(anchor_lang::error::ErrorCode::DeclaredProgramIdMismatch.into());
}
if data.len() < 8 {
return Err(anchor_lang::error::ErrorCode::InstructionMissing.into());
}
dispatch(program_id, accounts, data)
}
fn dispatch<'info>(
program_id: &Pubkey,
accounts: &'info [AccountInfo<'info>],
data: &[u8],
) -> anchor_lang::Result<()> {
let mut ix_data: &[u8] = data;
let sighash: [u8; 8] = {
let mut sighash: [u8; 8] = [0; 8];
sighash.copy_from_slice(&ix_data[..8]);
ix_data = &ix_data[8..];
sighash
};
use anchor_lang::Discriminator;
match sighash {
instruction::InitializeConfig::DISCRIMINATOR => {
__private::__global::initialize_config(program_id, accounts, ix_data)
}
instruction::InitializePool::DISCRIMINATOR => {
__private::__global::initialize_pool(program_id, accounts, ix_data)
}
// ... other instructions
}
}
程序首先对原始输入进行反序列化。它在进入函数之前执行基本检查(例如验证program_id
\并确保instruction_data
\至少有 8 个字节长)dispatch
\。在 中dispatch
\, 的前 8 个字节instruction_data
\被解释为指令鉴别符。该鉴别符确定应执行哪个特定的指令处理程序函数。
根据 Anchor 的文档,这个 8 字节鉴别器是从指令的命名空间和名称(例如global:initialize_config
\)中派生出来的,方法是取其 SHA-256 哈希的前 8 个字节。IDL Guesser 不会尝试从编译后的代码中提取这些原始鉴别器字节(这可能很复杂),而是采用一种更简单的方法:它首先专注于提取指令名称,然后使用 Anchor 的标准哈希方法计算相应的鉴别器。
我们如何找到指令名称及其对应的处理程序函数?Anchor 提供了另一种有用的模式。考虑一个典型指令处理程序的开头:
pub fn initialize_config<'info>( /* ... */ ) -> anchor_lang::Result<()> {
// Log the instruction name - a key pattern for us!
::solana_program::log::sol_log("Instruction: InitializeConfig");
// Deserialize instruction-specific parameters
let ix = instruction::InitializeConfig::deserialize(&mut &__ix_data[..])
.map_err(|_| /* ... */ )?;
let instruction::InitializeConfig { /* ... parameters ... */ } = ix;
// Process accounts via try_accounts
let mut __bumps = /* ... */;
let mut __reallocs = /* ... */;
let mut __remaining_accounts: &[AccountInfo] = __accounts;
let mut __accounts = InitializeConfig::try_accounts(
__program_id,
&mut __remaining_accounts,
__ix_data,
&mut __bumps,
&mut __reallocs,
)?;
// ...
}
Anchorsol_log
\在每个处理程序的开头插入一个调用,记录指令的名称(例如“Instruction: InitializeConfig”\)以供日志解析。此日志记录提供了我们可以在编译后的二进制文件中搜索的独特签名。
用于记录指令名称*InitializeConfig 的*****sol_log\系统调用序列。
在汇编级别(如上所示),此日志调用通常会转换为特定的lddw
\、mov64
\和call
\指令设置,sol_log
\以使用指令名称字符串调用系统调用。
通过识别这些模式,IDL Guesser 可以可靠地定位指令处理程序的入口点并提取其名称。
在初始日志记录和参数反序列化(通常由编译器内联)之后,处理程序通常会调用相应的try_accounts
\函数。此函数负责解析和验证指令所需的帐户。
参数反序列化之后调用try_accounts\函数(此处为sub_662B0\)。
让我们检查一下Accounts
\结构和生成的try_accounts
\函数initialize_config
\:
// Account definition
#[derive(Accounts)]
pub struct InitializeConfig<'info> {
#[account(init, payer = funder, space = WhirlpoolsConfig::LEN)]
pub config: Account<'info, WhirlpoolsConfig>, // To be initialized
#[account(mut)]
pub funder: Signer<'info>, // Must be mutable and a signer
pub system_program: Program<'info, System>, // System program
}
// Generated try_accounts function (simplified)
fn try_accounts( /* ... */ ) -> anchor_lang::Result<Self> {
// Check if enough accounts provided
if __accounts.is_empty() {
// Error 3005
return Err(anchor_lang::error::ErrorCode::AccountNotEnoughKeys.into());
}
// Process 'config' account (index 0) - checks applied later
let config = &__accounts[0];
*__accounts = &__accounts[1..];
// Process 'funder' account using Signer's try_accounts
let funder: Signer = anchor_lang::Accounts::try_accounts(/* ... */)
.map_err(|e| e.with_account_name("funder"))?; // Adds "funder" to error message
// Process 'system_program' account
let system_program: Program<System> = anchor_lang::Accounts::try_accounts(/* ... */)
.map_err(|e| e.with_account_name("system_program"))?; // Adds "system_program"
// Apply constraints checks
if !config.is_writable { // Check defined indirectly via init
// Error 2000
return Err(anchor_lang::error::ErrorCode::ConstraintMut.into().with_account_name("config"));
}
if !config.is_signer { // Check defined indirectly via init
// Error 2002
return Err(anchor_lang::error::ErrorCode::ConstraintSigner.into().with_account_name("config"));
}
// ... other checks like rent exemption (Error 2005), owner, etc.
if !funder.is_writable { // Check defined by #[account(mut)]
// Error 2000
return Err(anchor_lang::error::ErrorCode::ConstraintMut.into().with_account_name("funder"));
}
// ... checks for funder.is_signer happen inside its own try_accounts
Ok(Self { config, funder, system_program })
}
该try_accounts
\函数执行几个关键操作:
Account
\、Signer
\、等)对其进行解析。如果在嵌套调用(如 for )Program
\中解析失败,Anchor 会方便地将帐户名称(例如“funder”)附加到错误消息中。这使我们能够通过查找这些特定的错误处理模式来提取帐户名称。*try_accounts
*****funder
\mut
\、signer
\、has_one
\、seeds
\、 、租金豁免等) 。重要的是,每个约束违规通常映射到一个唯一的Anchor (例如,is 2000\、is 2002\)。*owner
**Accounts
**ErrorCode
**ConstraintMut
**ConstraintSigner
*检查需要初始化的帐户是否有足够的帐户密钥,如果失败则分支到错误3005\。
约束检查条件跳转,导致特定的错误代码如2000\(ConstraintMut\)或2002\(ConstraintSigner\)。
通过分析函数的控制流图(CFG)try_accounts
\,具体来说是遵循“快乐路径”(成功执行),IDL Guesser 可以拼凑出所需的账户:
虽然提取指令名称和账户详细信息依赖于相对不同的模式(日志字符串、错误代码、特定函数调用),但恢复有关指令参数的信息更加困难。
Anchor 通常不会生成针对单个参数反序列化失败的详细错误消息。这意味着 Rust 源代码中定义的原始参数名称通常会在编译期间丢失。此外,负责从切片中顺序反序列化参数的代码ix_data
\通常会由编译器优化和内联,这使得可靠的汇编级模式匹配非常困难。
ix_data
\更有希望的未来方向可能涉及符号执行,通过分析如何使用来确定预期的字节长度以及可能确定每个参数的类型。
然而,在 IDL Guesser 原型中,采用了一种利用动态分析的替代且更简单的方法:由于交易大小限制,Solana 指令数据通常很短,因此我们可以迭代探测处理程序函数。通过稍微增加模拟输入数据的长度并观察执行跟踪中的变化(例如,通过之前失败的检查),我们可能会在新的反序列化步骤成功时推断出边界,并可能推断出参数的类型。
这种迭代反馈循环技术也用于重建账户的内部布局。具体细节这里就不展开了,感兴趣的读者可以去源代码中探索相关的实现。此外,它还可能用于验证或完善恢复的指令和账户信息。
识别出这些模式后,实施过程涉及反汇编 sBPF 字节码并对指令序列和 CFG 执行模式匹配。我们以现有solana-sbpf
\项目(参见static_analysis.rs)为基础,为 Solana 程序分析奠定了基础。
我们对基本静态分析实现进行了一些修改。原始版本倾向于在每次函数调用后过度拆分基本块。我们对此进行了调整,以创建更大、更易于管理的块。此外,我们还为系统调用(如abort
\或panic
\)添加了特殊处理。这些更改使 CFG 更加精确,从而简化了模式匹配过程。
完整的实现是开源的,可以在IDLGuesser 存储库中获取。
当前代码处理许多常见场景,但也包括一些特殊情况的逻辑,例如UncheckedAccount
\和Sysvar
\帐户。
这些逻辑try_accounts
\通常由编译器内联,从而创建类似于init
帐户的模式。然而,挑战仍然存在,特别是对于多个连续UncheckedAccount
实例或更复杂的结构(如可选帐户和嵌套帐户上下文),此原型尚未完全处理。
IDL Guesser 展示了一种可行的方法,利用框架的代码生成模式和简单的动态分析,从基于 Anchor 的闭源 Solana 程序中恢复基本结构信息(具有相应帐户和参数信息的指令)。虽然原型有局限性,在复杂情况下可能需要手动进行逆向工程并与链上数据进行交叉引用,但它成功地为大量程序恢复了类似 IDL 的信息。
我们发现此功能很有用,可以更广泛地分析交易数据,并有助于自动扫描与帐户限制相关的基本漏洞(例如缺少签名者检查)。通过揭示闭源程序的内部工作原理,我们希望 IDL Guesser 等工具能够为保护 Solana 生态系统做出贡献。
返回与提供的公钥的账户相关的所有信息,和以太坊 eth_getAccount类似
curl https://api.devnet.solana.com -s -X \
POST -H "Content-Type: application/json" -d '
{
"jsonrpc": "2.0",
"id": 1,
"method": "getAccountInfo",
"params": [
"8TKG1ez28ZYzNgGTein6Pc97yvxWwnHecVL9ZhdZTxd5",
{
"encoding": "base58"
}
]
}
'
返回
{
"jsonrpc": "2.0",
"result": {
"context": {
"apiVersion": "2.2.3",
"slot": 370321974
},
"value": {
"data": [
"",
"base58"
],
"executable": false,
"lamports": 4994414840,
"owner": "11111111111111111111111111111111",
"rentEpoch": 18446744073709551615,
"space": 0
}
},
"id": 1
}
返回所提供公钥的账户的 Lamport 余额, 和以太坊eth_getBalance类似
curl https://api.devnet.solana.com -s -X \
POST -H "Content-Type: application/json" -d '
{
"jsonrpc": "2.0",
"id": 1,
"method": "getBalance",
"params": [
"8TKG1ez28ZYzNgGTein6Pc97yvxWwnHecVL9ZhdZTxd5"
]
}
'
返回
{
"jsonrpc": "2.0",
"result": {
"context": {
"apiVersion": "2.2.3",
"slot": 370322177
},
"value": 4994414840
},
"id": 1
}
返回账本中已确认区块的身份和交易信息,和以太坊 eth_getBlockByNumber类似
curl https://api.devnet.solana.com -s -X \
POST -H "Content-Type: application/json" -d '
{
"jsonrpc": "2.0",
"id": 1,
"method": "getBlock",
"params": [
358300848,
{
"encoding": "json",
"maxSupportedTransactionVersion": 0,
"transactionDetails": "full",
"rewards": false
}
]
}
'
如果类似如下提示,这通常意味着你尝试访问的特定槽(Slot)存在问题,具体可能是该槽被跳过或者在长期存储中缺失
{
"jsonrpc": "2.0",
"error": {
"code": -32009,
"message": "Slot 100000430 was skipped, or missing in long-term storage"
},
"id": 1
}
{
"jsonrpc": "2.0",
"result": {
"blockHeight": 346328196,
"blockTime": 1738488135,
"blockhash": "HGrdL4Z5vEvQVWchG2pnJEQ5dMxQhqiXncpL6GJvZimx",
"parentSlot": 358300847,
"previousBlockhash": "3QuyTMBRAK5PhLXehDCTMH4zgnfn7xgZnHG3fBnZFsiQ",
"transactions": [
...
{
"meta": {
"computeUnitsConsumed": 2100,
"err": null,
"fee": 5000,
"innerInstructions": [],
"loadedAddresses": {
"readonly": [],
"writable": []
},
"logMessages": [
"Program Vote111111111111111111111111111111111111111 invoke [1]",
"Program Vote111111111111111111111111111111111111111 success"
],
"postBalances": [
912514572484583,
8402660357128602,
1
],
"postTokenBalances": [],
"preBalances": [
912514572489583,
8402660357128602,
1
],
"preTokenBalances": [],
"rewards": null,
"status": {
"Ok": null
}
},
"transaction": {
"message": {
"accountKeys": [
"dv4ACNkpYPcE3aKmYDqZm9G5EB3J4MRoeE7WNDRBVJB",
"23AoPQc3EPkfLWb14cKiWNahh1H9rtb3UBk8gWseohjF",
"Vote111111111111111111111111111111111111111"
],
"header": {
"numReadonlySignedAccounts": 0,
"numReadonlyUnsignedAccounts": 1,
"numRequiredSignatures": 1
},
"instructions": [
{
"accounts": [
1,
0
],
"data": "67MGn8CZqesiDCbWWYBQre79LVuia8eBTRNppRhYeXWhLFMWQsHQrrLxehkcbeCZ9ncYWSaBVdzbL6iUumunuVXWuVgnYAZ6oLZVMsEUZohcJMkAa7sAhbmGAT4ZUjfcZd7LH99cRPUwUZu7HUaG5Q8Y7TM98PR6bJJoySwh8QpuxtWzBqpLAH4nv2cDY946EQPmXS5zvB",
"programIdIndex": 2,
"stackHeight": null
}
],
"recentBlockhash": "3QuyTMBRAK5PhLXehDCTMH4zgnfn7xgZnHG3fBnZFsiQ"
},
"signatures": [
"56uXBR4rgLNbDCtCr8gyTktGYCBBsKdJ4mUEGS9rEkcS9tG9ZghksbZCUdjXdoLJqnScG2e4zQCxvUKE6ctwyjpw"
]
},
"version": "legacy"
}
...
]
},
"id": 1
}
返回特定区块的承诺
curl https://api.devnet.solana.com -s -X \
POST -H "Content-Type: application/json" -d '
{
"jsonrpc": "2.0",
"id": 1,
"method": "getBlockCommitment",
"params": [
346328196
]
}
'
返回
{
"jsonrpc": "2.0",
"result": {
"commitment": null,
"totalStake": 157541826450322300
},
"id": 1
}
返回节点当前的区块高度,和以太坊eth_blockNumber 类似
curl https://api.devnet.solana.com -s -X \
POST -H "Content-Type: application/json" -d '
{
"jsonrpc": "2.0",
"id": 1,
"method": "getBlockHeight",
"params": []
}
'
返回
{
"jsonrpc": "2.0",
"result": 358302920,
"id": 1
}
获取当前epoch期间内,所有产生区块的地址和相应的产块结果[应产出:实际产出]
range是当前周期的起始和当前slot区间
curl https://api.devnet.solana.com -s -X \
POST -H "Content-Type: application/json" -d '
{
"jsonrpc": "2.0",
"id": 1,
"method": "getBlockProduction"
}
'
注:对于产块结果,可以作为对应验证节点的稳定性判定
返回两个 slot 之间已确认的区块列表
curl https://api.devnet.solana.com -s -X \
POST -H "Content-Type: application/json" -d '
{
"jsonrpc": "2.0",
"id": 1,
"method": "getBlocks",
"params": [
5, // 起始Slot
10 // 结束Slot (间距<= 500000)
]
}
'
返回从给定槽开始的已确认区块列表
和getBlocks类似,区别就是由结束Slot
改为了Limit
curl https://api.devnet.solana.com -s -X \
POST -H "Content-Type: application/json" -d '
{
"jsonrpc": "2.0",
"id": 1,
"method": "getBlocksWithLimit",
"params": [
5, // 起始Slot
3 // Limit (间距<= 500000)
]
}
'
返回某个区块的预计生产时间。
每个验证者都会定期向账本报告其 UTC 时间,方法是间歇性地为特定区块的投票添加时间戳。请求的区块时间是根据账本上记录的一组最近区块中投票时间戳的权益加权平均值计算得出的。
curl https://api.devnet.solana.com -s -X \
POST -H "Content-Type: application/json" -d '
{
"jsonrpc": "2.0",
"id": 1,
"method": "getBlockTime",
"params": [
358319114
]
}
'
返回有关参与集群的所有节点的信息
curl https://api.devnet.solana.com -s -X \
POST -H "Content-Type: application/json" -d '
{
"jsonrpc": "2.0",
"id": 1,
"method": "getClusterNodes"
}
'
返回有关当前纪元的信息
curl https://api.devnet.solana.com -s -X \
POST -H "Content-Type: application/json" -d '
{
"jsonrpc": "2.0",
"id": 1,
"method": "getEpochInfo"
}
'
返回
{
"jsonrpc": "2.0",
"result": {
"absoluteSlot": 370353885, // 当前槽位
"blockHeight": 358332171, // 当前区块高度
"epoch": 857, // 当前epoch
"slotIndex": 129885, // 当前 slot 相对于当前 epoch 的开始
"slotsInEpoch": 432000, // 此纪元中的时隙数
"transactionCount": 15406146576 // 自创世以来无错误处理的交易总数
},
"id": 1
}
从此集群的创世配置中返回纪元调度信息
curl https://api.devnet.solana.com -s -X \
POST -H "Content-Type: application/json" -d '
{
"jsonrpc": "2.0",
"id": 1,
"method": "getEpochSchedule"
}
'
返回
{
"jsonrpc": "2.0",
"result": {
"firstNormalEpoch": 0,
"firstNormalSlot": 0,
"leaderScheduleSlotOffset": 432000,
"slotsPerEpoch": 432000,
"warmup": false // 是否周期一开始很短,然后逐渐增长
},
"id": 1
}
获取网络针对特定消息收取的费用,和以太坊eth_estimateGas类似
curl https://api.devnet.solana.com -s -X \
POST -H "Content-Type: application/json" -d '
{
"jsonrpc": "2.0",
"id": 1,
"method": "getFeeForMessage",
"params": [
"AQABAgIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEBAQAA",
{
"commitment": "processed"
}
]
}
'
返回
{
"jsonrpc": "2.0",
"result": {
"context": { "slot": 5068 },
"value": 5000
},
"id": 1
}
返回当前节点上可访问的最早的区块编号(block number)
curl https://api.devnet.solana.com -s -X \
POST -H "Content-Type: application/json" -d '
{
"jsonrpc": "2.0",
"id": 1,
"method": "getFirstAvailableBlock"
}
'
注:简单说就是查询当前RPC节点存储的历史区块最早的高度,由于存储成本问题,对于官方devnet testnet都只保留近期一部分数据,对于主网保留所有数据,对于历史数据保存需要使用bigtable外部存储,对于设置启动参数--limit-ledger-size
,大概保存近期 600w左右区块数据
查询genesis hash
curl https://api.devnet.solana.com -s -X \
POST -H "Content-Type: application/json" -d '
{
"jsonrpc": "2.0",
"id": 1,
"method": "getGenesisHash"
}
'
返回
{
"jsonrpc": "2.0",
"result": "EtWTRABZaYq6iMfeYKouRu166VU2xqa1wcaWoxPkrZBG",
"id": 1
}
返回节点的当前健康状况。健康节点是指位于 HEALTH_CHECK_SLOT_DISTANCE最新集群确认槽内的节点。
curl https://api.devnet.solana.com -s -X \
POST -H "Content-Type: application/json" -d '
{
"jsonrpc": "2.0",
"id": 1,
"method": "getHealth"
}
'
注:如果节点健康将返回ok
,可用于节点的监控指标
返回节点具有快照的最高插槽信息。
这将找到最高的完整快照槽,以及基于完整快照槽的最高增量快照槽(如果有)
curl https://api.devnet.solana.com -s -X \
POST -H "Content-Type: application/json" -d '
{
"jsonrpc": "2.0",
"id": 1,
"method": "getHighestSnapshotSlot"
}
'
返回当前节点的身份公钥
curl https://api.devnet.solana.com -s -X \
POST -H "Content-Type: application/json" -d '
{
"jsonrpc": "2.0",
"id": 1,
"method": "getIdentity"
}
'
注:也就是返回对应RPC 配置的--identity
的pubkey
返回当前通货膨胀调控器
curl https://api.devnet.solana.com -s -X \
POST -H "Content-Type: application/json" -d '
{
"jsonrpc": "2.0",
"id": 1,
"method": "getInflationGovernor"
}
'
返回当前时期的具体通胀值
curl https://api.devnet.solana.com -s -X \
POST -H "Content-Type: application/json" -d '
{
"jsonrpc": "2.0",
"id": 1,
"method": "getInflationRate"
}
'
返回某个时期地址列表的通胀/权益奖励
curl https://api.devnet.solana.com -s -X \
POST -H "Content-Type: application/json" -d '
{
"jsonrpc": "2.0",
"id": 1,
"method": "getInflationReward",
"params": [
[
"6dmNQ5jwLeLk5REvio1JcMshcbvkYMwy26sJ8pbkvStu",
"BGsqMegLpV6n6Ve146sSX2dTjUMj3M92HnU8BbNRMhF2"
],
{
"epoch": 2
}
]
}
'
按 Lamport 余额返回前 20 个最大的账户(结果可能缓存长达两小时)
curl https://api.devnet.solana.com -s -X \
POST -H "Content-Type: application/json" -d '
{
"jsonrpc": "2.0",
"id": 1,
"method": "getLargestAccounts",
"params": []
}
'
返回最新的区块哈希
curl https://api.devnet.solana.com -s -X \
POST -H "Content-Type: application/json" -d '
{
"jsonrpc": "2.0",
"id": 1,
"method": "getLatestBlockhash",
"params": [
{
"commitment": "processed"
}
]
}
'
注:当前值用于常规交易发送时的recentBlockhash
返回一个时期的领导者时间表
curl https://api.devnet.solana.com -s -X \
POST -H "Content-Type: application/json" -d '
{
"jsonrpc": "2.0",
"id": 1,
"method": "getLeaderSchedule",
"params": [
null,
{
"identity": "4Qkev8aNZcqFNSRhQzwyLMFSsi94jHqE8WNVTJzTP99F"
}
]
}
'
获取重传阶段看到的最大时隙。
curl https://api.devnet.solana.com -s -X \
POST -H "Content-Type: application/json" -d '
{
"jsonrpc": "2.0",
"id": 1,
"method": "getMaxRetransmitSlot"
}
'
获取撕碎插入后看到的最大插槽。
curl https://api.devnet.solana.com -s -X \
POST -H "Content-Type: application/json" -d '
{
"jsonrpc": "2.0",
"id": 1,
"method": "getMaxShredInsertSlot"
}
'
返回免除账户租金所需的最低余额。
curl https://api.devnet.solana.com -s -X \
POST -H "Content-Type: application/json" -d '
{
"jsonrpc": "2.0",
"id": 1,
"method": "getMinimumBalanceForRentExemption",
"params": [
50
]
}
'
返回公钥列表的帐户信息。
curl https://api.devnet.solana.com -s -X \
POST -H "Content-Type: application/json" -d '
{
"jsonrpc": "2.0",
"id": 1,
"method": "getMultipleAccounts",
"params": [
[
"vines1vzrYbzLMRdu58ou5XTby4qAqVRLmqo36NKPTg",
"4fYNw3dojWmQ4dXtSGE9epjRGy9pFSx62YypT7avPYvA"
],
{
"encoding": "base58"
}
]
}
'
返回所提供程序 Pubkey 拥有的所有账户
curl https://api.devnet.solana.com -s -X \
POST -H "Content-Type: application/json" -d '
{
"jsonrpc": "2.0",
"id": 1,
"method": "getProgramAccounts",
"params": [
"4Nd1mBQtrMJVYVfKf2PJy9NZUZdTAsp7D4xWLs4gDB4T",
{
"filters": [
{ "dataSize": 17 },
{
"memcmp": {
"offset": 4,
"bytes": "3Mc6vR"
}
}
]
}
]
}
'
返回最近性能样本的列表(按槽位倒序排列)。性能样本每 60 秒采集一次,包括给定时间窗口内发生的事务和槽位数量。
curl https://api.devnet.solana.com -s -X \
POST -H "Content-Type: application/json" -d '
{
"jsonrpc": "2.0",
"id": 1,
"method": "getRecentPerformanceSamples",
"params": [
2
]
}
'
返回最近区块的优先费用列表。
curl https://api.devnet.solana.com -s -X \
POST -H "Content-Type: application/json" -d '
{
"jsonrpc": "2.0",
"id": 1,
"method": "getRecentPrioritizationFees",
"params": [
["CxELquR1gPP8wHe33gZ4QxqGB3sZ9RSwsJ2KshVewkFY"]
]
}
'
返回已确认交易的签名,这些交易的列表中包含指定地址accountKeys。按时间倒序返回提供的签名或最近确认的区块的签名
curl https://api.devnet.solana.com -s -X \
POST -H "Content-Type: application/json" -d '
{
"jsonrpc": "2.0",
"id": 1,
"method": "getSignaturesForAddress",
"params": [
"Vote111111111111111111111111111111111111111",
{
"limit": 1 // 1-1000, 默认1000
}
]
}
'
返回签名列表的状态。每个签名必须是 txid,即交易的第一个签名。
除非searchTransactionHistory包含配置参数,否则此方法仅搜索签名的近期状态缓存,其中保留所有活动插槽和MAX_RECENT_BLOCKHASHES根插槽的状态。
curl https://api.devnet.solana.com -s -X \
POST -H "Content-Type: application/json" -d '
{
"jsonrpc": "2.0",
"id": 1,
"method": "getSignatureStatuses",
"params": [
[
"5VERv8NMvzbJMEkV8xnrLkEaWRtSz9CosKDYjCJjBRnbJLgp8uirBgmQpjKhoR4tjF3ZpRzrFmBV6UjKdiSZkQUW"
],
{
"searchTransactionHistory": true
}
]
}
'
返回
{
"jsonrpc": "2.0",
"result": {
"context": {
"apiVersion": "2.0.18",
"slot": 24219064
},
"value": [
{
"confirmationStatus": "finalized", // processed->confirmed->finalized
"confirmations": null,
"err": null,
"slot": 24218468,
"status": {
"Ok": null
}
}
]
},
"id": 1
}
返回已达到给定或默认承诺级别的槽
curl https://api.devnet.solana.com -s -X \
POST -H "Content-Type: application/json" -d '
{
"jsonrpc": "2.0",
"id": 1,
"method": "getSlot"
}
'
注:默认是返回最近finalized的Slot
返回当前 slot 领导者
curl https://api.devnet.solana.com -s -X \
POST -H "Content-Type: application/json" -d '
{
"jsonrpc": "2.0",
"id": 1,
"method": "getSlotLeader"
}
'
注:如果定制链做Rollup,那返回的为sequencer的账户地址
返回给定槽范围的槽领导者
curl https://api.devnet.solana.com -s -X \
POST -H "Content-Type: application/json" -d '
{
"jsonrpc": "2.0",
"id": 1,
"method": "getSlotLeaders",
"params": [
100, // 起始Slot
10 //Limit (1-5000)
]
}
'
以 lampors 为单位,返回股权最低授权。
curl https://api.devnet.solana.com -s -X \
POST -H "Content-Type: application/json" -d '
{
"jsonrpc": "2.0",
"id": 1,
"method": "getStakeMinimumDelegation"
}
'
注:如果限制外部节点的加入,可以修改链代码,将最小委托量修改为10000000000000000000
返回有关当前供应的信息。
curl https://api.devnet.solana.com -s -X \
POST -H "Content-Type: application/json" -d '
{
"jsonrpc": "2.0",
"id": 1,
"method": "getSupply"
}
'
返回 SPL 代币账户的代币余额
curl https://api.devnet.solana.com -s -X \
POST -H "Content-Type: application/json" -d '
{
"jsonrpc": "2.0",
"id": 1,
"method": "getTokenAccountBalance",
"params": [
"3tykH6gHjjyqYHBigukDXZaj3K6XupjKxYLMA2Srfbc2" // 对应账户地址->ATA账户
]
}
'
返回
{
"jsonrpc": "2.0",
"result": {
"context": {
"apiVersion": "2.1.15",
"slot": 370370345
},
"value": {
"amount": "100000000000",
"decimals": 9,
"uiAmount": 100.0, // amount /(10的decimals次方)
"uiAmountString": "100" // uiAmount 转字符串
}
},
"id": 1
}
注:查询地址为对应账户->对应Token(mint)->ATA账户地址
返回已获批准的代表的所有 SPL 代币账户
curl https://api.devnet.solana.com -s -X \
POST -H "Content-Type: application/json" -d '
{
"jsonrpc": "2.0",
"id": 1,
"method": "getTokenAccountsByDelegate",
"params": [
"4Nd1mBQtrMJVYVfKf2PJy9NZUZdTAsp7D4xWLs4gDB4T",
{
"programId": "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA"
},
{
"encoding": "jsonParsed"
}
]
}
'
按代币所有者返回所有 SPL 代币账户
curl https://api.devnet.solana.com -s -X \
POST -H "Content-Type: application/json" -d '
{
"jsonrpc": "2.0",
"id": 1,
"method": "getTokenAccountsByOwner",
"params": [
"8TKG1ez28ZYzNgGTein6Pc97yvxWwnHecVL9ZhdZTxd5", // 这里是Keypair原地址
{
"programId": "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA"
},
{
"encoding": "jsonParsed"
}
]
}
'
返回
{
"jsonrpc": "2.0",
"result": {
"context": {
"apiVersion": "2.2.3",
"slot": 370371798
},
"value": [
{
"account": {
"data": {
"parsed": {
"info": {
"isNative": false,
"mint": "34GbKbRzLMfvpHEFywhe2KyheKMMxab8tQAFj5t2rBYT", // 代币标志位
"owner": "8TKG1ez28ZYzNgGTein6Pc97yvxWwnHecVL9ZhdZTxd5",
"state": "initialized",
"tokenAmount": {
"amount": "100000000000",
"decimals": 9,
"uiAmount": 100.0,
"uiAmountString": "100"
}
},
"type": "account"
},
"program": "spl-token",
"space": 165
},
"executable": false,
"lamports": 2039280,
"owner": "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA",
"rentEpoch": 18446744073709551615,
"space": 165
},
"pubkey": "3tykH6gHjjyqYHBigukDXZaj3K6XupjKxYLMA2Srfbc2" // ATA账户地址
}
]
},
"id": 1
}
返回特定 SPL 代币类型的 20 个最大余额账户。
curl https://api.devnet.solana.com -s -X \
POST -H "Content-Type: application/json" -d '
{
"jsonrpc": "2.0",
"id": 1,
"method": "getTokenLargestAccounts",
"params": [
"34GbKbRzLMfvpHEFywhe2KyheKMMxab8tQAFj5t2rBYT" // 代币mint地址
]
}
'
返回
{
"jsonrpc": "2.0",
"result": {
"context": {
"apiVersion": "2.1.15",
"slot": 370372669
},
"value": [
{
"address": "ChpLTT95FAZNVSeM79C7iun141Lp54dxjoURegsQAtZN",
"amount": "100000000000",
"decimals": 9,
"uiAmount": 100.0,
"uiAmountString": "100"
},
{
"address": "3tykH6gHjjyqYHBigukDXZaj3K6XupjKxYLMA2Srfbc2",
"amount": "100000000000",
"decimals": 9,
"uiAmount": 100.0,
"uiAmountString": "100"
}
]
},
"id": 1
}
返回 SPL 代币类型的总供应量
curl https://api.devnet.solana.com -s -X \
POST -H "Content-Type: application/json" -d '
{
"jsonrpc": "2.0",
"id": 1,
"method": "getTokenSupply",
"params": [
"34GbKbRzLMfvpHEFywhe2KyheKMMxab8tQAFj5t2rBYT"
]
}
'
返回
{
"jsonrpc": "2.0",
"result": {
"context": {
"apiVersion": "2.1.15",
"slot": 370372872
},
"value": {
"amount": "200000000000",
"decimals": 9,
"uiAmount": 200.0,
"uiAmountString": "200"
}
},
"id": 1
}
返回已确认交易的交易详情
curl https://api.devnet.solana.com -s -X \
POST -H "Content-Type: application/json" -d '
{
"jsonrpc": "2.0",
"id": 1,
"method": "getTransaction",
"params": [
"5cvy15goDmKA81kaMKk8nuuWuSN3AgCssjTK4qTvm49FyeZPoHXp5DjatqvSoUTDYArjio5PHE2LCuzBss9P7BDK",
"json"
]
}
'
返回
{
"jsonrpc": "2.0",
"result": {
"blockTime": 1743069997,
"meta": {
"computeUnitsConsumed": 4648,
"err": null,
"fee": 5000,
"innerInstructions": [],
"loadedAddresses": {
"readonly": [],
"writable": []
},
"logMessages": [
"Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA invoke [1]",
"Program log: Instruction: MintToChecked",
"Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA consumed 4498 of 4648 compute units",
"Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA success",
"Program ComputeBudget111111111111111111111111111111 invoke [1]",
"Program ComputeBudget111111111111111111111111111111 success"
],
"postBalances": [
4994414840,
1461600,
2039280,
1,
934087680
],
"postTokenBalances": [
{
"accountIndex": 2,
"mint": "34GbKbRzLMfvpHEFywhe2KyheKMMxab8tQAFj5t2rBYT",
"owner": "8TKG1ez28ZYzNgGTein6Pc97yvxWwnHecVL9ZhdZTxd5",
"programId": "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA",
"uiTokenAmount": {
"amount": "100000000000",
"decimals": 9,
"uiAmount": 100.0,
"uiAmountString": "100"
}
}
],
"preBalances": [
4994419840,
1461600,
2039280,
1,
934087680
],
"preTokenBalances": [
{
"accountIndex": 2,
"mint": "34GbKbRzLMfvpHEFywhe2KyheKMMxab8tQAFj5t2rBYT",
"owner": "8TKG1ez28ZYzNgGTein6Pc97yvxWwnHecVL9ZhdZTxd5",
"programId": "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA",
"uiTokenAmount": {
"amount": "0",
"decimals": 9,
"uiAmount": null,
"uiAmountString": "0"
}
}
],
"rewards": [],
"status": {
"Ok": null
}
},
"slot": 370161711,
"transaction": {
"message": {
"accountKeys": [
"8TKG1ez28ZYzNgGTein6Pc97yvxWwnHecVL9ZhdZTxd5",
"34GbKbRzLMfvpHEFywhe2KyheKMMxab8tQAFj5t2rBYT",
"3tykH6gHjjyqYHBigukDXZaj3K6XupjKxYLMA2Srfbc2",
"ComputeBudget111111111111111111111111111111",
"TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA"
],
"header": {
"numReadonlySignedAccounts": 0,
"numReadonlyUnsignedAccounts": 2,
"numRequiredSignatures": 1
},
"instructions": [
{
"accounts": [
1,
2,
0
],
"data": "ndcLHYJ5qd8F2",
"programIdIndex": 4,
"stackHeight": null
},
{
"accounts": [],
"data": "F7UDE7",
"programIdIndex": 3,
"stackHeight": null
}
],
"recentBlockhash": "BUuLwgC1zPLC1VcQ5xRrKSnDz2vheZaxW82GUJX59CLM"
},
"signatures": [
"5cvy15goDmKA81kaMKk8nuuWuSN3AgCssjTK4qTvm49FyeZPoHXp5DjatqvSoUTDYArjio5PHE2LCuzBss9P7BDK"
]
}
},
"id": 1
}
返回当前网络从创世以来所有的交易总数
curl https://api.devnet.solana.com -s -X \
POST -H "Content-Type: application/json" -d '
{
"jsonrpc": "2.0",
"id": 1,
"method": "getTransactionCount"
}
'
返回当前查询RPC节点所运行的Solana 版本
curl https://api.devnet.solana.com -s -X \
POST -H "Content-Type: application/json" -d '
{
"jsonrpc": "2.0",
"id": 1,
"method": "getVersion"
}
'
返回当前银行所有投票账户的账户信息和相关股份。
curl https://api.devnet.solana.com -s -X \
POST -H "Content-Type: application/json" -d '
{
"jsonrpc": "2.0",
"id": 1,
"method": "getVoteAccounts",
"params": [
{
"votePubkey": "3ZT31jkAGhUaw8jsy4bTknwBMP8i4Eueh52By4zXcsVw"
}
]
}
'
查询指定块hash是否仍然有效
curl https://api.devnet.solana.com -s -X \
POST -H "Content-Type: application/json" -d '
{
"jsonrpc": "2.0",
"id": 45,
"method": "isBlockhashValid",
"params": [
"J7rBdM6AecPDEZp8aPq5iPSNKVkU5Q76F3oAV4eW5wsW",
{
"commitment": "processed"
}
]
}
'
在 Solana 中,交易的 confirmationStatus 用于表示交易的确认状态,主要有以下几种状态:
在使用 @solana/web3.js 进行交易时,可以指定 confirmationStatus 参数。以下是一个示例代码:
const { Connection, Keypair, LAMPORTS_PER_SOL, PublicKey, SystemProgram, Transaction, sendAndConfirmTransaction } = require('@solana/web3.js');
// 连接到 Solana 网络
const connection = new Connection('https://api-devnet.solana.com', 'confirmed');
// 生成发送方和接收方的密钥对
const fromKeypair = Keypair.generate();
const toPublicKey = new PublicKey('...'); // 替换为实际的接收方公钥
// 创建交易
const transaction = new Transaction().add(
SystemProgram.transfer({
fromPubkey: fromKeypair.publicKey,
toPubkey: toPublicKey,
lamports: 1 * LAMPORTS_PER_SOL,
})
);
// 发送并确认交易,指定 confirmationStatus 为 'finalized'
sendAndConfirmTransaction(connection, transaction, [fromKeypair], {
commitment: 'finalized'
})
.then((signature) => {
console.log(`交易已成功发送,签名: ${signature}`);
})
.catch((error) => {
console.error('转账过程中出现错误:', error);
});
在上述代码中, commitment 参数指定了交易的确认状态,这里设置为 finalized ,表示等待交易达到最终确认状态。你可以根据实际需求将其设置为 confirmed 或 processed 。
各种 Solana RPC 方法将返回更复杂的响应作为结构化 JSON 对象,并填充特定的键值。
这些 JSON 数据结构中最常见的包括:
交易与其他区块链上的交易截然不同。请务必查看交易剖析以了解 Solana 上的交易。
交易的JSON结构定义如下:
"transaction": {
"message": {
"accountKeys": [
"EF3cbGuhJus5mCdGZkVz7GQce7QHbswBhZu6fmK9zkCR",
"4LAyP5B5jNyNm7Ar2dG8sNipEiwTMEyCHd1iCHhhXYkY",
"11111111111111111111111111111111"
],
"header": {
"numReadonlySignedAccounts": 0,
"numReadonlyUnsignedAccounts": 1,
"numRequiredSignatures": 1
},
"instructions": [
{
"accounts": [
0,
1
],
"data": "3Bxs411Dtc7pkFQj",
"programIdIndex": 2,
"stackHeight": null
}
],
"recentBlockhash": "6pw7JBwq9tb5GHiBQgVY6RAp5otbouwYvEc1kbbxKFec"
},
"signatures": [
"2M8mvwhtxyz3vAokXESVeR9FQ4t9QQxF5ek6ENNBBHVkW5XyZvJVK5MQej5ccwTZH6iWBJJoZ2CcizBs89pvpPBh"
]
}
message: <object>
- 定义交易的内容。
accountKeys: <array[string]>
- 交易使用的 base-58 编码公钥列表,包括指令和签名。第一个 message.header.numRequiredSignatures
公钥必须签署交易。
header: <object>
- 详细说明交易所需的账户类型和签名。
numRequiredSignatures: <number>
- 使交易有效所需的签名总数。签名必须与第一个numRequiredSignatures
签名相匹配message.accountKeys
。
numReadonlySignedAccounts: <number>
- 最后 numReadonlySignedAccounts
一个签名密钥是只读帐户。程序可以处理在单个 PoH 条目中加载只读帐户的多个交易,但不允许贷记或借记 lamport 或修改帐户数据。针对同一读写帐户的交易将按顺序进行评估。
numReadonlyUnsignedAccounts: <number>
- 最后一个 numReadonlyUnsignedAccounts
未签名的密钥是只读账户。
recentBlockhash: <string>
- 账本中最近一个区块的 Base58 编码哈希值,用于防止交易重复并提供交易生命周期。
instructions: <array[object]>
- 将按顺序执行并在一个原子事务中提交的程序指令列表(如果全部成功)。
programIdIndex: <number>
- 数组索引,message.accountKeys
指示执行该指令的程序账户。
accounts: <array[number]>
- 数组中有序索引的列表, message.accountKeys
指示要传递给程序的帐户。
data: <string>
- 程序输入以 base-58 字符串编码的数据。
addressTableLookups: <array[object]|undefined>
- 交易使用的地址表查找列表,用于从链上地址查找表中动态加载地址。如果maxSupportedTransactionVersion
未设置,则为未定义。
accountKey: <string>
- 用于地址查找表帐户的 base-58 编码公钥。
writableIndexes: <array[number]>
- 用于从查找表加载可写帐户地址的索引列表。
readonlyIndexes: <array[number]>
- 用于从查找表加载只读帐户地址的索引列表。
signatures: <array[string]>
- 应用于交易的 base-58 编码签名列表。列表的长度始终为 message.header.numRequiredSignatures
且不为空。索引处的签名 对应于中i
索引处的公钥。第一个用作 交易 ID。i``message.accountKeys
交易的 JSON 解析结构遵循与常规 JSON 格式类似的结构,并附加了帐户和指令信息的解析:
"transaction": {
"message": {
"accountKeys": [
{
"pubkey": "EF3cbGuhJus5mCdGZkVz7GQce7QHbswBhZu6fmK9zkCR",
"signer": true,
"source": "transaction",
"writable": true
},
{
"pubkey": "4LAyP5B5jNyNm7Ar2dG8sNipEiwTMEyCHd1iCHhhXYkY",
"signer": false,
"source": "transaction",
"writable": true
},
{
"pubkey": "11111111111111111111111111111111",
"signer": false,
"source": "transaction",
"writable": false
}
],
"instructions": [
{
"parsed": {
"info": {
"destination": "4LAyP5B5jNyNm7Ar2dG8sNipEiwTMEyCHd1iCHhhXYkY",
"lamports": 100000000,
"source": "EF3cbGuhJus5mCdGZkVz7GQce7QHbswBhZu6fmK9zkCR"
},
"type": "transfer"
},
"program": "system",
"programId": "11111111111111111111111111111111",
"stackHeight": null
}
],
"recentBlockhash": "6pw7JBwq9tb5GHiBQgVY6RAp5otbouwYvEc1kbbxKFec"
},
"signatures": [
"2M8mvwhtxyz3vAokXESVeR9FQ4t9QQxF5ek6ENNBBHVkW5XyZvJVK5MQej5ccwTZH6iWBJJoZ2CcizBs89pvpPBh"
]
}
message: <object>
- 定义交易的内容。
accountKeys: <array[object]>
- 交易使用的账户信息列表。
pubkey: <string>
- 账户的 Base58 编码公钥。
signer: <boolean>
- 表明该账户是否是必需的交易签名者。
writable: <boolean>
- 指示该帐户是否可写。
source: <string>
- 账户来源(交易或查找表)。
recentBlockhash: <string>
- 账本中最近一个区块的 Base58 编码哈希值,用于防止交易重复并提供交易生命周期。
instructions: <array[object]>
- 已解析的程序指令列表。
program: <string>
- 被调用的程序的名称。
programId: <string>
- 该程序的 Base58 编码公钥。
stackHeight: <number|null>
- 指令的堆栈高度。
parsed: <object>
- 特定程序的解析数据。
type: <string>
- 指令的类型(例如“转移”)。
info: <object>
- 解析特定于程序和指令类型的指令信息。
signatures: <array[string]>
- 应用于交易的 Base-58 编码签名列表。
{
"meta": {
"err": null,
"fee": 5000,
"innerInstructions": [],
"logMessages": [],
"postBalances": [499998932500, 26858640, 1, 1, 1],
"postTokenBalances": [],
"preBalances": [499998937500, 26858640, 1, 1, 1],
"preTokenBalances": [],
"rewards": null,
"status": {
"Ok": null
}
}
}
err: <object|null>
- 如果交易失败则返回错误,如果交易成功则返回 null。TransactionError 定义
fee: <u64>
- 本次交易收取的费用,以 u64 整数形式
preBalances: <array>
- 交易处理前的 u64 账户余额数组
postBalances: <array>
- 交易处理后的 u64 账户余额数组
innerInstructions: <array|null>
-内部指令列表 或null
此交易期间是否未启用内部指令记录
preTokenBalances: <array|undefined>
- 交易处理之前的代币余额列表 ,如果在此交易期间尚未启用代币余额记录,则省略
postTokenBalances: <array|undefined>
- 交易处理后的代币余额列表 ,如果在此交易期间尚未启用代币余额记录,则省略
logMessages: <array|null>
- 字符串日志消息数组或null
如果在此事务期间未启用日志消息记录
rewards: <array|null>
- 交易级奖励;包含以下内容的 JSON 对象数组:
pubkey: <string>
- 收到奖励的账户的公钥,以 base58 编码的字符串形式lamports: <i64>
- 账户记入或扣除的奖励灯数量,以 i64 表示postBalance: <u64>
- 奖励使用后 lampors 账户余额rewardType: <string|undefined>
- 奖励类型:“费用”、“租金”、“投票”、“质押”commission: <u8|undefined>
- 奖励到账时投票账户佣金,仅适用于投票和质押奖励已弃用:
status: <object>
- 交易状态"Ok": <null>
- 交易成功"Err": <ERR>
- 交易失败,出现 TransactionErrorloadedAddresses: <object|undefined>
- 从地址查找表加载交易地址。如果 axSupportedTransactionVersion
请求参数中未设置,或者 jsonParsed
请求参数中设置了编码,则未定义。
writable: <array[string]>
- 可写加载账户的 Base58 编码地址的有序列表readonly: <array[string]>
- 只读加载帐户的 Base58 编码地址的有序列表returnData: <object|undefined>
- 交易中指令生成的最新返回数据,包含以下字段:
programId: <string>
- 生成返回数据的程序,以 base-58 编码的公钥形式data: <[string, encoding]>
- 返回数据本身,以 base64 编码的二进制数据computeUnitsConsumed: <u64|undefined>
-交易消耗的计算单元数量
version: <"legacy"|number|undefined>
- 交易版本。如果 maxSupportedTransactionVersion
请求参数中未设置,则为未定义。
signatures: <array>
- 如果请求“签名”以获取交易详细信息,则存在;签名字符串数组,对应于块中的交易顺序
Solana 运行时记录在交易处理期间调用的跨程序指令,并使这些指令可用,从而提高每个交易指令在链上执行内容的透明度。调用的指令按原始交易指令分组,并按处理顺序列出。
内部指令的 JSON 结构定义为以下结构的对象列表
"innerInstructions": [
{
"index": 0,
"instructions": [
{
"accounts": [
0,
1,
2
],
"data": "WPNHsFPyEMr",
"programIdIndex": 3,
"stackHeight": 2
},
{
"accounts": [
0,
1
],
"data": "11111dBUPbGETd4QtNMQVg8HqgcZtKy6DcJm6R4TZufJkuhkDS47VsauCCGhLf2xrm5BQ",
"programIdIndex": 2,
"stackHeight": 3
}
]
}
]
参考交易:
"transaction": {
"message": {
"accountKeys": [
"4kh6HxYZiAebF8HWLsUWod2EaQQ6iWHpHYCz8UcmFbM1",
"Bpo7aaM9kqfCjM6JgZCSdev7HsaUFj51mBPPEhQcDpUR",
"11111111111111111111111111111111",
"8HupNBr7SBhBLcBsLhbtes3tCarBm6Bvpqp5AfVjHuj8",
"GENmb1D59wqCKRwujq4PJ8461EccQ5srLHrXyXp4HMTH"
],
"header": {
"numReadonlySignedAccounts": 0,
"numReadonlyUnsignedAccounts": 3,
"numRequiredSignatures": 2
},
"instructions": [
{
"accounts": [
0,
1,
2,
3
],
"data": "H2ot5wbZsmL",
"programIdIndex": 4,
"stackHeight": null
}
],
"recentBlockhash": "28CroH2jyCaCFF6ssyUK989zBZY6dBxnUNU9A4oPUbER"
},
"signatures": [
"4i4EuRQ1sNzKWEBYwg28VAMkQbaAeHyRRwu1tQRksowtQhGRJtgaHXpDAhBfpYZnVodGoQYwiUiB5yBRDoWbZ7VH",
"2dipFcFF4CvwtbCFbRdctQmyzAYcq8RWrLryZErbKPhnriCJ6wDmKfJoSJfDjFNzUEcJDKkfasS2pcjvGEUjdYN6"
]
}
index: number
- 内部指令来源的交易指令索引
instructions: <array[object]>
- 在单个事务指令期间调用的内部程序指令的有序列表。
programIdIndex: <number>
- 数组索引,message.accountKeys
指示执行该指令的程序账户。accounts: <array[number]>
- 数组中有序索引的列表, message.accountKeys
指示要传递给程序的帐户。data: <string>
- 程序输入以 base-58 字符串编码的数据。"postTokenBalances": [
{
"accountIndex": 1,
"mint": "6HvU8PbqP3nZLkFF59rr2zkTHqetPLgb6NnxKZLHQxNp",
"owner": "DKypunNAGLPGBj3SocY8fF4ZrnDNVTf6QcUyW4trvkB",
"programId": "TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb",
"uiTokenAmount": {
"amount": "0",
"decimals": 2,
"uiAmount": null,
"uiAmountString": "0"
}
},
{
"accountIndex": 2,
"mint": "6HvU8PbqP3nZLkFF59rr2zkTHqetPLgb6NnxKZLHQxNp",
"owner": "8xm9beCpBH7SgqRz1mKua7KJF52whAVCiDEV1qREGHNV",
"programId": "TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb",
"uiTokenAmount": {
"amount": "100",
"decimals": 2,
"uiAmount": 1.0,
"uiAmountString": "1"
}
}
],
"preTokenBalances": [
{
"accountIndex": 1,
"mint": "6HvU8PbqP3nZLkFF59rr2zkTHqetPLgb6NnxKZLHQxNp",
"owner": "DKypunNAGLPGBj3SocY8fF4ZrnDNVTf6QcUyW4trvkB",
"programId": "TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb",
"uiTokenAmount": {
"amount": "100",
"decimals": 2,
"uiAmount": 1.0,
"uiAmountString": "1"
}
},
{
"accountIndex": 2,
"mint": "6HvU8PbqP3nZLkFF59rr2zkTHqetPLgb6NnxKZLHQxNp",
"owner": "8xm9beCpBH7SgqRz1mKua7KJF52whAVCiDEV1qREGHNV",
"programId": "TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb",
"uiTokenAmount": {
"amount": "0",
"decimals": 2,
"uiAmount": null,
"uiAmountString": "0"
}
}
]
参考交易:
"transaction": {
"message": {
"accountKeys": [
{
"pubkey": "DKypunNAGLPGBj3SocY8fF4ZrnDNVTf6QcUyW4trvkB",
"signer": true,
"source": "transaction",
"writable": true
},
{
"pubkey": "39nzuQ2WYHf231DJRPt1TLfaXSWXEKYGcqP3NQf6zK7G",
"signer": false,
"source": "transaction",
"writable": true
},
{
"pubkey": "DtCPWGmvCTov7CNmNTx8AFe3SEFSxgy265kZawv8SVL3",
"signer": false,
"source": "transaction",
"writable": true
},
{
"pubkey": "TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb",
"signer": false,
"source": "transaction",
"writable": false
}
],
"addressTableLookups": [],
"instructions": [
{
"parsed": {
"info": {
"amount": "100",
"authority": "DKypunNAGLPGBj3SocY8fF4ZrnDNVTf6QcUyW4trvkB",
"destination": "DtCPWGmvCTov7CNmNTx8AFe3SEFSxgy265kZawv8SVL3",
"source": "39nzuQ2WYHf231DJRPt1TLfaXSWXEKYGcqP3NQf6zK7G"
},
"type": "transfer"
},
"program": "spl-token",
"programId": "TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb",
"stackHeight": null
}
],
"recentBlockhash": "42mjf871LtzDK8NVZUAht1xBoCvNMagQGSM7BnFWZD6M"
},
"signatures": [
"5ZQqsF4tTFJDR5vuNJxejtw2GMc8KEtnPXnQjwhGzAtdbPTKtrLfPkFAbBTyPjZSVB3CbR5BiP5S8zAfZNtuwh88"
]
}
代币余额的 JSON 结构定义为以下结构的对象列表:
accountIndex: <number>
- 提供代币余额的账户索引。
mint: <string>
- 代币铸币厂的公钥。
owner: <string|undefined>
- 代币余额所有者的公钥。
programId: <string|undefined>
- 拥有该账户的Token程序的公钥。
uiTokenAmount: <object>
amount: <string>
- 代币的原始数量为字符串,忽略小数。decimals: <number>
- 为代币铸造配置的小数位数。uiAmount: <number|null>
- 代币数量为浮点数,包含小数。已弃用uiAmountString: <string>
- Token数量为字符串,保留小数位。