# 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](/api/examples/javascript/quic.md).

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


---

# 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-single-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.
