Skip to main content

Overview

Slot and block monitoring lets you watch Solana’s network in real-time. See when blocks are produced, track network health, and monitor transaction volume as it happens.
Start here first: Complete the Quickstart Guide to set up your stream manager.

What You Can Monitor

  • Slots
  • Full Blocks
  • Block Metadata
Watch the network heartbeatSlots are 400ms time windows. Watch them advance to see network health:
    const subscribeRequest: SubscribeRequest = {
      slots: {
        slotSubscribe: {
          filterByCommitment: false // Get all commitment levels
        }
      },
      commitment: CommitmentLevel.CONFIRMED
    };
What you’ll see:
  • Slot number
  • Parent slot
  • Status (processed, confirmed, or finalized)
Good for: Network health dashboards, timing analysis, detecting problems

Real Examples

Example 1: Basic Slot Monitor

Watch slots and track network performance:
import { StreamManager } from './yellowstone';
import { CommitmentLevel, SubscribeRequest } from "@triton-one/yellowstone-grpc";

async function monitorSlots() {
  const streamManager = new StreamManager(
    "https://grpc.solanatracker.io",
    process.env.GRPC_API_TOKEN || "your-api-key",
    handleSlotUpdate
  );

  const subscribeRequest: SubscribeRequest = {
    slots: {
      slotSubscribe: {
        filterByCommitment: false
      }
    },
    commitment: CommitmentLevel.PROCESSED
  };

  console.log("Starting slot monitoring...");
  await streamManager.connect(subscribeRequest);
}

const slotStats = {
  totalSlots: 0,
  lastSlot: 0,
  lastTimestamp: Date.now(),
  slotTimes: [] as number[],
  skippedSlots: 0
};

function handleSlotUpdate(data: any): void {
  if (data.slot) {
    const slot = data.slot;
    const currentTime = Date.now();
    
    slotStats.totalSlots++;
    
    console.log(`\n[Slot #${slotStats.totalSlots}]`);
    console.log(`  Slot Number: ${slot.slot}`);
    console.log(`  Parent Slot: ${slot.parentSlot}`);
    console.log(`  Status: ${slot.status.toUpperCase()}`);
    
    // Calculate timing if we have a previous slot
    if (slotStats.lastSlot > 0) {
      const slotDiff = slot.slot - slotStats.lastSlot;
      const timeDiff = currentTime - slotStats.lastTimestamp;
      
      if (slotDiff === 1) {
        // Normal progression
        slotStats.slotTimes.push(timeDiff);
        
        // Keep last 100 slots
        if (slotStats.slotTimes.length > 100) {
          slotStats.slotTimes.shift();
        }
        
        const avgSlotTime = slotStats.slotTimes.reduce((a, b) => a + b, 0) / slotStats.slotTimes.length;
        
        console.log(`  Time Since Last: ${timeDiff}ms`);
        console.log(`  Average (100 slots): ${avgSlotTime.toFixed(1)}ms`);
        
        // Alert on slow slots
        if (timeDiff > 800) {
          console.log(`  ⚠️  SLOW SLOT: Expected ~400ms, got ${timeDiff}ms`);
        }
      } else if (slotDiff > 1) {
        // Skipped slots
        const skipped = slotDiff - 1;
        slotStats.skippedSlots += skipped;
        console.log(`  ⚠️  SKIPPED ${skipped} SLOT(S)`);
        console.log(`  Total Skipped: ${slotStats.skippedSlots}`);
      }
    }
    
    slotStats.lastSlot = slot.slot;
    slotStats.lastTimestamp = currentTime;
    
    // Summary every 100 slots
    if (slotStats.totalSlots % 100 === 0) {
      printSlotSummary();
    }
  }
}

function printSlotSummary(): void {
  const avgSlotTime = slotStats.slotTimes.length > 0
    ? slotStats.slotTimes.reduce((a, b) => a + b, 0) / slotStats.slotTimes.length
    : 0;
  const skipRate = (slotStats.skippedSlots / slotStats.totalSlots * 100).toFixed(2);
  
  console.log(`\n=== Slot Summary ===`);
  console.log(`  Total Slots: ${slotStats.totalSlots}`);
  console.log(`  Skipped: ${slotStats.skippedSlots} (${skipRate}%)`);
  console.log(`  Average Slot Time: ${avgSlotTime.toFixed(1)}ms`);
  console.log(`  Expected: ~400ms per slot`);
}

