FortiBlox LogoFortiBlox Docs
NexusRPC NodesRPC Guides

Transaction Submission

Complete guide to sending, simulating, and monitoring blockchain transactions with best practices for reliability

Transaction Submission

Transaction submission is the process of sending signed transactions to the X1 Blockchain. This guide covers everything from building transactions to handling failures and ensuring successful execution.

Overview

Submitting transactions involves several steps:

  1. Building - Construct transaction with instructions and accounts
  2. Simulating - Test transaction before sending (optional but recommended)
  3. Signing - Sign with required private keys
  4. Sending - Submit to the network
  5. Confirming - Wait for confirmation and verify success

When to Use Transaction Methods

Use these methods when you need to:

  • Send SOL transfers - Move native tokens between accounts
  • Execute program instructions - Call on-chain programs
  • Create or modify accounts - Deploy programs or initialize data
  • Test transactions - Simulate before actual submission

Core Methods

sendTransaction

Submits a fully-signed transaction to the blockchain.

const {
  Connection,
  Keypair,
  SystemProgram,
  Transaction,
  sendAndConfirmTransaction,
  PublicKey,
  LAMPORTS_PER_SOL
} = require('@solana/web3.js');

const connection = new Connection(
  'https://nexus.fortiblox.com/rpc',
  {
    commitment: 'confirmed',
    httpHeaders: {
      'X-API-Key': process.env.FORTIBLOX_API_KEY
    }
  }
);

async function sendTransaction(fromKeypair, toAddress, amountSOL) {
  const toPubkey = new PublicKey(toAddress);

  // Create transaction
  const transaction = new Transaction().add(
    SystemProgram.transfer({
      fromPubkey: fromKeypair.publicKey,
      toPubkey: toPubkey,
      lamports: amountSOL * LAMPORTS_PER_SOL
    })
  );

  // Get recent blockhash
  const { blockhash, lastValidBlockHeight } = await connection.getLatestBlockhash();
  transaction.recentBlockhash = blockhash;
  transaction.feePayer = fromKeypair.publicKey;

  // Sign transaction
  transaction.sign(fromKeypair);

  // Send transaction
  const signature = await connection.sendRawTransaction(
    transaction.serialize()
  );

  console.log('Transaction sent:', signature);

  // Wait for confirmation
  const confirmation = await connection.confirmTransaction({
    signature,
    blockhash,
    lastValidBlockHeight
  });

  if (confirmation.value.err) {
    throw new Error(`Transaction failed: ${JSON.stringify(confirmation.value.err)}`);
  }

  console.log('Transaction confirmed:', signature);

  return signature;
}

// Helper: Send with automatic confirmation
async function sendAndConfirm(fromKeypair, toAddress, amountSOL) {
  const transaction = new Transaction().add(
    SystemProgram.transfer({
      fromPubkey: fromKeypair.publicKey,
      toPubkey: new PublicKey(toAddress),
      lamports: amountSOL * LAMPORTS_PER_SOL
    })
  );

  const signature = await sendAndConfirmTransaction(
    connection,
    transaction,
    [fromKeypair]
  );

  console.log('Transaction confirmed:', signature);
  return signature;
}

// Example usage
const sender = Keypair.fromSecretKey(/* your secret key */);
sendTransaction(
  sender,
  'vines1vzrYbzLMRdu58ou5XTby4qAqVRLmqo36NKPTg',
  0.1
);
from solana.rpc.api import Client
from solana.transaction import Transaction
from solana.system_program import TransferParams, transfer
from solana.keypair import Keypair
from solana.publickey import PublicKey
import os

client = Client(
    "https://nexus.fortiblox.com/rpc",
    extra_headers={"X-API-Key": os.environ['FORTIBLOX_API_KEY']}
)

