@crypto-dex-sdk/path-finder-api
Version:
Zenlink Interface Path Finder Api
146 lines (129 loc) • 5.01 kB
text/typescript
import type { LiquidityProviders } from '@crypto-dex-sdk/smart-router'
import type { VercelRequest, VercelResponse } from '@vercel/node'
import { ParachainId } from '@crypto-dex-sdk/chain'
import { Native } from '@crypto-dex-sdk/currency'
import {
getAggregationExecutorAddressForChainId,
getAggregationRouterAddressForChainId,
} from '@crypto-dex-sdk/smart-router'
import { BigNumber } from 'ethers'
import { z } from 'zod'
import redis from '../../lib/redis'
import { convertChainId, getClient, getDataFetcher, MAX_REQUESTS_PER_MIN } from './config'
import { getToken } from './tokens'
const querySchema = z.object({
chainId: z.coerce.number().int().gte(0).lte(2 ** 256).default(ParachainId.ASTAR),
fromTokenId: z.string().default('Native'),
toTokenId: z.string().default('Native'),
gasPrice: z.coerce.number().int().gte(1),
amount: z.coerce.string(),
to: z.optional(z.string()),
priceImpact: z.optional(z.coerce.number()),
liquidityProviders: z.optional(z.string()),
})
export function getFeeSettlementAddressForChainId(chainId: ParachainId) {
switch (chainId) {
case ParachainId.MOONBEAM:
return '0x8C7d87A2bAb7b48C4767983483E339eC0C8785a8'
case ParachainId.SCROLL_ALPHA:
return '0x4A7Dc8a7f62c46353dF2529c0789cF83C0e0e016'
case ParachainId.SCROLL:
return '0x7F12564eca712fa59b0EEdfE56EABC8b53a7B0cd'
case ParachainId.BASE:
return '0x7F12564eca712fa59b0EEdfE56EABC8b53a7B0cd'
case ParachainId.ASTAR:
return '0x85CbA73Cf58b5CA8FA20AcDB220F92ce350936C0'
default:
throw new Error(`Unsupported aggregation router network for ${chainId}`)
}
}
export default async (request: VercelRequest, response: VercelResponse) => {
const ip = request.headers['x-forwarded-for'] || request.socket.remoteAddress
if (ip) {
const key = `path_finder_v2_rate_limit:${ip}`
const currentCount = (await redis.get(key) || 0).toString()
if (Number.parseInt(currentCount, 10) >= MAX_REQUESTS_PER_MIN) {
response.status(429).send('Too many requests. Please try again later.')
return
}
await redis.multi().set(key, Number.parseInt(currentCount, 10) + 1, 'EX', 60).exec()
}
const {
chainId: _chainId,
fromTokenId,
toTokenId,
amount,
gasPrice,
to,
priceImpact,
liquidityProviders,
} = querySchema.parse(request.query)
const chainId = convertChainId(_chainId)
const client = getClient(chainId)
const dataFetcher = getDataFetcher(chainId)
if (!dataFetcher || !client)
return response.status(400).json({ message: `Unsupported chainId ${chainId}` })
const [fromToken, toToken] = await Promise.all([
getToken(chainId, fromTokenId),
getToken(chainId, toTokenId),
])
if (!fromToken || !toToken)
return response.status(400).json({ message: `Token not supported ${fromTokenId} or ${toTokenId}` })
const providers = liquidityProviders
? JSON.parse(liquidityProviders) as LiquidityProviders[]
: undefined
dataFetcher.startDataFetching(providers)
await dataFetcher.fetchPoolsForToken(fromToken, toToken)
const currentGasPrice = await client.getGasPrice()
const router = new Router(
dataFetcher,
fromToken,
BigNumber.from(amount),
toToken,
Number(currentGasPrice) ?? gasPrice ?? 30e9,
)
router.startRouting(() => {
router.stopRouting()
dataFetcher.stopDataFetching()
})
const bestRoute = router.getBestRoute()
if (!bestRoute)
return response.status(400).json({ message: 'Cannot find route, please try again.' })
const poolCodesMap = dataFetcher.getCurrentPoolCodeMap()
return response.status(200).json({
routeHumanString: Router.routeToHumanString(dataFetcher, bestRoute, fromToken, toToken),
bestRoute: {
status: bestRoute.status,
fromToken: bestRoute.fromToken.address === '' ? Native.onChain(chainId) : bestRoute.fromToken,
toToken: bestRoute.toToken.address === '' ? Native.onChain(chainId) : bestRoute.toToken,
primaryPrice: bestRoute.primaryPrice,
swapPrice: bestRoute.swapPrice,
amountIn: bestRoute.amountIn,
amountInBN: bestRoute.amountInBN.toString(),
amountOut: bestRoute.amountOut,
amountOutBN: bestRoute.amountOutBN.toString(),
priceImpact: bestRoute.priceImpact,
totalAmountOut: bestRoute.totalAmountOut,
totalAmountOutBN: bestRoute.totalAmountOutBN.toString(),
gasSpent: bestRoute.gasSpent,
legs: bestRoute.legs.map(l => ({
...l,
protocol: poolCodesMap.get(l.poolId)?.poolName,
})),
},
routerAddress: getAggregationRouterAddressForChainId(chainId),
executorAddress: getAggregationExecutorAddressForChainId(chainId),
routeParams: to
? Router.aggregationRouterParams(
dataFetcher,
bestRoute,
fromToken,
toToken,
to,
getAggregationExecutorAddressForChainId(chainId),
getFeeSettlementAddressForChainId(chainId),
priceImpact,
)
: undefined,
})
}