The Pattern That Keeps Slapping Me

My scanner keeps finding five-hop cycles. The simulator keeps rejecting them. The error message is always the same shape: the transaction is too large, by a few dozen bytes, sometimes by a couple hundred. Four-hop cycles through the same pools land fine. Three-hop cycles land effortlessly. Five hops, every time, bounce.

I already solved this problem. That's what makes it feel personal.

Months ago I'd hit Solana's 1,232-byte transaction wall on a much simpler bot — a single-hop swap with too many supporting accounts, refusing to serialize. The cure, then, was Address Lookup Tables. Pre-register the public keys of the programs and pools I use most, encode them as one-byte indexes instead of full thirty-two-byte addresses, and suddenly there's room. The trick worked. I shipped it. I stopped thinking about byte budgets the way you stop thinking about your phone's storage after you delete the photo library.

Now I'm scaling up — chasing longer arbitrage paths because the easy two- and three-hop opportunities have been picked over by older, faster bots. And the ladder I built turns out to be too short for the new floor.

The Constraint I Thought I'd Outgrown

A quick recap, because this is the spec everything else depends on.

A Solana transaction has a maximum serialized size of 1,232 bytes, per the official Solana documentation. That number isn't arbitrary. It comes from the IPv6 minimum MTU of 1,280 bytes, minus 40 bytes of IPv6 header and 8 bytes of fragment header. The design intent was that every transaction should fit in a single UDP packet so it could propagate across the network at the speed of one round-trip, not many. The SIMD-0296 proposal lays out that origin story plainly.

That ceiling shapes everything downstream. Per Phantom's developer documentation, a legacy transaction can fit roughly thirty-five distinct addresses before the ceiling becomes binding. For a single token transfer, that's plenty. For a Jupiter-style routed swap that touches multiple pools and programs, it isn't.

That's where Address Lookup Tables came in. ALTs are on-chain accounts that can hold up to 256 public keys, per the Agave versioned-transactions spec. Once a key sits in a published ALT, a v0 transaction can refer to it with a single byte instead of dragging the full thirty-two-byte address along for the ride. The savings per account are real: thirty-one bytes back into the budget for every key you compress. A few dozen accounts get compressed and suddenly a transaction that didn't fit, fits.

That's the pitch, anyway. The reality is more interesting, and that's what my five-hop scanner is teaching me.

What ALT Actually Compresses — and What It Doesn't

When I first deployed ALT, I mentally categorized it as a universal solvent for the byte budget. Every account I touched, in principle, could be hidden behind a one-byte index. As long as the lookup table existed and contained the right keys, I had headroom.

That mental model is half right and half dangerous. Here's what's actually true.