def send_transaction(from_keypair, to_address, amount_sol):
    """Send SOL from one account to another"""
    to_pubkey = PublicKey(to_address)

    # Create transfer instruction
    transfer_ix = transfer(
        TransferParams(
            from_pubkey=from_keypair.public_key,
            to_pubkey=to_pubkey,
            lamports=int(amount_sol * 1e9)
        )
    )

    # Create transaction
    transaction = Transaction()
    transaction.add(transfer_ix)

    # Get recent blockhash
    blockhash_resp = client.get_latest_blockhash()
    transaction.recent_blockhash = blockhash_resp.value.blockhash

    # Sign transaction
    transaction.sign(from_keypair)

    # Send transaction
    response = client.send_raw_transaction(
        transaction.serialize()
    )

    signature = response.value

    print(f"Transaction sent: {signature}")

    # Wait for confirmation
    client.confirm_transaction(signature)

    print(f"Transaction confirmed: {signature}")

    return signature

# Example usage
sender = Keypair.from_secret_key(b"your_secret_key")
send_transaction(
    sender,
    'vines1vzrYbzLMRdu58ou5XTby4qAqVRLmqo36NKPTg',
    0.1
)
# Note: You need to construct and sign the transaction first
# This example shows the raw RPC call

curl -X POST https://nexus.fortiblox.com/rpc \
  -H "X-API-Key: $FORTIBLOX_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "jsonrpc": "2.0",
    "id": 1,
    "method": "sendTransaction",
    "params": [
      "BASE64_ENCODED_SIGNED_TRANSACTION",
      {
        "encoding": "base64",
        "skipPreflight": false,
        "preflightCommitment": "confirmed",
        "maxRetries": 3
      }
    ]
  }'

Use Cases:

  • SOL transfers between wallets
  • Token transfers (SPL tokens)
  • Program instruction execution
  • Account creation and initialization

Performance Tips:

  • Heavy method: 10 credits - Most expensive operation
  • Use skipPreflight: false to catch errors before sending
  • Set appropriate maxRetries for reliability
  • Always confirm transactions before showing success to users

Security Note: Never expose private keys in client-side code. Always sign transactions on the backend or use wallet adapters for browser applications.


simulateTransaction

Simulates a transaction without actually submitting it to the network.

async function simulateTransaction(transaction) {
  // Ensure transaction has recent blockhash
  const { blockhash } = await connection.getLatestBlockhash();
  transaction.recentBlockhash = blockhash;

  // Simulate (doesn't require signatures)
  const simulation = await connection.simulateTransaction(transaction);

  if (simulation.value.err) {
    console.error('Simulation failed:', simulation.value.err);
    console.error('Logs:', simulation.value.logs);
    return { success: false, error: simulation.value.err };
  }

  console.log('Simulation successful');
  console.log('Units consumed:', simulation.value.unitsConsumed);
  console.log('Logs:', simulation.value.logs);

  return {
    success: true,
    unitsConsumed: simulation.value.unitsConsumed,
    logs: simulation.value.logs
  };
}

// Simulate before sending
async function sendWithSimulation(fromKeypair, toAddress, amountSOL) {
  const transaction = new Transaction().add(
    SystemProgram.transfer({
      fromPubkey: fromKeypair.publicKey,
      toPubkey: new PublicKey(toAddress),
      lamports: amountSOL * LAMPORTS_PER_SOL
    })
  );

  // Set fee payer for simulation
  transaction.feePayer = fromKeypair.publicKey;

  // Simulate first
  const simulation = await simulateTransaction(transaction);

  if (!simulation.success) {
    throw new Error(`Simulation failed: ${JSON.stringify(simulation.error)}`);
  }

  console.log('Simulation passed, sending transaction...');

  // Sign and send
  const { blockhash, lastValidBlockHeight } = await connection.getLatestBlockhash();
  transaction.recentBlockhash = blockhash;
  transaction.sign(fromKeypair);

  const signature = await connection.sendRawTransaction(
    transaction.serialize()
  );

  await connection.confirmTransaction({
    signature,
    blockhash,
    lastValidBlockHeight
  });

  return signature;
}

