Solana transactions and limits: 1232 bytes, 64 signatures, 1.4M CUs
Every Solana transaction lives under hard limits — 1232 bytes, 64 signatures, 1.4M compute units, 64 accounts. Here's where each comes from and how to fit.
A Solana transaction has four hard limits. Every one of them is load-bearing for the chain's performance properties, and every one of them is something you'll bump into building anything non-trivial.
1. Total serialized size: 1,232 bytes
2. Signatures: 64 max
3. Compute units (per tx): 1,400,000 max
4. Account references: 64 unique (more via ALTs)The 1232-byte ceiling
Why 1232? Two reasons stacked: Solana's networking layer uses QUIC frames, and the IPv6 MTU minus headers leaves ~1232 bytes for the transaction payload. The number is baked in at the protocol level — there's no version of Solana where a 1300-byte transaction lands.
What fits inside those 1232 bytes:
Signatures section: 1 + (sig_count × 64) bytes
Message header: 3 bytes
Account keys: 1 + (key_count × 32) bytes
Recent blockhash: 32 bytes
Instructions: 1 + Σ instruction_bytes
per instruction: 1 (program_id_index)
+ 1 (accounts_count) + accounts_count bytes (indexes)
+ 1 (data_len) + data_len bytes
For v0 transactions, an additional:
Address lookup tables: 1 + Σ alt_entry bytes
per ALT: 32 (table_address)
+ 1 (writable_indexes_count) + writable_indexes
+ 1 (readonly_indexes_count) + readonly_indexesFor a one-instruction tx with one signature and three accounts, you have ~1130 bytes left for instruction data. For a complex DEX swap with 15 accounts and three instructions, you can be at zero bytes of headroom.
Address Lookup Tables — the breathing room
Once you bump the size limit, the only real escape is Address Lookup Tables (ALTs). They replace each 32-byte account key in your tx with a 1-byte index into a pre-published table, plus a 33-byte table reference per ALT used.
Net effect: a swap that hits 8 reusable accounts saves ~217 bytes. That's often the difference between "tx too large" and "ships fine." ALTs require versioned (v0) transactions, which all modern web3.js / kit clients produce by default.
The 64-signature limit
Encoded in 1 byte (the signature count is a single byte at the start of the wire format). 255 would be theoretically encodable but the runtime caps it at 64. In practice you almost never hit this — most txs have 1-3 signers — but multi-sig flows and atomic bundles can.
The 1.4M compute unit limit
Per transaction, regardless of how many instructions or how you've configured the budget program. See the compute units article for the cost model — the relevant point here is that a single transaction can't exceed 1.4M CUs of work total. If your flow needs more, split into multiple transactions.
Account references
Practical cap: ~64 unique accounts referenced in one transaction, derived from the size budget (each account key eats 32 bytes; 64 keys = 2,048 bytes — already over the limit, so the practical number is lower without ALTs).
With ALTs, you can reference hundreds of accounts because each added one only costs 1 byte. But each instruction still has its own account-list overhead, and the total of all account references across all instructions still needs to fit.
Building under the limits
import {
Connection, Keypair, PublicKey,
TransactionMessage, VersionedTransaction,
} from "@solana/web3.js"
const conn = new Connection("https://api.mainnet-beta.solana.com")
const payer = Keypair.generate()
// Fetch the ALT you'll reference
const lookupTableAccount = (await conn.getAddressLookupTable(altAddress)).value!
// Build a v0 message
const { blockhash } = await conn.getLatestBlockhash()
const message = new TransactionMessage({
payerKey: payer.publicKey,
recentBlockhash: blockhash,
instructions: [
/* setComputeUnitLimit, setComputeUnitPrice,
then your actual instructions */
],
}).compileToV0Message([lookupTableAccount]) // <-- ALT compaction here
const tx = new VersionedTransaction(message)
tx.sign([payer])
// Check the serialized size before sending
const serialized = tx.serialize()
console.log("tx size:", serialized.length, "bytes") // must be ≤ 1232
if (serialized.length > 1232) throw new Error("tx too large")
await conn.sendTransaction(tx)When you hit a wall
Order of escalation when you bump a limit:
- Size: add ALTs for repeatedly-referenced accounts (system program, token program, your program, oracle accounts).
- Size, still: shrink instruction data (use shorter encodings, drop optional fields).
- Compute: rewrite the hottest path in a more CU-efficient framework (Pinocchio over Anchor) or off-load work (precompute off-chain, pass the result in).
- Compute or size: split into multiple transactions, use idempotent state machines so partial execution is recoverable.
- Compute or size, at scale: Jito bundles for atomicity across multiple txs.
Why none of these limits are going up
Each limit ties to a structural property of Solana:
- 1232 bytes ↔ network MTU minus headers
- 1.4M CUs ↔ per-slot leader compute budget divided across txs
- 64 accounts ↔ scheduler write-lock graph complexity
Loosening any of them would degrade either propagation latency (size), throughput (CUs), or parallelisation (accounts). The ceiling is the price of the rest of Solana's performance envelope.
References
Memorize the four numbers: 1232, 64, 1.4M, 64. Every interesting Solana transaction lives inside that box.