# Submit Single Transaction

Submit individual transactions to NextBlock using JavaScript/TypeScript with proper tipping and error handling.

If you want to send raw signed transaction bytes over QUIC instead of gRPC, see [QUIC Transaction Submission](https://docs.nextblock.io/api/examples/javascript/quic).

This example shows transaction 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, 
  sendAndConfirmTransaction 
} from '@solana/web3.js';

<strong>// NextBlock tip wallets for load balancing
</strong>const NEXTBLOCK_TIP_WALLETS = [
  'NEXTbLoCkB51HpLBLojQfpyVAMorm3zzKg7w9NFdqid',
  'nextBLoCkPMgmG8ZgJtABeScP35qLa2AMCNKntAP7Xc',
  'NextbLoCkVtMGcV47JzewQdvBpLqT9TxQFozQkN98pE',
  'NexTbLoCkWykbLuB1NkjXgFWkX9oAtcoagQegygXXA2',
  'NeXTBLoCKs9F1y5PJS9CKrFNNLU1keHW71rfh7KgA1X',
  'NexTBLockJYZ7QD7p2byrUa6df8ndV2WSd8GkbWqfbb',
  'neXtBLock1LeC67jYd1QdAa32kbVeubsfPNTJC1V5At',
  'nEXTBLockYgngeRmRrjDV31mGSekVPqZoMGhQEZtPVG',
];

<strong>// Submission options interface
</strong>interface SubmissionOptions {
  skipPreflight?: boolean;
  frontRunningProtection?: boolean;
  revertOnFail?: boolean;
  disableRetries?: boolean;
  snipeTransaction?: boolean;
}

<strong>// Get random tip wallet for load balancing
</strong>function getRandomNextblockTipWallet(): PublicKey {
  const randomIndex = Math.floor(Math.random() * NEXTBLOCK_TIP_WALLETS.length);
  return new PublicKey(NEXTBLOCK_TIP_WALLETS[randomIndex]);
}

<strong>// Build transaction with tip
</strong>async function buildTransactionWithTip(
  connection: Connection,
  payer: Keypair,
  tipAmount: number,
  instructions: any[]
): Promise&#x3C;Transaction> {
<strong>  // Get recent blockhash
</strong>  const { blockhash } = await connection.getLatestBlockhash('finalized');
  
<strong>  // Create tip instruction (should be first)
</strong>  const tipWallet = getRandomNextblockTipWallet();
  const tipInstruction = SystemProgram.transfer({
    fromPubkey: payer.publicKey,
    toPubkey: tipWallet,
    lamports: tipAmount,
  });
  
<strong>  // Create transaction with tip and user instructions
</strong>  const transaction = new Transaction({
    recentBlockhash: blockhash,
    feePayer: payer.publicKey,
  });
  
  transaction.add(tipInstruction);
  instructions.forEach(instruction => transaction.add(instruction));
  
<strong>  // Sign transaction
</strong>  transaction.sign(payer);
  
  return transaction;
}

<strong>// Submit single transaction to NextBlock
</strong>async function submitSingleTransaction(
  // nextblockClient: any, // Your generated gRPC client
  connection: Connection,
  signer: Keypair,
  recipient: PublicKey,
  transferAmount: number,
  tipAmount: number,
  options: SubmissionOptions = {}
): Promise&#x3C;string> {
<strong>  // Default options
</strong>  const opts: SubmissionOptions = {
    skipPreflight: true,
    frontRunningProtection: false,
    revertOnFail: false,
    disableRetries: false,
    snipeTransaction: false,
    ...options,
  };
  
<strong>  // Create transfer instruction
</strong>  const transferInstruction = SystemProgram.transfer({
    fromPubkey: signer.publicKey,
    toPubkey: recipient,
    lamports: transferAmount,
  });
  
<strong>  // Build transaction with tip
</strong>  const transaction = await buildTransactionWithTip(
    connection,
    signer,
    tipAmount,
    [transferInstruction]
  );
  
<strong>  // Convert to base64 for submission
</strong>  const serializedTx = transaction.serialize();
  const base64Tx = serializedTx.toString('base64');
  
<strong>  // Submit to NextBlock
</strong>  /* Uncomment when you have the generated gRPC client
  const request = {
    transaction: { content: base64Tx },
    skipPreFlight: opts.skipPreflight,
    snipeTransaction: opts.snipeTransaction,
    frontRunningProtection: opts.frontRunningProtection,
    disableRetries: opts.disableRetries,
    revertOnFail: opts.revertOnFail,
  };
  
  const response = await nextblockClient.postSubmitV2(request);
  
  console.log('Transaction submitted successfully!');
  console.log(`Signature: ${response.signature}`);
  console.log(`UUID: ${response.uuid}`);
  
  return response.signature;
  */
  