// Check if transaction will succeed
async function willTransactionSucceed(transaction) {
  try {
    const simulation = await simulateTransaction(transaction);
    return simulation.success;
  } catch (error) {
    console.error('Simulation error:', error);
    return false;
  }
}
def simulate_transaction(transaction):
    """Simulate a transaction without sending"""
    # Get recent blockhash
    blockhash_resp = client.get_latest_blockhash()
    transaction.recent_blockhash = blockhash_resp.value.blockhash

    # Simulate
    response = client.simulate_transaction(transaction)

    if response.value.err:
        print(f"Simulation failed: {response.value.err}")
        print(f"Logs: {response.value.logs}")
        return {"success": False, "error": response.value.err}

    print("Simulation successful")
    print(f"Units consumed: {response.value.units_consumed}")
    print(f"Logs: {response.value.logs}")

    return {
        "success": True,
        "units_consumed": response.value.units_consumed,
        "logs": response.value.logs
    }

def send_with_simulation(from_keypair, to_address, amount_sol):
    """Simulate before sending"""
    to_pubkey = PublicKey(to_address)

    # Create transaction
    transfer_ix = transfer(
        TransferParams(
            from_pubkey=from_keypair.public_key,
            to_pubkey=to_pubkey,
            lamports=int(amount_sol * 1e9)
        )
    )

    transaction = Transaction()
    transaction.add(transfer_ix)
    transaction.fee_payer = from_keypair.public_key

    # Simulate first
    simulation = simulate_transaction(transaction)

    if not simulation["success"]:
        raise Exception(f"Simulation failed: {simulation['error']}")

    print("Simulation passed, sending transaction...")

    # Sign and send
    blockhash_resp = client.get_latest_blockhash()
    transaction.recent_blockhash = blockhash_resp.value.blockhash
    transaction.sign(from_keypair)

    response = client.send_raw_transaction(transaction.serialize())
    signature = response.value

    client.confirm_transaction(signature)

    return signature
curl -X POST https://nexus.fortiblox.com/rpc \
  -H "X-API-Key: $FORTIBLOX_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "jsonrpc": "2.0",
    "id": 1,
    "method": "simulateTransaction",
    "params": [
      "BASE64_ENCODED_TRANSACTION",
      {
        "encoding": "base64",
        "commitment": "confirmed"
      }
    ]
  }'

Use Cases:

  • Testing transactions before spending fees
  • Estimating compute units needed
  • Debugging transaction failures
  • Validating program logic

Performance Tips:

  • Heavy method: 10 credits - Same as sendTransaction
  • Doesn't require transaction signatures for simulation
  • Use to catch errors before actual submission
  • Great for development and testing

getFeeForMessage

Gets the fee the network will charge for a particular message (transaction).

async function getTransactionFee(transaction) {
  // Get recent blockhash
  const { blockhash } = await connection.getLatestBlockhash();
  transaction.recentBlockhash = blockhash;

  // Compile message
  const message = transaction.compileMessage();

  // Get fee
  const feeResponse = await connection.getFeeForMessage(message);

  if (feeResponse.value === null) {
    throw new Error('Failed to get fee estimate');
  }

  const feeLamports = feeResponse.value;
  const feeSOL = feeLamports / LAMPORTS_PER_SOL;

  console.log(`Estimated fee: ${feeLamports} lamports (${feeSOL} SOL)`);

  return { lamports: feeLamports, sol: feeSOL };
}

// Check if user can afford transaction
async function canAffordTransaction(payerPubkey, transaction) {
  const balance = await connection.getBalance(payerPubkey);
  const fee = await getTransactionFee(transaction);

  // Calculate total cost (including transfer amount if applicable)
  let totalCost = fee.lamports;

  // Add transfer amount if it's a transfer instruction
  transaction.instructions.forEach(ix => {
    if (ix.programId.equals(SystemProgram.programId)) {
      // Parse transfer amount (simplified)
      totalCost += ix.data.readBigUInt64LE(4);
    }
  });

  const canAfford = balance >= totalCost;

  console.log(`Balance: ${balance} lamports`);
  console.log(`Total cost: ${totalCost} lamports`);
  console.log(`Can afford: ${canAfford}`);

  return canAfford;
}

// Example usage
const transaction = new Transaction().add(
  SystemProgram.transfer({
    fromPubkey: sender.publicKey,
    toPubkey: recipient,
    lamports: 0.1 * LAMPORTS_PER_SOL
  })
);

