Submit Single Transaction

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

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;
  */
  
  // Mock response for demonstration
  const signature = transaction.signature?.toString() || 'mock-signature';
  console.log(`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() || 'mock-signature';
    console.log(`Custom transaction submitted: ${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

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
  const recipient = new PublicKey('<recipient-public-key>');

  // 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 transaction submission
    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`);

    // Example 2: Transaction with optimal tip
    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`);

    // Example 3: Custom transaction builder
    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`);

    // Example 4: Multiple concurrent transactions
    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`);

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

    for (let i = 0; i < 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 {
    // Clean up connection
    // await manager.disconnect();
  }
}

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

  for (let attempt = 1; attempt <= maxRetries; attempt++) {
    try {
      return await submitSingleTransaction(
        // nextblockClient,
        connection,
        signer,
        recipient,
        transferAmount,
        tipAmount
      );
    } catch (error) {
      lastError = error as Error;
      
      if (attempt < 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,
};

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