Skip to main content

Overview

Transaction monitoring lets you watch Solana transactions as they happen. Track swaps, transfers, success/failures, program calls, and token changes - all in real-time.
You’ll need: Run npm install @triton-one/yellowstone-grpc first

Installation

npm install @triton-one/yellowstone-grpc

Complete Working Example

Here’s a ready-to-use transaction monitor that you can customize:
const Client = require("@triton-one/yellowstone-grpc").default;
const { CommitmentLevel } = require("@triton-one/yellowstone-grpc");

// Example program IDs - replace with programs you want to monitor
const TOKEN_PROGRAM = "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA";
const SYSTEM_PROGRAM = "11111111111111111111111111111111";

// Connect to Yellowstone gRPC
const getClient = async () => {
  let client = false;
  try {
    client = new Client(
      process.env.GRPC_ENDPOINT || "https://grpc.solanatracker.io",
      process.env.GRPC_API_KEY,
      {
        "grpc.max_receive_message_length": 100 * 1024 * 1024,
      }
    );
    const version = await client.getVersion();
    if (version) {
      console.log("Connected to Yellowstone gRPC! Version:", version);
      return client;
    }
  } catch (e) {
    console.error("Failed to connect:", e);
  }
  if (!client) {
    throw new Error("Failed to connect!");
  }
};

// Transaction statistics
const txStats = {
  totalTransactions: 0,
  successfulTx: 0,
  failedTx: 0,
  totalFees: 0,
  totalComputeUnits: 0,
  programCounts: new Map(),
  lastReportTime: Date.now()
};

// Calculate SOL balance changes
function calculateSolChanges(meta) {
  const changes = [];
  
  if (!meta.preBalances || !meta.postBalances) return changes;
  
  meta.preBalances.forEach((preBalance, index) => {
    const postBalance = meta.postBalances[index] || 0;
    const change = (postBalance - preBalance) / 1e9; // Convert lamports to SOL
    
    if (Math.abs(change) > 0.000001) { // Ignore tiny changes
      changes.push({ 
        accountIndex: index,
        change: change
      });
    }
  });
  
  return changes;
}

// Calculate token balance changes
function calculateTokenChanges(meta) {
  const changes = [];
  
  if (!meta.preTokenBalances || !meta.postTokenBalances) return changes;
  
  meta.preTokenBalances.forEach((preBalance, index) => {
    const postBalance = meta.postTokenBalances[index];
    if (preBalance && postBalance && preBalance.mint === postBalance.mint) {
      const preAmount = preBalance.uiTokenAmount?.uiAmount || 0;
      const postAmount = postBalance.uiTokenAmount?.uiAmount || 0;
      const change = postAmount - preAmount;
      
      if (change !== 0) {
        changes.push({ 
          mint: preBalance.mint, 
          change,
          decimals: preBalance.uiTokenAmount?.decimals || 0
        });
      }
    }
  });
  
  return changes;
}