getTransactionFee(transaction);
def get_transaction_fee(transaction):
    """Get estimated fee for transaction"""
    # Get recent blockhash
    blockhash_resp = client.get_latest_blockhash()
    transaction.recent_blockhash = blockhash_resp.value.blockhash

    # Compile message
    message = transaction.compile_message()

    # Get fee
    response = client.get_fee_for_message(message)

    if response.value is None:
        raise Exception("Failed to get fee estimate")

    fee_lamports = response.value
    fee_sol = fee_lamports / 1e9

    print(f"Estimated fee: {fee_lamports} lamports ({fee_sol} SOL)")

    return {"lamports": fee_lamports, "sol": fee_sol}

def can_afford_transaction(payer_pubkey, transaction):
    """Check if payer can afford transaction"""
    balance_resp = client.get_balance(payer_pubkey)
    balance = balance_resp.value

    fee = get_transaction_fee(transaction)

    # Simplified - add transfer amount if applicable
    total_cost = fee["lamports"]

    can_afford = balance >= total_cost

    print(f"Balance: {balance} lamports")
    print(f"Total cost: {total_cost} lamports")
    print(f"Can afford: {can_afford}")

    return can_afford
# First, compile your transaction message
# Then get the fee estimate

curl -X POST https://nexus.fortiblox.com/rpc \
  -H "X-API-Key: $FORTIBLOX_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "jsonrpc": "2.0",
    "id": 1,
    "method": "getFeeForMessage",
    "params": [
      "BASE64_ENCODED_MESSAGE",
      {
        "commitment": "confirmed"
      }
    ]
  }'

Use Cases:

  • Estimating transaction costs before submission
  • Validating sufficient balance
  • Fee estimation for users
  • Cost optimization

Performance Tips:

  • Lightweight method (1 credit)
  • Doesn't require signatures
  • Fees are deterministic based on signatures and compute units

Transaction Lifecycle

Understanding the transaction lifecycle is crucial for reliable submissions:

1. Transaction States

Created → Signed → Sent → Processed → Confirmed → Finalized
  • Created - Transaction built with instructions
  • Signed - All required signatures added
  • Sent - Submitted to RPC node
  • Processed - Included in a block (not confirmed yet)
  • Confirmed - Supermajority of validators confirmed
  • Finalized - Permanent (32+ blocks later)

2. Confirmation Strategies

// Basic confirmation
async function confirmTransaction(signature, blockhash, lastValidBlockHeight) {
  const confirmation = await connection.confirmTransaction({
    signature,
    blockhash,
    lastValidBlockHeight
  });

  if (confirmation.value.err) {
    throw new Error(`Transaction failed: ${JSON.stringify(confirmation.value.err)}`);
  }

  return confirmation;
}
// With custom timeout
async function confirmTransactionWithTimeout(
  signature,
  blockhash,
  lastValidBlockHeight,
  timeoutMs = 60000
) {
  const startTime = Date.now();

  while (Date.now() - startTime < timeoutMs) {
    const status = await connection.getSignatureStatus(signature);

    if (status.value?.confirmationStatus === 'confirmed' ||
        status.value?.confirmationStatus === 'finalized') {

      if (status.value.err) {
        throw new Error(`Transaction failed: ${JSON.stringify(status.value.err)}`);
      }

      return status;
    }

    // Wait before checking again
    await new Promise(resolve => setTimeout(resolve, 1000));
  }

  throw new Error('Transaction confirmation timeout');
}
// With automatic retries
async function sendTransactionWithRetry(
  transaction,
  signers,
  maxRetries = 3
) {
  let lastError;

  for (let attempt = 0; attempt < maxRetries; attempt++) {
    try {
      // Get fresh blockhash for each attempt
      const { blockhash, lastValidBlockHeight } =
        await connection.getLatestBlockhash();

      transaction.recentBlockhash = blockhash;
      transaction.sign(...signers);

      const signature = await connection.sendRawTransaction(
        transaction.serialize(),
        {
          skipPreflight: false,
          maxRetries: 0 // We handle retries manually
        }
      );

      console.log(`Attempt ${attempt + 1}: Transaction sent ${signature}`);

      // Wait for confirmation
      await connection.confirmTransaction({
        signature,
        blockhash,
        lastValidBlockHeight
      });

      console.log('Transaction confirmed:', signature);
      return signature;

    } catch (error) {
      console.error(`Attempt ${attempt + 1} failed:`, error.message);
      lastError = error;

      if (attempt < maxRetries - 1) {
        const delay = Math.pow(2, attempt) * 1000; // Exponential backoff
        console.log(`Retrying in ${delay}ms...`);
        await new Promise(resolve => setTimeout(resolve, delay));
      }
    }
  }

  throw new Error(`Transaction failed after ${maxRetries} attempts: ${lastError.message}`);
}

