Skip to main content

Documentation Index

Fetch the complete documentation index at: https://www.helius.dev/docs/llms.txt

Use this file to discover all available pages before exploring further.

Slot and block monitoring gives you a window into Solana’s network consensus, block production timing, and overall health. With LaserStream you can track slot progression, block finalization, and network performance metrics in real-time using the helius-laserstream SDK.
Prerequisites: This guide assumes you’ve completed the LaserStream gRPC Quickstart and have an API key.

Monitoring Types

Track network consensus progressionMonitor slot advancement across commitment levels:
import { subscribe, CommitmentLevel, LaserstreamConfig, SubscribeRequest } from 'helius-laserstream';

const subscriptionRequest: SubscribeRequest = {
  slots: {
    slotSubscribe: {
      filterByCommitment: false // Receive all commitment levels
    }
  },
  commitment: CommitmentLevel.CONFIRMED,
  accounts: {}, transactions: {}, transactionsStatus: {},
  blocks: {}, blocksMeta: {}, entry: {}, accountsDataSlice: [],
};
Slot data includes: slot number, parent slot, commitment status (processed / confirmed / finalized), and leader information.
Best for: Network health monitoring, slot timing analysis, consensus tracking.

Practical Examples

Example 1: Network Health Monitor

Track slot progression and identify network issues:
import { subscribe, CommitmentLevel, LaserstreamConfig, SubscribeRequest } from 'helius-laserstream';

let lastSlot = 0;
let lastTimestamp = Date.now();
const slotTimes: number[] = [];

// CommitmentLevel only ships the forward (name → number) mapping, so we keep
// a small reverse lookup for the numeric status the SDK returns on slot updates.
const STATUS_NAMES = ['PROCESSED', 'CONFIRMED', 'FINALIZED'] as const;

async function monitorNetworkHealth() {
  const subscriptionRequest: SubscribeRequest = {
    slots: {
      slotSubscribe: {
        filterByCommitment: true // Track processed commitment levels
      }
    },
    commitment: CommitmentLevel.PROCESSED,
    accounts: {}, transactions: {}, transactionsStatus: {},
    blocks: {}, blocksMeta: {}, entry: {}, accountsDataSlice: [],
  };

  const config: LaserstreamConfig = {
    apiKey: 'YOUR_API_KEY',
    endpoint: 'https://laserstream-mainnet-ewr.helius-rpc.com',
  };

  await subscribe(config, subscriptionRequest, async (data) => {
    if (!data.slot) return;
    const slot = data.slot;
    // The SDK returns u64 fields as strings to preserve precision.
    const currentSlot = Number(slot.slot);
    const currentTime = Date.now();

    console.log(`\n📊 Slot Update:`);
    console.log(`  Slot: ${currentSlot}`);
    console.log(`  Parent: ${slot.parent}`);
    // slot.status is a numeric enum (0=processed, 1=confirmed, 2=finalized).
    console.log(`  Status: ${STATUS_NAMES[slot.status] ?? slot.status}`);

    if (lastSlot > 0) {
      const slotDiff = currentSlot - lastSlot;
      const timeDiff = currentTime - lastTimestamp;

      if (slotDiff === 1) {
        slotTimes.push(timeDiff);
        if (slotTimes.length > 100) slotTimes.shift();

        const avg = slotTimes.reduce((a, b) => a + b, 0) / slotTimes.length;
        console.log(`  Slot Time: ${timeDiff}ms`);
        console.log(`  Avg Slot Time: ${avg.toFixed(1)}ms`);

        if (timeDiff > 800) {
          console.log(`  ⚠️  SLOW SLOT: ${timeDiff}ms (normal ~400ms)`);
        }
      } else if (slotDiff > 1) {
        console.log(`  ⚠️  SKIPPED ${slotDiff - 1} SLOTS`);
      }
    }

    lastSlot = currentSlot;
    lastTimestamp = currentTime;
  }, async (error) => {
    console.error('Stream error:', error);
  });
}

monitorNetworkHealth().catch(console.error);

Example 2: Block Production Monitor

