Overview

Transaction monitoring enables you to track transaction execution, success/failure status, program interactions, and token balance changes across Solana in real-time. This guide covers filtering strategies and practical implementations for different transaction monitoring use cases.
Prerequisites: This guide assumes you’ve completed the Yellowstone gRPC Quickstart and have a working stream setup.

Transaction Filtering Options

Monitor transactions involving specific programsTrack all transactions that interact with your programs:
const subscribeRequest: SubscribeRequest = {
  transactions: {
    client: {
      accountInclude: [
        "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA", // Token Program
        "11111111111111111111111111111111",              // System Program
        "675kPX9MHTjS2zt1qfr1NYHuzeLXfQM9H24wFSUt1Mp8"  // Your program
      ],
      accountExclude: [],
      accountRequired: [],
      vote: false,
      failed: false
    }
  },
  commitment: CommitmentLevel.CONFIRMED
};
Best for: Program-specific monitoring, DeFi protocol tracking, smart contract interactions

Practical Examples

Example 1: Monitor DEX Transactions

Track all transactions involving popular DEX programs:
import { StreamManager } from './stream-manager'; // From quickstart guide

async function monitorDEXTransactions() {
  const streamManager = new StreamManager(
    "your-grpc-endpoint",
    "your-api-key",
    handleDEXTransaction
  );

  const subscribeRequest: SubscribeRequest = {
    transactions: {
      client: {
        accountInclude: [
          "9WzDXwBbmkg8ZTbNMqUxvQRAyrZzDsGYdLVL9zYtAWWM", // Raydium V4
          "675kPX9MHTjS2zt1qfr1NYHuzeLXfQM9H24wFSUt1Mp8", // Raydium V5
          "CAMMCzo5YL8w4VFF8KVHrK22GGUQpMpTFb6xRmpLFGNnSm", // Raydium CLMM
          "JUP6LkbZbjS1jKKwapdHNy74zcZ3tLUZoi5QNyVTaV4"   // Jupiter
        ],
        vote: false,
        failed: false
      }
    },
    commitment: CommitmentLevel.CONFIRMED
  };

  await streamManager.connect(subscribeRequest);
}

function handleDEXTransaction(data: any): void {
  if (data.transaction?.transaction) {
    const tx = data.transaction.transaction;
    console.log(`\n🔄 DEX Transaction:`);
    console.log(`  Signature: ${tx.signature}`);
    console.log(`  Slot: ${data.transaction.slot}`);
    console.log(`  Status: ${tx.meta?.err ? 'Failed' : 'Success'}`);
    console.log(`  Fee: ${tx.meta?.fee || 0} lamports`);
    console.log(`  Compute Units: ${tx.meta?.computeUnitsConsumed || 0}`);
    
    // Show token balance changes
    if (tx.meta?.preTokenBalances?.length > 0) {
      console.log(`  Token Balance Changes:`);
      tx.meta.preTokenBalances.forEach((preBalance: any, index: number) => {
        const postBalance = tx.meta.postTokenBalances[index];
        if (preBalance && postBalance) {
          const change = postBalance.uiTokenAmount.uiAmount - preBalance.uiTokenAmount.uiAmount;
          if (change !== 0) {
            console.log(`    ${preBalance.mint}: ${change > 0 ? '+' : ''}${change}`);
          }
        }
      });
    }
  }
}

Example 2: Monitor Failed Transactions

Track failed transactions to identify issues:
async function monitorFailedTransactions() {
  const streamManager = new StreamManager(
    "your-grpc-endpoint",
    "your-api-key",
    handleFailedTransaction
  );

  const subscribeRequest: SubscribeRequest = {
    transactions: {
      client: {
        accountInclude: ["YourProgramId"], // Your program
        vote: false,
        failed: true // Only failed transactions
      }
    },
    commitment: CommitmentLevel.CONFIRMED
  };

  await streamManager.connect(subscribeRequest);
}

function handleFailedTransaction(data: any): void {
  if (data.transaction?.transaction?.meta?.err) {
    const tx = data.transaction.transaction;
    console.log(`\n❌ Failed Transaction:`);
    console.log(`  Signature: ${tx.signature}`);
    console.log(`  Slot: ${data.transaction.slot}`);
    console.log(`  Error: ${JSON.stringify(tx.meta.err)}`);
    console.log(`  Fee: ${tx.meta.fee} lamports`);
    console.log(`  Compute Units: ${tx.meta.computeUnitsConsumed || 0}`);
    
    // Log instruction details for debugging
    if (tx.transaction?.message?.instructions) {
      console.log(`  Instructions:`);
      tx.transaction.message.instructions.forEach((inst: any, i: number) => {
        console.log(`    ${i}: Program ${inst.programIdIndex}, Data: ${inst.data}`);
      });
    }
  }
}

Example 3: Monitor High-Value Transactions

