Submit Batched Transactions

Submit 2-4 transactions as an atomic bundle to NextBlock using Rust. Batched transactions are processed as Jito bundles - either all transactions succeed, or none do.

This example shows bundle 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
const NEXTBLOCK_TIP_WALLETS: [&str; 8] = [
    "NEXTbLoCkB51HpLBLojQfpyVAMorm3zzKg7w9NFdqid",
    "nextBLoCkPMgmG8ZgJtABeScP35qLa2AMCNKntAP7Xc",
    "NextbLoCkVtMGcV47JzewQdvBpLqT9TxQFozQkN98pE", 
    "NexTbLoCkWykbLuB1NkjXgFWkX9oAtcoagQegygXXA2",
    "NeXTBLoCKs9F1y5PJS9CKrFNNLU1keHW71rfh7KgA1X",
    "NexTBLockJYZ7QD7p2byrUa6df8ndV2WSd8GkbWqfbb",
    "neXtBLock1LeC67jYd1QdAa32kbVeubsfPNTJC1V5At",
    "nEXTBLockYgngeRmRrjDV31mGSekVPqZoMGhQEZtPVG",
];

// Transaction builder for bundle
struct TransactionBundle {
    transactions: Vec<Transaction>,
}

impl TransactionBundle {
    fn new() -> Self {
        Self {
            transactions: Vec::new(),
        }
    }
    
    // Add transaction to bundle
    fn add_transaction(&mut self, transaction: Transaction) -> Self {
        self.transactions.push(transaction);
        self
    }
    
    // Get transactions as base64 strings
    fn to_base64_transactions(&self) -> Result<Vec<String>, Box<dyn std::error::Error>> {
        self.transactions
            .iter()
            .map(|tx| {
                let serialized = bincode::serialize(tx)?;
                Ok(base64::encode(serialized))
            })
            .collect()
    }
}

// Build single transaction with tip
fn build_transaction_with_tip(
    payer: &Keypair,
    recent_blockhash: Hash,
    tip_amount: u64,
    instructions: Vec<Instruction>,
) -> Result<Transaction, Box<dyn std::error::Error>> {
    let mut rng = rand::thread_rng();
    let tip_wallet_str = NEXTBLOCK_TIP_WALLETS.choose(&mut rng)
        .ok_or("No tip wallets available")?;
    let tip_wallet = Pubkey::from_str(tip_wallet_str)?;
    
    // Create tip instruction (should be first)
    let tip_instruction = system_instruction::transfer(
        &payer.pubkey(),
        &tip_wallet,
        tip_amount,
    );
    
    // Combine instructions
    let mut all_instructions = vec![tip_instruction];
    all_instructions.extend(instructions);
    
    // Build 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 batched transactions
async fn submit_batched_transactions(
    // nextblock_client: &mut YourGeneratedApiClient,
    rpc_client: &RpcClient,
    signer: &Keypair,
) -> Result<String, Box<dyn std::error::Error>> {
    // Get recent blockhash (same for all transactions in bundle)
    let recent_blockhash = rpc_client.get_latest_blockhash()?;
    
    // Build transaction bundle
    let mut bundle = TransactionBundle::new();
    
    // Transaction 1: Setup transaction
    let setup_instructions = vec![
        system_instruction::transfer(
            &signer.pubkey(),
            &Pubkey::from_str("<recipient1-public-key>")?,
            100_000, // 0.0001 SOL
        ),
    ];
    
    let setup_tx = build_transaction_with_tip(
        signer,
        recent_blockhash,
        500_000, // 0.0005 SOL tip
        setup_instructions,
    )?;
    bundle = bundle.add_transaction(setup_tx);
    
    // Transaction 2: Main operation
    let main_instructions = vec![
        system_instruction::transfer(
            &signer.pubkey(),
            &Pubkey::from_str("<recipient2-public-key>")?,
            200_000, // 0.0002 SOL
        ),
    ];
    
    let main_tx = build_transaction_with_tip(
        signer,
        recent_blockhash,
        1_000_000, // 0.001 SOL tip (higher for main transaction)
        main_instructions,
    )?;
    bundle = bundle.add_transaction(main_tx);
    
    // Transaction 3: Cleanup transaction
    let cleanup_instructions = vec![
        system_instruction::transfer(
            &signer.pubkey(),
            &Pubkey::from_str("<recipient3-public-key>")?,
            50_000, // 0.00005 SOL
        ),
    ];
    
    let cleanup_tx = build_transaction_with_tip(
        signer,
        recent_blockhash,
        500_000, // 0.0005 SOL tip
        cleanup_instructions,
    )?;
    bundle = bundle.add_transaction(cleanup_tx);
    
    // Convert to base64 for submission
    let base64_transactions = bundle.to_base64_transactions()?;
    
    // Print transaction signatures for tracking
    for (i, tx) in bundle.transactions.iter().enumerate() {
        println!("Transaction {} signature: {}", i + 1, tx.signatures[0]);
    }
    
    // Submit bundle to NextBlock
    /* Uncomment when you have the generated API client
    let entries: Vec<PostSubmitRequestEntry> = base64_transactions
        .into_iter()
        .map(|tx_base64| PostSubmitRequestEntry {
            transaction: Some(TransactionMessage {
                content: tx_base64,
            }),
        })
        .collect();
    
    let request = tonic::Request::new(PostSubmitBatchRequest {
        entries,
    });
    
    let response = nextblock_client.post_submit_batch_v2(request).await?;
    let submit_response = response.into_inner();
    
    println!("Batch submitted successfully!");
    println!("Bundle signature: {}", submit_response.signature);
    
    Ok(submit_response.signature)
    */
    
    println!("Local bundle prepared with {} transactions.", bundle.transactions.len());
    Ok("".to_string())
}

Arbitrage Bundle Example

Complex DeFi Bundle Example

Usage Example

Bundle Best Practices

Size and Composition

  • Bundle size: 2-4 transactions (optimal: 2-3)

  • Transaction order: Setup → Main operations → Cleanup

  • Compute limits: Stay within bundle compute unit limits

Tip Strategy

  • Progressive tipping: Higher tips for more critical transactions

  • Bundle coherence: All transactions should have meaningful tips

  • Market conditions: Adjust tips based on network congestion

Error Handling

Common Use Cases

  1. Arbitrage: Multi-DEX price differences

  2. DeFi operations: Account setup → Trade → Stake

  3. NFT operations: Create → Mint → Transfer → List

  4. Token operations: Create mint → Create accounts → Transfer

  5. Liquidations: Detect → Execute → Collect rewards

Bundle Failure Prevention

  • Account validation: Ensure all accounts exist and have sufficient balance

  • Instruction compatibility: Avoid conflicting program interactions

  • Blockhash freshness: Use recent blockhashes (valid ~60 seconds)

  • Compute budget: Monitor total compute units across all transactions

Last updated