DEX Swap Detection: Finding Gold in Solana TX Bytes

The Firehose I Have to Drink From

The first time I opened a raw Solana transaction stream and watched the bytes scroll by, the feeling reminded me of standing at a Times Square subway turnstile during rush hour. Per Chainspect, Solana is humming along at roughly 878.8 transactions per second on a one-hour average, with 100-block bursts hitting 6,284 tx/s. Coinlaw notes that the network has, on its best day, processed over 91 million transactions in 24 hours. And I want exactly one kind of needle out of that haystack: DEX swaps.

That is the entire problem this week. Not building a bot. Not chasing latency. Just answering one question — when a chunk of bytes shows up at my socket, is this a swap, and if so, what got swapped for what?

Why the Haystack Is Even Bigger Than It Looks

A naive guess would be "most of those 880 tx/s must be trading, right?" Nope. A huge slice is validator voting overhead. Another slice is failed transactions — attempts that never modified state. Yet another is NFT mints, DePIN heartbeats, and pure SOL transfers. According to a 2026 Solana report, DePIN traffic alone accounts for an estimated 6–8% of daily transaction count, and stablecoin transfers (35% of all on-chain stablecoin moves globally) eat another huge chunk.

So the funnel I am drawing on a napkin looks like:

  • Raw stream: ~880 tx/s
  • After dropping vote transactions: a meaningful fraction
  • After dropping failed transactions: smaller still
  • After filtering to DEX program IDs: a slice
  • Of those, actual swap instructions (not pool creation, claims, position management): smaller still

The scale of the underlying DEX market makes the funnel worth building. Industry reports place Solana at roughly 38% of all on-chain DEX volume (excluding Ethereum mainnet), with a 7-day average DEX volume of $7.8 billion — more than Base, Arbitrum, and Optimism combined. Solana's DEX trading volume surpassed $1.5 trillion in 2025. The signal is there. I just have to find it inside the byte stream without melting my laptop.

Who Is Actually Trading on Solana

Before I can detect a swap, I have to know whose protocol stamped it. The Solana DEX scene is concentrated but not monolithic. A working mental model:

  • Jupiter: the router. According to a research desk, Jupiter captures more than 90% of aggregator activity on Solana, processes over $1 trillion in lifetime volume, and on peak days clears more than $1 billion in daily volume. Jupiter doesn't really "hold liquidity" — it shops between many underlying DEXes simultaneously.
  • Raydium: classic AMM with deep pools and an OpenBook order book hook. One institutional brief notes that Raydium has consistently held over 25% of Solana's DEX market share.
  • Meteora: dynamic AMMs and DLMM (Dynamic Liquidity Market Maker) pools. One report puts Meteora at over 22% of Solana's DEX market share at points during 2025.
  • Orca: Whirlpools (concentrated liquidity).
  • Pump.fun and the meme-coin venues: a separate ecosystem with its own bonding-curve math.

This matters because each of these protocols uses a different on-chain "signature" for what counts as a swap. There is no single Solana "swap instruction." Detection means writing a small adapter per protocol family, or — more often — a couple of generic adapters that cover several families at once.

What a Solana Transaction Actually Looks Like

The moment I started staring at raw bytes, I realized how different this is from Ethereum. The model on soldev.ca is the cleanest summary: a Transaction is Signatures plus a Message, and a Message is an array of Instructions. Each Instruction is three things:

  1. A program ID — which on-chain program (DEX, token program, system program) gets called.
  2. Accounts — referenced by index into a top-level accounts array, not by full address.
  3. Data — raw bytes. This is where the action lives.

The outer wrapping uses Base58 (deliberately dropping confusing characters like 0 vs O, I vs l), and the inner data uses BORSH — Binary Object Representation Serializer for Hashing — for fixed-layout buffer reading.

Compared to Ethereum's familiar 4-byte function selectors, Solana uses 8-byte discriminators. Coinmonks lays out the contrast neatly: EVM uses keccak256(functionSignature).slice(0,4), while Solana's Anchor framework uses sha256(namespace:name).slice(0,8). EVM developers get auto-decoding via ethers.js and the ABI. Solana developers, more often than not, write the parsers by hand against an IDL — when one even exists.

