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

227 lines
7 KiB
JavaScript

/**
* Synor Perpetuals Trading Engine
* Leveraged trading with 2x-100x positions
*
* Port: 17510 (REST), 17511 (WebSocket)
* Endpoints:
* GET /health - Health check
* GET /markets - Available markets
* GET /market/:id - Market details
* GET /positions/:address - User positions
* GET /funding - Current funding rates
* POST /order - Place order (simulated)
*/
const http = require('http');
const PORT = process.env.PORT || 17510;
const WS_PORT = process.env.WS_PORT || 17511;
const ORACLE_URL = process.env.ORACLE_URL || 'http://localhost:17500';
const MIN_LEVERAGE = parseInt(process.env.MIN_LEVERAGE) || 2;
const MAX_LEVERAGE = parseInt(process.env.MAX_LEVERAGE) || 100;
const MAINTENANCE_MARGIN_BPS = parseInt(process.env.MAINTENANCE_MARGIN_BPS) || 50;
const FUNDING_INTERVAL_HOURS = parseInt(process.env.FUNDING_INTERVAL_HOURS) || 8;
// Markets (in-memory state)
const markets = new Map([
[1, { id: 1, symbol: 'BTC/USD', tickSize: 0.1, lotSize: 0.001, maxLeverage: MAX_LEVERAGE }],
[2, { id: 2, symbol: 'ETH/USD', tickSize: 0.01, lotSize: 0.01, maxLeverage: MAX_LEVERAGE }],
[3, { id: 3, symbol: 'SYNOR/USD', tickSize: 0.0001, lotSize: 1.0, maxLeverage: 50 }],
[4, { id: 4, symbol: 'SOL/USD', tickSize: 0.01, lotSize: 0.1, maxLeverage: MAX_LEVERAGE }],
]);
// Open positions (in-memory)
const positions = new Map();
let positionIdCounter = 1;
// Funding rates (simulated)
const fundingRates = new Map([
[1, { marketId: 1, rate: 0.0001, nextFundingTime: Date.now() + 8 * 3600 * 1000 }],
[2, { marketId: 2, rate: 0.00012, nextFundingTime: Date.now() + 8 * 3600 * 1000 }],
[3, { marketId: 3, rate: 0.0002, nextFundingTime: Date.now() + 8 * 3600 * 1000 }],
[4, { marketId: 4, rate: 0.00008, nextFundingTime: Date.now() + 8 * 3600 * 1000 }],
]);
// Insurance fund (simulated)
let insuranceFund = 10000000; // $10M
// Calculate PnL
function calculatePnL(position, currentPrice) {
const priceDiff = currentPrice - position.entryPrice;
const multiplier = position.direction === 'long' ? 1 : -1;
return priceDiff * position.size * multiplier;
}
// Calculate margin ratio
function calculateMarginRatio(position, currentPrice) {
const pnl = calculatePnL(position, currentPrice);
const equity = position.collateral + pnl;
const notional = position.size * currentPrice;
return (equity / notional) * 10000; // in BPS
}
// Parse JSON body
function parseBody(req) {
return new Promise((resolve, reject) => {
let body = '';
req.on('data', chunk => body += chunk);
req.on('end', () => {
try {
resolve(body ? JSON.parse(body) : {});
} catch (e) {
reject(e);
}
});
});
}
// 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');
if (req.method === 'OPTIONS') {
res.writeHead(200);
res.end();
return;
}
const url = new URL(req.url, `http://localhost:${PORT}`);
const path = url.pathname;
try {
// Health check
if (path === '/health') {
res.writeHead(200);
res.end(JSON.stringify({
status: 'healthy',
markets: markets.size,
openPositions: positions.size,
insuranceFund,
timestamp: Date.now(),
}));
return;
}
// All markets
if (path === '/markets') {
res.writeHead(200);
res.end(JSON.stringify({
markets: Array.from(markets.values()),
config: { minLeverage: MIN_LEVERAGE, maxLeverage: MAX_LEVERAGE, maintenanceMarginBps: MAINTENANCE_MARGIN_BPS },
}));
return;
}
// Single market
if (path.startsWith('/market/')) {
const marketId = parseInt(path.slice(8));
const market = markets.get(marketId);
if (market) {
const funding = fundingRates.get(marketId);
res.writeHead(200);
res.end(JSON.stringify({ ...market, funding }));
} else {
res.writeHead(404);
res.end(JSON.stringify({ error: 'Market not found' }));
}
return;
}
// User positions
if (path.startsWith('/positions/')) {
const address = path.slice(11);
const userPositions = Array.from(positions.values()).filter(p => p.owner === address);
res.writeHead(200);
res.end(JSON.stringify({ address, positions: userPositions }));
return;
}
// Funding rates
if (path === '/funding') {
res.writeHead(200);
res.end(JSON.stringify({
rates: Array.from(fundingRates.values()),
interval: `${FUNDING_INTERVAL_HOURS}h`,
}));
return;
}
// Insurance fund
if (path === '/insurance') {
res.writeHead(200);
res.end(JSON.stringify({ insuranceFund, timestamp: Date.now() }));
return;
}
// Place order (simulated)
if (path === '/order' && req.method === 'POST') {
const body = await parseBody(req);
const { marketId, direction, size, collateral, leverage, owner } = body;
// Validate
if (!marketId || !direction || !size || !collateral || !leverage || !owner) {
res.writeHead(400);
res.end(JSON.stringify({ error: 'Missing required fields' }));
return;
}
if (leverage < MIN_LEVERAGE || leverage > MAX_LEVERAGE) {
res.writeHead(400);
res.end(JSON.stringify({ error: `Leverage must be between ${MIN_LEVERAGE}x and ${MAX_LEVERAGE}x` }));
return;
}
const market = markets.get(marketId);
if (!market) {
res.writeHead(400);
res.end(JSON.stringify({ error: 'Invalid market' }));
return;
}
// Create position (simulated - in production would interact with smart contract)
const positionId = positionIdCounter++;
const position = {
id: positionId,
owner,
marketId,
direction,
size,
collateral,
entryPrice: 45000, // Would fetch from oracle
leverage,
openedAt: Date.now(),
isOpen: true,
};
positions.set(positionId, position);
res.writeHead(201);
res.end(JSON.stringify({ success: true, position }));
return;
}
// Default
res.writeHead(404);
res.end(JSON.stringify({ error: 'Not found' }));
} catch (err) {
res.writeHead(500);
res.end(JSON.stringify({ error: err.message }));
}
});
server.listen(PORT, '0.0.0.0', () => {
console.log(`Perpetuals Engine running on port ${PORT}`);
console.log(`Leverage: ${MIN_LEVERAGE}x - ${MAX_LEVERAGE}x`);
console.log(`Maintenance Margin: ${MAINTENANCE_MARGIN_BPS / 100}%`);
console.log(`Markets: ${markets.size}`);
console.log('Endpoints:');
console.log(' GET /health - Health check');
console.log(' GET /markets - Available markets');
console.log(' GET /market/:id - Market details');
console.log(' GET /positions/:address - User positions');
console.log(' GET /funding - Current funding rates');
console.log(' POST /order - Place order');
});