monitorSlots().catch(console.error);

Example 2: Block Metadata Monitor

Track blocks and transaction volume:
async function monitorBlocks() {
  const streamManager = new StreamManager(
    "https://grpc.solanatracker.io",
    process.env.GRPC_API_TOKEN || "your-api-key",
    handleBlockMeta
  );

  const subscribeRequest: SubscribeRequest = {
    blocksMeta: {
      blockMetaSubscribe: {}
    },
    commitment: CommitmentLevel.CONFIRMED
  };

  console.log("Monitoring blocks...");
  await streamManager.connect(subscribeRequest);
}

const blockStats = {
  totalBlocks: 0,
  totalTransactions: 0,
  totalRewards: 0,
  highActivityBlocks: 0,
  blockTimes: [] as number[],
  lastBlockTime: 0
};

function handleBlockMeta(data: any): void {
  if (data.blockMeta) {
    const meta = data.blockMeta;
    blockStats.totalBlocks++;
    blockStats.totalTransactions += meta.transactionCount || 0;
    
    console.log(`\n[Block #${blockStats.totalBlocks}]`);
    console.log(`  Slot: ${meta.slot}`);
    console.log(`  Block Height: ${meta.blockHeight}`);
    console.log(`  Block Hash: ${meta.blockhash?.slice(0, 16)}...`);
    console.log(`  Transactions: ${meta.transactionCount || 0}`);
    
    // Track block timing
    if (meta.blockTime) {
      const blockTime = new Date(meta.blockTime * 1000);
      console.log(`  Block Time: ${blockTime.toISOString()}`);
      
      if (blockStats.lastBlockTime > 0) {
        const timeDiff = meta.blockTime - blockStats.lastBlockTime;
        blockStats.blockTimes.push(timeDiff);
        
        if (blockStats.blockTimes.length > 50) {
          blockStats.blockTimes.shift();
        }
        
        console.log(`  Time Since Last Block: ${timeDiff}s`);
      }
      
      blockStats.lastBlockTime = meta.blockTime;
    }
    
    // Track rewards
    if (meta.rewards && meta.rewards.length > 0) {
      const totalReward = meta.rewards.reduce((sum: number, r: any) => sum + (r.lamports || 0), 0);
      blockStats.totalRewards += totalReward;
      
      console.log(`  Block Rewards: ${(totalReward / 1e9).toFixed(6)} SOL`);
      console.log(`  Reward Count: ${meta.rewards.length}`);
      
      // Show top rewards
      const sortedRewards = [...meta.rewards]
        .sort((a: any, b: any) => (b.lamports || 0) - (a.lamports || 0))
        .slice(0, 3);
      
      sortedRewards.forEach((reward: any, idx: number) => {
        console.log(`    ${idx + 1}. ${reward.pubkey?.slice(0, 8)}... ${(reward.lamports / 1e9).toFixed(6)} SOL (${reward.rewardType || 'unknown'})`);
      });
    }
    
    // Flag high-activity blocks
    if (meta.transactionCount > 3000) {
      blockStats.highActivityBlocks++;
      console.log(`  🔥 HIGH ACTIVITY: ${meta.transactionCount} transactions`);
    }
    
    // Summary every 50 blocks
    if (blockStats.totalBlocks % 50 === 0) {
      printBlockSummary();
    }
  }
}

function printBlockSummary(): void {
  const avgTxPerBlock = blockStats.totalBlocks > 0
    ? (blockStats.totalTransactions / blockStats.totalBlocks).toFixed(1)
    : 0;
  const avgBlockTime = blockStats.blockTimes.length > 0
    ? (blockStats.blockTimes.reduce((a, b) => a + b, 0) / blockStats.blockTimes.length).toFixed(1)
    : 0;
  const totalRewardsSOL = (blockStats.totalRewards / 1e9).toFixed(4);
  
  console.log(`\n=== Block Summary ===`);
  console.log(`  Total Blocks: ${blockStats.totalBlocks}`);
  console.log(`  Total Transactions: ${blockStats.totalTransactions}`);
  console.log(`  Avg Tx/Block: ${avgTxPerBlock}`);
  console.log(`  High Activity Blocks: ${blockStats.highActivityBlocks}`);
  console.log(`  Avg Block Time: ${avgBlockTime}s`);
  console.log(`  Total Rewards: ${totalRewardsSOL} SOL`);
}

