Ethereum: How to monitor and react to events reliably in JavaScript?
Reliably monitoring and reacting to blockchain events is crucial for maintaining data consistency and ensuring a smooth user experience, while building on Ethereum. Relying on a single node can be risky due to potential downtime or connectivity issues.
A redundant event listener addresses this by subscribing to multiple Ethereum nodes, ensuring that important events are not missed. Here are its core advantages:
- Increased reliability: By subscribing to multiple nodes, you reduce the risk of missing events due to node failures.
- Enhanced fault tolerance: The system remains functional even if some nodes experience issues.
- Improved performance: Lower latency and higher transaction capacity.
This guide will show you how to build a redundant event listener using Node.js, web3.js, and ethers.js on atop robust Chainstack infrastructure to ensure fault tolerance and reliability.
How to build a redundant Ethereum event listener in JavaScript?
This tutorial guides you through building a redundant Ethereum event listener using Node.js with web3.js and ethers.js libraries, leveraging global and regional Chainstack infrastructure. This setup ensures reliable and fault-tolerant monitoring of Wrapped Ether (WETH) transfer events.
Prerequisites
- Node.js: Install Node.js (version 18 or higher recommended).
- Chainstack account: Sign up on Chainstack, deploy nodes, and get their access credentials.
- Dependencies: Install required packages:
npm init -y npm install web3 ethers dotenv
- Environment variables: Create a
.env
file to store your WebSocket endpoints:
ENDPOINT_1=YOUR_CHAINSTACK_GLOBAL_NODE
ENDPOINT_2=YOUR_CHAINSTACK_REGIONAL_NODE
ENDPOINT_3=YOUR_CHAINSTACK_REGIONAL_NODE
How to set up the redundant event listener JavaScript code?
The following DApp ensures redundancy by establishing multiple WebSocket connections to Ethereum RPC nodes using our global and regional infrastructure. By subscribing to multiple nodes simultaneously, the DApp increases its chances of receiving events even if some nodes experience downtime or connectivity issues. Here’s how the logic works:
- Connect to WebSocket endpoints: Define an array of WebSocket endpoints from various Ethereum node providers.
- Set up an event filter: Create a filter object specifying the WETH contract address and the “Transfer” event signature.
- Implement unique event tracking: Initialize a Set data structure to track unique event identifiers and prevent duplicate event processing.
- Define the subscription function: Define a function,
subscribeToLogs
, that:- Takes an endpoint as input.
- Creates a new Web3 instance with that endpoint.
- Sets up a WebSocket subscription to listen for logs matching the defined filter.
- Handle events effectively:
- When a new event is received, check if the event identifier (transaction hash and log index for web3.js; transaction hash and block for ethers) has been seen before.
- If the event is new, log the event data to the console and add the event identifier to the Set to mark it as processed.
- Handle errors with ease: Log any subscription errors to the console.
By implementing this redundant event listener, the application ensures that critical events, such as WETH transfers, are not missed, even if node failures or connectivity issues occur. This increased reliability and fault tolerance are essential for applications that rely heavily on real-time monitoring and reacting to Ethereum events.
How to set up the redundant event listener script with Web3JS?
Create a file named index.js
and add the following code:
const { Web3 } = require("web3");
require('dotenv').config();
const endpoints = [
process.env.ENDPOINT_1,
process.env.ENDPOINT_2,
process.env.ENDPOINT_3,
];
const logsFilter = {
address: "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2", // WETH contract address
topics: [
"0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef",
],
};
const seenEvents = new Set();
async function subscribeToLogs(endpoint) {
const web3 = new Web3(endpoint);
try {
const subscription = await web3.eth.subscribe("logs", logsFilter);
console.log(`Subscription created with ID: ${subscription.id}`);
subscription.on("data", (log) => {
const eventId = `${log.transactionHash}-${log.logIndex}`;
if (!seenEvents.has(eventId)) {
seenEvents.add(eventId);
console.log(`Event received first from ${endpoint.slice(0, 33)}:`, log);
}
});
subscription.on("error", (error) => {
console.error(`Error when subscribing to logs from ${endpoint}:`, error);
});
} catch (error) {
console.error(`Error setting up subscription from ${endpoint}:`, error);
}
}
endpoints.forEach((endpoint) => {
subscribeToLogs(endpoint);
});
How to set up the redundant event listener script with ethersJS?
Alternatively, create a new file and add the following code:
const { ethers } = require("ethers");
require('dotenv').config();
const endpoints = [
process.env.ENDPOINT_1,
process.env.ENDPOINT_2,
process.env.ENDPOINT_3,
];
const contractAddress = "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2"; // WETH contract address
const contractABI = [
"event Transfer(address indexed from, address indexed to, uint amount)",
];
const seenEvents = new Set();
async function subscribeToEvents(endpoint) {
const provider = new ethers.WebSocketProvider(endpoint);
const contract = new ethers.Contract(contractAddress, contractABI, provider);
contract.on("Transfer", (from, to, amount, event) => {
const eventId = `${event.log.transactionHash}-${event.log.blockNumber}`;
if (!seenEvents.has(eventId)) {
seenEvents.add(eventId);
console.log(`Event received first from ${endpoint.slice(0, 33)}:`);
console.log(
`Transfer from ${from} to ${to} of ${ethers.formatEther(amount.toString())} ETH`
);
}
});
provider.on("error", (error) => {
console.error(`WebSocket error from ${endpoint}:`, error);
});
}
endpoints.forEach((endpoint) => {
subscribeToEvents(endpoint);
});
Bringing it all together
Building reliable and fault-tolerant systems is essential for Ethereum blockchain applications. Implementing a redundant event listener using Node.js with web3.js or ethers.js libraries ensures your application consistently receives important events, like token transfers, without interruptions.
This strategy reduces the risks of depending on a single node by subscribing to multiple nodes simultaneously. This redundancy ensures events are captured even if some nodes fail.
This tutorial offered a step-by-step guide to setting up a project, configuring environment variables, and creating a redundant event listener. With this setup, your Ethereum-based application will remain responsive, up-to-date, and provide a seamless user experience, even in the face of node failures or connectivity issues.
Power-boost your project on Chainstack
- Discover how you can save thousands in infra costs every month with our unbeatable pricing on the most complete Web3 development platform.
- Input your workload and see how affordable Chainstack is compared to other RPC providers.
- Connect to Ethereum, Solana, BNB Smart Chain, Polygon, Arbitrum, Base, Optimism, Avalanche, TON, Ronin, zkSync Era, Starknet, Scroll, Aptos, Fantom, Cronos, Gnosis Chain, Klaytn, Moonbeam, Celo, Aurora, Oasis Sapphire, Polygon zkEVM, Bitcoin, Tezos and Harmony mainnet or testnets through an interface designed to help you get the job done.
- To learn more about Chainstack, visit our Developer Portal or join our Discord server and Telegram group.
- Are you in need of testnet tokens? Request some from our faucets. Multi-chain faucet, Sepolia faucet, Holesky faucet, BNB faucet, zkSync faucet, Scroll faucet.
Have you already explored what you can achieve with Chainstack? Get started for free today.