Overview
Transactions are the fundamental unit of state change on Solana. This guide covers creating instructions, building transactions, managing signers, and using the transaction skeleton pattern.
Creating Instructions
Instructions are the building blocks of transactions.
Basic Instruction Creation
import software.sava.core.tx.Instruction;
import software.sava.core.accounts.PublicKey;
import software.sava.core.accounts.meta.AccountMeta;
import java.util.List;
PublicKey programId = PublicKey . fromBase58Encoded ( "YourProgramId" );
// Define instruction accounts
List < AccountMeta > accounts = List . of (
AccountMeta . createWrite (sourceAccount),
AccountMeta . createWrite (destinationAccount),
AccountMeta . createReadOnlySigner (authority)
);
// Create instruction data
byte [] data = new byte [] { 0 , 1 , 2 , 3 }; // Your instruction data
// Create the instruction
Instruction instruction = Instruction . createInstruction (
programId,
accounts,
data
);
Source: Instruction.java:33-42
The program ID can be wrapped as invoked account metadata:
AccountMeta programMeta = AccountMeta . createInvoked (programId);
Instruction instruction = Instruction . createInstruction (
programMeta,
accounts,
data
);
Source: Instruction.java:15-24
Instructions support adding additional accounts:
Instruction baseInstruction = Instruction . createInstruction (
programId,
accounts,
data
);
// Add single account
AccountMeta extraAccount = AccountMeta . createRead (somePublicKey);
Instruction withExtra = baseInstruction . extraAccount (extraAccount);
// Add multiple accounts
List < AccountMeta > extraAccounts = List . of (
AccountMeta . createRead (account1),
AccountMeta . createWrite (account2)
);
Instruction withMultiple = baseInstruction . extraAccounts (extraAccounts);
Source: Instruction.java:45-50
Building Transactions
Simple Transaction
Create a transaction from instructions:
import software.sava.core.tx.Transaction;
import software.sava.core.accounts.PublicKey;
import software.sava.core.tx.Instruction;
import java.util.List;
PublicKey feePayer = PublicKey . fromBase58Encoded ( "FeePayerAddress" );
List < Instruction > instructions = List . of (
instruction1,
instruction2
);
// Create transaction
Transaction transaction = Transaction . createTx (feePayer, instructions);
Source: Transaction.java:90-91
Single Instruction Transaction
// For single instruction transactions
Transaction transaction = Transaction . createTx (feePayer, instruction);
Source: Transaction.java:177-179
Transaction Without Fee Payer
Create a transaction and add the fee payer later:
// Fee payer will be determined from signers
Transaction transaction = Transaction . createTx (instructions);
Source: Transaction.java:126-128
Step 1: Create Instructions
Define all instructions with their required accounts and data.
Step 2: Specify Fee Payer
Identify which account will pay transaction fees.
Step 3: Build Transaction
Call Transaction.createTx() with your instructions and fee payer.
Step 4: Set Recent Blockhash
Add a recent blockhash before signing (covered in the next guide).
Managing Signers
Multiple Signers
Transactions support multiple signers:
import software.sava.core.accounts.Signer;
import java.util.List;
Signer feePayer = // ... your fee payer signer
Signer authority1 = // ... first authority
Signer authority2 = // ... second authority
List < Signer > signers = List . of (
feePayer,
authority1,
authority2
);
// Transaction will track required signers
Transaction transaction = Transaction . createTx (
feePayer . publicKey (),
instructions
);
Ensure signer accounts are marked correctly:
// Fee payer is special signer
AccountMeta feePayerMeta = AccountMeta . createFeePayer ( feePayer . publicKey ());
// Regular signer (read-only)
AccountMeta signerMeta = AccountMeta . createReadOnlySigner ( signer . publicKey ());
// Writable signer
AccountMeta writableSignerMeta = AccountMeta . createWritableSigner (
authority . publicKey ()
);
Source: AccountMeta.java:38-50
Using Address Lookup Tables
Lookup tables enable larger transactions by referencing accounts off-chain.
Versioned Transactions with Lookup Tables
import software.sava.core.accounts.lookup.AddressLookupTable;
AddressLookupTable lookupTable = // ... your lookup table
Transaction transaction = Transaction . createTx (
feePayer,
instructions,
lookupTable
);
System . out . println ( "Transaction version: " + transaction . version ());
Source: Transaction.java:130-139
Multiple Lookup Tables
import software.sava.core.accounts.meta.LookupTableAccountMeta;
LookupTableAccountMeta [] tables = // ... your lookup table metas
Transaction transaction = Transaction . createTx (
feePayer,
instructions,
null , // no single lookup table
tables // array of lookup tables
);
Source: Transaction.java:147-158
Transaction Skeleton Pattern
The skeleton pattern allows parsing and analyzing existing transactions.
Deserializing a Transaction
import software.sava.core.tx.TransactionSkeleton;
// From transaction data (e.g., fetched from RPC)
byte [] txData = // ... transaction bytes
TransactionSkeleton skeleton = TransactionSkeleton . deserializeSkeleton (txData);
// Parse instructions
Instruction [] instructions = skeleton . parseLegacyInstructions ();
for ( Instruction ix : instructions) {
System . out . println ( "Program: " + ix . programId (). publicKey (). toBase58 ());
System . out . println ( "Accounts: " + ix . accounts (). size ());
}
Source: SimpleTxSkeletonExample.java:20-21
Real-World Example
Parsing a Pump.fun transaction:
import software.sava.rpc.json.http.client.SolanaRpcClient;
import software.sava.core.tx.TransactionSkeleton;
// Fetch transaction from RPC
var tx = rpcClient . getTransaction (signature). join ();
// Parse the skeleton
var skeleton = TransactionSkeleton . deserializeSkeleton ( tx . data ());
var instructions = skeleton . parseLegacyInstructions ();
// Access specific instruction data
var buyIx = instructions[ 3 ];
var buyAccounts = buyIx . accounts ();
PublicKey mint = buyAccounts . get ( 2 ). publicKey ();
PublicKey buyer = buyAccounts . get ( 6 ). publicKey ();
System . out . println ( "Mint: " + mint . toBase58 ());
System . out . println ( "Buyer: " + buyer . toBase58 ());
Source: SimpleTxSkeletonExample.java:15-37
Prepending and Appending Instructions
Modify existing transactions:
import software.sava.core.tx.Instruction;
Transaction originalTx = Transaction . createTx (feePayer, instructions);
// Prepend compute budget instruction
Instruction computeBudget = // ... create compute budget instruction
Transaction withBudget = originalTx . prependIx (computeBudget);
// Append additional instruction
Instruction extraIx = // ... additional instruction
Transaction extended = withBudget . appendIx (extraIx);
// Prepend multiple instructions
List < Instruction > priorityInstructions = List . of (
computeBudgetIx,
priorityFeeIx
);
Transaction withPriority = originalTx . prependInstructions (priorityInstructions);
Source: Transaction.java:746-754
Transaction Size Limits
Solana has transaction size limits:
Transaction transaction = Transaction . createTx (feePayer, instructions);
int size = transaction . size ();
boolean exceedsLimit = transaction . exceedsSizeLimit ();
if (exceedsLimit) {
System . err . println ( "Transaction exceeds 1232 byte limit: " + size);
// Consider using lookup tables or splitting into multiple transactions
}
Source: Transaction.java:21-26, Transaction.java:724-726
The maximum transaction size is 1232 bytes. Use address lookup tables for transactions that exceed this limit.
Advanced Patterns
Custom Account Sorting
Sava automatically sorts accounts for transactions:
import java.util.Map;
import java.util.HashMap;
// Accounts are automatically sorted: fee payer, signers, writable, read-only
Map < PublicKey , AccountMeta > accountMap = HashMap . newHashMap ( 64 );
// Add accounts
for ( Instruction ix : instructions) {
for ( AccountMeta meta : ix . accounts ()) {
accountMap . merge ( meta . publicKey (), meta, Transaction . MERGE_ACCOUNT_META );
}
}
// Sorted for legacy transactions
AccountMeta [] sorted = Transaction . sortLegacyAccounts (accountMap);
// Sorted for v0 transactions
AccountMeta [] sortedV0 = Transaction . sortV0Accounts (accountMap);
Source: Transaction.java:29-46, Transaction.java:84-88, Transaction.java:277-280
Replacing Instructions
Transaction transaction = Transaction . createTx (feePayer, instructions);
// Replace instruction at index
Instruction newInstruction = // ... replacement
Transaction modified = transaction . replaceInstruction ( 0 , newInstruction);
Source: Transaction.java:756
Best Practices
Place compute budget instructions first
Group related instructions together
Minimize the number of instructions when possible
Mark accounts as writable only when necessary
Reuse account references within a transaction
Use lookup tables for frequently accessed accounts
Monitor transaction size with transaction.size()
Use exceedsSizeLimit() before signing
Consider versioned transactions for complex operations
Working with Accounts Learn about keypairs and account metadata
Signing and Sending Sign and submit your transactions to Solana