Skinsmonkey API Blog

Building a Real-time Trading System with Skinsmonkey API

Building a Real-time Trading System with Skinsmonkey API

Introduction to Real-time Trading Systems

In the fast-paced world of gaming item trading, real-time updates and instant notifications are essential for a competitive platform. Users expect to see price changes, new listings, and transaction updates immediately, without having to refresh the page.

The Skinsmonkey API offers powerful real-time capabilities that allow you to build dynamic, responsive trading systems. In this article, we'll explore how to leverage these features to create a seamless trading experience for your users.

Why Real-time Matters in Trading Platforms

Before diving into implementation details, let's understand why real-time functionality is crucial for trading applications:

  • Market Volatility: Gaming item prices can change rapidly, and users need to see current values
  • Competitive Advantage: Users who receive instant notifications about new listings can act quickly
  • User Experience: Real-time updates create a more engaging and interactive platform
  • Trust and Transparency: Immediate transaction updates build user confidence

Understanding Skinsmonkey API's Real-time Architecture

The Skinsmonkey API provides two main approaches for implementing real-time functionality:

  1. WebSocket API: For continuous, bidirectional communication
  2. Webhooks: For server-to-server event notifications

Let's explore how to implement both methods and when to use each one.

Setting Up WebSocket Connections

WebSockets provide a persistent connection between your client application and the Skinsmonkey API server, allowing for instant data transmission in both directions.

Establishing a WebSocket Connection

// Basic WebSocket implementation
class SkinsmonkeyWebSocket {
  constructor(apiKey, options = {}) {
    this.apiKey = apiKey;
    this.baseUrl = options.baseUrl || 'wss://api.skinsmonkey.com/ws';
    this.reconnectAttempts = 0;
    this.maxReconnectAttempts = options.maxReconnectAttempts || 10;
    this.reconnectDelay = options.reconnectDelay || 1000;
    this.eventHandlers = new Map();
    this.socket = null;
    this.isConnecting = false;
    
    // Bind methods to preserve 'this' context
    this.connect = this.connect.bind(this);
    this.handleOpen = this.handleOpen.bind(this);
    this.handleMessage = this.handleMessage.bind(this);
    this.handleClose = this.handleClose.bind(this);
    this.handleError = this.handleError.bind(this);
  }
  
  async connect() {
    if (this.socket && (this.socket.readyState === WebSocket.OPEN || this.socket.readyState === WebSocket.CONNECTING)) {
      console.log('WebSocket already connected or connecting');
      return;
    }
    
    if (this.isConnecting) {
      console.log('WebSocket connection already in progress');
      return;
    }
    
    this.isConnecting = true;
    
    try {
      // Authenticate first to get a WebSocket token
      const response = await fetch(`${this.baseUrl.replace('wss', 'https')}/token`, {
        method: 'POST',
        headers: {
          'Authorization': `Bearer ${this.apiKey}`,
          'Content-Type': 'application/json'
        }
      });
      
      if (!response.ok) {
        throw new Error(`Failed to get WebSocket token: ${response.statusText}`);
      }
      
      const data = await response.json();
      const { token } = data;
      
      // Create WebSocket connection with the token
      this.socket = new WebSocket(`${this.baseUrl}?token=${token}`);
      this.socket.onopen = this.handleOpen;
      this.socket.onmessage = this.handleMessage;
      this.socket.onclose = this.handleClose;
      this.socket.onerror = this.handleError;
    } catch (error) {
      console.error('Error establishing WebSocket connection:', error);
      this.isConnecting = false;
      this.attemptReconnect();
    }
  }
  
  handleOpen(event) {
    console.log('WebSocket connection established');
    this.isConnecting = false;
    this.reconnectAttempts = 0;
    
    // Subscribe to default channels
    this.subscribe('market.updates');
    
    // Notify any listeners
    this.dispatchEvent('connected', { timestamp: new Date() });
  }
  
