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.

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.

import { 
  Connection, 
  Keypair, 
  PublicKey, 
  SystemProgram, 
  Transaction, 
  sendAndConfirmTransaction 
} from '@solana/web3.js';

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

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

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

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

// Submit single transaction to NextBlock
async function submitSingleTransaction(
  // nextblockClient: any, // Your generated gRPC client
  connection: Connection,
  signer: Keypair,
  recipient: PublicKey,
  transferAmount: number,
  tipAmount: number,
  options: SubmissionOptions = {}
): Promise<string> {
  // Default options
  const opts: SubmissionOptions = {
    skipPreflight: true,
    frontRunningProtection: false,
    revertOnFail: false,
    disableRetries: false,
    snipeTransaction: false,
    ...options,
  };
  
  // Create transfer instruction
  const transferInstruction = SystemProgram.transfer({
    fromPubkey: signer.publicKey,
    toPubkey: recipient,
    lamports: transferAmount,
  });
  
  // Build transaction with tip
  const transaction = await buildTransactionWithTip(
    connection,
    signer,
    tipAmount,
    [transferInstruction]
  );
  
  // Convert to base64 for submission
  const serializedTx = transaction.serialize();
  const base64Tx = serializedTx.toString('base64');
  
  // Submit to NextBlock
  /* 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;
  */
  
  // Until your generated client is wired in, return the local signature
  const signature = transaction.signature?.toString() || '';
  console.log(`Local transaction built successfully: ${signature}`);
  return signature;
}

// Submit with priority-based tip calculation
async function submitWithOptimalTip(
  // nextblockClient: any,
  connection: Connection,
  signer: Keypair,
  recipient: PublicKey,
  transferAmount: number,
  priorityLevel: 'conservative' | 'normal' | 'aggressive' | 'priority' = 'normal'
): Promise<string> {
  // Tip amounts based on priority level
  // 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
  );
}

// Transaction builder class for complex transactions
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<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
    );

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

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

// Batch multiple single transactions (concurrent submission)
async function submitMultipleSingleTransactions(
  // nextblockClient: any,
  connection: Connection,
  signer: Keypair,
  transactionsData: Array<{
    recipient: PublicKey;
    transferAmount: number;
    tipAmount: number;
  }>
): Promise<string[]> {
  // Create promises for all transactions
  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;
    }
  });

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

  return successfulSignatures;
}

// Performance monitoring
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),
    };
  }
}

// Submit with monitoring
async function submitWithMonitoring(
  // nextblockClient: any,
  connection: Connection,
  signer: Keypair,
  recipient: PublicKey,
  transferAmount: number,
  tipAmount: number,
  metrics: TransactionMetrics
): Promise<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;
  }
}

Usage Examples

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

Last updated