diff --git a/libs/oeth/swap/src/actions/defaultApi.ts b/libs/oeth/swap/src/actions/defaultApi.ts index 1012858b3..31fd06653 100644 --- a/libs/oeth/swap/src/actions/defaultApi.ts +++ b/libs/oeth/swap/src/actions/defaultApi.ts @@ -1,33 +1,56 @@ import { isNilOrEmpty } from '@origin/shared/utils'; -import { getAvailableRoutes } from '../utils'; +import type { + EstimateAmount, + EstimateGas, + EstimateRoute, + Swap, +} from '../types'; + +const estimateAmount: EstimateAmount = async ( + _tokenIn, + _tokenOut, + amountIn, +) => { + if (amountIn === 0n) { + return 0n; + } -import type { SwapApi, SwapState } from '../types'; + return amountIn; +}; -const estimateAmount = async ({ tokenIn, tokenOut, amountIn }: SwapState) => { +const estimateGas: EstimateGas = async (_tokenIn, _tokenOut, amountIn) => { if (amountIn === 0n) { return 0n; } - return amountIn; + return 0n; }; -const estimateRoutes = async ({ tokenIn, tokenOut, amountIn }: SwapState) => { +const estimateRoute: EstimateRoute = async ( + tokenIn, + tokenOut, + amountIn, + route, +) => { if (amountIn === 0n) { - return []; + return { ...route, estimatedAmount: 0n, gas: 0n, rate: 0 }; } - return getAvailableRoutes(tokenIn, tokenOut); + const estimatedAmount = await estimateAmount(tokenIn, tokenOut, amountIn); + + return { ...route, estimatedAmount, gas: 0n, rate: 0 }; }; -const swap = async ({ tokenIn, tokenOut, amountIn, swapRoute }: SwapState) => { - if (amountIn === 0n || isNilOrEmpty(swapRoute)) { +const swap: Swap = async (_tokenIn, _tokenOut, amountIn, route) => { + if (amountIn === 0n || isNilOrEmpty(route)) { return; } }; export default { estimateAmount, - estimateRoutes, + estimateGas, + estimateRoute, swap, -} as SwapApi; +}; diff --git a/libs/oeth/swap/src/actions/mintVault.ts b/libs/oeth/swap/src/actions/mintVault.ts index 0fe42fcaa..323ad32a5 100644 --- a/libs/oeth/swap/src/actions/mintVault.ts +++ b/libs/oeth/swap/src/actions/mintVault.ts @@ -1,8 +1,6 @@ -import { isNilOrEmpty } from '@origin/shared/utils'; +import type { EstimateAmount } from '../types'; -import type { SwapApi, SwapState } from '../types'; - -const estimateAmount = async ({ tokenIn, tokenOut, amountIn }: SwapState) => { +const estimateAmount: EstimateAmount = async (tokenIn, tokenOut, amountIn) => { if (amountIn === 0n) { return 0n; } @@ -10,22 +8,6 @@ const estimateAmount = async ({ tokenIn, tokenOut, amountIn }: SwapState) => { return amountIn * 2n; }; -const estimateRoutes = async ({ tokenIn, tokenOut, amountIn }: SwapState) => { - if (amountIn === 0n) { - return []; - } - - return []; -}; - -const swap = async ({ tokenIn, tokenOut, amountIn, swapRoute }: SwapState) => { - if (amountIn === 0n || isNilOrEmpty(swapRoute)) { - return; - } -}; - export default { estimateAmount, - estimateRoutes, - swap, -} as Partial; +}; diff --git a/libs/oeth/swap/src/actions/redeemVault.ts b/libs/oeth/swap/src/actions/redeemVault.ts index 9bdd2720b..323ad32a5 100644 --- a/libs/oeth/swap/src/actions/redeemVault.ts +++ b/libs/oeth/swap/src/actions/redeemVault.ts @@ -1,6 +1,6 @@ -import type { SwapApi, SwapState } from '../types'; +import type { EstimateAmount } from '../types'; -const estimateAmount = async ({ tokenIn, tokenOut, amountIn }: SwapState) => { +const estimateAmount: EstimateAmount = async (tokenIn, tokenOut, amountIn) => { if (amountIn === 0n) { return 0n; } @@ -10,4 +10,4 @@ const estimateAmount = async ({ tokenIn, tokenOut, amountIn }: SwapState) => { export default { estimateAmount, -} as Partial; +}; diff --git a/libs/oeth/swap/src/actions/swapCurve.ts b/libs/oeth/swap/src/actions/swapCurve.ts index b9acbb16a..b13dc55e5 100644 --- a/libs/oeth/swap/src/actions/swapCurve.ts +++ b/libs/oeth/swap/src/actions/swapCurve.ts @@ -1,14 +1,11 @@ import curve from '@curvefi/api'; -import { isNilOrEmpty } from '@origin/shared/utils'; import { formatUnits, parseUnits } from 'viem'; -import { getAvailableRoutes } from '../utils'; - -import type { SwapApi, SwapState } from '../types'; +import type { EstimateAmount, EstimateRoute } from '../types'; const ETH = '0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE'; -const estimateAmount = async ({ tokenIn, tokenOut, amountIn }: SwapState) => { +const estimateAmount: EstimateAmount = async (tokenIn, tokenOut, amountIn) => { if (amountIn === 0n) { return 0n; } @@ -22,22 +19,22 @@ const estimateAmount = async ({ tokenIn, tokenOut, amountIn }: SwapState) => { return parseUnits(routes.output, tokenOut.decimals); }; -const estimateRoutes = async ({ tokenIn, tokenOut, amountIn }: SwapState) => { +const estimateRoute: EstimateRoute = async ( + tokenIn, + tokenOut, + amountIn, + route, +) => { if (amountIn === 0n) { - return []; + return { ...route, estimatedAmount: 0n, gas: 0n, rate: 0 }; } - return getAvailableRoutes(tokenIn, tokenOut); -}; + const estimatedAmount = await estimateAmount(tokenIn, tokenOut, amountIn); -const swap = async ({ tokenIn, tokenOut, amountIn, swapRoute }: SwapState) => { - if (amountIn === 0n || isNilOrEmpty(swapRoute)) { - return; - } + return { ...route, estimatedAmount, gas: 0n, rate: 0 }; }; export default { estimateAmount, - estimateRoutes, - swap, -} as Partial; + estimateRoute, +}; diff --git a/libs/oeth/swap/src/actions/swapZapperEth.ts b/libs/oeth/swap/src/actions/swapZapperEth.ts index 1012858b3..129bb7aa0 100644 --- a/libs/oeth/swap/src/actions/swapZapperEth.ts +++ b/libs/oeth/swap/src/actions/swapZapperEth.ts @@ -1,10 +1,6 @@ -import { isNilOrEmpty } from '@origin/shared/utils'; +import type { EstimateAmount } from '../types'; -import { getAvailableRoutes } from '../utils'; - -import type { SwapApi, SwapState } from '../types'; - -const estimateAmount = async ({ tokenIn, tokenOut, amountIn }: SwapState) => { +const estimateAmount: EstimateAmount = async (tokenIn, tokenOut, amountIn) => { if (amountIn === 0n) { return 0n; } @@ -12,22 +8,6 @@ const estimateAmount = async ({ tokenIn, tokenOut, amountIn }: SwapState) => { return amountIn; }; -const estimateRoutes = async ({ tokenIn, tokenOut, amountIn }: SwapState) => { - if (amountIn === 0n) { - return []; - } - - return getAvailableRoutes(tokenIn, tokenOut); -}; - -const swap = async ({ tokenIn, tokenOut, amountIn, swapRoute }: SwapState) => { - if (amountIn === 0n || isNilOrEmpty(swapRoute)) { - return; - } -}; - export default { estimateAmount, - estimateRoutes, - swap, -} as SwapApi; +}; diff --git a/libs/oeth/swap/src/actions/swapZapperSfrxeth.ts b/libs/oeth/swap/src/actions/swapZapperSfrxeth.ts index 1012858b3..129bb7aa0 100644 --- a/libs/oeth/swap/src/actions/swapZapperSfrxeth.ts +++ b/libs/oeth/swap/src/actions/swapZapperSfrxeth.ts @@ -1,10 +1,6 @@ -import { isNilOrEmpty } from '@origin/shared/utils'; +import type { EstimateAmount } from '../types'; -import { getAvailableRoutes } from '../utils'; - -import type { SwapApi, SwapState } from '../types'; - -const estimateAmount = async ({ tokenIn, tokenOut, amountIn }: SwapState) => { +const estimateAmount: EstimateAmount = async (tokenIn, tokenOut, amountIn) => { if (amountIn === 0n) { return 0n; } @@ -12,22 +8,6 @@ const estimateAmount = async ({ tokenIn, tokenOut, amountIn }: SwapState) => { return amountIn; }; -const estimateRoutes = async ({ tokenIn, tokenOut, amountIn }: SwapState) => { - if (amountIn === 0n) { - return []; - } - - return getAvailableRoutes(tokenIn, tokenOut); -}; - -const swap = async ({ tokenIn, tokenOut, amountIn, swapRoute }: SwapState) => { - if (amountIn === 0n || isNilOrEmpty(swapRoute)) { - return; - } -}; - export default { estimateAmount, - estimateRoutes, - swap, -} as SwapApi; +}; diff --git a/libs/oeth/swap/src/actions/unwrapWOETH.ts b/libs/oeth/swap/src/actions/unwrapWOETH.ts index e881ab28b..c8a27846b 100644 --- a/libs/oeth/swap/src/actions/unwrapWOETH.ts +++ b/libs/oeth/swap/src/actions/unwrapWOETH.ts @@ -1,12 +1,14 @@ -import { contracts } from '@origin/shared/contracts'; +import { contracts, whales } from '@origin/shared/contracts'; import { isNilOrEmpty } from '@origin/shared/utils'; -import { readContract } from '@wagmi/core'; +import { getAccount, getPublicClient, readContract } from '@wagmi/core'; -import { getAvailableRoutes } from '../utils'; +import type { EstimateAmount, EstimateGas, EstimateRoute } from '../types'; -import type { SwapApi, SwapState } from '../types'; - -const estimateAmount = async ({ amountIn }: SwapState) => { +const estimateAmount: EstimateAmount = async ( + _tokenIn, + _tokenOut, + amountIn, +) => { if (amountIn === 0n) { return 0n; } @@ -21,22 +23,67 @@ const estimateAmount = async ({ amountIn }: SwapState) => { return data; }; -const estimateRoutes = async ({ tokenIn, tokenOut, amountIn }: SwapState) => { +const estimateGas: EstimateGas = async (_tokenIn, _tokenOut, amountIn) => { + let gasEstimate = 0n; + let isError = false; + + const publicClient = getPublicClient(); + if (amountIn === 0n) { - return []; + return gasEstimate; + } + + const { address } = getAccount(); + + if (!isNilOrEmpty(address)) { + try { + gasEstimate = await publicClient.estimateContractGas({ + address: contracts.mainnet.WOETH.address, + abi: contracts.mainnet.WOETH.abi, + functionName: 'redeem', + args: [amountIn, address, address], + account: address, + }); + } catch { + isError = true; + } } - return getAvailableRoutes(tokenIn, tokenOut); + if (isError) { + try { + gasEstimate = await publicClient.estimateContractGas({ + address: contracts.mainnet.WOETH.address, + abi: contracts.mainnet.WOETH.abi, + functionName: 'redeem', + args: [amountIn, whales.mainnet.WOETH, whales.mainnet.WOETH], + account: whales.mainnet.WOETH, + }); + } catch {} + } + + return gasEstimate; }; -const swap = async ({ tokenIn, tokenOut, amountIn, swapRoute }: SwapState) => { - if (amountIn === 0n || isNilOrEmpty(swapRoute)) { - return; +const estimateRoute: EstimateRoute = async ( + tokenIn, + tokenOut, + amountIn, + route, +) => { + if (amountIn === 0n) { + return { ...route, estimatedAmount: 0n, gas: 0n, rate: 0 }; } + + const [estimatedAmount, gas] = await Promise.all([ + estimateAmount(tokenIn, tokenOut, amountIn), + estimateGas(tokenIn, tokenOut, amountIn), + ]); + + return { ...route, estimatedAmount, gas, rate: 0 }; }; export default { estimateAmount, - estimateRoutes, - swap, -} as SwapApi; + estimateGas, + estimateRoute, +}; diff --git a/libs/oeth/swap/src/actions/wrapOETH.ts b/libs/oeth/swap/src/actions/wrapOETH.ts index 3d887e1b8..94cffd247 100644 --- a/libs/oeth/swap/src/actions/wrapOETH.ts +++ b/libs/oeth/swap/src/actions/wrapOETH.ts @@ -1,12 +1,14 @@ -import { contracts } from '@origin/shared/contracts'; +import { contracts, whales } from '@origin/shared/contracts'; import { isNilOrEmpty } from '@origin/shared/utils'; -import { readContract } from '@wagmi/core'; +import { getAccount, getPublicClient, readContract } from '@wagmi/core'; -import { getAvailableRoutes } from '../utils'; +import type { EstimateAmount, EstimateGas, EstimateRoute } from '../types'; -import type { SwapApi, SwapState } from '../types'; - -const estimateAmount = async ({ amountIn }: SwapState) => { +const estimateAmount: EstimateAmount = async ( + _tokenIn, + _tokenOut, + amountIn, +) => { if (amountIn === 0n) { return 0n; } @@ -21,22 +23,67 @@ const estimateAmount = async ({ amountIn }: SwapState) => { return data; }; -const estimateRoutes = async ({ tokenIn, tokenOut, amountIn }: SwapState) => { +const estimateGas: EstimateGas = async (_tokenIn, _tokenOut, amountIn) => { + let gasEstimate = 0n; + let isError = false; + + const publicClient = getPublicClient(); + if (amountIn === 0n) { - return []; + return gasEstimate; + } + + const { address } = getAccount(); + + if (!isNilOrEmpty(address)) { + try { + gasEstimate = await publicClient.estimateContractGas({ + address: contracts.mainnet.WOETH.address, + abi: contracts.mainnet.WOETH.abi, + functionName: 'deposit', + args: [amountIn, address], + account: address, + }); + } catch { + isError = true; + } } - return getAvailableRoutes(tokenIn, tokenOut); + if (isError) { + try { + gasEstimate = await publicClient.estimateContractGas({ + address: contracts.mainnet.WOETH.address, + abi: contracts.mainnet.WOETH.abi, + functionName: 'deposit', + args: [amountIn, whales.mainnet.OETH], + account: whales.mainnet.OETH, + }); + } catch {} + } + + return gasEstimate; }; -const swap = async ({ tokenIn, tokenOut, amountIn, swapRoute }: SwapState) => { - if (amountIn === 0n || isNilOrEmpty(swapRoute)) { - return; +const estimateRoute: EstimateRoute = async ( + tokenIn, + tokenOut, + amountIn, + route, +) => { + if (amountIn === 0n) { + return { ...route, estimatedAmount: 0n, gas: 0n, rate: 0 }; } + + const [estimatedAmount, gas] = await Promise.all([ + estimateAmount(tokenIn, tokenOut, amountIn), + estimateGas(tokenIn, tokenOut, amountIn), + ]); + + return { ...route, estimatedAmount, gas, rate: 0 }; }; export default { estimateAmount, - estimateRoutes, - swap, -} as SwapApi; + estimateGas, + estimateRoute, +}; diff --git a/libs/oeth/swap/src/hooks.ts b/libs/oeth/swap/src/hooks.ts index 4bfa5fe6b..81f0a9e47 100644 --- a/libs/oeth/swap/src/hooks.ts +++ b/libs/oeth/swap/src/hooks.ts @@ -4,11 +4,7 @@ import { isNilOrEmpty } from '@origin/shared/utils'; import { produce } from 'immer'; import { useSwapState } from './state'; -import { - getAllAvailableTokens, - getAvailableRoutes, - getAvailableTokensForSource, -} from './utils'; +import { getAllAvailableTokens, getAvailableTokensForSource } from './utils'; import type { Token } from '@origin/shared/contracts'; @@ -103,11 +99,7 @@ export const useHandleTokenChange = () => { } state.amountIn = 0n; state.amountOut = 0n; - // TODO compute best route - state.swapRoute = getAvailableRoutes( - state.tokenIn, - state.tokenOut, - )[0]; + state.swapRoutes = []; }), ); }, @@ -132,11 +124,11 @@ export const useHandleTokenFlip = () => { }; export const useHandleSwap = () => { - const [{ swapRoute }] = useSwapState(); + const [{ swapRoutes }] = useSwapState(); return useCallback(async () => { - if (isNilOrEmpty(swapRoute)) { + if (isNilOrEmpty(swapRoutes)) { return; } - }, [swapRoute]); + }, [swapRoutes]); }; diff --git a/libs/oeth/swap/src/state.ts b/libs/oeth/swap/src/state.ts index 96e8e89db..7a5f356c1 100644 --- a/libs/oeth/swap/src/state.ts +++ b/libs/oeth/swap/src/state.ts @@ -1,5 +1,6 @@ import { useState } from 'react'; +import { queryClient } from '@origin/oeth/shared'; import { tokens } from '@origin/shared/contracts'; import { useDebouncedEffect } from '@react-hookz/web'; import { produce } from 'immer'; @@ -21,17 +22,44 @@ export const { Provider: SwapProvider, useTracked: useSwapState } = isPriceOutLoading: false, isBalanceOutLoading: false, slippage: 0.01, - swapRoute: getAvailableRoutes(tokens.mainnet.ETH, tokens.mainnet.OETH)[0], + swapRoutes: [], }); useDebouncedEffect( async () => { - const estimatedAmout = await swapActions[ - state.swapRoute.action - ].estimateAmount(state); + const routes = await Promise.all( + getAvailableRoutes(state.tokenIn, state.tokenOut).map((route) => + queryClient.fetchQuery({ + queryKey: [ + 'estimateRoute', + state.tokenIn, + state.tokenOut, + route.action, + state.amountIn.toString(), + ] as const, + queryFn: async ({ queryKey: [, tokenIn, tokenOut, action] }) => + swapActions[action].estimateRoute( + tokenIn, + tokenOut, + state.amountIn, + route, + ), + }), + ), + ); + + const sortedRoutes = routes.sort((a, b) => + a.estimatedAmount < b.estimatedAmount + ? 1 + : a.estimatedAmount > b.estimatedAmount + ? -1 + : 0, + ); + setState( produce((draft) => { - draft.amountOut = estimatedAmout; + draft.swapRoutes = sortedRoutes; + draft.amountOut = sortedRoutes[0].estimatedAmount ?? 0n; draft.isAmountOutLoading = false; draft.isPriceOutLoading = false; }), diff --git a/libs/oeth/swap/src/types.ts b/libs/oeth/swap/src/types.ts index eb92bf4f8..e06265700 100644 --- a/libs/oeth/swap/src/types.ts +++ b/libs/oeth/swap/src/types.ts @@ -11,10 +11,37 @@ export type SwapAction = | 'wrap-oeth' | 'unwrap-woeth'; +export type EstimateAmount = ( + tokenIn: Token, + tokenOut: Token, + amountIn: bigint, +) => Promise; + +export type EstimateGas = ( + tokenIn: Token, + tokenOut: Token, + amountIn: bigint, +) => Promise; + +export type EstimateRoute = ( + tokenIn: Token, + tokenOut: Token, + amountIn: bigint, + route: SwapRoute, +) => Promise; + +export type Swap = ( + tokenIn: Token, + tokenOut: Token, + amountIn: bigint, + route: EstimatedSwapRoute, +) => Promise; + export type SwapApi = { - estimateAmount: (state: SwapState) => Promise; - estimateRoutes: (state: SwapState) => Promise; - swap: (state: SwapState) => Promise; + estimateAmount: EstimateAmount; + estimateGas: EstimateGas; + estimateRoute: EstimateRoute; + swap: Swap; }; export type SwapRoute = { @@ -23,6 +50,12 @@ export type SwapRoute = { action: SwapAction; }; +export type EstimatedSwapRoute = { + estimatedAmount: bigint; + rate: number; + gas: bigint; +} & SwapRoute; + export type SwapState = { amountIn: bigint; tokenIn: Token; @@ -32,5 +65,5 @@ export type SwapState = { isPriceOutLoading: boolean; isBalanceOutLoading: boolean; slippage: number; - swapRoute: SwapRoute | null; + swapRoutes: EstimatedSwapRoute[]; };