  handleMessage(event) {
    try {
      const message = JSON.parse(event.data);
      const { type, data } = message;
      
      console.log(`Received WebSocket message: ${type}`);
      this.dispatchEvent(type, data);
    } catch (error) {
      console.error('Error parsing WebSocket message:', error);
    }
  }
  
  handleClose(event) {
    this.isConnecting = false;
    
    if (event.wasClean) {
      console.log(`WebSocket closed cleanly, code=${event.code}, reason=${event.reason}`);
    } else {
      console.error('WebSocket connection died');
    }
    
    this.dispatchEvent('disconnected', {
      code: event.code,
      reason: event.reason,
      wasClean: event.wasClean
    });
    
    this.attemptReconnect();
  }
  
  handleError(error) {
    console.error('WebSocket error:', error);
    this.dispatchEvent('error', { error });
  }
  
  attemptReconnect() {
    if (this.reconnectAttempts >= this.maxReconnectAttempts) {
      console.error(`Maximum reconnection attempts (${this.maxReconnectAttempts}) reached`);
      this.dispatchEvent('reconnect_failed', {
        attempts: this.reconnectAttempts
      });
      return;
    }
    
    this.reconnectAttempts++;
    const delay = this.reconnectDelay * Math.pow(1.5, this.reconnectAttempts - 1);
    
    console.log(`Attempting to reconnect (${this.reconnectAttempts}/${this.maxReconnectAttempts}) in ${delay}ms`);
    
    setTimeout(() => {
      this.connect();
    }, delay);
  }
  
  subscribe(channel, params = {}) {
    if (!this.socket || this.socket.readyState !== WebSocket.OPEN) {
      console.error('Cannot subscribe: WebSocket not connected');
      return false;
    }
    
    const message = {
      action: 'subscribe',
      channel,
      params
    };
    
    this.socket.send(JSON.stringify(message));
    console.log(`Subscribed to channel: ${channel}`);
    return true;
  }
  
  unsubscribe(channel) {
    if (!this.socket || this.socket.readyState !== WebSocket.OPEN) {
      console.error('Cannot unsubscribe: WebSocket not connected');
      return false;
    }
    
    const message = {
      action: 'unsubscribe',
      channel
    };
    
    this.socket.send(JSON.stringify(message));
    console.log(`Unsubscribed from channel: ${channel}`);
    return true;
  }
  
  on(eventType, handler) {
    if (!this.eventHandlers.has(eventType)) {
      this.eventHandlers.set(eventType, []);
    }
    
    this.eventHandlers.get(eventType).push(handler);
    return this;
  }
  
  off(eventType, handler) {
    if (!this.eventHandlers.has(eventType)) {
      return this;
    }
    
    if (!handler) {
      this.eventHandlers.delete(eventType);
      return this;
    }
    
    const handlers = this.eventHandlers.get(eventType);
    const index = handlers.indexOf(handler);
    
    if (index !== -1) {
      handlers.splice(index, 1);
    }
    
    return this;
  }
  
  dispatchEvent(eventType, data) {
    if (!this.eventHandlers.has(eventType)) {
      return;
    }
    
    const handlers = this.eventHandlers.get(eventType);
    handlers.forEach(handler => {
      try {
        handler(data);
      } catch (error) {
        console.error(`Error in ${eventType} event handler:`, error);
      }
    });
  }
  
  disconnect() {
    if (this.socket) {
      this.socket.close();
      this.socket = null;
    }
  }
}

// Usage example
const apiKey = 'your-api-key';
const ws = new SkinsmonkeyWebSocket(apiKey);

// Listen for events
ws.on('connected', () => {
  console.log('Connected to Skinsmonkey WebSocket API');
});

ws.on('market.update', data => {
  console.log('Market update received:', data);
  updatePriceDisplay(data);
});

ws.on('trade.created', data => {
  console.log('New trade created:', data);
  showTradeNotification(data);
});

