- 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.
246 lines
7.2 KiB
JavaScript
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');
|
|
});
|