Submit Single Transaction
Submit individual transactions to NextBlock using Rust and the Solana SDK.
Example
use solana_sdk::{
instruction::Instruction,
message::Message,
pubkey::Pubkey,
signature::{Keypair, Signature, Signer},
system_instruction,
transaction::Transaction,
hash::Hash,
};
use solana_client::rpc_client::RpcClient;
use rand::seq::SliceRandom;
use std::str::FromStr;
// NextBlock tip wallets for load balancing
const NEXTBLOCK_TIP_WALLETS: [&str; 8] = [
"NEXTbLoCkB51HpLBLojQfpyVAMorm3zzKg7w9NFdqid",
"nextBLoCkPMgmG8ZgJtABeScP35qLa2AMCNKntAP7Xc",
"NextbLoCkVtMGcV47JzewQdvBpLqT9TxQFozQkN98pE",
"NexTbLoCkWykbLuB1NkjXgFWkX9oAtcoagQegygXXA2",
"NeXTBLoCKs9F1y5PJS9CKrFNNLU1keHW71rfh7KgA1X",
"NexTBLockJYZ7QD7p2byrUa6df8ndV2WSd8GkbWqfbb",
"neXtBLock1LeC67jYd1QdAa32kbVeubsfPNTJC1V5At",
"nEXTBLockYgngeRmRrjDV31mGSekVPqZoMGhQEZtPVG",
];
// Get random tip wallet for load balancing
fn get_random_nextblock_tip_wallet() -> Result<Pubkey, Box<dyn std::error::Error>> {
let mut rng = rand::thread_rng();
let wallet_str = NEXTBLOCK_TIP_WALLETS.choose(&mut rng)
.ok_or("No tip wallets available")?;
Ok(Pubkey::from_str(wallet_str)?)
}
// Build transaction with tip and instructions
fn build_transaction_with_tip(
payer: &Keypair,
recent_blockhash: Hash,
tip_amount: u64,
instructions: Vec<Instruction>,
) -> Result<Transaction, Box<dyn std::error::Error>> {
let tip_wallet = get_random_nextblock_tip_wallet()?;
// Create tip instruction (should be first)
let tip_instruction = system_instruction::transfer(
&payer.pubkey(),
&tip_wallet,
tip_amount,
);
// Combine tip instruction with user instructions
let mut all_instructions = vec![tip_instruction];
all_instructions.extend(instructions);
// Create and sign transaction
let message = Message::new(&all_instructions, Some(&payer.pubkey()));
let mut transaction = Transaction::new_unsigned(message);
transaction.sign(&[payer], recent_blockhash);
Ok(transaction)
}
// Submit transaction to NextBlock
async fn submit_single_transaction(
// nextblock_client: &mut YourGeneratedApiClient,
rpc_client: &RpcClient,
signer: &Keypair,
recipient: Pubkey,
transfer_amount: u64,
tip_amount: u64,
) -> Result<Signature, Box<dyn std::error::Error>> {
// Get recent blockhash
let recent_blockhash = rpc_client.get_latest_blockhash()?;
// Create transfer instruction
let transfer_instruction = system_instruction::transfer(
&signer.pubkey(),
&recipient,
transfer_amount,
);
// Build transaction with tip
let transaction = build_transaction_with_tip(
signer,
recent_blockhash,
tip_amount,
vec![transfer_instruction],
)?;
// Convert to base64 for submission
let serialized_tx = bincode::serialize(&transaction)?;
let base64_tx = base64::encode(serialized_tx);
// Configure submission options
let front_running_protection = false;
let revert_on_fail = false;
let disable_retries = false;
let snipe_transaction = false;
// Submit to NextBlock
/* Uncomment when you have the generated API client
let request = tonic::Request::new(PostSubmitRequest {
transaction: Some(TransactionMessage {
content: base64_tx,
}),
skip_pre_flight: Some(true),
snipe_transaction: Some(snipe_transaction),
front_running_protection: Some(front_running_protection),
disable_retries: Some(disable_retries),
revert_on_fail: Some(revert_on_fail),
});
let response = nextblock_client.post_submit_v2(request).await?;
let submit_response = response.into_inner();
println!("Transaction submitted successfully!");
println!("Signature: {}", submit_response.signature);
println!("UUID: {}", submit_response.uuid);
Ok(Signature::from_str(&submit_response.signature)?)
*/
println!("Transaction built successfully: {}", transaction.signatures[0]);
Ok(transaction.signatures[0])
}
// Example with dynamic tip calculation
async fn submit_with_optimal_tip(
// nextblock_client: &mut YourGeneratedApiClient,
rpc_client: &RpcClient,
signer: &Keypair,
recipient: Pubkey,
transfer_amount: u64,
) -> Result<Signature, Box<dyn std::error::Error>> {
// Get optimal tip amount (implement tip floor API call)
let optimal_tip = get_optimal_tip_amount().await?;
submit_single_transaction(
// nextblock_client,
rpc_client,
signer,
recipient,
transfer_amount,
optimal_tip,
).await
}
// Get optimal tip amount from tip floor API
async fn get_optimal_tip_amount() -> Result<u64, Box<dyn std::error::Error>> {
// This should call the tip floor API to get current percentiles
// For now, return a reasonable default
Ok(1_000_000) // 0.001 SOL
}
Usage Example
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
// Initialize configuration
let signer = Keypair::new(); // Use your actual keypair
let recipient = Pubkey::from_str("<recipient-public-key>")?;
let rpc_client = RpcClient::new("https://api.mainnet-beta.solana.com".to_string());
// Connect to NextBlock (see connection.md)
// let config = NextBlockConfig::default();
// let mut nextblock_client = create_nextblock_client(config).await?;
// Submit transaction
let signature = submit_single_transaction(
// &mut nextblock_client,
&rpc_client,
&signer,
recipient,
10_000, // Transfer 10,000 lamports
1_000_000, // Tip 1,000,000 lamports (0.001 SOL)
).await?;
println!("Transaction submitted with signature: {}", signature);
// Alternative: Submit with optimal tip
let signature2 = submit_with_optimal_tip(
// &mut nextblock_client,
&rpc_client,
&signer,
recipient,
20_000, // Transfer 20,000 lamports
).await?;
println!("Optimally tipped transaction: {}", signature2);
Ok(())
}
Advanced Features
Custom Instruction Building
use solana_sdk::instruction::{AccountMeta, Instruction};
// Build custom program instruction
fn build_custom_instruction(
program_id: Pubkey,
accounts: Vec<AccountMeta>,
data: Vec<u8>,
) -> Instruction {
Instruction {
program_id,
accounts,
data,
}
}
// Example: Token transfer instruction
fn build_token_transfer_instruction(
source: Pubkey,
destination: Pubkey,
authority: Pubkey,
amount: u64,
) -> Instruction {
// This is a simplified example - use spl-token crate for real token transfers
let accounts = vec![
AccountMeta::new(source, false),
AccountMeta::new(destination, false),
AccountMeta::new_readonly(authority, true),
];
build_custom_instruction(
spl_token::id(),
accounts,
amount.to_le_bytes().to_vec(),
)
}
Error Handling and Retries
use tokio::time::{sleep, Duration};
// Retry logic with exponential backoff
async fn submit_with_retry(
// nextblock_client: &mut YourGeneratedApiClient,
rpc_client: &RpcClient,
signer: &Keypair,
recipient: Pubkey,
transfer_amount: u64,
tip_amount: u64,
max_retries: u32,
) -> Result<Signature, Box<dyn std::error::Error>> {
let mut retry_count = 0;
let mut delay = Duration::from_millis(1000);
loop {
match submit_single_transaction(
// nextblock_client,
rpc_client,
signer,
recipient,
transfer_amount,
tip_amount,
).await {
Ok(signature) => return Ok(signature),
Err(e) if retry_count < max_retries => {
println!("Attempt {} failed: {}. Retrying in {:?}...",
retry_count + 1, e, delay);
sleep(delay).await;
retry_count += 1;
delay *= 2; // Exponential backoff
}
Err(e) => return Err(e),
}
}
}
Best Practices
Always include tips: NextBlock prioritizes transactions with appropriate tips
Use random tip wallets: Distribute load across multiple tip addresses
Monitor tip floors: Adjust tips based on current network conditions
Handle errors gracefully: Implement retry logic for network issues
Validate inputs: Always validate public keys and amounts before submission
Use appropriate RPC endpoints: Choose reliable RPC providers
Keep connections alive: Reuse gRPC connections for better performance
Last updated