synor/docker/dex/api/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

246 lines
7.2 KiB
JavaScript

/**
* Synor DEX API Gateway
* Unified trading interface for all DEX services
*
* Port: 17530 (REST), 17531 (WebSocket)
* Endpoints:
* GET /health - Health check
* GET /status - System status
* GET /v1/oracle/* - Oracle proxy
* GET /v1/perps/* - Perps proxy
* GET /v1/aggregator/* - Aggregator proxy
* GET /v1/markets - All markets overview
*/
const http = require('http');
const PORT = process.env.PORT || 17530;
const ORACLE_URL = process.env.ORACLE_URL || 'http://localhost:17500';
const PERPS_URL = process.env.PERPS_URL || 'http://localhost:17510';
const AGGREGATOR_URL = process.env.AGGREGATOR_URL || 'http://localhost:17520';
// Service status tracking
const serviceStatus = {
oracle: { healthy: false, lastCheck: 0 },
perps: { healthy: false, lastCheck: 0 },
aggregator: { healthy: false, lastCheck: 0 },
};
// Fetch helper
async function fetchJSON(url, options = {}) {
return new Promise((resolve, reject) => {
const urlObj = new URL(url);
const reqOptions = {
hostname: urlObj.hostname,
port: urlObj.port,
path: urlObj.pathname + urlObj.search,
method: options.method || 'GET',
headers: options.headers || { 'Content-Type': 'application/json' },
timeout: 5000,
};
const req = http.request(reqOptions, (res) => {
let data = '';
res.on('data', chunk => data += chunk);
res.on('end', () => {
try {
resolve({ status: res.statusCode, data: JSON.parse(data) });
} catch {
resolve({ status: res.statusCode, data });
}
});
});
req.on('error', reject);
req.on('timeout', () => {
req.destroy();
reject(new Error('Timeout'));
});
if (options.body) {
req.write(JSON.stringify(options.body));
}
req.end();
});
}
// Health check for services
async function checkServiceHealth() {
const checks = [
{ name: 'oracle', url: `${ORACLE_URL}/health` },
{ name: 'perps', url: `${PERPS_URL}/health` },
{ name: 'aggregator', url: `${AGGREGATOR_URL}/health` },
];
for (const check of checks) {
try {
const result = await fetchJSON(check.url);
serviceStatus[check.name] = {
healthy: result.status === 200,
lastCheck: Date.now(),
latency: Date.now() - serviceStatus[check.name].lastCheck,
};
} catch {
serviceStatus[check.name] = { healthy: false, lastCheck: Date.now() };
}
}
}
// Check health every 30 seconds
setInterval(checkServiceHealth, 30000);
checkServiceHealth();
// Proxy request to service
async function proxyRequest(targetUrl, req, res) {
try {
let body = '';
for await (const chunk of req) {
body += chunk;
}
const result = await fetchJSON(targetUrl, {
method: req.method,
body: body ? JSON.parse(body) : undefined,
});
res.writeHead(result.status);
res.end(JSON.stringify(result.data));
} catch (err) {
res.writeHead(502);
res.end(JSON.stringify({ error: 'Service unavailable', message: err.message }));
}
}
// HTTP Server
const server = http.createServer(async (req, res) => {
res.setHeader('Content-Type', 'application/json');
res.setHeader('Access-Control-Allow-Origin', '*');
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');
res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization');
if (req.method === 'OPTIONS') {
res.writeHead(200);
res.end();
return;
}
const url = new URL(req.url, `http://localhost:${PORT}`);
const path = url.pathname;
// Health check
if (path === '/health') {
const allHealthy = Object.values(serviceStatus).every(s => s.healthy);
res.writeHead(allHealthy ? 200 : 503);
res.end(JSON.stringify({
status: allHealthy ? 'healthy' : 'degraded',
timestamp: Date.now(),
}));
return;
}
// System status
if (path === '/status') {
const allHealthy = Object.values(serviceStatus).every(s => s.healthy);
res.writeHead(200);
res.end(JSON.stringify({
status: allHealthy ? 'operational' : 'degraded',
services: serviceStatus,
version: '1.0.0',
timestamp: Date.now(),
}));
return;
}
// API documentation
if (path === '/' || path === '/docs') {
res.writeHead(200);
res.end(JSON.stringify({
name: 'Synor DEX API',
version: '1.0.0',
endpoints: {
'/health': 'Health check',
'/status': 'System status with all services',
'/v1/oracle/prices': 'Get all price feeds',
'/v1/oracle/price/:symbol': 'Get price for symbol',
'/v1/oracle/twap/:symbol': 'Get TWAP for symbol',
'/v1/perps/markets': 'Get all perpetual markets',
'/v1/perps/positions/:address': 'Get user positions',
'/v1/perps/funding': 'Get funding rates',
'/v1/aggregator/sources': 'Get liquidity sources',
'/v1/aggregator/quote': 'Get swap quote',
'/v1/aggregator/swap': 'Execute swap',
'/v1/markets': 'Combined market overview',
},
}));
return;
}
// Proxy to Oracle
if (path.startsWith('/v1/oracle/')) {
const targetPath = path.replace('/v1/oracle', '');
const targetUrl = `${ORACLE_URL}${targetPath}${url.search}`;
await proxyRequest(targetUrl, req, res);
return;
}
// Proxy to Perps
if (path.startsWith('/v1/perps/')) {
const targetPath = path.replace('/v1/perps', '');
const targetUrl = `${PERPS_URL}${targetPath}${url.search}`;
await proxyRequest(targetUrl, req, res);
return;
}
// Proxy to Aggregator
if (path.startsWith('/v1/aggregator/')) {
const targetPath = path.replace('/v1/aggregator', '');
const targetUrl = `${AGGREGATOR_URL}${targetPath}${url.search}`;
await proxyRequest(targetUrl, req, res);
return;
}
// Combined markets overview
if (path === '/v1/markets') {
try {
const [perpsResult, oracleResult, sourcesResult] = await Promise.allSettled([
fetchJSON(`${PERPS_URL}/markets`),
fetchJSON(`${ORACLE_URL}/prices`),
fetchJSON(`${AGGREGATOR_URL}/sources`),
]);
res.writeHead(200);
res.end(JSON.stringify({
perpetuals: perpsResult.status === 'fulfilled' ? perpsResult.value.data : null,
prices: oracleResult.status === 'fulfilled' ? oracleResult.value.data : null,
liquiditySources: sourcesResult.status === 'fulfilled' ? sourcesResult.value.data : null,
timestamp: Date.now(),
}));
} catch (err) {
res.writeHead(500);
res.end(JSON.stringify({ error: err.message }));
}
return;
}
// Default
res.writeHead(404);
res.end(JSON.stringify({ error: 'Not found', path }));
});
server.listen(PORT, '0.0.0.0', () => {
console.log(`DEX API Gateway running on port ${PORT}`);
console.log('');
console.log('Service URLs:');
console.log(` Oracle: ${ORACLE_URL}`);
console.log(` Perps: ${PERPS_URL}`);
console.log(` Aggregator: ${AGGREGATOR_URL}`);
console.log('');
console.log('Endpoints:');
console.log(' GET /health - Health check');
console.log(' GET /status - System status');
console.log(' GET /docs - API documentation');
console.log(' GET /v1/oracle/* - Oracle proxy');
console.log(' GET /v1/perps/* - Perps proxy');
console.log(' GET /v1/aggregator/* - Aggregator proxy');
console.log(' GET /v1/markets - Combined market overview');
});