RISE Logo-Light

Watching Events

Realtime shred subscriptions and event streaming

Watching Events

RISE enhances standard Ethereum subscriptions with realtime shred data, delivering events in milliseconds instead of seconds.

Overview

WebSocket subscriptions on RISE work just like standard Ethereum, but with dramatically faster event delivery. Events are streamed from shreds as transactions are processed, providing instant notifications for your applications.

Shred Subscription

Subscribe to new shreds in realtime for instant transaction visibility.

Subscribe

{
  "jsonrpc": "2.0",
  "method": "eth_subscribe",
  "params": ["shreds"],
  "id": 1
}

You can add an extra true parameter to populate the stateChanges field in shred notifications:

{
  "jsonrpc": "2.0",
  "method": "eth_subscribe",
  "params": ["shreds", true],
  "id": 1
}

Without this parameter, stateChanges will be an empty array by default.

Subscription Response

{
  "jsonrpc": "2.0",
  "result": "0x9ce59a13059e417087c02d3236a0b1cc",
  "id": 1
}

Notification Format

{
  "jsonrpc": "2.0",
  "method": "eth_subscription",
  "params": {
    "subscription": "0x9ce59a13059e417087c02d3236a0b1cc",
    "result": {
      "blockTimestamp": "0x...",
      "blockNumber": "0x1",
      "shredIndex": 0,
      "startingLogIndex": 0,
      "transactions": [
        {
          "hash": "0x...",
          "status": "success",
          "gasUsed": "0x5208",
          "cumulativeGasUsed": "0x5208",
          "logs": []
        }
      ],
      "stateChanges": [
        {
          "address": "0x...",
          "nonce": 1,
          "balance": "0x...",
          "storageChanges": [],
          "newCode": null
        }
      ]
    }
  }
}

Shred Object Structure

interface Shred {
  blockTimestamp: bigint;
  blockNumber: bigint;
  shredIndex: number;
  startingLogIndex: number;
  transactions: ShredTransaction[];
  stateChanges: ShredStateChange[];
}

interface ShredTransaction {
  hash: string;
  from: string;
  to: string;
  value: bigint;
  gas: bigint;
  status: "success" | "reverted";
  type: "eip1559" | "legacy" | "eip2930" | "eip7702" | "deposit";
  cumulativeGasUsed: bigint;
  logs: Array<{
    address: string;
    topics: string[];
    data: string;
  }>;
  // Additional fields available depending on transaction type
  gasPrice?: bigint;           // legacy, eip2930
  maxFeePerGas?: bigint;       // eip1559, eip7702
  maxPriorityFeePerGas?: bigint; // eip1559, eip7702
  accessList?: AccessList;     // eip2930, eip1559, eip7702
  chainId?: number;
  nonce?: bigint;
}

interface ShredStateChange {
  address: string;
  nonce: number;
  balance: string;
  storageChanges: StorageChange[];
  newCode: string | null;
}

Using with viem

import { createPublicClient, webSocket } from 'viem'
import { shredActions } from 'shreds/viem'
import { riseTestnet } from 'viem/chains'

const client = createPublicClient({
  chain: riseTestnet,
  transport: webSocket('wss://testnet.riselabs.xyz/ws')
}).extend(shredActions)

// Watch for new shreds
const unwatch = client.watchShreds({
  includeStateChanges: true, // Optional: include state changes in shreds
  onShred: (shred) => {
    console.log(`Shred ${shred.shredIndex} confirmed`)
    console.log(`Transactions: ${shred.transactions.length}`)

    shred.transactions.forEach(tx => {
      console.log(`- ${tx.hash}: ${tx.status}`)
    })
  },
  onError: (error) => {
    console.error('Subscription error:', error)
  }
})

// Stop watching
// unwatch()

Logs Subscription

Subscribe to contract event logs with shred-speed delivery. On RISE, logs are delivered from shreds instead of blocks for faster event processing.

Subscribe with Filter

{
  "jsonrpc": "2.0",
  "method": "eth_subscribe",
  "params": [
    "logs",
    {
      "address": "0x...",
      "topics": ["0x..."]
    }
  ],
  "id": 1
}