// Connect to the WebSocket server
ws.connect();

Handling Different Event Types

The Skinsmonkey WebSocket API provides various event types you can subscribe to:

Common WebSocket Event Types
  • market.update - Price changes and new listings
  • trade.created - New trade offer created
  • trade.updated - Status changes for existing trades
  • trade.completed - Trade successfully completed
  • inventory.change - Changes to user inventory
  • balance.update - User balance changes

Implementing Webhooks for Server-side Events

While WebSockets are ideal for client-facing real-time updates, webhooks are better suited for server-to-server communication and background processing:

// Express.js webhook handler
const express = require('express');
const crypto = require('crypto');
const app = express();

// Parse JSON requests
app.use(express.json());

// Verify webhook signature
function verifyWebhookSignature(payload, signature, secret) {
  const computedSignature = crypto
    .createHmac('sha256', secret)
    .update(JSON.stringify(payload))
    .digest('hex');
  
  return crypto.timingSafeEqual(
    Buffer.from(signature),
    Buffer.from(computedSignature)
  );
}

// Webhook endpoint
app.post('/webhooks/skinsmonkey', (req, res) => {
  const signature = req.headers['x-skinsmonkey-signature'];
  const webhookSecret = process.env.SKINSMONKEY_WEBHOOK_SECRET;
  
  // Verify the webhook signature
  if (!verifyWebhookSignature(req.body, signature, webhookSecret)) {
    console.error('Invalid webhook signature');
    return res.status(401).send('Invalid signature');
  }
  
  const { event, data } = req.body;
  
  console.log(`Received webhook: ${event}`);
  
  // Process different event types
  switch (event) {
    case 'trade.completed':
      handleTradeCompleted(data);
      break;
    case 'market.price_change':
      updateItemPrices(data.items);
      break;
    case 'user.deposit':
      creditUserAccount(data.user_id, data.amount);
      break;
    case 'user.withdrawal':
      processWithdrawal(data);
      break;
    default:
      console.log(`Unhandled webhook event: ${event}`);
  }
  
  // Always respond quickly to webhook calls
  res.status(200).send('Webhook received');
});

// Example event handlers
async function handleTradeCompleted(data) {
  try {
    const { trade_id, user_id, items, total_value } = data;
    
    // Update database records
    await db.trades.update({ id: trade_id }, { status: 'completed' });
    
    // Send notification to user
    await notificationService.send(user_id, {
      type: 'trade_completed',
      title: 'Trade Completed',
      message: `Your trade #${trade_id} has been completed successfully.`,
      data: {
        trade_id,
        total_value
      }
    });
    
    // Update inventory
    await inventoryService.refreshUserInventory(user_id);
    
    console.log(`Trade #${trade_id} completed successfully`);
  } catch (error) {
    console.error('Error handling trade.completed webhook:', error);
  }
}

// Start the server
const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
  console.log(`Webhook server listening on port ${PORT}`);
});

Registering Webhooks

To receive webhook events, you first need to register your endpoint with the Skinsmonkey API:

// Register a webhook endpoint
async function registerWebhook() {
  try {
    const response = await fetch('https://api.skinsmonkey.com/api/v1/webhooks', {
      method: 'POST',
      headers: {
        'Authorization': `Bearer ${apiKey}`,
        'Content-Type': 'application/json'
      },
      body: JSON.stringify({
        url: 'https://your-domain.com/webhooks/skinsmonkey',
        secret: webhookSecret, // Store this securely
        events: [
          'trade.completed',
          'market.price_change',
          'user.deposit',
          'user.withdrawal'
        ]
      })
    });
    
    if (!response.ok) {
      throw new Error(`Failed to register webhook: ${response.statusText}`);
    }
    
    const data = await response.json();
    console.log('Webhook registered successfully:', data);
    return data;
  } catch (error) {
    console.error('Error registering webhook:', error);
    throw error;
  }
}

Building a Real-time Trading Interface