Track block production and transaction volume:
async function monitorBlockProduction() {
  const subscriptionRequest: SubscribeRequest = {
    blocksMeta: {
      blockMetaSubscribe: {}
    },
    commitment: CommitmentLevel.CONFIRMED,
    accounts: {}, transactions: {}, transactionsStatus: {},
    slots: {}, blocks: {}, entry: {}, accountsDataSlice: [],
  };

  const config: LaserstreamConfig = {
    apiKey: 'YOUR_API_KEY',
    endpoint: 'https://laserstream-mainnet-ewr.helius-rpc.com',
  };

  await subscribe(config, subscriptionRequest, async (data) => {
    if (!data.blockMeta) return;
    const blockMeta = data.blockMeta;

    console.log(`\n🧱 Block Produced:`);
    console.log(`  Slot: ${blockMeta.slot}`);
    // blockHeight is a wrapper object: { blockHeight: '397657352' }
    console.log(`  Block Height: ${blockMeta.blockHeight?.blockHeight}`);
    console.log(`  Block Hash: ${blockMeta.blockhash}`);
    console.log(`  Parent Slot: ${blockMeta.parentSlot}`);
    console.log(`  Parent Hash: ${blockMeta.parentBlockhash}`);
    console.log(`  Transactions: ${blockMeta.executedTransactionCount}`);
    console.log(`  Entries: ${blockMeta.entriesCount}`);
    if (blockMeta.blockTime?.timestamp) {
      // blockTime.timestamp is a u64 as a string (Unix seconds).
      console.log(`  Block Time: ${new Date(Number(blockMeta.blockTime.timestamp) * 1000).toISOString()}`);
    }

    // rewards is a wrapper object: { rewards: [...], numPartitions: number | null }
    if (blockMeta.rewards?.rewards?.length > 0) {
      console.log(`  Rewards:`);
      blockMeta.rewards.rewards.forEach((r: any) => {
        console.log(`    ${r.pubkey}: ${r.lamports} lamports (${r.rewardType})`);
      });
    }

    if (Number(blockMeta.executedTransactionCount) > 3000) {
      console.log(`  🔥 HIGH ACTIVITY: ${blockMeta.executedTransactionCount} transactions`);
    }
  }, async (error) => {
    console.error('Stream error:', error);
  });
}

Example 3: Filtered Block Monitor

Monitor blocks containing specific program activity:
async function monitorDEXBlocks() {
  const subscriptionRequest: SubscribeRequest = {
    blocks: {
      blockSubscribe: {
        accountInclude: [
          "675kPX9MHTjS2zt1qfr1NYHuzeLXfQM9H24wFSUt1Mp8", // Raydium
          "CAMMCzo5YL8w4VFF8KVHrK22GGUsp5VTaW7grrKgrWqK", // Raydium CLMM
          "JUP6LkbZbjS1jKKwapdHNy74zcZ3tLUZoi5QNyVTaV4"   // Jupiter
        ],
        includeTransactions: true,
        includeAccounts: false,
        includeEntries: false
      }
    },
    commitment: CommitmentLevel.CONFIRMED,
    accounts: {}, transactions: {}, transactionsStatus: {},
    slots: {}, blocksMeta: {}, entry: {}, accountsDataSlice: [],
  };

  const config: LaserstreamConfig = {
    apiKey: 'YOUR_API_KEY',
    endpoint: 'https://laserstream-mainnet-ewr.helius-rpc.com',
  };

  await subscribe(config, subscriptionRequest, async (data) => {
    if (!data.block) return;
    const block = data.block;

    let successfulDexTx = 0;
    let totalFees = 0; // lamports
    block.transactions?.forEach((tx: any) => {
      if (tx.meta && !tx.meta.err) {
        successfulDexTx++;
        // tx.meta.fee is a u64 string — coerce before adding.
        totalFees += Number(tx.meta.fee ?? 0);
      }
    });

    console.log(`\n🔄 DEX Activity Block:`);
    console.log(`  Slot: ${block.slot}`);
    console.log(`  Block Height: ${block.blockHeight?.blockHeight}`);
    console.log(`  Block Hash: ${block.blockhash}`);
    console.log(`  Total transactions in block: ${block.executedTransactionCount}`);
    console.log(`  Matched DEX transactions: ${block.transactions?.length ?? 0}`);
    console.log(`  Successful DEX transactions: ${successfulDexTx}`);
    if (successfulDexTx > 0) {
      console.log(`  Total Fees: ${(totalFees / 1e9).toFixed(4)} SOL`);
      console.log(`  Avg Fee: ${(totalFees / successfulDexTx / 1e9).toFixed(6)} SOL`);
    }
  }, async (error) => {
    console.error('Stream error:', error);
  });
}

Data Structures

