Transaction Introspection

Summary

@solana/transaction-introspection turns a getTransaction RPC response into a list of instructions with real addresses. It resolves account indices (including lookup-table addresses), decodes inner CPI instructions, and walks the full instruction tree in explorer order.

Looking for how a program inspects sibling instructions on-chain at runtime? That is the Instructions sysvar - see Instruction Introspection. This page is about decoding a transaction off-chain from an RPC response.

A getTransaction response does not hand you a ready-to-use list of instructions. Accounts are stored as numeric indices, versioned transactions split those accounts into static keys plus addresses loaded from Address Lookup Tables, and inner instructions from cross-program invocations (CPIs) arrive as encoded blobs in the transaction metadata.

@solana/transaction-introspection does this resolution for you. It decodes the wire transaction, maps every account index back to its Address, normalizes the inner instructions, and returns instructions in the same outer-then-inner order a block explorer shows. The returned instructions plug directly into the codama-generated clients' (e.g., @solana-program/* ) parseXInstruction helpers.

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

Decode the response

Fetch the transaction with a wire-format encoding and pass the response to 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);

The remaining snippets reuse rpcTx, compiledMessage, and loadedAddresses from this step.

Preparing for v1 transactions

Once the v1 format activates, pass maxSupportedTransactionVersion: 1 or the fetch fails on v1 transactions. decodeTransactionFromRpcResponse handles legacy, 0, and 1 alike (from @solana/kit 7.0). Note that a v1 message carries its resource limits in a transactionConfig object rather than in ComputeBudget instructions, so code that recovers compute limits or priority fees by scanning the decoded instructions finds nothing for v1.

Fetch with base64, base58, or json encoding. decodeTransactionFromRpcResponse rejects jsonParsed responses - that encoding asks the RPC node to parse instructions server-side, so there is nothing left to decode on the client.

Walk the instructions

walkInstructions returns every instruction - outer and inner - as an array in the order a block explorer shows: each outer instruction followed immediately by the inner instructions its CPIs produced. Each instruction carries a trace field describing where it sits in the call hierarchy.

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);
}

The trace is a discriminated union: { kind: "outer", index } for a top-level instruction, or { kind: "inner", outerIndex, innerIndex, stackHeight? } for a CPI instruction nested under an outer one.

meta is optional - pass rpcTx.meta directly even when it may be null. With no meta, walkInstructions returns only the outer instructions.

Parse with a program client

Each instruction from walkInstructions is a ResolvedInstruction, so it works directly with the predicates from @solana/instructions and the identifyXInstruction / parseXInstruction helpers generated by the @solana-program/* clients - no conversion step.

This example audits every Token Program SyncNative instruction in a transaction, whether it ran at the top level or inside a 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);
}

Lower-level helpers

walkInstructions covers most needs, but the package also exposes the pieces it builds on:

  • getInstructionsFromCompiledTransactionMessage(compiledMessage, loadedAddresses?) returns only the outer instructions, with resolved accounts and decoded data.
  • getInnerInstructionsFromMeta(meta, accountMetas) decodes the inner CPI instructions from the transaction metadata.
  • getAccountMetasFromCompiledTransactionMessage(compiledMessage, loadedAddresses?) returns the full account list in transaction order - the input the previous helper needs to resolve inner-instruction account indices.
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);

Using Rust?

There is no Rust equivalent of this package. To get parsed instructions from Rust, request UiTransactionEncoding::JsonParsed from getTransaction and let the RPC node resolve accounts and inner instructions for you.

See also

Is this page helpful?

Table of Contents

Edit Page
© 2026 Solana Foundation. All rights reserved.