Add live route matrix and stable bridge decision flows
Some checks failed
CI/CD Pipeline / Solidity Contracts (push) Failing after 1m25s
CI/CD Pipeline / Lint and Format (push) Has been cancelled
CI/CD Pipeline / Terraform Validation (push) Has been cancelled
CI/CD Pipeline / Kubernetes Validation (push) Has been cancelled
Deploy ChainID 138 / Deploy ChainID 138 (push) Has been cancelled
Validation / validate-genesis (push) Has been cancelled
Validation / validate-terraform (push) Has been cancelled
Validation / validate-kubernetes (push) Has been cancelled
Validation / validate-smart-contracts (push) Has been cancelled
Validation / validate-security (push) Has been cancelled
Validation / validate-documentation (push) Has been cancelled
CI/CD Pipeline / Security Scanning (push) Successful in 13m49s
Verify Deployment / Verify Deployment (push) Failing after 1m22s

This commit is contained in:
defiQUG
2026-03-27 12:02:36 -07:00
parent a780eff7c5
commit 721cdeb92f
16 changed files with 2001 additions and 6 deletions

View File

@@ -0,0 +1,91 @@
import { Router, Request, Response } from 'express';
import { cacheMiddleware } from '../middleware/cache';
import {
AggregatorRouteFilters,
filterLiveAggregatorRoutes,
getAggregatorRouteMatrixPath,
loadAggregatorRouteMatrix,
} from '../../config/aggregator-route-matrix';
const router: Router = Router();
function parseFilters(req: Request): AggregatorRouteFilters {
const fromChainId = req.query.fromChainId ? parseInt(String(req.query.fromChainId), 10) : undefined;
const toChainId = req.query.toChainId ? parseInt(String(req.query.toChainId), 10) : undefined;
return {
family: req.query.family ? String(req.query.family) : undefined,
fromChainId: Number.isFinite(fromChainId) ? fromChainId : undefined,
toChainId: Number.isFinite(toChainId) ? toChainId : undefined,
routeType: req.query.routeType ? String(req.query.routeType) : undefined,
tokenIn: req.query.tokenIn ? String(req.query.tokenIn) : undefined,
tokenOut: req.query.tokenOut ? String(req.query.tokenOut) : undefined,
};
}
/**
* GET /api/v1/routes/matrix
* Returns the canonical aggregator route matrix from config/aggregator-route-matrix.json.
*/
router.get('/routes/matrix', cacheMiddleware(30 * 1000), (req: Request, res: Response) => {
const matrix = loadAggregatorRouteMatrix();
if (!matrix) {
return res.status(503).json({
error: 'Aggregator route matrix not available',
});
}
const filters = parseFilters(req);
const includeNonLive = String(req.query.includeNonLive ?? 'false').toLowerCase() === 'true';
const liveRoutes = filterLiveAggregatorRoutes(
[...matrix.liveSwapRoutes, ...matrix.liveBridgeRoutes],
filters
);
return res.json({
generatedAt: new Date().toISOString(),
sourcePath: getAggregatorRouteMatrixPath(),
version: matrix.version,
updated: matrix.updated,
filters,
homeChainId: matrix.homeChainId,
liveRoutes,
blockedOrPlannedRoutes: includeNonLive ? matrix.blockedOrPlannedRoutes : undefined,
counts: {
liveSwapRoutes: matrix.liveSwapRoutes.length,
liveBridgeRoutes: matrix.liveBridgeRoutes.length,
blockedOrPlannedRoutes: matrix.blockedOrPlannedRoutes.length,
filteredLiveRoutes: liveRoutes.length,
},
});
});
/**
* GET /api/v1/routes/ingestion
* Adapter-focused flat export of only live routes, filtered by family/chain/token when provided.
*/
router.get('/routes/ingestion', cacheMiddleware(30 * 1000), (req: Request, res: Response) => {
const matrix = loadAggregatorRouteMatrix();
if (!matrix) {
return res.status(503).json({
error: 'Aggregator route matrix not available',
});
}
const filters = parseFilters(req);
const liveRoutes = filterLiveAggregatorRoutes(
[...matrix.liveSwapRoutes, ...matrix.liveBridgeRoutes],
filters
);
return res.json({
generatedAt: new Date().toISOString(),
format: 'aggregator-ingestion-v1',
version: matrix.version,
updated: matrix.updated,
filters,
routes: liveRoutes,
});
});
export default router;

View File

