# Submit Batched Transactions

Submit 2-4 transactions as an atomic bundle to NextBlock using JavaScript/TypeScript.

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`.

<pre class="language-typescript"><code class="lang-typescript"><strong>import { 
</strong>  Connection, 
  Keypair, 
  PublicKey, 
  SystemProgram, 
  Transaction 
} from '@solana/web3.js';

<strong>// Transaction bundle builder
</strong>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 &#x3C; 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`);
    }

<strong>    // Check for duplicate signatures
</strong>    const signatures = new Set();
    for (const tx of this.transactions) {
      const sig = tx.signature?.toString();
      if (sig &#x26;&#x26; 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;
  }
}

<strong>// Build single transaction with tip
</strong>async function buildTransactionWithTip(
  connection: Connection,
  payer: Keypair,
  tipAmount: number,
  instructions: any[]
): Promise&#x3C;Transaction> {
  const { blockhash } = await connection.getLatestBlockhash('finalized');
  
<strong>  // Random tip wallet for load balancing
</strong>  const tipWallets = [
    'NEXTbLoCkB51HpLBLojQfpyVAMorm3zzKg7w9NFdqid',
    'nextBLoCkPMgmG8ZgJtABeScP35qLa2AMCNKntAP7Xc',
    'NextbLoCkVtMGcV47JzewQdvBpLqT9TxQFozQkN98pE',
    'NexTbLoCkWykbLuB1NkjXgFWkX9oAtcoagQegygXXA2',
  ];
  
  const tipWallet = new PublicKey(tipWallets[Math.floor(Math.random() * tipWallets.length)]);
  
<strong>  // Create tip instruction (should be first)
</strong>  const tipInstruction = SystemProgram.transfer({
    fromPubkey: payer.publicKey,
    toPubkey: tipWallet,
    lamports: tipAmount,
  });

<strong>  // Create and sign transaction
</strong>  const transaction = new Transaction({
    recentBlockhash: blockhash,
    feePayer: payer.publicKey,
  });

  transaction.add(tipInstruction);
  instructions.forEach(instruction => transaction.add(instruction));
  transaction.sign(payer);

  return transaction;
}

<strong>// Submit batched transactions
</strong>async function submitBatchedTransactions(
  // nextblockClient: any, // Your generated gRPC client
  connection: Connection,
  signer: Keypair,
  transactionSpecs: Array&#x3C;{
    instructions: any[];
    tipAmount: number;
  }>
): Promise&#x3C;string> {
<strong>  // Build transaction bundle
</strong>  const bundle = new TransactionBundle();
  
  for (const { instructions, tipAmount } of transactionSpecs) {
    const transaction = await buildTransactionWithTip(
      connection,
      signer,
      tipAmount,
      instructions
    );
    bundle.addTransaction(transaction);
  }

<strong>  // Get base64 transactions for submission
</strong>  const base64Transactions = bundle.toBase64Transactions();
  const signatures = bundle.getSignatures();

<strong>  // Log transaction signatures
</strong>  signatures.forEach((sig, i) => {
    console.log(`Transaction ${i + 1} signature: ${sig}`);
  });

<strong>  // Submit bundle to NextBlock
</strong>  /* 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;
  */

<strong>  // Until your generated client is wired in, return a placeholder bundle signature
</strong>  const bundleSignature = '';
  console.log(`Local bundle prepared with ${bundle.size} transactions.`);
  return bundleSignature;
}

<strong>// Build arbitrage bundle
</strong>async function buildArbitrageBundle(
  // nextblockClient: any,
  connection: Connection,
  signer: Keypair,
  dexAAddress: PublicKey,
  dexBAddress: PublicKey,
  tradeAmount: number
): Promise&#x3C;string> {
<strong>  // Transaction 1: Buy on DEX A
</strong>  const buyInstructions = [
<strong>    // Add your DEX-specific buy instructions here
</strong>    SystemProgram.transfer({
      fromPubkey: signer.publicKey,
      toPubkey: dexAAddress,
      lamports: tradeAmount,
    })
  ];

<strong>  // Transaction 2: Sell on DEX B
</strong>  const sellInstructions = [
<strong>    // Add your DEX-specific sell instructions here
</strong>    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
  );
}

<strong>// Build complex DeFi operation bundle
</strong>async function buildDeFiOperationBundle(
  // nextblockClient: any,
  connection: Connection,
  signer: Keypair
): Promise&#x3C;string> {
<strong>  // Transaction 1: Setup - Create token accounts
</strong>  const setupInstructions = [
<strong>    // Add token account creation instructions
</strong>    SystemProgram.transfer({
      fromPubkey: signer.publicKey,
      toPubkey: new PublicKey('&#x3C;token-program-address>'),
      lamports: 1_000_000 // Rent exemption
    })
  ];

<strong>  // Transaction 2: Main operation - Execute swap
</strong>  const swapInstructions = [
<strong>    // Add swap instructions (Jupiter, Raydium, etc.)
</strong>    SystemProgram.transfer({
      fromPubkey: signer.publicKey,
      toPubkey: new PublicKey('&#x3C;swap-program-address>'),
      lamports: 0 // No SOL transfer for swap
    })
  ];

