Master Solana Transaction Parsing in Minutes!

Okay, look. Most Solana transaction parsing guides? They dump a wall of theory on you first. Accounts model. Parallel execution. Blah blah. You sit there glazing over, thinking "I just wanna parse a damn trade and see SOL amounts, not read a textbook." That's why you're still confused after 30 minutes. In my experience, you gotta jump straight into code that works on a real tx. Theory later. Or never, if you don't care.

So here's the deal. We're mastering this in minutes by parsing a pump.fun buy. k tokens for 0.0796 SOL. You'll extract trader, amounts, mint, buy/sell. Copy paste run. Boom.

Grab Your Tools - Don't Skip This

  • Node.js. Duh.
  • npm init -y
  • Packages: npm i @solana/web3.js @noble/hashes/sha256 @coral xyz/anchor @coral xyz/borsh
  • That's it. No fancy IDE needed. VS Code's fine.

Why these? Web3.js for RPC calls. Noble for SHA256 discriminators. Anchor/Borsh for decoding. In my experience, skipping noble hashes is the #1 noob mistake - your buy/sell filter returns empty.

Your First Parse: Pump.fun Trade in 60 Seconds

Copy this tx sig: 4XQZckrFKjaLHM68kJH7dpSPo2TCfMkwjYhLdcNRu5QdJTjAEehsS5UMaZKDXADD46d8v4XnuyuvLV36rNRTKhn7. Real mainnet buy.

Create parse.js. Paste this whole thing. Run node parse.js.