monitorBlocks().catch(console.error);

Example 3: Filtered Block Monitor

Watch blocks that contain specific program activity:
// Replace with programs you want to monitor
const TOKEN_PROGRAM = "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA";
const SYSTEM_PROGRAM = "11111111111111111111111111111111";

async function monitorFilteredBlocks() {
  const streamManager = new StreamManager(
    "https://grpc.solanatracker.io",
    process.env.GRPC_API_TOKEN || "your-api-key",
    handleFilteredBlock
  );

  const subscribeRequest: SubscribeRequest = {
    blocks: {
      filteredBlocks: {
        accountInclude: [TOKEN_PROGRAM, SYSTEM_PROGRAM], // Your programs here
        includeTransactions: true,
        includeAccounts: false,
        includeEntries: false
      }
    },
    commitment: CommitmentLevel.CONFIRMED
  };

  console.log("Monitoring filtered blocks...");
  await streamManager.connect(subscribeRequest);
}

const filteredStats = {
  totalBlocks: 0,
  totalTransactions: 0,
  successfulTx: 0,
  failedTx: 0,
  totalFees: 0,
  programCounts: new Map<string, number>()
};

function handleFilteredBlock(data: any): void {
  if (data.block) {
    const block = data.block;
    filteredStats.totalBlocks++;
    
    console.log(`\n[Filtered Block #${filteredStats.totalBlocks}]`);
    console.log(`  Slot: ${block.slot}`);
    console.log(`  Block Hash: ${block.blockhash?.slice(0, 16)}...`);
    console.log(`  Transactions in Block: ${block.transactions?.length || 0}`);
    
    if (block.transactions) {
      let blockTxCount = 0;
      let blockFees = 0;
      let successCount = 0;
      let failCount = 0;
      
      block.transactions.forEach((tx: any) => {
        blockTxCount++;
        filteredStats.totalTransactions++;
        
        const success = !tx.meta?.err;
        if (success) {
          successCount++;
          filteredStats.successfulTx++;
        } else {
          failCount++;
          filteredStats.failedTx++;
        }
        
        const fee = tx.meta?.fee || 0;
        blockFees += fee;
        filteredStats.totalFees += fee;
        
        // Track program usage
        const accountKeys = tx.transaction?.message?.accountKeys || [];
        const instructions = tx.transaction?.message?.instructions || [];
        
        instructions.forEach((ix: any) => {
          const programId = accountKeys[ix.programIdIndex];
          if (programId) {
            const count = filteredStats.programCounts.get(programId) || 0;
            filteredStats.programCounts.set(programId, count + 1);
          }
        });
      });
      
      console.log(`  Successful Tx: ${successCount}`);
      console.log(`  Failed Tx: ${failCount}`);
      console.log(`  Block Fees: ${(blockFees / 1e9).toFixed(6)} SOL`);
      console.log(`  Avg Fee: ${(blockFees / Math.max(blockTxCount, 1) / 1e9).toFixed(6)} SOL`);
      
      // Show program distribution in this block
      const programsInBlock = new Map<string, number>();
      block.transactions.forEach((tx: any) => {
        const keys = tx.transaction?.message?.accountKeys || [];
        const instructions = tx.transaction?.message?.instructions || [];
        
        instructions.forEach((ix: any) => {
          const programId = keys[ix.programIdIndex];
          if (programId) {
            const count = programsInBlock.get(programId) || 0;
            programsInBlock.set(programId, count + 1);
          }
        });
      });
      
      console.log(`  Programs Used:`);
      Array.from(programsInBlock.entries())
        .sort((a, b) => b[1] - a[1])
        .slice(0, 3)
        .forEach(([program, count]) => {
          const name = program === TOKEN_PROGRAM ? "Token Program"
            : program === SYSTEM_PROGRAM ? "System Program"
            : program.slice(0, 12) + "...";
          console.log(`    - ${name}: ${count} calls`);
        });
    }
    
    // Summary every 20 blocks
    if (filteredStats.totalBlocks % 20 === 0) {
      printFilteredSummary();
    }
  }
}

