Submit batched transactions
Submit 2-4 transactions as an atomic bundle to NextBlock. Batched transactions are processed as Jito bundles, meaning either all transactions succeed and land, or none of them 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
package main
import (
"context"
"fmt"
"log"
"math/rand"
"time"
"github.com/gagliardetto/solana-go"
"github.com/gagliardetto/solana-go/programs/system"
"github.com/gagliardetto/solana-go/rpc"
// Import your generated proto client
// "path/to/your/generated/api"
)
// NextBlock tip wallets - use these for tipping
var NextblockTipWallets = []solana.PublicKey{
solana.MustPublicKeyFromBase58("NEXTbLoCkB51HpLBLojQfpyVAMorm3zzKg7w9NFdqid"),
solana.MustPublicKeyFromBase58("nextBLoCkPMgmG8ZgJtABeScP35qLa2AMCNKntAP7Xc"),
solana.MustPublicKeyFromBase58("NextbLoCkVtMGcV47JzewQdvBpLqT9TxQFozQkN98pE"),
solana.MustPublicKeyFromBase58("NexTbLoCkWykbLuB1NkjXgFWkX9oAtcoagQegygXXA2"),
solana.MustPublicKeyFromBase58("NeXTBLoCKs9F1y5PJS9CKrFNNLU1keHW71rfh7KgA1X"),
solana.MustPublicKeyFromBase58("NexTBLockJYZ7QD7p2byrUa6df8ndV2WSd8GkbWqfbb"),
solana.MustPublicKeyFromBase58("neXtBLock1LeC67jYd1QdAa32kbVeubsfPNTJC1V5At"),
solana.MustPublicKeyFromBase58("nEXTBLockYgngeRmRrjDV31mGSekVPqZoMGhQEZtPVG"),
}
// GetRandomNextblockTipWallet returns a random tip wallet for load balancing
func GetRandomNextblockTipWallet() solana.PublicKey {
randVal := rand.IntN(len(NextblockTipWallets))
return NextblockTipWallets[randVal]
}
// BuildTransaction creates a single transaction with tip and instructions
func BuildTransaction(
rpcClient *rpc.Client,
signerPrivateKey solana.PrivateKey,
blockhash solana.Hash,
tipAmount uint64,
instructions []solana.Instruction,
) (*solana.Transaction, error) {
txBuilder := solana.NewTransactionBuilder()
txBuilder.SetFeePayer(signerPrivateKey.PublicKey())
txBuilder.SetRecentBlockHash(blockhash)
// Add tip instruction first (recommended practice)
tipInstruction := system.NewTransferInstruction(
tipAmount,
signerPrivateKey.PublicKey(),
GetRandomNextblockTipWallet(),
).Build()
txBuilder.AddInstruction(tipInstruction)
// Add all provided instructions
for _, instruction := range instructions {
txBuilder.AddInstruction(instruction)
}
// Build and sign transaction
tx, err := txBuilder.Build()
if err != nil {
return nil, fmt.Errorf("failed to build transaction: %w", err)
}
_, err = tx.Sign(func(key solana.PublicKey) *solana.PrivateKey {
if key.Equals(signerPrivateKey.PublicKey()) {
return &signerPrivateKey
}
return nil
})
if err != nil {
return nil, fmt.Errorf("failed to sign transaction: %w", err)
}
return tx, nil
}
// BuildAndSubmitBatchTransactions creates and submits multiple transactions as a bundle
func BuildAndSubmitBatchTransactions(
nextblockApiClient interface{}, // Replace with your generated API client type
rpcClient *rpc.Client,
signerPrivateKey solana.PrivateKey,
) error {
// Get latest blockhash (same for all transactions in bundle)
bh, err := rpcClient.GetLatestBlockhash(context.TODO(), rpc.CommitmentFinalized)
if err != nil {
return fmt.Errorf("failed to get latest blockhash: %w", err)
}
var transactions []*solana.Transaction
// Transaction 1: Setup transaction
setupInstructions := []solana.Instruction{
system.NewTransferInstruction(
100000, // 0.0001 SOL
signerPrivateKey.PublicKey(),
solana.MustPublicKeyFromBase58("recipient1-public-key-here"),
).Build(),
}
tx1, err := BuildTransaction(
rpcClient,
signerPrivateKey,
bh.Value.Blockhash,
500000, // 0.0005 SOL tip
setupInstructions,
)
if err != nil {
return fmt.Errorf("failed to build transaction 1: %w", err)
}
transactions = append(transactions, tx1)
// Transaction 2: Main operation
mainInstructions := []solana.Instruction{
system.NewTransferInstruction(
200000, // 0.0002 SOL
signerPrivateKey.PublicKey(),
solana.MustPublicKeyFromBase58("recipient2-public-key-here"),
).Build(),
}
tx2, err := BuildTransaction(
rpcClient,
signerPrivateKey,
bh.Value.Blockhash,
1000000, // 0.001 SOL tip (higher for main transaction)
mainInstructions,
)
if err != nil {
return fmt.Errorf("failed to build transaction 2: %w", err)
}
transactions = append(transactions, tx2)
// Transaction 3: Cleanup transaction (optional)
cleanupInstructions := []solana.Instruction{
system.NewTransferInstruction(
50000, // 0.00005 SOL
signerPrivateKey.PublicKey(),
solana.MustPublicKeyFromBase58("recipient3-public-key-here"),
).Build(),
}
tx3, err := BuildTransaction(
rpcClient,
signerPrivateKey,
bh.Value.Blockhash,
500000, // 0.0005 SOL tip
cleanupInstructions,
)
if err != nil {
return fmt.Errorf("failed to build transaction 3: %w", err)
}
transactions = append(transactions, tx3)
// Convert transactions to base64 for submission
var entries []*interface{} // Replace with your generated API types
for i, tx := range transactions {
base64EncodedTransaction := tx.ToBase64()
fmt.Printf("Transaction %d signature: %s\n", i+1, tx.Signatures[0].String())
// Create entry for batch submission
/* Uncomment when you have the generated API client
entry := &api.PostSubmitRequestEntry{
Transaction: &api.TransactionMessage{
Content: base64EncodedTransaction,
},
}
entries = append(entries, entry)
*/
_ = base64EncodedTransaction // Placeholder to avoid unused variable error
}
// Submit batch to NextBlock
/* Uncomment when you have the generated API client
submitResponse, err := nextblockApiClient.PostSubmitBatchV2(context.TODO(), &api.PostSubmitBatchRequest{
Entries: entries,
})
if err != nil {
return fmt.Errorf("failed to submit batch: %w", err)
}
fmt.Printf("Batch submitted successfully!\n")
fmt.Printf("Bundle signature: %s\n", submitResponse.Signature)
*/
fmt.Printf("Local bundle prepared with %d transactions.\n", len(transactions))
return nil
}
// Example: Arbitrage Bundle
func BuildArbitrageBundleExample(
nextblockApiClient interface{},
rpcClient *rpc.Client,
signerPrivateKey solana.PrivateKey,
) error {
// Get blockhash
bh, err := rpcClient.GetLatestBlockhash(context.TODO(), rpc.CommitmentFinalized)
if err != nil {
return fmt.Errorf("failed to get latest blockhash: %w", err)
}
var transactions []*solana.Transaction
// Transaction 1: Buy on DEX A
buyInstructions := []solana.Instruction{
// Add your DEX buy instructions here
system.NewTransferInstruction(
1000000, // Placeholder instruction
signerPrivateKey.PublicKey(),
solana.MustPublicKeyFromBase58("dex-a-address"),
).Build(),
}
buyTx, err := BuildTransaction(
rpcClient,
signerPrivateKey,
bh.Value.Blockhash,
2000000, // Higher tip for arbitrage
buyInstructions,
)
if err != nil {
return err
}
transactions = append(transactions, buyTx)
// Transaction 2: Sell on DEX B
sellInstructions := []solana.Instruction{
// Add your DEX sell instructions here
system.NewTransferInstruction(
1000000, // Placeholder instruction
signerPrivateKey.PublicKey(),
solana.MustPublicKeyFromBase58("dex-b-address"),
).Build(),
}
sellTx, err := BuildTransaction(
rpcClient,
signerPrivateKey,
bh.Value.Blockhash,
2000000, // Higher tip for arbitrage
sellInstructions,
)
if err != nil {
return err
}
transactions = append(transactions, sellTx)
fmt.Printf("Arbitrage bundle with %d transactions prepared\n", len(transactions))
// Submit logic similar to above...
return nil
}
func main() {
// Initialize random seed
rand.Seed(time.Now().UnixNano())
// Configuration
signerPrivateKey := solana.MustPrivateKeyFromBase58("your-private-key-here")
rpcEndpoint := "https://api.mainnet-beta.solana.com"
// Initialize RPC client
rpcClient := rpc.New(rpcEndpoint)
// Connect to NextBlock (see connection.md for full setup)
// conn, err := ConnectToNextblock("frankfurt.nextblock.io:443", "your-api-key", true)
// if err != nil {
// log.Fatal(err)
// }
// defer conn.Close()
// nextblockApiClient := api.NewApiClient(conn)
// Submit batch transactions
err := BuildAndSubmitBatchTransactions(
nil, // nextblockApiClient,
rpcClient,
signerPrivateKey,
)
if err != nil {
log.Fatal(err)
}
// Example: Submit arbitrage bundle
err = BuildArbitrageBundleExample(
nil, // nextblockApiClient,
rpcClient,
signerPrivateKey,
)
if err != nil {
log.Fatal(err)
}
}Batch Transaction Best Practices
Bundle Size Limits
Minimum: 2 transactions
Maximum: 4 transactions
Optimal: 2-3 transactions for better success rates
Transaction Ordering
Setup transactions: Account creation, token account setup
Main transactions: Core business logic
Cleanup transactions: Close accounts, collect fees
Tip Strategy for Bundles
Higher tips: Use higher tips for atomic operations like arbitrage
Distributed tips: Each transaction should have its own tip
Progressive tipping: Increase tips for more important transactions
Error Handling
Common Use Cases
Arbitrage: Buy low on one DEX, sell high on another
Complex DeFi operations: Setup → Trade → Cleanup
NFT operations: Create → Mint → Transfer
Token operations: Create mint → Create accounts → Transfer
Bundle Failure Scenarios
Insufficient balance: Ensure all accounts have enough SOL
Account conflicts: Avoid write conflicts between transactions
Instruction limits: Keep total compute units under limits
Blockhash expiry: Use fresh blockhashes (valid for ~60 seconds)
Last updated