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
- API Methods - Core Shred API methods
- Quickstart - Build your first app