(async () => {
  const client = await getClient();
  const stream = await client.subscribe();

  // Handle stream lifecycle
  const streamClosed = new Promise((resolve, reject) => {
    stream.on("error", (error) => {
      console.error("Stream error:", error);
    });
    stream.on("end", () => {
      console.log("Stream ended");
      resolve();
    });
    stream.on("close", () => {
      console.log("Stream closed");
      resolve();
    });
  });

  // Handle incoming transactions
  stream.on("data", (data) => {
    if (data?.transaction?.transaction) {
      const tx = data.transaction.transaction;
      const slot = data.transaction.slot;
      
      txStats.totalTransactions++;
      
      const success = !tx.meta?.err;
      if (success) {
        txStats.successfulTx++;
      } else {
        txStats.failedTx++;
      }
      
      const fee = tx.meta?.fee || 0;
      const computeUnits = tx.meta?.computeUnitsConsumed || 0;
      
      txStats.totalFees += fee;
      txStats.totalComputeUnits += computeUnits;
      
      // Track which programs were used
      const accountKeys = tx.transaction?.message?.accountKeys || [];
      const instructions = tx.transaction?.message?.instructions || [];
      
      instructions.forEach((ix) => {
        const programId = accountKeys[ix.programIdIndex];
        if (programId) {
          const count = txStats.programCounts.get(programId) || 0;
          txStats.programCounts.set(programId, count + 1);
        }
      });
      
      console.log(`\n[Transaction #${txStats.totalTransactions}]`);
      console.log(`  Signature: ${tx.signature}`);
      console.log(`  Slot: ${slot}`);
      console.log(`  Status: ${success ? '✅ SUCCESS' : '❌ FAILED'}`);
      console.log(`  Fee: ${(fee / 1e9).toFixed(6)} SOL`);
      console.log(`  Compute Units: ${computeUnits.toLocaleString()}`);
      console.log(`  Instructions: ${instructions.length}`);
      
      // Show SOL balance changes
      const solChanges = calculateSolChanges(tx.meta);
      if (solChanges.length > 0) {
        console.log(`  SOL Changes:`);
        solChanges.forEach(change => {
          console.log(`Account ${change.accountIndex}: ${change.change > 0 ? '+' : ''}${change.change.toFixed(6)} SOL`);
        });
      }
      
      // Show token balance changes
      const tokenChanges = calculateTokenChanges(tx.meta);
      if (tokenChanges.length > 0) {
        console.log(`  Token Changes:`);
        tokenChanges.forEach(change => {
          console.log(`${change.mint.slice(0, 8)}... ${change.change > 0 ? '+' : ''}${change.change.toFixed(6)}`);
        });
      }
      
      // Show program IDs used
      if (accountKeys.length > 0) {
        const uniquePrograms = new Set();
        instructions.forEach((ix) => {
          const programId = accountKeys[ix.programIdIndex];
          if (programId) uniquePrograms.add(programId);
        });
        
        if (uniquePrograms.size > 0) {
          console.log(`  Programs Called:`);
          uniquePrograms.forEach(program => {
            const name = program === TOKEN_PROGRAM ? "Token Program" 
              : program === SYSTEM_PROGRAM ? "System Program"
              : program.slice(0, 12) + "...";
            console.log(`    - ${name}`);
          });
        }
      }
      
      // Show error details for failed transactions
      if (!success && tx.meta?.err) {
        console.log(`  Error: ${JSON.stringify(tx.meta.err)}`);
        if (tx.meta?.logMessages && tx.meta.logMessages.length > 0) {
          console.log(`  Last Log: ${tx.meta.logMessages[tx.meta.logMessages.length - 1]}`);
        }
      }
      
      // Report statistics every 5 minutes
      const now = Date.now();
      if (now - txStats.lastReportTime > 300000) {
        const avgFee = txStats.totalTransactions > 0 
          ? txStats.totalFees / txStats.totalTransactions / 1e9
          : 0;
        const successRate = txStats.totalTransactions > 0
          ? (txStats.successfulTx / txStats.totalTransactions * 100)
          : 0;
          
        console.log(`\n=== Transaction Statistics Report ===`);
        console.log(`  Total Transactions: ${txStats.totalTransactions}`);
        console.log(`  Successful: ${txStats.successfulTx}`);
        console.log(`  Failed: ${txStats.failedTx}`);
        console.log(`  Success Rate: ${successRate.toFixed(2)}%`);
        console.log(`  Total Fees: ${(txStats.totalFees / 1e9).toFixed(4)} SOL`);
        console.log(`  Average Fee: ${avgFee.toFixed(6)} SOL`);
        console.log(`  Avg Compute Units: ${(txStats.totalComputeUnits / txStats.totalTransactions).toFixed(0)}`);
        
        console.log(`\n  Most Used Programs:`);
        Array.from(txStats.programCounts.entries())
          .sort((a, b) => b[1] - a[1])
          .slice(0, 5)
          .forEach(([program, count]) => {
            const name = program === TOKEN_PROGRAM ? "Token Program" 
              : program === SYSTEM_PROGRAM ? "System Program"
              : program.slice(0, 12) + "...";
            console.log(`    ${name}: ${count} transactions`);
          });
        
        txStats.lastReportTime = now;
      }
    }
  });

  // Subscribe to transactions
  // Customize the accountInclude array with programs you want to monitor
  const request = {
    accounts: {},
    slots: {},
    transactions: {
      allTransactions: {
        vote: false,
        failed: false,
        signature: undefined,
        accountInclude: [TOKEN_PROGRAM, SYSTEM_PROGRAM], // Add your program IDs here
        accountExclude: [],
        accountRequired: [],
      },
    },
    transactionsStatus: {},
    entry: {},
    blocks: {},
    blocksMeta: {},
    accountsDataSlice: [],
    ping: undefined,
    commitment: CommitmentLevel.CONFIRMED,
  };

  // Send subscribe request
  await new Promise((resolve, reject) => {
    stream.write(request, (err) => {
      if (err === null || err === undefined) {
        console.log("✅ Monitoring transactions\n");
        resolve();
      } else {
        reject(err);
      }
    });
  }).catch((reason) => {
    console.error("Subscribe failed:", reason);
    throw reason;
  });

  await streamClosed;
})();

