Versioned Transactions

Summary

Solana has three transaction formats: legacy, v0, and v1. v0 adds Address Lookup Tables (ALTs) for referencing accounts via 1-byte indices. v1 raises the size limit to 4,096 bytes, moves resource limits into the message itself, and removes ALTs.

Solana supports three transaction formats: legacy, v0, and v1. Each one is described below with the same three parts: how it lays out its bytes on the wire, how it references accounts, and where its resource limits come from.

v1 activation status

The v1 format is not yet active on any cluster. Activation is targeted with Agave v4.2. solana-test-validator 4.2+ enables you to test v1 transactions locally. Existing apps should review preparing for v1.

Format comparison

Limitlegacyv0v1
Max transaction size1,232 bytes1,232 bytes4,096 bytes
Account addresses~32, size-bound64, via lookup tables64, inline
Address lookup tablesnot supportedsupportednot supported
Resource limitsComputeBudget instructionsComputeBudget instructionsmessage config

Jump to: Legacy · v0 · v1

Legacy format

The original format, and still the default in most tooling. It has no version prefix at all: the first byte of the transaction is the compact-u16 count of the signature array, and the first byte of the message is num_required_signatures, whose high bit is always unset.

Legacy wire layout

FieldSizeDescription
num_signaturescompact-u16Number of signatures
signaturesnum_signatures x 64 bytesEd25519 signatures
header3 bytesMessageHeader — first byte has version bit unset
num_account_keyscompact-u16Number of account keys
account_keysnum_account_keys x 32 bytesPublic keys, all inline
recent_blockhash32 bytesLifetime specifier
num_instructionscompact-u16Number of instructions
instructionsvariableEach instruction serialized contiguously

Every variable-length array is prefixed with a compact-u16 length: 1 byte for values 0–127, 2–3 bytes for larger values. For the per-instruction layout and a worked size calculation, see transaction binary format.

Accounts in legacy

Every account is written out as a full 32-byte public key in account_keys, and instructions reference them by 1-byte index into that array. There is no way to reference an account that is not spelled out in the transaction, which is what bounds a legacy transaction to roughly 32 accounts before it runs out of its 1,232 bytes.

Resource limits in legacy

Compute unit limit, loaded accounts data size limit, heap size, and priority fee are all requested by including ComputeBudget program instructions in the transaction. Each one costs an instruction slot and 150 compute units. Omitting them is safe: the runtime falls back to defaults of 200,000 CU per instruction (capped at 1.4M), a 64 MiB data size limit, a 32 KiB heap, and a priority fee of zero.

V0 format

v0 is a legacy message plus two things: a 0x80 version prefix byte and an address_table_lookups array appended after the instructions. Everything before those is byte-identical to legacy.

V0 wire layout

FieldSizeDescription
num_signaturescompact-u16Number of signatures
signaturesnum_signatures x 64 bytesEd25519 signatures
0x801 byteVersion prefix byte — first byte of the message
header3 bytesMessageHeader (same as legacy)
num_account_keyscompact-u16Number of static account keys
static_account_keysnum_account_keys x 32 bytesKeys that appear literally in the transaction
recent_blockhash32 bytesLifetime specifier
num_instructionscompact-u16Number of instructions
instructionsvariableSame format as legacy
address_table_lookupscompact-u16 + variableALT references (see below)

Each address table lookup entry contains:

FieldSizeDescription
account_key32 bytesThe ALT account's public key
writable_indexescompact-u16 + N x 1 byteIndices into the ALT for writable accounts
readonly_indexescompact-u16 + N x 1 byteIndices into the ALT for read-only accounts

Address lookup tables

An ALT is an onchain account that stores up to 256 public keys. By referencing an ALT, a transaction can include additional accounts using 1-byte indices instead of 32-byte public keys, significantly reducing per-account overhead.

At runtime, before execution begins, the validator resolves all ALT references into full public keys. The resolved addresses are appended to the static account keys to form the complete account keys list. ALT-resolved accounts follow the same ordering as static accounts: writable lookups come before read-only lookups.

Address lookup tables only affect how accounts are referenced in the on-wire transaction. At execution time, the runtime resolves all indices to full account addresses. ALT-resolved accounts can only be writable or read-only (non-signer); they cannot be signers.

Resource limits in v0

Unchanged from legacy: ComputeBudget instructions, with the same defaults when they are omitted.

V1 format

v1 raises the size limit to 4,096 bytes and restructures the message around a transaction config: resource limits move out of ComputeBudget instructions and into fixed-position fields in the message itself. This lets the network rank a transaction by priority fee with a single fixed-offset read instead of scanning and deserializing its instruction list.

V1 wire layout

