On Solana, we send transactions tointeract with the network. Transactions include one or moreinstructions, each representing aspecific operation to be processed. The execution logic for instructions isstored on programs deployed to the Solana network, whereeach program stores its own set of instructions.
Below are key details about how transactions are executed:
- Execution Order: If a transaction includes multiple instructions, theinstructions are processed in the order they are added to the transaction.
- Atomicity: A transaction is atomic, meaning it either fully completes with allinstructions successfully processed, or fails altogether. If any instructionwithin the transaction fails, none of the instructions are executed.
For simplicity, a transaction can be thought of as a request to process one ormultiple instructions.
Transaction Simplified
You can imagine a transaction as an envelope, where each instruction is adocument that you fill out and place inside the envelope. We then mail out theenvelope to process the documents, just like sending a transaction on thenetwork to process our instructions.
Key Points #
Solana transactions consist of instructions that interact with variousprograms on the network, where each instruction represents a specificoperation.
Each instruction specifies the program to execute the instruction, theaccounts required by the instruction, and the data required for theinstruction's execution.
Instructions within a transaction are processed in the order they are listed.
Transactions are atomic, meaning either all instructions process successfully,or the entire transaction fails.
The maximum size of a transaction is 1232 bytes.
Basic Example #
Below is a diagram representing a transaction with a single instruction totransfer SOL from a sender to a receiver.
Individual "wallets" on Solana are accounts owned by theSystem Program. As part of theSolana Account Model, only the program that owns anaccount is allowed to modify the data on the account.
Therefore, transferring SOL from a "wallet" account requires sending atransaction to invoke the transfer instruction on the System Program.
SOL Transfer
The sender account must be included as a signer (is_signer
) on the transactionto approve the deduction of their lamport balance. Both the sender and recipientaccounts must be mutable (is_writable
) because the instruction modifies thelamport balance for both accounts.
Once the transaction is sent, the System Program is invoked to process thetransfer instruction. The System Program then updates the lamport balances ofboth the sender and recipient accounts accordingly.
SOL Transfer Process
Simple SOL Transfer #
Here is a Solana Playgroundexample of how to build a SOL transfer instruction using theSystemProgram.transfer
method:
// Define the amount to transferconst transferAmount = 0.01; // 0.01 SOL // Create a transfer instruction for transferring SOL from wallet_1 to wallet_2const transferInstruction = SystemProgram.transfer({ fromPubkey: sender.publicKey, toPubkey: receiver.publicKey, lamports: transferAmount * LAMPORTS_PER_SOL, // Convert transferAmount to lamports}); // Add the transfer instruction to a new transactionconst transaction = new Transaction().add(transferInstruction);
Run the script and inspect the transaction details logged to the console. In thesections below, we'll walk through the details of what's happening under thehood.
Transaction #
A Solanatransactionconsists of:
- Signatures:An array of signatures included on the transaction.
- Message:List of instructions to be processed atomically.
Transaction Format
The structure of a transaction message comprises of:
- Message Header: Specifies the numberof signer and read-only account.
- Account Addresses: Anarray of account addresses required by the instructions on the transaction.
- Recent Blockhash: Acts as atimestamp for the transaction.
- Instructions: An array ofinstructions to be executed.
Transaction Message
Transaction Size #
The Solana network adheres to a maximum transmission unit (MTU) size of 1280bytes, consistent with the IPv6 MTUsize constraints to ensure fast and reliable transmission of cluster informationover UDP. After accounting for the necessary headers (40 bytes for IPv6 and 8bytes for the fragment header),1232 bytes remain available for packet data,such as serialized transactions.
This means that the total size of a Solana transaction is limited to 1232 bytes.The combination of the signatures and the message cannot exceed this limit.
- Signatures: Each signature requires 64 bytes. The number of signatures canvary, depending on the transaction's requirements.
- Message: The message includes instructions, accounts, and additional metadata,with each account requiring 32 bytes. The combined size of the accounts plusmetadata can vary, depending on the instructions included in the transaction.
Transaction Format
Message Header #
Themessage headerspecifies the privileges of accounts included in the transaction's accountaddress array. It is comprised of three bytes, each containing a u8 integer,which collectively specify:
- The number of required signatures for the transaction.
- The number of read-only account addresses that require signatures.
- The number of read-only account addresses that do not require signatures.
Message Header
Compact-Array Format #
A compact array in the context of a transaction message refers to an arrayserialized in the following format:
- The length of the array, encoded ascompact-u16.
- The individual items of the array listed sequentially after the encodedlength.
Compact array format
This encoding method is used to specify the lengths of both theAccount Addresses andInstructions arrays within atransaction message.
Array of Account Addresses #
A transaction message includes an array containing all theaccount addressesneeded for the instructions within the transaction.
This array starts with acompact-u16 encoding of thenumber of account addresses, followed by the addresses ordered by the privilegesfor the accounts. The metadata in the message header is used to determine thenumber of accounts in each section.
- Accounts that are writable and signers
- Accounts that are read-only and signers
- Accounts that are writable and not signers
- Accounts that are read-only and not signers
Compact array of account addresses
Recent Blockhash #
All transactions include arecent blockhashto act as a timestamp for the transaction. The blockhash is used to preventduplications and eliminate stale transactions.
The maximum age of a transaction's blockhash is 150 blocks (~1 minute assuming400ms block times). If a transaction's blockhash is 150 blocks older than thelatest blockhash, it is considered expired. This means that transactions notprocessed within a specific timeframe will never be executed.
You can use the getLatestBlockhash RPCmethod to get the current blockhash and last block height at which the blockhashwill be valid. Here is an example onSolana Playground.
Array of Instructions #
A transaction message includes an array of allinstructionsrequesting to be processed. Instructions within a transaction message are in theformat ofCompiledInstruction.
Much like the array of account addresses, this compact array starts with acompact-u16 encoding of thenumber of instructions, followed by an array of instructions. Each instructionin the array specifies the following information:
- Program ID: Identifies an on-chain program that will process theinstruction. This is represented as an u8 index pointing to an accountaddress within the account addresses array.
- Compact array of account address indexes: Array of u8 indexes pointing tothe account addresses array for each account required by the instruction.
- Compact array of opaque u8 data: A u8 byte array specific to the programinvoked. This data specifies the instruction to invoke on the program alongwith any additional data that the instruction requires (such as functionarguments).
Compact array of Instructions
Example Transaction Structure #
Below is an example of the structure of a transaction including a singleSOL transfer instruction. It shows themessage details including the header, account keys, blockhash, and theinstructions, along with the signature for the transaction.
header
: Includes data used to specify the read/write and signer privilegesin theaccountKeys
array.accountKeys
: Array including account addresses for all instructions on thetransaction.recentBlockhash
: The blockhash included on the transaction when thetransaction was created.instructions
: Array including all the instructions on the transaction. Eachaccount
andprogramIdIndex
in an instruction references theaccountKeys
array by index.signatures
: Array including signatures for all accounts required as signersby the instructions on the transaction. A signature is created by signing thetransaction message using the corresponding private key for an account.
"transaction": { "message": { "header": { "numReadonlySignedAccounts": 0, "numReadonlyUnsignedAccounts": 1, "numRequiredSignatures": 1 }, "accountKeys": [ "3z9vL1zjN6qyAFHhHQdWYRTFAcy69pJydkZmSFBKHg1R", "5snoUseZG8s8CDFHrXY2ZHaCrJYsW457piktDmhyb5Jd", "11111111111111111111111111111111" ], "recentBlockhash": "DzfXchZJoLMG3cNftcf2sw7qatkkuwQf4xH15N5wkKAb", "instructions": [ { "accounts": [ 0, 1 ], "data": "3Bxs4NN8M2Yn4TLb", "programIdIndex": 2, "stackHeight": null } ], "indexToProgramIds": {} }, "signatures": [ "5LrcE2f6uvydKRquEJ8xp19heGxSvqsVbcqUeFoiWbXe8JNip7ftPQNTAVPyTK7ijVdpkzmKKaAQR7MWMmujAhXD" ] }
Instruction #
Aninstructionis a request to process a specific action on-chain and is the smallestcontiguous unit of execution logic in aprogram.
When building an instruction to add to a transaction, each instruction mustinclude the following information:
- Program address: Specifies the program being invoked.
- Accounts: Lists every account the instruction reads from or writes to,including other programs, using the
AccountMeta
struct. - Instruction Data: A byte array that specifies whichinstruction handler on the program toinvoke, plus any additional data required by the instruction handler (functionarguments).
Transaction Instruction
AccountMeta #
For every account required by an instruction, the following info must bespecified:
pubkey
: The on-chain address of an accountis_signer
: Specify if the account is required as a signer on the transactionis_writable
: Specify if the account data will be modified
This information is referred to as theAccountMeta.
AccountMeta
By specifying all accounts required by an instruction, and whether each accountis writable, transactions can be processed in parallel.
For example, two transactions that do not include any accounts that write to thesame state can be executed at the same time.
Example Instruction Structure #
Below is an example of the structure of aSOL transfer instruction which detailsthe account keys, program ID, and data required by the instruction.
keys
: Includes theAccountMeta
for each account required by aninstruction.programId
: The address of the program which contains the execution logic forthe instruction invoked.data
: The instruction data for the instruction as a buffer of bytes
{ "keys": [ { "pubkey": "3z9vL1zjN6qyAFHhHQdWYRTFAcy69pJydkZmSFBKHg1R", "isSigner": true, "isWritable": true }, { "pubkey": "BpvxsLYKQZTH42jjtWHZpsVSa7s6JVwLKwBptPSHXuZc", "isSigner": false, "isWritable": true } ], "programId": "11111111111111111111111111111111", "data": [2,0,0,0,128,150,152,0,0,0,0,0]}
Expanded Example #
The details for building program instructions are often abstracted away byclient libraries. However, if one is not available, you can always fall back tomanually building the instruction.
Manual SOL Transfer #
Here is a Solana Playgroundexample of how to manually build the a SOL transfer instruction:
// Define the amount to transferconst transferAmount = 0.01; // 0.01 SOL // Instruction index for the SystemProgram transfer instructionconst transferInstructionIndex = 2; // Create a buffer for the data to be passed to the transfer instructionconst instructionData = Buffer.alloc(4 + 8); // uint32 + uint64// Write the instruction index to the bufferinstructionData.writeUInt32LE(transferInstructionIndex, 0);// Write the transfer amount to the bufferinstructionData.writeBigUInt64LE(BigInt(transferAmount * LAMPORTS_PER_SOL), 4); // Manually create a transfer instruction for transferring SOL from sender to receiverconst transferInstruction = new TransactionInstruction({ keys: [ { pubkey: sender.publicKey, isSigner: true, isWritable: true }, { pubkey: receiver.publicKey, isSigner: false, isWritable: true }, ], programId: SystemProgram.programId, data: instructionData,}); // Add the transfer instruction to a new transactionconst transaction = new Transaction().add(transferInstruction);
Under the hood, thesimple example using theSystemProgram.transfer
method is functionally equivalent to the more verboseexample above. The SystemProgram.transfer
method simply abstracts away thedetails of creating the instruction data buffer and AccountMeta
for eachaccount required by the instruction.