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.
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!("Batch of {} transactions built successfully!", bundle.transactions.len());
Ok("mock-bundle-signature".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