Updated August 2026 • Solana Foundation
A major upcoming upgrade to Solana raises the maximum transaction size from 1232 bytes to 4096 bytes. The size increase is defined in SIMD-0296 and delivered through the v1 transaction format introduced in SIMD-0385. It unlocks workloads that previously could not fit inside a single transaction, including ZK proofs, large multisigs, and some onchain signature schemes.
The existing v0 and legacy transaction formats continue to work as they do
today, so applications and wallets that don't need the larger size will be
unaffected. Applications that want to take advantage of it will need to update
to v1 transactions.
Breaking Changes:
| If your app | Breaking? | What you have to do |
|---|---|---|
| Reads transactions or blocks | Yes | Pass maxSupportedTransactionVersion: 1. Learn more |
| Indexes transactions | Yes | Read limits for v1 transactions from transactionConfig instead of from ComputeBudget instructions. Learn more |
| Sends transactions | No (opt-in) | If using v1 you MUST set the compute unit and data size limits explicitly (both default to zero). Learn more |
| Sponsors fees or co-signs someone else's transaction | Yes | Caps that scan ComputeBudget instructions stop binding. Read the limits you gate on from transactionConfig. Learn more |
Expected Mainnet Activation Date:
Anza's Agave v4.2 release schedule targets mainnet feature activation in Agave v4.2. This lands alongside the first stage of reduced slot times and a rent reduction. Anza's schedule is explicitly tentative and subject to change.
Current feature gate activation status:
| Klaster | Status aktivasi |
|---|---|
| Testnet | Aktif |
| Devnet | Aktif |
| Mainnet | Belum diaktifkan |
If you want to check activation status of the feature gate yourself at any time,
you can do so with the Solana CLI (use -u d or -u t for devnet or testnet):
solana -u m feature status txv1aq4pp281K9um3tnPgkfX8UqtFT6wcVW3hNezGLL
Local testing is available today. V1 Transactions are enabled in:
- Local test validator in the Solana CLI (v4.2+)
- Surfpool (v1.5+)
Support for V1 Transactions
These are the first releases that handle v1 transactions. Upgrade your dependencies to avoid errors.
| Library | Minimum supported version | v1 support |
|---|---|---|
@solana/kit (Typescript) | 8.0.0 | Read and send |
@solana/web3.js (Typescript) 3.x | 3.0.0-rc.3 | Read and send |
@solana/web3.js (Typescript) 1.x | 1.99.0-beta.0 | Read only — cannot build, sign, or send |
solana-* crates (Rust) | 4.2.x | Read and send |
solders (Python) | 0.29.0 | Read and send |
solana-foundation/solana-go (Go) | 1.23.0 | Read and send — also in 2.0.0 on the /v2 module path |
yellowstone-grpc-proto (Rust) | 12.6.0 | First generated code carrying Message.config |
| Yellowstone geyser plugin (Rust) | 15.1.1 | Earlier builds downgrade v1 to v0 before the wire |
yellowstone-grpc-client (Rust) | 12.0.0 | Latest is 13.3.0 |
@triton-one/yellowstone-grpc (Typescript) | 6.0.0 | Decodes field 7 |
Migration Checklist
For UsersNo action needed.+
Wallets and applications keep working exactly as they do today. The legacy and
v0 transaction formats are unchanged, and nothing about how you approve, sign,
or send a transaction changes.
What the upgrade buys you is headroom. Work that previously had to be split across several chained transactions — ZK proofs such as those used by Confidential Transfers, large multisigs, and batched operations — can land as a single atomic transaction. That means fewer signatures to pay for and one confirmation instead of several.
For DevelopersReading transactions is a breaking change. Sending v1 is opt-in.+
Start by upgrading dependencies (see Support for V1 Transactions), then work through whichever of these describes your application.
If you read or stream transactions — RPC consumers, indexers, explorers, Geyser pipelines (details):
- When fetching data via HTTP RPC call, set
maxSupportedTransactionVersionto the integer1ongetTransactionandgetBlock. - When streaming data via Websockets, set
maxSupportedTransactionVersionto the integer1onblockSubscribe. - When streaming block data from Websockets via
blockSubscribe, handle theblock: nullwith an error as a failure, not an empty block. - Update raw transaction decoders to recognize the v1
129(0x81) version prefix and parse its new layout. - Regenerate Geyser / gRPC protobuf stubs, then identify v1 from the presence of
Message.configbefore checking theversionedflag.
If you introspect a transaction's compute budget or priority fee — indexers, onchain programs, relayers, paymasters, and anyone who sponsors a fee (indexing details, onchain details, sponsor details):
- When parsing the V1 transaction payload, read
transactionConfiginstead of introspecting the ComputeBudget instructions. - Normalize priority fees before comparing across versions: v0 states a price in micro-lamports per compute unit, v1 a total in lamports.
If you want to send V1 transactions (details):
- If you explicitly add ComputeBudget instructions, remove them from your transaction and use the
messageConfiginstead. - If you used Address Lookup Tables (ALTs) in V0, remove them from your transaction and place the accounts directly in the instruction (up to 64 accounts) instead.
- Set compute unit limit and loaded accounts data size explicitly (the defaults are zero and your transaction will fail without them).
- Estimate both from one simulation with both limits maxed; round the data size up to the next 32 KiB page.
- Convert priority fees from micro-lamports-per-CU to total lamports.
- Pass
encoding: 'base64'when simulating and sending.
The technical details below explain each of these in full. Sample code is available here.
GitHub: Transaction V1 Example Code has a runnable example in each of these languages — sending, decoding, reading blocks, and indexing over gRPC — against a local validator, plus a version matrix recording the first release of every dependency that handles v1, including the ones that drop the config silently rather than erroring. Details for all breaking changes are included below:
For Transaction ReadersOpt in to v1 transactions before they appear onchain.+
Breaking Change: Reading Transactions and Blocks
Once v1 transactions appear onchain, any RPC consumer that has not opted in starts failing. The fix is one line:
- Pass
maxSupportedTransactionVersion: 1ongetTransactionandgetBlock. It must be the JSON integer1, not the string"1". - Passing
0orlegacyfails on v1 transactions exactly like omitting the parameter. - The parameter declares the highest version your client can decode, NOT a
ceiling and NOT a request for a particular version. Raising it to
1does not change how legacy and v0 transactions are returned.
How each read surface behaves without the opt-in:
| Method | Without maxSupportedTransactionVersion: 1 |
|---|---|
getTransaction | v1 Transaction fails with error -32015 |
getBlock | One v1 transaction fails the entire block — no partial result |
blockSubscribe | Emits block: null and stops advancing — wedges on the first v1 slot |
getSignaturesForAddress | Not impacted — v1 signatures list normally, |
Opted-in responses carry a new transactionConfig object inside the message for
v1 transactions (it is absent entirely for legacy and v0):
"message": {
"instructions": ["… no ComputeBudget instruction here …"],
"recentBlockhash": "GsdgFbNBoZmAB5uPHfk2xUFYyM4Wg2hYZBfBrxrqjxfF",
"transactionConfig": {
"computeUnitLimit": 30000,
"heapSize": null,
"loadedAccountsDataSizeLimit": 200000,
"priorityFee": null
}
}
For IndexersDecode v1 transaction configuration instead of ComputeBudget instructions.+
Breaking Change: Indexing Transactions
ComputeBudget scanning returns nothing. Any pipeline that derives priority fees
or compute unit limits by scanning instructions for the ComputeBudget program
will report zero for every v1 transaction, without erroring. In v1 those
values live in transactionConfig. This is the most likely path to quietly
wrong analytics rather than a loud failure.
Geyser / gRPC streams have no version gate at all. There is no version field
in the protobuf. A stale consumer does not error on v1; it
silently misreads it as v0 with an empty compute budget. Regenerate your
protobuf stubs and detect the version structurally on Message.config, field
7, checking in this order:
| Check, in this order | Version |
|---|---|
config field present | v1 |
config absent, versioned true | v0 |
| neither | legacy |
The order matters because the versioned boolean is true for both v0 and v1.
Testing it first classifies every v1 transaction as v0 — silently, with a zeroed
compute budget.
In TypeScript:
function messageVersion(message: Message): "legacy" | "v0" | "v1" {
if (message.config !== undefined) {
return "v1";
}
return message.versioned ? "v0" : "legacy";
}
And in Rust:
fn message_version(message: &Message) -> MessageVersion {
match (&message.config, message.versioned) {
(Some(_), _) => MessageVersion::V1,
(None, true) => MessageVersion::V0,
(None, false) => MessageVersion::Legacy,
}
}
config is a submessage, and submessage fields always carry explicit presence in
proto3 — so this check holds however your decoder handles defaults, and whether or
not your copy of the schema marks the field optional.
Comparing fees across versions needs normalising. v0 states a priority fee as a price in micro-lamports per compute unit; v1 states a total in lamports. To put both in one dashboard, multiply the v0 price by the compute unit limit the transaction actually requested, then divide by 1,000,000:
20,000 CU × 250,000 micro-lamports/CU = 5,000 lamports // v0
5,000 lamports // the v1 equivalent
For Transaction SendersSending v1 is opt-in, but its resource limits must be explicit.+
Sending v1 Transactions
In legacy and v0, setting a resource limit is optional as the runtime provides default fallback values. In v1, omitting it sets the resource limits to zero — and a transaction that cannot run.
| Unset field | legacy / v0 | v1 |
|---|---|---|
| Compute unit limit | 200k per ix, max 1.4M | 0 CU |
| Loaded accounts data size | 64 MiB | 0 bytes |
| Heap size | 32 KiB | 32 KiB |
A v1 transaction with an empty config fails at account loading with MaxLoadedAccountsDataSizeExceeded, meaning you requested a budget of insufficient bytes. This means: always set the compute unit limit and loaded accounts data size limit explicitly. Simulate once with both limits maxed, read unitsConsumed and loadedAccountsDataSize from the result, and write those into the config (rounding the data size up to the next 32 KiB page, since the block cost model charges in 32 KiB pages and headroom below the next page boundary is free).
Size your data budget with headroom for accounts that do not exist yet. Loading
an account is charged 64 bytes of base metadata plus its data length, while an
account that does not exist is charged nothing at all, so creating one is a step
change from 0 to at least 64 bytes. If an account is created between your
simulation and your send, a data size limit measured exactly against simulation
can tip over into MaxLoadedAccountsDataSizeExceeded once the transaction
reaches the network.
> rather than >=, so 149 lands exactly on the limit and passes.Three more sending changes:
- Priority fee is now a total, not a price. v0 sets micro-lamports per compute unit; v1 sets an absolute total in lamports. Do not carry the per-CU multiplication across.
- ComputeBudget instructions become no-ops. They are neither parsed nor rejected — they execute successfully while doing nothing, burning 150 compute units and an instruction slot. Strip them.
- Use base64 encoding for large transactions. Submitting over 1232 bytes
requires
encoding: "base64". base58 stays capped at 1232 bytes regardless of transaction version, and was deliberately not raised on deprecation grounds, so the 4096-byte ceiling is only reachable over base64.
For Program DevelopersA program cannot read a v1 transaction's compute budget or priority fee.+
Breaking Change: Onchain Introspection of the Compute Budget
A program reads the compute budget today by scanning the Instructions sysvar for ComputeBudget instructions. In v1 those values live on the message, and nothing exposes the message config to onchain code: the Instructions sysvar carries instructions only, no sysvar holds the config, and no syscall returns it.
ComputeBudget instructions in a v1 transaction are not rejected; they execute as no-ops, meaning their internal set values are no longer reliable. At present there is no sysvar to determine a transaction's version or to extract the message config. This means that program should stop gating on introspected ComputeBudget Instructions once v1 transactions are live.
We are evaluating additional options. Check back here for updates.
For Paymasters and Server SignersA fee cap that scans instructions does not bind a v1 transaction.+
Breaking Change: Sponsoring and Co-Signing Transactions
If you co-sign, sponsor the fee for, or verify a transaction somebody else built (e.g., relayers, paymasters, and gasless backends):
-
Detect the version before you gate on anything. Unlike a program, a server can always tell what it is holding. Decoding the base64 a payer hands you yields the version directly the message's first byte is
0x81for v1. -
For v1 transactions, read the priority fee and compute limits from
transactionConfig, not from ComputeBudget instructions. Priority fees are a total in lamports, not micro-lamports per CU. The same decode that gave you the version gives you the config. Note: no-op ComputeBudget instructions can exist on a V1 instruction. These have no relevance to actual v1 resources requested. They should be disregarded. -
Pass
maxSupportedTransactionVersion: 1ongetTransactionandgetBlock. It is a ceiling, not a suggestion: leave it at0and reading back a v1 transaction fails with-32015while the transaction itself is onchain.
SDK Cheat Sheet
Use these APIs to build, send, and read v1 transactions.
TypeScript — @solana/kit@solana/kit 8.0.0 or later+
| Task | API |
|---|---|
| Build a v1 message | createTransactionMessage({ version: 1 }) |
| Set resource limits | setTransactionMessageComputeUnitLimitsetTransactionMessageLoadedAccountsDataSizeLimitsetTransactionMessageHeapSizesetTransactionMessagePriorityFeeLamports |
| Reserve limit space before simulating | fillTransactionMessageProvisoryResourceLimits |
| Measure limits by simulation | estimateResourceLimitsFactory |
| Write measured limits back | estimateAndSetResourceLimitsFactory |
| Decode a transaction off the wire | getTransactionDecodergetCompiledTransactionMessageDecoderdecompileTransactionMessage |
| Read the config | message.config on the v1 arm of TransactionMessage |
| Opt in to reading v1 | maxSupportedTransactionVersion: 1 on getTransaction / getBlock |
The compute unit limit, loaded accounts data size limit, and heap size setters
work for messages of any version. Priority fees differ: v1 stores a total in
lamports, while legacy and v0 use micro-lamports per compute unit. Use
setTransactionMessagePriorityFeeLamports for v1 and
setTransactionMessageComputeUnitPrice for legacy and v0; Kit enforces this
distinction with its types.
Rust — solana-*solana-* 4.2.x+
| Task | API |
|---|---|
| Build a v1 message | v1::Message::try_compile_with_config |
| Set resource limits | v1::TransactionConfig::empty().with_compute_unit_limit(…).with_loaded_accounts_data_size_limit(…).with_heap_size(…).with_priority_fee(…) |
| Reserve limit space before simulating | — |
| Measure limits by simulation | Read unitsConsumed and loadedAccountsDataSize from simulateTransaction. |
| Write measured limits back | — |
| Decode a transaction off the wire | VersionedTransaction deserialization, or EncodedTransaction::decode on an RPC response |
| Read the config | VersionedMessage::V1(m) => m.config |
| Opt in to reading v1 | max_supported_transaction_version: Some(1) on RpcTransactionConfig / RpcBlockConfig |
Full sample code for TypeScript, Rust, Go, and Python are available on GitHub.
Technical Details
Size Limit
The new per-transaction size limit is 4096 bytes, up from 1232. The previous limit was set by Solana's conservative use of a 1280-byte MTU (Maximum Transmission Unit), which after overhead left 1232 bytes for transaction payload.
In 2022, QUIC became the default transaction ingestion protocol, which does not specify a maximum stream size, so the maximum transaction size can be raised.
The ceiling stays at 4096 rather than going larger as this matches the standard 4 KiB memory page used by validator hardware, which keeps per-transaction memory handling cheap and avoids forcing a single transaction to span multiple pages.
Once a transaction exceeds the MTU, it has to be split across multiple QUIC frames, and a single dropped frame triggers a retransmit of the whole set. This makes larger transactions more expensive to receive reliably and puts more pressure on validator in-flight buffers. Keeping the transaction size under 4096 avoids this problem.
A 4096-byte ceiling covers a meaningful share of the use cases that have relied on bundles, letting developers land them as individual atomic transactions instead. Because larger transactions consume more validator bandwidth, the scheduler is expected to require higher priority fees to land them than smaller transactions of equivalent priority.
v1 Format
The larger size is only available in the v1 transaction format, specified in
SIMD-0385.
v1 reorders the transaction envelope. Signatures move to the tail and the
transaction discriminator moves to the first slot (offset zero), letting
infrastructure identify the format without deserializing anything. For v1, that
discriminator is the byte 129 (0x81).
0x81 at byte 0 and appends signatures last with no length prefix — the count is implied by the header. The config mask and its values are new.The config mask and values are new: compute unit limit, loaded accounts data
size limit, heap size, and priority fee live at fixed positions in the message
instead of inside ComputeBudgetProgram instructions. This means the network
can rank a transaction by priority fee with a single fixed-offset read instead
of scanning and deserializing its instruction list.
The limits also change. v1 transactions can be larger but must be flatter:
| Limit | legacy | v0 | v1 |
|---|---|---|---|
| Transaction size | 1232 bytes | 1232 bytes | 4096 bytes |
| Account addresses | ~32, size-bound | 64, via lookup tables | 64, inline |
| Address lookup tables | not supported | supported | not supported |
| Duplicate addresses | allowed | allowed | rejected |
The legacy and v0 numbers above are runtime limits, not format limits. Both formats can encode more accounts than they can execute — the runtime rejects any transaction locking more than 64 accounts. Legacy transactions cannot reach even that: 64 inline addresses at 32 bytes each is 2048 bytes, so a legacy transaction runs out of its 1232-byte budget somewhere around 32 addresses, depending on how many signatures and how much instruction data it carries. v0 reaches the full 64 by referencing lookup-table addresses at 1 byte each instead — which is the whole point of lookup tables. v1 reaches the same 64 with plain inline addresses, because 2048 bytes fits comfortably inside 4096, and makes 64 an explicit format limit checked at sanitization — so dropping lookup tables costs nothing in practice. A draft proposal, SIMD-0596, would raise the account limit to 96.
The larger size enables transaction-level workloads that did not fit inside 1232 bytes, including ZK proofs such as those used by Confidential Transfers, Winternitz one-time signatures, nested multisigs often used by institutions, and signature schemes like BLS.
About This Upgrade
Larger transaction sizes is a significant usability improvement for Solana developers. By raising the per-transaction byte limit to 4096 bytes, this upgrade lets developers land previously oversized workloads as single atomic transactions instead of stitching them together with address lookup tables or Jito bundles. In practice this also lowers cost and latency - there are fewer signatures to pay and one single confirmation rather than several chained transactions.
This upgrade addresses a practical ceiling that application teams have had to design around since the early days of Solana, and brings a whole new class of capabilities that we're looking forward to support.
The format details are specified in
SIMD-0385
and the size increase in
SIMD-0296.
See also
developer documentation on versioned transactions
and transaction-v1-examples for runnable code in Rust, TypeScript,
Python, and Go.
Learn more: Solana Upgrades