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.
npm init -ynpm i @solana/web3.js @noble/hashes/sha256 @coral xyz/anchor @coral xyz/borshWhy 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.
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.
{ maxSupportedTransactionVersion: 0 }. Most are legacy.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.
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.
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.
| Method | Speed (100 txs) | Accuracy | When to Use |
|---|---|---|---|
| Manual Decode | Fast | 100% any program | Non Anchor (Raydium) |
| Events | Instant | Anchor only | Pump.fun, Metaplex |
| Parsed RPC | Slowest | Basic transfers | Quick checks |
Raydium v4? Native Rust. No events. Manual all the way. Program ID: 675kPX9MHTjS2zt1qfr1NYHuzeLXfQM9H24wFSUt1Mp8.
Steps:
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.
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.
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.
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.
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.
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.