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
processedcommitment for speed; switch toconfirmedwhen 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.