Now that we have both WebSockets and webhooks set up, let's build a real-time trading interface that integrates these technologies:

1. Real-time Market Listings

// Example using React for a real-time market listing
import React, { useState, useEffect, useRef } from 'react';
import { SkinsmonkeyWebSocket } from './SkinsmonkeyWebSocket';

function MarketListings() {
  const [items, setItems] = useState([]);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);
  const wsRef = useRef(null);
  
  useEffect(() => {
    // Initialize WebSocket and load initial data
    const initMarket = async () => {
      try {
        // Load initial items
        const response = await fetch('https://api.skinsmonkey.com/api/v1/market/listings', {
          headers: { 'Authorization': `Bearer ${apiKey}` }
        });
        
        if (!response.ok) {
          throw new Error(`Failed to fetch listings: ${response.statusText}`);
        }
        
        const data = await response.json();
        setItems(data.items);
        setLoading(false);
        
        // Set up WebSocket for real-time updates
        const ws = new SkinsmonkeyWebSocket(apiKey);
        
        ws.on('connected', () => {
          console.log('WebSocket connected for market updates');
          ws.subscribe('market.listings', { game: 'csgo' });
        });
        
        ws.on('market.new_listing', newItem => {
          setItems(currentItems => [newItem, ...currentItems]);
          // Optional: Show notification or highlight
        });
        
        ws.on('market.price_update', updatedItem => {
          setItems(currentItems => 
            currentItems.map(item => 
              item.id === updatedItem.id 
                ? { ...item, price: updatedItem.price, updated: true } 
                : item
            )
          );
        });
        
        ws.on('market.item_sold', soldItemId => {
          setItems(currentItems => 
            currentItems.filter(item => item.id !== soldItemId)
          );
        });
        
        ws.connect();
        wsRef.current = ws;
      } catch (error) {
        console.error('Error initializing market:', error);
        setError('Failed to load market listings. Please try again later.');
        setLoading(false);
      }
    };
    
    initMarket();
    
    // Cleanup WebSocket on unmount
    return () => {
      if (wsRef.current) {
        wsRef.current.disconnect();
      }
    };
  }, []);
  
  // Helper to highlight price changes
  const getPriceClass = item => {
    if (!item.updated) return '';
    
    // Determine if price increased or decreased
    return item.priceChanged === 'up' 
      ? 'price-increased' 
      : 'price-decreased';
  };
  
  if (loading) {
    return 
Loading market data...
; } if (error) { return
{error}
; } return (

Live Market Listings

{items.map(item => (
{item.name}

{item.name}

${item.price.toFixed(2)}

))}
); }

2. Real-time Trading Status Updates

