Skip to content

Real-Time Balance Monitor

Monitor multiple addresses with live balance updates using shred state changes.

balance-monitor.ts
import { createPublicClient, webSocket, formatEther } from 'viem'
import { riseTestnet } from 'viem/chains'
import { watchShreds } from 'shreds/viem'
 
const wsClient = createPublicClient({
  chain: riseTestnet,
  transport: webSocket()
})
 
// Track balances for multiple addresses
class BalanceMonitor {
  private balances = new Map<string, bigint>()
  private monitoredAddresses = new Set<string>()
  private unwatch?: () => void
  
  async addAddress(address: string) {
    // Get initial balance (only once during setup)
    const balance = await wsClient.getBalance({ 
      address: address as `0x${string}` 
    })
    this.balances.set(address.toLowerCase(), balance)
    this.monitoredAddresses.add(address.toLowerCase())
    
    console.log(`Monitoring ${address}: ${formatEther(balance)} ETH`)
    
    // Start watching if this is the first address
    if (this.monitoredAddresses.size === 1) {
      this.startWatching()
    }
  }
  
  private startWatching() {
    // Watch for shreds and use state changes for efficient updates
    this.unwatch = watchShreds(wsClient, {
      onShred: (shred) => {
        // Process state changes from the shred
        for (const stateChange of shred.stateChanges) {
          const address = stateChange.address.toLowerCase()
          
          // Check if this address is being monitored
          if (this.monitoredAddresses.has(address)) {
            const oldBalance = this.balances.get(address) || 0n
            const newBalance = stateChange.balance
            
            if (newBalance !== oldBalance) {
              const change = newBalance - oldBalance
              console.log(`[BALANCE] Balance change for ${address}:`)
              console.log(`   ${formatEther(oldBalance)} -> ${formatEther(newBalance)} ETH`)
              console.log(`   Change: ${change > 0 ? '+' : ''}${formatEther(change)} ETH`)
              
              this.balances.set(address, newBalance)
            }
          }
        }
      },
      onError: (error) => {
        console.error('Shred watching error:', error)
      }
    })
  }
  
  getBalance(address: string): string {
    const balance = this.balances.get(address.toLowerCase()) || 0n
    return formatEther(balance)
  }
  
  stop() {
    this.unwatch?.()
    console.log('Stopped monitoring')
  }
}
 
// Usage
const monitor = new BalanceMonitor()
await monitor.addAddress('0x742d35Cc6634C0532925a3b844Bc9e7595f6E98d')
await monitor.addAddress('0x70997970C51812dc3A010C7d01b50e0d17dc79C8')
 
// Stop monitoring after 60 seconds
setTimeout(() => monitor.stop(), 60000)

Key Features

  • Efficient Updates: Uses shred state changes instead of RPC polling
  • Multiple Addresses: Monitor any number of addresses simultaneously
  • Real-Time: Balance updates arrive within milliseconds of transactions
  • Resource Friendly: No RPC calls in the shred callback

How It Works

  1. Initial Setup: Fetches balance once when adding an address
  2. State Changes: Shreds include balance changes for affected addresses
  3. Instant Updates: Balance changes are detected and logged immediately
  4. No Polling: Uses WebSocket subscription for push-based updates

Best Practices

  • Always use state changes from shreds for real-time data
  • Fetch initial state once, then rely on updates
  • Handle errors gracefully in the shred callback
  • Clean up subscriptions when done

Next Steps

  • Add persistence to track balance history
  • Create a web interface with real-time charts
  • Add notifications for large balance changes
  • Export balance data for analysis