Overview
Once you’ve built a transaction, you need to sign it with the required keypairs and submit it to the Solana network. This guide covers transaction signing, using the RPC client to send transactions, and handling responses.
Signing Transactions
Signing with a Single Signer
import software.sava.core.tx.Transaction;
import software.sava.core.accounts.Signer;
import software.sava.core.encoding.Base58;
Transaction transaction = Transaction . createTx (feePayer, instructions);
// Get a recent blockhash
byte [] recentBlockHash = // ... fetch from RPC (see below)
// Sign the transaction
transaction . sign (recentBlockHash, signer);
// Transaction is now ready to send
String base64Tx = transaction . base64EncodeToString ();
Source: Transaction.java:671-678
Signing with Multiple Signers
import java.util.List;
List < Signer > signers = List . of (
feePayer,
authority1,
authority2
);
// Sign with all required signers
transaction . sign (recentBlockHash, signers);
// Encode for submission
String base64Tx = transaction . base64EncodeToString ();
Source: Transaction.java:698-706
Signing and Encoding in One Step
// Sign and encode to Base64 in one call
String base64SignedTx = transaction . signAndBase64Encode (
recentBlockHash,
signer
);
// With multiple signers
String base64SignedTx = transaction . signAndBase64Encode (
recentBlockHash,
signers
);
Source: Transaction.java:666-669, Transaction.java:693-696
Step 1: Get Recent Blockhash
Fetch a recent blockhash from the RPC endpoint.
Call transaction.sign() with the blockhash and signer(s).
Step 3: Encode Transaction
Convert the signed transaction to Base64 format.
Submit the encoded transaction via RPC.
Using the RPC Client to Send Transactions
Setting Recent Blockhash
import software.sava.rpc.json.http.client.SolanaRpcClient;
import software.sava.rpc.json.http.response.LatestBlockHash;
import java.net.http.HttpClient;
import java.net.URI;
// Create RPC client
HttpClient httpClient = HttpClient . newHttpClient ();
URI endpoint = URI . create ( "https://api.mainnet-beta.solana.com" );
SolanaRpcClient rpcClient = SolanaRpcClient . createClient (endpoint, httpClient);
// Get latest blockhash
LatestBlockHash latestBlockHash = rpcClient . getLatestBlockHash (). join ();
byte [] blockHash = latestBlockHash . blockHash ();
// Set on transaction
transaction . setRecentBlockHash (blockHash);
Source: SolanaRpcClient.java:96-98
Sending with Sign Helper
The RPC client provides convenient methods to sign and send:
import java.util.concurrent.CompletableFuture;
// RPC client signs and sends the transaction
CompletableFuture < String > signatureFuture = rpcClient . sendTransaction (
transaction,
signer,
blockHash
);
// Wait for signature
String signature = signatureFuture . join ();
System . out . println ( "Transaction signature: " + signature);
Source: SolanaRpcClient.java:857-859
Sending with Multiple Signers
import java.util.List;
import java.util.SequencedCollection;
SequencedCollection < Signer > signers = List . of (
feePayer,
authority1,
authority2
);
CompletableFuture < String > signatureFuture = rpcClient . sendTransaction (
transaction,
signers,
blockHash
);
String signature = signatureFuture . join ();
Source: SolanaRpcClient.java:866-873
Sending Pre-Signed Transactions
// Sign transaction yourself
String base64SignedTx = transaction . signAndBase64Encode (
blockHash,
signer
);
// Send pre-signed transaction
CompletableFuture < String > signatureFuture = rpcClient . sendTransaction (
base64SignedTx
);
String signature = signatureFuture . join ();
Source: SolanaRpcClient.java:875-877
Handling Transaction Responses
Basic Response Handling
CompletableFuture < String > future = rpcClient . sendTransaction (
transaction,
signer,
blockHash
);
// Synchronous wait
try {
String signature = future . join ();
System . out . println ( "Success! Signature: " + signature);
} catch ( Exception e ) {
System . err . println ( "Transaction failed: " + e . getMessage ());
}
Asynchronous Response Handling
rpcClient . sendTransaction (transaction, signer, blockHash)
. thenAccept (signature -> {
System . out . println ( "Transaction sent: " + signature);
})
. exceptionally (error -> {
System . err . println ( "Failed to send: " + error . getMessage ());
return null ;
});
Getting Transaction Status
import software.sava.rpc.json.http.response.TxStatus;
import java.util.Map;
import java.util.List;
// Check signature status
List < String > signatures = List . of (signature);
CompletableFuture < Map < String , TxStatus >> statusFuture =
rpcClient . getSignatureStatuses (signatures);
Map < String , TxStatus > statuses = statusFuture . join ();
TxStatus status = statuses . get (signature);
if (status != null ) {
System . out . println ( "Confirmations: " + status . confirmations ());
System . out . println ( "Slot: " + status . slot ());
}
Source: SolanaRpcClient.java:746-751
Transaction Options
Using Preflight Commitment
import software.sava.rpc.json.http.request.Commitment;
// Specify preflight commitment level
CompletableFuture < String > signatureFuture = rpcClient . sendTransaction (
Commitment . CONFIRMED , // preflight commitment
transaction,
signer,
blockHash
);
String signature = signatureFuture . join ();
Source: SolanaRpcClient.java:861-864
Skipping Preflight Checks
// Skip preflight for faster submission (less safe)
CompletableFuture < String > signatureFuture =
rpcClient . sendTransactionSkipPreflight (base64SignedTx);
String signature = signatureFuture . join ();
Source: SolanaRpcClient.java:889-890
Setting Max Retries
int maxRetries = 3 ;
CompletableFuture < String > signatureFuture = rpcClient . sendTransaction (
base64SignedTx,
maxRetries
);
String signature = signatureFuture . join ();
Source: SolanaRpcClient.java:879
Commitment Levels
Solana supports different commitment levels:
import software.sava.rpc.json.http.request.Commitment;
// Processed: Fastest, least certain
CompletableFuture < String > sig1 = rpcClient . sendTransaction (
Commitment . PROCESSED ,
base64SignedTx,
maxRetries
);
// Confirmed: Good balance of speed and certainty
CompletableFuture < String > sig2 = rpcClient . sendTransaction (
Commitment . CONFIRMED ,
base64SignedTx,
maxRetries
);
// Finalized: Slowest, most certain
CompletableFuture < String > sig3 = rpcClient . sendTransaction (
Commitment . FINALIZED ,
base64SignedTx,
maxRetries
);
Source: Commitment.java:7-11
CONFIRMED is the recommended default for most use cases, balancing speed and finality.
Real-World Example
Complete flow from building to confirming:
import software.sava.core.tx.Transaction;
import software.sava.core.tx.Instruction;
import software.sava.core.accounts.Signer;
import software.sava.rpc.json.http.client.SolanaRpcClient;
import software.sava.rpc.json.http.request.Commitment;
import java.net.http.HttpClient;
import java.util.List;
public class TransactionExample {
public static void main ( String [] args ) {
// Setup
HttpClient httpClient = HttpClient . newHttpClient ();
var endpoint = URI . create ( "https://api.devnet.solana.com" );
var rpcClient = SolanaRpcClient . createClient (
endpoint,
httpClient,
Commitment . CONFIRMED
);
Signer feePayer = // ... your signer
List < Instruction > instructions = // ... your instructions
// Build transaction
Transaction transaction = Transaction . createTx (
feePayer . publicKey (),
instructions
);
// Get recent blockhash
var latestBlockHash = rpcClient . getLatestBlockHash (). join ();
byte [] blockHash = latestBlockHash . blockHash ();
// Send transaction (signs internally)
var signatureFuture = rpcClient . sendTransaction (
transaction,
feePayer,
blockHash
);
// Handle response
signatureFuture
. thenAccept (signature -> {
System . out . println ( "Transaction sent: " + signature);
// Monitor confirmation
monitorTransaction (rpcClient, signature);
})
. exceptionally (error -> {
System . err . println ( "Transaction failed: " + error . getMessage ());
return null ;
});
}
static void monitorTransaction ( SolanaRpcClient rpcClient , String signature ) {
// Poll for confirmation
var statusFuture = rpcClient . getSignatureStatuses ( List . of (signature));
statusFuture . thenAccept (statuses -> {
var status = statuses . get (signature);
if (status != null && status . confirmations () != null ) {
System . out . println ( "Confirmed in slot: " + status . slot ());
} else {
System . out . println ( "Not yet confirmed" );
}
});
}
}
Error Handling
Handling RPC Errors
import software.sava.rpc.json.http.response.JsonRpcException;
try {
String signature = rpcClient . sendTransaction (
transaction,
signer,
blockHash
). join ();
System . out . println ( "Success: " + signature);
} catch ( JsonRpcException e ) {
System . err . println ( "RPC Error: " + e . getMessage ());
// Handle specific error codes
} catch ( Exception e ) {
System . err . println ( "General error: " + e . getMessage ());
}
Transaction Simulation
Simulate before sending:
import software.sava.rpc.json.http.response.TxSimulation;
// Get fee for transaction
var feeFuture = rpcClient . getFeeForMessage (
transaction . base64EncodeToString ()
);
var fee = feeFuture . join ();
System . out . println ( "Transaction fee: " + fee . value () + " lamports" );
Source: SolanaRpcClient.java:92-94
Best Practices
Always use recent blockhashes (valid for ~60 seconds)
Fetch blockhash immediately before signing
Cache blockhash for multiple transactions within validity window
Always handle transaction failures gracefully
Implement retry logic for network issues
Log transaction signatures for tracking
Never log or expose private keys
Validate transaction contents before signing
Use appropriate commitment levels for your use case
Building Transactions Learn how to construct transactions and instructions
Using RPC Client Explore all RPC client features and methods