Error Handling

Common Transaction Errors

// Error: Insufficient funds
async function sendWithBalanceCheck(fromKeypair, toAddress, amountSOL) {
  const balance = await connection.getBalance(fromKeypair.publicKey);
  const requiredLamports = amountSOL * LAMPORTS_PER_SOL;
  const estimatedFee = 5000; // 5000 lamports typical fee

  if (balance < requiredLamports + estimatedFee) {
    throw new Error(
      `Insufficient balance. Have: ${balance / LAMPORTS_PER_SOL} SOL, ` +
      `Need: ${(requiredLamports + estimatedFee) / LAMPORTS_PER_SOL} SOL`
    );
  }

  return sendTransaction(fromKeypair, toAddress, amountSOL);
}
// Error: Blockhash expired
async function sendWithFreshBlockhash(transaction, signers) {
  // Get blockhash immediately before sending
  const { blockhash, lastValidBlockHeight } =
    await connection.getLatestBlockhash('finalized');

  transaction.recentBlockhash = blockhash;
  transaction.sign(...signers);

  // Send immediately
  const signature = await connection.sendRawTransaction(
    transaction.serialize(),
    { maxRetries: 3 }
  );

  // Confirm with the same blockhash reference
  await connection.confirmTransaction({
    signature,
    blockhash,
    lastValidBlockHeight
  });

  return signature;
}
// Error: Simulation failed
async function handleSimulationError(transaction) {
  try {
    const simulation = await connection.simulateTransaction(transaction);

    if (simulation.value.err) {
      console.error('Simulation failed with error:', simulation.value.err);
      console.error('Program logs:');
      simulation.value.logs?.forEach(log => console.error('  ', log));

      // Parse common errors
      if (JSON.stringify(simulation.value.err).includes('InsufficientFundsForFee')) {
        throw new Error('Insufficient funds to pay transaction fee');
      }

      if (JSON.stringify(simulation.value.err).includes('InvalidAccountData')) {
        throw new Error('Invalid account data - check account initialization');
      }

      throw new Error(`Simulation failed: ${JSON.stringify(simulation.value.err)}`);
    }

    return simulation;
  } catch (error) {
    console.error('Simulation error:', error);
    throw error;
  }
}
// Error: Network/RPC errors
async function sendWithNetworkRetry(transaction, signers) {
  const maxAttempts = 3;

  for (let attempt = 0; attempt < maxAttempts; attempt++) {
    try {
      return await sendTransactionWithRetry(transaction, signers, 1);
    } catch (error) {
      // Check if it's a network error
      if (error.message.includes('fetch') ||
          error.message.includes('network') ||
          error.message.includes('timeout')) {

        console.error(`Network error on attempt ${attempt + 1}:`, error.message);

        if (attempt < maxAttempts - 1) {
          await new Promise(resolve => setTimeout(resolve, 2000));
          continue;
        }
      }

      throw error;
    }
  }
}

Best Practices

1. Always Simulate First (Development)

async function safeSendTransaction(transaction, signers) {
  // Set fee payer
  transaction.feePayer = signers[0].publicKey;

  // Simulate first
  console.log('Simulating transaction...');
  const simulation = await connection.simulateTransaction(transaction);

  if (simulation.value.err) {
    throw new Error(`Simulation failed: ${JSON.stringify(simulation.value.err)}`);
  }

  console.log('Simulation passed. Sending transaction...');

  // Get fresh blockhash
  const { blockhash, lastValidBlockHeight } =
    await connection.getLatestBlockhash();

  transaction.recentBlockhash = blockhash;

  // Sign
  transaction.sign(...signers);

  // Send
  const signature = await connection.sendRawTransaction(
    transaction.serialize()
  );

  // Confirm
  await connection.confirmTransaction({
    signature,
    blockhash,
    lastValidBlockHeight
  });

  return signature;
}

