synor/docker/dex/oracle/index.js
Gulshan Yadav e2ce0022e5 feat(dex): add Docker deployment for DEX ecosystem services
- Add Dockerfile.contracts for building WASM contracts
- Add docker-compose.dex.yml for full DEX deployment
- Add docker-compose.dex-services.yml for lightweight services
- Add Node.js services for DEX ecosystem:
  - Oracle service (port 17500) - Price feeds with TWAP
  - Perps engine (port 17510) - Perpetual futures 2x-100x
  - Aggregator (port 17520) - Cross-chain liquidity routing
  - DEX API Gateway (port 17530) - Unified trading interface

Services verified operational on Docker Desktop.
2026-01-19 19:59:30 +05:30

159 lines
4.5 KiB
JavaScript

/**
* Synor Oracle Service
* Multi-source price aggregation with TWAP support
*
* Port: 17500
* Endpoints:
* GET /health - Health check
* GET /prices - All current prices
* GET /price/:symbol - Price for specific symbol
* GET /twap/:symbol - Time-weighted average price
*/
const http = require('http');
const PORT = process.env.PORT || 17500;
const UPDATE_INTERVAL = parseInt(process.env.UPDATE_INTERVAL_MS) || 1000;
const STALE_THRESHOLD = parseInt(process.env.STALE_THRESHOLD_MS) || 60000;
// Price state (in-memory cache)
const prices = new Map();
const priceHistory = new Map();
const MAX_HISTORY = 3600; // 1 hour of 1-second updates
// Initialize default prices (testnet)
const DEFAULT_PRICES = {
'BTC/USD': 45000,
'ETH/USD': 2500,
'SYNOR/USD': 1.50,
'SOL/USD': 95,
'ATOM/USD': 8.50,
'OSMO/USD': 0.75,
};
// Initialize prices
Object.entries(DEFAULT_PRICES).forEach(([symbol, price]) => {
prices.set(symbol, {
symbol,
price,
timestamp: Date.now(),
sources: ['simulator'],
confidence: 1.0,
});
priceHistory.set(symbol, [{ price, timestamp: Date.now() }]);
});
// Calculate TWAP
function calculateTWAP(symbol, windowMs = 300000) { // 5 minute default
const history = priceHistory.get(symbol) || [];
const cutoff = Date.now() - windowMs;
const relevantPrices = history.filter(p => p.timestamp >= cutoff);
if (relevantPrices.length === 0) return null;
const sum = relevantPrices.reduce((acc, p) => acc + p.price, 0);
return sum / relevantPrices.length;
}
// Update price with simulated volatility
function updatePrice(symbol, basePrice, volatility = 0.001) {
const change = (Math.random() - 0.5) * 2 * volatility * basePrice;
const newPrice = Math.max(basePrice + change, 0.01);
const timestamp = Date.now();
prices.set(symbol, {
symbol,
price: newPrice,
timestamp,
sources: ['simulator'],
confidence: 1.0,
});
// Update history
const history = priceHistory.get(symbol) || [];
history.push({ price: newPrice, timestamp });
if (history.length > MAX_HISTORY) history.shift();
priceHistory.set(symbol, history);
return newPrice;
}
// Periodic price updates
setInterval(() => {
Object.entries(DEFAULT_PRICES).forEach(([symbol, basePrice]) => {
const current = prices.get(symbol);
updatePrice(symbol, current?.price || basePrice);
});
}, UPDATE_INTERVAL);
// HTTP Server
const server = http.createServer((req, res) => {
res.setHeader('Content-Type', 'application/json');
res.setHeader('Access-Control-Allow-Origin', '*');
const url = new URL(req.url, `http://localhost:${PORT}`);
const path = url.pathname;
// Health check
if (path === '/health') {
res.writeHead(200);
res.end(JSON.stringify({ status: 'healthy', timestamp: Date.now() }));
return;
}
// All prices
if (path === '/prices') {
const allPrices = {};
prices.forEach((data, symbol) => {
const isStale = Date.now() - data.timestamp > STALE_THRESHOLD;
allPrices[symbol] = { ...data, stale: isStale };
});
res.writeHead(200);
res.end(JSON.stringify({ prices: allPrices, timestamp: Date.now() }));
return;
}
// Single price
if (path.startsWith('/price/')) {
const symbol = decodeURIComponent(path.slice(7)).toUpperCase();
const data = prices.get(symbol);
if (data) {
const isStale = Date.now() - data.timestamp > STALE_THRESHOLD;
res.writeHead(200);
res.end(JSON.stringify({ ...data, stale: isStale }));
} else {
res.writeHead(404);
res.end(JSON.stringify({ error: 'Symbol not found', symbol }));
}
return;
}
// TWAP
if (path.startsWith('/twap/')) {
const symbol = decodeURIComponent(path.slice(6)).toUpperCase();
const window = parseInt(url.searchParams.get('window')) || 300000;
const twap = calculateTWAP(symbol, window);
if (twap !== null) {
res.writeHead(200);
res.end(JSON.stringify({ symbol, twap, windowMs: window, timestamp: Date.now() }));
} else {
res.writeHead(404);
res.end(JSON.stringify({ error: 'No price history for symbol', symbol }));
}
return;
}
// Default
res.writeHead(404);
res.end(JSON.stringify({ error: 'Not found' }));
});
server.listen(PORT, '0.0.0.0', () => {
console.log(`Oracle Service running on port ${PORT}`);
console.log(`Tracking ${prices.size} price feeds`);
console.log('Endpoints:');
console.log(' GET /health - Health check');
console.log(' GET /prices - All current prices');
console.log(' GET /price/:symbol - Price for specific symbol');
console.log(' GET /twap/:symbol - Time-weighted average price');
});