@@ -0,0 +1,319 @@
import { Router, Request, Response } from 'express';
import { cacheMiddleware } from '../middleware/cache';
import {
filterLiveAggregatorRoutes,
loadAggregatorRouteMatrix,
} from '../../config/aggregator-route-matrix';
import {
buildPartnerPayload,
PartnerName,
} from '../../services/partner-payload-adapters';
import { dispatchPartnerPayload } from '../../services/partner-payload-dispatcher';
import { buildInternalExecutionPlan } from '../../services/internal-execution-plan';
const router: Router = Router();
interface PartnerPayloadRequestBody {
partner?: string;
amount?: string;
fromChainId?: number;
toChainId?: number;
routeType?: string;
tokenIn?: string;
tokenOut?: string;
takerAddress?: string;
fromAddress?: string;
toAddress?: string;
recipient?: string;
slippagePercent?: string;
slippageBps?: string;
includeUnsupported?: boolean;
routeId?: string;
}
function normalizePartner(input: string | undefined): PartnerName | null {
if (!input) return null;
const value = input.trim().toLowerCase();
if (value === '1inch') return '1inch';
if (value === '0x' || value === 'zeroex') return '0x';
if (value === 'lifi') return 'LiFi';
return null;
}
function buildPayloads(args: {
partner: PartnerName;
amount: string;
fromChainId?: number;
toChainId?: number;
routeType?: string;
tokenIn?: string;
tokenOut?: string;
takerAddress?: string;
fromAddress?: string;
toAddress?: string;
recipient?: string;
slippagePercent?: string;
slippageBps?: string;
includeUnsupported?: boolean;
}) {
const matrix = loadAggregatorRouteMatrix();
if (!matrix) {
return {
error: {
status: 503,
body: {
error: 'Aggregator route matrix not available',
},
},
};
}
const liveRoutes = filterLiveAggregatorRoutes(
[...matrix.liveSwapRoutes, ...matrix.liveBridgeRoutes],
{
fromChainId: args.fromChainId,
toChainId: args.toChainId,
routeType: args.routeType,
tokenIn: args.tokenIn,
tokenOut: args.tokenOut,
}
);
const payloads = liveRoutes.map((route) =>
buildPartnerPayload(args.partner, route, {
amount: args.amount,
takerAddress: args.takerAddress,
fromAddress: args.fromAddress,
toAddress: args.toAddress,
recipient: args.recipient,
slippagePercent: args.slippagePercent,
slippageBps: args.slippageBps,
})
);
const filteredPayloads = args.includeUnsupported ? payloads : payloads.filter((payload) => payload.supported);
return {
result: {
generatedAt: new Date().toISOString(),
format: 'partner-payload-templates-v1',
partner: args.partner,
amount: args.amount,
count: filteredPayloads.length,
supportedCount: payloads.filter((payload) => payload.supported).length,
payloads: filteredPayloads,
},
};
}
/**
* GET /api/v1/routes/partner-payloads
* Returns partner-specific request payload templates generated from live ingestion routes.
* By default returns only supported payloads; pass includeUnsupported=true to inspect all templates.
*/
router.get('/routes/partner-payloads', cacheMiddleware(30 * 1000), (req: Request, res: Response) => {
const partner = normalizePartner(req.query.partner ? String(req.query.partner) : undefined);
if (!partner) {
return res.status(400).json({
error: 'partner is required and must be one of: 1inch, 0x, LiFi',
example: '/api/v1/routes/partner-payloads?partner=LiFi&amount=1000000',
});
}
const amount = req.query.amount ? String(req.query.amount) : '';
if (!amount) {
return res.status(400).json({
error: 'amount is required',
example: '/api/v1/routes/partner-payloads?partner=0x&amount=1000000',
});
}
const response = buildPayloads({
partner,
amount,
fromChainId: req.query.fromChainId ? parseInt(String(req.query.fromChainId), 10) : undefined,
toChainId: req.query.toChainId ? parseInt(String(req.query.toChainId), 10) : undefined,
routeType: req.query.routeType ? String(req.query.routeType) : undefined,
tokenIn: req.query.tokenIn ? String(req.query.tokenIn) : undefined,
tokenOut: req.query.tokenOut ? String(req.query.tokenOut) : undefined,
takerAddress: req.query.takerAddress ? String(req.query.takerAddress) : undefined,
fromAddress: req.query.fromAddress ? String(req.query.fromAddress) : undefined,
toAddress: req.query.toAddress ? String(req.query.toAddress) : undefined,
recipient: req.query.recipient ? String(req.query.recipient) : undefined,
slippagePercent: req.query.slippagePercent ? String(req.query.slippagePercent) : undefined,
slippageBps: req.query.slippageBps ? String(req.query.slippageBps) : undefined,
includeUnsupported: String(req.query.includeUnsupported ?? 'false').toLowerCase() === 'true',
});
if (response.error) {
return res.status(response.error.status).json(response.error.body);
}
return res.json(response.result);
});
/**
* POST /api/v1/routes/partner-payloads/resolve
* Accepts JSON body and returns only supported partner payloads by default.
*/
router.post('/routes/partner-payloads/resolve', cacheMiddleware(30 * 1000), (req: Request, res: Response) => {
const body = (req.body ?? {}) as PartnerPayloadRequestBody;
const partner = normalizePartner(body.partner);
if (!partner) {
return res.status(400).json({
error: 'partner is required and must be one of: 1inch, 0x, LiFi',
example: {
partner: '0x',
amount: '1000000',
fromChainId: 138,
routeType: 'swap',
},
});
}
if (!body.amount || !String(body.amount).trim()) {
return res.status(400).json({
error: 'amount is required',
});
}
const response = buildPayloads({
partner,
amount: String(body.amount),
fromChainId: typeof body.fromChainId === 'number' ? body.fromChainId : undefined,
toChainId: typeof body.toChainId === 'number' ? body.toChainId : undefined,
routeType: body.routeType,
tokenIn: body.tokenIn,
tokenOut: body.tokenOut,
takerAddress: body.takerAddress,
fromAddress: body.fromAddress,
toAddress: body.toAddress,
recipient: body.recipient,
slippagePercent: body.slippagePercent,
slippageBps: body.slippageBps,
includeUnsupported: body.includeUnsupported === true,
});
if (response.error) {
return res.status(response.error.status).json(response.error.body);
}
return res.json(response.result);
});
/**
* POST /api/v1/routes/partner-payloads/dispatch
* Resolves partner payloads and dispatches exactly one supported payload.
*/
router.post('/routes/partner-payloads/dispatch', async (req: Request, res: Response) => {
const body = (req.body ?? {}) as PartnerPayloadRequestBody;
const partner = normalizePartner(body.partner);
if (!partner) {
return res.status(400).json({
error: 'partner is required and must be one of: 1inch, 0x, LiFi',
});
}
if (!body.amount || !String(body.amount).trim()) {
return res.status(400).json({
error: 'amount is required',
});
}
const response = buildPayloads({
partner,
amount: String(body.amount),
fromChainId: typeof body.fromChainId === 'number' ? body.fromChainId : undefined,
toChainId: typeof body.toChainId === 'number' ? body.toChainId : undefined,
routeType: body.routeType,
tokenIn: body.tokenIn,
tokenOut: body.tokenOut,
takerAddress: body.takerAddress,
fromAddress: body.fromAddress,
toAddress: body.toAddress,
recipient: body.recipient,
slippagePercent: body.slippagePercent,
slippageBps: body.slippageBps,
includeUnsupported: true,
});
if (response.error) {
return res.status(response.error.status).json(response.error.body);
}
const supportedPayloads = response.result.payloads.filter((payload) => payload.supported);
const selectedPayload = body.routeId
? supportedPayloads.find((payload) => payload.routeId === body.routeId)
: supportedPayloads.length === 1
? supportedPayloads[0]
: undefined;
if (!selectedPayload) {
const fallback = buildInternalExecutionPlan({
routeId: body.routeId,
fromChainId: typeof body.fromChainId === 'number' ? body.fromChainId : undefined,
toChainId: typeof body.toChainId === 'number' ? body.toChainId : undefined,
tokenIn: body.tokenIn,
tokenOut: body.tokenOut,
amountIn: String(body.amount),
slippageBps: body.slippageBps,
});
return res.status(400).json({
error: body.routeId
? 'No supported payload found for the requested routeId'
: 'Dispatch requires exactly one supported payload; refine filters or pass routeId',
supportedRouteIds: supportedPayloads.map((payload) => payload.routeId),
fallbackPlan: fallback.plan,
fallbackError: fallback.error,
});
}
const dispatchResult = await dispatchPartnerPayload(selectedPayload);
return res.json({
generatedAt: new Date().toISOString(),
partner,
routeId: selectedPayload.routeId,
dispatch: dispatchResult,
});
});
/**
* POST /api/v1/routes/internal-execution-plan
* Returns a Chain 138 DODO PMM fallback execution plan for one live internal route.
*/
router.post('/routes/internal-execution-plan', (req: Request, res: Response) => {
const body = (req.body ?? {}) as PartnerPayloadRequestBody;
if (!body.amount || !String(body.amount).trim()) {
return res.status(400).json({
error: 'amount is required',
});
}
const result = buildInternalExecutionPlan({
routeId: body.routeId,
fromChainId: typeof body.fromChainId === 'number' ? body.fromChainId : undefined,
toChainId: typeof body.toChainId === 'number' ? body.toChainId : undefined,
tokenIn: body.tokenIn,
tokenOut: body.tokenOut,
amountIn: String(body.amount),
slippageBps: body.slippageBps,
});
if (result.error || !result.plan) {
return res.status(400).json({
error: result.error || 'Unable to build internal execution plan',
candidateRouteIds: result.candidateRouteIds,
});
}
return res.json({
generatedAt: new Date().toISOString(),
format: 'internal-execution-plan-v1',
plan: result.plan,
});
});
export default router;

