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...' });
  });
});