FieldSizeDescription
0x811 byteVersion prefix byte — the first byte of the transaction
header3 bytesMessageHeader (same as legacy)
config_mask4 bytesu32 LE bitmask marking which config values are present
recent_blockhash32 bytesLifetime specifier
num_instructions1 byteFixed-width count, max 64
num_addresses1 byteFixed-width count, max 64
addressesN x 32 bytesAccount addresses, all inline — no lookup table references
config_values0–20 bytesOne value per set mask bit, in bit order (see below)
instruction_headersN x 4 bytesPer instruction: program_id_index (u8), num_accounts (u8), data_len (u16 LE)
instruction_payloadsvariablePer instruction: account indices, then instruction data
signaturesN x 64 bytesAt the tail, with no length prefix — the count comes from the header

Two structural differences from legacy and v0 are worth noting when writing a decoder. The counts are fixed-width u8 fields rather than compact-u16, and the instructions are split into two runs: every fixed-size header first, then every variable-length payload, instead of each instruction being contiguous.

Accounts in v1: no address lookup tables

v1 removes ALT support deliberately. 64 raw addresses is 2,048 bytes, comfortably inside the 4,096-byte limit, so every address is inline as it is in legacy. If your application depends on lookup tables, moving to v1 means inlining those addresses.

Resource limits in v1: the transaction config

The config is a u32 bitmask followed by fixed-width values for each field whose bit is set:

Bit(s)FieldWidthNotes
0–1Priority feeu64Total lamports — both bits set together
2Compute unit limitu32
3Loaded accounts data size limitu32
4Requested heap sizeu32

Unknown bits are rejected. Because the message is signed, unrecognized config fields cannot be silently dropped.

Priority fee is total lamports, not a price

In legacy and v0, the priority fee is set via SetComputeUnitPrice as micro-lamports per compute unit, multiplied by the compute unit limit. In v1 it is an absolute total in lamports — no multiplication, no rounding. Do not carry the per-CU arithmetic across. The total fee formula is otherwise unchanged: (signatures × lamports_per_signature) + priority_fee.

ComputeBudget instructions are no-ops in v1

A v1 transaction does not reject ComputeBudget instructions — it ignores them for configuration. They still execute as successful no-ops, consuming 150 compute units and one of the 64 instruction slots while having no effect on the budget. Strip them when building v1 transactions, and stop scanning for them when reading v1 transactions: the values live in the message config.

Config fields must be set explicitly

The most important behavioral change for senders: unlike legacy and v0, v1 transactions must set the compute unit limit and loaded accounts data size limit explicitly or your transaction will fail.

Unset fieldlegacy / v0v1Symptom if omitted
Compute unit limit200k per instruction, max 1.4M0 CUFails immediately, out of budget
Loaded accounts data size64 MiB0 bytesMaxLoadedAccountsDataSizeExceeded on the first account loaded
Priority fee00
Heap size32 KiB32 KiB

The recommended approach is to simulate once with both limits maxed, then write the returned unitsConsumed and loadedAccountsDataSize back into the config, rounding the data size up to the next 32 KiB page for headroom (the block cost model charges in 32 KiB pages, so headroom below the next page boundary is free).

Preparing for v1

v1 changes reading transactions, not just sending them. When v1 activates, any client that calls getTransaction or getBlock without opting in will start failing on v1 transactions:

  • Pass maxSupportedTransactionVersion: 1 — the JSON integer 1, not the string "1" — to getTransaction and getBlock. Passing 0 fails on v1 transactions exactly like omitting the parameter, so a codebase updated during the v0 rollout still needs the value changed.
  • getTransaction fails on a v1 transaction with error -32015, and one v1 transaction fails an entire getBlock response with the same error — there is no partial result.
  • blockSubscribe emits block: null and stops advancing, so a consumer that reads that as an empty block silently falls behind from the first v1 slot onward.
  • getSignaturesForAddress never inspects transaction bodies, so v1 signatures list normally.
  • Opted-in responses include a transactionConfig object in the message for v1 transactions (absent entirely for legacy and v0). Pipelines that derive priority fees or compute limits by scanning for ComputeBudget instructions will silently report zero for every v1 transaction.
  • Use encoding: 'base64' when you decode transactions client-side, and for sendTransaction/simulateTransaction with transactions over 1,232 bytes — base58 encoding remains capped at the old size.
  • Client library support requires recent versions: @solana/kit 8.0+, Agave 4.2.x-generation Rust crates, or web3.js v3. web3.js v1 reads v1 from 1.99.0 onward, but cannot build or send it.

For the full migration guide — including client library support, streaming (Geyser/gRPC) version detection, and simulation behavior — see the Transaction Format v1 upgrade page.

Is this page helpful?

© 2026 Solana Foundation. All rights reserved.