Інтроспекція транзакцій

Підсумок

@solana/transaction-introspection перетворює відповідь RPC getTransaction на список інструкцій із реальними адресами. Він розв'язує індекси облікових записів (включно з адресами таблиць пошуку), декодує внутрішні інструкції Cross Program Invocation та обходить повне дерево інструкцій у порядку, який використовує провідник.

Шукаєте інформацію про те, як програма перевіряє сусідні інструкції on-chain під час виконання? Це системна змінна Instructions — див. Інтроспекція інструкцій. Ця сторінка присвячена декодуванню транзакції off-chain з відповіді RPC.

Відповідь getTransaction не надає готового списку інструкцій. Облікові записи зберігаються як числові індекси, версійні транзакції розділяють ці облікові записи на статичні ключі та адреси, завантажені з таблиць пошуку адрес, а внутрішні інструкції з Cross Program Invocation надходять як закодовані блоби у метаданих транзакції.

@solana/transaction-introspection виконує це розв'язання за вас. Він декодує дротову транзакцію, зіставляє кожен індекс облікового запису з його Address, нормалізує внутрішні інструкції та повертає інструкції в тому самому порядку — спочатку зовнішні, потім внутрішні — як показує провідник блоків. Повернуті інструкції безпосередньо підключаються до згенерованих Codama клієнтів (наприклад, @solana-program/*) за допомогою помічників parseXInstruction.

Terminal
npm install @solana/transaction-introspection @solana/kit @solana/instructions @solana-program/token

Декодування відповіді

Отримайте транзакцію з кодуванням у форматі дроту та передайте відповідь до decodeTransactionFromRpcResponse.

Decode a transaction
import { createSolanaRpc, signature } from "@solana/kit";
import { decodeTransactionFromRpcResponse } from "@solana/transaction-introspection";
const rpc = createSolanaRpc("https://api.mainnet-beta.solana.com");
const txid =
"3jUKrQp1UGq5ih6FTDUUt2kkqUfoG2o4kY5T1DoVHK2tXXDLdxJSXzuJGY4JPoRivgbi45U2bc7LZfMa6C4R3szX";
const rpcTx = await rpc
.getTransaction(signature(txid), {
commitment: "confirmed",
encoding: "base64",
maxSupportedTransactionVersion: 0
})
.send();
if (!rpcTx) throw new Error(`Transaction ${txid} not found`);
const { compiledMessage, loadedAddresses } =
decodeTransactionFromRpcResponse(rpcTx);

Наступні фрагменти повторно використовують rpcTx, compiledMessage та loadedAddresses з цього кроку.

Отримуйте дані з кодуванням base64, base58 або json. decodeTransactionFromRpcResponse відхиляє відповіді jsonParsed — це кодування змушує вузол RPC розбирати інструкції на стороні сервера, тому на клієнті не залишається нічого для декодування.

Обхід інструкцій

walkInstructions повертає кожну інструкцію — зовнішню та внутрішню — у вигляді масиву в порядку, який показує блок-експлорер: кожна зовнішня інструкція, а одразу після неї — внутрішні інструкції, породжені її CPI. Кожна інструкція містить поле trace, що описує її місце в ієрархії викликів.

Walk every instruction
import { walkInstructions } from "@solana/transaction-introspection";
for (const ix of walkInstructions({
compiledMessage,
loadedAddresses,
meta: rpcTx.meta
})) {
const location =
ix.trace.kind === "outer"
? `outer[${ix.trace.index}]`
: `inner[${ix.trace.outerIndex}/${ix.trace.innerIndex}]`;
console.log(location, ix.programAddress, ix.accounts?.length ?? 0);
}

trace є дискримінованим об'єднанням: { kind: "outer", index } для інструкції верхнього рівня або { kind: "inner", outerIndex, innerIndex, stackHeight? } для інструкції CPI, вкладеної в зовнішню.

meta є необов'язковим — передавайте rpcTx.meta напряму, навіть якщо воно може бути null. Без метаданих walkInstructions повертає лише зовнішні інструкції.

Розбір за допомогою клієнта програми

Кожна інструкція з walkInstructions є ResolvedInstruction, тому вона безпосередньо працює з предикатами з @solana/instructions та допоміжними функціями identifyXInstruction / parseXInstruction, згенерованими клієнтами @solana-program/*, — без жодного кроку перетворення.

Цей приклад перевіряє кожну інструкцію Token Program SyncNative у транзакції, незалежно від того, чи виконувалась вона на верхньому рівні, чи всередині CPI.

Find and parse SyncNative instructions
import {
isInstructionForProgram,
isInstructionWithAccounts,
isInstructionWithData
} from "@solana/instructions";
import { walkInstructions } from "@solana/transaction-introspection";
import {
identifyTokenInstruction,
parseSyncNativeInstruction,
TOKEN_PROGRAM_ADDRESS,
TokenInstruction
} from "@solana-program/token";
for (const ix of walkInstructions({
compiledMessage,
loadedAddresses,
meta: rpcTx.meta
})) {
if (!isInstructionForProgram(ix, TOKEN_PROGRAM_ADDRESS)) continue;
if (!isInstructionWithData(ix) || !isInstructionWithAccounts(ix)) continue;
if (identifyTokenInstruction(ix) !== TokenInstruction.SyncNative) continue;
const parsed = parseSyncNativeInstruction(ix);
console.log(ix.trace, parsed);
}

Допоміжні функції нижнього рівня

walkInstructions охоплює більшість потреб, але пакет також надає компоненти, на яких він побудований:

  • getInstructionsFromCompiledTransactionMessage(compiledMessage, loadedAddresses?) повертає лише зовнішні інструкції з розв'язаними акаунтами та декодованими даними.
  • getInnerInstructionsFromMeta(meta, accountMetas) декодує внутрішні CPI-інструкції з метаданих транзакції.
  • getAccountMetasFromCompiledTransactionMessage(compiledMessage, loadedAddresses?) повертає повний список акаунтів у порядку транзакції — вхідні дані, необхідні попередній допоміжній функції для розв'язання індексів акаунтів внутрішніх інструкцій.
Resolve outer and inner instructions separately
import {
getAccountMetasFromCompiledTransactionMessage,
getInnerInstructionsFromMeta,
getInstructionsFromCompiledTransactionMessage
} from "@solana/transaction-introspection";
const outer = getInstructionsFromCompiledTransactionMessage(
compiledMessage,
loadedAddresses
);
const accountMetas = getAccountMetasFromCompiledTransactionMessage(
compiledMessage,
loadedAddresses
);
if (!rpcTx.meta) throw new Error("Transaction metadata missing");
const inner = getInnerInstructionsFromMeta(rpcTx.meta, accountMetas);

Використовуєте Rust?

Еквівалента цього пакету для Rust не існує. Щоб отримати розібрані інструкції з Rust, запитайте UiTransactionEncoding::JsonParsed з getTransaction і дозвольте вузлу RPC розв'язати акаунти та внутрішні інструкції за вас.

Дивіться також

Is this page helpful?

Зміст

Редагувати сторінку