{
  slot: string;     // Current slot number (u64 as string)
  parent: string;   // Parent slot number (u64 as string)
  status: number;   // CommitmentLevel enum: 0 = processed, 1 = confirmed, 2 = finalized
}
Each slot represents ~400ms of network time. The three commitment levels reflect progressively stronger guarantees: processed (initial), confirmed (supermajority voted), finalized (irreversible).
u64 fields (slot, parent) arrive as strings to preserve precision beyond Number.MAX_SAFE_INTEGER. Convert with Number(slot.slot) when you need arithmetic. status is a numeric enum — use CommitmentLevel[slot.status] for the human-readable name.
{
  slot: string;                                  // u64 as string
  blockhash: string;
  rewards: {
    rewards: Array<{
      pubkey: string;
      lamports: string;                          // u64 as string
      rewardType: string;                        // "fee" | "rent" | "voting" | "staking"
    }>;
    numPartitions: number | null;
  };
  blockTime: { timestamp: string };              // Unix seconds as a u64 string
  blockHeight: { blockHeight: string };          // u64 as string, wrapped
  parentSlot: string;                            // u64 as string
  parentBlockhash: string;
  executedTransactionCount: string;              // u64 as string
  entriesCount: string;                          // u64 as string
}
Numeric fields (slot, parentSlot, executedTransactionCount, entriesCount, the values inside blockHeight and blockTime) are emitted as strings because they are u64 in the underlying proto. Wrap them in Number(...) for arithmetic or comparisons.
{
  slot: string;                                  // u64 as string
  blockhash: string;
  rewards: {
    rewards: Array<{
      pubkey: string;
      lamports: string;                          // u64 as string
      rewardType: string;
    }>;
    numPartitions: number | null;
  };
  blockTime: { timestamp: string };              // Unix seconds (u64 string)
  blockHeight: { blockHeight: string };          // u64 as string, wrapped
  parentSlot: string;                            // u64 as string
  parentBlockhash: string;
  executedTransactionCount: string;              // total executed tx in the block (u64 as string)
  updatedAccountCount: string;                   // total account updates in the block (u64 as string)
  entriesCount: string;                          // u64 as string
  transactions: Array<{
    signature: Buffer;                           // base58-encode for display
    isVote: boolean;
    transaction: TransactionMessage;             // full transaction payload
    meta: TransactionMeta;                       // execution metadata (fee, err, balances, …)
    index: string;                               // u64 as string
  }>;
  accounts: AccountUpdate[];                     // populated when includeAccounts: true
  entries: Entry[];                              // populated when includeEntries: true
}
Full blocks can be several MB with all transactions and accounts. The same u64-as-string convention applies — wrap numeric fields with Number(...) for arithmetic. Inside each transaction, meta.fee, meta.preBalances, meta.postBalances, etc. are also strings.

Performance Considerations

Slot Monitoring

Lightweight: very low bandwidth, minimal processing overhead. Good for monitoring dashboards.

Block Metadata

Balanced: moderate bandwidth, block-level insights without full data. Suitable for analytics.

Full Blocks

High volume: complete transaction data, requires robust processing. Always pair with filters.

Filtered Blocks

Optimized: use accountInclude, disable includeAccounts/includeEntries you don’t need.

Use Cases

Track network health and performance — slot timing, congestion, consensus.
const targetSlotTime = 400; // ms
const tolerance = 200; // ms
if (Math.abs(slotTime - targetSlotTime) > tolerance) {
  console.log(`Network performance issue detected`);
}

Error Handling

Symptom: Gaps in slot progression.Causes: Network connectivity issues, validator downtime, client processing delays.Solutions: Track slot gaps and alert; implement catch-up logic via historical replay; monitor connection health.
Symptom: Too much block data.Solutions: Use block metadata instead of full blocks; apply account filters; disable unnecessary inclusions (entries, accounts); process asynchronously.
Symptom: Inconsistent slot timing.Analysis: Calculate moving averages; track deviations; monitor network health metrics; correlate with validator performance.

Best Practices

Production Guidelines:
  • Start with metadata — use block metadata before subscribing to full blocks
  • Apply filters — use accountInclude to drop irrelevant data
  • Monitor timing — track slot progression as a network-health canary
  • Handle gaps — combine with historical replay so missing slots are auto-backfilled on reconnect
  • Process async — don’t block stream processing with heavy computations
  • Match commitment to needprocessed for low-latency UIs, confirmed/finalized for state writes

Next Steps

Transaction Monitoring

Filter transactions by program, account, vote, or failure status.

Stream Pump AMM Data

Real-world example: monitor Pump AMM transactions.

Decoding Transaction Data

Parse the binary transactionUpdate payloads into readable Solana transactions.

Yellowstone protocol reference

The same workflow against the raw Yellowstone gRPC protocol.