// Active trades component with real-time updates
function ActiveTrades() {
  const [trades, setTrades] = useState([]);
  const wsRef = useRef(null);
  
  useEffect(() => {
    // Load initial trades
    const loadTrades = async () => {
      try {
        const response = await fetch('https://api.skinsmonkey.com/api/v1/trades/active', {
          headers: { 'Authorization': `Bearer ${apiKey}` }
        });
        
        const data = await response.json();
        setTrades(data.trades);
        
        // Setup WebSocket for trade updates
        const ws = new SkinsmonkeyWebSocket(apiKey);
        
        ws.on('connected', () => {
          ws.subscribe('user.trades');
        });
        
        ws.on('trade.created', newTrade => {
          setTrades(currentTrades => [newTrade, ...currentTrades]);
          
          // Show notification for new trade
          showNotification({
            title: 'New Trade Created',
            message: `Trade #${newTrade.id} has been created and is waiting for confirmation.`,
            type: 'info'
          });
        });
        
        ws.on('trade.status_update', updatedTrade => {
          setTrades(currentTrades => 
            currentTrades.map(trade => 
              trade.id === updatedTrade.id 
                ? { ...trade, status: updatedTrade.status, updated: true } 
                : trade
            )
          );
          
          // Show notification for status update
          const statusMessages = {
            'accepted': 'Your trade has been accepted and is being processed.',
            'completed': 'Your trade has been completed successfully!',
            'cancelled': 'Your trade has been cancelled.',
            'rejected': 'Your trade has been rejected.'
          };
          
          showNotification({
            title: `Trade #${updatedTrade.id} ${updatedTrade.status}`,
            message: statusMessages[updatedTrade.status] || `Status changed to ${updatedTrade.status}`,
            type: updatedTrade.status === 'completed' ? 'success' : 'info'
          });
          
          // Remove completed trades after a delay
          if (['completed', 'cancelled', 'rejected'].includes(updatedTrade.status)) {
            setTimeout(() => {
              setTrades(currentTrades => 
                currentTrades.filter(trade => trade.id !== updatedTrade.id)
              );
            }, 60000); // Remove after 1 minute
          }
        });
        
        ws.connect();
        wsRef.current = ws;
      } catch (error) {
        console.error('Error loading trades:', error);
      }
    };
    
    loadTrades();
    
    return () => {
      if (wsRef.current) {
        wsRef.current.disconnect();
      }
    };
  }, []);
  
  const getStatusClass = status => {
    const statusClasses = {
      'pending': 'status-pending',
      'accepted': 'status-accepted',
      'processing': 'status-processing',
      'completed': 'status-completed',
      'cancelled': 'status-cancelled',
      'rejected': 'status-rejected'
    };
    
    return statusClasses[status] || '';
  };
  
  return (
    

Active Trades

{trades.length === 0 ? (

You have no active trades.

) : (
{trades.map(trade => (

Trade #{trade.id}

{trade.status}

Items

    {trade.items.map(item => (
  • {item.name} - ${item.price.toFixed(2)}
  • ))}

Total: ${trade.total.toFixed(2)}

Created: {new Date(trade.created_at).toLocaleString()}

{trade.status === 'pending' && (
)}
))}
)}
); }

Implementing Real-time Notifications

To keep users informed about important events, implement a notification system that works with your real-time data:

// Notification system for real-time updates
class NotificationManager {
  constructor() {
    this.listeners = [];
    this.notificationCount = 0;
    
    // Check if browser notifications are supported
    this.browserNotificationsSupported = 'Notification' in window;
    this.browserNotificationsEnabled = false;
    
    if (this.browserNotificationsSupported) {
      this.browserNotificationsEnabled = Notification.permission === 'granted';
    }
  }
  
  addListener(listener) {
    this.listeners.push(listener);
    return () => {
      this.listeners = this.listeners.filter(l => l !== listener);
    };
  }
  
  notify(notification) {
    const id = ++this.notificationCount;
    const timestamp = new Date();
    
    const fullNotification = {
      id,
      timestamp,
      read: false,
      ...notification
    };
    
    // Notify all UI listeners
    this.listeners.forEach(listener => {
      listener(fullNotification);
    });
    
    // Show browser notification if enabled
    if (this.browserNotificationsEnabled) {
      this.showBrowserNotification(fullNotification);
    }
    
    // Play sound if specified
    if (notification.sound) {
      this.playNotificationSound(notification.sound);
    }
    
    return id;
  }
  
  async requestBrowserNotificationPermission() {
    if (!this.browserNotificationsSupported) {
      return false;
    }
    
    if (Notification.permission === 'granted') {
      this.browserNotificationsEnabled = true;
      return true;
    }
    
    if (Notification.permission === 'denied') {
      return false;
    }
    
    try {
      const permission = await Notification.requestPermission();
      this.browserNotificationsEnabled = permission === 'granted';
      return this.browserNotificationsEnabled;
    } catch (error) {
      console.error('Error requesting notification permission:', error);
      return false;
    }
  }
  
