All articles
Solana·June 28, 2026·6 min read

Address Lookup Tables for Solana Bots: Fit More Into One Txn

A Solana address lookup tables trading bot packs more accounts per txn with v0 transactions, fitting multi-hop arb routes under the 1232-byte cap.

A legacy Solana transaction gives you roughly 1232 bytes to work with, and every account you touch eats 32 of them in the static address array. A three-hop arbitrage route through Raydium, Orca, and Meteora can easily reference 30-plus accounts once you count pool vaults, token accounts, oracle sysvars, and the swap program IDs. Do the math and you'll blow the packet limit before you've even added your compute budget instructions. Address lookup tables (ALTs) are the fix, and if you run an arb or sniper bot on Solana without them, you're leaving routes on the table that competitors are already taking.

Where the 1232-byte wall comes from

Solana caps a serialized transaction at 1232 bytes because that's what fits in a single IPv6 MTU packet (1280 bytes) after headers. There's no fragmentation. Your whole transaction — signatures, the message header, the account address array, recent blockhash, and every instruction's data and account indices — has to land in one UDP datagram.

The account array is the expensive part. In a legacy transaction, every account is written out as a full 32-byte pubkey. Signatures are 64 bytes each. So a transaction with two signers and 28 accounts is already at 128 + 28*32 = 1024 bytes before you've serialized a single instruction. That's why naive multi-hop routing tends to cap out around three hops on legacy transactions, and why a lot of bots quietly drop the most profitable four-leg cycles.

What ALTs actually change

An address lookup table is an on-chain account that stores a list of pubkeys. Once it's created and warmed, a versioned (v0) transaction can reference an address by a 1-byte index into the table instead of embedding the full 32-byte key. That's a 32x compression on every account you move into a table.

Two things have to be true for this to work:

  • The transaction must be a v0 (versioned) transaction, not legacy. Legacy transactions have no concept of lookup tables.
  • The accounts you're indexing must not be signers or the fee payer, and they can't be writable program accounts you're invoking as the top-level program. Signers and the fee payer always stay in the static keys.

The message splits accounts into three buckets: static keys (signers, fee payer, plus anything not in a table), then writable indexes pulled from ALTs, then readonly indexes from ALTs. The runtime resolves those indexes at execution by reading the table account, so your ALT is itself loaded as an account — budget for that one extra address.

Here's the shape of building one with @solana/web3.js:

const [ix, altAddress] = AddressLookupTableProgram.createLookupTable({
  authority: payer.publicKey,
  payer: payer.publicKey,
  recentSlot: await connection.getSlot("finalized"),
});

// extend with the pool accounts your routes hit repeatedly
const extendIx = AddressLookupTableProgram.extendLookupTable({
  authority: payer.publicKey,
  payer: payer.publicKey,
  lookupTable: altAddress,
  addresses: poolAccounts, // vaults, token accounts, program ids
});

// later, build the v0 tx
const alt = (await connection.getAddressLookupTable(altAddress)).value;
const msg = new TransactionMessage({
  payerKey: payer.publicKey,
  recentBlockhash,
  instructions,
}).compileToV0Message([alt]);
const tx = new VersionedTransaction(msg);

compileToV0Message does the substitution automatically: any instruction account that exists in a supplied table gets swapped for an index. You don't hand-pick indexes.

The gotcha nobody warns you about: the one-slot cooldown

You cannot use a lookup table in the same slot you extend it. A freshly extended table needs the extension to be finalized before its new entries are resolvable — in practice, wait at least one slot (~400ms) after the extend transaction confirms. Bots that create-and-fire in a tight loop hit "invalid account index" or "address lookup table not found" and can't figure out why. The table exists; the entry just isn't live yet.

The correct pattern is to treat ALTs as infrastructure, not per-trade objects. Pre-warm a table with the accounts for every pool you route through, deploy it once, and reference it for thousands of transactions. For a market-making or arb operation covering a fixed universe of pairs, one or two tables cover almost everything. This is the same mindset that goes into the rest of your Solana bot infrastructure and data layer — do the expensive setup once, off the hot path.

Capacity math, done properly

A single ALT holds up to 256 addresses. A v0 transaction can reference up to 256 accounts total across all its lookup tables plus static keys — that's a hard runtime limit, separate from the byte limit. So the two constraints you're juggling are:

  • 256 accounts maximum resolved per transaction.
  • 1232 bytes serialized.

With ALTs, the byte limit usually stops being the binding constraint and the account-count limit takes over. A four-hop route referencing 40 accounts, once 36 of them live in tables, serializes those 36 as 36 index bytes instead of 1152 bytes of pubkeys. You go from impossible to comfortable, with headroom for a compute-unit-price instruction and a Jito tip.

One subtlety: writable and readonly indexes are stored in separate arrays in the compiled message, each prefixed by a length byte per table. The savings are still overwhelming, but don't expect the serialized size to drop by exactly 32 bytes per account — there's a couple bytes of table bookkeeping.

Where this pays off in a real bot

For an MEV and arbitrage bot on Solana, ALTs are what let you quote and land cyclic routes that a legacy-transaction competitor literally cannot express in one atomic transaction. Atomicity matters here: if you can't fit the full cycle in one txn, you either split it (and eat leg risk) or skip the opportunity. ALTs keep the cycle atomic.

Snipers benefit differently. A new-launch Solana sniper bot often needs to create a token account, set compute budget, and swap in one shot, sometimes across two venues to split fills. Trimming the account array with a pre-warmed table for the common program IDs and sysvars leaves you bytes to spare for a larger tip or a second fallback instruction.

Fitting the transaction is only half the battle — landing it is the other half. Once your v0 transaction is well under the limit, delivery is what decides whether you win the slot. That's downstream of ALTs but tightly coupled: how you handle stake-weighted QoS for transaction priority, whether you're getting throttled by QUIC connection limits that silently drop your transactions, and whether you're reading state early off a low-latency shred feed via Jito ShredStream all determine outcomes after the transaction is built. ALTs get you to the starting line; those get you across it.

Practical checklist

  • Deploy one or two tables covering your fixed pool universe; don't create tables per trade.
  • Wait a slot after extend before first use.
  • Keep signers and fee payer in static keys — they can't be tabled.
  • Verify you're actually under 256 resolved accounts, not just under 1232 bytes.
  • Re-derive and cache the resolved AddressLookupTableAccount so you're not fetching the table on every build; refresh it only when you extend.

If you're building routing that needs to stay atomic across three or more hops, our team designs the arbitrage and MEV bot transaction layer around ALTs from the first line so you're never fighting the packet limit again.

Need a bot like this built?

We design, build and run trading bots on Solana, Hyperliquid and Polymarket.

Start a project
#Solana#Address Lookup Tables#Arbitrage Bots#Versioned Transactions#MEV