npm package: @geeklad/solana-tx-parser
Description: TypeScript library for parsing Solana transactions with Anchor IDL support
Author: GeekLad
Latest version: 1.0.0

# @geeklad/solana-tx-parser

A TypeScript library for parsing Solana transactions with [Anchor](https://www.anchor-lang.com/) IDL support. Decode instructions, extract emitted events, parse transaction logs, and serialize transactions for storage or transport.

## Features

- **Instruction decoding** — decode top-level and inner (CPI) instructions using Anchor IDLs; falls back gracefully to natively-parsed RPC output for well-known programs (System, Token, etc.)
- **Event extraction** — harvest Anchor events emitted via `emit!` and `emit_cpi!` macros from logs and CPI return data
- **Log parsing** — convert the flat transaction log array into structured per-program contexts with messages, data payloads, compute units, and errors
- **Account decoding** — deserialize on-chain account data from a named type in an Anchor IDL
- **Hydration** — serialize `ParsedTransactionWithMeta` to JSON-safe form and reconstruct it, preserving `PublicKey` instances across the round-trip
- **Performance** — module-level caches for Borsh coders prevent recompilation across repeated calls for the same program

## Installation

```bash
npm install @geeklad/solana-tx-parser @coral-xyz/anchor @solana/web3.js
```

`@solana/web3.js` is a peer dependency and must be installed alongside the package.

## Quick Start

```typescript
import { Connection } from "@solana/web3.js";
import { parseInstructions } from "@geeklad/solana-tx-parser";
import { Idl } from "@coral-xyz/anchor";
import idl from "./some-program-idl.json";

const connection = new Connection("https://api.mainnet-beta.solana.com");
const signature = "PUT A VALID SIGNATURE HERE";
const programId = "PUT THE VALID IDL PROGRAM ADDRESS HERE";

const transaction = await connection.getParsedTransaction(signature, {
    maxSupportedTransactionVersion: 0,
    commitment: "confirmed",
});

// Parse with the IDL
const parsed = parseInstructions([{ idl: idl as Idl, programId }], transaction!);

for (const ix of parsed) {
    console.log(ix.programId, ix.parsedInstruction);
}
```

## API Reference

### `parseInstructions(idls, transaction)`

Decodes all instructions in a `ParsedTransactionWithMeta`, including nested CPI calls and Anchor events.

```typescript
import { parseInstructions } from "@geeklad/solana-tx-parser";
import { Idl } from "@coral-xyz/anchor";
import myProgramIdl from "./my_program.json";

const idls = [
    {
        idl: myProgramIdl as Idl,
        programId: "MyProgram1111111111111111111111111111111111",
    },
];

const parsed = parseInstructions(idls, transaction);
```

Each element of the returned array has the shape:

```typescript
{
    index: number;                    // Position in the transaction
    programId: string;                // Base58-encoded program ID
    parsedInstruction: Instruction    // Decoded instruction (Anchor or native RPC)
                     | ParsedInstruction
                     | null;          // null if no matching IDL was found
    accounts?: {
        pubkey: string;               // Base58-encoded public key
        name?: string;                // Account name from the IDL (if available)
        isSigner: boolean;
        isWritable: boolean;
    }[];
    events?: { name: string; data: any }[] | null;  // Anchor events
    parsedInnerInstructions?: ParsedInstructionWithEvents[];  // CPI calls
}
```

For natively-decoded instructions (System Program, Token Program, etc.) `parsedInstruction` contains a `parsed` field with `type` and `info`. For Anchor instructions it contains `name` and `data`.

### `parseAccount(idl, accountData, accountName, programId?)`

Deserializes raw on-chain account data using a named struct from an Anchor IDL.

```typescript
import { parseAccount } from "@geeklad/solana-tx-parser";

const accountInfo = await connection.getAccountInfo(pubkey);
const decoded = parseAccount(
    myProgramIdl as Idl,
    accountInfo!.data,
    "MyAccount"
);
```

Returns the decoded object, or `null` if deserialization fails.

### `parseLogs(logs)`

Converts the flat `logs` array from a transaction into structured per-program contexts.

```typescript
import { parseLogs } from "@geeklad/solana-tx-parser";

const contexts = parseLogs(transaction.meta!.logMessages!);

for (const ctx of contexts) {
    console.log(ctx.programId);
    console.log(ctx.logMessages);   // msg!() output
    console.log(ctx.dataLogs);      // base64 Anchor event payloads
    console.log(ctx.unitsConsumed); // compute units used
    console.log(ctx.errors);        // error messages, if any
}
```

Each `LogContext` has:

```typescript
{
    programId: string;
    depth: number;          // CPI nesting depth (0 = top-level)
    id: number;             // Unique invocation ID within the transaction
    instructionIndex: number;
    rawLogs: string[];
    logMessages: string[];  // Lines from msg!()
    dataLogs: string[];     // Base64 payloads from emit! / emit_cpi!
    errors: string[];
    unitsConsumed?: number;
    invokeResult?: string;  // Return value or "Log truncated"
}
```

### `dehydrateParsedTransactionWithMeta(transaction)`

Converts a `ParsedTransactionWithMeta` into a plain JSON-serializable object by replacing `PublicKey` instances with base58 strings.

```typescript
import { dehydrateParsedTransactionWithMeta } from "@geeklad/solana-tx-parser";

const dehydrated = dehydrateParsedTransactionWithMeta(transaction);
const json = JSON.stringify(dehydrated);
// Store in a database, Redis, or send over the network
```

### `rehydrateParsedTransactionWithMeta(transaction)`

Reconstructs a `ParsedTransactionWithMeta` from a dehydrated object, restoring `PublicKey` instances.

```typescript
import { rehydrateParsedTransactionWithMeta } from "@geeklad/solana-tx-parser";

const loaded = JSON.parse(json);
const transaction = rehydrateParsedTransactionWithMeta(loaded);
// Pass directly to parseInstructions or any @solana/web3.js API
```

## Usage Examples

### Parsing native instructions (no IDL)

```typescript
import { Connection } from "@solana/web3.js";
import { parseInstructions } from "@geeklad/solana-tx-parser";

const connection = new Connection("https://api.mainnet-beta.solana.com");

const transaction = await connection.getParsedTransaction(signature, {
    maxSupportedTransactionVersion: 0,
    commitment: "confirmed",
});

const parsed = parseInstructions([], transaction!);

for (const ix of parsed) {
    console.log(`[${ix.index}] Program: ${ix.programId}`);

    if (ix.parsedInstruction && "parsed" in ix.parsedInstruction) {
        // Natively decoded (System Program, Token Program, etc.)
        const native = ix.parsedInstruction.parsed as { type?: string };
        console.log(`  Type: ${native.type}`);
    } else if (ix.parsedInstruction) {
        // Decoded via Anchor IDL
        console.log(`  Instruction: ${ix.parsedInstruction.name}`);
        console.log(`  Data:`, ix.parsedInstruction.data);
    }

    for (const acc of ix.accounts ?? []) {
        console.log(`  ${acc.name ?? acc.pubkey} [signer=${acc.isSigner}, writable=${acc.isWritable}]`);
    }
}
```

### Parsing a custom Anchor program

```typescript
import { Connection } from "@solana/web3.js";
import { Idl } from "@coral-xyz/anchor";
import { parseInstructions } from "@geeklad/solana-tx-parser";
import myProgramIdl from "./my_program.json";

const connection = new Connection("https://api.mainnet-beta.solana.com");

const idls = [
    {
        idl: myProgramIdl as Idl,
        programId: "MyProgram1111111111111111111111111111111111",
    },
];

const transaction = await connection.getParsedTransaction(signature, {
    maxSupportedTransactionVersion: 0,
    commitment: "confirmed",
});

const parsed = parseInstructions(idls, transaction!);

for (const ix of parsed) {
    if (!ix.parsedInstruction) continue;

    console.log(`Instruction: ${ix.parsedInstruction.name}`);
    console.log(`Data:`, ix.parsedInstruction.data);

    // Anchor events emitted during this instruction
    for (const event of ix.events ?? []) {
        console.log(`Event [${event.name}]:`, event.data);
    }

    // CPI calls made within this instruction
    for (const inner of ix.parsedInnerInstructions ?? []) {
        console.log(`  CPI -> ${inner.programId}: ${inner.parsedInstruction?.name}`);
    }
}
```

### Dehydrate / rehydrate round-trip

```typescript
import {
    dehydrateParsedTransactionWithMeta,
    rehydrateParsedTransactionWithMeta,
} from "@geeklad/solana-tx-parser";

// Fetch and dehydrate
const transaction = await connection.getParsedTransaction(signature, {
    maxSupportedTransactionVersion: 0,
});
const dehydrated = dehydrateParsedTransactionWithMeta(transaction!);

// Store (database, cache, message queue, …)
await db.set(signature, JSON.stringify(dehydrated));

// Load and rehydrate
const raw = await db.get(signature);
const rehydrated = rehydrateParsedTransactionWithMeta(JSON.parse(raw));

// Use exactly like a freshly fetched transaction
const parsed = parseInstructions(idls, rehydrated);
```

## Running the Examples

```bash
npm run example
```

> The examples use the public Solana mainnet RPC which is rate-limited. For production workloads use a dedicated RPC provider such as [Helius](https://helius.dev/), [QuickNode](https://www.quicknode.com/), or [Alchemy](https://www.alchemy.com/).

## Building

```bash
npm run build
```

Compiled output is written to `dist/`.

## License

MIT