/** * Synor Liquidity Aggregator * Cross-chain DEX routing with optimal pricing * * Port: 17520 * Endpoints: * GET /health - Health check * GET /sources - Available liquidity sources * GET /quote - Get best price quote * GET /routes - Get optimal routing * POST /swap - Execute swap (simulated) */ const http = require('http'); const PORT = process.env.PORT || 17520; const AGGREGATION_FEE_BPS = parseInt(process.env.AGGREGATION_FEE_BPS) || 5; const MAX_SLIPPAGE_BPS = parseInt(process.env.MAX_SLIPPAGE_BPS) || 100; // Liquidity sources (simulated) const sources = new Map([ ['synor', { id: 'synor', name: 'Synor DEX', type: 'amm', chain: 'synor', active: true, liquidity: 5000000 }], ['osmosis', { id: 'osmosis', name: 'Osmosis', type: 'amm', chain: 'cosmos', active: true, liquidity: 50000000 }], ['dydx', { id: 'dydx', name: 'dYdX', type: 'orderbook', chain: 'cosmos', active: true, liquidity: 100000000 }], ['uniswap', { id: 'uniswap', name: 'Uniswap V3', type: 'amm', chain: 'ethereum', active: true, liquidity: 500000000 }], ]); // Simulated price quotes const baseQuotes = { 'BTC/USDT': 45000, 'ETH/USDT': 2500, 'SYNOR/USDT': 1.50, 'SOL/USDT': 95, 'ATOM/USDT': 8.50, }; // Get quote from source with slippage simulation function getQuoteFromSource(sourceId, tokenIn, tokenOut, amountIn) { const source = sources.get(sourceId); if (!source || !source.active) return null; const pair = `${tokenIn}/${tokenOut}`; const basePrice = baseQuotes[pair] || baseQuotes[`${tokenOut}/${tokenIn}`]; if (!basePrice) return null; // Add random spread based on source const spreadMultiplier = source.type === 'orderbook' ? 0.9995 : 0.999; // Orderbooks typically tighter const slippageImpact = (amountIn / source.liquidity) * 0.01; // Price impact const effectivePrice = basePrice * spreadMultiplier * (1 - slippageImpact); const amountOut = (amountIn / effectivePrice) * (1 - AGGREGATION_FEE_BPS / 10000); return { source: sourceId, sourceName: source.name, tokenIn, tokenOut, amountIn, amountOut, effectivePrice, priceImpact: slippageImpact * 100, fee: amountIn * (AGGREGATION_FEE_BPS / 10000), timestamp: Date.now(), }; } // Find best route function findBestRoute(tokenIn, tokenOut, amountIn) { const quotes = []; for (const [sourceId] of sources) { const quote = getQuoteFromSource(sourceId, tokenIn, tokenOut, amountIn); if (quote) quotes.push(quote); } if (quotes.length === 0) return null; // Sort by best output amount quotes.sort((a, b) => b.amountOut - a.amountOut); // For large amounts, consider split routing const bestSingle = quotes[0]; // Simple split route: 60% to best, 40% to second best if (quotes.length >= 2 && amountIn > 10000) { const splitRoute = { type: 'split', routes: [ { ...getQuoteFromSource(quotes[0].source, tokenIn, tokenOut, amountIn * 0.6), percentage: 60 }, { ...getQuoteFromSource(quotes[1].source, tokenIn, tokenOut, amountIn * 0.4), percentage: 40 }, ], totalAmountOut: 0, }; splitRoute.routes.forEach(r => splitRoute.totalAmountOut += r.amountOut); // Use split if better if (splitRoute.totalAmountOut > bestSingle.amountOut) { return { best: splitRoute, alternatives: quotes.slice(0, 3) }; } } return { best: bestSingle, alternatives: quotes.slice(1, 4) }; } // 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') { const activeSources = Array.from(sources.values()).filter(s => s.active).length; res.writeHead(200); res.end(JSON.stringify({ status: 'healthy', sources: activeSources, feeBps: AGGREGATION_FEE_BPS, timestamp: Date.now(), })); return; } // Available sources if (path === '/sources') { res.writeHead(200); res.end(JSON.stringify({ sources: Array.from(sources.values()), count: sources.size, })); return; } // Get quote if (path === '/quote') { const tokenIn = url.searchParams.get('tokenIn')?.toUpperCase(); const tokenOut = url.searchParams.get('tokenOut')?.toUpperCase(); const amountIn = parseFloat(url.searchParams.get('amount')); if (!tokenIn || !tokenOut || !amountIn) { res.writeHead(400); res.end(JSON.stringify({ error: 'Missing tokenIn, tokenOut, or amount' })); return; } const result = findBestRoute(tokenIn, tokenOut, amountIn); if (!result) { res.writeHead(404); res.end(JSON.stringify({ error: 'No route found', tokenIn, tokenOut })); return; } res.writeHead(200); res.end(JSON.stringify(result)); return; } // Get routes (same as quote but more detailed) if (path === '/routes') { const tokenIn = url.searchParams.get('tokenIn')?.toUpperCase(); const tokenOut = url.searchParams.get('tokenOut')?.toUpperCase(); const amountIn = parseFloat(url.searchParams.get('amount')); if (!tokenIn || !tokenOut || !amountIn) { res.writeHead(400); res.end(JSON.stringify({ error: 'Missing parameters' })); return; } const quotes = []; for (const [sourceId] of sources) { const quote = getQuoteFromSource(sourceId, tokenIn, tokenOut, amountIn); if (quote) quotes.push(quote); } quotes.sort((a, b) => b.amountOut - a.amountOut); res.writeHead(200); res.end(JSON.stringify({ tokenIn, tokenOut, amountIn, routes: quotes, bestSource: quotes[0]?.source, })); return; } // Execute swap (simulated) if (path === '/swap' && req.method === 'POST') { const body = await parseBody(req); const { tokenIn, tokenOut, amountIn, minAmountOut, route, sender } = body; if (!tokenIn || !tokenOut || !amountIn || !sender) { res.writeHead(400); res.end(JSON.stringify({ error: 'Missing required fields' })); return; } const result = findBestRoute(tokenIn, tokenOut, amountIn); if (!result) { res.writeHead(400); res.end(JSON.stringify({ error: 'No route available' })); return; } const amountOut = result.best.type === 'split' ? result.best.totalAmountOut : result.best.amountOut; if (minAmountOut && amountOut < minAmountOut) { res.writeHead(400); res.end(JSON.stringify({ error: 'Slippage too high', expected: minAmountOut, actual: amountOut })); return; } // Simulate transaction const txId = `0x${Math.random().toString(16).slice(2)}${Date.now().toString(16)}`; res.writeHead(200); res.end(JSON.stringify({ success: true, txId, tokenIn, tokenOut, amountIn, amountOut, route: result.best, sender, timestamp: Date.now(), })); 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(`Liquidity Aggregator running on port ${PORT}`); console.log(`Sources: ${Array.from(sources.values()).map(s => s.name).join(', ')}`); console.log(`Aggregation Fee: ${AGGREGATION_FEE_BPS / 100}%`); console.log('Endpoints:'); console.log(' GET /health - Health check'); console.log(' GET /sources - Available liquidity sources'); console.log(' GET /quote?tokenIn=X&tokenOut=Y&amount=Z - Get best quote'); console.log(' GET /routes?tokenIn=X&tokenOut=Y&amount=Z - Get all routes'); console.log(' POST /swap - Execute swap'); });