View File

@@ -0,0 +1,94 @@
import { Router, Request, Response } from 'express';
import { cacheMiddleware } from '../middleware/cache';
import { RouteDecisionTreeService } from '../../services/route-decision-tree';
const router = Router();
const treeService = new RouteDecisionTreeService();
/**
* GET /api/v1/routes/tree
* Query:
* - chainId
* - tokenIn
* - tokenOut (optional)
* - amountIn (optional)
* - destinationChainId (optional)
*/
router.get('/routes/tree', cacheMiddleware(10 * 1000), async (req: Request, res: Response) => {
try {
const chainId = parseInt(req.query.chainId as string, 10);
const tokenIn = req.query.tokenIn as string;
const tokenOut = req.query.tokenOut as string | undefined;
const amountIn = req.query.amountIn as string | undefined;
const destinationChainIdRaw = req.query.destinationChainId as string | undefined;
const destinationChainId = destinationChainIdRaw ? parseInt(destinationChainIdRaw, 10) : undefined;
if (!chainId || !tokenIn) {
return res.status(400).json({
error: 'chainId and tokenIn are required',
});
}
const tree = await treeService.build({
chainId,
tokenIn,
tokenOut,
amountIn,
destinationChainId,
});
res.json(tree);
} catch (error) {
// eslint-disable-next-line no-console -- route error logging
console.error('Route tree error:', error);
res.status(500).json({
error: error instanceof Error ? error.message : 'Internal server error',
});
}
});
/**
* GET /api/v1/routes/depth
* Convenience endpoint for the most relevant depth metrics.
*/
router.get('/routes/depth', cacheMiddleware(10 * 1000), async (req: Request, res: Response) => {
try {
const chainId = parseInt(req.query.chainId as string, 10);
const tokenIn = req.query.tokenIn as string;
const tokenOut = req.query.tokenOut as string | undefined;
const amountIn = req.query.amountIn as string | undefined;
const destinationChainIdRaw = req.query.destinationChainId as string | undefined;
const destinationChainId = destinationChainIdRaw ? parseInt(destinationChainIdRaw, 10) : undefined;
if (!chainId || !tokenIn) {
return res.status(400).json({
error: 'chainId and tokenIn are required',
});
}
const tree = await treeService.build({
chainId,
tokenIn,
tokenOut,
amountIn,
destinationChainId,
});
res.json({
generatedAt: tree.generatedAt,
decision: tree.decision,
source: tree.source,
destination: tree.destination,
pools: tree.pools,
missingQuoteTokenPools: tree.missingQuoteTokenPools,
});
} catch (error) {
// eslint-disable-next-line no-console -- route error logging
console.error('Route depth error:', error);
res.status(500).json({
error: error instanceof Error ? error.message : 'Internal server error',
});
}
});
export default router;