function printFilteredSummary(): void {
  const avgTxPerBlock = filteredStats.totalBlocks > 0
    ? (filteredStats.totalTransactions / filteredStats.totalBlocks).toFixed(1)
    : 0;
  const successRate = filteredStats.totalTransactions > 0
    ? (filteredStats.successfulTx / filteredStats.totalTransactions * 100).toFixed(1)
    : 0;
  
  console.log(`\n=== Filtered Block Summary ===`);
  console.log(`  Blocks Processed: ${filteredStats.totalBlocks}`);
  console.log(`  Total Transactions: ${filteredStats.totalTransactions}`);
  console.log(`  Avg Tx/Block: ${avgTxPerBlock}`);
  console.log(`  Success Rate: ${successRate}%`);
  console.log(`  Total Fees: ${(filteredStats.totalFees / 1e9).toFixed(4)} SOL`);
  
  console.log(`\n  Most Used Programs:`);
  Array.from(filteredStats.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} instructions`);
    });
}

monitorFilteredBlocks().catch(console.error);

Understanding the Data

  {
    slot: number;           // Which slot
    parentSlot: number;     // Previous slot  
    status: string;         // "processed", "confirmed", or "finalized"
  }
Slots happen every ~400msStatus levels:
  • Processed: Just happened
  • Confirmed: Majority of network agrees
  • Finalized: Can’t be undone
  {
    slot: number;
    blockHeight: number;
    blockhash: string;
    parentBlockhash: string;
    blockTime: number;      // Unix timestamp
    transactionCount: number;
    rewards: Array<{
      pubkey: string;
      lamports: number;
      rewardType: string;   // "fee", "rent", "voting", "staking"
    }>;
  }
Block time: When the block was madeRewards: What validators earned
  {
    slot: number;
    parentSlot: number;
    blockhash: string;
    previousBlockhash: string;
    transactions: Transaction[];  // All transactions
    accounts: AccountUpdate[];    // Account changes
    entries: Entry[];            // Entries (optional)
  }
Warning: Full blocks can be several MB

How Much Data?

Slot Monitoring

Very light
  • Minimal data usage
  • Real-time network pulse
  • Easy to process
  • Great for dashboards

Block Metadata

Medium
  • Moderate data usage
  • Block-level insights
  • Transaction counts
  • Good for analytics

Full Blocks

Heavy
  • High data usage
  • Everything included
  • Needs strong hardware
  • Use filters!

Filtered Blocks

Smart choice
  • Use accountInclude
  • Turn off what you don’t need
  • Focus on specific programs
  • Best balance

What You Can Build

  • Network Monitors
  • Analytics Tools
  • Sync Tools
Track network performance
  • Slot timing dashboards
  • Congestion alerts
  • Consensus tracking
  • Validator stats
  • Skip rate alerts

Common Problems

Problem: You see gaps in slot numbersWhy it happens:
  • Connection dropped
  • Validator went down
  • Your code is too slow
Fix it:
  • Track gaps and alert
  • Add catch-up code
  • Check your connection
Problem: Full blocks are overwhelmingFix it:
  • Use metadata instead
  • Add account filters
  • Turn off unnecessary data
  • Process async

Best Practices

Tips for production:
  • Start small - Use metadata before full blocks
  • Filter everything - Use accountInclude to reduce data
  • Watch timing - Track slots to spot problems
  • Handle gaps - Code for missing slots
  • Don’t block - Process data async
  • Match commitment - Pick the right level for your needs
  • Track yourself - Monitor your own performance
  • Set alerts - Get notified of problems

Common Questions

A slot is a 400ms time window. Solana processes transactions in slots. One slot = one potential block (though some slots get skipped).
Start with metadata. It’s way lighter and has most of what you need. Only use full blocks if you need every transaction detail.
Validators sometimes miss their turn to produce a block. This is normal and the network handles it automatically. A 5% skip rate is typical.
A busy block can be several MB. That’s why filtering is important - you don’t want to download gigabytes of data you don’t need.
  • Processed (~400ms): Just happened, might change
  • Confirmed (~1 second): Supermajority agrees, probably won’t change
  • Finalized (~13 seconds): Locked in, can’t change

Next Steps

I