That last clause is doing a lot of work. We'll get there.

The Discriminator: Solana's Fingerprint

Here is the single most useful fact for swap detection. Per the Anchor docs and Jason Stanley Yoman's writeup, every Anchor instruction prefixes its data with 8 bytes computed as:

discriminator = sha256("global:<instruction_name>")[0..8]

Account types use "account:<AccountName>". Events use "event:<event_name>". The pattern is the same: hash, slice, prepend.

That single design choice transforms detection from a parsing problem into a comparison problem. I don't have to deserialize the full instruction body to know what it is. I read bytes 0 through 7, compare against a precomputed table of "swap-like" discriminators, and the answer is yes/no in O(1).

A concrete example helps. One source documents the Pump.fun create instruction discriminator as [24, 30, 200, 40, 5, 28, 7, 119] in decimal, or 0x18 0x1E 0xC8 0x28 0x05 0x1C 0x07 0x77 in hex. If the first 8 bytes of instruction data match that sequence, the transaction is a Pump.fun token-creation call. No further parsing required to confirm the type. As that source puts it:

"When processing transaction streams, developers cannot parse every transaction completely. Instead, they apply byte-level filtering."

This is the whole game. The full deserialization is expensive. The byte comparison is essentially free. So you cheap-filter first, parse second.

There is also a pleasing property here: 8 bytes is a much larger address space than EVM's 4. Collisions are far less likely. The 8 bytes feel arbitrary at first but are doing real work as a fingerprint.

The Pattern Isn't Always That Clean

If every program were Anchor-based with a published IDL, this would be a one-week project. It isn't.

First, one analysis identifies four discriminator patterns in the wild: sequential single-byte values, Anchor's 8-byte SHA256 convention, nested Anchor events using 16 total bytes, and fully custom formats with no public pattern. Jupiter's swap event, for instance, uses a 16-byte discriminator (0xe445a52e51cb9a1d40c6cde8260871e2) because the Anchor event convention layers an event: discriminator on top of an inner event type tag.

Second, plenty of programs aren't Anchor. The SPL Token program — the one that actually moves the tokens — uses single-byte instruction IDs. Byte value 3 is Transfer. Byte value 12 (0x0C) is TransferChecked. That's it. No SHA-256 in sight.

Third, and this one is the kicker: research on closed-source programs found that roughly 50% of Solana's top 100 programs lack published IDLs, and the number drops to about 20% in the top 1000. Without an IDL, you don't have a name-to-discriminator table. You have to derive it — by reverse-engineering the program's bytecode, watching real swaps and matching what shows up, or stitching together community-maintained tables. The Solana Foundation acknowledged this gap directly in 2024 with a $60,000 RFP for a community discriminator database, including features like reverse mapping of hash → function and downloadable Apache Parquet datasets.

For someone building a detector, the practical impact is straightforward. The well-known DEXes — Jupiter, Raydium, Orca, Meteora, Pump.fun — all have either published IDLs or community-maintained discriminator tables. The long tail does not. So I focus on the top of the market, where coverage is solid, and accept that exotic protocols may slip through unparsed.

Three Honest Paths to Detection

Reading through every parser library I could find, a clean taxonomy emerged. There are essentially three ways to recognize a swap. They are not competing strategies so much as different filters on the same bytes. A serious detector uses all three.

Path A — Discriminator-First

This is the fastest prefilter. The recipe:

  1. Look at the instruction's programIdIndex and check against a list of known DEX program IDs. (Raydium AMM v4 sits at 675kPX9MHTjS2zt1qfr1NYHuzeLXfQM9H24wFSUt1Mp8, Pump.fun at 6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P, and so on.)
  2. Read the first 8 bytes of instruction data.
  3. Match against a precomputed discriminator map keyed by program ID.
  4. On a hit, deserialize the remaining bytes using the known BORSH layout for that instruction's arguments.

Where this shines: Anchor-based programs that emit events for swaps. Jupiter's RouteV2 and Pump.fun's trade events are the canonical examples — they're declarative about "here is what happened" in a structured payload.

Path B — Transfer Tracking

This is the most universal approach across older AMMs. Raydium, Orca, Meteora, and PumpSwap don't always emit a single "here is your swap" event. What they always do is move tokens — and those moves happen through the SPL Token program as inner instructions.