<strong>  // Transaction 3: Cleanup - Stake or provide liquidity
</strong>  const stakeInstructions = [
<strong>    // Add staking/liquidity instructions
</strong>    SystemProgram.transfer({
      fromPubkey: signer.publicKey,
      toPubkey: new PublicKey('&#x3C;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
  );
}

<strong>// Bundle optimizer
</strong>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&#x3C;T>(
    transactions: T[], 
    transactionTypes: string[]
  ): { transactions: T[]; types: string[] } {
<strong>    // Priority order: setup -> main -> cleanup
</strong>    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)
    };
  }
}

<strong>// Bundle status tracking
</strong>interface BundleStatus {
  bundleId: string;
  transactionCount: number;
  submittedAt: number;
  signatures: string[];
  status: 'pending' | 'confirmed' | 'failed';
}

class BundleTracker {
  private bundles: Map&#x3C;string, BundleStatus> = new Map();

  trackBundle(bundleStatus: BundleStatus): void {
    this.bundles.set(bundleStatus.bundleId, bundleStatus);
  }

  async checkBundleStatus(
    bundleId: string,
    connection: Connection
  ): Promise&#x3C;BundleStatus | null> {
    const bundleStatus = this.bundles.get(bundleId);
    if (!bundleStatus) return null;

<strong>    // Check if all transactions are confirmed
</strong>    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);
      }
    }

<strong>    // Update bundle status
</strong>    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;
  }
}
</code></pre>

## Usage Examples

<pre class="language-typescript"><code class="lang-typescript"><strong>async function main() {
</strong><strong>  // Initialize Solana connection
</strong>  const connection = new Connection('https://api.mainnet-beta.solana.com', 'confirmed');
  const signer = Keypair.generate(); // Use your actual keypair

<strong>  // Connect to NextBlock (see connection.md)
</strong>  // const config = configFromEnv();
  // const manager = new NextBlockConnectionManager(config);
  // await manager.connect();
  // const nextblockClient = manager.grpcClient;

  try {
<strong>    // Example 1: Basic batch submission
</strong>    console.log('1. Basic batch submission:');
    const transactionSpecs = [
<strong>      // Setup transaction
</strong>      {
        instructions: [SystemProgram.transfer({
          fromPubkey: signer.publicKey,
          toPubkey: new PublicKey('&#x3C;recipient1>'),
          lamports: 100_000,
        })],
        tipAmount: 500_000, // 0.0005 SOL tip
      },
<strong>      // Main transaction
</strong>      {
        instructions: [SystemProgram.transfer({
          fromPubkey: signer.publicKey,
          toPubkey: new PublicKey('&#x3C;recipient2>'),
          lamports: 200_000,
        })],
        tipAmount: 1_000_000, // 0.001 SOL tip
      },
<strong>      // Cleanup transaction
</strong>      {
        instructions: [SystemProgram.transfer({
          fromPubkey: signer.publicKey,
          toPubkey: new PublicKey('&#x3C;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`);

<strong>    // Example 2: Arbitrage bundle
</strong>    console.log('2. Arbitrage bundle:');
    const arbSignature = await buildArbitrageBundle(
      // nextblockClient,
      connection,
      signer,
      new PublicKey('&#x3C;dex-a-address>'),
      new PublicKey('&#x3C;dex-b-address>'),
      1_000_000 // 0.001 SOL trade
    );
    console.log(`Arbitrage bundle: ${arbSignature}\n`);

<strong>    // Example 3: Optimized DeFi bundle
</strong>    console.log('3. DeFi operation bundle:');
    const defiSignature = await buildDeFiOperationBundle(
      // nextblockClient,
      connection,
      signer
    );
    console.log(`DeFi bundle: ${defiSignature}\n`);

<strong>    // Example 4: Bundle optimization
</strong>    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}`);

<strong>    // Example 5: Bundle tracking
</strong>    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);
    
<strong>    // Check status after some time
</strong>    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 {
<strong>    // Clean up connection
</strong>    // await manager.disconnect();
  }
}

if (require.main === module) {
  main().catch(console.error);
}

export {
  TransactionBundle,
  submitBatchedTransactions,
  buildArbitrageBundle,
  buildDeFiOperationBundle,
  BundleOptimizer,
  BundleTracker,
};
</code></pre>

## Best Practices

1. **Bundle size limits**: Keep bundles between 2-4 transactions for optimal success rates
2. **Transaction ordering**: Setup → Main operations → Cleanup
3. **Progressive tipping**: Use higher tips for more critical transactions
4. **Error handling**: Validate bundles before submission
5. **Performance monitoring**: Track bundle success rates and timing
6. **Tip optimization**: Adjust tips based on transaction importance and network conditions
7. **Use TypeScript**: Leverage type safety for complex bundle operations


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.nextblock.io/api/examples/javascript/submit-batched-transactions.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
