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
// Example: Build arbitrage bundle
async fn build_arbitrage_bundle(
// nextblock_client: &mut YourGeneratedApiClient,
rpc_client: &RpcClient,
signer: &Keypair,
dex_a_address: Pubkey,
dex_b_address: Pubkey,
trade_amount: u64,
) -> Result<String, Box<dyn std::error::Error>> {
let recent_blockhash = rpc_client.get_latest_blockhash()?;
let mut bundle = TransactionBundle::new();
// Transaction 1: Buy on DEX A
let buy_instructions = vec![
// Add your DEX-specific buy instructions here
system_instruction::transfer(
&signer.pubkey(),
&dex_a_address,
trade_amount,
),
];
let buy_tx = build_transaction_with_tip(
signer,
recent_blockhash,
2_000_000, // Higher tip for arbitrage
buy_instructions,
)?;
bundle = bundle.add_transaction(buy_tx);
// Transaction 2: Sell on DEX B
let sell_instructions = vec![
// Add your DEX-specific sell instructions here
system_instruction::transfer(
&signer.pubkey(),
&dex_b_address,
trade_amount,
),
];
let sell_tx = build_transaction_with_tip(
signer,
recent_blockhash,
2_000_000, // Higher tip for arbitrage
sell_instructions,
)?;
bundle = bundle.add_transaction(sell_tx);
println!("Arbitrage bundle with {} transactions prepared", bundle.transactions.len());
// Submit the bundle
// Implementation similar to submit_batched_transactions
Ok("arbitrage-bundle-signature".to_string())
}
Complex DeFi Bundle Example
// Example: Complex DeFi operation bundle
async fn build_defi_operation_bundle(
// nextblock_client: &mut YourGeneratedApiClient,
rpc_client: &RpcClient,
signer: &Keypair,
) -> Result<String, Box<dyn std::error::Error>> {
let recent_blockhash = rpc_client.get_latest_blockhash()?;
let mut bundle = TransactionBundle::new();
// Transaction 1: Create token accounts
let account_creation_instructions = vec![
// Add token account creation instructions
system_instruction::create_account(
&signer.pubkey(),
&Keypair::new().pubkey(), // New account
1_000_000, // Rent exemption
165, // Token account space
&spl_token::id(),
),
];
let account_tx = build_transaction_with_tip(
signer,
recent_blockhash,
500_000,
account_creation_instructions,
)?;
bundle = bundle.add_transaction(account_tx);
// Transaction 2: Execute swap
let swap_instructions = vec![
// Add swap instructions (Jupiter, Raydium, etc.)
system_instruction::transfer(
&signer.pubkey(),
&Pubkey::from_str("<swap-program-address>")?,
0, // No SOL transfer for swap
),
];
let swap_tx = build_transaction_with_tip(
signer,
recent_blockhash,
1_500_000, // Higher tip for main operation
swap_instructions,
)?;
bundle = bundle.add_transaction(swap_tx);
// Transaction 3: Stake or provide liquidity
let stake_instructions = vec![
// Add staking/liquidity instructions
system_instruction::transfer(
&signer.pubkey(),
&Pubkey::from_str("<stake-pool-address>")?,
0,
),
];
let stake_tx = build_transaction_with_tip(
signer,
recent_blockhash,
750_000,
stake_instructions,
)?;
bundle = bundle.add_transaction(stake_tx);
println!("DeFi operation bundle prepared with {} transactions", bundle.transactions.len());
Ok("defi-bundle-signature".to_string())
}
Usage Example
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
// Initialize configuration
let signer = Keypair::new(); // Use your actual keypair
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 basic batch
let bundle_signature = submit_batched_transactions(
// &mut nextblock_client,
&rpc_client,
&signer,
).await?;
println!("Batch submitted with signature: {}", bundle_signature);
// Submit arbitrage bundle
let arb_signature = build_arbitrage_bundle(
// &mut nextblock_client,
&rpc_client,
&signer,
Pubkey::from_str("<dex-a-address>")?,
Pubkey::from_str("<dex-b-address>")?,
1_000_000, // 0.001 SOL
).await?;
println!("Arbitrage bundle: {}", arb_signature);
// Submit complex DeFi bundle
let defi_signature = build_defi_operation_bundle(
// &mut nextblock_client,
&rpc_client,
&signer,
).await?;
println!("DeFi bundle: {}", defi_signature);
Ok(())
}
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
// Validate bundle before submission
fn validate_bundle(bundle: &TransactionBundle) -> Result<(), &'static str> {
if bundle.transactions.is_empty() {
return Err("Bundle cannot be empty");
}
if bundle.transactions.len() > 4 {
return Err("Bundle cannot contain more than 4 transactions");
}
if bundle.transactions.len() < 2 {
return Err("Bundle must contain at least 2 transactions");
}
// Check for signature duplicates
let mut signatures = std::collections::HashSet::new();
for tx in &bundle.transactions {
if !signatures.insert(tx.signatures[0]) {
return Err("Duplicate transaction signatures in bundle");
}
}
Ok(())
}
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