Platform
Web3 [de]coded
- Introduction
- Protocols
- Overview
- Ethereum: How to analyze pending blocks
- Blob transactions the hard way
- Ethereum Dencun: Rundown with examples
- Ethereum: Academic certificates with Truffle
- Ethereum: Asset tokenization with Embark
- Ethereum: Trust fund account with Remix
- Chainlink: Estimating the price of a call
- Polygon: Bridging ERC-20 from Ethereum to Polygon
- BNB Smart Chain: BEP-1155 contract with Truffle & OpenZeppelin
- BNB Smart Chain: Lorentz hardfork
- Base: Deploy an ERC-721 contract with Hardhat
- Using eth_getStorageAt instead of debug_storageRangeAt on Reth
- Avalanche: Aave V3 flash loan with Hardhat
- TON: Deploy a smart contract
- TON: How to develop fungible tokens (Jettons)
- TON: How to customize fungible tokens (Jettons)
- TON: How to develop non-fungible tokens (NFT)
- TON: Wallet initialization with Tonweb
- TON: How to interact with Jettons
- TON: Choosing v2 or v3
- Unichain: Collecting Uniswap v4 ETH-USDC trades
- Arbitrum: L1 to L2 messaging smart contract
- zkSync Era: Develop a custom paymaster contract
- Polygon zkEVM: Deploy a smart contract using Hardhat
- Optimism: Bridge ether from Ethereum L1 to Optimism L2 using the Optimism JavaScript SDK
- NEAR: Creating & Upgrading a simple message contract
- Scroll: Deploy Uniswap V3 on Scroll
- Ronin: Make a game's smart contract
- Ronin: on-chain meta racing game
- Aptos: Publish a module to save & retrieve a message on-chain
- Oasis Sapphire: Understanding confidential smart contracts
- Gnosis Chain: Simple soulbound token with Remix and OpenZeppelin
- Cronos: Dutch auction smart contract with Hardhat
- Filecoin: Deploy a deal-making contract using Hardhat
- Fantom: ERC-721 Collection contract with Truffle & OpenZeppelin
- TRON: Mastering Energy & Bandwidth with Python and Chainstack
- TRON: Polling for TRC20 transfers in Node.js
- Sonic: Swap farming for points walkthrough in Python
- Starknet: An NFT contract with Nile and L1 <-> L2 reputation messaging
- Harmony: A simple metaverse contract with Foundry
- Tezos: A simple fund contract in LIGO
- Etherlink: Tezos-powered EVM Layer 2
- Solana Archive nodes: The backbone of Solana’s data availability and developer tooling
- Ronin gaming: Overview of Axie & Pixels
- Ronin: Consensus algorithm
- Kaia (ex. Klaytn): Contract Sizzle 100
- Blast: Tracking Automatic, Void, Claimable accounts
- Sui: On-chain validator analytics with pysui
- Berachain: On-chain data quickstart with Python
- Linea: Real-time transaction activity monitor with Python
- Mantle: Fetching token prices from Merchant Moe
- Zora: Real-time creator token detection and monitoring
- Polkadot: Real-time network health monitoring dashboard
- Celo: Build a simple voting dApp with Foundry, Next.js, and Web3.js
- Moonbeam: Monitoring the Conviction Voting contract
- opBNB: How to listen to deposits on the opBNB bridge
- Chainstack Subgraphs
- Chainstack Marketplace
- Blockchain APIs guides
- Best practices handbook
- Mastering Solana
- Web3pedia
IPFS storage
Chainstack Compare
Chainstack ChatGPT plugin
Chainstack DLP browser extension
Protocols
Advanced APIs
- Introduction
- Warp Transactions
- Debug & Trace APIs
Tooling
- Introduction
- Ethereum tooling
- Solana tooling
- BNB Smart Chain tooling
- Polygon tooling
- Arbitrum tooling
- Base tooling
- Optimism tooling
- Avalanche tooling
- TON tooling
- Unichain tooling
- Ronin tooling
- Blast tooling
- Sui tooling
- Berachain tooling
- Linea tooling
- Mantle tooling
- Polkadot tooling
- zkSync Era tooling
- Zora tooling
- Starknet tooling
- Scroll tooling
- Aptos tooling
- Sonic tooling
- Fantom tooling
- TRON tooling
- Cronos tooling
- Gnosis Chain tooling
- Kaia (ex. Klaytn) tooling
- Celo tooling
- Moonbeam tooling
- Oasis Sapphire tooling
- Polygon zkEVM tooling
- Bitcoin tooling
- Harmony tooling
- opBNB tooling
- Tezos tooling
- Filecoin tooling
- NEAR tooling
Polkadot: Real-time network health monitoring dashboard
Learn how to build a real-time network health monitoring dashboard for Polkadot using Python and the Substrate interface through Chainstack RPC endpoints.
TLDR:
- Demonstrates building a real-time network health monitoring dashboard for Polkadot mainnet.
- Shows how to work with Substrate interface to query network statistics, validators, staking, and governance data.
- Includes practical examples of data visualization and continuous monitoring with graceful error handling.
Overview
Polkadot is a multi-chain blockchain protocol that enables different blockchains to transfer messages and value in a trust-free fashion. Polkadot relies on validators to secure the network and produce blocks.
This tutorial guides you through creating a sample real-time network health monitoring dashboard for Polkadot. You’ll learn how to connect to the Polkadot network using the Substrate interface, fetch critical network metrics including block production, validator statistics, staking information, and governance data, and create an interactive dashboard with live updates.
Prerequisites
- substrate-interface
- A Chainstack Polkadot node endpoint—register on Chainstack
Network health monitoring dashboard
Now let’s create the dashboard.
The script connects to the Polkadot network over a Chainstack node WebSocket endpoint, then continuously queries various pallets to gather network statistics, validator information, staking metrics, and governance data.
The dashboard displays live information about block production, finalization lag, validator counts, staking eras, and governance activity in an organized terminal interface.
Here’s the full script and the comments:
import time
from datetime import datetime
from typing import Dict, Optional
import signal
import sys
from substrateinterface import SubstrateInterface
from rich.console import Console
from rich.panel import Panel
from rich.layout import Layout
from rich.live import Live
from rich.align import Align
import click
console = Console()
class PolkadotHealthMonitor:
def __init__(self, endpoint: str):
self.endpoint = endpoint
self.substrate = None
self.running = False
self.start_time = datetime.now()
self.blocks_processed = 0
def connect(self) -> bool:
"""Connect to Polkadot network"""
try:
with console.status("[bold green]Connecting..."):
self.substrate = SubstrateInterface(
url=self.endpoint,
ss58_format=0,
type_registry_preset='polkadot'
)
chain_info = self.substrate.get_chain_finalised_head()
console.print(f"✅ Connected to {self.substrate.chain}")
console.print(f"🔗 Latest finalized: {chain_info}")
return True
except Exception as e:
console.print(f"❌ Connection failed: {e}", style="bold red")
return False
def safe_query(self, pallet: str, method: str, params: list = None) -> Optional[any]:
"""Safely execute substrate queries with error handling"""
try:
result = self.substrate.query(pallet, method, params or [])
return result.value if result else None
except:
return None
def get_all_data(self) -> Dict:
"""Fetch all network data in one consolidated method"""
data = {'timestamp': datetime.now()}
try:
# Network info
latest_hash = self.substrate.get_chain_head()
finalized_hash = self.substrate.get_chain_finalised_head()
latest_block = self.substrate.get_block(latest_hash)
finalized_block = self.substrate.get_block(finalized_hash)
latest_num = latest_block['header']['number'] if latest_block and 'header' in latest_block else 0
finalized_num = finalized_block['header']['number'] if finalized_block and 'header' in finalized_block else 0
data.update({
'chain': str(self.substrate.chain),
'latest_block': latest_num,
'finalized_block': finalized_num,
'finalization_lag': latest_num - finalized_num,
'block_hash': str(latest_hash)[:12] + '...' if latest_hash else 'N/A'
})
# Runtime version
try:
runtime = self.substrate.get_runtime_version()
data.update({
'runtime_version': runtime.get('specVersion', 'N/A') if runtime else 'N/A',
'runtime_name': runtime.get('specName', 'N/A') if runtime else 'N/A'
})
except Exception as e:
# Fallback: try to get from metadata
try:
metadata = self.substrate.get_metadata()
runtime_version = getattr(metadata, 'runtime_version', None)
if runtime_version:
data.update({
'runtime_version': str(runtime_version.get('spec_version', 'N/A')),
'runtime_name': str(runtime_version.get('spec_name', 'Polkadot'))
})
else:
# Fallback: show we have connection
data.update({'runtime_version': 'Connected', 'runtime_name': 'Polkadot'})
except:
data.update({'runtime_version': 'Connected', 'runtime_name': 'Polkadot'})
# Validators and staking
validators = self.safe_query('Session', 'Validators') or []
validator_count = len(validators)
current_era = self.safe_query('Staking', 'CurrentEra') or 0
total_issuance = self.safe_query('Balances', 'TotalIssuance') or 0
active_validators = self.safe_query('Staking', 'CounterForValidators') or validator_count
data.update({
'validators': validator_count,
'active_validators': active_validators,
'current_era': current_era,
'total_supply': f"{total_issuance / 10**10:,.0f} DOT" if total_issuance else "N/A"
})
# Governance
referendum_count = (self.safe_query('Referenda', 'ReferendumCount') or
self.safe_query('Democracy', 'ReferendumCount') or 0)
council_members = self.safe_query('Council', 'Members') or []
fellowship = self.safe_query('FellowshipCollective', 'Members') or []
proposals = self.safe_query('Democracy', 'PublicProps') or []
data.update({
'referendums': referendum_count,
'council_size': len(council_members) or len(fellowship),
'proposals': len(proposals),
'governance': 'OpenGov v2' if referendum_count > 0 else 'Legacy'
})
self.blocks_processed += 1
except Exception as e:
console.print(f"❌ Data fetch error: {e}")
return data
def create_panel(self, title: str, content: str, style: str = "green") -> Panel:
"""Create a formatted panel"""
return Panel(content.strip(), title=title, border_style=style)
def create_dashboard(self) -> Layout:
"""Create compact dashboard layout"""
data = self.get_all_data()
# Network panel
network_content = f"""[bold green]Chain:[/bold green] {data.get('chain', 'N/A')}
[bold blue]Latest:[/bold blue] #{data.get('latest_block', 'N/A')}
[bold yellow]Finalized:[/bold yellow] #{data.get('finalized_block', 'N/A')}
[bold red]Lag:[/bold red] {data.get('finalization_lag', 'N/A')} blocks
[bold magenta]Runtime:[/bold magenta] {data.get('runtime_version', 'N/A')}"""
# Validators panel
validator_content = f"""[bold green]Total:[/bold green] {data.get('validators', 'N/A')}
[bold blue]Active:[/bold blue] {data.get('active_validators', 'N/A')}
[bold yellow]Supply:[/bold yellow] {data.get('total_supply', 'N/A')}"""
# Staking panel
staking_content = f"""[bold green]Era:[/bold green] {data.get('current_era', 'N/A')}
[bold blue]Duration:[/bold blue] 24 hours
[bold yellow]Validators:[/bold yellow] {data.get('validators', 'N/A')}"""
# Governance panel
gov_content = f"""[bold green]Referendums:[/bold green] {data.get('referendums', 'N/A')}
[bold blue]Council:[/bold blue] {data.get('council_size', 'N/A')}
[bold yellow]Proposals:[/bold yellow] {data.get('proposals', 'N/A')}
[bold magenta]System:[/bold magenta] {data.get('governance', 'Unknown')}"""
# Status panel
uptime = datetime.now() - self.start_time
status_content = f"""[bold green]Status:[/bold green] {'🟢 Running' if self.running else '🔴 Stopped'}
[bold blue]Uptime:[/bold blue] {str(uptime).split('.')[0]}
[bold yellow]Blocks:[/bold yellow] {self.blocks_processed}
[bold magenta]Updated:[/bold magenta] {datetime.now().strftime('%H:%M:%S')}"""
# Create layout
layout = Layout()
layout.split_column(
Layout(Panel(Align.center("🟡 Polkadot Network Health Monitor 📊", style="bold yellow"),
style="bold blue"), size=3),
Layout(name="body"),
Layout(self.create_panel("📊 Status", status_content, "cyan"), size=7)
)
layout["body"].split_row(
Layout(name="left"),
Layout(name="right")
)
layout["left"].split_column(
self.create_panel("🌐 Network", network_content),
self.create_panel("👥 Validators", validator_content, "blue")
)
layout["right"].split_column(
self.create_panel("🏦 Staking", staking_content, "yellow"),
self.create_panel("🗳️ Governance", gov_content, "magenta")
)
return layout
def start_monitoring(self, refresh_interval: int = 10):
"""Start monitoring with graceful shutdown"""
if not self.connect():
return
self.running = True
signal.signal(signal.SIGINT, lambda s, f: self._shutdown())
console.print(f"🚀 Starting monitor (refresh: {refresh_interval}s)")
console.print("💡 Press Ctrl+C to stop\n")
try:
with Live(self.create_dashboard(), refresh_per_second=1/refresh_interval, screen=True) as live:
while self.running:
live.update(self.create_dashboard())
time.sleep(refresh_interval)
except KeyboardInterrupt:
self._shutdown()
except Exception as e:
console.print(f"\n❌ Error: {e}")
finally:
self.running = False
def _shutdown(self):
"""Graceful shutdown"""
self.running = False
console.print("\n👋 Monitor stopped")
sys.exit(0)
@click.command()
@click.option('--endpoint', '-e',
default='YOUR_CHAINSTACK_WSS_ENDPOINT',
help='Chainstack endpoint')
@click.option('--refresh', '-r', default=10, help='Refresh interval (seconds)')
def main(endpoint: str, refresh: int):
"""🟡 Polkadot Network Health Monitor 📊"""
console.print(Panel.fit(
"""🟡 [bold yellow]Polkadot Network Health Monitor[/bold yellow] 📊
Real-time monitoring of:
• Network statistics & block production
• Validator performance metrics
• Staking analysis & governance activity
[italic]Powered by Chainstack infrastructure[/italic]""",
border_style="bold blue"
))
monitor = PolkadotHealthMonitor(endpoint)
monitor.start_monitoring(refresh_interval=refresh)
if __name__ == "__main__":
main()
where YOUR_CHAINSTACK_WSS_ENDPOINT
is your Chainstack WebSocket endpoint.
Conclusion
The monitoring approach demonstrated here can be easily extended to track additional metrics, implement alerting systems, or integrate into larger network monitoring solutions requiring detailed Polkadot network insights.