Submit Single Transaction

Submit individual transactions to NextBlock using Rust and the Solana SDK.

If you want to send raw signed transaction bytes over QUIC instead of gRPC, see QUIC Transaction Submission.

This example shows transaction construction and the request shape you send to NextBlock. Replace the placeholder generated client types with the client generated from nextblock-proto.

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!("Local 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

Advanced Features

Custom Instruction Building

Error Handling and Retries

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