Transactions
To interact with the Solana network, you must send a transaction. You can think of a transaction as an envelope that holds several forms. Each form is an instruction that tells the network what to do. Sending the transaction is like mailing the envelope so the forms can be processed.
The example below shows a simplified version of two transactions. When the first transaction is processed, it will execute a single instruction. When the second transaction is processed, it will execute three instructions in sequential order: first instruction 1, followed by instruction 2, followed by instruction 3.
Transactions are atomic: If a single instruction fails, the entire transaction will fail and no changes will occur.
A simplified diagram showing two transactions
A Transaction
consists of the following information:
signatures: An array of signaturesmessage: Transaction information, including the list of instructions to be processed
pub struct Transaction {#[wasm_bindgen(skip)]#[serde(with = "short_vec")]pub signatures: Vec<Signature>,#[wasm_bindgen(skip)]pub message: Message,}
Diagram showing the two parts of a transaction
Transactions have a total size limit of
1232 bytes.
This limit includes both the signatures array
and the message struct.
This limit comes from the IPv6 Maximum Transmission Unit (MTU) size of 1280 bytes, minus 48 bytes for network headers (40 bytes IPv6 + 8 bytes header).
Diagram showing the transaction format and size limits
Signatures
The transaction's signatures array contains Signature structs.
Each Signature
is 64 bytes and is created by signing the transaction's Message with the account's private key.
A signature must be provided for each signer account
included on any of the transaction's instructions.
The first signature belongs to the account that will pay the transaction's base fee and is the transaction signature. The transaction signature can be used to look up the transaction's details on the network.
Message
The transaction's message is a Message
struct that contains the following information:
header: The message headeraccount_keys: An array of account addresses required by the transaction's instructionsrecent_blockhash: A blockhash that acts as a timestamp for the transactioninstructions: An array of instructions
To save space, the transaction does not store permissions for each account individually.
Instead, account permissions are determined using the header and account_keys.
pub struct Message {/// The message header, identifying signed and read-only `account_keys`.pub header: MessageHeader,/// All the account keys used by this transaction.#[serde(with = "short_vec")]pub account_keys: Vec<Pubkey>,/// The id of a recent ledger entry.pub recent_blockhash: Hash,/// Programs that will be executed in sequence and committed in/// one atomic transaction if all succeed.#[serde(with = "short_vec")]pub instructions: Vec<CompiledInstruction>,}
Header
The message's header is a MessageHeader
struct.
It contains the following information:
num_required_signatures: The total number of signatures required by the transactionnum_readonly_signed_accounts: The total number of read-only accounts that require signaturesnum_readonly_unsigned_accounts: The total number of read-only accounts that don't require signatures
pub struct MessageHeader {/// The number of signatures required for this message to be considered/// valid. The signers of those signatures must match the first/// `num_required_signatures` of [`Message::account_keys`].pub num_required_signatures: u8,/// The last `num_readonly_signed_accounts` of the signed keys are read-only/// accounts.pub num_readonly_signed_accounts: u8,/// The last `num_readonly_unsigned_accounts` of the unsigned keys are/// read-only accounts.pub num_readonly_unsigned_accounts: u8,}
Diagram showing the three parts of the message header
Account addresses
The message's account_keys
is an array of account addresses, sent in compact array format.
The array's prefix indicates its length.
Each item in the array is a public key, pointing to an account used by its instructions.
The accounts_keys array must be complete, and strictly ordered, as follows:
- Signer + Writable
- Signer + Read-only
- Not signer + Writable
- Not signer + Read-only
Strict ordering allows the account_keys array to be combined with the information in the
message's header to determine the permissions for each account.
Diagram showing the order of the account addresses array
Recent blockhash
The message's recent_blockhash is a hash value that acts as a transaction timestamp
and prevents duplicate transactions.
A blockhash expires after 150 blocks.
(Equivalent to one minute—assuming each block is 400ms.)
After the block expires, the transaction is expired and cannot be processed.
The getLatestBlockhash RPC method
allows you to get the current blockhash and last block height at which the blockhash
will be valid.
Instructions
The message's instructions
is an array of all the instructions to be processed, sent in compact array format.
The array's prefix indicates its length.
Each item in the array is a CompiledInstruction
struct and includes the following information:
program_id_index: An index pointing to an address in theaccount_keysarray. This value indicates the address of the program that processes the instruction.accounts: An array of indices pointing to addresses in theaccount_keysarray. Each index points to the address of an account required for this instruction.data: A byte array specifying which instruction to invoke on the program. It also includes any additional data required by the instruction. (For example, function arguments)
pub struct CompiledInstruction {/// Index into the transaction keys array indicating the program account that executes this instruction.pub program_id_index: u8,/// Ordered indices into the transaction keys array indicating which accounts to pass to the program.#[serde(with = "short_vec")]pub accounts: Vec<u8>,/// The program input data.#[serde(with = "short_vec")]pub data: Vec<u8>,}
Compact array of Instructions
Example transaction structure
The following example shows the structure of a transaction that contains a single SOL transfer instruction.
import {createSolanaRpc,generateKeyPairSigner,lamports,createTransactionMessage,setTransactionMessageFeePayerSigner,setTransactionMessageLifetimeUsingBlockhash,appendTransactionMessageInstructions,pipe,signTransactionMessageWithSigners,getCompiledTransactionMessageDecoder} from "@solana/kit";import { getTransferSolInstruction } from "@solana-program/system";const rpc = createSolanaRpc("http://localhost:8899");const { value: latestBlockhash } = await rpc.getLatestBlockhash().send();// Generate sender and recipient keypairsconst sender = await generateKeyPairSigner();const recipient = await generateKeyPairSigner();// Define the amount to transferconst LAMPORTS_PER_SOL = 1_000_000_000n;const transferAmount = lamports(LAMPORTS_PER_SOL / 100n); // 0.01 SOL// Create a transfer instruction for transferring SOL from sender to recipientconst transferInstruction = getTransferSolInstruction({source: sender,destination: recipient.address,amount: transferAmount});// Create transaction messageconst transactionMessage = pipe(createTransactionMessage({ version: 0 }),(tx) => setTransactionMessageFeePayerSigner(sender, tx),(tx) => setTransactionMessageLifetimeUsingBlockhash(latestBlockhash, tx),(tx) => appendTransactionMessageInstructions([transferInstruction], tx));const signedTransaction =await signTransactionMessageWithSigners(transactionMessage);// Decode the messageBytesconst compiledTransactionMessage =getCompiledTransactionMessageDecoder().decode(signedTransaction.messageBytes);console.log(JSON.stringify(compiledTransactionMessage, null, 2));
The code below shows the output from the previous code snippets. The format differs between SDKs, but notice that each instruction contains the same required information.
{"version": 0,"header": {"numSignerAccounts": 1,"numReadonlySignerAccounts": 0,"numReadonlyNonSignerAccounts": 1},"staticAccounts": ["HoCy8p5xxDDYTYWEbQZasEjVNM5rxvidx8AfyqA4ywBa","5T388jBjovy7d8mQ3emHxMDTbUF8b7nWvAnSiP3EAdFL","11111111111111111111111111111111"],"lifetimeToken": "EGCWPUEXhqHJWYBfDirq3mHZb4qDpATmYqBZMBy9TBC1","instructions": [{"programAddressIndex": 2,"accountIndices": [0, 1],"data": {"0": 2,"1": 0,"2": 0,"3": 0,"4": 128,"5": 150,"6": 152,"7": 0,"8": 0,"9": 0,"10": 0,"11": 0}}]}
After a transaction is submitted, you can retrieve its details using the transaction signature and the getTransaction RPC method. The response will have a structure similar to the following snippet.
You can also find the transaction using Solana Explorer.
{"blockTime": 1745196488,"meta": {"computeUnitsConsumed": 150,"err": null,"fee": 5000,"innerInstructions": [],"loadedAddresses": {"readonly": [],"writable": []},"logMessages": ["Program 11111111111111111111111111111111 invoke [1]","Program 11111111111111111111111111111111 success"],"postBalances": [989995000, 10000000, 1],"postTokenBalances": [],"preBalances": [1000000000, 0, 1],"preTokenBalances": [],"rewards": [],"status": {"Ok": null}},"slot": 13049,"transaction": {"message": {"header": {"numReadonlySignedAccounts": 0,"numReadonlyUnsignedAccounts": 1,"numRequiredSignatures": 1},"accountKeys": ["8PLdpLxkuv9Nt8w3XcGXvNa663LXDjSrSNon4EK7QSjQ","7GLg7bqgLBv1HVWXKgWAm6YoPf1LoWnyWGABbgk487Ma","11111111111111111111111111111111"],"recentBlockhash": "7ZCxc2SDhzV2bYgEQqdxTpweYJkpwshVSDtXuY7uPtjf","instructions": [{"accounts": [0, 1],"data": "3Bxs4NN8M2Yn4TLb","programIdIndex": 2,"stackHeight": null}],"indexToProgramIds": {}},"signatures": ["3jUKrQp1UGq5ih6FTDUUt2kkqUfoG2o4kY5T1DoVHK2tXXDLdxJSXzuJGY4JPoRivgbi45U2bc7LZfMa6C4R3szX"]},"version": "legacy"}
Is this page helpful?