VeztaVezta
Architecture

Real-Time

Socket.IO architecture with Redis adapter

Vezta uses Socket.IO for real-time communication between the backend and all connected clients. The WebSocket server runs at the /ws namespace and supports 8 channels covering market data, user events, and platform signals.

Server Configuration

The MarketDataGateway is a NestJS WebSocket gateway decorated with @WebSocketGateway:

@WebSocketGateway({ cors: { origin: '*' }, namespace: '/ws' })
export class MarketDataGateway
  implements OnGatewayInit, OnGatewayConnection, OnGatewayDisconnect
{
  @WebSocketServer()
  server: Server;
}

Key details:

  • Namespace: /ws
  • CORS: Open to all origins (authentication is handled via JWT in the handshake)
  • Adapter: Redis adapter for horizontal scaling across multiple backend instances

Channels

ChannelAuth RequiredDescription
market:pricesNoReal-time YES/NO price updates for active markets
market:orderbookNoOrder book depth snapshots
market:tradesNoLive trade feed from exchanges
user:ordersYesOrder status changes (fills, cancellations)
user:notificationsYesIn-app notification delivery
user:portfolioYesPortfolio value and position updates
user:copy-tradeYesCopy trade execution events
monitor:signalsNoNews signals and whale activity alerts

Subscribe / Unsubscribe

Clients join channels by sending a subscribe event with the channel name. The gateway adds the client to a Socket.IO room matching the channel:

// Subscribe
socket.emit('subscribe', { channel: 'market:prices' });

// Server response
// { event: 'subscribed', data: { channel: 'market:prices' } }

// Unsubscribe
socket.emit('unsubscribe', { channel: 'market:prices' });

// Server response
// { event: 'unsubscribed', data: { channel: 'market:prices' } }

Under the hood, subscribe calls client.join(data.channel) and unsubscribe calls client.leave(data.channel).

Broadcast Pattern

Backend services broadcast data to channels using the gateway's broadcastToChannel method:

broadcastToChannel(channel: string, event: string, data: any) {
  this.server.to(channel).emit('data', { channel, data });
}

All broadcasts emit a consistent 'data' event with a { channel, data } payload. The client routes messages by inspecting the channel field:

socket.on('data', (message) => {
  switch (message.channel) {
    case 'market:prices':
      handlePriceUpdate(message.data);
      break;
    case 'user:orders':
      handleOrderUpdate(message.data);
      break;
    // ...
  }
});

Authentication

The web client connects with JWT authentication in the Socket.IO handshake:

import { io } from 'socket.io-client';

const socket = io('wss://backend.vezta.io/ws', {
  transports: ['websocket'],
  auth: {
    token: accessToken,
  },
});

Public channels (market:*, monitor:signals) do not require authentication. User-specific channels (user:*) require a valid JWT.

Client Integration

Web (vezta-fe)

The WsProvider wraps Socket.IO in Jotai atoms for reactive state management:

  • Connects when the user is authenticated, disconnects on logout
  • WebSocket-only transport with exponential backoff reconnection (1s initial, 30s max)
  • Global subscriptions: user:orders, user:notifications, monitor:signals
  • Per-market subscriptions via useMarketSubscription(marketId) hook for prices, orderbook, and trades
  • Connection status tracked in wsStatusAtom

Mobile (vezta-mobile)

The mobile WebSocket client is adapted from the web version with additional AppState handling:

  • Disconnects when the app goes to background
  • Reconnects when the app returns to foreground
  • Same Jotai atom pattern for reactive state

Redis Adapter

The Socket.IO server uses the Redis adapter to synchronize events across multiple Node.js processes. When a service broadcasts to a channel, Redis pub/sub ensures the message reaches all connected clients regardless of which process they are connected to.

This is essential for horizontal scaling -- multiple backend instances can run behind a load balancer while maintaining consistent real-time delivery.

On this page