Ethereum WebSocket Guide
WebSocket connections provide real-time, bidirectional communication with the Ethereum blockchain, enabling your applications to receive instant updates for new blocks, transactions, and smart contract events without polling.
Why Use WebSockets?
Advantages over HTTP Polling
- Real-time Updates: Instant notifications when events occur
- Lower Latency: No polling delays
- Reduced Bandwidth: Only receive data when it changes
- Better Performance: Persistent connection reduces overhead
- Event-Driven: React to blockchain events as they happen
Common Use Cases
- Trading Applications: Real-time price feeds and order book updates
- DeFi Dashboards: Live liquidity and yield changes
- NFT Marketplaces: Instant sale and listing notifications
- Wallet Applications: Balance updates and transaction confirmations
- Analytics Platforms: Real-time blockchain metrics
Connection Setup
WebSocket Endpoints
wss://ethereum-mainnet.blockeden.xyz/<your-api-key>
wss://ethereum-sepolia.blockeden.xyz/<your-api-key>
wss://polygon-mainnet.blockeden.xyz/<your-api-key>
wss://arbitrum-mainnet.blockeden.xyz/<your-api-key>
Basic Connection
JavaScript (Browser/Node.js)
const ws = new WebSocket('wss://ethereum-mainnet.blockeden.xyz/<your-api-key>');
ws.onopen = function(event) {
console.log('WebSocket connected');
};
ws.onmessage = function(event) {
const data = JSON.parse(event.data);
console.log('Received:', data);
};
ws.onerror = function(error) {
console.error('WebSocket error:', error);
};
ws.onclose = function(event) {
console.log('WebSocket closed:', event.code, event.reason);
};
Python
import asyncio
import websockets
import json
async def connect():
uri = "wss://ethereum-mainnet.blockeden.xyz/<your-api-key>"
async with websockets.connect(uri) as websocket:
print("WebSocket connected")
# Subscribe to new heads
subscribe_msg = {
"jsonrpc": "2.0",
"method": "eth_subscribe",
"params": ["newHeads"],
"id": 1
}
await websocket.send(json.dumps(subscribe_msg))
# Listen for messages
async for message in websocket:
data = json.loads(message)
print(f"Received: {data}")
# Run the connection
asyncio.run(connect())
Go
package main
import (
"encoding/json"
"fmt"
"log"
"github.com/gorilla/websocket"
)
type SubscriptionRequest struct {
JSONRPC string `json:"jsonrpc"`
Method string `json:"method"`
Params []string `json:"params"`
ID int `json:"id"`
}
func main() {
url := "wss://ethereum-mainnet.blockeden.xyz/<your-api-key>"
conn, _, err := websocket.DefaultDialer.Dial(url, nil)
if err != nil {
log.Fatal("dial:", err)
}
defer conn.Close()
// Subscribe to new heads
req := SubscriptionRequest{
JSONRPC: "2.0",
Method: "eth_subscribe",
Params: []string{"newHeads"},
ID: 1,
}
if err := conn.WriteJSON(req); err != nil {
log.Fatal("write:", err)
}
// Read messages
for {
var msg map[string]interface{}
if err := conn.ReadJSON(&msg); err != nil {
log.Fatal("read:", err)
}
fmt.Printf("Received: %+v\n", msg)
}
}
Subscription Types
1. New Block Headers (newHeads)
Subscribe to new block headers as they're mined.
const subscribeToNewHeads = () => {
const subscription = {
jsonrpc: '2.0',
method: 'eth_subscribe',
params: ['newHeads'],
id: 1
};
ws.send(JSON.stringify(subscription));
};
// Handle new block headers
ws.onmessage = function(event) {
const data = JSON.parse(event.data);
if (data.method === 'eth_subscription' && data.params.subscription) {
const blockHeader = data.params.result;
console.log('New block:', {
number: parseInt(blockHeader.number, 16),
hash: blockHeader.hash,
timestamp: parseInt(blockHeader.timestamp, 16),
gasUsed: parseInt(blockHeader.gasUsed, 16),
gasLimit: parseInt(blockHeader.gasLimit, 16)
});
}
};
2. Pending Transactions (newPendingTransactions)
Subscribe to new pending transactions in the mempool.
const subscribeToPendingTransactions = () => {
const subscription = {
jsonrpc: '2.0',
method: 'eth_subscribe',
params: ['newPendingTransactions'],
id: 2
};
ws.send(JSON.stringify(subscription));
};
// Handle pending transactions
ws.onmessage = function(event) {
const data = JSON.parse(event.data);
if (data.method === 'eth_subscription' && data.params.subscription) {
const txHash = data.params.result;
console.log('New pending transaction:', txHash);
// Optionally fetch full transaction details
fetchTransactionDetails(txHash);
}
};
3. Smart Contract Events (logs)
Subscribe to specific smart contract events using log filters.
const subscribeToContractEvents = (contractAddress, topics) => {
const subscription = {
jsonrpc: '2.0',
method: 'eth_subscribe',
params: [
'logs',
{
address: contractAddress,
topics: topics
}
],
id: 3
};
ws.send(JSON.stringify(subscription));
};
// Subscribe to ERC-20 Transfer events
const ERC20_TRANSFER_TOPIC = '0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef';
subscribeToContractEvents('0xA0b86a33E6c0e4A2a2a5FB1C6A6D6a30BF8b6B3a', [ERC20_TRANSFER_TOPIC]);
// Handle contract events
ws.onmessage = function(event) {
const data = JSON.parse(event.data);
if (data.method === 'eth_subscription' && data.params.subscription) {
const log = data.params.result;
console.log('Contract event:', {
address: log.address,
topics: log.topics,
data: log.data,
blockNumber: parseInt(log.blockNumber, 16),
transactionHash: log.transactionHash
});
}
};
4. Synchronized Blocks (syncing)
Subscribe to synchronization status updates.
const subscribeToSyncing = () => {
const subscription = {
jsonrpc: '2.0',
method: 'eth_subscribe',
params: ['syncing'],
id: 4
};
ws.send(JSON.stringify(subscription));
};
// Handle sync status
ws.onmessage = function(event) {
const data = JSON.parse(event.data);
if (data.method === 'eth_subscription' && data.params.subscription) {
const syncStatus = data.params.result;
if (syncStatus === false) {
console.log('Node is fully synchronized');
} else {
console.log('Sync status:', {
startingBlock: parseInt(syncStatus.startingBlock, 16),
currentBlock: parseInt(syncStatus.currentBlock, 16),
highestBlock: parseInt(syncStatus.highestBlock, 16)
});
}
}
};
Advanced Usage Patterns
Managing Multiple Subscriptions
class EthereumWebSocketManager {
constructor(endpoint) {
this.endpoint = endpoint;
this.ws = null;
this.subscriptions = new Map();
this.messageHandlers = new Map();
this.reconnectAttempts = 0;
this.maxReconnectAttempts = 5;
}
connect() {
return new Promise((resolve, reject) => {
this.ws = new WebSocket(this.endpoint);
this.ws.onopen = () => {
console.log('WebSocket connected');
this.reconnectAttempts = 0;
resolve();
};
this.ws.onmessage = (event) => {
this.handleMessage(JSON.parse(event.data));
};
this.ws.onerror = (error) => {
console.error('WebSocket error:', error);
reject(error);
};
this.ws.onclose = (event) => {
console.log('WebSocket closed:', event.code, event.reason);
this.handleReconnection();
};
});
}
subscribe(type, params, handler) {
const id = Date.now();
const subscription = {
jsonrpc: '2.0',
method: 'eth_subscribe',
params: [type, ...(params || [])],
id: id
};
this.ws.send(JSON.stringify(subscription));
this.messageHandlers.set(id, handler);
return id;
}
unsubscribe(subscriptionId) {
const unsubscribe = {
jsonrpc: '2.0',
method: 'eth_unsubscribe',
params: [subscriptionId],
id: Date.now()
};
this.ws.send(JSON.stringify(unsubscribe));
this.subscriptions.delete(subscriptionId);
}
handleMessage(data) {
if (data.method === 'eth_subscription') {
// Handle subscription data
const subscriptionId = data.params.subscription;
const handler = this.subscriptions.get(subscriptionId);
if (handler) {
handler(data.params.result);
}
} else if (data.id && this.messageHandlers.has(data.id)) {
// Handle subscription response
const handler = this.messageHandlers.get(data.id);
this.messageHandlers.delete(data.id);
if (data.result) {
this.subscriptions.set(data.result, handler);
}
}
}
handleReconnection() {
if (this.reconnectAttempts < this.maxReconnectAttempts) {
this.reconnectAttempts++;
const delay = Math.pow(2, this.reconnectAttempts) * 1000;
setTimeout(() => {
console.log(`Reconnecting... Attempt ${this.reconnectAttempts}`);
this.connect().then(() => {
// Resubscribe to all previous subscriptions
this.resubscribeAll();
});
}, delay);
}
}
resubscribeAll() {
// Implementation depends on your subscription tracking
console.log('Resubscribing to all previous subscriptions');
}
}
// Usage
const wsManager = new EthereumWebSocketManager('wss://ethereum-mainnet.blockeden.xyz/<your-api-key>');
wsManager.connect().then(() => {
// Subscribe to new blocks
wsManager.subscribe('newHeads', null, (blockHeader) => {
console.log('New block:', blockHeader);
});
// Subscribe to USDC transfers
wsManager.subscribe('logs', [{
address: '0xA0b86a33E6c0e4A2a2a5FB1C6A6D6a30BF8b6B3a',
topics: ['0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef']
}], (log) => {
console.log('USDC transfer:', log);
});
});
Real-time Trading Dashboard
class TradingDashboard {
constructor(wsEndpoint) {
this.wsManager = new EthereumWebSocketManager(wsEndpoint);
this.priceFeeds = new Map();
this.orderBookUpdates = new Map();
}
async start() {
await this.wsManager.connect();
// Subscribe to DEX events
this.subscribeToUniswapV3();
this.subscribeToSushiSwap();
this.subscribeToNewBlocks();
}
subscribeToUniswapV3() {
// Uniswap V3 Swap events
const SWAP_TOPIC = '0xc42079f94a6350d7e6235f29174924f928cc2ac818eb64fed8004e115fbcca67';
this.wsManager.subscribe('logs', [{
address: '0x1F98431c8aD98523631AE4a59f267346ea31F984', // Uniswap V3 Factory
topics: [SWAP_TOPIC]
}], (log) => {
this.handleUniswapSwap(log);
});
}
subscribeToSushiSwap() {
// SushiSwap Swap events
const SWAP_TOPIC = '0xd78ad95fa46c994b6551d0da85fc275fe613ce37657fb8d5e3d130840159d822';
this.wsManager.subscribe('logs', [{
topics: [SWAP_TOPIC]
}], (log) => {
this.handleSushiSwap(log);
});
}
subscribeToNewBlocks() {
this.wsManager.subscribe('newHeads', null, (blockHeader) => {
this.updateBlockInfo(blockHeader);
});
}
handleUniswapSwap(log) {
// Parse Uniswap V3 swap data
const poolAddress = log.address;
const data = this.parseSwapData(log.data);
this.updatePriceFeed('uniswap', poolAddress, data);
}
handleSushiSwap(log) {
// Parse SushiSwap swap data
const poolAddress = log.address;
const data = this.parseSwapData(log.data);
this.updatePriceFeed('sushiswap', poolAddress, data);
}
updatePriceFeed(exchange, pool, swapData) {
const key = `${exchange}:${pool}`;
const currentFeed = this.priceFeeds.get(key) || { prices: [], volume: 0 };
currentFeed.prices.push({
price: swapData.price,
timestamp: Date.now(),
volume: swapData.amount
});
// Keep only last 100 prices
if (currentFeed.prices.length > 100) {
currentFeed.prices.shift();
}
currentFeed.volume += swapData.amount;
this.priceFeeds.set(key, currentFeed);
// Emit update to UI
this.emitPriceUpdate(exchange, pool, currentFeed);
}
parseSwapData(data) {
// Implementation depends on the specific DEX
// This is a simplified example
return {
price: parseFloat(data.slice(0, 66)),
amount: parseFloat(data.slice(66, 130))
};
}
emitPriceUpdate(exchange, pool, feed) {
// Send to frontend/dashboard
console.log(`Price update for ${exchange}:${pool}`, feed);
}
updateBlockInfo(blockHeader) {
const blockInfo = {
number: parseInt(blockHeader.number, 16),
timestamp: parseInt(blockHeader.timestamp, 16),
gasUsed: parseInt(blockHeader.gasUsed, 16),
gasLimit: parseInt(blockHeader.gasLimit, 16)
};
console.log('New block:', blockInfo);
}
}
// Usage
const dashboard = new TradingDashboard('wss://ethereum-mainnet.blockeden.xyz/<your-api-key>');
dashboard.start();
NFT Marketplace Monitor
class NFTMarketplaceMonitor {
constructor(wsEndpoint) {
this.wsManager = new EthereumWebSocketManager(wsEndpoint);
this.marketplaces = {
opensea: '0x00000000006c3852cbEf3e08E8dF289169EdE581',
looksrare: '0x59728544B08AB483533076417FbBB2fD0B17CE3a',
x2y2: '0x74312363e45DCaBA76c59ec49a7Aa8A65a67EeD3'
};
}
async start() {
await this.wsManager.connect();
Object.entries(this.marketplaces).forEach(([name, address]) => {
this.subscribeToMarketplace(name, address);
});
}
subscribeToMarketplace(name, address) {
// Subscribe to all events from marketplace contract
this.wsManager.subscribe('logs', [{
address: address
}], (log) => {
this.handleMarketplaceEvent(name, log);
});
}
handleMarketplaceEvent(marketplace, log) {
const eventSignature = log.topics[0];
switch (eventSignature) {
case '0x9d9af8e38d66c62e2c12f0225249fd9d721c54b83f48d9352c97c6cacdcb6f31': // Sale
this.handleSale(marketplace, log);
break;
case '0x3cbb63f144840e5b1b0a38a7c19211d2e89de4d7c5faf8b2d3c1776c302d1d33': // Listing
this.handleListing(marketplace, log);
break;
default:
console.log(`Unknown event from ${marketplace}:`, eventSignature);
}
}
handleSale(marketplace, log) {
// Parse sale data
const saleData = this.parseSaleData(log);
console.log(`🎉 NFT Sale on ${marketplace}:`, {
collection: saleData.collection,
tokenId: saleData.tokenId,
price: saleData.price,
buyer: saleData.buyer,
seller: saleData.seller,
txHash: log.transactionHash
});
// Send notification to users
this.sendSaleNotification(marketplace, saleData);
}
handleListing(marketplace, log) {
// Parse listing data
const listingData = this.parseListingData(log);
console.log(`📝 New Listing on ${marketplace}:`, {
collection: listingData.collection,
tokenId: listingData.tokenId,
price: listingData.price,
seller: listingData.seller
});
}
parseSaleData(log) {
// Implementation depends on marketplace contract
return {
collection: log.topics[1],
tokenId: parseInt(log.topics[2], 16),
price: parseInt(log.data.slice(0, 66), 16),
buyer: `0x${log.data.slice(66, 106)}`,
seller: `0x${log.data.slice(106, 146)}`
};
}
parseListingData(log) {
// Implementation depends on marketplace contract
return {
collection: log.topics[1],
tokenId: parseInt(log.topics[2], 16),
price: parseInt(log.data.slice(0, 66), 16),
seller: `0x${log.data.slice(66, 106)}`
};
}
sendSaleNotification(marketplace, saleData) {
// Send to Discord, Telegram, email, etc.
console.log(`Sending notification for ${marketplace} sale`);
}
}
// Usage
const nftMonitor = new NFTMarketplaceMonitor('wss://ethereum-mainnet.blockeden.xyz/<your-api-key>');
nftMonitor.start();
Error Handling & Reconnection
Robust Error Handling
class RobustWebSocketClient {
constructor(endpoint, options = {}) {
this.endpoint = endpoint;
this.options = {
reconnectInterval: 5000,
maxReconnectAttempts: 10,
heartbeatInterval: 30000,
...options
};
this.ws = null;
this.reconnectAttempts = 0;
this.heartbeatTimer = null;
this.subscriptions = new Map();
this.isConnected = false;
}
connect() {
return new Promise((resolve, reject) => {
try {
this.ws = new WebSocket(this.endpoint);
this.ws.onopen = () => {
console.log('WebSocket connected');
this.isConnected = true;
this.reconnectAttempts = 0;
this.startHeartbeat();
this.resubscribeAll();
resolve();
};
this.ws.onmessage = (event) => {
try {
const data = JSON.parse(event.data);
this.handleMessage(data);
} catch (error) {
console.error('Error parsing message:', error);
}
};
this.ws.onerror = (error) => {
console.error('WebSocket error:', error);
this.isConnected = false;
};
this.ws.onclose = (event) => {
console.log('WebSocket closed:', event.code, event.reason);
this.isConnected = false;
this.stopHeartbeat();
if (event.code !== 1000) { // Not a normal closure
this.handleReconnection();
}
};
} catch (error) {
reject(error);
}
});
}
startHeartbeat() {
this.heartbeatTimer = setInterval(() => {
if (this.isConnected && this.ws.readyState === WebSocket.OPEN) {
this.ping();
}
}, this.options.heartbeatInterval);
}
stopHeartbeat() {
if (this.heartbeatTimer) {
clearInterval(this.heartbeatTimer);
this.heartbeatTimer = null;
}
}
ping() {
// Send a simple request to check connection
const ping = {
jsonrpc: '2.0',
method: 'net_version',
params: [],
id: 'ping'
};
this.send(ping);
}
send(data) {
if (this.isConnected && this.ws.readyState === WebSocket.OPEN) {
this.ws.send(JSON.stringify(data));
return true;
} else {
console.warn('WebSocket not connected, queueing message');
return false;
}
}
handleReconnection() {
if (this.reconnectAttempts < this.options.maxReconnectAttempts) {
this.reconnectAttempts++;
const delay = Math.min(
this.options.reconnectInterval * Math.pow(2, this.reconnectAttempts - 1),
30000 // Max 30 seconds
);
console.log(`Reconnecting in ${delay}ms... Attempt ${this.reconnectAttempts}`);
setTimeout(() => {
this.connect().catch((error) => {
console.error('Reconnection failed:', error);
});
}, delay);
} else {
console.error('Max reconnection attempts reached');
}
}
resubscribeAll() {
// Resubscribe to all active subscriptions
this.subscriptions.forEach((handler, subscription) => {
// Implementation depends on your subscription tracking
});
}
disconnect() {
this.stopHeartbeat();
if (this.ws) {
this.ws.close(1000, 'Client disconnecting');
}
}
}
Performance Optimization
Efficient Message Processing
class OptimizedWebSocketProcessor {
constructor(endpoint) {
this.ws = new RobustWebSocketClient(endpoint);
this.messageQueue = [];
this.processing = false;
this.batchSize = 100;
this.batchTimeout = 100; // ms
}
async start() {
await this.ws.connect();
this.ws.onMessage = (data) => {
this.queueMessage(data);
};
this.startBatchProcessor();
}
queueMessage(message) {
this.messageQueue.push({
message,
timestamp: Date.now()
});
// Process immediately if queue is full
if (this.messageQueue.length >= this.batchSize) {
this.processBatch();
}
}
startBatchProcessor() {
setInterval(() => {
if (this.messageQueue.length > 0 && !this.processing) {
this.processBatch();
}
}, this.batchTimeout);
}
async processBatch() {
if (this.processing || this.messageQueue.length === 0) {
return;
}
this.processing = true;
const batch = this.messageQueue.splice(0, this.batchSize);
try {
await this.processBatchAsync(batch);
} catch (error) {
console.error('Error processing batch:', error);
} finally {
this.processing = false;
}
}
async processBatchAsync(batch) {
// Process messages in parallel where possible
const promises = batch.map(item => this.processMessage(item.message));
await Promise.allSettled(promises);
}
async processMessage(message) {
// Your message processing logic
if (message.method === 'eth_subscription') {
const subscription = message.params.subscription;
const result = message.params.result;
// Handle based on subscription type
this.handleSubscriptionMessage(subscription, result);
}
}
handleSubscriptionMessage(subscription, result) {
// Route to appropriate handler
console.log(`Processing subscription ${subscription}:`, result);
}
}
Testing WebSocket Connections
Unit Testing
// Mock WebSocket for testing
class MockWebSocket {
constructor(url) {
this.url = url;
this.readyState = WebSocket.CONNECTING;
this.messages = [];
// Simulate connection
setTimeout(() => {
this.readyState = WebSocket.OPEN;
if (this.onopen) this.onopen();
}, 100);
}
send(data) {
this.messages.push(JSON.parse(data));
}
close() {
this.readyState = WebSocket.CLOSED;
if (this.onclose) this.onclose({ code: 1000, reason: 'Normal closure' });
}
simulateMessage(data) {
if (this.onmessage) {
this.onmessage({ data: JSON.stringify(data) });
}
}
}
// Test example
describe('WebSocket Manager', () => {
let wsManager;
let mockWs;
beforeEach(() => {
global.WebSocket = MockWebSocket;
wsManager = new EthereumWebSocketManager('wss://test.example.com');
});
test('should connect and subscribe', async () => {
await wsManager.connect();
const handler = jest.fn();
wsManager.subscribe('newHeads', null, handler);
// Simulate subscription confirmation
mockWs.simulateMessage({
id: 1,
result: 'subscription-id-123'
});
// Simulate new block
mockWs.simulateMessage({
method: 'eth_subscription',
params: {
subscription: 'subscription-id-123',
result: { number: '0x123', hash: '0xabc...' }
}
});
expect(handler).toHaveBeenCalledWith({ number: '0x123', hash: '0xabc...' });
});
});
Best Practices
1. Connection Management
- Always implement reconnection logic
- Use heartbeat/ping to detect connection issues
- Handle network interruptions gracefully
- Limit concurrent connections
2. Resource Management
- Unsubscribe from unused subscriptions
- Implement proper cleanup on disconnect
- Monitor memory usage for long-running connections
- Use connection pooling for multiple subscriptions
3. Error Handling
- Implement exponential backoff for reconnections
- Log errors for debugging
- Handle malformed messages gracefully
- Set connection timeouts
4. Performance
- Batch process messages when possible
- Use appropriate buffer sizes
- Implement message filtering client-side
- Monitor subscription overhead
5. Security
- Validate all incoming data
- Use secure WebSocket connections (wss://)
- Implement rate limiting
- Keep API keys secure
Next Steps
- Learn about Web3.js Integration for JavaScript development
- Explore Ethers.js Integration for modern web development
- Check out smart contract interaction patterns for advanced use cases
- Follow JSON-RPC API Reference for low-level operations