import { clusterApiUrl, Connection, PublicKey } from "@solana/web3.js";
import { sha256 } from '@noble/hashes/sha256';
import { bs58 } from '@coral xyz/anchor/dist/cjs/utils/bytes';
import * as borsh from "@coral xyz/borsh"; const main = async () => { const signature = "4XQZckrFKjaLHM68kJH7dpSPo2TCfMkwjYhLdcNRu5QdJTjAEehsS5UMaZKDXADD46d8v4XnuyuvLV36rNRTKhn7"; const connection = new Connection(clusterApiUrl("mainnet beta")); const transaction = await connection.getParsedTransaction(signature, { maxSupportedTransactionVersion: 0 }); const PumpFunProgram = new PublicKey("6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P"); const pumpIxs = transaction?.transaction.message.instructions.filter((ix) => (ix.programId as PublicKey).equals(PumpFunProgram) ); const buyDiscriminator = Buffer.from(sha256('global:buy').slice(0, 8)); const sellDiscriminator = Buffer.from(sha256('global:sell').slice(0, 8)); const buySellIxs = pumpIxs?.filter(ix => { const discriminator = bs58.decode((ix as any).data).subarray(0, 8); return discriminator.equals(buyDiscriminator) || discriminator.equals(sellDiscriminator); }); const tradeSchema = borsh.struct([ borsh.u64("discriminator"), // Ignore this, we already filtered borsh.u64("amount"), // Token amount borsh.u64("solAmount") // SOL amount (lamports) ]); for (let ix of buySellIxs || []) { const ixDataArray = bs58.decode((ix as any).data); const ixData = tradeSchema.decode(ixDataArray); const type = bs58.decode((ix as any).data).subarray(0, 8).equals(buyDiscriminator) ? 'buy' : 'sell'; const tokenAmount = ixData.amount.toString(); const mint = (ix as any).accounts.toBase58(); const trader = (ix as any).accounts.toBase58(); // Calc SOL delta from bonding curve balance change const bondingCurve = (ix as any).accounts; const index = transaction?.transaction.message.accountKeys.findIndex((: any) =>.pubkey.equals(bondingCurve)); const preBalances = transaction?.meta?.preBalances || []; const postBalances = transaction?.meta?.postBalances || []; const solDelta = Math.abs(preBalances[index!] - postBalances[index!]) / 1e9; // To SOL console.log(" Trade Data "); console.log(SOL: ${solDelta.toFixed(4)}); console.log(Tokens: ${tokenAmount}); console.log(Type: ${type}); console.log(Mint: ${mint}); console.log(Trader: ${trader}); console.log(""); }
}; main().catch(console.error);

Output? Exactly what you want:

 Trade Data SOL: 0.0796
Tokens: 724879000000
Type: buy
Mint: [some mint]
Trader: [wallet] 

Short sentences. Works. Questions? "Why bonding curve index 3?" That's pump.fun's account layout. Peek at IDL later if curious.

Common Screw Ups and Fixes

  1. Empty buySellIxs? Wrong SHA256 lib. Use @noble/hashes. Not crypto js.
  2. Versioned tx fails? Add { maxSupportedTransactionVersion: 0 }. Most are legacy.
  3. RPC timeout? Swap to Helius/QuickNode. Free tier's fine, ~20 req/sec.
  4. solAmount wrong? Divide lamports by 1e9. Forgot that once. Dumb.

Why Bother? Real Talk

Parsing txs lets you build bots. Track whales. Alert on rugs. Me? I use this for a sniper that pings Discord on big buys. Fees? Negligible, ~0.000005 SOL per RPC call. Scales to thousands/minute on paid RPC.

But honestly, most folks quit here thinking "cool demo." Nah. Next: events. Easier.

Events: Even Lazier Parsing

Pump.fun emits events. Anchor makes this dead simple. Grab IDL first.

Create idl.ts. Paste pump.fun IDL (fetch from pump.program or GitHub). Or hardcode basics.

// Simplified PumpFunIDL - full one's longer
export const PumpFunIDL = { version: '0.1.0', name: 'pump', instructions: [..], events: [{ name: 'TradeEvent', fields: [ { name: 'mint', type: 'publicKey' }, { name: 'solAmount', type: 'u64' }, { name: 'tokenAmount', type: 'u64' }, { name: 'isBuy', type: 'bool' }, { name: 'user', type: 'publicKey' } ] }]
} as any;

Now events.js:

import { clusterApiUrl, Connection, PublicKey } from "@solana/web3.js";
import { BorshCoder, EventParser, Idl } from "@coral xyz/anchor";
import { PumpFunIDL } from './idl'; const parseEvents = async () => { const signature = "4XQZckrFKjaLHM68kJH7dpSPo2TCfMkwjYhLdcNRu5QdJTjAEehsS5UMaZKDXADD46d8v4XnuyuvLV36rNRTKhn7"; const PumpFunProgram = new PublicKey("6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P"); const connection = new Connection(clusterApiUrl("mainnet beta")); const transaction = await connection.getParsedTransaction(signature, { maxSupportedTransactionVersion: 0 }); const eventParser = new EventParser(PumpFunProgram, new BorshCoder(PumpFunIDL as unknown as Idl)); const events = eventParser.parseLogs(transaction?.meta?.logMessages || []); for (let event of events) { console.log(" Event Data "); console.log(SOL: ${(event.data.solAmount / 1e9).toFixed(4)}); console.log(Tokens: ${event.data.tokenAmount.toString()}); console.log(Type: ${event.data.isBuy ? 'buy' : 'sell'}); console.log(Mint: ${event.data.mint.toBase58()}); console.log(Trader: ${event.data.user.toBase58()}); }
}; parseEvents().catch(console.error);

Cleaner? Yup. Events give exact data, no balance math. Downside: Only Anchor programs emit 'em clean. Native Rust? Back to manual decode.

What's next? Batch parsing. 'Cause one tx is cute, 1000 is money.

Batch Mode: Parse 1000+ Txs Fast

Loop over sigs. But RPC limits suck. Solution: getSignaturesForAddress + parallel fetches.

const batchParse = async (wallet: string, limit = 100) => { const connection = new Connection(clusterApiUrl("mainnet beta")); const sigs = await connection.getSignaturesForAddress(new PublicKey(wallet), { limit }); const trades = []; for (const sigInfo of sigs) { const tx = await connection.getParsedTransaction(sigInfo.signature, { maxSupportedTransactionVersion: 0 }); // Parse like before, push to trades } console.log(Found ${trades.length} trades);
};

Pro tip: Throttle with Promise.allSettled in chunks of 10. Free RPC chokes otherwise. Paid? Fire away.

MethodSpeed (100 txs)AccuracyWhen to Use
Manual DecodeFast100% any programNon Anchor (Raydium)
EventsInstantAnchor onlyPump.fun, Metaplex
Parsed RPCSlowestBasic transfersQuick checks

Trickier Stuff: Raydium Swaps (No IDL)

Raydium v4? Native Rust. No events. Manual all the way. Program ID: 675kPX9MHTjS2zt1qfr1NYHuzeLXfQM9H24wFSUt1Mp8.

Steps:

  1. Fetch tx.
  2. Filter Raydium Ixs. Discriminators from decompiling or known bytes.
  3. Accounts: Look for AMM pool index 0 usually.
  4. Balance deltas on token accounts for amounts.
  5. Token mints from metadata or associated token accounts.

Example snippet for swap amount:

// After filtering raydiumIx
const poolIndex = 0; // Typical
const preTokenA = tx.meta.preTokenBalances?.find(b => b.accountIndex === raydiumIx.accounts[poolIndex]);
const postTokenA = tx.meta.postTokenBalances?.find(b => b.accountIndex === raydiumIx.accounts[poolIndex]);
const deltaA = Math.abs(postTokenA.uiTokenAmount.amount - preTokenA.uiTokenAmount.amount);

Harder? Yeah. But 80% of volume's Raydium. Master this, you're golden. Issues? Logs show inner CPIs - drill down.

Edge Cases That'll Bite You

Partial signs. Failed txs. Versioned txs with address lookups. Sound familiar?

Versioned: Set maxSupportedTransactionVersion: 0. Or handle v0 separately - loadedAddressKeys has extras.

Fails: Check tx.meta.err. Skip.

Multi instruction trades: Jupiter aggregates. Parse all inner swaps. Use getParsedTransaction - it unrolls CPIs sometimes.

In my experience, 10% of "weird" txs are just bundles. Jito tips? Ignore, they're post script.

Live Stream: Wallet Tracker

Want real time? WebSocket subscribe.

connection.onLogs(PumpFunProgram, (logs) => { // Parse logs for events if (logs.logs.some(log => log.includes('Program log: Instruction: Buy'))) { // Fetch full tx, parse }
}, 'confirmed');

Filter by wallet. Ping Telegram on sells >1 SOL. Fees? ~0.000005 SOL per sub update.

Scale It: Local JSON for Speed

RPC slow? Dump tx JSONs locally.

const txJson = JSON.parse(fs.readFileSync('tx.json'));
const transaction = ParsedTransactionWithMeta.from(txJson); // Web3.js magic
// Parse same as RPC

Backtest 1M txs in minutes. No rate limits.

Python? Quick Port

No official yet. But:

from solana.rpc.api import Client
from solders.pubkey import Pubkey

Honestly, JS faster for Solana. Stick unless you hate npm.

Costs Breakdown

RPC: Free 100k/day QuickNode. Paid ~$50/mo unlimited.

Compute: Negligible. 1000 txs/sec on laptop.

Storage: Tx JSON ~2KB each. 1M = 2GB.

That's your base. Tweak for Jup, Orca. Build from here. Hit snags? Common - wrong discriminator bytes. Double check sha256('global:buy').slice(0,8). Go crush some txs.