OS Trading Engine
Technical Documentation
Integrations
Pyth Network

Pyth Network

Nexgent uses Pyth Network as the oracle for SOL/USD price. This price is used to convert token prices from USD to SOL.

Overview

FeatureDescription
SOL/USD PriceReal-time SOL price for conversions
SSE StreamingSub-second price updates
Polling FallbackAutomatic fallback if streaming fails

Configuration

# Pyth Hermes API URL
PYTH_PRICE_SERVICE_URL=https://hermes.pyth.network

# SOL/USD Price Feed ID
PYTH_PRICE_FEED_ID_SOL_USD=0xef0d8b6fda2ceba41da15d4095d1da392a0d2f8ed0c6c7bc0f4cfac8c280b56d

The feed ID is the Pyth price feed identifier for SOL/USD. Find other feed IDs at pyth.network/price-feeds (opens in a new tab).


Architecture

┌─────────────────────────────────────────────────┐
│              Price Service                       │
│  ┌─────────────────────────────────────────┐    │
│  │         SSE Streaming (Primary)          │    │
│  │   Real-time price updates via EventSource│    │
│  └─────────────────────┬───────────────────┘    │
│                        │                        │
│                        │ Fallback               │
│                        ▼                        │
│  ┌─────────────────────────────────────────┐    │
│  │         Polling (Fallback)               │    │
│  │   10-second interval REST API polling    │    │
│  └─────────────────────────────────────────┘    │
└─────────────────────────────────────────────────┘


┌─────────────────────────────────────────────────┐
│           In-Memory Price Store                  │
│   solPrice: number                              │
│   lastUpdated: Date                             │
└─────────────────────────────────────────────────┘

Price Service

The PriceService maintains the current SOL/USD price in memory.

Initialization

// infrastructure/external/pyth/price-service.ts
 
class PriceService {
  private solPrice: number = 100; // Default fallback
  private lastUpdated: Date | null = null;
  
  async initialize(): Promise<void> {
    console.log('📊 Initializing price service...');
    
    // Fetch price immediately on startup
    await this.fetchPrice();
    
    // Start SSE streaming, fallback to polling if fails
    try {
      await this.startStreaming();
    } catch (error) {
      console.warn('Failed to start SSE, falling back to polling');
      this.fallbackToPolling();
    }
    
    console.log('✅ Price service initialized');
  }
  
  // Get current price (fast, in-memory)
  getSolPrice(): number {
    return this.solPrice;
  }
}

SSE Streaming

private async startStreaming(): Promise<void> {
  const streamUrl = `${this.baseUrl}/v2/updates/price/stream?ids[]=${this.solUsdFeedId}`;
  
  // Use eventsource package for Node.js SSE support
  const EventSourceModule = await import('eventsource');
  this.eventSource = new EventSourceModule.EventSource(streamUrl);
  
  this.eventSource.onopen = () => {
    this.connectionState = 'connected';
    this.reconnectAttempts = 0;
    console.log('📡 SSE connection established');
    this.startHealthMonitoring();
    this.startProactiveReconnection();
  };
  
  this.eventSource.onmessage = (event: MessageEvent) => {
    this.parsePriceUpdate(event.data);
    this.lastMessageTime = new Date();
  };
  
  this.eventSource.onerror = (error: Event) => {
    this.handleStreamError(error);
  };
}

Parse Price Update

private parsePriceUpdate(data: string): void {
  const response = JSON.parse(data);
  
  // Pyth SSE format: { parsed: [{ id, price: { price, expo, ... } }] }
  const message = response.parsed?.[0];
  
  if (!message?.price) {
    throw new Error('Invalid price data');
  }
  
  // Normalize: price * 10^expo
  const price = parseFloat(message.price.price);
  const expo = message.price.expo || -8;
  const actualPrice = price * Math.pow(10, expo);
  
  // Only log if price changed significantly (> $0.05)
  const priceChanged = Math.abs(actualPrice - this.solPrice) > 0.05;
  
  this.solPrice = actualPrice;
  this.lastUpdated = new Date();
  
  if (priceChanged) {
    console.log(`💰 Price updated: $${actualPrice.toFixed(2)} SOL/USD`);
  }
}

