Running a Hyperliquid node?Activate a direct path for blocks and full mempool with Hyperliquid Peering.
Learn moreSOC 2 Type II Certified · ISO 27001
Solana v1 Transactions Explained: Size Limits and Breaking Changes
Transaction v1 raises Solana's maximum transaction size from 1,232 bytes to 4,096 bytes and removes Address Lookup Tables. Here is what changes for applications that read Solana data, and what changes for applications that send it.

September 1, 2026 — 10 min read

Developers building complex applications on Solana have spent years working around the transaction size limit. v0 introduced address lookup tables, letting a transaction reference more accounts without exceeding 1,232 bytes. The latest upgrade introduces v1 transactions, which raise the limit itself by 3.3x.
Transaction v1 raises the maximum serialized transaction size from 1,232 bytes to 4,096 bytes, activating with Agave v4.2 in early September 2026. For teams building zero-knowledge (ZK) apps, multisig systems, and complex DeFi primitives, the extra space removes a whole class of workarounds.
Nobody has to adopt v1 to be affected by it. Everything that reads Solana data is affected, whether or not it ever sends a v1 transaction.
The 1,232-byte limit came from UDP. Solana moved transaction ingestion to QUIC years ago, which removed that constraint. The limit stayed. Teams kept splitting transactions, standing up lookup tables, and building around a ceiling that no longer matched the transport underneath it.
The new limit is not based on a networking constraint. 4,096 bytes aligns with validator memory pages. That makes room for ZK proofs, BLS signature schemes, confidential transfers, oracle price update requests, and the ability to fit more instructions in a single transaction without needing bundle schemes. It also fits a full 64-account set inline, which is why v1 drops Address Lookup Tables entirely.
A v1 transaction is a new Solana wire format with a 4,096-byte ceiling. It is not v0 with a larger limit, and five changes separate them:
Version byte 0x81 sits at offset zero, so any consumer identifies the format from a single byte
Signatures move to the tail of the transaction instead of preceding the message
Fixed-width u8 counts replace compact-u16 encoding
Instruction headers group ahead of their payloads, making boundaries directly computable
Resource limits move into a header configuration mask
Support for Address Lookup Tables is removed in v1. Every account now travels inline as a full 32-byte public key, and a maximum set of 64 addresses occupies 2,048 bytes, half the new envelope before a single instruction is added.
Solana measured the cost across mainnet traffic. Roughly 62% of v0 transactions reference at least one lookup table. Converted to v1, half of them show under 420 bytes of excess and 90% show under 1,400. Transactions that referenced several tables to pull a handful of addresses each get smaller, because the table overhead cost more than the compression returned.
Removing lookup tables also simplifies validator ingestion. v0 forces a state-dependent table resolution before a validator even knows which accounts a transaction touches. v1 hands over the complete account set in the transaction itself, so fee and priority calculation happen earlier.
Payload-rich applications gain capacity, because proofs and instruction data consume bytes without consuming account slots. Account-heavy applications, including multi-venue routing and broad protocol composition, spend most of the new space re-inlining the addresses that lookup tables used to compress. For those teams the 64-account ceiling is now the binding constraint, and staying on v0 is a reasonable choice.
Bytes are the only limit v1 raises. Accounts stay at 64, instructions stay at 64, and signatures stay at 12.
Applications that read or index Solana data fail the first time a v1 transaction appears in a block they query. Code reading a transaction from the front hits message data where legacy and v0 put signatures, so a parser cannot be fixed by raising its length check.
A call that has not declared v1 support returns error -32015 as soon as it touches one, and the entire block read fails. Indexers and backfill jobs stop rather than skip the one transaction they cannot parse. Set maxSupportedTransactionVersion: 1 on both calls, as the JSON integer 1. The string "1" fails, and so does 0, so code that opted into v0 support during the v0 rollout is not already covered. getSignaturesForAddress is unaffected, because it never inspects transaction bodies.
The call that breaks:
{
"jsonrpc": "2.0",
"id": 1,
"method": "getBlock",
"params": [402184291, { "encoding": "json" }]
}The same call, corrected. Base64 is required alongside the version parameter, because base58 cannot encode a transaction above 1,232 bytes:
{
"jsonrpc": "2.0",
"id": 1,
"method": "getBlock",
"params": [
402184291,
{
"encoding": "base64",
"maxSupportedTransactionVersion": 1
}
]
}blockSubscribe emits null and stalls when it reaches a v1 transaction, with no error to catch. Monitoring built on error rates never fires, so the subscription wedges on the first v1 slot and stays there. Set maxSupportedTransactionVersion: 1 on the subscription, treat block: null as a failure rather than an empty block, and add a staleness check, because nothing else will surface it.
v1 moved compute limits and priority fees out of instructions and into a message-level configuration. Indexers that scan for ComputeBudget instructions match nothing and write zeros into every downstream dataset without erroring at any layer. Read the limits from the transaction configuration instead. It arrives inside the message on opted-in responses and is absent entirely for legacy and v0. Unset fields come back null:
"message": {
"instructions": ["… no ComputeBudget instruction here …"],
"recentBlockhash": "GsdgFbNBoZmAB5uPHfk2xUFYyM4Wg2hYZBfBrxrqjxfF",
"transactionConfig": {
"computeUnitLimit": 30000,
"heapSize": null,
"loadedAccountsDataSizeLimit": 200000,
"priorityFee": null
}
}Legacy and v0 express the fee as micro-lamports per compute unit. v1 expresses it as an absolute total in lamports. Put both on one dashboard by multiplying the v0 price by the compute unit limit the transaction requested, then dividing by 1,000,000:
20,000 CU x 250,000 micro-lamports/CU = 5,000 lamports // v0
5,000 lamports // v1 equivalentSkip the conversion and the totals mean nothing.
Streaming consumers have no equivalent to maxSupportedTransactionVersion and no way to opt out. Upgrade the Yellowstone geyser plugin to 15.1.1 and regenerate protobuf stubs at 12.6.0 or later, which adds the Message.config field. A stale consumer does not error on v1. It reads the transaction as v0 with an empty compute budget.
Detect the version structurally on Message.config, and check in this order:
Check, in this order | Version |
|---|---|
config field present | v1 |
config absent, versioned true | v0 |
neither | legacy |
The order matters, because versioned is true for both v0 and v1. Testing it first classifies every v1 transaction as v0, silently, with a zeroed compute budget.
function messageVersion(message: Message): "legacy" | "v0" | "v1" {
if (message.config !== undefined) {
return "v1";
}
return message.versioned ? "v0" : "legacy";
}config is a submessage, and submessage fields always carry explicit presence in proto3, so this check holds regardless of how a decoder handles defaults.
Yellowstone geyser plugin builds before 15.1.1 convert v1 transactions into a v0 representation before they reach the wire. The stream keeps working, but what it delivers no longer matches what executed on chain. Upgrade to 15.1.1 or later, and account for the gap in anything that reconciled stream data against chain state while running an older build.
The failures above arrive whether or not a team adopts v1. These do not. Nothing below happens until an application builds and submits a v1 transaction itself, and every item is a change to how a transaction gets constructed rather than how it gets read. Legacy and v0 transactions keep working unchanged at 1,232 bytes, so sending v1 is a decision based on capacity. A transaction that needs more than 1,232 bytes has no other option.
Legacy and v0 fall back to 200,000 compute units per instruction and 64 MiB of loaded account data when a transaction specifies nothing. v1 falls back to zero and fails the transaction. Every v1 transaction sets a compute unit limit and a loaded accounts data size limit explicitly. In @solana/kit 8.0 those move out of ComputeBudget instructions and into the message config:
import { setTransactionMessageConfig } from '@solana/kit';
const message = setTransactionMessageConfig(
{
computeUnitLimit: 20_000,
loadedAccountsDataSizeLimit: 64 * 1024,
priorityFeeLamports: 5_000n,
},
baseMessage,
);Size both limits from one simulation pass with each set to maximum, then write the returned unitsConsumed and loadedAccountsDataSize back into the config. Round the data size up to the next 32 KiB page. The block cost model charges in 32 KiB pages, so headroom below the next boundary is free.
Leave room beyond what the simulation reports. An account that does not exist costs nothing to load, but once created it costs 64 bytes plus its data, so a limit sized exactly against simulation fails with MaxLoadedAccountsDataSizeExceeded if the account appears before the transaction lands.
v1 accepts them, charges 150 compute units each, and ignores them for configuration. A transaction carrying ComputeBudget instructions and no message configuration passes validation and then runs with zero limits. Drop them from v1 builders entirely. The message configuration replaces them, and each one left behind spends an instruction slot out of the 64 available plus 150 compute units to do nothing.
v1 sets the priority fee as an absolute lamport total. The micro-lamports-per-compute-unit calculation from v0 does not carry over. The value is a bigint, and the setter is rejected at compile time on legacy and v0 messages:
import { setTransactionMessagePriorityFeeLamports } from '@solana/kit';
const message = setTransactionMessagePriorityFeeLamports(5_000n, baseMessage);Transactions above that size require encoding: "base64" on submission. Applications that hardcode base58 fail on the large transactions v1 enables.
{
"jsonrpc": "2.0",
"id": 1,
"method": "sendTransaction",
"params": [
"<base64-encoded transaction>",
{ "encoding": "base64" }
]
}Legacy and v0 tolerate repeated account addresses within a transaction. v1 rejects them.
v1 support starts at @solana/kit 8.0.0, the 4.2.x Rust crates, solders 0.29.0, and solana-go 1.23.0.
Library | Minimum version | Support |
|---|---|---|
@solana/kit | 8.0.0 | Read and send |
@solana/web3.js 1.x | 1.99.0 (coming soon) | Read only, cannot build, sign, or send |
@solana/web3.js 3.x | rc-0.3 (coming soon) | Read and send |
The v1 format runs on a local validator today with no feature gate required. solana-test-validator supports it from version 4.2, Surfpool from version 1.5, and an ephemeral Surfnet configured for SIMD-0296 provides a hosted option.
Quicknode RPC endpoints and APIs support v1 transactions at activation. No endpoint migration, plan change, configuration flag, or support ticket is required.
Quicknode already serves v1 transactions on the Alpenglow cluster today, so the format can be tested against a live endpoint before it reaches testnet. Testnet is pending the SIMD-0385 Transaction V1 activation, targeting Wednesday, September 2, 2026 at 10:30 UTC.
The remaining work sits in application code. Pass maxSupportedTransactionVersion: 1 on getTransaction and getBlock, upgrade to a client version listed above, replace ComputeBudget instruction scanning with the message configuration, normalize priority fee units across formats, and switch to base64 encoding for transactions above 1,232 bytes.
Founded in 2017, Quicknode deploys institutional-grade blockchain infrastructure for developers and enterprises. With 99.99% uptime and support for 80+ chains, teams build and scale onchain applications without compromise.
The latest engineering insights, product updates, and web3 news delivered straight to your inbox.
4.2.x |
Read and send |
solders (Python) | 0.29.0 | Read and send |
solana-go | 1.23.0 | Read and send |
Yellowstone geyser plugin | 15.1.1 | Earlier builds downgrade v1 to v0 |
Protobuf stubs | 12.6.0 | Adds Message.config |