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.
What You Can Monitor
Slots
Full Blocks
Block Metadata
Watch the network heartbeat Slots 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 );
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 ~400ms Status levels:
Processed: Just happened
Confirmed: Majority of network agrees
Finalized: Can’t be undone
{
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).
Should I use full blocks or metadata?
Start with metadata. It’s way lighter and has most of what you need. Only use full blocks if you need every transaction detail.
Why are some slots skipped?
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.
What's the difference between processed, confirmed, and finalized?
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