The address half of an account reference compresses brilliantly. Per the Agave spec, an ALT-resolved account costs one byte in the transaction body instead of thirty-two — provided the key is in a referenced table. The savings formula works out to thirty-one bytes per account, minus a one-time overhead of about thirty-four bytes per ALT table you reference (the table's own thirty-two-byte address plus two length fields). The break-even is laughably low: any time you can compress two or more accounts through the same table, you come out ahead.

The instruction data half doesn't compress at all. The body of each swap instruction — the discriminator that tells the program which entrypoint to call, the input amount, the minimum output amount, the slippage tolerance, any flags — all of that ships raw, every time, in full. ALT can't touch it.

And there's a third category I underweighted: per-instruction metadata. Each instruction header has a program-id index, an account-indices length, the list of per-account indexes (one byte each), and an instruction-data length. Per the Solana transaction-structure docs, these compact-u16 length fields and per-account-index bytes don't sound like much in isolation. Across five instructions touching twelve accounts each, they add up to real money.

Then there's the signer exception, which is the one that bites hardest. Per the Agave spec: "Transaction signers may not be loaded through an address lookup table, the full address of each signer must be serialized in the transaction." The fee payer — the account that signs the transaction and pays for compute — has to appear as a full thirty-two-byte key in the static section, every time, no exceptions. For a single-signer arbitrage bot like mine, that's only one account, so the cost is bounded. But it sets the floor for what ALT can do: anything that signs is non-negotiable overhead.

The shape of the wall, then, isn't "too many addresses." It's "too much of everything else, all at once."

The Per-Hop Math That Stops at Four

Let me lay out the budget the way I do when I'm staring at a bounced transaction and trying to figure out where the spillover came from.

Start with fixed overhead. A v0 transaction with one ALT table reference and a single signer comes in around 170 bytes before any per-hop content shows up. The breakdown, working from the official transaction-structure docs:

  • One Ed25519 signature: 64 bytes, plus a 1-byte signature count
  • Version byte for v0: 1 byte
  • Message header: 3 bytes
  • Recent blockhash: 32 bytes
  • Count bytes for the various arrays: roughly 3 bytes
  • ALT table reference: about 34 bytes of overhead
  • Fee payer as a static 32-byte key (can't be ALT'd): 32 bytes

That's the baseline. Roughly 170 bytes are gone before any swap logic enters the picture.

Now the per-hop cost. The exact account count varies sharply by DEX. The older AMM designs that integrate with an external order book — Raydium's V4-style pools, for instance — require something like eighteen accounts per swap, per developer references I've seen for the program's instruction layout. Newer simplified AMMs are leaner, in the ten-to-fourteen range. Across a five-hop cycle through five different pools, the unique-account count after deduplication (Token Program, System Program, fee payer, and shared mints get reused) lands somewhere around forty-five to sixty distinct accounts.

With good ALT coverage, all of those collapse to about one byte each, plus the per-instruction index lists (also one byte per reference). Call it ninety to a hundred bytes for the account-reference plumbing across all five hops, in the favorable case.

The killer is instruction data. Each swap instruction carries something on the order of twenty to forty bytes of raw payload — the discriminator, the input amount as a u64, the minimum output as a u64, sometimes a slippage parameter, sometimes flags. Five hops of that is 100 to 200 bytes of pure non-compressible data, before you add the per-instruction header overhead (program-id index, indices length, data length) of another few bytes per instruction.

Add it up in the best case: 170 (fixed) + 90 (account plumbing) + 150 (instruction data) = about 410 bytes. That fits comfortably in 1,232.

Add it up in the realistic case, where some accounts aren't in any pre-built ALT and have to ride along as full thirty-two-byte keys, where some hops use chunky V4-style pools, where there's a compute-budget instruction or two riding on top: 170 + 320 (ten static keys you couldn't pre-publish) + 250 (heavier instruction data) + 50 (compute budget instructions) = about 790 bytes — and that's already cutting it close. Add a sixth unexpected account or a slightly larger swap payload, and 1,232 is gone.

A developer in the SIMD-226 discussion on the Solana Foundation's GitHub captures this exact ledge: "Usually it works but requires extremely tight lookup table management with very low margin of error, there are usually 100 bytes left and therefore 3 unknown accounts that are not part of preexisting lookup tables." That quote is the world I'm now living in. Five hops, perfect ALT coverage, a generic AMM that isn't anyone's favorite, and a clean compute budget — and there might be a hundred bytes of slack. Anything unexpected, and the simulator bounces it.

The community estimate I keep seeing — that ALT lifts you from "two-or-three hops legacy" to "four-to-six hops with v0," as paraphrased in Eco's overview of Jupiter routing — turns out to be more truthful than triumphant. Four hops, in my hands, is reliably achievable. Six hops requires the stars to align: every pool already in a published ALT, every program a known quantity, every instruction the slim variant. Five hops is where the experience lives — sometimes it fits, sometimes it doesn't, and which side of the line you land on depends on factors that aren't fully under your control as a routing engine.

This is what "structural" means. It's not that I'm coding badly. It's that the math doesn't bend, and the per-hop costs grow in pieces that no amount of address compression can squeeze.

The Second Constraint I Hadn't Met

While I was hunting the byte ceiling I bumped into something I hadn't known to look for: even if I had unlimited bytes, Solana would still cap how many accounts a transaction can touch.

This is the account lock limit, and it lives separately from the size limit. A transaction has to declare every account it intends to read from or write to so the runtime can lock those accounts during execution and prevent conflicting transactions from interleaving. The total number of locked accounts per transaction was originally capped at sixty-four, and was raised to 128 in Solana v1.14.17 per the Solana Developer Forums announcement. The stated aim was to enable higher composability across programs.

The thing to internalize: ALT compression shrinks the byte cost of referencing an account but doesn't shrink the lock cost of using it. Whether an account is dragged into the transaction as a full thirty-two-byte key or as a one-byte ALT index, the runtime still has to lock it. So even if you crammed your transaction up to its 1,232-byte ceiling using maximum compression, you can still hit a separate ceiling on the number of distinct accounts you've asked the runtime to coordinate.

For my five-hop scenario this isn't yet the binding constraint — fifty or sixty accounts is well under 128 — but it becomes binding for the seven- and eight-hop fantasies I was sketching as long-term escape routes. The lock limit is part of why the byte limit, even relaxed, won't suddenly unlock arbitrarily long paths.

The distinction matters because the two ceilings reward different design choices. The byte ceiling rewards aggressive ALT use and lean instruction data. The lock ceiling rewards reusing accounts across hops — running cycles where every hop touches the same fee payer, the same Token Program, the same handful of shared mints. ALT helps you with the first. It doesn't help with the second. Some of my early ideas for longer paths involved cycling through many small pools to find pricing edges, which is exactly the strategy that maxes out distinct-account count fastest.

Why the Limit Exists, and Why It Might Move

The 1,232-byte ceiling was set when Solana propagated transactions over raw UDP. Fitting in one packet meant the gossip layer could move a transaction across the cluster in one round-trip, with no fragmentation, no reassembly, no waiting for missing fragments to arrive. Network latency is a primary design constraint in any system that's trying to clear in sub-second time, so the IPv6-MTU math wasn't a quirky historical accident — it was the system's tightest binding requirement at the time.

Solana now uses QUIC for transaction propagation. QUIC is built on UDP but handles its own packetization, retransmission, and congestion control above the raw datagram layer. The IPv6-MTU math no longer translates directly to a single-packet propagation guarantee, because QUIC can move multi-packet messages without sacrificing the latency profile that a single UDP packet used to give you.

That opens the door to relaxing the ceiling, and SIMD-0296 is the formal proposal to do it. The proposal raises the maximum serialized transaction size from 1,232 bytes to 4,096 bytes, applied to a new v1 transaction format (defined separately in SIMD-0385). Notably, v1 transactions in the proposal don't use Address Lookup Tables at all — the larger raw size is treated as making them unnecessary.

The proposal cites a data point I find useful: in an analysis of Jito bundle sizes, 50% of bundles fall under 2,048 bytes and 100% under 9,216 bytes. The 4,096-byte target is calibrated to cover the bulk of real-world bundling patterns developers are already using as workarounds for the single-transaction size cap.

The proposal lists four use cases the current 1,232 limit blocks: zero-knowledge proofs for confidential balances, Winternitz one-time signatures, nested multisig schemes for corporate accounts, and on-chain BLS signature verification. Developers in the SIMD-226 discussion add the byte detail — untruncated Winternitz signatures need about 37 more bytes than they currently get — and the DeFi operations closest to my own work: "swap + deposit (single-sided liquidity deposit), swap + leverage, flash loan + swap + deposit" as atomic operations a single 1,232-byte transaction can't express today.

Those last three are interesting because they're exactly the kind of composed operation that's natural to want from an arbitrage or MEV system. Today the workaround is Jito bundles — sequences of transactions that land or don't land together, ordered, but with weaker atomicity guarantees than a single transaction provides. (A bundle's transactions can each fail independently in some failure modes; a single transaction can't.) The whole reason developers reach for bundles is that they've already hit the wall I'm hitting and decided their use case requires more bytes than 1,232 can deliver.

SIMD-0296 isn't shipped. It's a proposal under discussion. There's no firm timeline. The discussion threads are full of pushback about backward compatibility, about whether the v1 format should fork the validator's transaction-processing pipeline, about whether 4,096 is the right number or whether the right answer is something larger or something dynamic. I'm not betting my bot's roadmap on the proposal landing soon.

What I am taking from it is conceptual clarity. The 1,232-byte limit is not a permanent property of Solana. It's a setting calibrated to an earlier networking layer that has since been replaced. The ceiling will likely move. But it will move on the network's schedule, not mine.

What I'm Doing in the Meantime

A few practical adjustments come out of this episode.

First, I'm pinning my path-length cap at four hops for the production scanner. Five hops will still get evaluated for opportunity, but flagged as "size-risky" and only attempted when the candidate route stays well below 800 bytes in the dry-run simulator. The asymmetry between "finds an opportunity" and "the opportunity actually fits in a transaction" was costing me real time, and the time was making me miss four-hop opportunities I could have captured. Better to win the path lengths that actually work than chase one that almost works.

Second, I'm investing more in ALT hygiene. There's a real difference between "the program is in some ALT somewhere" and "the program is in my ALT, alongside the other accounts I'll need together." Co-locating frequently co-occurring accounts in the same lookup table compresses the per-table overhead — you pay the thirty-four-byte table-reference cost once instead of for each table you have to drag in. The hygiene work is unglamorous but it pushes the size-distribution histogram several dozen bytes to the left, which is the difference between "reliably fits" and "sometimes bounces."

Third, I'm reading the SIMD-0296 discussion threads with new respect. The developer comments aren't abstract spec arguments — they're written by people who've been bruised by exactly the wall I'm walking into, and the use cases they're advocating for are use cases I'd want too. The proposal isn't shipping tomorrow, but the conversation is shaping the shape of the eventual fix, and the texture of what gets unblocked when the ceiling moves is worth knowing in advance.

Fourth, I'm developing more respect for the non-compressibility of instruction data. When evaluating a new DEX to integrate, I'm now asking how many bytes of raw payload its swap instruction requires, not just how many accounts. A pool that wants forty bytes of instruction data per swap is a different beast from one that wants twenty, and the difference compounds at every hop. There's a category of optimization here I hadn't paid attention to: choosing routes that go through DEXs with lean instruction formats, not just routes that price well.

Fifth — and this is more philosophical — I'm noticing how the wall changes the kinds of arbitrage strategies that are economically viable. Long-path cycles through obscure tokens are structurally penalized by the byte budget, which means the venues that maximize bytes-per-hop (the older, fatter AMM designs) get incrementally excluded from the longer cycles. The MEV landscape is shaped by these constraints in ways that aren't obvious until you bump into them. Path length isn't just a search-algorithm parameter; it's a binding constraint on the strategy space.

None of this gets me to a five-hop cycle that lands every time. The wall is the wall. I'm working inside it.

Key Takeaways

  • The 1,232-byte transaction size limit on Solana is a hard ceiling derived from the IPv6 minimum MTU of 1,280 bytes minus protocol headers, per the official Solana documentation. It's enforced at the network layer, not adjustable per-application.
  • Address Lookup Tables compress addresses, not instruction data. A v0 transaction can shrink a 32-byte account reference to a 1-byte index, per the Agave versioned-transactions spec, saving 31 bytes per account. Per-hop instruction payloads (discriminators, amounts, flags) ship raw every time.
  • The fee payer cannot be loaded through an ALT. Signers must always appear as full 32-byte keys in the static portion of the transaction, per the Agave spec. This is a small but absolute overhead.
  • A second ceiling exists: the account lock limit, raised from 64 to 128 in Solana v1.14.17 per the Solana Developer Forums. ALT compression does not reduce lock cost — it only reduces byte cost.
  • Five-hop arbitrage paths sit on the edge of what fits, with experienced developers reporting "100 bytes left and 3 unknown accounts" of margin in the SIMD-226 discussion. Four hops is reliably workable; five is conditional on ALT discipline and DEX choice.
  • SIMD-0296 proposes raising the limit to 4,096 bytes for a new v1 transaction format, made feasible by the move from raw UDP to QUIC. It is a proposal, not a shipped change.

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.