Track transactions with significant SOL transfers:
async function monitorHighValueTransactions() {
  const streamManager = new StreamManager(
    "your-grpc-endpoint",
    "your-api-key",
    handleHighValueTransaction
  );

  const subscribeRequest: SubscribeRequest = {
    transactions: {
      client: {
        accountInclude: ["11111111111111111111111111111111"], // System Program
        vote: false,
        failed: false
      }
    },
    commitment: CommitmentLevel.CONFIRMED
  };

  await streamManager.connect(subscribeRequest);
}

function handleHighValueTransaction(data: any): void {
  if (data.transaction?.transaction?.meta) {
    const tx = data.transaction.transaction;
    const preBalances = tx.meta.preBalances || [];
    const postBalances = tx.meta.postBalances || [];
    
    // Calculate largest balance change
    let maxChange = 0;
    preBalances.forEach((preBalance: number, index: number) => {
      const postBalance = postBalances[index] || 0;
      const change = Math.abs(postBalance - preBalance);
      maxChange = Math.max(maxChange, change);
    });
    
    // Only log transactions with > 10 SOL moved
    const changeInSOL = maxChange / 1e9;
    if (changeInSOL > 10) {
      console.log(`\n💰 High-Value Transaction:`);
      console.log(`  Signature: ${tx.signature}`);
      console.log(`  Slot: ${data.transaction.slot}`);
      console.log(`  Max SOL Transfer: ${changeInSOL.toFixed(2)} SOL`);
      console.log(`  Fee: ${tx.meta.fee / 1e9} SOL`);
      console.log(`  Accounts: ${tx.transaction?.message?.accountKeys?.length || 0}`);
    }
  }
}

Transaction Data Structure

Understanding the transaction data structure helps extract relevant information:
Core transaction data:
{
  signature: string;
  isVote: boolean;
  transaction: {
    message: {
      accountKeys: string[];        // All accounts involved
      instructions: Instruction[];  // Program instructions
      recentBlockhash: string;     // Recent blockhash used
    };
    signatures: string[];          // Transaction signatures
  };
  meta: {
    err: any;                      // Error details if failed
    fee: number;                   // Transaction fee in lamports
    computeUnitsConsumed: number;  // Compute units used
    preBalances: number[];         // Account balances before
    postBalances: number[];        // Account balances after
    preTokenBalances: TokenBalance[];
    postTokenBalances: TokenBalance[];
    logMessages: string[];         // Program log messages
  };
}
Token balance structure:
{
  accountIndex: number;
  mint: string;                   // Token mint address
  owner: string;                  // Account owner
  uiTokenAmount: {
    amount: string;               // Raw token amount
    decimals: number;             // Token decimals
    uiAmount: number;             // Human-readable amount
    uiAmountString: string;       // String representation
  };
}
Program instruction structure:
{
  programIdIndex: number;         // Index in accountKeys array
  accounts: number[];             // Account indices involved
  data: string;                   // Instruction data (base58)
}

Filter Logic Reference

Include Logic (OR)

accountInclude: Transaction must involve ANY of these accountsExample: ["A", "B"] matches transactions involving account A OR account B

Required Logic (AND)

accountRequired: Transaction must involve ALL of these accountsExample: ["A", "B"] matches transactions involving account A AND account B

Exclude Logic (NOT)

accountExclude: Transaction must NOT involve any of these accountsExample: ["A", "B"] excludes transactions involving account A or account B

Combined Logic

Final filter: (accountInclude OR empty) AND (accountRequired AND all) AND NOT (accountExclude OR any)

Performance Considerations

Transaction streams can be high-volume
  • Start with specific program filters
  • Use commitment levels appropriately
  • Monitor your processing capacity
  • Implement backpressure handling
// Rate limiting example
let transactionCount = 0;
const startTime = Date.now();

function handleTransaction(data: any): void {
  transactionCount++;
  
  if (transactionCount % 100 === 0) {
    const elapsed = (Date.now() - startTime) / 1000;
    const rate = transactionCount / elapsed;
    console.log(`Processing ${rate.toFixed(1)} tx/sec`);
  }
  
  // Your transaction processing logic
}

Error Handling

Common transaction monitoring errors and solutions:
Error: Overwhelming transaction volumeSolutions:
  • Add more specific filters (accountRequired, accountExclude)
  • Use higher commitment levels to reduce volume
  • Implement sampling or rate limiting
  • Process transactions asynchronously
Error: Expected transactions not appearingSolutions:
  • Verify program addresses are correct
  • Check if transactions actually occur
  • Try PROCESSED commitment for faster updates
  • Ensure filters aren’t too restrictive
Error: Cannot parse transaction dataSolutions:
  • Handle missing fields gracefully
  • Validate data structure before processing
  • Log problematic transactions for debugging
  • Use try-catch blocks around parsing logic

Next Steps