Connection Management

Reconnection with Exponential Backoff

private async reconnect(): Promise<void> {
  if (this.reconnectAttempts >= this.maxReconnectAttempts) {
    this.fallbackToPolling();
    return;
  }
  
  // Exponential backoff: 1s, 2s, 4s, 8s, 16s
  const delay = this.reconnectDelay * Math.pow(2, this.reconnectAttempts);
  this.reconnectAttempts++;
  
  console.log(`🔄 Reconnecting in ${delay}ms...`);
  await new Promise(resolve => setTimeout(resolve, delay));
  
  await this.startStreaming();
}

Health Monitoring

private startHealthMonitoring(): void {
  this.healthCheckInterval = setInterval(() => {
    if (!this.lastMessageTime || this.connectionState !== 'connected') {
      return;
    }
    
    const timeSinceLastMessage = Date.now() - this.lastMessageTime.getTime();
    
    // Reconnect if no messages for 30 seconds
    if (timeSinceLastMessage > 30000) {
      console.warn('⚠️ SSE connection stale, reconnecting...');
      this.stopStreaming();
      this.reconnect();
    }
  }, 30000);
}

Proactive Reconnection

Pyth has a 24-hour connection timeout. We reconnect proactively at 23 hours:

private startProactiveReconnection(): void {
  const reconnectDelay = 23 * 60 * 60 * 1000; // 23 hours
  
  this.proactiveReconnectInterval = setTimeout(() => {
    console.log('🔄 Proactive reconnection before 24h timeout...');
    this.stopStreaming();
    this.reconnectAttempts = 0;
    this.reconnect();
  }, reconnectDelay);
}

Polling Fallback

If SSE streaming fails after max reconnection attempts:

private fallbackToPolling(): void {
  this.connectionState = 'fallback';
  this.stopStreaming();
  
  // Poll every 10 seconds
  this.updateInterval = setInterval(() => {
    this.fetchPrice();
  }, 10000);
  
  console.log('⚠️ Falling back to polling mode');
}
 
private async fetchPrice(): Promise<void> {
  const url = `${this.baseUrl}/v2/updates/price/latest?ids[]=${this.solUsdFeedId}`;
  
  const response = await fetch(url);
  const data = await response.json();
  
  const priceData = data.parsed?.[0];
  if (!priceData?.price) {
    throw new Error('Invalid price data');
  }
  
  const price = parseFloat(priceData.price.price);
  const expo = priceData.price.expo || -8;
  this.solPrice = price * Math.pow(10, expo);
  this.lastUpdated = new Date();
}

Usage

The price service is used by Jupiter to convert USD prices to SOL:

// In JupiterPriceProvider
private getSolUsdPrice(): number {
  const priceService = PriceService.getInstance();
  return priceService.getSolPrice();
}
 
// Convert USD to SOL
const priceUsd = tokenData.usdPrice;
const solUsdPrice = this.getSolUsdPrice();
const priceSol = priceUsd / solUsdPrice;

API Reference

Pyth Hermes Endpoints

EndpointMethodDescription
/v2/updates/price/latestGETGet latest prices
/v2/updates/price/streamGET (SSE)Stream price updates

Price Feed Response

interface PythPriceResponse {
  parsed: Array<{
    id: string;          // Feed ID
    price: {
      price: string;     // Price value (needs expo conversion)
      conf: string;      // Confidence interval
      expo: number;      // Exponent (usually -8)
      publish_time: number;
    };
  }>;
}

Connection States

StateDescription
disconnectedNot connected
connectingAttempting to connect
connectedSSE streaming active
fallbackUsing polling mode