The recipe:

  1. Filter to a transaction whose outer instruction targets a known DEX program ID.
  2. Walk the inner instructions for SPL Token Transfer (discriminator 0x03) or TransferChecked (0x0C) calls.
  3. Identify which accounts are the pool's token vaults versus the user's token accounts.
  4. Compute net flow: tokens out of vault A, tokens into vault B, vice versa for the user.

The transfer pattern is the universal language. It works even when you don't have an IDL for the DEX itself, as long as you can identify the pool's vault accounts.

Path C — Pre/Post Balance Diff

The simplest of all. One writeup makes the case beautifully: the fastest way to handle Solana transactions isn't about decoding everything — it's about focusing on what changes. Every transaction's metadata carries preTokenBalances and postTokenBalances arrays. Subtract one from the other, drop the zeroes, and you have a list of who lost what and who gained what.

This approach is gorgeous for analytics and "is this wallet trading?" questions. It is less useful for the routing-aware detection an MEV-style strategy needs, because you lose information about which DEX did the swap and through what path. For the highest-level "a swap happened" signal, though, it's basically free.

The right answer is to combine: balance diff for the cheap first-pass classifier, discriminator matching for the rich detail when an Anchor event exists, transfer tracking for everything else.

Logs vs. Events: Solana Is Backwards From Ethereum

A thing I keep tripping over is that Solana "events" do not behave like Ethereum events. One source puts it bluntly: on Solana, logs are written via the sol_log_data syscall, and "it is not possible to scan for past logs like it is in Ethereum — they must be watched for while the transaction is happening."

On Ethereum, an analyst can scan the last million blocks for Swap events and build a complete dataset. On Solana, you can't. Events are a frontend-notification mechanism, not a historical-query mechanism. The compensation is that Solana gives you superior per-address transaction querying — getSignaturesForAddress and getParsedTransaction will hand you the complete history of any wallet or program — but the philosophy is flipped: events flow forward, transactions get archived.

For swap detection, the operational consequence is that I have to be subscribed when the swap happens. There is no "replay yesterday" via event logs. There is only "replay yesterday" via fetching full transactions and re-parsing.

One small but powerful trick that several practitioners mention: a cheap log-string prefilter. Before doing any BORSH deserialization, just grep the log array for the strings "swap", "Swap", "BUY", or "SELL". It's a coarse filter, but it kills a huge percentage of irrelevant transactions before the expensive work starts.

How Early Can You See a Swap?

The research that surprised me most was on shred-level streaming. A "shred" is the physical unit Solana validators ship around — roughly 1.2 KB, sized to fit a network MTU, grouped into Forward Error Correction sets (typically 32 data + 32 coding shreds, tolerating up to 50% packet loss). Shreds propagate via the Turbine protocol in a stake-weighted tree, which means high-stake validators see them earliest.

If you can subscribe to shreds — via ShredStream-style products — you see transactions before they are executed, before logs exist, before pre/post balances exist. One provider reports latency savings of 100–400+ milliseconds compared to standard RPC, with ShredStream operations in the 10–50ms range depending on geographic proximity.

One provider's Deshred benchmarks push this further: p50 latency of ~6.3ms and p90 of ~20ms from shred arrival. That same source is also honest about the cost of being that early:

"A deshredded transaction may fail execution, land on a forked slot, or never confirm."

This is the trade. Shred-level data tells you the intent to swap before the network confirms it. You lose the metadata that only exists post-execution — success/failure status, inner instructions, balance changes, compute units. So shred-level detection is good for "someone is trying to do X right now," and post-execution gRPC streams are good for "X definitely happened." Both are useful. Neither is sufficient on its own.

The Streaming Stack Underneath

None of this works without a way to actually subscribe to the network. Standard JSON-RPC WebSockets can wobble between 400ms and 30 seconds depending on commitment level. Yellowstone-compatible gRPC streams are the workhorse for serious detection: one provider's 2026 guide reports p90 slot latency around 5ms and account latency around 215ms, with Protobuf-over-HTTP/2 payloads instead of JSON+Base64.

