Event Monitoring
Subscribing to and processing blockchain events
Event monitoring enables reactive applications. Subscribe to real-time updates for transactions, transfers, contract events. Selendra fast block times (~1 second) provide quick event delivery.
Event Monitoring Methods
| Method | Use Case | Latency | Resource Usage |
|---|---|---|---|
| WebSocket | Real-time updates, user actions | <1 second | Low (persistent connection) |
| HTTP Polling | Periodic checks, background sync | 5-30 seconds | Medium (repeated requests) |
| Historical Query | Past events, analysis, backfill | Instant | High (blockchain scan) |
| Indexer | Complex queries, aggregations | <2 seconds | Lowest (pre-indexed) |
WebSocket Subscriptions
Basic Event Listening
import { Web3 } from 'web3';
const web3 = new Web3('wss://rpc.selendra.org');
// Subscribe to new blocks
const subscription = await web3.eth.subscribe('newBlockHeaders');
subscription.on('data', (block) => {
console.log('New block:', block.number);
});
subscription.on('error', console.error);
Contract Events
const contract = new web3.eth.Contract(abi, contractAddress);
// Subscribe to Transfer events
const subscription = contract.events.Transfer({
filter: { from: userAddress }
});
subscription.on('data', (event) => {
console.log('Transfer:', event.returnValues);
});
subscription.on('error', console.error);
Multiple Events
// Monitor all events
const allEvents = contract.events.allEvents();
allEvents.on('data', (event) => {
switch (event.event) {
case 'Transfer':
handleTransfer(event.returnValues);
break;
case 'Approval':
handleApproval(event.returnValues);
break;
case 'Burn':
handleBurn(event.returnValues);
break;
}
});
Filtered Subscription
// Only transfers to specific address
const filtered = contract.events.Transfer({
filter: {
to: recipientAddress
}
});
filtered.on('data', (event) => {
notifyUser({
title: 'Tokens Received',
amount: web3.utils.fromWei(event.returnValues.value, 'ether'),
from: event.returnValues.from
});
});
Historical Event Queries
Fetch Past Events
// Get all Transfer events in last 1000 blocks
const currentBlock = await web3.eth.getBlockNumber();
const events = await contract.getPastEvents('Transfer', {
fromBlock: currentBlock - 1000,
toBlock: 'latest'
});
console.log(`Found ${events.length} transfers`);
Paginated Query
async function fetchEventsPaginated(fromBlock, toBlock, batchSize = 1000) {
const allEvents = [];
for (let start = fromBlock; start <= toBlock; start += batchSize) {
const end = Math.min(start + batchSize - 1, toBlock);
const batch = await contract.getPastEvents('Transfer', {
fromBlock: start,
toBlock: end
});
allEvents.push(...batch);
}
return allEvents;
}
Filtered Query
// Get specific user's transfers
const userTransfers = await contract.getPastEvents('Transfer', {
filter: {
from: userAddress
},
fromBlock: 0,
toBlock: 'latest'
});
// Calculate total sent
const totalSent = userTransfers.reduce((sum, event) => {
return sum + BigInt(event.returnValues.value);
}, 0n);
React Integration
Custom Event Hook
import { useState, useEffect } from 'react';
function useContractEvent(contract, eventName, filter = {}) {
const [events, setEvents] = useState([]);
useEffect(() => {
const subscription = contract.events[eventName]({ filter });
subscription.on('data', (event) => {
setEvents(prev => [event, ...prev]);
});
return () => subscription.unsubscribe();
}, [contract, eventName, JSON.stringify(filter)]);
return events;
}
// Usage
function TransferMonitor({ userAddress }) {
const transfers = useContractEvent(contract, 'Transfer', {
to: userAddress
});
return (
<div>
<h3>Recent Transfers ({transfers.length})</h3>
{transfers.map(event => (
<div key={event.transactionHash}>
{web3.utils.fromWei(event.returnValues.value, 'ether')} SEL
from {event.returnValues.from}
</div>
))}
</div>
);
}
Notification Component
function EventNotifications({ contract }) {
const [notifications, setNotifications] = useState([]);
useEffect(() => {
const sub = contract.events.allEvents();
sub.on('data', (event) => {
setNotifications(prev => [{
id: event.transactionHash,
type: event.event,
data: event.returnValues,
timestamp: Date.now()
}, ...prev].slice(0, 10)); // Keep last 10
});
return () => sub.unsubscribe();
}, [contract]);
return (
<div className="notifications">
{notifications.map(notif => (
<div key={notif.id} className="notification">
{notif.type}: {JSON.stringify(notif.data)}
</div>
))}
</div>
);
}
Event Processing
Aggregation
class EventAggregator {
constructor(contract) {
this.contract = contract;
this.stats = {
totalTransfers: 0,
totalVolume: 0n
};
}
async start() {
const sub = this.contract.events.Transfer();
sub.on('data', (event) => {
this.stats.totalTransfers++;
this.stats.totalVolume += BigInt(event.returnValues.value);
});
}
getStats() {
return {
transfers: this.stats.totalTransfers,
volume: web3.utils.fromWei(this.stats.totalVolume.toString(), 'ether')
};
}
}
Debouncing
function debounce(fn, delay) {
let timeout;
return (...args) => {
clearTimeout(timeout);
timeout = setTimeout(() => fn(...args), delay);
};
}
const debouncedHandler = debounce((event) => {
updateUI(event);
}, 1000);
contract.events.Transfer().on('data', debouncedHandler);
Block Monitoring
New Blocks
const blockSub = await web3.eth.subscribe('newBlockHeaders');
blockSub.on('data', async (blockHeader) => {
console.log(`Block #${blockHeader.number}`);
console.log(`Transactions: ${blockHeader.transactions?.length || 0}`);
// Fetch full block if needed
const fullBlock = await web3.eth.getBlock(blockHeader.number, true);
});
Transaction Receipts
async function monitorUserTransactions(userAddress) {
const blockSub = await web3.eth.subscribe('newBlockHeaders');
blockSub.on('data', async (block) => {
const fullBlock = await web3.eth.getBlock(block.number, true);
for (const tx of fullBlock.transactions) {
if (tx.from === userAddress || tx.to === userAddress) {
const receipt = await web3.eth.getTransactionReceipt(tx.hash);
console.log('User transaction:', {
hash: tx.hash,
status: receipt.status ? 'success' : 'failed'
});
}
}
});
}
Error Handling
class ConnectionManager {
constructor(wsUrl) {
this.wsUrl = wsUrl;
this.reconnecting = false;
}
async connect() {
try {
this.web3 = new Web3(this.wsUrl);
this.setupSubscriptions();
} catch (error) {
console.error('Connection failed:', error);
this.reconnect();
}
}
setupSubscriptions() {
const sub = this.web3.eth.subscribe('newBlockHeaders');
sub.on('error', () => {
if (!this.reconnecting) {
this.reconnect();
}
});
sub.on('data', (block) => {
this.reconnecting = false;
});
}
reconnect() {
if (this.reconnecting) return;
this.reconnecting = true;
setTimeout(() => this.connect(), 5000);
}
}
Performance Optimization
Event Caching
class EventCache {
constructor(maxSize = 1000) {
this.cache = new Map();
this.maxSize = maxSize;
}
add(event) {
this.cache.set(event.transactionHash, event);
if (this.cache.size > this.maxSize) {
const firstKey = this.cache.keys().next().value;
this.cache.delete(firstKey);
}
}
get(txHash) {
return this.cache.get(txHash);
}
}
Batch Processing
class EventBatcher {
constructor(processFn, delay = 1000) {
this.batch = [];
this.processFn = processFn;
this.delay = delay;
}
add(event) {
this.batch.push(event);
if (!this.timer) {
this.timer = setTimeout(() => this.flush(), this.delay);
}
}
flush() {
if (this.batch.length > 0) {
this.processFn(this.batch);
this.batch = [];
}
this.timer = null;
}
}
StadiumX Implementation
StadiumX monitors 4 key events:
- TicketPurchased: Update seat map, confirm payment
- TicketTransferred: Update ownership, notify recipient
- EventCancelled: Trigger refunds, update status
- RefundProcessed: Confirm refund completion
Lessons Learned
- Reconnection: Implement automatic reconnect with exponential backoff
- Deduplication: Cache processed events to prevent duplicate handling
- Rate Limiting: Batch updates to avoid overwhelming UI
- Error Recovery: Gracefully handle connection failures
Related Documentation
Transaction Handling | Using Selendra SDK
Community
Telegram: t.me/selendranetwork | X: @selendranetwork | GitHub: github.com/selendra/selendra
Contribute
Found an issue or want to contribute?
Help us improve this documentation by editing this page on GitHub.