Enhanced Behavior

  • Events delivered immediately when transactions are processed in shreds
  • No waiting for block confirmation
  • Standard Ethereum log format maintained for compatibility
  • Filters work exactly like standard Ethereum

Using with viem

// Watch for all Transfer events on a token contract
const unwatch = client.watchContractEvent({
  address: '0x...',
  abi: erc20Abi,
  eventName: 'Transfer',
  onLogs: logs => {
    // Events arrive in milliseconds!
    logs.forEach(log => {
      console.log(`Transfer: ${log.args.from} -> ${log.args.to}`)
      console.log(`Amount: ${formatEther(log.args.value)}`)
    })
  }
})

Filter Specific Events

// Watch for transfers to a specific address
client.watchContractEvent({
  address: tokenAddress,
  abi: erc20Abi,
  eventName: 'Transfer',
  args: {
    to: myAddress // Filter by recipient
  },
  onLogs: logs => {
    console.log(`Received ${logs.length} transfers`)
  }
})

Example

Balance Monitor

Monitor an address for balance changes in realtime:

let lastBalance = await client.getBalance({ address: monitoredAddress })

client.watchShreds({
  onShred: async (shred) => {
    // Check if this shred affects the monitored address
    const affected = shred.stateChanges.find(
      change => change.address.toLowerCase() === monitoredAddress.toLowerCase()
    )

    if (affected) {
      const newBalance = BigInt(affected.balance)
      const change = newBalance - lastBalance

      console.log(`Balance changed by ${formatEther(change)} ETH`)
      console.log(`New balance: ${formatEther(newBalance)} ETH`)

      lastBalance = newBalance
    }
  }
})

Multiple Subscriptions

You can maintain multiple subscriptions simultaneously:

// Watch shreds
const unwatchShreds = client.watchShreds({
  onShred: handleShred
})

// Watch token transfers
const unwatchTransfers = client.watchContractEvent({
  address: tokenAddress,
  abi: erc20Abi,
  eventName: 'Transfer',
  onLogs: handleTransfers
})

// Watch DEX swaps
const unwatchSwaps = client.watchContractEvent({
  address: dexAddress,
  abi: dexAbi,
  eventName: 'Swap',
  onLogs: handleSwaps
})

// Clean up when done
// unwatchShreds()
// unwatchTransfers()
// unwatchSwaps()

Error Handling

Handle connection errors and reconnection:

client.watchShreds({
  onShred: (shred) => {
    console.log('New shred:', shred.shredIndex)
  },
  onError: (error) => {
    console.error('Subscription error:', error)
    // Implement reconnection logic
  }
})

Performance Characteristics

Event Delivery Times

  • Shred Events: 3-5ms from transaction execution
  • Log Events: 3-5ms from transaction execution
  • State Changes: Immediate in shred notification

Throughput

  • Events per Second: 10,000+
  • Concurrent Subscriptions: Unlimited
  • Message Size: Optimized for low latency

Best Practices

1. Use Event Filters

Filter events at the RPC level to reduce bandwidth:

// ✅ Good - filtered at source
client.watchContractEvent({
  address: tokenAddress,
  eventName: 'Transfer',
  args: { to: myAddress }
})

// ❌ Avoid - filtering client-side
client.watchContractEvent({
  address: tokenAddress,
  eventName: 'Transfer',
  onLogs: logs => {
    const filtered = logs.filter(log => log.args.to === myAddress)
  }
})

2. Handle High-Frequency Events

Debounce or batch updates for high-frequency events:

let pendingUpdates = []

client.watchContractEvent({
  eventName: 'PriceUpdate',
  onLogs: logs => {
    pendingUpdates.push(...logs)
  }
})

// Batch process every 100ms
setInterval(() => {
  if (pendingUpdates.length > 0) {
    processBatch(pendingUpdates)
    pendingUpdates = []
  }
}, 100)

3. Clean Up Subscriptions

Always unsubscribe when done:

const unwatch = client.watchShreds({ onShred })

// In cleanup (e.g., React useEffect)
return () => {
  unwatch()
}

Next Steps