How Solana Transactions Are Structured: Legacy vs Versioned
A serialized Solana transaction combines signatures with a compact message that tells the runtime which accounts and programs a transaction needs. Reading that structure helps you distinguish facts present in the bytes from context that requires chain data.
Paste Base64, Base58, or hexadecimal bytes into the Solana Transaction Decoder to inspect legacy and v0 transactions locally. It does not fetch a transaction by signature, query an RPC endpoint, resolve lookup-table contents, simulate, sign, broadcast, or connect a wallet.
Transaction envelope: signatures plus message
The wire transaction begins with a compact signature count, followed by 64-byte Ed25519 signature slots, then the compiled message. Each required signer corresponds to the first signer accounts described by the message header.
A slot containing all zero bytes is commonly used for a missing signature in an unsigned or partially signed transaction. That fact does not say why the signature is absent. A present signature also does not prove that the proposed action is desirable; it only supplies authorization evidence for the exact message bytes.
The message itself contains:
- a format marker: legacy messages have no version prefix, while versioned messages use a high-bit version marker;
- a three-value header describing signer and read-only counts;
- static account keys;
- a recent blockhash or other lifetime token;
- compact compiled instructions;
- address lookup table metadata for formats that support it.
How the message header defines account roles
The header records the number of required signer accounts, read-only signer accounts, and read-only non-signer accounts. Account ordering gives those counts meaning.
Signer accounts come first. Writable signers precede read-only signers. Non-signers follow, with writable non-signers before read-only non-signers. The first account is normally the fee payer.
The Crypto Address Validator can verify that an account string decodes to 32 bytes. It cannot determine account ownership, program code, current balance, or whether a public key is appropriate for the transaction.
Recent blockhash and transaction lifetime
Most transactions include a recent blockhash. Validators use it as part of transaction freshness and duplicate-processing controls. The serialized value is visible offline, but whether it is still recent is live chain state and cannot be established without an RPC query.
Therefore, an offline decoder should label the field without claiming that the transaction is valid for submission. A structurally valid old transaction can decode perfectly while no longer being eligible to land.
Compiled instructions use indexes
Compiled instructions do not repeat full account addresses. Each instruction stores:
- an index identifying the program account;
- a list of account indexes used by that instruction;
- an opaque byte string supplied to the program.
For a legacy transaction, these indexes refer to the static account list. A local decoder can identify a reviewed well-known program when its program index points to a static key such as the System Program, Compute Budget Program, SPL Token Program, or Associated Token Account Program.
Program recognition is narrower than instruction decoding. Raw data bytes only gain meaning under the exact program implementation. Even a familiar program ID does not expose inner instructions, account state, balances, or the result of execution. Use the Base58 Encoder and Decoder when you need to compare raw instruction bytes across Base58 and hexadecimal representations.
Legacy messages
Legacy messages keep every account address directly in the static account list. This makes index resolution self-contained, but also limits how many accounts fit inside the maximum transaction size.
When reviewing a legacy transaction, map every program and account index back to the static list. Account roles indicate requested access, not the final changes execution will make.
v0 messages and address lookup tables
Version 0 messages add address lookup table references. Each reference contains:
- the lookup table account address;
- indexes of table entries to load as writable accounts;
- indexes of table entries to load as read-only accounts.
The serialized transaction does not contain the public keys stored at those table indexes. Resolving them requires the current lookup table account data, normally obtained through RPC. An offline decoder should preserve the table address and index arrays while marking any instruction account or program that depends on them as unresolved.
That boundary matters. Guessing a lookup-resolved program from instruction bytes would turn missing information into false certainty. It is more useful to show an exact unresolved index than an unsupported program label.
Amounts and instruction semantics
Lamports or token amounts appear inside program-specific instruction data, not in a universal transaction field. Once a maintained program decoder establishes an exact integer and you know its unit, use the Lamports to SOL Converter for exact decimal conversion.
A practical offline review checklist
Before seeking live context, record the facts available locally:
- Is the format legacy or v0, and are all required signature slots present?
- Which static accounts are signers, writable, or read-only?
- Which static program IDs are recognized, and which program indexes remain unresolved?
- Which account indexes and raw data bytes does each instruction use?
- Does a v0 message depend on lookup tables that still need independent resolution?
- Is the recent blockhash merely present, or has freshness actually been checked elsewhere?
Then obtain program, lookup-table, and account-state information from an independent official source. Structural decoding makes the transaction legible; simulation and chain state answer different questions.
Primary references
Ready to try it yourself?
Put what you have learned into practice with our free online tool.
Decode a Solana Transaction Locally