OS Trading Engine
API Reference
WebSocket API
Example Implementations

WebSocket Examples

Complete code examples for integrating with the Nexgent WebSocket API.


Basic JavaScript Example

A minimal example showing connection, message handling, and ping/pong.

class NexgentWebSocket {
  constructor(apiUrl, token, agentId) {
    this.url = this.buildUrl(apiUrl, token, agentId);
    this.ws = null;
    this.positions = [];
    this.isConnected = false;
  }
 
  buildUrl(apiUrl, token, agentId) {
    const wsUrl = apiUrl.replace(/^http/, 'ws');
    return `${wsUrl}/ws?token=${encodeURIComponent(token)}&agentId=${encodeURIComponent(agentId)}`;
  }
 
  connect() {
    this.ws = new WebSocket(this.url);
 
    this.ws.onopen = () => {
      console.log('WebSocket opened, waiting for authentication...');
    };
 
    this.ws.onmessage = (event) => {
      const message = JSON.parse(event.data);
      this.handleMessage(message);
    };
 
    this.ws.onclose = (event) => {
      console.log('WebSocket closed:', event.code, event.reason);
      this.isConnected = false;
    };
 
    this.ws.onerror = (error) => {
      console.error('WebSocket error:', error);
    };
  }
 
  handleMessage(message) {
    switch (message.type) {
      case 'connected':
        console.log('Connected to agent:', message.data.agentId);
        this.isConnected = true;
        break;
 
      case 'initial_data':
        console.log('Received', message.data.positions.length, 'positions');
        this.positions = message.data.positions;
        this.onPositionsUpdate(this.positions);
        break;
 
      case 'position_update':
        this.handlePositionUpdate(message.data);
        break;
 
      case 'price_update_batch':
        this.handlePriceUpdates(message.data.updates);
        break;
 
      case 'ping':
        // Respond to server ping
        this.ws.send(JSON.stringify({
          type: 'pong',
          timestamp: new Date().toISOString()
        }));
        break;
 
      case 'error':
        console.error('Server error:', message.data.message);
        break;
    }
  }
 
  handlePositionUpdate(data) {
    const { eventType, position, positionId } = data;
 
    switch (eventType) {
      case 'position_created':
        this.positions.push(position);
        break;
 
      case 'position_updated':
        const updateIndex = this.positions.findIndex(p => p.id === position.id);
        if (updateIndex >= 0) {
          this.positions[updateIndex] = { ...this.positions[updateIndex], ...position };
        }
        break;
 
      case 'position_closed':
        this.positions = this.positions.filter(p => p.id !== positionId);
        break;
    }
 
    this.onPositionsUpdate(this.positions);
  }
 
  handlePriceUpdates(updates) {
    for (const update of updates) {
      const position = this.positions.find(
        p => p.tokenAddress.toLowerCase() === update.tokenAddress.toLowerCase()
      );
 
      if (position) {
        position.currentPrice = update.price;
        position.currentPriceUsd = update.priceUsd;
        this.recalculatePL(position);
      }
    }
 
    this.onPositionsUpdate(this.positions);
  }
 
  recalculatePL(position) {
    const currentValue = position.currentPrice * position.purchaseAmount;
    const purchaseValue = position.purchasePrice * position.purchaseAmount;
    
    position.positionValueSol = currentValue;
    position.profitLossSol = currentValue - purchaseValue;
    position.profitLossPercent = (position.profitLossSol / purchaseValue) * 100;
  }
 
  // Override this method to handle position updates
  onPositionsUpdate(positions) {
    console.log('Positions updated:', positions.length);
  }
 
  disconnect() {
    if (this.ws) {
      this.ws.close(1000, 'User disconnect');
    }
  }
}
 
// Usage
const ws = new NexgentWebSocket(
  'https://your-instance.com',
  'your-jwt-token',
  'your-agent-id'
);
 
ws.onPositionsUpdate = (positions) => {
  // Update your UI here
  console.log('Positions:', positions);
};
 
ws.connect();

React Hook Example

A React hook for managing WebSocket connections with automatic reconnection.

import { useState, useEffect, useRef, useCallback } from 'react';
 
interface Position {
  id: string;
  tokenAddress: string;
  tokenSymbol: string;
  purchasePrice: number;
  purchaseAmount: number;
  currentPrice?: number;
  currentPriceUsd?: number;
  profitLossPercent?: number;
}
 
interface UseWebSocketOptions {
  autoConnect?: boolean;
  reconnectInterval?: number;
  maxReconnectAttempts?: number;
}
 
interface UseWebSocketReturn {
  positions: Position[];
  isConnected: boolean;
  isConnecting: boolean;
  error: string | null;
  connect: () => void;
  disconnect: () => void;
}
 