<strong>  // Until your generated client is wired in, return the local signature
</strong>  const signature = transaction.signature?.toString() || '';
  console.log(`Local transaction built successfully: ${signature}`);
  return signature;
}

<strong>// Submit with priority-based tip calculation
</strong>async function submitWithOptimalTip(
  // nextblockClient: any,
  connection: Connection,
  signer: Keypair,
  recipient: PublicKey,
  transferAmount: number,
  priorityLevel: 'conservative' | 'normal' | 'aggressive' | 'priority' = 'normal'
): Promise&#x3C;string> {
<strong>  // Tip amounts based on priority level
</strong>  // Get tip amounts from current tip floor data
  // Higher tips = higher priority
  // Use tip floor streaming API to get current optimal tips
  const tipAmount = await getOptimalTipFromTipFloor(priorityLevel);
  

  
  return await submitSingleTransaction(
    // nextblockClient,
    connection,
    signer,
    recipient,
    transferAmount,
    tipAmount
  );
}

<strong>// Transaction builder class for complex transactions
</strong>class TransactionBuilder {
  private payer: Keypair;
  private instructions: any[] = [];
  private tipAmount: number = 1_000_000; // Default tip

  constructor(payer: Keypair) {
    this.payer = payer;
  }

  addInstruction(instruction: any): TransactionBuilder {
    this.instructions.push(instruction);
    return this;
  }

  setTipAmount(amount: number): TransactionBuilder {
    this.tipAmount = amount;
    return this;
  }

  async buildAndSubmit(
    // nextblockClient: any,
    connection: Connection,
    options?: SubmissionOptions
  ): Promise&#x3C;string> {
    if (this.instructions.length === 0) {
      throw new Error('No instructions added to transaction');
    }

    const transaction = await buildTransactionWithTip(
      connection,
      this.payer,
      this.tipAmount,
      this.instructions
    );

<strong>    // Convert and submit (similar to submitSingleTransaction)
</strong>    const serializedTx = transaction.serialize();
    const base64Tx = serializedTx.toString('base64');

    const signature = transaction.signature?.toString() || '';
    console.log(`Custom transaction built locally: ${signature}`);
    return signature;
  }
}

<strong>// Batch multiple single transactions (concurrent submission)
</strong>async function submitMultipleSingleTransactions(
  // nextblockClient: any,
  connection: Connection,
  signer: Keypair,
  transactionsData: Array&#x3C;{
    recipient: PublicKey;
    transferAmount: number;
    tipAmount: number;
  }>
): Promise&#x3C;string[]> {
<strong>  // Create promises for all transactions
</strong>  const transactionPromises = transactionsData.map(async ({ recipient, transferAmount, tipAmount }) => {
    try {
      return await submitSingleTransaction(
        // nextblockClient,
        connection,
        signer,
        recipient,
        transferAmount,
        tipAmount
      );
    } catch (error) {
      console.error(`Transaction failed:`, error);
      return null;
    }
  });

<strong>  // Execute all transactions concurrently
</strong>  const results = await Promise.allSettled(transactionPromises);
  
<strong>  // Process results
</strong>  const successfulSignatures: string[] = [];
  results.forEach((result, index) => {
    if (result.status === 'fulfilled' &#x26;&#x26; result.value) {
      successfulSignatures.push(result.value);
      console.log(`Transaction ${index + 1} successful: ${result.value}`);
    } else {
      console.error(`Transaction ${index + 1} failed:`, result);
    }
  });

  return successfulSignatures;
}

<strong>// Performance monitoring
</strong>class TransactionMetrics {
  private submissions: number = 0;
  private successes: number = 0;
  private failures: number = 0;
  private totalTime: number = 0;
  private startTime?: number;

  startSubmission(): void {
    this.submissions++;
    this.startTime = Date.now();
  }

  recordSuccess(): void {
    if (this.startTime) {
      this.totalTime += Date.now() - this.startTime;
      this.successes++;
      this.startTime = undefined;
    }
  }

  recordFailure(): void {
    if (this.startTime) {
      this.totalTime += Date.now() - this.startTime;
      this.failures++;
      this.startTime = undefined;
    }
  }

  getStats() {
    return {
      totalSubmissions: this.submissions,
      successes: this.successes,
      failures: this.failures,
      successRate: (this.successes / Math.max(this.submissions, 1)) * 100,
      averageTime: this.totalTime / Math.max(this.successes, 1),
    };
  }
}

