Files
smom-dbis-138/orchestration/bridge/bridge-quote-routes.ts
2026-03-02 12:14:09 -08:00

88 lines
3.6 KiB
TypeScript

/**
* Bridge quote API routes (swap+bridge+swap).
* Mount at POST /api/bridge/quote and POST /api/bridge/quote/swap-bridge-swap.
* Requires RPC_URL, BRIDGE_REGISTRY_ADDRESS (and registry ABI); optional: ENHANCED_SWAP_ROUTER_ADDRESS, DESTINATION_RPC_URL, DESTINATION_SWAP_ROUTER_ADDRESS.
* Sends CORS and Content-Type: application/json so the DApp (dapp.d-bis.org) can call cross-origin without CORB.
*/
import { Router, Request, Response } from 'express';
import { createQuoteServiceFromEnv } from './quote-service';
import { DestinationType } from './workflow-engine';
const router = Router();
const DAPP_ORIGINS = ['https://dapp.d-bis.org', 'http://192.168.11.58', 'http://localhost:5173'];
function setCorsAndJson(res: Response, req: Request): void {
const origin = req.get('origin');
if (origin && (DAPP_ORIGINS.includes(origin) || process.env.NODE_ENV !== 'production')) {
res.setHeader('Access-Control-Allow-Origin', origin);
} else if (process.env.NODE_ENV !== 'production') {
res.setHeader('Access-Control-Allow-Origin', '*');
}
res.setHeader('Content-Type', 'application/json');
}
function getQuoteService(): ReturnType<typeof createQuoteServiceFromEnv> | null {
try {
return createQuoteServiceFromEnv();
} catch {
return null;
}
}
/**
* POST /api/bridge/quote
* Body: { token, amount, destinationChainId, destinationType?, destinationAddress }
* Or swap+bridge+swap: { sourceToken, destinationToken, sourceChainId, destinationChainId, amount, destinationAddress }
*/
router.options('/quote', (req: Request, res: Response) => {
const origin = req.get('origin');
if (origin && (DAPP_ORIGINS.includes(origin) || process.env.NODE_ENV !== 'production')) {
res.setHeader('Access-Control-Allow-Origin', origin);
} else if (process.env.NODE_ENV !== 'production') {
res.setHeader('Access-Control-Allow-Origin', '*');
}
res.setHeader('Access-Control-Allow-Methods', 'POST, OPTIONS');
res.setHeader('Access-Control-Allow-Headers', 'Content-Type');
res.setHeader('Access-Control-Max-Age', '86400');
res.sendStatus(204);
});
router.post('/quote', async (req: Request, res: Response) => {
setCorsAndJson(res, req);
try {
const svc = getQuoteService();
if (!svc) {
return res.status(503).json({ error: 'Quote service not configured (set RPC_URL, BRIDGE_REGISTRY_ADDRESS)' });
}
const body = req.body as Record<string, unknown>;
const sourceToken = body.sourceToken as string | undefined;
const destinationToken = body.destinationToken as string | undefined;
const sourceChainId = body.sourceChainId as number | undefined;
const destinationChainId = (body.destinationChainId ?? body.destinationChainId) as number;
const amount = (body.amount ?? body.amountIn) as string;
const token = (body.token ?? sourceToken) as string;
const destinationAddress = (body.destinationAddress ?? body.recipient) as string;
if (!amount || !destinationChainId) {
return res.status(400).json({ error: 'amount and destinationChainId are required' });
}
const request = {
token: token || '0x0000000000000000000000000000000000000000',
amount: String(amount),
destinationChainId: Number(destinationChainId),
destinationType: (body.destinationType as DestinationType) ?? DestinationType.EVM,
destinationAddress: destinationAddress || '0x0000000000000000000000000000000000000000',
};
const quote = await svc.getQuote(request);
res.json(quote);
} catch (err: unknown) {
const message = err instanceof Error ? err.message : 'Unknown error';
res.status(500).json({ error: message });
}
});
export default router;