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

  1. Always include tips: NextBlock prioritizes transactions with appropriate tips

  2. Use random tip wallets: Distribute load across multiple tip addresses

  3. Monitor tip floors: Adjust tips based on current network conditions

  4. Handle errors gracefully: Implement retry logic for network issues

  5. Validate inputs: Always validate public keys and amounts before submission

  6. Use appropriate RPC endpoints: Choose reliable RPC providers

  7. Keep connections alive: Reuse gRPC connections for better performance

Last updated