<strong>// Submit with monitoring
</strong>async function submitWithMonitoring(
  // nextblockClient: any,
  connection: Connection,
  signer: Keypair,
  recipient: PublicKey,
  transferAmount: number,
  tipAmount: number,
  metrics: TransactionMetrics
): Promise&#x3C;string | null> {
  metrics.startSubmission();

  try {
    const signature = await submitSingleTransaction(
      // nextblockClient,
      connection,
      signer,
      recipient,
      transferAmount,
      tipAmount
    );
    metrics.recordSuccess();
    return signature;
  } catch (error) {
    metrics.recordFailure();
    console.error('Transaction failed:', error);
    return 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
  const recipient = new PublicKey('&#x3C;recipient-public-key>');

<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 transaction submission
</strong>    console.log('1. Basic transaction submission:');
    const signature1 = await submitSingleTransaction(
      // nextblockClient,
      connection,
      signer,
      recipient,
      10_000,     // Transfer 10,000 lamports
      1_000_000   // Tip 1,000,000 lamports (0.001 SOL)
    );
    console.log(`Basic transaction: ${signature1}\n`);

<strong>    // Example 2: Transaction with optimal tip
</strong>    console.log('2. Transaction with optimal tip:');
    const signature2 = await submitWithOptimalTip(
      // nextblockClient,
      connection,
      signer,
      recipient,
      20_000,      // Transfer 20,000 lamports
      'aggressive' // Use aggressive tip strategy
    );
    console.log(`Optimally tipped transaction: ${signature2}\n`);

<strong>    // Example 3: Custom transaction builder
</strong>    console.log('3. Custom transaction builder:');
    const builder = new TransactionBuilder(signer);
    const signature3 = await builder
      .addInstruction(SystemProgram.transfer({
        fromPubkey: signer.publicKey,
        toPubkey: recipient,
        lamports: 30_000,
      }))
      .setTipAmount(1_500_000)
      .buildAndSubmit(
        // nextblockClient,
        connection
      );
    console.log(`Custom built transaction: ${signature3}\n`);

<strong>    // Example 4: Multiple concurrent transactions
</strong>    console.log('4. Multiple concurrent transactions:');
    const transactionsData = [
      { recipient, transferAmount: 5_000, tipAmount: 500_000 },
      { recipient, transferAmount: 7_500, tipAmount: 750_000 },
      { recipient, transferAmount: 12_500, tipAmount: 1_250_000 },
    ];

    const signatures = await submitMultipleSingleTransactions(
      // nextblockClient,
      connection,
      signer,
      transactionsData
    );
    console.log(`Multiple transactions: ${signatures}\n`);

<strong>    // Example 5: Transaction monitoring
</strong>    console.log('5. Transaction with performance monitoring:');
    const metrics = new TransactionMetrics();

    for (let i = 0; i &#x3C; 5; i++) {
      await submitWithMonitoring(
        // nextblockClient,
        connection,
        signer,
        recipient,
        1_000 * (i + 1), // Varying amounts
        500_000,         // Fixed tip
        metrics
      );
    }

    console.log('Performance metrics:', metrics.getStats());

  } catch (error) {
    console.error('Error in main:', error);
  } finally {
<strong>    // Clean up connection
</strong>    // await manager.disconnect();
  }
}

<strong>// Error handling with retry logic
</strong>async function submitWithRetry(
  // nextblockClient: any,
  connection: Connection,
  signer: Keypair,
  recipient: PublicKey,
  transferAmount: number,
  tipAmount: number,
  maxRetries: number = 3
): Promise&#x3C;string> {
  let lastError: Error;

  for (let attempt = 1; attempt &#x3C;= maxRetries; attempt++) {
    try {
      return await submitSingleTransaction(
        // nextblockClient,
        connection,
        signer,
        recipient,
        transferAmount,
        tipAmount
      );
    } catch (error) {
      lastError = error as Error;
      
      if (attempt &#x3C; maxRetries) {
        const delay = Math.min(1000 * Math.pow(2, attempt - 1), 30000);
        console.log(`Attempt ${attempt} failed, retrying in ${delay}ms...`);
        await new Promise(resolve => setTimeout(resolve, delay));
      }
    }
  }

  throw lastError!;
}

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

export {
  submitSingleTransaction,
  submitWithOptimalTip,
  TransactionBuilder,
  submitMultipleSingleTransactions,
  TransactionMetrics,
  submitWithMonitoring,
  submitWithRetry,
};
</code></pre>

## Best Practices

1. **Always include tips**: NextBlock prioritizes transactions with appropriate tips
2. **Use random tip wallets**: Distribute load across multiple tip addresses
3. **Monitor tip floors**: Adjust tips based on current network conditions
4. **Handle errors gracefully**: Implement retry logic with exponential backoff
5. **Validate inputs**: Always validate public keys and amounts before submission
6. **Use TypeScript**: Leverage type safety for better development experience
7. **Monitor performance**: Track success rates and response times
8. **Choose appropriate RPC endpoints**: Use reliable RPC providers for blockhash retrieval
