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.
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("Batch of %d transactions built successfully!\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("fra.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
// Check if bundle was included atomically
func CheckBundleStatus(signature string) {
// Implement bundle status checking
// All transactions should have the same block slot if successful
}
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