// Graceful shutdown
process.on('SIGINT', () => {
  console.log('\n\nShutting down gracefully...');
  console.log(`Final Stats - Total: ${txStats.totalTransactions}, Success: ${txStats.successfulTx}, Failed: ${txStats.failedTx}`);
  process.exit(0);
});

How to Filter Transactions

  • By Program
  • By Account
  • Advanced
Watch transactions using specific programs:
    const request = {
      // ... other fields
      transactions: {
        myTransactions: {
          vote: false,
          failed: false,
          accountInclude: [
            "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA",
            "11111111111111111111111111111111"
          ],
          accountExclude: [],
          accountRequired: [],
        }
      },
      commitment: CommitmentLevel.CONFIRMED,
    };

More Examples

Example 2: Track Failed Transactions

Find out why transactions fail:
const failureStats = {
  total: 0,
  errorTypes: new Map()
};

(async () => {
  const client = await getClient();
  const stream = await client.subscribe();

  const streamClosed = new Promise((resolve, reject) => {
    stream.on("error", (error) => console.error("Stream error:", error));
    stream.on("end", () => resolve());
    stream.on("close", () => resolve());
  });

  stream.on("data", (data) => {
    if (data?.transaction?.transaction?.meta?.err) {
      const tx = data.transaction.transaction;
      const slot = data.transaction.slot;
      
      failureStats.total++;
      
      const errorType = JSON.stringify(tx.meta.err);
      const count = failureStats.errorTypes.get(errorType) || 0;
      failureStats.errorTypes.set(errorType, count + 1);
      
      console.log(`\n[Failed Transaction #${failureStats.total}]`);
      console.log(`  Signature: ${tx.signature}`);
      console.log(`  Slot: ${slot}`);
      console.log(`  Error: ${errorType}`);
      console.log(`  Fee Paid: ${(tx.meta.fee / 1e9).toFixed(6)} SOL`);
      
      // Log messages for debugging
      if (tx.meta?.logMessages) {
        console.log(`  Last 5 Log Messages:`);
        tx.meta.logMessages.slice(-5).forEach((msg) => {
          console.log(`    ${msg}`);
        });
      }
      
      // Report failure patterns every 20 failures
      if (failureStats.total % 20 === 0) {
        console.log(`\n=== Failure Analysis ===`);
        console.log(`  Total Failures: ${failureStats.total}`);
        console.log(`\n  Top Error Types:`);
        Array.from(failureStats.errorTypes.entries())
          .sort((a, b) => b[1] - a[1])
          .slice(0, 5)
          .forEach(([error, count]) => {
            const percentage = (count / failureStats.total * 100).toFixed(1);
            console.log(`    ${error}: ${count} (${percentage}%)`);
          });
      }
    }
  });

  const request = {
    accounts: {},
    slots: {},
    transactions: {
      failedTransactions: {
        vote: false,
        failed: true, // Only failed transactions
        accountInclude: ["YourProgramId"],
        accountExclude: [],
        accountRequired: [],
      },
    },
    transactionsStatus: {},
    entry: {},
    blocks: {},
    blocksMeta: {},
    accountsDataSlice: [],
    ping: undefined,
    commitment: CommitmentLevel.CONFIRMED,
  };

  await new Promise((resolve, reject) => {
    stream.write(request, (err) => {
      if (err === null || err === undefined) {
        console.log("✅ Monitoring failed transactions\n");
        resolve();
      } else {
        reject(err);
      }
    });
  });

  await streamClosed;
})();

Example 3: Watch Big Transfers

Track large SOL transfers:
const HIGH_VALUE_THRESHOLD = 10; // 10 SOL

const highValueStats = {
  totalMonitored: 0,
  highValueCount: 0,
  totalVolume: 0,
  largestTransfer: 0,
  largestSignature: ""
};

