import {z} from 'zod' export type AllowDenyPrefer = {allow?: Array | undefined, deny?: Array | undefined, prefer?: Array | undefined}; export type DistributionFeeEntry = {percentage: number, receiver: string}; export type RouteOptions = {insurance?: boolean | undefined, integrator?: string | undefined, slippage?: number | undefined, slippageScope?: ('step' | 'route') | undefined, bridges?: AllowDenyPrefer | undefined, exchanges?: AllowDenyPrefer | undefined, pools?: ({deny?: (Array | null) | undefined}) | undefined, order?: ('FASTEST' | 'CHEAPEST') | undefined, allowSwitchChain?: boolean | undefined, allowDestinationCall?: boolean | undefined, referrer?: string | undefined, fee?: number | undefined, distributionFees?: Array | undefined, maxPriceImpact?: number | undefined, timing?: ({swapStepTimingStrategies?: (Array<{strategy?: 'minWaitTime' | undefined, minWaitTimeMs?: number | undefined, startingExpectedResults?: number | undefined, reduceEveryMs?: number | undefined}>) | undefined, routeTimingStrategies?: (Array<{strategy?: 'minWaitTime' | undefined, minWaitTimeMs?: number | undefined, startingExpectedResults?: number | undefined, reduceEveryMs?: number | undefined}>) | undefined}) | undefined, preset?: string | undefined, svmMaxSupportedTransactionVersion?: (number | null) | undefined, svmPriorityFeeLevel?: ('NORMAL' | 'FAST' | 'ULTRA') | undefined}; export type RoutesRequest = {fromChainId: number, fromAmount: string, fromTokenAddress: string, toChainId: number, toTokenAddress: string, options?: RouteOptions | undefined, fromAddress?: string | undefined, toAddress?: string | undefined, fromAmountForGas?: string | undefined}; export type CreateApiV1AdvancedRoutesArgs = {'x-lifi-api-key'?: string | undefined, body: RoutesRequest}; export type CreateApiV1AdvancedRoutesOptions = {baseUrl?: string | undefined, fetch?: typeof fetch | undefined}; export const token = z.object({address: z.string(), decimals: z.number(), symbol: z.string(), chainId: z.number(), coinKey: z.string().optional(), name: z.string(), logoURI: z.string().optional(), priceUSD: z.string().optional()}); export const routeSlippageCommitment = z.object({scope: z.literal("route"), slippage: z.string(), referenceToAmount: z.string(), minimumToAmount: z.string(), toChainId: z.number().int(), toTokenAddress: z.string(), toAddress: z.string().optional(), settlement: z.enum(["atomic", "conditional"]), checkpoints: z.array(z.object({stepId: z.string(), chainId: z.number().int(), tokenAddress: z.string(), minimumAmount: z.string()}))}); export const action = z.object({fromChainId: z.number(), fromAmount: z.string(), fromToken: token, toChainId: z.number(), toToken: token, slippage: z.number().optional(), fromAddress: z.string().optional(), toAddress: z.string().optional()}); export const feeCost = z.object({name: z.string(), description: z.string().optional(), percentage: z.string(), token: token, amount: z.string().optional(), amountUSD: z.string(), included: z.boolean(), feeSplit: z.object({lifiFee: z.string().optional(), integratorFee: z.string().optional(), intermediaryFee: z.string().optional(), recipients: z.array(z.object({name: z.string(), fee: z.string(), type: z.string().optional(), walletAddress: z.string().optional()})).optional()}).optional()}); export const gasCost = z.object({type: z.enum(["SUM", "APPROVE", "SEND", "FEE"]), price: z.string().optional(), estimate: z.string().optional(), limit: z.string().optional(), amount: z.string(), amountUSD: z.string().optional(), token: token}); export const estimate = z.object({tool: z.string(), fromAmount: z.string(), fromAmountUSD: z.string().optional(), toAmount: z.string(), toAmountMin: z.string(), toAmountUSD: z.string().optional(), approvalAddress: z.string(), skipApproval: z.boolean().optional(), feeCosts: z.array(feeCost).optional(), gasCosts: z.array(gasCost).optional(), executionDuration: z.number(), data: z.object({bid: z.object({user: z.string().optional(), router: z.string().optional(), initiator: z.string().optional(), sendingChainId: z.number().optional(), sendingAssetId: z.string().optional(), amount: z.string().optional(), receivingChainId: z.number().optional(), receivingAssetId: z.string().optional(), amountReceived: z.string().optional(), receivingAddress: z.string().optional(), transactionId: z.string().optional(), expiry: z.number().optional(), callDataHash: z.string().optional(), callTo: z.string().optional(), encryptedCallData: z.string().optional(), sendingChainTxManagerAddress: z.string().optional(), receivingChainTxManagerAddress: z.string().optional(), bidExpiry: z.number().optional()}).optional(), bidSignature: z.string().optional(), gasFeeInReceivingToken: z.string().optional(), totalFee: z.string().optional(), metaTxRelayerFee: z.string().optional(), routerFee: z.string().optional()}).optional()}); export const includedStep = z.object({id: z.string(), type: z.enum(["swap", "cross", "lifi", "protocol"]), tool: z.string(), toolDetails: z.object({key: z.string().optional(), name: z.string().optional(), logoURI: z.string().optional()}), action: action, estimate: estimate}); export const step = z.object({id: z.string(), type: z.enum(["swap", "cross", "lifi", "protocol"]), tool: z.string(), toolDetails: z.object({key: z.string().optional(), name: z.string().optional(), logoURI: z.string().optional()}).optional(), action: action, estimate: estimate.optional(), routeSlippage: routeSlippageCommitment.optional(), integrator: z.string().optional(), includedSteps: z.array(includedStep).optional(), referrer: z.string().optional(), execution: z.unknown(), transactionRequest: z.object({svmTransactionVersion: z.union([z.literal(0), z.literal(1)]).optional()}).optional()}); export const route = z.object({id: z.string(), fromChainId: z.number(), fromAmountUSD: z.string(), fromAmount: z.string(), fromToken: token, toChainId: z.number(), toAmountUSD: z.string(), toAmount: z.string(), toAmountMin: z.string(), routeSlippage: routeSlippageCommitment.optional(), toToken: token, gasCostUSD: z.string().optional(), steps: z.array(step), fromAddress: z.string().optional(), toAddress: z.string().optional(), containsSwitchChain: z.boolean().optional()}); export const toolError = z.object({errorType: z.literal("NO_QUOTE").optional(), code: z.enum(["NO_POSSIBLE_ROUTE", "INSUFFICIENT_LIQUIDITY", "TOOL_TIMEOUT", "UNKNOWN_ERROR", "RPC_ERROR", "AMOUNT_TOO_LOW", "AMOUNT_TOO_HIGH", "FEES_HIGHER_THAN_AMOUNT", "DIFFERENT_RECIPIENT_NOT_SUPPORTED", "TOOL_SPECIFIC_ERROR", "CANNOT_GUARANTEE_MIN_AMOUNT", "RATE_LIMIT_EXCEEDED"]).optional(), action: action.optional(), tool: z.string().optional(), message: z.string().optional()}); export const unavailableRoutes = z.object({filteredOut: z.array(z.object({overallPath: z.string().optional(), reason: z.string().optional()})).optional(), failed: z.array(z.object({overallPath: z.string().optional(), subpaths: z.record(z.string(), toolError).optional()})).optional()}); export const routesResponse = z.object({routes: z.array(route), unavailableRoutes: z.array(unavailableRoutes).optional()}); export const validateResponse = async ( schema: { readonly '~standard': { readonly validate: (value: unknown) => ({ readonly value: Output; readonly issues?: undefined } | { readonly issues: ReadonlyArray<{ readonly message: string; readonly path?: ReadonlyArray | undefined }> }) | Promise<{ readonly value: Output; readonly issues?: undefined } | { readonly issues: ReadonlyArray<{ readonly message: string; readonly path?: ReadonlyArray | undefined }> }> } }, value: unknown ): Promise => { const result = await schema['~standard'].validate(value) if (result.issues !== undefined) { const detail = result.issues .map(({ message, path }) => path?.length ? `${path.map((segment) => String(typeof segment === 'object' ? segment.key : segment)).join('.')}: ${message}` : message ) .join('; ') throw new Error(`Response does not match the schema: ${detail}`, { cause: { issues: result.issues } }) } return result.value }; /** * Get a set of routes for a request that describes a transfer of tokens * * POST /v1/advanced/routes */ export const createApiV1AdvancedRoutes = async (args: CreateApiV1AdvancedRoutesArgs, options?: CreateApiV1AdvancedRoutesOptions) => { const {baseUrl = "https://li.quest", fetch: fetchFn = fetch} = options ?? {} const query = new URLSearchParams() const headers = new Headers() headers.set('content-type', "application/json") headers.set('accept', "application/json") if (args['x-lifi-api-key'] != null) headers.set("x-lifi-api-key", String(args['x-lifi-api-key'])) const url = `${baseUrl.replace(/\/+$/, '')}/v1/advanced/routes${query.size > 0 ? `?${query}` : ''}` const res = await fetchFn(url, {method: "POST", headers, body: JSON.stringify(args.body)}) if (!res.ok) throw new Error(`Request failed with status ${res.status}`, {cause: {status: res.status, body: await res.text()}}) return validateResponse(routesResponse, await res.json()) };