Saltar al contenido principal

Sei WebSocket Guide

WebSocket connections provide real-time access to Sei blockchain data, enabling your applications to receive instant updates for new blocks, transactions, and smart contract events without polling.

Why Use WebSockets with Sei?

Advantages

  • Sub-second Updates: Get notified of events as they happen (~400ms block time)
  • Lower Latency: No polling delays
  • Reduced Bandwidth: Only receive data when it changes
  • Real-time Trading: Perfect for DeFi applications needing instant updates
  • Event-Driven Architecture: React to blockchain events immediately

Common Use Cases

  • Trading Bots: Instant price and liquidity updates
  • Portfolio Trackers: Real-time balance changes
  • DeFi Dashboards: Live yield and farming data
  • Gaming Applications: Immediate game state updates
  • Analytics Platforms: Real-time blockchain metrics

WebSocket Endpoints

wss://api.blockeden.xyz/sei/<your-api-key>

EVM WebSocket Subscriptions

Sei's EVM layer supports Ethereum-compatible WebSocket subscriptions.

Basic Connection

const ws = new WebSocket('wss://api.blockeden.xyz/sei/<your-api-key>');

ws.onopen = function(event) {
console.log('Connected to Sei WebSocket');
};

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

Subscribe to New Blocks

Get notified of new blocks as they're mined (~400ms on Sei).

function 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') {
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),
baseFeePerGas: blockHeader.baseFeePerGas ? parseInt(blockHeader.baseFeePerGas, 16) : null
});
}
};

// Start subscription
subscribeToNewHeads();

Subscribe to Pending Transactions

Monitor the mempool for pending transactions.

function 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);
}
};

Subscribe to Smart Contract Events

Monitor specific smart contract events in real-time.

function 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('0xContractAddress...', [ERC20_TRANSFER_TOPIC]);

// Handle contract events
ws.onmessage = function(event) {
const data = JSON.parse(event.data);

if (data.method === 'eth_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
});
}
};

Tendermint WebSocket Subscriptions

Access Sei's consensus layer events through Tendermint WebSocket API.

Base URL

wss://api.blockeden.xyz/sei/<your-api-key>/websocket

Connection and Subscription

const tmWs = new WebSocket('wss://api.blockeden.xyz/sei/<your-api-key>/websocket');

tmWs.onopen = function() {
console.log('Connected to Tendermint WebSocket');

// Subscribe to new blocks
tmWs.send(JSON.stringify({
jsonrpc: '2.0',
method: 'subscribe',
params: ["tm.event='NewBlock'"],
id: 1
}));

// Subscribe to transactions
tmWs.send(JSON.stringify({
jsonrpc: '2.0',
method: 'subscribe',
params: ["tm.event='Tx'"],
id: 2
}));
};

tmWs.onmessage = function(event) {
const data = JSON.parse(event.data);

if (data.result && data.result.events) {
const eventType = data.result.events['tm.event'][0];

switch (eventType) {
case 'NewBlock':
handleNewBlock(data.result.data.value.block);
break;
case 'Tx':
handleTransaction(data.result.data.value.TxResult);
break;
}
}
};

function handleNewBlock(block) {
console.log('New Tendermint block:', {
height: block.header.height,
time: block.header.time,
proposer: block.header.proposer_address,
txCount: block.data.txs ? block.data.txs.length : 0
});
}

function handleTransaction(txResult) {
console.log('New transaction:', {
hash: txResult.tx,
height: txResult.height,
code: txResult.result.code,
gasUsed: txResult.result.gas_used
});
}

Available Tendermint Subscriptions

// New blocks
"tm.event='NewBlock'"

// New transactions
"tm.event='Tx'"

// Validator set updates
"tm.event='ValidatorSetUpdates'"

// New block evidence
"tm.event='Evidence'"

// Custom event filters
"tm.event='Tx' AND transfer.recipient='sei1abc...'"
"tm.event='Tx' AND message.module='bank'"

Advanced WebSocket Patterns

WebSocket Manager Class

class SeiWebSocketManager {
constructor(endpoint) {
this.endpoint = endpoint;
this.ws = null;
this.subscriptions = new Map();
this.messageHandlers = new Map();
this.reconnectAttempts = 0;
this.maxReconnectAttempts = 5;
this.reconnectDelay = 1000;
}

connect() {
return new Promise((resolve, reject) => {
this.ws = new WebSocket(this.endpoint);

this.ws.onopen = () => {
console.log('Sei WebSocket connected');
this.reconnectAttempts = 0;
this.resubscribeAll();
resolve();
};

this.ws.onmessage = (event) => {
this.handleMessage(JSON.parse(event.data));
};

this.ws.onerror = (error) => {
console.error('Sei WebSocket error:', error);
reject(error);
};

this.ws.onclose = (event) => {
console.log('Sei WebSocket closed:', event.code, event.reason);
this.handleReconnection();
};
});
}

subscribe(type, params, handler) {
const id = Date.now() + Math.random();
const subscription = {
jsonrpc: '2.0',
method: 'eth_subscribe',
params: [type, ...(params || [])],
id: id
};

this.ws.send(JSON.stringify(subscription));
this.messageHandlers.set(id, { handler, type, params });

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, type, params } = 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 = this.reconnectDelay * Math.pow(2, this.reconnectAttempts - 1);

setTimeout(() => {
console.log(`Reconnecting... Attempt ${this.reconnectAttempts}`);
this.connect().catch(() => {});
}, delay);
}
}

resubscribeAll() {
// Store subscription info to resubscribe after reconnection
// Implementation depends on your subscription tracking needs
}

