Submit Batched Transactions
Submit 2-4 transactions as an atomic bundle to NextBlock using JavaScript/TypeScript.
import {
Connection,
Keypair,
PublicKey,
SystemProgram,
Transaction
} from '@solana/web3.js';
// Transaction bundle builder
class TransactionBundle {
private transactions: Transaction[] = [];
private readonly maxSize: number = 4;
private readonly minSize: number = 2;
addTransaction(transaction: Transaction): TransactionBundle {
if (this.transactions.length >= this.maxSize) {
throw new Error(`Bundle cannot contain more than ${this.maxSize} transactions`);
}
this.transactions.push(transaction);
return this;
}
validate(): boolean {
if (this.transactions.length < this.minSize) {
throw new Error(`Bundle must contain at least ${this.minSize} transactions`);
}
if (this.transactions.length > this.maxSize) {
throw new Error(`Bundle cannot contain more than ${this.maxSize} transactions`);
}
// Check for duplicate signatures
const signatures = new Set();
for (const tx of this.transactions) {
const sig = tx.signature?.toString();
if (sig && signatures.has(sig)) {
throw new Error('Duplicate transaction signatures in bundle');
}
if (sig) signatures.add(sig);
}
return true;
}
toBase64Transactions(): string[] {
this.validate();
return this.transactions.map(tx => {
const serialized = tx.serialize();
return serialized.toString('base64');
});
}
getSignatures(): string[] {
return this.transactions
.map(tx => tx.signature?.toString())
.filter((sig): sig is string => sig !== undefined);
}
get size(): number {
return this.transactions.length;
}
}
// Build single transaction with tip
async function buildTransactionWithTip(
connection: Connection,
payer: Keypair,
tipAmount: number,
instructions: any[]
): Promise<Transaction> {
const { blockhash } = await connection.getLatestBlockhash('finalized');
// Random tip wallet for load balancing
const tipWallets = [
'NEXTbLoCkB51HpLBLojQfpyVAMorm3zzKg7w9NFdqid',
'nextBLoCkPMgmG8ZgJtABeScP35qLa2AMCNKntAP7Xc',
'NextbLoCkVtMGcV47JzewQdvBpLqT9TxQFozQkN98pE',
'NexTbLoCkWykbLuB1NkjXgFWkX9oAtcoagQegygXXA2',
];
const tipWallet = new PublicKey(tipWallets[Math.floor(Math.random() * tipWallets.length)]);
// Create tip instruction (should be first)
const tipInstruction = SystemProgram.transfer({
fromPubkey: payer.publicKey,
toPubkey: tipWallet,
lamports: tipAmount,
});
// Create and sign transaction
const transaction = new Transaction({
recentBlockhash: blockhash,
feePayer: payer.publicKey,
});
transaction.add(tipInstruction);
instructions.forEach(instruction => transaction.add(instruction));
transaction.sign(payer);
return transaction;
}
// Submit batched transactions
async function submitBatchedTransactions(
// nextblockClient: any, // Your generated gRPC client
connection: Connection,
signer: Keypair,
transactionSpecs: Array<{
instructions: any[];
tipAmount: number;
}>
): Promise<string> {
// Build transaction bundle
const bundle = new TransactionBundle();
for (const { instructions, tipAmount } of transactionSpecs) {
const transaction = await buildTransactionWithTip(
connection,
signer,
tipAmount,
instructions
);
bundle.addTransaction(transaction);
}
// Get base64 transactions for submission
const base64Transactions = bundle.toBase64Transactions();
const signatures = bundle.getSignatures();
// Log transaction signatures
signatures.forEach((sig, i) => {
console.log(`Transaction ${i + 1} signature: ${sig}`);
});
// Submit bundle to NextBlock
/* Uncomment when you have the generated gRPC client
const entries = base64Transactions.map(base64Tx => ({
transaction: { content: base64Tx }
}));
const request = { entries };
const response = await nextblockClient.postSubmitBatchV2(request);
console.log('Batch submitted successfully!');
console.log(`Bundle signature: ${response.signature}`);
return response.signature;
*/
// Mock response for demonstration
const bundleSignature = `bundle-${bundle.size}-transactions`;
console.log(`Batch of ${bundle.size} transactions built successfully!`);
return bundleSignature;
}
// Build arbitrage bundle
async function buildArbitrageBundle(
// nextblockClient: any,
connection: Connection,
signer: Keypair,
dexAAddress: PublicKey,
dexBAddress: PublicKey,
tradeAmount: number
): Promise<string> {
// Transaction 1: Buy on DEX A
const buyInstructions = [
// Add your DEX-specific buy instructions here
SystemProgram.transfer({
fromPubkey: signer.publicKey,
toPubkey: dexAAddress,
lamports: tradeAmount,
})
];
// Transaction 2: Sell on DEX B
const sellInstructions = [
// Add your DEX-specific sell instructions here
SystemProgram.transfer({
fromPubkey: signer.publicKey,
toPubkey: dexBAddress,
lamports: tradeAmount,
})
];
const transactionSpecs = [
{ instructions: buyInstructions, tipAmount: 2_000_000 }, // Higher tip for arbitrage
{ instructions: sellInstructions, tipAmount: 2_000_000 }, // Higher tip for arbitrage
];
return await submitBatchedTransactions(
// nextblockClient,
connection,
signer,
transactionSpecs
);
}
// Build complex DeFi operation bundle
async function buildDeFiOperationBundle(
// nextblockClient: any,
connection: Connection,
signer: Keypair
): Promise<string> {
// Transaction 1: Setup - Create token accounts
const setupInstructions = [
// Add token account creation instructions
SystemProgram.transfer({
fromPubkey: signer.publicKey,
toPubkey: new PublicKey('<token-program-address>'),
lamports: 1_000_000 // Rent exemption
})
];
// Transaction 2: Main operation - Execute swap
const swapInstructions = [
// Add swap instructions (Jupiter, Raydium, etc.)
SystemProgram.transfer({
fromPubkey: signer.publicKey,
toPubkey: new PublicKey('<swap-program-address>'),
lamports: 0 // No SOL transfer for swap
})
];
// Transaction 3: Cleanup - Stake or provide liquidity
const stakeInstructions = [
// Add staking/liquidity instructions
SystemProgram.transfer({
fromPubkey: signer.publicKey,
toPubkey: new PublicKey('<stake-pool-address>'),
lamports: 0
})
];
const transactionSpecs = [
{ instructions: setupInstructions, tipAmount: 500_000 }, // Setup tip
{ instructions: swapInstructions, tipAmount: 1_500_000 }, // Main operation tip
{ instructions: stakeInstructions, tipAmount: 750_000 }, // Cleanup tip
];
return await submitBatchedTransactions(
// nextblockClient,
connection,
signer,
transactionSpecs
);
}
// Bundle optimizer
class BundleOptimizer {
private tipMultipliers = {
setup: 0.5, // Lower priority
main: 1.5, // Higher priority
cleanup: 0.75, // Medium priority
arbitrage: 2.0, // Highest priority
};
optimizeTips(baseTip: number, transactionTypes: string[]): number[] {
return transactionTypes.map(txType => {
const multiplier = this.tipMultipliers[txType as keyof typeof this.tipMultipliers] || 1.0;
return Math.floor(baseTip * multiplier);
});
}
reorderTransactions<T>(
transactions: T[],
transactionTypes: string[]
): { transactions: T[]; types: string[] } {
// Priority order: setup -> main -> cleanup
const priorityOrder = { setup: 1, main: 2, cleanup: 3, arbitrage: 0 };
const pairs = transactions.map((tx, i) => ({
transaction: tx,
type: transactionTypes[i],
priority: priorityOrder[transactionTypes[i] as keyof typeof priorityOrder] || 2
}));
pairs.sort((a, b) => a.priority - b.priority);
return {
transactions: pairs.map(p => p.transaction),
types: pairs.map(p => p.type)
};
}
}
// Bundle status tracking
interface BundleStatus {
bundleId: string;
transactionCount: number;
submittedAt: number;
signatures: string[];
status: 'pending' | 'confirmed' | 'failed';
}
class BundleTracker {
private bundles: Map<string, BundleStatus> = new Map();
trackBundle(bundleStatus: BundleStatus): void {
this.bundles.set(bundleStatus.bundleId, bundleStatus);
}
async checkBundleStatus(
bundleId: string,
connection: Connection
): Promise<BundleStatus | null> {
const bundleStatus = this.bundles.get(bundleId);
if (!bundleStatus) return null;
// Check if all transactions are confirmed
let confirmedCount = 0;
for (const signature of bundleStatus.signatures) {
try {
const status = await connection.getSignatureStatus(signature);
if (status.value?.confirmationStatus) {
confirmedCount++;
}
} catch (error) {
console.error(`Failed to check signature ${signature}:`, error);
}
}
// Update bundle status
if (confirmedCount === bundleStatus.transactionCount) {
bundleStatus.status = 'confirmed';
} else if (Date.now() - bundleStatus.submittedAt > 60000) { // Timeout after 60 seconds
bundleStatus.status = 'failed';
}
return bundleStatus;
}
getBundleStatus(bundleId: string): BundleStatus | null {
return this.bundles.get(bundleId) || null;
}
}
Usage Examples
async function main() {
// Initialize Solana connection
const connection = new Connection('https://api.mainnet-beta.solana.com', 'confirmed');
const signer = Keypair.generate(); // Use your actual keypair
// Connect to NextBlock (see connection.md)
// const config = configFromEnv();
// const manager = new NextBlockConnectionManager(config);
// await manager.connect();
// const nextblockClient = manager.grpcClient;
try {
// Example 1: Basic batch submission
console.log('1. Basic batch submission:');
const transactionSpecs = [
// Setup transaction
{
instructions: [SystemProgram.transfer({
fromPubkey: signer.publicKey,
toPubkey: new PublicKey('<recipient1>'),
lamports: 100_000,
})],
tipAmount: 500_000, // 0.0005 SOL tip
},
// Main transaction
{
instructions: [SystemProgram.transfer({
fromPubkey: signer.publicKey,
toPubkey: new PublicKey('<recipient2>'),
lamports: 200_000,
})],
tipAmount: 1_000_000, // 0.001 SOL tip
},
// Cleanup transaction
{
instructions: [SystemProgram.transfer({
fromPubkey: signer.publicKey,
toPubkey: new PublicKey('<recipient3>'),
lamports: 50_000,
})],
tipAmount: 500_000, // 0.0005 SOL tip
},
];
const bundleSignature = await submitBatchedTransactions(
// nextblockClient,
connection,
signer,
transactionSpecs
);
console.log(`Basic batch: ${bundleSignature}\n`);
// Example 2: Arbitrage bundle
console.log('2. Arbitrage bundle:');
const arbSignature = await buildArbitrageBundle(
// nextblockClient,
connection,
signer,
new PublicKey('<dex-a-address>'),
new PublicKey('<dex-b-address>'),
1_000_000 // 0.001 SOL trade
);
console.log(`Arbitrage bundle: ${arbSignature}\n`);
// Example 3: Optimized DeFi bundle
console.log('3. DeFi operation bundle:');
const defiSignature = await buildDeFiOperationBundle(
// nextblockClient,
connection,
signer
);
console.log(`DeFi bundle: ${defiSignature}\n`);
// Example 4: Bundle optimization
console.log('4. Bundle optimization:');
const optimizer = new BundleOptimizer();
const baseTip = 1_000_000;
const transactionTypes = ['setup', 'main', 'cleanup'];
const optimizedTips = optimizer.optimizeTips(baseTip, transactionTypes);
console.log(`Optimized tips: ${optimizedTips}`);
// Example 5: Bundle tracking
console.log('5. Bundle tracking:');
const tracker = new BundleTracker();
const bundleStatus: BundleStatus = {
bundleId: 'bundle-123',
transactionCount: 3,
submittedAt: Date.now(),
signatures: ['sig1', 'sig2', 'sig3'],
status: 'pending'
};
tracker.trackBundle(bundleStatus);
// Check status after some time
setTimeout(async () => {
const status = await tracker.checkBundleStatus('bundle-123', connection);
console.log('Bundle status:', status);
}, 5000);
} catch (error) {
console.error('Error in main:', error);
} finally {
// Clean up connection
// await manager.disconnect();
}
}
if (require.main === module) {
main().catch(console.error);
}
export {
TransactionBundle,
submitBatchedTransactions,
buildArbitrageBundle,
buildDeFiOperationBundle,
BundleOptimizer,
BundleTracker,
};
Best Practices
Bundle size limits: Keep bundles between 2-4 transactions for optimal success rates
Transaction ordering: Setup → Main operations → Cleanup
Progressive tipping: Use higher tips for more critical transactions
Error handling: Validate bundles before submission
Performance monitoring: Track bundle success rates and timing
Tip optimization: Adjust tips based on transaction importance and network conditions
Use TypeScript: Leverage type safety for complex bundle operations
Last updated