2. Implement Proper Retry Logic

class TransactionSender {
  constructor(connection, options = {}) {
    this.connection = connection;
    this.maxRetries = options.maxRetries || 3;
    this.retryDelay = options.retryDelay || 1000;
    this.skipPreflight = options.skipPreflight || false;
  }

  async send(transaction, signers) {
    let lastError;

    for (let attempt = 0; attempt < this.maxRetries; attempt++) {
      try {
        // Get fresh blockhash
        const { blockhash, lastValidBlockHeight } =
          await this.connection.getLatestBlockhash();

        transaction.recentBlockhash = blockhash;
        transaction.sign(...signers);

        // Send with preflight checks
        const signature = await this.connection.sendRawTransaction(
          transaction.serialize(),
          {
            skipPreflight: this.skipPreflight,
            preflightCommitment: 'confirmed',
            maxRetries: 0
          }
        );

        console.log(`Sent transaction: ${signature}`);

        // Confirm
        const confirmation = await this.connection.confirmTransaction({
          signature,
          blockhash,
          lastValidBlockHeight
        });

        if (confirmation.value.err) {
          throw new Error(`Transaction failed: ${JSON.stringify(confirmation.value.err)}`);
        }

        return signature;

      } catch (error) {
        lastError = error;
        console.error(`Attempt ${attempt + 1} failed:`, error.message);

        if (attempt < this.maxRetries - 1) {
          const delay = this.retryDelay * Math.pow(2, attempt);
          console.log(`Retrying in ${delay}ms...`);
          await new Promise(resolve => setTimeout(resolve, delay));
        }
      }
    }

    throw new Error(
      `Transaction failed after ${this.maxRetries} attempts: ${lastError.message}`
    );
  }
}

// Usage
const sender = new TransactionSender(connection, {
  maxRetries: 3,
  retryDelay: 1000,
  skipPreflight: false
});

const signature = await sender.send(transaction, [signer]);

3. Monitor Transaction Status

async function monitorTransaction(signature) {
  const maxChecks = 60; // Check for up to 60 seconds
  let checks = 0;

  console.log(`Monitoring transaction: ${signature}`);

  while (checks < maxChecks) {
    const status = await connection.getSignatureStatus(signature);

    if (status.value === null) {
      console.log(`Check ${checks + 1}: Not found yet`);
    } else {
      const confirmation = status.value.confirmationStatus;
      const hasError = status.value.err !== null;

      console.log(`Check ${checks + 1}: ${confirmation}${hasError ? ' (ERROR)' : ''}`);

      if (confirmation === 'finalized') {
        if (hasError) {
          throw new Error(`Transaction failed: ${JSON.stringify(status.value.err)}`);
        }
        console.log('Transaction finalized successfully');
        return status;
      }
    }

    checks++;
    await new Promise(resolve => setTimeout(resolve, 1000));
  }

  throw new Error('Transaction monitoring timeout');
}

4. Use Transaction Receipts

async function getTransactionReceipt(signature) {
  // Wait for transaction to be finalized
  await connection.confirmTransaction(signature, 'finalized');

  // Get full transaction details
  const transaction = await connection.getTransaction(signature, {
    maxSupportedTransactionVersion: 0
  });

  if (!transaction) {
    throw new Error('Transaction not found');
  }

  // Extract receipt information
  const receipt = {
    signature,
    slot: transaction.slot,
    blockTime: new Date(transaction.blockTime * 1000),
    fee: transaction.meta.fee / LAMPORTS_PER_SOL,
    success: transaction.meta.err === null,
    preBalances: transaction.meta.preBalances.map(b => b / LAMPORTS_PER_SOL),
    postBalances: transaction.meta.postBalances.map(b => b / LAMPORTS_PER_SOL),
    logs: transaction.meta.logMessages
  };

  console.log('Transaction Receipt:', JSON.stringify(receipt, null, 2));

  return receipt;
}

Real-World Use Cases

Payment System with Confirmation