disconnect() {
if (this.ws) {
this.ws.close(1000, 'Client disconnecting');
}
}
}

Real-time DeFi Dashboard

class SeiDeFiMonitor {
constructor(wsEndpoint) {
this.wsManager = new SeiWebSocketManager(wsEndpoint);
this.priceFeeds = new Map();
this.liquidityPools = new Map();
}

async start() {
await this.wsManager.connect();

// Monitor DEX events
this.subscribeToSeiDEX();
this.subscribeToLiquidityPools();
this.subscribeToNewBlocks();
}

subscribeToSeiDEX() {
// Monitor Sei-specific DEX events
const DEX_SWAP_TOPIC = '0x...'; // Sei DEX swap event signature

this.wsManager.subscribe('logs', [{
topics: [DEX_SWAP_TOPIC]
}], (log) => {
this.handleDEXSwap(log);
});
}

subscribeToLiquidityPools() {
// Monitor liquidity pool events
const POOL_EVENTS = [
'0x...', // Add liquidity
'0x...', // Remove liquidity
'0x...' // Sync event
];

POOL_EVENTS.forEach(topic => {
this.wsManager.subscribe('logs', [{
topics: [topic]
}], (log) => {
this.handlePoolEvent(log);
});
});
}

subscribeToNewBlocks() {
this.wsManager.subscribe('newHeads', null, (blockHeader) => {
this.updateBlockInfo(blockHeader);
});
}

handleDEXSwap(log) {
const swapData = this.parseSwapData(log);
console.log('DEX Swap detected:', {
pool: log.address,
tokenIn: swapData.tokenIn,
tokenOut: swapData.tokenOut,
amountIn: swapData.amountIn,
amountOut: swapData.amountOut,
price: swapData.price,
blockNumber: parseInt(log.blockNumber, 16)
});

this.updatePriceFeed(log.address, swapData);
}

handlePoolEvent(log) {
// Process liquidity pool events
const poolData = this.parsePoolData(log);
this.liquidityPools.set(log.address, poolData);

console.log('Pool update:', {
pool: log.address,
reserves: poolData.reserves,
totalSupply: poolData.totalSupply
});
}

updatePriceFeed(poolAddress, swapData) {
const currentFeed = this.priceFeeds.get(poolAddress) || { prices: [], volume24h: 0 };

currentFeed.prices.push({
price: swapData.price,
timestamp: Date.now(),
volume: swapData.amountIn
});

// Keep only last 1000 prices
if (currentFeed.prices.length > 1000) {
currentFeed.prices.shift();
}

// Calculate 24h volume
const now = Date.now();
const dayAgo = now - 24 * 60 * 60 * 1000;
currentFeed.volume24h = currentFeed.prices
.filter(p => p.timestamp > dayAgo)
.reduce((sum, p) => sum + parseFloat(p.volume), 0);

this.priceFeeds.set(poolAddress, currentFeed);

// Emit to frontend
this.emitPriceUpdate(poolAddress, currentFeed);
}

parseSwapData(log) {
// Parse swap event data based on contract ABI
// This is a simplified example
return {
tokenIn: log.topics[1],
tokenOut: log.topics[2],
amountIn: parseInt(log.data.slice(0, 66), 16),
amountOut: parseInt(log.data.slice(66, 130), 16),
price: parseFloat(log.data.slice(130, 194))
};
}

parsePoolData(log) {
// Parse pool event data
return {
reserves: [
parseInt(log.data.slice(0, 66), 16),
parseInt(log.data.slice(66, 130), 16)
],
totalSupply: parseInt(log.data.slice(130, 194), 16)
};
}

emitPriceUpdate(poolAddress, feed) {
// Send updates to frontend/dashboard
console.log(`Price update for pool ${poolAddress}:`, {
currentPrice: feed.prices[feed.prices.length - 1]?.price,
volume24h: feed.volume24h,
priceChange24h: this.calculatePriceChange(feed.prices)
});
}

calculatePriceChange(prices) {
if (prices.length < 2) return 0;

const latest = prices[prices.length - 1].price;
const dayAgo = Date.now() - 24 * 60 * 60 * 1000;
const dayAgoPrice = prices.find(p => p.timestamp <= dayAgo)?.price;

if (!dayAgoPrice) return 0;

return ((latest - dayAgoPrice) / dayAgoPrice) * 100;
}

updateBlockInfo(blockHeader) {
console.log('New block:', {
number: parseInt(blockHeader.number, 16),
timestamp: parseInt(blockHeader.timestamp, 16),
gasUsed: parseInt(blockHeader.gasUsed, 16),
gasLimit: parseInt(blockHeader.gasLimit, 16)
});
}
}

// Usage
const deFiMonitor = new SeiDeFiMonitor('wss://api.blockeden.xyz/sei/<your-api-key>');
deFiMonitor.start();

Error Handling & Best Practices

Robust Error Handling

class RobustSeiWebSocket {
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('Sei 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 WebSocket message:', error);
}
};

this.ws.onerror = (error) => {
console.error('Sei WebSocket error:', error);
this.isConnected = false;
};

this.ws.onclose = (event) => {
console.log('Sei 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: 'eth_chainId',
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, message queued');
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 OptimizedSeiWebSocket {
constructor(endpoint) {
this.ws = new RobustSeiWebSocket(endpoint);
this.messageQueue = [];
this.processing = false;
this.batchSize = 100;
this.batchTimeout = 50; // ms - faster for Sei's quick blocks
}

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;

this.handleSubscriptionMessage(subscription, result);
}
}
}

Next Steps

Resources