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
Arbitrage: Multi-DEX price differences
DeFi operations: Account setup → Trade → Stake
NFT operations: Create → Mint → Transfer → List
Token operations: Create mint → Create accounts → Transfer
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