)
diff --git a/apps/web/src/app/(networks)/(evm)/[chainId]/(trade)/swap/loading.tsx b/apps/web/src/app/(networks)/(evm)/[chainId]/(trade)/swap/loading.tsx
index 97f11a2e68..7c547392cd 100644
--- a/apps/web/src/app/(networks)/(evm)/[chainId]/(trade)/swap/loading.tsx
+++ b/apps/web/src/app/(networks)/(evm)/[chainId]/(trade)/swap/loading.tsx
@@ -7,20 +7,20 @@ export default function SimpleSwapLoading() {
-
-
+
+
-
-
-
-
+
+
+
+
-
)
diff --git a/apps/web/src/app/(networks)/(evm)/api/cross-chain/route.ts b/apps/web/src/app/(networks)/(evm)/api/cross-chain/route.ts
deleted file mode 100644
index d11ea2ab7c..0000000000
--- a/apps/web/src/app/(networks)/(evm)/api/cross-chain/route.ts
+++ /dev/null
@@ -1,75 +0,0 @@
-import { NextRequest } from 'next/server'
-import { SushiXSwap2Adapter } from 'src/lib/swap/cross-chain'
-import { getCrossChainTrade } from 'src/lib/swap/cross-chain/actions/getCrossChainTrade'
-import { getCrossChainTrades } from 'src/lib/swap/cross-chain/actions/getCrossChainTrades'
-import { ChainId } from 'sushi/chain'
-import { SushiXSwap2ChainId, isSushiXSwap2ChainId } from 'sushi/config'
-import { getAddress } from 'viem'
-import { z } from 'zod'
-
-const schema = z.object({
- adapter: z.optional(z.nativeEnum(SushiXSwap2Adapter)),
- srcChainId: z.coerce
- .number()
- .refine((chainId) => isSushiXSwap2ChainId(chainId as ChainId), {
- message: `srchChainId must exist in SushiXSwapV2ChainId`,
- })
- .transform((chainId) => chainId as SushiXSwap2ChainId),
- dstChainId: z.coerce
- .number()
- .refine((chainId) => isSushiXSwap2ChainId(chainId as ChainId), {
- message: `dstChainId must exist in SushiXSwapV2ChainId`,
- })
- .transform((chainId) => chainId as SushiXSwap2ChainId),
- tokenIn: z.string().transform((token) => getAddress(token)),
- tokenOut: z.string().transform((token) => getAddress(token)),
- amount: z.string().transform((amount) => BigInt(amount)),
- srcGasPrice: z.optional(
- z.coerce
- .number()
- .int('gasPrice should be integer')
- .gt(0, 'gasPrice should be positive')
- .transform((gasPrice) => BigInt(gasPrice)),
- ),
- dstGasPrice: z.optional(
- z.coerce
- .number()
- .int('gasPrice should be integer')
- .gt(0, 'gasPrice should be positive')
- .transform((gasPrice) => BigInt(gasPrice)),
- ),
- from: z
- .optional(z.string())
- .transform((from) => (from ? getAddress(from) : undefined)),
- recipient: z
- .optional(z.string())
- .transform((to) => (to ? getAddress(to) : undefined)),
- preferSushi: z.optional(z.coerce.boolean()),
- maxSlippage: z.coerce
- .number()
- .lt(1, 'maxPriceImpact should be lesser than 1')
- .gt(0, 'maxPriceImpact should be positive'),
-})
-
-export const revalidate = 600
-
-export async function GET(request: NextRequest) {
- const params = Object.fromEntries(request.nextUrl.searchParams.entries())
-
- const { adapter, ...parsedParams } = schema.parse(params)
-
- const getCrossChainTradeParams = {
- ...parsedParams,
- slippagePercentage: (parsedParams.maxSlippage * 100).toString(),
- }
-
- const crossChainSwap = await (typeof adapter === 'undefined'
- ? getCrossChainTrades(getCrossChainTradeParams)
- : getCrossChainTrade({ adapter, ...getCrossChainTradeParams }))
-
- return Response.json(crossChainSwap, {
- headers: {
- 'Cache-Control': 'max-age=60, stale-while-revalidate=600',
- },
- })
-}
diff --git a/apps/web/src/app/(networks)/(evm)/api/cross-chain/routes/route.ts b/apps/web/src/app/(networks)/(evm)/api/cross-chain/routes/route.ts
new file mode 100644
index 0000000000..7198b19dd3
--- /dev/null
+++ b/apps/web/src/app/(networks)/(evm)/api/cross-chain/routes/route.ts
@@ -0,0 +1,80 @@
+import { NextRequest } from 'next/server'
+import { isXSwapSupportedChainId } from 'src/config'
+import { isAddress } from 'viem'
+import { z } from 'zod'
+
+const schema = z.object({
+ fromChainId: z.coerce
+ .number()
+ .refine((chainId) => isXSwapSupportedChainId(chainId), {
+ message: `fromChainId must exist in XSwapChainId`,
+ }),
+ fromAmount: z.string(),
+ fromTokenAddress: z.string().refine((token) => isAddress(token), {
+ message: 'fromTokenAddress does not conform to Address',
+ }),
+ toChainId: z.coerce
+ .number()
+ .refine((chainId) => isXSwapSupportedChainId(chainId), {
+ message: `toChainId must exist in XSwapChainId`,
+ }),
+ toTokenAddress: z.string().refine((token) => isAddress(token), {
+ message: 'toTokenAddress does not conform to Address',
+ }),
+ fromAddress: z
+ .string()
+ .refine((address) => isAddress(address), {
+ message: 'fromAddress does not conform to Address',
+ })
+ .optional(),
+ toAddress: z
+ .string()
+ .refine((address) => isAddress(address), {
+ message: 'toAddress does not conform to Address',
+ })
+ .optional(),
+ slippage: z.coerce.number(), // decimal
+ order: z.enum(['CHEAPEST', 'FASTEST']).optional(),
+})
+
+export const revalidate = 20
+
+export async function GET(request: NextRequest) {
+ const params = Object.fromEntries(request.nextUrl.searchParams.entries())
+
+ const { slippage, order = 'CHEAPEST', ...parsedParams } = schema.parse(params)
+
+ const url = new URL('https://li.quest/v1/advanced/routes')
+
+ const options = {
+ method: 'POST',
+ headers: {
+ accept: 'application/json',
+ 'content-type': 'application/json',
+ ...(process.env.LIFI_API_KEY && {
+ 'x-lifi-api-key': process.env.LIFI_API_KEY,
+ }),
+ },
+ body: JSON.stringify({
+ ...parsedParams,
+ options: {
+ slippage,
+ order,
+ integrator: 'sushi',
+ exchanges: { allow: ['sushiswap'] },
+ allowSwitchChain: false,
+ allowDestinationCall: true,
+ // fee: // TODO: must set up feeReceiver w/ lifi
+ },
+ }),
+ }
+
+ const response = await fetch(url, options)
+
+ return Response.json(await response.json(), {
+ status: response.status,
+ headers: {
+ 'Cache-Control': 's-maxage=15, stale-while-revalidate=20',
+ },
+ })
+}
diff --git a/apps/web/src/app/(networks)/(evm)/api/cross-chain/step/route.ts b/apps/web/src/app/(networks)/(evm)/api/cross-chain/step/route.ts
new file mode 100644
index 0000000000..8eb9cf4ec4
--- /dev/null
+++ b/apps/web/src/app/(networks)/(evm)/api/cross-chain/step/route.ts
@@ -0,0 +1,50 @@
+import { NextRequest } from 'next/server'
+import {
+ crossChainActionSchema,
+ crossChainStepSchema,
+} from 'src/lib/swap/cross-chain/schema'
+import { isAddress, stringify } from 'viem'
+import { z } from 'zod'
+
+const schema = crossChainStepSchema.extend({
+ action: crossChainActionSchema.extend({
+ fromAddress: z.string().refine((address) => isAddress(address), {
+ message: 'fromAddress does not conform to Address',
+ }),
+ toAddress: z.string().refine((address) => isAddress(address), {
+ message: 'toAddress does not conform to Address',
+ }),
+ }),
+})
+
+export async function POST(request: NextRequest) {
+ const params = await request.json()
+
+ const parsedParams = schema.parse(params)
+
+ const url = new URL('https://li.quest/v1/advanced/stepTransaction')
+
+ const options = {
+ method: 'POST',
+ headers: {
+ accept: 'application/json',
+ 'content-type': 'application/json',
+ ...(process.env.LIFI_API_KEY && {
+ 'x-lifi-api-key': process.env.LIFI_API_KEY,
+ }),
+ },
+ body: stringify({
+ ...parsedParams,
+ integrator: 'sushi',
+ }),
+ }
+
+ const response = await fetch(url, options)
+
+ return Response.json(await response.json(), {
+ status: response.status,
+ headers: {
+ 'Cache-Control': 's-maxage=8, stale-while-revalidate=10',
+ },
+ })
+}
diff --git a/apps/web/src/config.ts b/apps/web/src/config.ts
index 98674eb2b8..1c565ee5b4 100644
--- a/apps/web/src/config.ts
+++ b/apps/web/src/config.ts
@@ -112,6 +112,19 @@ export const PREFERRED_CHAINID_ORDER = [
ChainId.BOBA_BNB,
] as const
+export const getSortedChainIds =
(
+ chainIds: readonly T[],
+) => {
+ return Array.from(
+ new Set([
+ ...(PREFERRED_CHAINID_ORDER.filter((el) =>
+ chainIds.includes(el as (typeof chainIds)[number]),
+ ) as T[]),
+ ...chainIds,
+ ]),
+ )
+}
+
export const CHAIN_IDS = [
...SUSHISWAP_SUPPORTED_CHAIN_IDS,
...AGGREGATOR_ONLY_CHAIN_IDS,
@@ -218,3 +231,37 @@ export const isZapSupportedChainId = (
chainId: number,
): chainId is ZapSupportedChainId => false
// ZAP_SUPPORTED_CHAIN_IDS.includes(chainId as ZapSupportedChainId)
+
+export const XSWAP_SUPPORTED_CHAIN_IDS = [
+ ChainId.ARBITRUM,
+ ChainId.AVALANCHE,
+ ChainId.BSC,
+ ChainId.BASE,
+ ChainId.BLAST,
+ ChainId.BOBA,
+ ChainId.CELO,
+ ChainId.CRONOS,
+ ChainId.ETHEREUM,
+ ChainId.FUSE,
+ ChainId.FANTOM,
+ ChainId.GNOSIS,
+ ChainId.LINEA,
+ ChainId.MANTLE,
+ ChainId.METIS,
+ ChainId.MODE,
+ ChainId.MOONBEAM,
+ ChainId.MOONRIVER,
+ ChainId.OPTIMISM,
+ ChainId.POLYGON,
+ ChainId.POLYGON_ZKEVM,
+ ChainId.ROOTSTOCK,
+ ChainId.SCROLL,
+ ChainId.TAIKO,
+ ChainId.ZKSYNC_ERA,
+] as const
+
+export type XSwapSupportedChainId = (typeof XSWAP_SUPPORTED_CHAIN_IDS)[number]
+export const isXSwapSupportedChainId = (
+ chainId: number,
+): chainId is XSwapSupportedChainId =>
+ XSWAP_SUPPORTED_CHAIN_IDS.includes(chainId as XSwapSupportedChainId)
diff --git a/apps/web/src/lib/hooks/api/index.ts b/apps/web/src/lib/hooks/api/index.ts
index e6275b95a7..0b301d7b77 100644
--- a/apps/web/src/lib/hooks/api/index.ts
+++ b/apps/web/src/lib/hooks/api/index.ts
@@ -1,5 +1,4 @@
export * from './useApprovedCommunityTokens'
-export * from './useCrossChainTrade'
export * from './usePoolGraphData'
export * from './usePoolsInfinite'
export * from './userSmartPools'
diff --git a/apps/web/src/lib/hooks/api/useCrossChainTrade.ts b/apps/web/src/lib/hooks/api/useCrossChainTrade.ts
deleted file mode 100644
index 94b368fffc..0000000000
--- a/apps/web/src/lib/hooks/api/useCrossChainTrade.ts
+++ /dev/null
@@ -1,164 +0,0 @@
-import { UseQueryOptions, useQuery } from '@tanstack/react-query'
-import { NativeAddress } from 'src/lib/constants'
-import { apiAdapter02To01 } from 'src/lib/hooks/react-query'
-import {
- CrossChainTradeSchema,
- GetCrossChainTradeParams,
- SushiXSwap2Adapter,
- SushiXSwapFunctionName,
- SushiXSwapTransactionType,
- SushiXSwapWriteArgs,
-} from 'src/lib/swap/cross-chain'
-import { Amount, Native, Token, Type } from 'sushi/currency'
-import { Percent } from 'sushi/math'
-import { RouteStatus } from 'sushi/router'
-import { stringify } from 'viem'
-
-export interface UseCrossChainTradeReturn {
- status: RouteStatus
- adapter: SushiXSwap2Adapter
- tokenIn: Type
- tokenOut: Type
- srcBridgeToken?: Type
- dstBridgeToken?: Type
- amountIn?: Amount
- amountOut?: Amount
- amountOutMin?: Amount
- priceImpact?: Percent
- srcTrade?: ReturnType
- dstTrade?: ReturnType
- transactionType?: SushiXSwapTransactionType
- gasSpent?: string
- bridgeFee?: string
- srcGasFee?: string
- functionName?: SushiXSwapFunctionName
- writeArgs?: SushiXSwapWriteArgs
- value?: string
-}
-
-export interface UseCrossChainTradeParms
- extends Omit<
- GetCrossChainTradeParams,
- 'tokenIn' | 'tokenOut' | 'amount' | 'gasSpent'
- > {
- adapter?: SushiXSwap2Adapter
- tokenIn?: Type
- tokenOut?: Type
- amount?: Amount
- query?: Omit<
- UseQueryOptions,
- 'queryFn' | 'queryKey'
- >
-}
-
-export const useCrossChainTrade = ({
- query,
- ...params
-}: UseCrossChainTradeParms) => {
- const { tokenIn, tokenOut, amount, slippagePercentage, ...rest } = params
-
- return useQuery({
- ...query,
- queryKey: ['cross-chain', params],
- queryFn: async (): Promise => {
- if (!tokenIn || !tokenOut || !amount) throw new Error()
-
- const url = new URL('/api/cross-chain', window.location.origin)
-
- url.searchParams.set(
- 'tokenIn',
- tokenIn.isNative ? NativeAddress : tokenIn.address,
- )
- url.searchParams.set(
- 'tokenOut',
- tokenOut.isNative ? NativeAddress : tokenOut.address,
- )
- url.searchParams.set('amount', amount.quotient.toString())
- url.searchParams.set('maxSlippage', `${+slippagePercentage / 100}`)
-
- Object.entries(rest).forEach(([key, value]) => {
- value && url.searchParams.set(key, value.toString())
- })
-
- const res = await fetch(url.toString())
-
- const json = await res.json()
-
- const parsed = CrossChainTradeSchema.parse(json)
-
- const { status, adapter } = parsed
-
- if (status === RouteStatus.NoWay)
- return {
- status,
- adapter,
- tokenIn,
- tokenOut,
- }
-
- const srcBridgeToken = parsed.srcBridgeToken.isNative
- ? Native.deserialize(parsed.srcBridgeToken)
- : Token.deserialize(parsed.srcBridgeToken)
-
- const dstBridgeToken = parsed.dstBridgeToken.isNative
- ? Native.deserialize(parsed.dstBridgeToken)
- : Token.deserialize(parsed.dstBridgeToken)
-
- const srcTrade = parsed.srcTrade
- ? apiAdapter02To01(
- parsed.srcTrade,
- tokenIn,
- srcBridgeToken,
- parsed.srcTrade?.status !== RouteStatus.NoWay
- ? parsed.srcTrade?.routeProcessorArgs?.to
- : undefined,
- )
- : undefined
-
- const dstTrade = parsed.dstTrade
- ? apiAdapter02To01(
- parsed.dstTrade,
- dstBridgeToken,
- tokenOut,
- parsed.dstTrade.status !== RouteStatus.NoWay
- ? parsed.dstTrade.routeProcessorArgs?.to
- : undefined,
- )
- : undefined
-
- return {
- ...parsed,
- tokenIn,
- tokenOut,
- srcBridgeToken,
- dstBridgeToken,
- srcTrade,
- dstTrade,
- amountIn: Amount.fromRawAmount(tokenIn, parsed.amountIn),
- amountOut: Amount.fromRawAmount(tokenOut, parsed.amountOut),
- amountOutMin: Amount.fromRawAmount(tokenOut, parsed.amountOutMin),
- priceImpact: new Percent(Math.round(parsed.priceImpact * 10000), 10000),
- gasSpent: parsed.gasSpent
- ? Amount.fromRawAmount(
- Native.onChain(tokenIn.chainId),
- parsed.gasSpent,
- ).toFixed(6)
- : undefined,
- bridgeFee: parsed.bridgeFee
- ? Amount.fromRawAmount(
- Native.onChain(tokenIn.chainId),
- parsed.bridgeFee,
- ).toFixed(6)
- : undefined,
- srcGasFee: parsed.srcGasFee
- ? Amount.fromRawAmount(
- Native.onChain(tokenIn.chainId),
- parsed.srcGasFee,
- ).toFixed(6)
- : undefined,
- }
- },
- enabled: query?.enabled !== false && Boolean(tokenIn && tokenOut && amount),
- queryKeyHashFn: stringify,
- })
-}
diff --git a/apps/web/src/lib/hooks/react-query/cross-chain-trade/index.ts b/apps/web/src/lib/hooks/react-query/cross-chain-trade/index.ts
new file mode 100644
index 0000000000..e200dd2f85
--- /dev/null
+++ b/apps/web/src/lib/hooks/react-query/cross-chain-trade/index.ts
@@ -0,0 +1,2 @@
+export * from './useCrossChainTradeRoutes'
+export * from './useCrossChainTradeStep'
diff --git a/apps/web/src/lib/hooks/react-query/cross-chain-trade/types.ts b/apps/web/src/lib/hooks/react-query/cross-chain-trade/types.ts
new file mode 100644
index 0000000000..09c921de92
--- /dev/null
+++ b/apps/web/src/lib/hooks/react-query/cross-chain-trade/types.ts
@@ -0,0 +1,15 @@
+import { z } from 'zod'
+import {
+ crossChainActionSchema,
+ crossChainRouteSchema,
+ crossChainStepSchema,
+ crossChainToolDetailsSchema,
+} from '../../../swap/cross-chain/schema'
+
+export type CrossChainAction = z.infer
+
+export type CrossChainRoute = z.infer
+
+export type CrossChainStep = z.infer
+
+export type CrossChainToolDetails = z.infer
diff --git a/apps/web/src/lib/hooks/react-query/cross-chain-trade/useCrossChainTradeRoutes.ts b/apps/web/src/lib/hooks/react-query/cross-chain-trade/useCrossChainTradeRoutes.ts
new file mode 100644
index 0000000000..6eee8c965b
--- /dev/null
+++ b/apps/web/src/lib/hooks/react-query/cross-chain-trade/useCrossChainTradeRoutes.ts
@@ -0,0 +1,81 @@
+import { UseQueryOptions, useQuery } from '@tanstack/react-query'
+import { Amount, Type } from 'sushi/currency'
+import { Percent } from 'sushi/math'
+import { Address, zeroAddress } from 'viem'
+import { z } from 'zod'
+import { crossChainRouteSchema } from '../../../swap/cross-chain/schema'
+import { CrossChainRoute } from '../../../swap/cross-chain/types'
+
+const crossChainRoutesResponseSchema = z.object({
+ routes: z.array(crossChainRouteSchema),
+})
+
+export interface UseCrossChainTradeRoutesParms {
+ fromAmount?: Amount
+ toToken?: Type
+ fromAddress?: Address
+ toAddress?: Address
+ slippage: Percent
+ order?: 'CHEAPEST' | 'FASTEST'
+ query?: Omit, 'queryFn' | 'queryKey'>
+}
+
+export const useCrossChainTradeRoutes = ({
+ query,
+ ...params
+}: UseCrossChainTradeRoutesParms) => {
+ return useQuery({
+ queryKey: ['cross-chain/routes', params],
+ queryFn: async (): Promise => {
+ const { fromAmount, toToken, slippage } = params
+
+ if (!fromAmount || !toToken) throw new Error()
+
+ const url = new URL('/api/cross-chain/routes', window.location.origin)
+
+ url.searchParams.set(
+ 'fromChainId',
+ fromAmount.currency.chainId.toString(),
+ )
+ url.searchParams.set('toChainId', toToken.chainId.toString())
+ url.searchParams.set(
+ 'fromTokenAddress',
+ fromAmount.currency.isNative
+ ? zeroAddress
+ : fromAmount.currency.address,
+ )
+ url.searchParams.set(
+ 'toTokenAddress',
+ toToken.isNative ? zeroAddress : toToken.address,
+ )
+ url.searchParams.set('fromAmount', fromAmount.quotient.toString())
+ url.searchParams.set('slippage', `${+slippage.toFixed(2) / 100}`)
+ params.fromAddress &&
+ url.searchParams.set('fromAddress', params.fromAddress)
+ params.toAddress ||
+ (params.fromAddress &&
+ url.searchParams.set(
+ 'toAddress',
+ params.toAddress || params.fromAddress,
+ ))
+ params.order && url.searchParams.set('order', params.order)
+
+ const response = await fetch(url)
+
+ if (!response.ok) {
+ throw new Error(response.statusText)
+ }
+
+ const json = await response.json()
+
+ const { routes } = crossChainRoutesResponseSchema.parse(json)
+
+ return routes
+ },
+ refetchInterval: query?.refetchInterval ?? 1000 * 20, // 20s
+ enabled:
+ query?.enabled !== false &&
+ Boolean(params.toToken && params.fromAmount?.greaterThan(0)),
+ ...query,
+ })
+}
diff --git a/apps/web/src/lib/hooks/react-query/cross-chain-trade/useCrossChainTradeStep.ts b/apps/web/src/lib/hooks/react-query/cross-chain-trade/useCrossChainTradeStep.ts
new file mode 100644
index 0000000000..fe31038cff
--- /dev/null
+++ b/apps/web/src/lib/hooks/react-query/cross-chain-trade/useCrossChainTradeStep.ts
@@ -0,0 +1,110 @@
+import { UseQueryOptions, useQuery } from '@tanstack/react-query'
+import { Amount, Native, Token, Type } from 'sushi/currency'
+import { Percent } from 'sushi/math'
+import { zeroAddress } from 'viem'
+import { stringify } from 'viem/utils'
+import { crossChainStepSchema } from '../../../swap/cross-chain/schema'
+import { CrossChainStep } from '../../../swap/cross-chain/types'
+
+export interface UseCrossChainTradeStepReturn extends CrossChainStep {
+ tokenIn: Type
+ tokenOut: Type
+ amountIn?: Amount
+ amountOut?: Amount
+ amountOutMin?: Amount
+ priceImpact?: Percent
+}
+
+export interface UseCrossChainTradeStepParms {
+ step: CrossChainStep | undefined
+ query?: Omit<
+ UseQueryOptions,
+ 'queryFn' | 'queryKey' | 'queryKeyFn'
+ >
+}
+
+export const useCrossChainTradeStep = ({
+ query,
+ ...params
+}: UseCrossChainTradeStepParms) => {
+ return useQuery({
+ queryKey: ['cross-chain/step', params],
+ queryFn: async () => {
+ const { step } = params
+
+ if (!step) throw new Error()
+
+ const url = new URL('/api/cross-chain/step', window.location.origin)
+
+ const options = {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ body: stringify(step),
+ }
+
+ const response = await fetch(url, options)
+
+ if (!response.ok) {
+ throw new Error(response.statusText)
+ }
+
+ const json = await response.json()
+
+ const parsedStep = crossChainStepSchema.parse(json)
+
+ const tokenIn =
+ parsedStep.action.fromToken.address === zeroAddress
+ ? Native.onChain(parsedStep.action.fromToken.chainId)
+ : new Token(parsedStep.action.fromToken)
+
+ const tokenOut =
+ parsedStep.action.toToken.address === zeroAddress
+ ? Native.onChain(parsedStep.action.toToken.chainId)
+ : new Token(parsedStep.action.toToken)
+
+ const amountIn = Amount.fromRawAmount(
+ tokenIn,
+ parsedStep.action.fromAmount,
+ )
+ const amountOut = Amount.fromRawAmount(
+ tokenOut,
+ parsedStep.estimate.toAmount,
+ )
+ const amountOutMin = Amount.fromRawAmount(
+ tokenOut,
+ parsedStep.estimate.toAmountMin,
+ )
+
+ const fromAmountUSD =
+ (Number(parsedStep.action.fromToken.priceUSD) *
+ Number(amountIn.quotient)) /
+ 10 ** tokenIn.decimals
+
+ const toAmountUSD =
+ (Number(parsedStep.action.toToken.priceUSD) *
+ Number(amountOut.quotient)) /
+ 10 ** tokenOut.decimals
+
+ const priceImpact = new Percent(
+ Math.floor((fromAmountUSD / toAmountUSD - 1) * 10_000),
+ 10_000,
+ )
+
+ return {
+ ...parsedStep,
+ tokenIn,
+ tokenOut,
+ amountIn,
+ amountOut,
+ amountOutMin,
+ priceImpact,
+ }
+ },
+ refetchInterval: query?.refetchInterval ?? 1000 * 10, // 10s
+ enabled: query?.enabled !== false && Boolean(params.step),
+ queryKeyHashFn: stringify,
+ ...query,
+ })
+}
diff --git a/apps/web/src/lib/hooks/react-query/index.ts b/apps/web/src/lib/hooks/react-query/index.ts
index 80de98a612..ece2de67b6 100644
--- a/apps/web/src/lib/hooks/react-query/index.ts
+++ b/apps/web/src/lib/hooks/react-query/index.ts
@@ -1,3 +1,4 @@
+export * from './cross-chain-trade'
export * from './pools'
export * from './prices'
export * from './rewards'
diff --git a/apps/web/src/lib/swap/cross-chain/actions/getCrossChainTrade.ts b/apps/web/src/lib/swap/cross-chain/actions/getCrossChainTrade.ts
deleted file mode 100644
index f3c5670d6b..0000000000
--- a/apps/web/src/lib/swap/cross-chain/actions/getCrossChainTrade.ts
+++ /dev/null
@@ -1,91 +0,0 @@
-import { tradeValidator02 } from 'src/lib/hooks/react-query'
-import { SushiXSwap2ChainId } from 'sushi/config'
-import { RouteStatus } from 'sushi/router'
-import { Address } from 'viem'
-import { z } from 'zod'
-import {
- SushiXSwap2Adapter,
- SushiXSwapFunctionName,
- SushiXSwapTransactionType,
-} from '../lib'
-import { getSquidCrossChainTrade } from './getSquidCrossChainTrade'
-import { getStargateCrossChainTrade } from './getStargateCrossChainTrade'
-
-export interface GetCrossChainTradeParams {
- srcChainId: SushiXSwap2ChainId
- dstChainId: SushiXSwap2ChainId
- tokenIn: Address
- tokenOut: Address
- amount: bigint
- srcGasPrice?: bigint
- dstGasPrice?: bigint
- slippagePercentage: string
- from?: Address
- recipient?: Address
-}
-
-const currencyValidator = z.union([
- z.object({
- isNative: z.literal(true),
- name: z.optional(z.string()),
- symbol: z.optional(z.string()),
- decimals: z.number(),
- chainId: z.number(),
- }),
- z.object({
- isNative: z.literal(false),
- name: z.optional(z.string()),
- symbol: z.optional(z.string()),
- address: z.string(),
- decimals: z.number(),
- chainId: z.number(),
- }),
-])
-
-const CrossChainTradeNotFoundSchema = z.object({
- adapter: z.nativeEnum(SushiXSwap2Adapter),
- status: z.enum([RouteStatus.NoWay]),
-})
-
-const CrossChainTradeFoundSchema = z.object({
- status: z.enum([RouteStatus.Success, RouteStatus.Partial]),
- adapter: z.nativeEnum(SushiXSwap2Adapter),
- tokenIn: z.string(),
- tokenOut: z.string(),
- srcBridgeToken: currencyValidator,
- dstBridgeToken: currencyValidator,
- amountIn: z.string(),
- amountOut: z.string(),
- amountOutMin: z.string(),
- priceImpact: z.number(),
- srcTrade: z.optional(tradeValidator02),
- dstTrade: z.optional(tradeValidator02),
- transactionType: z.optional(z.nativeEnum(SushiXSwapTransactionType)),
- gasSpent: z.optional(z.string()),
- bridgeFee: z.optional(z.string()),
- srcGasFee: z.optional(z.string()),
- functionName: z.optional(z.nativeEnum(SushiXSwapFunctionName)),
- writeArgs: z.optional(
- z.array(z.union([z.string(), z.object({}).passthrough()])),
- ),
- value: z.optional(z.string()),
-})
-
-export const CrossChainTradeSchema = z.union([
- CrossChainTradeNotFoundSchema,
- CrossChainTradeFoundSchema,
-])
-
-export type CrossChainTradeSchemaType = z.infer
-
-export const getCrossChainTrade = async ({
- adapter,
- ...params
-}: GetCrossChainTradeParams & { adapter: SushiXSwap2Adapter }) => {
- switch (adapter) {
- case SushiXSwap2Adapter.Squid:
- return getSquidCrossChainTrade(params)
- case SushiXSwap2Adapter.Stargate:
- return getStargateCrossChainTrade(params)
- }
-}
diff --git a/apps/web/src/lib/swap/cross-chain/actions/getCrossChainTrades.ts b/apps/web/src/lib/swap/cross-chain/actions/getCrossChainTrades.ts
deleted file mode 100644
index cfbeb45d5c..0000000000
--- a/apps/web/src/lib/swap/cross-chain/actions/getCrossChainTrades.ts
+++ /dev/null
@@ -1,12 +0,0 @@
-import { GetCrossChainTradeParams } from './getCrossChainTrade'
-import { getSquidCrossChainTrade } from './getSquidCrossChainTrade'
-import { getStargateCrossChainTrade } from './getStargateCrossChainTrade'
-
-export const getCrossChainTrades = async (params: GetCrossChainTradeParams) => {
- return (
- await Promise.all([
- getSquidCrossChainTrade(params),
- getStargateCrossChainTrade(params),
- ])
- ).filter((resp) => !!resp)
-}
diff --git a/apps/web/src/lib/swap/cross-chain/actions/getSquidCrossChainTrade.ts b/apps/web/src/lib/swap/cross-chain/actions/getSquidCrossChainTrade.ts
deleted file mode 100644
index 1a7274a260..0000000000
--- a/apps/web/src/lib/swap/cross-chain/actions/getSquidCrossChainTrade.ts
+++ /dev/null
@@ -1,373 +0,0 @@
-import {
- ChainType,
- DexName,
- Hook,
- RouteRequest,
- SquidCallType,
-} from '@0xsquid/squid-types'
-import { NativeAddress } from 'src/lib/constants'
-import { routeProcessor4Abi_processRoute, squidRouterAbi } from 'sushi/abi'
-import {
- ROUTE_PROCESSOR_4_ADDRESS,
- SQUID_ADAPTER_ADDRESS,
- SQUID_ROUTER_ADDRESS,
- isSquidAdapterChainId,
-} from 'sushi/config'
-import { axlUSDC } from 'sushi/currency'
-import { RouteStatus, RouterLiquiditySource } from 'sushi/router'
-import { Address, Hex, encodeFunctionData, erc20Abi, zeroAddress } from 'viem'
-import {
- SushiXSwap2Adapter,
- SushiXSwapFunctionName,
- SushiXSwapTransactionType,
- applySlippage,
- decodeSquidRouterCallData,
- encodeRouteProcessorArgs,
- encodeSquidBridgeParams,
- getSquidTrade,
- isSquidRouteProcessorEnabled,
-} from '../lib'
-import {
- CrossChainTradeSchemaType,
- GetCrossChainTradeParams,
-} from './getCrossChainTrade'
-import { getSquidRoute } from './getSquidRoute'
-import {
- SuccessfulTradeReturn,
- getTrade,
- isSuccessfulTradeReturn,
-} from './getTrade'
-
-export const getSquidCrossChainTrade = async ({
- srcChainId,
- dstChainId,
- tokenIn,
- tokenOut,
- amount,
- slippagePercentage,
- srcGasPrice,
- dstGasPrice,
- from,
- recipient,
-}: GetCrossChainTradeParams): Promise => {
- try {
- const bridgePath =
- isSquidAdapterChainId(srcChainId) && isSquidAdapterChainId(dstChainId)
- ? {
- srcBridgeToken: axlUSDC[srcChainId],
- dstBridgeToken: axlUSDC[dstChainId],
- }
- : undefined
-
- if (!bridgePath) {
- throw new Error('getSquidCrossChainTrade: no bridge route found')
- }
- const { srcBridgeToken, dstBridgeToken } = bridgePath
-
- // has swap on source chain
- const isSrcSwap = Boolean(
- tokenIn.toLowerCase() !== srcBridgeToken.address.toLowerCase(),
- )
-
- // has swap on destination chain
- const isDstSwap = Boolean(
- tokenOut.toLowerCase() !== dstBridgeToken.address.toLowerCase(),
- )
-
- // whether to use RP for routing, uses to Squid when
- // no liquidity through RP-compatible pools
- const useRPOnSrc = Boolean(
- isSrcSwap && isSquidRouteProcessorEnabled[srcChainId],
- )
- const useRPOnDst = Boolean(
- isDstSwap &&
- isSquidRouteProcessorEnabled[dstChainId] &&
- Boolean(isSrcSwap ? useRPOnSrc : true),
- )
-
- const _srcRPTrade = useRPOnSrc
- ? await getTrade({
- chainId: srcChainId,
- amount,
- fromToken: tokenIn,
- toToken: srcBridgeToken.address,
- slippagePercentage,
- gasPrice: srcGasPrice,
- recipient: SQUID_ROUTER_ADDRESS[srcChainId],
- source: RouterLiquiditySource.XSwap,
- })
- : undefined
-
- if (useRPOnSrc && !isSuccessfulTradeReturn(_srcRPTrade!)) {
- throw new Error('getSquidCrossChainTrade: srcRPTrade failed')
- }
-
- const srcRPTrade = useRPOnSrc
- ? (_srcRPTrade as SuccessfulTradeReturn)
- : undefined
-
- const dstAmountIn = useRPOnSrc
- ? BigInt(srcRPTrade!.assumedAmountOut)
- : amount
-
- const _dstRPTrade = useRPOnDst
- ? await getTrade({
- chainId: dstChainId,
- amount: dstAmountIn,
- fromToken: dstBridgeToken.address,
- toToken: tokenOut,
- slippagePercentage,
- gasPrice: dstGasPrice,
- recipient,
- source: RouterLiquiditySource.XSwap,
- })
- : undefined
-
- if (useRPOnDst && !isSuccessfulTradeReturn(_dstRPTrade!)) {
- throw new Error('getSquidCrossChainTrade: dstRPTrade failed')
- }
-
- const dstRPTrade = useRPOnDst
- ? (_dstRPTrade as SuccessfulTradeReturn)
- : undefined
-
- const routeRequest: RouteRequest = {
- fromAddress: from ?? zeroAddress,
- toAddress: recipient ?? zeroAddress,
- fromChain: srcChainId.toString(),
- toChain: dstChainId.toString(),
- fromToken: useRPOnSrc ? srcBridgeToken.address : tokenIn,
- toToken: useRPOnDst ? dstBridgeToken.address : tokenOut,
- fromAmount: dstAmountIn.toString(),
- slippage: +slippagePercentage,
- prefer: [DexName.SUSHISWAP_V3, DexName.SUSHISWAP_V2],
- quoteOnly: !from || !recipient,
- }
-
- if (useRPOnDst && dstRPTrade?.routeProcessorArgs) {
- const rpAddress = ROUTE_PROCESSOR_4_ADDRESS[dstChainId]
-
- // Transfer dstBridgeToken to RouteProcessor & call ProcessRoute()
- routeRequest.postHook = {
- chainType: ChainType.EVM,
- calls: [
- // Transfer full balance of dstBridgeToken to RouteProcessor
- {
- chainType: ChainType.EVM,
- callType: SquidCallType.FULL_TOKEN_BALANCE,
- target: dstBridgeToken.address,
- callData: encodeFunctionData({
- abi: erc20Abi,
- functionName: 'transfer',
- args: [rpAddress, 0n],
- }),
- value: '0',
- payload: {
- tokenAddress: dstBridgeToken.address,
- inputPos: 1,
- },
- estimatedGas: '30000',
- },
- // Invoke RouteProcessor.processRoute()
- {
- chainType: ChainType.EVM,
- callType: SquidCallType.DEFAULT,
- target: rpAddress,
- callData: encodeFunctionData({
- abi: routeProcessor4Abi_processRoute,
- functionName: 'processRoute',
- args: [
- dstRPTrade.routeProcessorArgs.tokenIn as Address,
- BigInt(dstRPTrade.routeProcessorArgs.amountIn),
- dstRPTrade.routeProcessorArgs.tokenOut as Address,
- BigInt(dstRPTrade.routeProcessorArgs.amountOutMin),
- dstRPTrade.routeProcessorArgs.to as Address,
- dstRPTrade.routeProcessorArgs.routeCode as Hex,
- ],
- }),
- value: '0',
- payload: {
- tokenAddress: zeroAddress,
- inputPos: 0,
- },
- estimatedGas: (1.2 * dstRPTrade!.gasSpent + 20_000).toString(),
- },
- ],
- description: `Swap ${tokenIn} -> ${tokenOut} on RouteProcessor`,
- } as Hook
- }
-
- const { route: squidRoute } = await getSquidRoute(routeRequest)
-
- const srcSquidTrade =
- isSrcSwap && !useRPOnSrc
- ? getSquidTrade(squidRoute.estimate.fromToken, srcBridgeToken)
- : undefined
-
- const dstSquidTrade =
- isDstSwap && !useRPOnDst
- ? getSquidTrade(dstBridgeToken, squidRoute.estimate.toToken)
- : undefined
-
- const dstAmountOut = useRPOnDst
- ? BigInt(dstRPTrade!.assumedAmountOut)
- : BigInt(squidRoute.estimate.toAmount)
-
- const dstAmountOutMin =
- useRPOnSrc && !isDstSwap
- ? applySlippage(srcRPTrade!.assumedAmountOut, slippagePercentage)
- : useRPOnDst
- ? applySlippage(dstRPTrade!.assumedAmountOut, slippagePercentage)
- : BigInt(squidRoute.estimate.toAmountMin)
-
- let priceImpact = 0
- if (useRPOnSrc) {
- priceImpact += srcRPTrade!.priceImpact
- }
- if (useRPOnDst) {
- priceImpact += dstRPTrade!.priceImpact
- }
-
- priceImpact += +squidRoute.estimate.aggregatePriceImpact / 100
-
- let writeArgs
- let functionName
- const transactionType =
- !isSrcSwap && !isDstSwap
- ? SushiXSwapTransactionType.Bridge
- : isSrcSwap && !isDstSwap
- ? SushiXSwapTransactionType.SwapAndBridge
- : !isSrcSwap && isDstSwap
- ? SushiXSwapTransactionType.BridgeAndSwap
- : SushiXSwapTransactionType.CrossChainSwap
-
- const srcTrade = useRPOnSrc ? srcRPTrade : srcSquidTrade
- const dstTrade = useRPOnDst ? dstRPTrade : dstSquidTrade
-
- if (!recipient || !from) {
- return {
- status: RouteStatus.Success,
- adapter: SushiXSwap2Adapter.Squid,
- priceImpact,
- amountIn: amount.toString(),
- amountOut: dstAmountOut.toString(),
- amountOutMin: dstAmountOutMin.toString(),
- tokenIn,
- tokenOut,
- srcBridgeToken: srcBridgeToken.serialize(),
- dstBridgeToken: dstBridgeToken.serialize(),
- srcTrade,
- dstTrade,
- transactionType,
- }
- }
-
- if (useRPOnSrc) {
- const srcSwapData = encodeRouteProcessorArgs(
- (srcTrade as SuccessfulTradeReturn).routeProcessorArgs!,
- )
-
- const squidCallData = decodeSquidRouterCallData(
- squidRoute.transactionRequest?.data as `0x${string}`,
- )
-
- const squidCallArgs =
- squidCallData.args && squidCallData.args.length > 1
- ? [squidCallData.args[0], 0, ...squidCallData.args.slice(2)]
- : undefined
-
- functionName = SushiXSwapFunctionName.SwapAndBridge
- writeArgs = [
- {
- refId: '0x0000',
- adapter: SQUID_ADAPTER_ADDRESS[srcChainId],
- tokenIn,
- amountIn: amount.toString(),
- to: recipient,
- adapterData: encodeSquidBridgeParams({
- srcBridgeToken,
- callData: encodeFunctionData({
- abi: squidRouterAbi,
- functionName: squidCallData.functionName,
- args: squidCallArgs,
- }),
- }),
- },
- recipient, // refundAddress
- srcSwapData, // srcSwapData
- '0x', // dstSwapData
- '0x', // dstPayloadData
- ]
- } else {
- functionName = SushiXSwapFunctionName.Bridge
- writeArgs = [
- {
- refId: '0x0000',
- adapter: SQUID_ADAPTER_ADDRESS[srcChainId],
- tokenIn,
- amountIn: amount.toString(),
- to: recipient,
- adapterData: encodeSquidBridgeParams({
- srcBridgeToken,
- callData: squidRoute.transactionRequest?.data as Hex,
- }),
- },
- recipient, // refundAddress
- '0x', // dstSwapData
- '0x', // dstPayloadData
- ]
- }
-
- // Add 10 % buffer
- const bridgeFee =
- (squidRoute.estimate.feeCosts.reduce(
- (accumulator, current) => accumulator + BigInt(current.amount),
- 0n,
- ) *
- 11n) /
- 10n
-
- const value =
- tokenIn.toLowerCase() === NativeAddress.toLowerCase()
- ? BigInt(amount) + BigInt(bridgeFee)
- : BigInt(bridgeFee)
-
- const srcGasEstimate =
- BigInt(squidRoute.transactionRequest?.gasLimit ?? 0) +
- (useRPOnSrc ? BigInt((srcTrade as SuccessfulTradeReturn).gasSpent) : 0n)
-
- const srcGasFee = srcGasPrice
- ? srcGasPrice * srcGasEstimate
- : srcGasEstimate
-
- const gasSpent = srcGasFee + bridgeFee
-
- return {
- adapter: SushiXSwap2Adapter.Squid,
- status: RouteStatus.Success,
- transactionType,
- tokenIn,
- tokenOut,
- srcBridgeToken: srcBridgeToken.serialize(),
- dstBridgeToken: dstBridgeToken.serialize(),
- amountIn: amount.toString(),
- amountOut: dstAmountOut.toString(),
- amountOutMin: dstAmountOutMin.toString(),
- srcTrade,
- dstTrade,
- priceImpact,
- gasSpent: gasSpent.toString(),
- bridgeFee: bridgeFee.toString(),
- srcGasFee: srcGasFee.toString(),
- writeArgs,
- functionName,
- value: value ? value.toString() : '0',
- }
- } catch (e) {
- console.error(e)
- return {
- adapter: SushiXSwap2Adapter.Squid,
- status: RouteStatus.NoWay,
- }
- }
-}
diff --git a/apps/web/src/lib/swap/cross-chain/actions/getSquidRoute.ts b/apps/web/src/lib/swap/cross-chain/actions/getSquidRoute.ts
deleted file mode 100644
index 5222015a46..0000000000
--- a/apps/web/src/lib/swap/cross-chain/actions/getSquidRoute.ts
+++ /dev/null
@@ -1,25 +0,0 @@
-import { RouteRequest, RouteResponse } from '@0xsquid/squid-types'
-import { SquidApiURL, SquidIntegratorId } from 'sushi/config'
-
-export const getSquidRoute = async (
- params: RouteRequest,
-): Promise => {
- const url = new URL(`${SquidApiURL}/route`)
-
- const response = await fetch(url, {
- method: 'POST',
- body: JSON.stringify(params),
- headers: {
- 'x-integrator-id': SquidIntegratorId,
- 'Content-Type': 'application/json',
- },
- })
-
- const json = await response.json()
-
- if (response.status !== 200) {
- throw new Error(json.message)
- }
-
- return json
-}
diff --git a/apps/web/src/lib/swap/cross-chain/actions/getStargateCrossChainTrade.ts b/apps/web/src/lib/swap/cross-chain/actions/getStargateCrossChainTrade.ts
deleted file mode 100644
index 7e3fb7185f..0000000000
--- a/apps/web/src/lib/swap/cross-chain/actions/getStargateCrossChainTrade.ts
+++ /dev/null
@@ -1,405 +0,0 @@
-import { NativeAddress } from 'src/lib/constants'
-import { stargateAdapterAbi_getFee } from 'sushi/abi'
-import {
- STARGATE_ADAPTER_ADDRESS,
- STARGATE_CHAIN_ID,
- StargateAdapterChainId,
- isStargateAdapterChainId,
- publicClientConfig,
-} from 'sushi/config'
-import { RouteStatus, RouterLiquiditySource } from 'sushi/router'
-import {
- createPublicClient,
- encodeAbiParameters,
- parseAbiParameters,
-} from 'viem'
-import {
- STARGATE_SLIPPAGE_PERCENTAGE,
- SushiXSwap2Adapter,
- SushiXSwapFunctionName,
- SushiXSwapTransactionType,
- applySlippage,
- encodeRouteProcessorArgs,
- encodeStargateTeleportParams,
- estimateStargateDstGas,
- getStargateBridgePath,
-} from '../lib'
-import {
- CrossChainTradeSchemaType,
- GetCrossChainTradeParams,
-} from './getCrossChainTrade'
-import { getStargateFees } from './getStargateFees'
-import {
- SuccessfulTradeReturn,
- getTrade,
- isSuccessfulTradeReturn,
-} from './getTrade'
-
-export const getStargateCrossChainTrade = async ({
- srcChainId,
- dstChainId,
- tokenIn,
- tokenOut,
- amount,
- slippagePercentage,
- srcGasPrice,
- dstGasPrice,
- recipient,
-}: GetCrossChainTradeParams): Promise => {
- try {
- const bridgePath =
- isStargateAdapterChainId(srcChainId) &&
- isStargateAdapterChainId(dstChainId)
- ? getStargateBridgePath({ srcChainId, dstChainId, tokenIn, tokenOut })
- : undefined
-
- if (!bridgePath) {
- throw new Error('getStaragetCrossChainTrade: no bridge route found')
- }
-
- const { srcBridgeToken, dstBridgeToken } = bridgePath
-
- // has swap on source chain
- const isSrcSwap = Boolean(
- srcBridgeToken.isNative
- ? tokenIn.toLowerCase() !== NativeAddress.toLowerCase()
- : tokenIn.toLowerCase() !== srcBridgeToken.address.toLowerCase(),
- )
-
- // has swap on destination chain
- const isDstSwap = Boolean(
- dstBridgeToken.isNative
- ? tokenOut.toLowerCase() !== NativeAddress.toLowerCase()
- : tokenOut.toLowerCase() !== dstBridgeToken.address.toLowerCase(),
- )
-
- const _srcTrade = isSrcSwap
- ? await getTrade({
- chainId: srcChainId,
- amount,
- fromToken: tokenIn,
- toToken: srcBridgeToken.isNative
- ? NativeAddress
- : srcBridgeToken.address,
- slippagePercentage,
- gasPrice: srcGasPrice,
- recipient:
- STARGATE_ADAPTER_ADDRESS[srcChainId as StargateAdapterChainId],
- source: RouterLiquiditySource.XSwap,
- })
- : undefined
-
- if (isSrcSwap && !isSuccessfulTradeReturn(_srcTrade!)) {
- throw new Error('getStaragetCrossChainTrade: srcTrade failed')
- }
-
- const srcTrade = isSrcSwap
- ? (_srcTrade as SuccessfulTradeReturn)
- : undefined
-
- const bridgeFees = await getStargateFees({
- amount: isSrcSwap ? BigInt(srcTrade!.assumedAmountOut) : amount,
- srcBridgeToken,
- dstBridgeToken,
- })
-
- if (!bridgeFees) {
- throw new Error('getStaragetCrossChainTrade: getStargateFees failed')
- }
-
- const [eqFee, eqReward, lpFee, protocolFee] = bridgeFees
-
- const bridgeFeeAmount = eqFee - eqReward + lpFee + protocolFee
-
- const bridgeImpact =
- Number(bridgeFeeAmount) /
- Number(isSrcSwap ? srcTrade!.assumedAmountOut : amount)
-
- const srcAmountOut =
- (isSrcSwap ? BigInt(srcTrade!.assumedAmountOut) : amount) -
- bridgeFeeAmount
-
- const srcAmountOutMin = applySlippage(
- isSrcSwap
- ? applySlippage(srcTrade!.assumedAmountOut, slippagePercentage) -
- bridgeFeeAmount
- : srcAmountOut,
- STARGATE_SLIPPAGE_PERCENTAGE,
- )
-
- // adapted from amountSDtoLD in https://www.npmjs.com/package/@layerzerolabs/sg-sdk
- const dstAmountIn =
- (srcAmountOut * 10n ** BigInt(dstBridgeToken.decimals)) /
- 10n ** BigInt(srcBridgeToken.decimals)
- const dstAmountInMin =
- (srcAmountOutMin * 10n ** BigInt(dstBridgeToken.decimals)) /
- 10n ** BigInt(srcBridgeToken.decimals)
-
- const _dstTrade = isDstSwap
- ? await getTrade({
- chainId: dstChainId,
- amount: dstAmountIn,
- fromToken: dstBridgeToken.isNative
- ? NativeAddress
- : dstBridgeToken.address,
- toToken: tokenOut,
- slippagePercentage,
- gasPrice: dstGasPrice,
- recipient,
- source: RouterLiquiditySource.XSwap,
- })
- : undefined
-
- if (isDstSwap && !isSuccessfulTradeReturn(_dstTrade!)) {
- throw new Error('getStaragetCrossChainTrade: dstTrade failed')
- }
-
- const dstTrade = isDstSwap
- ? (_dstTrade as SuccessfulTradeReturn)
- : undefined
-
- const dstAmountOut = isDstSwap
- ? BigInt(dstTrade!.assumedAmountOut)
- : dstAmountIn
-
- const dstAmountOutMin = isDstSwap
- ? applySlippage(dstTrade!.assumedAmountOut, slippagePercentage)
- : dstAmountInMin
-
- let priceImpact = bridgeImpact
- if (isSrcSwap) priceImpact += srcTrade!.priceImpact
- if (isDstSwap) priceImpact += dstTrade!.priceImpact
-
- if (!recipient) {
- return {
- adapter: SushiXSwap2Adapter.Stargate,
- status: RouteStatus.Success,
- priceImpact,
- amountIn: amount.toString(),
- amountOut: dstAmountOut.toString(),
- amountOutMin: dstAmountOutMin.toString(),
- tokenIn,
- tokenOut,
- srcBridgeToken: srcBridgeToken.serialize(),
- dstBridgeToken: dstBridgeToken.serialize(),
- srcTrade,
- dstTrade,
- }
- }
-
- let writeArgs
- let functionName
- let dstPayload
- let dstGasEst = 0n
- let transactionType
-
- if (!isSrcSwap && !isDstSwap) {
- transactionType = SushiXSwapTransactionType.Bridge
- functionName = SushiXSwapFunctionName.Bridge
- writeArgs = [
- {
- refId: '0x0000',
- adapter:
- STARGATE_ADAPTER_ADDRESS[srcChainId as StargateAdapterChainId],
- tokenIn,
- amountIn: amount.toString(),
- to: recipient,
- adapterData: encodeStargateTeleportParams({
- srcBridgeToken,
- dstBridgeToken,
- amount: amount,
- amountMin: srcAmountOutMin,
- dustAmount: 0,
- receiver: recipient, // receivier is recipient because no dstPayload
- to: recipient,
- dstGas: dstGasEst,
- }),
- },
- recipient, // refundAddress
- '0x', // swapPayload
- '0x', // payloadData
- ]
- } else if (isSrcSwap && !isDstSwap) {
- const srcSwapData = encodeRouteProcessorArgs(
- srcTrade!.routeProcessorArgs!,
- )
-
- transactionType = SushiXSwapTransactionType.SwapAndBridge
- functionName = SushiXSwapFunctionName.SwapAndBridge
- writeArgs = [
- {
- refId: '0x0000',
- adapter:
- STARGATE_ADAPTER_ADDRESS[srcChainId as StargateAdapterChainId],
- tokenIn,
- amountIn: amount.toString(),
- to: recipient,
- adapterData: encodeStargateTeleportParams({
- srcBridgeToken,
- dstBridgeToken,
- amount: 0, // StargateAdapter sends srcBridgeToken to StargateComposer
- amountMin: srcAmountOutMin,
- dustAmount: 0,
- receiver: recipient, // receivier is recipient because no dstPayload
- to: recipient,
- dstGas: dstGasEst,
- }),
- },
- recipient, // refundAddress
- srcSwapData,
- '0x',
- '0x',
- ]
- } else if (!isSrcSwap) {
- const dstSwapData = encodeRouteProcessorArgs(
- dstTrade!.routeProcessorArgs!,
- )
-
- dstGasEst = estimateStargateDstGas(dstTrade!.gasSpent)
-
- dstPayload = encodeAbiParameters(
- parseAbiParameters('address, bytes, bytes'),
- [
- recipient,
- dstSwapData,
- '0x', // payloadData
- ],
- )
-
- transactionType = SushiXSwapTransactionType.BridgeAndSwap
- functionName = SushiXSwapFunctionName.Bridge
- writeArgs = [
- {
- refId: '0x0000',
- adapter:
- STARGATE_ADAPTER_ADDRESS[srcChainId as StargateAdapterChainId],
- tokenIn,
- amountIn: amount.toString(),
- to: recipient,
- adapterData: encodeStargateTeleportParams({
- srcBridgeToken,
- dstBridgeToken,
- amount: amount,
- amountMin: srcAmountOutMin,
- dustAmount: 0,
- receiver:
- STARGATE_ADAPTER_ADDRESS[dstChainId as StargateAdapterChainId],
- to: recipient,
- dstGas: dstGasEst,
- }),
- },
- recipient, // refundAddress
- dstSwapData,
- '0x', // dstPayload
- ]
- } else if (isSrcSwap && isDstSwap) {
- const srcSwapData = encodeRouteProcessorArgs(
- srcTrade!.routeProcessorArgs!,
- )
- const dstSwapData = encodeRouteProcessorArgs(
- dstTrade!.routeProcessorArgs!,
- )
-
- dstPayload = encodeAbiParameters(
- parseAbiParameters('address, bytes, bytes'),
- [
- recipient, // to
- dstSwapData, // swapData
- '0x', // payloadData
- ],
- )
- dstGasEst = estimateStargateDstGas(dstTrade!.gasSpent)
-
- transactionType = SushiXSwapTransactionType.CrossChainSwap
- functionName = SushiXSwapFunctionName.SwapAndBridge
- writeArgs = [
- {
- refId: '0x0000',
- adapter:
- STARGATE_ADAPTER_ADDRESS[srcChainId as StargateAdapterChainId],
- tokenIn,
- amountIn: amount.toString(),
- to: recipient,
- adapterData: encodeStargateTeleportParams({
- srcBridgeToken,
- dstBridgeToken,
- amount: 0, // StargateAdapter sends srcBridgeToken to StargateComposer
- amountMin: srcAmountOutMin,
- dustAmount: 0,
- receiver:
- STARGATE_ADAPTER_ADDRESS[dstChainId as StargateAdapterChainId],
- to: recipient,
- dstGas: dstGasEst,
- }),
- },
- recipient, // refundAddress
- srcSwapData, //srcSwapPayload
- dstSwapData, // dstPayload
- '0x',
- ]
- } else {
- throw new Error('Crosschain swap not found.')
- }
-
- const client = createPublicClient(publicClientConfig[srcChainId])
-
- let [lzFee] = await client.readContract({
- address: STARGATE_ADAPTER_ADDRESS[srcChainId as StargateAdapterChainId],
- abi: stargateAdapterAbi_getFee,
- functionName: 'getFee',
- args: [
- STARGATE_CHAIN_ID[dstChainId as StargateAdapterChainId], // dstChain
- 1, // functionType
- isDstSwap
- ? STARGATE_ADAPTER_ADDRESS[dstChainId as StargateAdapterChainId]
- : recipient, // receiver
- dstGasEst, // gasAmount
- 0n, // dustAmount
- isDstSwap ? dstPayload! : '0x', // payload
- ],
- })
-
- // Add 20% buffer to LZ fee
- lzFee = (lzFee * 5n) / 4n
-
- const value =
- tokenIn.toLowerCase() === NativeAddress.toLowerCase()
- ? BigInt(amount) + lzFee
- : lzFee
-
- // est 500K gas for XSwapV2 call
- const srcGasEst = 500000n + BigInt(srcTrade?.gasSpent ?? 0)
-
- const srcGasFee = srcGasPrice ? srcGasPrice * srcGasEst : srcGasEst
-
- const gasSpent = srcGasFee + lzFee
-
- return {
- adapter: SushiXSwap2Adapter.Stargate,
- status: RouteStatus.Success,
- transactionType,
- tokenIn,
- tokenOut,
- srcBridgeToken: srcBridgeToken.serialize(),
- dstBridgeToken: dstBridgeToken.serialize(),
- amountIn: amount.toString(),
- amountOut: dstAmountOut.toString(),
- amountOutMin: dstAmountOutMin.toString(),
- srcTrade,
- dstTrade,
- priceImpact,
- gasSpent: gasSpent.toString(),
- bridgeFee: lzFee.toString(),
- srcGasFee: srcGasFee.toString(),
- writeArgs,
- functionName,
- value: value ? value.toString() : '0',
- }
- } catch (e) {
- console.error(e)
- return {
- adapter: SushiXSwap2Adapter.Stargate,
- status: RouteStatus.NoWay,
- }
- }
-}
diff --git a/apps/web/src/lib/swap/cross-chain/actions/getStargateFees.ts b/apps/web/src/lib/swap/cross-chain/actions/getStargateFees.ts
deleted file mode 100644
index 1dcba0edaf..0000000000
--- a/apps/web/src/lib/swap/cross-chain/actions/getStargateFees.ts
+++ /dev/null
@@ -1,180 +0,0 @@
-import {
- stargateFeeLibraryV03Abi_getFees,
- stargatePoolAbi_feeLibrary,
- stargatePoolAbi_getChainPath,
- stargatePoolAbi_sharedDecimals,
-} from 'sushi/abi'
-import {
- STARGATE_ADAPTER_ADDRESS,
- STARGATE_CHAIN_ID,
- STARGATE_ETH_ADDRESS,
- STARGATE_POOL_ADDRESS,
- STARGATE_POOL_ID,
- StargateAdapterChainId,
- StargateChainId,
- publicClientConfig,
-} from 'sushi/config'
-import { Amount, Type } from 'sushi/currency'
-import { Address, createPublicClient, zeroAddress } from 'viem'
-
-interface GetStargateFeesParams {
- amount: bigint
- srcBridgeToken: Type
- dstBridgeToken: Type
-}
-
-export const getStargateFees = async ({
- amount: _amount,
- srcBridgeToken,
- dstBridgeToken,
-}: GetStargateFeesParams) => {
- if (!_amount) return undefined
-
- const client = createPublicClient(publicClientConfig[srcBridgeToken.chainId])
-
- const stargatePoolResults = await getStargatePool({
- srcBridgeToken,
- dstBridgeToken,
- })
-
- const amount = Amount.fromRawAmount(srcBridgeToken, _amount)
-
- const adjusted = (() => {
- if (!stargatePoolResults?.[2]?.result) return undefined
- const localDecimals = BigInt(amount.currency.decimals)
- const sharedDecimals = stargatePoolResults[2].result
- if (localDecimals === sharedDecimals) return amount
- return localDecimals > sharedDecimals
- ? amount.asFraction.divide(10n ** (localDecimals - sharedDecimals))
- : amount.asFraction.multiply(10n ** (sharedDecimals - localDecimals))
- })()
-
- const feesResults = await client.readContract({
- address: stargatePoolResults?.[1]?.result ?? zeroAddress,
- functionName: 'getFees',
- args: [
- BigInt(
- STARGATE_POOL_ID[srcBridgeToken.chainId][
- srcBridgeToken.isNative
- ? STARGATE_ETH_ADDRESS[
- srcBridgeToken.chainId as keyof typeof STARGATE_ETH_ADDRESS
- ]
- : srcBridgeToken.address
- ] ?? 0,
- ),
- BigInt(
- STARGATE_POOL_ID[dstBridgeToken.chainId][
- dstBridgeToken.isNative
- ? STARGATE_ETH_ADDRESS[
- dstBridgeToken.chainId as keyof typeof STARGATE_ETH_ADDRESS
- ]
- : dstBridgeToken.address
- ] ?? 0,
- ),
- STARGATE_CHAIN_ID[dstBridgeToken.chainId as StargateChainId],
- STARGATE_ADAPTER_ADDRESS[
- srcBridgeToken.chainId as StargateAdapterChainId
- ] as Address,
- BigInt(adjusted?.quotient ?? 0),
- ],
- abi: stargateFeeLibraryV03Abi_getFees,
- })
-
- if (
- !amount ||
- !feesResults ||
- !stargatePoolResults?.[1]?.result ||
- !stargatePoolResults?.[2]?.result ||
- !srcBridgeToken ||
- !dstBridgeToken
- ) {
- return undefined
- }
-
- const localDecimals = BigInt(amount.currency.decimals)
- const sharedDecimals = stargatePoolResults[2].result
-
- const { eqFee, eqReward, lpFee, protocolFee } = feesResults
-
- if (localDecimals === sharedDecimals)
- return [eqFee, eqReward, lpFee, protocolFee]
-
- const _eqFee =
- localDecimals > sharedDecimals
- ? eqFee * 10n ** (localDecimals - sharedDecimals)
- : eqFee / 10n ** (sharedDecimals - localDecimals)
-
- const _eqReward =
- localDecimals > sharedDecimals
- ? eqReward * 10n ** (localDecimals - sharedDecimals)
- : eqReward / 10n ** (sharedDecimals - localDecimals)
-
- const _lpFee =
- localDecimals > sharedDecimals
- ? lpFee * 10n ** (localDecimals - sharedDecimals)
- : lpFee / 10n ** (sharedDecimals - localDecimals)
-
- const _protocolFee =
- localDecimals > sharedDecimals
- ? protocolFee * 10n ** (localDecimals - sharedDecimals)
- : protocolFee / 10n ** (sharedDecimals - localDecimals)
-
- return [_eqFee, _eqReward, _lpFee, _protocolFee]
-}
-
-const getStargatePool = async ({
- srcBridgeToken,
- dstBridgeToken,
-}: Omit) => {
- const client = createPublicClient(publicClientConfig[srcBridgeToken.chainId])
-
- return client.multicall({
- contracts: [
- {
- address: STARGATE_POOL_ADDRESS[srcBridgeToken.chainId][
- srcBridgeToken.isNative
- ? STARGATE_ETH_ADDRESS[
- srcBridgeToken.chainId as keyof typeof STARGATE_ETH_ADDRESS
- ]
- : srcBridgeToken.address
- ] as Address,
- functionName: 'getChainPath',
- args: [
- STARGATE_CHAIN_ID[dstBridgeToken.chainId as StargateChainId],
- BigInt(
- STARGATE_POOL_ID[dstBridgeToken.chainId][
- dstBridgeToken.isNative
- ? STARGATE_ETH_ADDRESS[
- dstBridgeToken.chainId as keyof typeof STARGATE_ETH_ADDRESS
- ]
- : dstBridgeToken.address
- ] ?? 0,
- ),
- ],
- abi: stargatePoolAbi_getChainPath,
- },
- {
- address: STARGATE_POOL_ADDRESS[srcBridgeToken.chainId][
- srcBridgeToken.isNative
- ? STARGATE_ETH_ADDRESS[
- srcBridgeToken.chainId as keyof typeof STARGATE_ETH_ADDRESS
- ]
- : srcBridgeToken.address
- ] as Address,
- functionName: 'feeLibrary',
- abi: stargatePoolAbi_feeLibrary,
- },
- {
- address: STARGATE_POOL_ADDRESS[srcBridgeToken.chainId][
- srcBridgeToken.isNative
- ? STARGATE_ETH_ADDRESS[
- srcBridgeToken.chainId as keyof typeof STARGATE_ETH_ADDRESS
- ]
- : srcBridgeToken.address
- ] as Address,
- functionName: 'sharedDecimals',
- abi: stargatePoolAbi_sharedDecimals,
- },
- ] as const,
- })
-}
diff --git a/apps/web/src/lib/swap/cross-chain/actions/getTrade.ts b/apps/web/src/lib/swap/cross-chain/actions/getTrade.ts
deleted file mode 100644
index d159a6516a..0000000000
--- a/apps/web/src/lib/swap/cross-chain/actions/getTrade.ts
+++ /dev/null
@@ -1,56 +0,0 @@
-import {
- UseTradeParams,
- // getTradeQueryApiVersion,
- tradeValidator02,
-} from 'src/lib/hooks/react-query'
-import { API_BASE_URL } from 'sushi/config'
-import { RouteStatus } from 'sushi/router'
-import { Address } from 'viem'
-import { z } from 'zod'
-
-export type GetTrade = Pick<
- UseTradeParams,
- 'chainId' | 'gasPrice' | 'slippagePercentage' | 'recipient' | 'source'
-> & {
- fromToken: Address
- toToken: Address
- amount: bigint
-}
-
-export type GetTradeReturn = z.infer
-
-export type SuccessfulTradeReturn = Extract<
- GetTradeReturn,
- { status: 'Success' | 'Partial' }
->
-
-export const isSuccessfulTradeReturn = (
- trade: GetTradeReturn,
-): trade is SuccessfulTradeReturn => trade.status === RouteStatus.Success
-
-export const getTrade = async ({
- chainId,
- fromToken,
- toToken,
- amount,
- gasPrice = 50n,
- slippagePercentage,
- recipient,
- source,
-}: GetTrade) => {
- const params = new URL(`${API_BASE_URL}/swap/v4/${chainId}`)
- params.searchParams.set('chainId', `${chainId}`)
- params.searchParams.set('tokenIn', `${fromToken}`)
- params.searchParams.set('tokenOut', `${toToken}`)
- params.searchParams.set('amount', `${amount.toString()}`)
- params.searchParams.set('maxPriceImpact', `${+slippagePercentage / 100}`)
- params.searchParams.set('gasPrice', `${gasPrice}`)
- recipient && params.searchParams.set('to', `${recipient}`)
- params.searchParams.set('preferSushi', 'true')
- if (source !== undefined) params.searchParams.set('source', `${source}`)
-
- const res = await fetch(params.toString())
- const json = await res.json()
- const resp = tradeValidator02.parse(json)
- return resp
-}
diff --git a/apps/web/src/lib/swap/cross-chain/actions/index.ts b/apps/web/src/lib/swap/cross-chain/actions/index.ts
deleted file mode 100644
index 27ea0665af..0000000000
--- a/apps/web/src/lib/swap/cross-chain/actions/index.ts
+++ /dev/null
@@ -1,2 +0,0 @@
-export * from './getCrossChainTrade'
-export * from './getCrossChainTrades'
diff --git a/apps/web/src/lib/swap/cross-chain/hooks/index.ts b/apps/web/src/lib/swap/cross-chain/hooks/index.ts
index 38382c1eec..5bbdfaf967 100644
--- a/apps/web/src/lib/swap/cross-chain/hooks/index.ts
+++ b/apps/web/src/lib/swap/cross-chain/hooks/index.ts
@@ -1,2 +1 @@
-export * from './useAxelarScanLink'
-export * from './useLayerZeroScanLink'
+export * from './useLifiScanLink'
diff --git a/apps/web/src/lib/swap/cross-chain/hooks/useAxelarScanLink.ts b/apps/web/src/lib/swap/cross-chain/hooks/useAxelarScanLink.ts
deleted file mode 100644
index 9cb0bd8ed7..0000000000
--- a/apps/web/src/lib/swap/cross-chain/hooks/useAxelarScanLink.ts
+++ /dev/null
@@ -1,56 +0,0 @@
-import { StatusResponse } from '@0xsquid/squid-types'
-import { useQuery } from '@tanstack/react-query'
-import { ChainId } from 'sushi/chain'
-import { SquidApiURL, SquidIntegratorId } from 'sushi/config'
-
-export const getSquidStatus = async (
- txHash: string,
-): Promise => {
- const url = new URL(`${SquidApiURL}/status`)
- url.searchParams.set('transactionId', txHash)
-
- const response = await fetch(url, {
- headers: {
- 'x-integrator-id': SquidIntegratorId,
- },
- })
-
- const json = await response.json()
-
- return json
-}
-
-export const useAxelarScanLink = ({
- tradeId,
- network0,
- network1,
- txHash,
- enabled,
-}: {
- tradeId: string
- network0: ChainId
- network1: ChainId
- txHash: string | undefined
- enabled?: boolean
-}) => {
- return useQuery({
- queryKey: ['axelarScanLink', { txHash, network0, network1, tradeId }],
- queryFn: async () => {
- if (txHash) {
- return getSquidStatus(txHash).then((data) => ({
- link: data.axelarTransactionUrl,
- status: data.squidTransactionStatus,
- dstTxHash: data.toChain?.transactionId,
- }))
- }
-
- return {
- link: undefined,
- status: undefined,
- dstTxHash: undefined,
- }
- },
- refetchInterval: 2000,
- enabled: enabled && !!txHash,
- })
-}
diff --git a/apps/web/src/lib/swap/cross-chain/hooks/useLayerZeroScanLink.ts b/apps/web/src/lib/swap/cross-chain/hooks/useLayerZeroScanLink.ts
deleted file mode 100644
index c19dd5f18b..0000000000
--- a/apps/web/src/lib/swap/cross-chain/hooks/useLayerZeroScanLink.ts
+++ /dev/null
@@ -1,50 +0,0 @@
-import { createClient } from '@layerzerolabs/scan-client'
-import { useQuery } from '@tanstack/react-query'
-import { ChainId } from 'sushi/chain'
-import { STARGATE_CHAIN_ID } from 'sushi/config'
-
-const client = createClient('mainnet')
-
-export const useLayerZeroScanLink = ({
- tradeId,
- network0,
- network1,
- txHash,
- enabled,
-}: {
- tradeId: string
- network0: ChainId
- network1: ChainId
- txHash: string | undefined
- enabled?: boolean
-}) => {
- return useQuery({
- queryKey: ['lzLink', { txHash, network0, network1, tradeId }],
- queryFn: async () => {
- if (
- txHash &&
- network0 in STARGATE_CHAIN_ID &&
- network1 in STARGATE_CHAIN_ID
- ) {
- const result = await client.getMessagesBySrcTxHash(txHash)
- if (result.messages.length > 0) {
- const { status, dstTxHash } = result.messages[0]
-
- return {
- link: `https://layerzeroscan.com/tx/${txHash}`,
- status,
- dstTxHash,
- }
- }
- }
-
- return {
- link: undefined,
- status: undefined,
- dstTxHash: undefined,
- }
- },
- refetchInterval: 2000,
- enabled: enabled && !!txHash,
- })
-}
diff --git a/apps/web/src/lib/swap/cross-chain/hooks/useLifiScanLink.ts b/apps/web/src/lib/swap/cross-chain/hooks/useLifiScanLink.ts
new file mode 100644
index 0000000000..ae38416e80
--- /dev/null
+++ b/apps/web/src/lib/swap/cross-chain/hooks/useLifiScanLink.ts
@@ -0,0 +1,59 @@
+import { useQuery } from '@tanstack/react-query'
+import { Hex } from 'viem'
+import { z } from 'zod'
+
+const LiFiStatusResponseSchema = z.object({
+ sending: z.object({
+ txHash: z.string().transform((txHash) => txHash as Hex),
+ }),
+ receiving: z
+ .object({
+ txHash: z.string().transform((txHash) => txHash as Hex),
+ })
+ .optional(),
+ lifiExplorerLink: z.string().optional(),
+ status: z.string().optional(),
+ substatus: z.string().optional(),
+})
+
+type LiFiStatusResponseType = z.infer
+
+const getLiFiStatus = async (
+ txHash: string,
+): Promise => {
+ const url = new URL('https://li.quest/v1/status')
+ url.searchParams.set('txHash', txHash)
+
+ const response = await fetch(url)
+
+ const json = await response.json()
+
+ return json
+}
+
+interface UseLiFiStatusParams {
+ txHash: Hex | undefined
+ tradeId: string
+ enabled?: boolean
+}
+
+export const useLiFiStatus = ({
+ txHash,
+ tradeId,
+ enabled = true,
+}: UseLiFiStatusParams) => {
+ return useQuery({
+ queryKey: ['lifiStatus', { tradeId }],
+ queryFn: async () => {
+ if (!txHash) throw new Error('txHash is required')
+
+ return getLiFiStatus(txHash)
+ },
+ refetchInterval: 5000,
+ enabled: ({ state: { data } }) =>
+ enabled &&
+ !!txHash &&
+ data?.status !== 'DONE' &&
+ data?.status !== 'FAILED',
+ })
+}
diff --git a/apps/web/src/lib/swap/cross-chain/index.ts b/apps/web/src/lib/swap/cross-chain/index.ts
index 2b743fece2..6ab3bc1bf8 100644
--- a/apps/web/src/lib/swap/cross-chain/index.ts
+++ b/apps/web/src/lib/swap/cross-chain/index.ts
@@ -1,3 +1,4 @@
-export * from './actions'
export * from './hooks'
-export * from './lib'
+export * from './utils'
+export * from './schema'
+export * from './types'
diff --git a/apps/web/src/lib/swap/cross-chain/lib/SquidAdapter.ts b/apps/web/src/lib/swap/cross-chain/lib/SquidAdapter.ts
deleted file mode 100644
index 631ad020f8..0000000000
--- a/apps/web/src/lib/swap/cross-chain/lib/SquidAdapter.ts
+++ /dev/null
@@ -1,90 +0,0 @@
-import { Token as SquidToken } from '@0xsquid/squid-types'
-import { tradeValidator02 } from 'src/lib/hooks/react-query'
-import { squidRouterAbi } from 'sushi/abi'
-import { ChainId } from 'sushi/chain'
-import { SquidAdapterChainId } from 'sushi/config'
-import { Token } from 'sushi/currency'
-import { RouteStatus } from 'sushi/router'
-import {
- Hex,
- decodeFunctionData,
- encodeAbiParameters,
- parseAbiParameters,
-} from 'viem'
-import { z } from 'zod'
-
-export const isSquidRouteProcessorEnabled: Record<
- SquidAdapterChainId,
- boolean
-> = {
- [ChainId.ETHEREUM]: true,
- [ChainId.BSC]: true,
- [ChainId.AVALANCHE]: true,
- [ChainId.POLYGON]: true,
- [ChainId.ARBITRUM]: true,
- [ChainId.OPTIMISM]: true,
- [ChainId.BASE]: true,
- [ChainId.FANTOM]: true,
- [ChainId.LINEA]: true,
- [ChainId.KAVA]: true,
- [ChainId.MOONBEAM]: false,
- [ChainId.CELO]: true,
- [ChainId.SCROLL]: true,
- [ChainId.FILECOIN]: true,
- [ChainId.BLAST]: true,
-}
-
-/*
- SquidBridgeParams {
- address token; // token being bridged
- bytes squidRouterData; // abi-encoded squidRouter calldata
- }
-*/
-export const encodeSquidBridgeParams = ({
- srcBridgeToken,
- callData,
-}: {
- srcBridgeToken: Token
- callData: Hex
-}) => {
- return encodeAbiParameters(parseAbiParameters('address, bytes'), [
- srcBridgeToken.address,
- callData,
- ])
-}
-
-export const decodeSquidRouterCallData = (data: `0x${string}`) => {
- return decodeFunctionData({ abi: squidRouterAbi, data })
-}
-
-// this is only used for route preview
-export const getSquidTrade = (
- fromToken: SquidToken | Token,
- toToken: SquidToken | Token,
-): z.infer => {
- return {
- status: RouteStatus.Success,
- tokens: [
- {
- name: fromToken.name ?? '',
- symbol: fromToken.symbol ?? '',
- decimals: fromToken.decimals,
- address: fromToken.address,
- },
- {
- name: toToken.name ?? '',
- symbol: toToken.symbol ?? '',
- decimals: toToken.decimals,
- address: toToken.address,
- },
- ],
- tokenFrom: 0,
- tokenTo: 1,
- primaryPrice: 0,
- swapPrice: 0,
- priceImpact: 0,
- amountIn: '',
- assumedAmountOut: '',
- gasSpent: 0,
- }
-}
diff --git a/apps/web/src/lib/swap/cross-chain/lib/StargateAdapter.ts b/apps/web/src/lib/swap/cross-chain/lib/StargateAdapter.ts
deleted file mode 100644
index 76fe5e2f0d..0000000000
--- a/apps/web/src/lib/swap/cross-chain/lib/StargateAdapter.ts
+++ /dev/null
@@ -1,152 +0,0 @@
-import { NativeAddress } from 'src/lib/constants'
-import {
- STARGATE_CHAIN_ID,
- STARGATE_CHAIN_PATHS,
- STARGATE_ETH_ADDRESS,
- STARGATE_POOL_ID,
- STARGATE_USDC,
- STARGATE_USDC_ADDRESS,
- STARGATE_USDT,
- STARGATE_USDT_ADDRESS,
- StargateAdapterChainId,
-} from 'sushi/config'
-import { Native, Type } from 'sushi/currency'
-import { Address, encodeAbiParameters, parseAbiParameters } from 'viem'
-
-export const STARGATE_SLIPPAGE_PERCENTAGE = 1 // 1%
-
-/*
- struct StargateTeleportParams {
- uint16 dstChainId; // stargate dst chain id
- address token; // token getting bridged
- uint256 srcPoolId; // stargate src pool id
- uint256 dstPoolId; // stargate dst pool id
- uint256 amount; // amount to bridge
- uint256 amountMin; // amount to bridge minimum
- uint256 dustAmount; // native token to be received on dst chain
- address receiver; // detination address for sgReceive
- address to; // address for fallback tranfers on sgReceive
- uint256 gas; // extra gas to be sent for dst chain operations
- }
-*/
-
-export const encodeStargateTeleportParams = ({
- srcBridgeToken,
- dstBridgeToken,
- amount,
- amountMin,
- dustAmount,
- receiver,
- to,
- dstGas,
-}: {
- srcBridgeToken: Type
- dstBridgeToken: Type
- amount: Parameters[0]
- amountMin: Parameters[0]
- dustAmount: Parameters[0]
- receiver: Address
- to: Address
- dstGas: Parameters[0]
-}): string => {
- return encodeAbiParameters(
- parseAbiParameters(
- 'uint16, address, uint256, uint256, uint256, uint256, uint256, address, address, uint256',
- ),
- [
- STARGATE_CHAIN_ID[dstBridgeToken.chainId as StargateAdapterChainId],
- srcBridgeToken.isNative
- ? '0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE'
- : (srcBridgeToken.address as Address),
- BigInt(
- STARGATE_POOL_ID[srcBridgeToken.chainId as StargateAdapterChainId][
- srcBridgeToken.isNative
- ? STARGATE_ETH_ADDRESS[
- srcBridgeToken.chainId as keyof typeof STARGATE_ETH_ADDRESS
- ]
- : srcBridgeToken.address
- ],
- ),
- BigInt(
- STARGATE_POOL_ID[dstBridgeToken.chainId as StargateAdapterChainId][
- dstBridgeToken.isNative
- ? STARGATE_ETH_ADDRESS[
- dstBridgeToken.chainId as keyof typeof STARGATE_ETH_ADDRESS
- ]
- : dstBridgeToken.address
- ],
- ),
- BigInt(amount),
- BigInt(amountMin),
- BigInt(dustAmount),
- receiver,
- to,
- BigInt(dstGas),
- ],
- )
-}
-
-// estiamte gas in sgReceive()
-export const estimateStargateDstGas = (gasUsed: number) => {
- // estGas = (150K + gasSpentTines * 1.25)
- return BigInt(Math.floor(gasUsed * 1.25) + 150000)
-}
-
-export const getStargateBridgePath = ({
- srcChainId,
- dstChainId,
- tokenIn,
-}: {
- srcChainId: StargateAdapterChainId
- dstChainId: StargateAdapterChainId
- tokenIn: Address
- tokenOut: Address
-}) => {
- const srcChainPaths = STARGATE_CHAIN_PATHS[srcChainId]
-
- // If srcCurrency is ETH, check for ETH path
- if (
- tokenIn.toLowerCase() === NativeAddress.toLowerCase() &&
- srcChainId in STARGATE_ETH_ADDRESS
- ) {
- const ethPaths =
- srcChainPaths[
- STARGATE_ETH_ADDRESS[srcChainId as keyof typeof STARGATE_ETH_ADDRESS]
- ]
-
- if (
- ethPaths.find((dstBridgeToken) => dstBridgeToken.chainId === dstChainId)
- ) {
- return {
- srcBridgeToken: Native.onChain(srcChainId),
- dstBridgeToken: Native.onChain(dstChainId),
- }
- }
- }
-
- // Else fallback to USDC/USDT
- if (
- srcChainId in STARGATE_USDC_ADDRESS ||
- srcChainId in STARGATE_USDT_ADDRESS
- ) {
- const srcBridgeToken =
- srcChainId in STARGATE_USDC
- ? STARGATE_USDC[srcChainId as keyof typeof STARGATE_USDC]
- : STARGATE_USDT[srcChainId as keyof typeof STARGATE_USDT]
-
- const usdPaths = srcChainPaths[srcBridgeToken.address as Address]
-
- const dstBridgeToken = usdPaths.find(
- (dstBridgeToken) => dstBridgeToken.chainId === dstChainId,
- )
-
- if (dstBridgeToken) {
- return {
- srcBridgeToken,
- dstBridgeToken,
- }
- }
- }
-
- return undefined
-}
diff --git a/apps/web/src/lib/swap/cross-chain/lib/SushiXSwap.ts b/apps/web/src/lib/swap/cross-chain/lib/SushiXSwap.ts
deleted file mode 100644
index 9489780475..0000000000
--- a/apps/web/src/lib/swap/cross-chain/lib/SushiXSwap.ts
+++ /dev/null
@@ -1,106 +0,0 @@
-import { sushiXSwap2Abi_bridge, sushiXSwap2Abi_swapAndBridge } from 'sushi/abi'
-import {
- Address,
- Hex,
- WriteContractParameters,
- encodeAbiParameters,
- parseAbiParameters,
-} from 'viem'
-import { SuccessfulTradeReturn } from '../actions/getTrade'
-
-export enum SushiXSwap2Adapter {
- Stargate = 'Stargate',
- Squid = 'Squid',
-}
-
-export enum SushiXSwapTransactionType {
- Bridge = 'Bridge',
- SwapAndBridge = 'SwapAndBridge',
- BridgeAndSwap = 'BridgeAndSwap',
- CrossChainSwap = 'CrossChainSwap',
-}
-
-export enum SushiXSwapFunctionName {
- Bridge = 'bridge',
- SwapAndBridge = 'swapAndBridge',
-}
-
-export type SushiXSwapWriteArgsBridge = WriteContractParameters<
- typeof sushiXSwap2Abi_bridge,
- SushiXSwapFunctionName.Bridge
->['args']
-
-export type SushiXSwapWriteArgsSwapAndBridge = WriteContractParameters<
- typeof sushiXSwap2Abi_swapAndBridge,
- SushiXSwapFunctionName.SwapAndBridge
->['args']
-
-export type SushiXSwapWriteArgs =
- | SushiXSwapWriteArgsBridge
- | SushiXSwapWriteArgsSwapAndBridge
-
-export const encodePayloadData = ({
- target,
- gasLimit,
- targetData,
-}: {
- target: Address
- gasLimit: bigint
- targetData: `0x${string}`
-}) => {
- return encodeAbiParameters(parseAbiParameters('address, uint256, bytes'), [
- target,
- gasLimit,
- targetData,
- ])
-}
-
-type ProcessRouteInput = readonly [
- Address,
- bigint,
- Address,
- bigint,
- Address,
- `0x${string}`,
-]
-
-export function encodeSwapData([
- tokenIn,
- amountIn,
- tokenOut,
- amountOut,
- to,
- route,
-]: ProcessRouteInput) {
- return encodeAbiParameters(
- parseAbiParameters(
- '(address tokenIn, uint256 amountIn, address tokenOut, uint256 amountOut, address to, bytes route)',
- ),
- [{ tokenIn, amountIn, tokenOut, amountOut, to, route }],
- )
-}
-
-export function encodeRouteProcessorArgs({
- tokenIn,
- amountIn,
- tokenOut,
- amountOutMin,
- to,
- routeCode,
-}: NonNullable) {
- return encodeAbiParameters(
- parseAbiParameters(
- '(address tokenIn, uint256 amountIn, address tokenOut, uint256 amountOut, address to, bytes route)',
- ),
- [
- {
- tokenIn: tokenIn as Address,
- amountIn: BigInt(amountIn),
- tokenOut: tokenOut as Address,
- amountOut: BigInt(amountOutMin),
- to: to as Address,
- route: routeCode as Hex,
- },
- ],
- )
-}
diff --git a/apps/web/src/lib/swap/cross-chain/lib/index.ts b/apps/web/src/lib/swap/cross-chain/lib/index.ts
deleted file mode 100644
index ac0768b5cd..0000000000
--- a/apps/web/src/lib/swap/cross-chain/lib/index.ts
+++ /dev/null
@@ -1,4 +0,0 @@
-export * from './SquidAdapter'
-export * from './StargateAdapter'
-export * from './SushiXSwap'
-export * from './utils'
diff --git a/apps/web/src/lib/swap/cross-chain/lib/utils.ts b/apps/web/src/lib/swap/cross-chain/lib/utils.ts
deleted file mode 100644
index 70ea05346b..0000000000
--- a/apps/web/src/lib/swap/cross-chain/lib/utils.ts
+++ /dev/null
@@ -1,11 +0,0 @@
-import { BigintIsh, getBigInt } from 'sushi/math'
-
-export const applySlippage = (
- amount: BigintIsh,
- slippagePercentage: string | number,
-) => {
- return (
- (BigInt(amount) * getBigInt((1 - +slippagePercentage / 100) * 1_000_000)) /
- 1_000_000n
- )
-}
diff --git a/apps/web/src/lib/swap/cross-chain/schema.ts b/apps/web/src/lib/swap/cross-chain/schema.ts
new file mode 100644
index 0000000000..f684d5fadb
--- /dev/null
+++ b/apps/web/src/lib/swap/cross-chain/schema.ts
@@ -0,0 +1,154 @@
+import { isXSwapSupportedChainId } from 'src/config'
+import { hexToBigInt, isAddress, isHex } from 'viem'
+import { z } from 'zod'
+
+export const crossChainTokenSchema = z.object({
+ address: z.string().refine((address) => isAddress(address), {
+ message: 'address does not conform to Address',
+ }),
+ decimals: z.number(),
+ symbol: z.string(),
+ chainId: z.number().refine((chainId) => isXSwapSupportedChainId(chainId), {
+ message: `chainId must exist in XSwapChainId`,
+ }),
+ name: z.string(),
+ priceUSD: z.string(),
+})
+
+export const crossChainActionSchema = z.object({
+ fromChainId: z
+ .number()
+ .refine((chainId) => isXSwapSupportedChainId(chainId), {
+ message: `fromChainId must exist in XSwapChainId`,
+ }),
+ fromAmount: z.string().transform((amount) => BigInt(amount)),
+ fromToken: crossChainTokenSchema,
+ toChainId: z.number().refine((chainId) => isXSwapSupportedChainId(chainId), {
+ message: `toChainId must exist in XSwapChainId`,
+ }),
+ toToken: crossChainTokenSchema,
+ slippage: z.number(),
+ fromAddress: z
+ .string()
+ .refine((address) => isAddress(address), {
+ message: 'fromAddress does not conform to Address',
+ })
+ .optional(),
+ toAddress: z
+ .string()
+ .refine((address) => isAddress(address), {
+ message: 'toAddress does not conform to Address',
+ })
+ .optional(),
+})
+
+export const crossChainEstimateSchema = z.object({
+ tool: z.string(),
+ fromAmount: z.string().transform((amount) => BigInt(amount)),
+ toAmount: z.string().transform((amount) => BigInt(amount)),
+ toAmountMin: z.string().transform((amount) => BigInt(amount)),
+ approvalAddress: z.string().refine((address) => isAddress(address), {
+ message: 'approvalAddress does not conform to Address',
+ }),
+ feeCosts: z
+ .array(
+ z.object({
+ name: z.string(),
+ description: z.string(),
+ percentage: z.string(),
+ token: crossChainTokenSchema,
+ amount: z.string().transform((amount) => BigInt(amount)),
+ amountUSD: z.string(),
+ included: z.boolean(),
+ }),
+ )
+ .default([]),
+ gasCosts: z.array(
+ z.object({
+ type: z.enum(['SUM', 'APPROVE', 'SEND']),
+ price: z.string(),
+ estimate: z.string(),
+ limit: z.string(),
+ amount: z.string().transform((amount) => BigInt(amount)),
+ amountUSD: z.string(),
+ token: crossChainTokenSchema,
+ }),
+ ),
+ executionDuration: z.number(),
+})
+
+export const crossChainToolDetailsSchema = z.object({
+ key: z.string(),
+ name: z.string(),
+ logoURI: z.string(),
+})
+
+export const crossChainTransactionRequestSchema = z.object({
+ chainId: z.number().refine((chainId) => isXSwapSupportedChainId(chainId), {
+ message: `chainId must exist in XSwapChainId`,
+ }),
+ data: z.string().refine((data) => isHex(data), {
+ message: 'data does not conform to Hex',
+ }),
+ from: z.string().refine((from) => isAddress(from), {
+ message: 'from does not conform to Address',
+ }),
+ gasLimit: z
+ .string()
+ .refine((gasLimit) => isHex(gasLimit), {
+ message: 'gasLimit does not conform to Hex',
+ })
+ .transform((gasLimit) => hexToBigInt(gasLimit)),
+ gasPrice: z
+ .string()
+ .refine((gasPrice) => isHex(gasPrice), {
+ message: 'gasPrice does not conform to Hex',
+ })
+ .transform((gasPrice) => hexToBigInt(gasPrice)),
+ to: z.string().refine((to) => isAddress(to), {
+ message: 'to does not conform to Address',
+ }),
+ value: z
+ .string()
+ .refine((value) => isHex(value), {
+ message: 'value does not conform to Hex',
+ })
+ .transform((value) => hexToBigInt(value)),
+})
+
+const _crossChainStepSchema = z.object({
+ id: z.string(),
+ type: z.enum(['swap', 'cross', 'lifi']),
+ tool: z.string(),
+ toolDetails: crossChainToolDetailsSchema,
+ action: crossChainActionSchema,
+ estimate: crossChainEstimateSchema,
+ transactionRequest: crossChainTransactionRequestSchema.optional(),
+})
+
+export const crossChainStepSchema = _crossChainStepSchema.extend({
+ includedSteps: z.array(_crossChainStepSchema),
+})
+
+export const crossChainRouteSchema = z.object({
+ id: z.string(),
+ fromChainId: z.coerce
+ .number()
+ .refine((chainId) => isXSwapSupportedChainId(chainId), {
+ message: `fromChainId must exist in XSwapChainId`,
+ }),
+ fromAmount: z.string().transform((amount) => BigInt(amount)),
+ fromToken: crossChainTokenSchema,
+ toChainId: z.coerce
+ .number()
+ .refine((chainId) => isXSwapSupportedChainId(chainId), {
+ message: `toChainId must exist in XSwapChainId`,
+ }),
+ toAmount: z.string().transform((amount) => BigInt(amount)),
+ toAmountMin: z.string().transform((amount) => BigInt(amount)),
+ toToken: crossChainTokenSchema,
+ gasCostUSD: z.string(),
+ steps: z.array(crossChainStepSchema),
+ tags: z.array(z.string()).optional(),
+ transactionRequest: crossChainTransactionRequestSchema.optional(),
+})
diff --git a/apps/web/src/lib/swap/cross-chain/types.ts b/apps/web/src/lib/swap/cross-chain/types.ts
new file mode 100644
index 0000000000..9111754dbb
--- /dev/null
+++ b/apps/web/src/lib/swap/cross-chain/types.ts
@@ -0,0 +1,35 @@
+import { z } from 'zod'
+import {
+ crossChainActionSchema,
+ crossChainEstimateSchema,
+ crossChainRouteSchema,
+ crossChainStepSchema,
+ crossChainToolDetailsSchema,
+ crossChainTransactionRequestSchema,
+} from './schema'
+
+type CrossChainAction = z.infer
+
+type CrossChainEstimate = z.infer
+
+type CrossChainRoute = z.infer
+
+type CrossChainStep = z.infer
+
+type CrossChainToolDetails = z.infer
+
+type CrossChainTransactionRequest = z.infer<
+ typeof crossChainTransactionRequestSchema
+>
+
+type CrossChainRouteOrder = 'CHEAPEST' | 'FASTEST'
+
+export type {
+ CrossChainAction,
+ CrossChainEstimate,
+ CrossChainRoute,
+ CrossChainStep,
+ CrossChainToolDetails,
+ CrossChainTransactionRequest,
+ CrossChainRouteOrder,
+}
diff --git a/apps/web/src/lib/swap/cross-chain/utils.tsx b/apps/web/src/lib/swap/cross-chain/utils.tsx
new file mode 100644
index 0000000000..f14fbd64b2
--- /dev/null
+++ b/apps/web/src/lib/swap/cross-chain/utils.tsx
@@ -0,0 +1,79 @@
+import { ChainId } from 'sushi/chain'
+import { Amount, Native, Token, Type } from 'sushi/currency'
+import { zeroAddress } from 'viem'
+import { CrossChainStep } from './types'
+
+interface FeeBreakdown {
+ amount: Amount
+ amountUSD: number
+}
+
+export interface FeesBreakdown {
+ gas: Map
+ protocol: Map
+}
+
+enum FeeType {
+ GAS = 'GAS',
+ PROTOCOL = 'PROTOCOL',
+}
+
+export const getCrossChainFeesBreakdown = (route: CrossChainStep[]) => {
+ const gasFeesBreakdown = getFeesBreakdown(route, FeeType.GAS)
+ const protocolFeesBreakdown = getFeesBreakdown(route, FeeType.PROTOCOL)
+ const gasFeesUSD = Array.from(gasFeesBreakdown.values()).reduce(
+ (sum, gasCost) => sum + gasCost.amountUSD,
+ 0,
+ )
+ const protocolFeesUSD = Array.from(protocolFeesBreakdown.values()).reduce(
+ (sum, feeCost) => sum + feeCost.amountUSD,
+ 0,
+ )
+ const totalFeesUSD = gasFeesUSD + protocolFeesUSD
+
+ return {
+ feesBreakdown: {
+ gas: gasFeesBreakdown,
+ protocol: protocolFeesBreakdown,
+ },
+ totalFeesUSD,
+ gasFeesUSD,
+ protocolFeesUSD,
+ }
+}
+
+const getFeesBreakdown = (route: CrossChainStep[], feeType: FeeType) => {
+ return route.reduce((feesByChainId, step) => {
+ const fees =
+ feeType === FeeType.PROTOCOL
+ ? step.estimate.feeCosts.filter((fee) => fee.included === false)
+ : step.estimate.gasCosts
+
+ if (fees.length === 0) return feesByChainId
+
+ const token =
+ fees[0].token.address === zeroAddress
+ ? Native.onChain(fees[0].token.chainId)
+ : new Token(fees[0].token)
+
+ const { amount, amountUSD } = fees.reduce(
+ (acc, feeCost) => {
+ const amount = Amount.fromRawAmount(token, feeCost.amount)
+
+ acc.amount = acc.amount.add(amount)
+ acc.amountUSD += +feeCost.amountUSD
+ return acc
+ },
+ { amount: Amount.fromRawAmount(token, 0), amountUSD: 0 },
+ )
+
+ const feeByChainId = feesByChainId.get(amount.currency.chainId)
+
+ feesByChainId.set(amount.currency.chainId, {
+ amount: feeByChainId ? feeByChainId.amount.add(amount) : amount,
+ amountUSD: feeByChainId ? feeByChainId.amountUSD + amountUSD : amountUSD,
+ })
+
+ return feesByChainId
+ }, new Map())
+}
diff --git a/apps/web/src/lib/swap/queryParamsSchema.ts b/apps/web/src/lib/swap/queryParamsSchema.ts
deleted file mode 100644
index 23e2120abb..0000000000
--- a/apps/web/src/lib/swap/queryParamsSchema.ts
+++ /dev/null
@@ -1,65 +0,0 @@
-import { Address, isAddress } from 'viem'
-import { z } from 'zod'
-
-import { SwapChainId } from '../../types'
-
-export const queryParamsSchema = z.object({
- chainId: z.coerce
- .number()
- .int()
- .gte(0)
- .lte(2 ** 256)
- .optional()
- .transform((chainId) => chainId as SwapChainId | undefined),
- fromChainId: z.coerce
- .number()
- .int()
- .gte(0)
- .lte(2 ** 256)
- .optional()
- .transform((chainId) => chainId as SwapChainId | undefined),
- // .refine(
- // (chainId) =>
- // isUniswapV2FactoryChainId(chainId) ||
- // isConstantProductPoolFactoryChainId(chainId) ||
- // isStablePoolFactoryChainId(chainId),
- // {
- // message: 'ChainId not supported.',
- // }
- // ),
- // fromCurrency: z
- // .string()
- // .nullable()
- // .transform((arg) => (arg ? arg : 'NATIVE')),
- fromCurrency: z
- .nullable(z.string())
- .transform((currency) =>
- typeof currency === 'string' ? currency : 'NATIVE',
- ),
- toChainId: z.coerce
- .number()
- .int()
- .gte(0)
- .lte(2 ** 256)
- .optional()
- .transform((chainId) => chainId as SwapChainId | undefined),
- toCurrency: z
- .nullable(z.string())
- .transform((currency) => (typeof currency === 'string' ? currency : '')),
- // .transform((currency) => (typeof currency === 'string' ? currency : 'NATIVE')),
- // toCurrency: z
- // .string()
- // .nullable()
- // .transform((arg) => (arg ? arg : 'SUSHI')),
- amount: z.optional(z.nullable(z.string())).transform((val) => val ?? ''),
- recipient: z.optional(
- z
- .nullable(z.string())
- .transform((val) => (val && isAddress(val) ? (val as Address) : null)),
- ),
- review: z.optional(z.nullable(z.boolean())),
-})
-// .transform((val) => ({
-// ...val,
-// toCurrency: defaultQuoteCurrency[val.fromChainId].wrapped.address,
-// }))
diff --git a/apps/web/src/lib/wagmi/components/web3-input/Currency/CurrencyInput.tsx b/apps/web/src/lib/wagmi/components/web3-input/Currency/CurrencyInput.tsx
index eb1412ca6a..0d62bcf8de 100644
--- a/apps/web/src/lib/wagmi/components/web3-input/Currency/CurrencyInput.tsx
+++ b/apps/web/src/lib/wagmi/components/web3-input/Currency/CurrencyInput.tsx
@@ -1,7 +1,15 @@
'use client'
+import { ChevronRightIcon } from '@heroicons/react/24/outline'
import { useIsMounted } from '@sushiswap/hooks'
-import { Badge, Button, SelectIcon, TextField, classNames } from '@sushiswap/ui'
+import {
+ Badge,
+ Button,
+ SelectIcon,
+ SelectPrimitive,
+ TextField,
+ classNames,
+} from '@sushiswap/ui'
import { Currency } from '@sushiswap/ui'
import { SkeletonBox } from '@sushiswap/ui'
import { NetworkIcon } from '@sushiswap/ui/icons/NetworkIcon'
@@ -13,7 +21,7 @@ import {
useState,
useTransition,
} from 'react'
-import { ChainId } from 'sushi/chain'
+import { Chain, ChainId } from 'sushi/chain'
import { Token, Type, tryParseAmount } from 'sushi/currency'
import { Percent } from 'sushi/math'
import { useAccount } from 'wagmi'
@@ -165,14 +173,15 @@ const CurrencyInput: FC = ({
id={id}
type="button"
className={classNames(
- currency ? 'pl-2 pr-3 text-xl' : '',
+ currency ? 'pl-2 pr-3' : '',
+ networks ? '!h-11' : '',
'!rounded-full data-[state=inactive]:hidden data-[state=active]:flex',
)}
>
{currency ? (
- <>
-
- {networks ? (
+ networks ? (
+ <>
+
= ({
/>
}
>
-
+
- ) : (
+
+
+ {currency.symbol}
+
+ {Chain.from(currency.chainId)?.name}
+
+
+
+
+
+ >
+ ) : (
+ <>
+
- )}
-
- {currency.symbol}
-
- >
+
+ {currency.symbol}
+
+ >
+ )
) : (
'Select token'
)}
diff --git a/apps/web/src/ui/swap/cross-chain/cross-chain-swap-confirmation-dialog.tsx b/apps/web/src/ui/swap/cross-chain/cross-chain-swap-confirmation-dialog.tsx
index 05b465d10e..3dfb59e4ff 100644
--- a/apps/web/src/ui/swap/cross-chain/cross-chain-swap-confirmation-dialog.tsx
+++ b/apps/web/src/ui/swap/cross-chain/cross-chain-swap-confirmation-dialog.tsx
@@ -1,50 +1,44 @@
import { ArrowTopRightOnSquareIcon } from '@heroicons/react/20/solid'
-import { Button, Currency, Dots, Loader, classNames } from '@sushiswap/ui'
+import { Button, Dots, Loader, classNames } from '@sushiswap/ui'
import { CheckMarkIcon } from '@sushiswap/ui/icons/CheckMarkIcon'
import { FailedMarkIcon } from '@sushiswap/ui/icons/FailedMarkIcon'
-import { SquidIcon } from '@sushiswap/ui/icons/SquidIcon'
import { FC, ReactNode } from 'react'
-import { UseCrossChainTradeReturn } from 'src/lib/hooks'
-import {
- SushiXSwap2Adapter,
- SushiXSwapTransactionType,
-} from 'src/lib/swap/cross-chain/lib'
import { Chain } from 'sushi/chain'
-import { STARGATE_TOKEN } from 'sushi/config'
import { shortenAddress } from 'sushi/format'
import {
- useCrossChainSwapTrade,
+ UseSelectedCrossChainTradeRouteReturn,
useDerivedStateCrossChainSwap,
+ useSelectedCrossChainTradeRoute,
} from './derivedstate-cross-chain-swap-provider'
interface ConfirmationDialogContent {
txHash?: string
dstTxHash?: string
bridgeUrl?: string
- adapter?: SushiXSwap2Adapter
dialogState: { source: StepState; bridge: StepState; dest: StepState }
- tradeRef: React.MutableRefObject
+ routeRef: React.MutableRefObject
}
export const ConfirmationDialogContent: FC = ({
txHash,
bridgeUrl,
- adapter,
dstTxHash,
dialogState,
- tradeRef,
+ routeRef,
}) => {
const {
state: { chainId0, chainId1, token0, token1, recipient },
} = useDerivedStateCrossChainSwap()
- const { data: trade } = useCrossChainSwapTrade()
+ const { data: trade } = useSelectedCrossChainTradeRoute()
const swapOnDest =
- trade?.transactionType &&
+ trade?.steps[0] &&
[
- SushiXSwapTransactionType.BridgeAndSwap,
- SushiXSwapTransactionType.CrossChainSwap,
- ].includes(trade.transactionType)
+ trade.steps[0].includedSteps[1]?.type,
+ trade.steps[0].includedSteps[2]?.type,
+ ].includes('swap')
+ ? true
+ : false
if (dialogState.source === StepState.Sign) {
return <>Please sign order with your wallet.>
@@ -98,17 +92,24 @@ export const ConfirmationDialogContent: FC = ({
{' '}
-
>
)
}
if (dialogState.dest === StepState.PartialSuccess) {
+ const fromTokenSymbol =
+ routeRef?.current?.steps?.[0]?.includedSteps?.[1]?.type === 'swap'
+ ? routeRef?.current?.steps?.[0]?.includedSteps?.[1]?.action?.fromToken
+ ?.symbol
+ : routeRef?.current?.steps?.[0]?.includedSteps?.[2]?.type === 'swap'
+ ? routeRef?.current?.steps?.[0]?.includedSteps?.[2]?.action?.fromToken
+ ?.symbol
+ : undefined
+
return (
<>
- We {`couldn't`} swap {tradeRef?.current?.dstBridgeToken?.symbol} into{' '}
- {token1?.symbol}, {tradeRef?.current?.dstBridgeToken?.symbol} has been
- send to{' '}
+ We {`couldn't`} swap {fromTokenSymbol} into {token1?.symbol},{' '}
+ {fromTokenSymbol} has been send to{' '}
{recipient ? (