(async () => {
  const client = await getClient();
  const stream = await client.subscribe();

  const streamClosed = new Promise((resolve, reject) => {
    stream.on("error", (error) => console.error("Stream error:", error));
    stream.on("end", () => resolve());
    stream.on("close", () => resolve());
  });

  stream.on("data", (data) => {
    if (data?.transaction?.transaction?.meta) {
      const tx = data.transaction.transaction;
      const slot = data.transaction.slot;
      
      highValueStats.totalMonitored++;
      
      const preBalances = tx.meta.preBalances || [];
      const postBalances = tx.meta.postBalances || [];
      
      // Calculate largest balance change
      let maxChange = 0;
      let changedAccountIndex = -1;
      
      preBalances.forEach((preBalance, index) => {
        const postBalance = postBalances[index] || 0;
        const change = Math.abs(postBalance - preBalance);
        if (change > maxChange) {
          maxChange = change;
          changedAccountIndex = index;
        }
      });
      
      const changeInSOL = maxChange / 1e9;
      
      if (changeInSOL > HIGH_VALUE_THRESHOLD) {
        highValueStats.highValueCount++;
        highValueStats.totalVolume += changeInSOL;
        
        if (changeInSOL > highValueStats.largestTransfer) {
          highValueStats.largestTransfer = changeInSOL;
          highValueStats.largestSignature = tx.signature;
        }
        
        const accountKeys = tx.transaction?.message?.accountKeys || [];
        const changedAccount = accountKeys[changedAccountIndex];
        
        console.log(`\n[High-Value Transaction #${highValueStats.highValueCount}]`);
        console.log(`  Signature: ${tx.signature}`);
        console.log(`  Slot: ${slot}`);
        console.log(`  💰 Transfer Amount: ${changeInSOL.toFixed(2)} SOL`);
        console.log(`  Fee: ${(tx.meta.fee / 1e9).toFixed(6)} SOL`);
        console.log(`  Changed Account: ${changedAccount || 'Unknown'}`);
        
        // Report summary every 10 high-value transactions
        if (highValueStats.highValueCount % 10 === 0) {
          console.log(`\n=== High-Value Summary ===`);
          console.log(`  Monitored: ${highValueStats.totalMonitored} transactions`);
          console.log(`  High-Value: ${highValueStats.highValueCount} transactions`);
          console.log(`  Total Volume: ${highValueStats.totalVolume.toFixed(2)} SOL`);
          console.log(`  Largest Transfer: ${highValueStats.largestTransfer.toFixed(2)} SOL`);
        }
      }
      
      // Log progress every 100 monitored transactions
      if (highValueStats.totalMonitored % 100 === 0) {
        console.log(`\n[Progress] Monitored ${highValueStats.totalMonitored} transactions, found ${highValueStats.highValueCount} high-value`);
      }
    }
  });

  const request = {
    accounts: {},
    slots: {},
    transactions: {
      systemTransactions: {
        vote: false,
        failed: false,
        accountInclude: ["11111111111111111111111111111111"], // System Program
        accountExclude: [],
        accountRequired: [],
      },
    },
    transactionsStatus: {},
    entry: {},
    blocks: {},
    blocksMeta: {},
    accountsDataSlice: [],
    ping: undefined,
    commitment: CommitmentLevel.CONFIRMED,
  };

  await new Promise((resolve, reject) => {
    stream.write(request, (err) => {
      if (err === null || err === undefined) {
        console.log(`✅ Monitoring transactions with transfers > ${HIGH_VALUE_THRESHOLD} SOL\n`);
        resolve();
      } else {
        reject(err);
      }
    });
  });

  await streamClosed;
})();

Understanding Filters

Include (OR)

accountInclude: Transaction must touch ANY of these accounts

Required (AND)

accountRequired: Transaction must touch ALL of these accounts

Exclude (NOT)

accountExclude: Transaction must NOT touch any of these

How They Work Together

Transaction passes if: (include OR empty) AND (all required) AND NOT (any excluded)

What You Can Build

Monitor DEX transactions to find trading opportunities, track competitors, or analyze market activity in real-time.
Watch specific wallets or programs to see every transaction they make. Perfect for portfolio tracking or whale watching.
Track failed transactions to debug your program, understand user issues, or monitor network problems.
Measure transaction volume, fees, and activity across programs to understand usage patterns and trends.
Get instant notifications for high-value transfers, specific program calls, or suspicious activity.

Tips for Better Performance

Pick Right Commitment

CONFIRMED is usually best - fast but reliable

Filter Smart

Use specific program IDs to reduce data

Process Async

Don’t block - handle transactions asynchronously

Watch Resources

Monitor your memory and CPU usage

Common Questions

It depends on your filters. Watching all transactions = huge data. Watching one program = manageable. Watching a specific wallet = very little. Always use filters!
Yes! Just add them all to accountInclude. The transaction will match if it touches ANY of them.
  • Include (OR): Transaction touches ANY of these accounts
  • Required (AND): Transaction touches ALL of these accounts
Use Include for “I want to see transactions involving A or B or C” Use Required for “I only want transactions that involve both A and B”
Usually no - they’re just noise. But set failed: true if you’re debugging or analyzing failure patterns.
Add the wallet address to accountInclude. You’ll see every transaction that touches that wallet.
Set vote: false in your filter. Vote transactions are validator consensus votes and usually aren’t what you want.
Compute units measure how much computation a transaction used. More complex transactions use more compute units and usually cost more.

Next Steps

I