---
title: Transaction Introspection
description:
  Decode and inspect a confirmed Solana transaction with
  @solana/transaction-introspection - resolve account indices and lookup-table
  addresses, walk outer and inner (CPI) instructions, and parse them with
  program clients.
url: /docs/core/transactions/transaction-introspection
type: conceptual
prerequisites:
  - /docs/core/transactions/transaction-structure
  - /docs/core/instructions/instruction-structure
related:
  - /docs/core/transactions/versioned-transactions
  - /docs/rpc/http/gettransaction
---

<Callout type="info" title="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.
</Callout>

<Callout type="info">
  Looking for how a program inspects sibling instructions _on-chain_ at runtime?
  That is the Instructions sysvar - see [Instruction
  Introspection](/docs/core/instructions/instruction-introspection). This page
  is about decoding a transaction _off-chain_ from an RPC response.
</Callout>

A [`getTransaction`](/docs/rpc/http/gettransaction) response does not hand you a
ready-to-use list of instructions. Accounts are stored as numeric indices,
[versioned transactions](/docs/core/transactions/versioned-transactions) split
those accounts into static keys plus addresses loaded from
[Address Lookup Tables](/docs/core/transactions/versioned-transactions), 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.

```shell title="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`.

```ts title="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.

<Callout type="warn">
  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.
</Callout>

## 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.

```ts title="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.

```ts title="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.

```ts title="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);
```

<Callout type="info" title="Using Rust?">
  There is no Rust equivalent of this package. To get parsed instructions from
  Rust, request `UiTransactionEncoding::JsonParsed` from
  [`getTransaction`](/docs/rpc/http/gettransaction) and let the RPC node resolve
  accounts and inner instructions for you.
</Callout>

## See also

- [Transaction Structure](/docs/core/transactions/transaction-structure) - the
  anatomy of the response this package decodes
- [Versioned Transactions](/docs/core/transactions/versioned-transactions) - how
  Address Lookup Tables split accounts into static and loaded groups
- [`getTransaction`](/docs/rpc/http/gettransaction) - the RPC method that
  returns the transaction