export function useNexgentWebSocket(
  apiUrl: string,
  token: string | null,
  agentId: string | null,
  options: UseWebSocketOptions = {}
): UseWebSocketReturn {
  const {
    autoConnect = true,
    reconnectInterval = 5000,
    maxReconnectAttempts = 5,
  } = options;
 
  const [positions, setPositions] = useState<Position[]>([]);
  const [isConnected, setIsConnected] = useState(false);
  const [isConnecting, setIsConnecting] = useState(false);
  const [error, setError] = useState<string | null>(null);
 
  const wsRef = useRef<WebSocket | null>(null);
  const reconnectAttemptsRef = useRef(0);
  const reconnectTimeoutRef = useRef<NodeJS.Timeout>();
 
  const getWebSocketUrl = useCallback(() => {
    if (!token || !agentId) return null;
    const wsUrl = apiUrl.replace(/^http/, 'ws');
    return `${wsUrl}/ws?token=${encodeURIComponent(token)}&agentId=${encodeURIComponent(agentId)}`;
  }, [apiUrl, token, agentId]);
 
  const connect = useCallback(() => {
    const url = getWebSocketUrl();
    if (!url) {
      setError('Missing token or agentId');
      return;
    }
 
    if (wsRef.current?.readyState === WebSocket.OPEN) {
      return;
    }
 
    setIsConnecting(true);
    setError(null);
 
    const ws = new WebSocket(url);
    wsRef.current = ws;
 
    ws.onopen = () => {
      console.log('[WS] Connection opened');
    };
 
    ws.onmessage = (event) => {
      const message = JSON.parse(event.data);
 
      switch (message.type) {
        case 'connected':
          setIsConnected(true);
          setIsConnecting(false);
          reconnectAttemptsRef.current = 0;
          break;
 
        case 'initial_data':
          setPositions(message.data.positions || []);
          break;
 
        case 'position_update':
          handlePositionUpdate(message.data);
          break;
 
        case 'price_update_batch':
          handlePriceUpdates(message.data.updates);
          break;
 
        case 'ping':
          ws.send(JSON.stringify({ type: 'pong', timestamp: new Date().toISOString() }));
          break;
 
        case 'error':
          setError(message.data.message);
          break;
      }
    };
 
    ws.onclose = (event) => {
      setIsConnected(false);
      setIsConnecting(false);
      wsRef.current = null;
 
      // Auto-reconnect with exponential backoff
      if (event.code !== 1000 && reconnectAttemptsRef.current < maxReconnectAttempts) {
        const delay = reconnectInterval * Math.pow(1.5, reconnectAttemptsRef.current);
        reconnectTimeoutRef.current = setTimeout(() => {
          reconnectAttemptsRef.current++;
          connect();
        }, delay);
      }
    };
 
    ws.onerror = () => {
      setError('WebSocket connection error');
      setIsConnecting(false);
    };
  }, [getWebSocketUrl, reconnectInterval, maxReconnectAttempts]);
 
  const handlePositionUpdate = useCallback((data: any) => {
    setPositions(prev => {
      switch (data.eventType) {
        case 'position_created':
          return [...prev, data.position];
        case 'position_updated':
          return prev.map(p => p.id === data.position.id ? { ...p, ...data.position } : p);
        case 'position_closed':
          return prev.filter(p => p.id !== data.positionId);
        default:
          return prev;
      }
    });
  }, []);
 
  const handlePriceUpdates = useCallback((updates: any[]) => {
    setPositions(prev => prev.map(position => {
      const update = updates.find(
        u => u.tokenAddress.toLowerCase() === position.tokenAddress.toLowerCase()
      );
      if (!update) return position;
 
      const currentValue = update.price * position.purchaseAmount;
      const purchaseValue = position.purchasePrice * position.purchaseAmount;
      
      return {
        ...position,
        currentPrice: update.price,
        currentPriceUsd: update.priceUsd,
        profitLossPercent: ((currentValue - purchaseValue) / purchaseValue) * 100,
      };
    }));
  }, []);
 
  const disconnect = useCallback(() => {
    if (reconnectTimeoutRef.current) {
      clearTimeout(reconnectTimeoutRef.current);
    }
    if (wsRef.current) {
      wsRef.current.close(1000, 'User disconnect');
      wsRef.current = null;
    }
    setIsConnected(false);
    setPositions([]);
  }, []);
 
  // Auto-connect
  useEffect(() => {
    if (autoConnect && token && agentId) {
      connect();
    }
    return () => disconnect();
  }, [autoConnect, token, agentId, connect, disconnect]);
 
  return {
    positions,
    isConnected,
    isConnecting,
    error,
    connect,
    disconnect,
  };
}

Usage in Component