View File

@@ -8,9 +8,12 @@ import adminRoutes from './routes/admin';
import configRoutes from './routes/config';
import bridgeRoutes from './routes/bridge';
import quoteRoutes from './routes/quote';
import routeTreeRoutes from './routes/routes';
import tokenMappingRoutes from './routes/token-mapping';
import heatmapRoutes from './routes/heatmap';
import arbitrageRoutes from './routes/arbitrage';
import aggregatorRouteMatrixRoutes from './routes/aggregator-routes';
import partnerPayloadRoutes from './routes/partner-payloads';
import { MultiChainIndexer } from '../indexer/chain-indexer';
import { getDatabasePool } from '../database/client';
import winston from 'winston';
@@ -102,10 +105,13 @@ export class ApiServer {
this.app.use('/api/v1', configRoutes);
this.app.use('/api/v1/report', reportRoutes);
this.app.use('/api/v1/bridge', bridgeRoutes);
this.app.use('/api/v1', routeTreeRoutes);
this.app.use('/api/v1/token-mapping', tokenMappingRoutes);
this.app.use('/api/v1', quoteRoutes);
this.app.use('/api/v1', heatmapRoutes);
this.app.use('/api/v1', arbitrageRoutes);
this.app.use('/api/v1', aggregatorRouteMatrixRoutes);
this.app.use('/api/v1', partnerPayloadRoutes);
// Admin routes (stricter rate limit)
this.app.use('/api/v1/admin', strictRateLimiter, adminRoutes);