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.
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.
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.
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.
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.
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.
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
- Transaction Structure - the anatomy of the response this package decodes
- Versioned Transactions - how Address Lookup Tables split accounts into static and loaded groups
getTransaction- the RPC method that returns the transaction
Is this page helpful?