class PaymentProcessor {
  constructor(connection) {
    this.connection = connection;
  }

  async processPayment(senderKeypair, recipientAddress, amountSOL) {
    const recipient = new PublicKey(recipientAddress);

    // 1. Validate balance
    const balance = await this.connection.getBalance(senderKeypair.publicKey);
    const requiredLamports = amountSOL * LAMPORTS_PER_SOL + 5000; // + fee

    if (balance < requiredLamports) {
      throw new Error('Insufficient balance');
    }

    // 2. Create transaction
    const transaction = new Transaction().add(
      SystemProgram.transfer({
        fromPubkey: senderKeypair.publicKey,
        toPubkey: recipient,
        lamports: amountSOL * LAMPORTS_PER_SOL
      })
    );

    // 3. Simulate
    transaction.feePayer = senderKeypair.publicKey;
    const { blockhash } = await this.connection.getLatestBlockhash();
    transaction.recentBlockhash = blockhash;

    const simulation = await this.connection.simulateTransaction(transaction);
    if (simulation.value.err) {
      throw new Error(`Simulation failed: ${JSON.stringify(simulation.value.err)}`);
    }

    // 4. Send with retry
    const sender = new TransactionSender(this.connection, { maxRetries: 3 });
    const signature = await sender.send(transaction, [senderKeypair]);

    // 5. Get receipt
    const receipt = await getTransactionReceipt(signature);

    // 6. Return payment confirmation
    return {
      success: true,
      signature,
      amount: amountSOL,
      fee: receipt.fee,
      timestamp: receipt.blockTime,
      recipient: recipientAddress
    };
  }
}

// Usage
const processor = new PaymentProcessor(connection);
const result = await processor.processPayment(
  senderKeypair,
  'vines1vzrYbzLMRdu58ou5XTby4qAqVRLmqo36NKPTg',
  0.1
);
console.log('Payment successful:', result);

Batch Transaction Processor

async function processBatchPayments(senderKeypair, payments) {
  // payments: [{ recipient: string, amount: number }, ...]

  const results = [];

  for (const payment of payments) {
    try {
      console.log(`Processing payment to ${payment.recipient}...`);

      const signature = await sendTransaction(
        senderKeypair,
        payment.recipient,
        payment.amount
      );

      results.push({
        recipient: payment.recipient,
        amount: payment.amount,
        signature,
        success: true
      });

      // Rate limiting
      await new Promise(resolve => setTimeout(resolve, 500));

    } catch (error) {
      console.error(`Failed to send to ${payment.recipient}:`, error.message);

      results.push({
        recipient: payment.recipient,
        amount: payment.amount,
        error: error.message,
        success: false
      });
    }
  }

  // Summary
  const successful = results.filter(r => r.success).length;
  const failed = results.filter(r => !r.success).length;

  console.log(`\nBatch complete: ${successful} successful, ${failed} failed`);

  return results;
}

Performance Optimization

Use Priority Fees (When Available)

async function sendWithPriorityFee(transaction, signers, priorityFee) {
  // Add compute budget instruction for priority fee
  const modifyComputeUnits = ComputeBudgetProgram.setComputeUnitLimit({
    units: 200000
  });

  const addPriorityFee = ComputeBudgetProgram.setComputeUnitPrice({
    microLamports: priorityFee
  });

  // Add priority instructions at the beginning
  transaction.instructions = [
    modifyComputeUnits,
    addPriorityFee,
    ...transaction.instructions
  ];

  return sendTransaction(transaction, signers);
}

Optimize Compute Units

async function optimizeComputeUnits(transaction) {
  // Simulate to get actual compute units used
  const simulation = await connection.simulateTransaction(transaction);

  if (simulation.value.err) {
    throw new Error('Simulation failed');
  }

  const unitsUsed = simulation.value.unitsConsumed;
  console.log(`Compute units used: ${unitsUsed}`);

  // Add 10% buffer
  const unitsToRequest = Math.ceil(unitsUsed * 1.1);

  // Add compute budget instruction
  transaction.instructions.unshift(
    ComputeBudgetProgram.setComputeUnitLimit({
      units: unitsToRequest
    })
  );

  return transaction;
}

Additional Resources