Flash unwinder contracts and scripts, relay lane tuning, trustless bridge and token-aggregation updates.
Made-with: Cursor
This commit is contained in:
@@ -8,6 +8,17 @@ interface CacheEntry {
|
||||
const cache = new Map<string, CacheEntry>();
|
||||
const DEFAULT_TTL = 60 * 1000; // 1 minute
|
||||
|
||||
/** Never cache generic API error envelopes (avoids poisoning cache if status/body ever disagree). */
|
||||
function looksLikeGenericErrorPayload(body: unknown): boolean {
|
||||
if (body == null || typeof body !== 'object' || Array.isArray(body)) return false;
|
||||
const o = body as Record<string, unknown>;
|
||||
if (typeof o.error !== 'string') return false;
|
||||
// Success shapes we must not treat as errors
|
||||
if ('pools' in o || 'tokens' in o || 'data' in o || 'chains' in o || 'tree' in o || 'quote' in o) return false;
|
||||
if (o.status === 'healthy') return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
export function cacheMiddleware(ttl: number = DEFAULT_TTL) {
|
||||
return (req: Request, res: Response, next: NextFunction) => {
|
||||
const bypassCache =
|
||||
@@ -29,7 +40,9 @@ export function cacheMiddleware(ttl: number = DEFAULT_TTL) {
|
||||
|
||||
// Override json method to cache response
|
||||
res.json = function (body: unknown) {
|
||||
if (!bypassCache) {
|
||||
// Only cache successful payloads. Otherwise a 500 body gets replayed on cache hit with HTTP 200.
|
||||
const okStatus = res.statusCode >= 200 && res.statusCode < 300;
|
||||
if (!bypassCache && okStatus && !looksLikeGenericErrorPayload(body)) {
|
||||
cache.set(key, {
|
||||
data: body,
|
||||
expiresAt: Date.now() + ttl,
|
||||
|
||||
@@ -283,38 +283,57 @@ router.get('/tokens/:address/pools', cacheMiddleware(60 * 1000), async (req: Req
|
||||
return res.status(400).json({ error: 'chainId is required' });
|
||||
}
|
||||
|
||||
const pools = await getPoolsByTokenWithFallback(chainId, address);
|
||||
let pools: LiquidityPool[];
|
||||
try {
|
||||
pools = await getPoolsByTokenWithFallback(chainId, address);
|
||||
} catch (error) {
|
||||
logger.error('Error resolving pools list:', error);
|
||||
pools = [];
|
||||
}
|
||||
|
||||
res.json({
|
||||
pools: await Promise.all(
|
||||
pools.map(async (pool) => {
|
||||
const { token0, token1 } = await resolvePoolTokenDisplays(tokenRepo, chainId, pool.token0Address, pool.token1Address);
|
||||
return {
|
||||
address: pool.poolAddress,
|
||||
dex: pool.dexType,
|
||||
token0: {
|
||||
address: pool.token0Address,
|
||||
symbol: token0.symbol,
|
||||
name: token0.name,
|
||||
source: token0.source,
|
||||
},
|
||||
token1: {
|
||||
address: pool.token1Address,
|
||||
symbol: token1.symbol,
|
||||
name: token1.name,
|
||||
source: token1.source,
|
||||
},
|
||||
reserves: {
|
||||
token0: pool.reserve0,
|
||||
token1: pool.reserve1,
|
||||
},
|
||||
tvl: pool.totalLiquidityUsd,
|
||||
volume24h: pool.volume24h,
|
||||
feeTier: pool.feeTier,
|
||||
};
|
||||
})
|
||||
),
|
||||
});
|
||||
const settled = await Promise.allSettled(
|
||||
pools.map(async (pool) => {
|
||||
const { token0, token1 } = await resolvePoolTokenDisplays(tokenRepo, chainId, pool.token0Address, pool.token1Address);
|
||||
return {
|
||||
address: pool.poolAddress,
|
||||
dex: String(pool.dexType ?? ''),
|
||||
token0: {
|
||||
address: pool.token0Address,
|
||||
symbol: token0.symbol,
|
||||
name: token0.name,
|
||||
source: token0.source,
|
||||
},
|
||||
token1: {
|
||||
address: pool.token1Address,
|
||||
symbol: token1.symbol,
|
||||
name: token1.name,
|
||||
source: token1.source,
|
||||
},
|
||||
reserves: {
|
||||
token0: pool.reserve0,
|
||||
token1: pool.reserve1,
|
||||
},
|
||||
tvl: pool.totalLiquidityUsd,
|
||||
volume24h: pool.volume24h,
|
||||
feeTier: pool.feeTier,
|
||||
};
|
||||
})
|
||||
);
|
||||
|
||||
const poolsOut = [];
|
||||
for (const row of settled) {
|
||||
if (row.status === 'fulfilled') {
|
||||
poolsOut.push(row.value);
|
||||
} else {
|
||||
logger.warn('Skipping pool row in /tokens/:address/pools:', row.reason);
|
||||
}
|
||||
}
|
||||
|
||||
// BigInt (e.g. from live RPC paths) breaks res.json; stringify replacer keeps Mission Control / E2E stable.
|
||||
const payload = JSON.parse(
|
||||
JSON.stringify({ pools: poolsOut }, (_key, value) => (typeof value === 'bigint' ? value.toString() : value))
|
||||
) as { pools: typeof poolsOut };
|
||||
res.json(payload);
|
||||
} catch (error) {
|
||||
logger.error('Error fetching pools:', error);
|
||||
res.status(500).json({ error: 'Internal server error' });
|
||||
|
||||
@@ -101,14 +101,22 @@ export interface RouteDecisionTreeResponse {
|
||||
missingQuoteTokenPools: MissingQuoteTokenPool[];
|
||||
}
|
||||
|
||||
function safeBigInt(raw: string | undefined): bigint {
|
||||
try {
|
||||
return BigInt(String(raw || '0').trim() || '0');
|
||||
} catch {
|
||||
return 0n;
|
||||
}
|
||||
}
|
||||
|
||||
function normalizedTvlUsd(pool: LiquidityPool): number {
|
||||
let tvl = Math.max(0, pool.totalLiquidityUsd || 0);
|
||||
if (pool.chainId === CHAIN_138 && pool.dexType === 'dodo') {
|
||||
const estimated = estimateChain138DodoLiquidityUsd({
|
||||
token0Address: pool.token0Address,
|
||||
token1Address: pool.token1Address,
|
||||
reserve0: BigInt(pool.reserve0 || '0'),
|
||||
reserve1: BigInt(pool.reserve1 || '0'),
|
||||
reserve0: safeBigInt(pool.reserve0),
|
||||
reserve1: safeBigInt(pool.reserve1),
|
||||
}).totalLiquidityUsd;
|
||||
if (estimated > 0) {
|
||||
tvl = Math.max(tvl, estimated);
|
||||
|
||||
Reference in New Issue
Block a user