function PositionsTable({ agentId }: { agentId: string }) {
  const { data: session } = useSession();
  
  const {
    positions,
    isConnected,
    isConnecting,
    error,
  } = useNexgentWebSocket(
    process.env.NEXT_PUBLIC_API_URL!,
    session?.accessToken ?? null,
    agentId,
    { autoConnect: true }
  );
 
  if (error) {
    return <div className="text-red-500">Error: {error}</div>;
  }
 
  if (isConnecting) {
    return <div>Connecting...</div>;
  }
 
  return (
    <div>
      <div className="mb-4">
        Status: {isConnected ? '🟢 Connected' : '🔴 Disconnected'}
      </div>
      
      <table>
        <thead>
          <tr>
            <th>Token</th>
            <th>Amount</th>
            <th>Entry Price</th>
            <th>Current Price</th>
            <th>P/L %</th>
          </tr>
        </thead>
        <tbody>
          {positions.map(position => (
            <tr key={position.id}>
              <td>{position.tokenSymbol}</td>
              <td>{position.purchaseAmount.toLocaleString()}</td>
              <td>${position.purchasePrice.toFixed(8)}</td>
              <td>${position.currentPrice?.toFixed(8) ?? '-'}</td>
              <td className={position.profitLossPercent >= 0 ? 'text-green-500' : 'text-red-500'}>
                {position.profitLossPercent?.toFixed(2) ?? '-'}%
              </td>
            </tr>
          ))}
        </tbody>
      </table>
    </div>
  );
}

Python Example

For backend integrations or scripts.

import asyncio
import json
import websockets
from datetime import datetime
 
class NexgentWebSocket:
    def __init__(self, api_url: str, token: str, agent_id: str):
        ws_url = api_url.replace('http', 'ws', 1)
        self.url = f"{ws_url}/ws?token={token}&agentId={agent_id}"
        self.positions = []
        self.is_connected = False
        
    async def connect(self):
        async with websockets.connect(self.url) as ws:
            self.ws = ws
            
            # Handle messages
            async for message in ws:
                await self.handle_message(json.loads(message))
                
    async def handle_message(self, message: dict):
        msg_type = message.get('type')
        data = message.get('data', {})
        
        if msg_type == 'connected':
            print(f"Connected to agent: {data.get('agentId')}")
            self.is_connected = True
            
        elif msg_type == 'initial_data':
            self.positions = data.get('positions', [])
            print(f"Received {len(self.positions)} positions")
            await self.on_positions_update(self.positions)
            
        elif msg_type == 'position_update':
            await self.handle_position_update(data)
            
        elif msg_type == 'price_update_batch':
            await self.handle_price_updates(data.get('updates', []))
            
        elif msg_type == 'ping':
            await self.ws.send(json.dumps({
                'type': 'pong',
                'timestamp': datetime.utcnow().isoformat() + 'Z'
            }))
            
        elif msg_type == 'error':
            print(f"Server error: {data.get('message')}")
            
    async def handle_position_update(self, data: dict):
        event_type = data.get('eventType')
        position = data.get('position')
        position_id = data.get('positionId')
        
        if event_type == 'position_created':
            self.positions.append(position)
        elif event_type == 'position_updated':
            for i, p in enumerate(self.positions):
                if p['id'] == position['id']:
                    self.positions[i] = {**p, **position}
                    break
        elif event_type == 'position_closed':
            self.positions = [p for p in self.positions if p['id'] != position_id]
            
        await self.on_positions_update(self.positions)
        
    async def handle_price_updates(self, updates: list):
        for update in updates:
            token_address = update['tokenAddress'].lower()
            for position in self.positions:
                if position['tokenAddress'].lower() == token_address:
                    position['currentPrice'] = update['price']
                    position['currentPriceUsd'] = update['priceUsd']
                    
        await self.on_positions_update(self.positions)
        
    async def on_positions_update(self, positions: list):
        """Override this method to handle position updates"""
        for p in positions:
            pnl = p.get('profitLossPercent', 0)
            print(f"  {p['tokenSymbol']}: {pnl:.2f}%")
 
# Usage
async def main():
    ws = NexgentWebSocket(
        api_url='https://your-instance.com',
        token='your-jwt-token',
        agent_id='your-agent-id'
    )
    await ws.connect()
 
if __name__ == '__main__':
    asyncio.run(main())

Best Practices

1. Batch Price Updates

Queue price updates and apply them in batches to avoid excessive re-renders:

// Instead of updating on every price message
let pendingUpdates = new Map();
let updateTimeout = null;
 
function queuePriceUpdate(tokenAddress, price, priceUsd) {
  pendingUpdates.set(tokenAddress.toLowerCase(), { price, priceUsd });
  
  if (!updateTimeout) {
    updateTimeout = setTimeout(() => {
      applyPendingUpdates();
      updateTimeout = null;
    }, 100); // Batch every 100ms
  }
}
 
function applyPendingUpdates() {
  // Apply all pending updates at once
  setPositions(prev => prev.map(p => {
    const update = pendingUpdates.get(p.tokenAddress.toLowerCase());
    return update ? { ...p, ...update } : p;
  }));
  pendingUpdates.clear();
}

2. Preserve Data During Reconnection

Don't clear positions when automatically reconnecting:

ws.onclose = (event) => {
  if (event.code === 1000) {
    // Clean close - clear positions
    setPositions([]);
  } else {
    // Unexpected close - keep positions, reconnect
    scheduleReconnect();
  }
};

3. Handle Token Expiration

Refresh your JWT before it expires and reconnect with the new token.