Parsing transactions on Solana is one of those things that sounds simple, until you actually look at the raw JSON. Between preTokenBalances, innerInstructions, and all the account indices, it’s easy to get lost in the noise.

After building a few explorers and monitoring tools, I realized that the fastest way to handle Solana transactions isn’t about decoding everything — it’s about focusing on what changes.

Here’s the approach that works best for me.


1. Focus on Balance Differences First

Most developers start by decoding instructions. That’s slow. Instead, start with token balance diffs between preTokenBalances and postTokenBalances. That instantly tells you what tokens moved and by how much.

function parseTokenChanges(meta) {
  const result = [];
  const pre = meta.preTokenBalances || [];
  const post = meta.postTokenBalances || [];

  for (const p of pre) {
    const match = post.find(
      (x) => x.accountIndex === p.accountIndex && x.mint === p.mint
    );
    if (!match) continue;

    const before = p.uiTokenAmount.uiAmount ?? 0;
    const after = match.uiTokenAmount.uiAmount ?? 0;
    const delta = after - before;

    if (delta !== 0) {
      result.push({
        owner: p.owner,
        mint: p.mint,
        change: delta,
      });
    }
  }

  return result;
}

That’s it — you get a list of token movements without decoding every instruction.


2. Map Account Indices to Addresses

Each instruction on Solana references accounts by index, not address. If you want to know which wallet actually moved tokens, you need to map indices to real addresses.

const keys = tx.transaction.message.accountKeys.map((k) => k.toString());

for (const ix of tx.transaction.message.instructions) {
  const program = keys[ix.programIdIndex];
  const accounts = ix.accounts.map((a) => keys[a]);
  // use program + accounts however you need
}

This helps you identify which programs were called (Raydium, Jupiter, SystemProgram, etc.) and which wallets were involved.


3. Use Yellowstone gRPC for Real-Time Parsing

If you’re monitoring live swaps or building a bot, RPC calls won’t cut it — they’re too slow. That’s where Yellowstone gRPC comes in. It streams live transactions as they’re processed.

import { GrpcStreamClient } from "@triton-one/yellowstone-grpc";

const client = new GrpcStreamClient("grpc.mainnet-beta.solana.com");

client.onTransaction((tx) => {
  const changes = parseTokenChanges(tx.meta);
  console.log("Token flow:", changes);
});

This setup gives you real-time token movement updates with much lower latency than standard RPC polling.


4. Keep It Lightweight

When you’re parsing thousands of transactions per minute, small optimizations matter. Here are a few that made a big difference for me:

  • Cache mint metadata (symbol, decimals) once — don’t refetch it for every tx
  • Ignore transactions with no token changes
  • Use processed commitment for speed; switch to confirmed when accuracy matters
  • Split heavy parsing logic into worker threads if needed

5. Example Output

After comparing pre and post balances, you’ll get something like this:

[
  {
    "owner": "6hV7...xyz",
    "mint": "So11111111111111111111111111111111111111112",
    "change": -0.25
  },
  {
    "owner": "6hV7...xyz",
    "mint": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v",
    "change": 25.02
  }
]

In this case, the wallet swapped 0.25 SOL → 25.02 USDC. No instruction decoding, no guesswork — just clean data.


6. Tools I Recommend

Library Use For
@solana/web3.js Core RPC + transaction types
@triton-one/yellowstone-grpc Real-time stream
borsh Deserializing custom program data
bs58 Signature and pubkey encoding
superstruct JSON validation

Final Thoughts

Parsing Solana transactions doesn’t have to be painful. Focus on the state changes — token balances, key accounts, and logs — instead of chasing every instruction.

Once you get that part right, you can layer anything on top of it: dashboards, bots, analytics, or MEV tools.