The filter language is half the value. One reference lists the core fields: account_include (OR across listed accounts), account_required (AND), account_exclude (NOT), plus vote: false and failed: false. The minimum viable swap-detection filter is essentially:

transactions: {
  myDexFilter: {
    vote: false,
    failed: false,
    account_include: [<DEX program IDs>]
  }
}

That single config cut the firehose from "impossible" to "plausible" the first time I tried it. The commitment-level dial matters too. PROCESSED is the fastest at roughly 400ms but can revert. CONFIRMED is around one second and is the safe default for trading. FINALIZED takes ~13 seconds and is mostly for auditing. Each is the right answer for a different question.

Several infrastructure providers offer Yellowstone-compatible gRPC endpoints, with bandwidth pricing that has settled into a familiar range — one provider's guide pegs typical costs at $0.08–0.15 per GB. That sounds cheap until you realize that an unfiltered subscription bills the entire firehose. Tight filters aren't just a clarity tool. They're a budget tool.

What Detecting One Swap Actually Costs

The academic paper ran the numbers head-to-head against commercial price APIs. For a 48-hour analytics workload across a major market event, a DIY pipeline cost $1.35 versus $921.60 on a commercial service — about 99.85% cheaper, with pooled MAPE of 0.4% and median per-asset MAPE of 0.2% across 870 pricing points. The takeaway is not that commercial APIs are bad. The takeaway is that the marginal cost of pulling swaps directly from the byte stream, once you've built the parsers, is essentially zero.

The paper also formalizes something I'd been doing intuitively: a hierarchy of evidence for what counts as a swap. Jupiter Route events sit at the top, because they explicitly say "I routed this many of X for that many of Y." Aggregator-style log aggregates come next. Pump.fun events after that. And at the bottom — the universal fallback — leg aggregation from transfer instructions. Higher-priority signals override lower ones when they conflict.

That hierarchy is the design I keep coming back to. Don't try to do one thing well. Do four things, in priority order, and let the cheapest reliable signal win.

Where This Leaves Me

The mental model I am working from, at this point:

  • The network is a firehose; the first job is to filter it down to "transactions that touch DEX programs and didn't fail." Yellowstone-compatible streams with vote: false, failed: false, and program-ID inclusion lists do this cheaply.
  • Once a transaction makes it through the filter, the discriminator does most of the classification work. For Anchor programs, an 8-byte read tells me everything.
  • For the long tail without IDLs, transfer tracking against the SPL Token program is the universal lingua franca.
  • For totals and sanity checks, pre/post balance diffs are basically free truth.
  • For the bleeding edge of "saw the swap first," shred-level streams exist, but they trade certainty for speed.

None of this answers the question of what to do with the swaps once I see them. That's a different problem, and a harder one — turning detection into action requires its own stack of decisions about routing, capital, and execution. For now, the win is just being able to look at the firehose and reliably say "there. That was a swap. SOL in, USDC out, here is the AMM, here are the amounts." That is the whole job this week, and it is a surprising amount of engineering for what sounds like a one-line question.

Key Takeaways

  • Solana's scale is the actual problem. ~880 tx/s sustained, peaking past 6,000 tx/s, with $7.8B in 7-day DEX volume per industry reports. Filtering is mandatory before parsing.
  • The 8-byte discriminator is the cheat code. For Anchor programs, comparing the first 8 bytes of instruction data against a precomputed table classifies a swap in O(1) without deserialization.
  • No single approach covers everything. Discriminator-first works for Jupiter and Pump.fun; transfer tracking works for Raydium, Orca, and Meteora; pre/post balance diffs work as a universal fallback.
  • About half the top Solana programs lack IDLs. Coverage is fine for major DEXes, sparse for the long tail.
  • Earlier data costs certainty. Shred-level streams (Deshred p50 ~6.3ms) see intent before execution; gRPC streams see confirmed reality. Use both.

Disclaimer

This article is for informational and educational purposes only and does not constitute financial, investment, legal, or professional advice. Content is produced independently and supported by advertising revenue. While we strive for accuracy, this article may contain unintentional errors or outdated information. Readers should independently verify all facts and data before making decisions. Company names and trademarks are referenced for analysis purposes under fair use principles. Always consult qualified professionals before making financial or legal decisions.