import {z} from 'zod' export type GetApiV1RelayerStatusTaskIdOptions = {baseUrl?: string | undefined, fetch?: typeof fetch | undefined}; export const relayTransactionMetadata = z.object({chainId: z.number().int().optional(), txHash: z.string().optional()}); 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 includedSwapSteps = z.object({tool: z.string().optional(), toolDetails: z.object({key: z.string().optional(), name: z.string().optional(), logoURI: z.string().optional()}).optional(), fromAmount: z.string().optional(), fromToken: z.string().optional(), toAmount: z.string().optional(), toToken: z.string().optional(), bridgedAmount: z.string().optional()}); export const transactionInfo = z.object({txHash: z.string(), txLink: z.string(), amount: z.string(), token: token, chainId: z.number(), gasToken: token.optional(), gasAmount: z.string().optional(), gasAmountUSD: z.string().optional(), gasPrice: z.string().optional(), gasUsed: z.string().optional(), timestamp: z.number().optional(), value: z.string().optional(), includedSteps: z.array(includedSwapSteps).optional()}); export const metadata = z.object({integrator: z.string().optional()}); 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 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 transferQuote = z.object({stepId: z.string(), tool: z.string(), type: z.string(), action: action, estimate: estimate, includedSteps: z.array(z.object({tool: z.string(), type: z.string(), action: action, estimate: estimate})), integrator: z.string().optional(), gasLimit: z.string(), createdAt: z.string(), routeSlippage: routeSlippageCommitment.optional()}); export const routeSlippageOutcome = z.object({outcome: z.enum(["PENDING", "FULFILLED", "NOT_FULFILLED"]), minimumToAmount: z.string(), deliveredAmount: z.string().optional(), shortfall: z.enum(["REVERTED", "REFUNDED", "DIFFERENT_TOKEN", "BELOW_FLOOR"]).optional()}); export const statusResponse = z.object({sending: transactionInfo, receiving: transactionInfo.optional(), feeCosts: z.array(z.object({name: z.string().optional(), description: z.string().optional(), percentage: z.string().optional(), token: z.object({address: z.string().optional(), decimals: z.number().optional(), symbol: z.string().optional(), chainId: z.number().optional(), coinKey: z.string().optional(), name: z.string().optional(), logoURI: z.string().optional()}).optional(), amount: z.string().optional(), amountUSD: z.string().optional(), included: z.boolean().optional()})).optional(), status: z.enum(["NOT_FOUND", "INVALID", "PENDING", "DONE", "FAILED"]), substatus: z.enum(["WAIT_SOURCE_CONFIRMATIONS", "WAIT_DESTINATION_TRANSACTION", "BRIDGE_NOT_AVAILABLE", "CHAIN_NOT_AVAILABLE", "REFUND_IN_PROGRESS", "UNKNOWN_ERROR", "COMPLETED", "PARTIAL", "REFUNDED"]).optional(), substatusMessage: z.string().optional(), tool: z.string(), transactionId: z.string().optional(), fromAddress: z.string().optional(), toAddress: z.string().optional(), lifiExplorerLink: z.string().optional(), metadata: metadata.optional(), quote: transferQuote.optional(), routeSlippage: routeSlippageOutcome.optional()}); export const relayTransactionStatus = z.object({status: z.string().optional(), message: z.string().optional(), metadata: relayTransactionMetadata.optional(), transactionStatus: statusResponse.optional()}); export const relayTransactionStatusResponse = z.object({status: z.string().optional(), data: relayTransactionStatus}); 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 }; /** * Cross chain relay transfers might take a while to complete. Waiting on the transaction on the sending chain doesn't help here. For this reason we build a simple endpoint that let's you check the status of your transfer. * * GET /v1/relayer/status/{taskId} */ export const getApiV1RelayerStatusTaskId = async (options?: GetApiV1RelayerStatusTaskIdOptions) => { const {baseUrl = "https://li.quest", fetch: fetchFn = fetch} = options ?? {} const query = new URLSearchParams() const headers = new Headers() headers.set('accept', "application/json") const url = `${baseUrl.replace(/\/+$/, '')}/v1/relayer/status/${encodeURIComponent(String(args.taskId))}${query.size > 0 ? `?${query}` : ''}` const res = await fetchFn(url, {method: "GET", headers}) if (!res.ok) throw new Error(`Request failed with status ${res.status}`, {cause: {status: res.status, body: await res.text()}}) return validateResponse(relayTransactionStatusResponse, await res.json()) };