  showBrowserNotification(notification) {
    if (!this.browserNotificationsEnabled) return;
    
    const browserNotification = new Notification(notification.title, {
      body: notification.message,
      icon: notification.icon || '/notification-icon.png'
    });
    
    browserNotification.onclick = () => {
      window.focus();
      if (notification.onClick) {
        notification.onClick();
      }
    };
  }
  
  playNotificationSound(soundName = 'default') {
    const sounds = {
      default: '/sounds/notification.mp3',
      trade: '/sounds/trade.mp3',
      success: '/sounds/success.mp3',
      error: '/sounds/error.mp3'
    };
    
    const soundFile = sounds[soundName] || sounds.default;
    const audio = new Audio(soundFile);
    audio.play().catch(error => {
      console.error('Error playing notification sound:', error);
    });
  }
}

// Create a singleton instance
const notificationManager = new NotificationManager();

// Usage in React component
function NotificationCenter() {
  const [notifications, setNotifications] = useState([]);
  
  useEffect(() => {
    // Register listener for new notifications
    const removeListener = notificationManager.addListener(newNotification => {
      setNotifications(current => [newNotification, ...current]);
    });
    
    // Request permission for browser notifications
    notificationManager.requestBrowserNotificationPermission();
    
    return () => {
      removeListener();
    };
  }, []);
  
  const markAsRead = notificationId => {
    setNotifications(current => 
      current.map(notification => 
        notification.id === notificationId 
          ? { ...notification, read: true } 
          : notification
      )
    );
  };
  
  const clearAll = () => {
    setNotifications([]);
  };
  
  const unreadCount = notifications.filter(n => !n.read).length;
  
  return (
    

Notifications {unreadCount > 0 && {unreadCount}}

{notifications.length > 0 && ( )}
{notifications.length === 0 ? (

No notifications

) : ( notifications.map(notification => (
markAsRead(notification.id)} >

{notification.title}

{notification.message}

{formatTimeAgo(notification.timestamp)}
)) )}
); } // Helper to format timestamps function formatTimeAgo(timestamp) { const now = new Date(); const diff = now - new Date(timestamp); const seconds = Math.floor(diff / 1000); if (seconds < 60) return `${seconds}s ago`; const minutes = Math.floor(seconds / 60); if (minutes < 60) return `${minutes}m ago`; const hours = Math.floor(minutes / 60); if (hours < 24) return `${hours}h ago`; const days = Math.floor(hours / 24); return `${days}d ago`; }

Performance Considerations for Real-time Systems

When implementing real-time features, keep these performance considerations in mind:

  • Message Filtering: Only subscribe to events your application needs
  • Batching Updates: Group multiple UI updates together to reduce re-renders
  • Efficient DOM Updates: Use virtual DOM frameworks like React for efficient UI updates
  • Connection Management: Implement robust reconnection logic for WebSockets
  • Memory Management: Be mindful of memory leaks in long-lived connections

Testing Real-time Systems

Testing real-time applications requires special considerations:

Testing Strategies for Real-time Features

  • WebSocket Mocking: Use libraries like Mock Socket to simulate WebSocket behavior
  • Event Simulation: Create test utilities to trigger simulated events
  • Connection Interruption Testing: Test how your app handles network disconnections
  • Load Testing: Verify your system can handle many simultaneous connections
  • End-to-End Testing: Use tools like Cypress to test the full user experience

Conclusion

Building a real-time trading system with the Skinsmonkey API requires combining WebSockets for client-facing real-time updates with webhooks for server-side event processing. By implementing these technologies effectively, you can create a responsive, engaging trading platform that provides users with instant updates and notifications.

Remember that real-time features should enhance the user experience, not overwhelm it. Focus on delivering timely, relevant information that helps users make informed decisions quickly.

With the knowledge from this article series, you now have a comprehensive understanding of how to implement, secure, optimize, handle errors, and add real-time capabilities to your Skinsmonkey API integration. We hope these articles help you build successful applications with the Skinsmonkey API!

Previous Post: Error Handling with Skinsmonkey API
Back to Blog

Share this article: