diff --git a/apps/vaults/contexts/useSolver.tsx b/apps/vaults/contexts/useSolver.tsx index 05c420357..42b09a33b 100644 --- a/apps/vaults/contexts/useSolver.tsx +++ b/apps/vaults/contexts/useSolver.tsx @@ -18,14 +18,15 @@ import type {MaybeString} from '@yearn-finance/web-lib/types'; import type {TNormalizedBN} from '@common/types/types'; import type {TInitSolverArgs, TSolverContext, TWithSolver} from '@vaults/types/solvers'; -export enum Solver { +export enum Solver { VANILLA = 'Vanilla', PARTNER_CONTRACT = 'PartnerContract', CHAIN_COIN = 'ChainCoin', INTERNAL_MIGRATION = 'InternalMigration', COWSWAP = 'Cowswap', WIDO = 'Wido', - PORTALS = 'Portals' + PORTALS = 'Portals', + NONE = 'None' } export const isSolverDisabled = { @@ -35,10 +36,18 @@ export const isSolverDisabled = { [Solver.INTERNAL_MIGRATION]: false, [Solver.COWSWAP]: false, [Solver.WIDO]: false, - [Solver.PORTALS]: false + [Solver.PORTALS]: false, + [Solver.NONE]: false }; -const DefaultWithSolverContext: TWithSolver = { +type TUpdateSolverHandler = { + request: TInitSolverArgs; + quote: PromiseSettledResult; + solver: Solver; + ctx: TSolverContext; +} + +const DefaultWithSolverContext: TWithSolver = { currentSolver: Solver.VANILLA, effectiveSolver: Solver.VANILLA, expectedOut: toNormalizedBN(0), @@ -51,8 +60,8 @@ const DefaultWithSolverContext: TWithSolver = { onExecuteWithdraw: async (): Promise => Promise.resolve() }; -const WithSolverContext = createContext(DefaultWithSolverContext); -function WithSolverContextApp({children}: {children: React.ReactElement}): React.ReactElement { +const WithSolverContext = createContext(DefaultWithSolverContext); +function WithSolverContextApp({children}: { children: React.ReactElement }): React.ReactElement { const {address} = useWeb3(); const {currentVault, actionParams, currentSolver, isDepositing} = useActionFlow(); const cowswap = useSolverCowswap(); @@ -62,189 +71,114 @@ function WithSolverContextApp({children}: {children: React.ReactElement}): React const chainCoin = useSolverChainCoin(); const partnerContract = useSolverPartnerContract(); const internalMigration = useSolverInternalMigration(); - const [currentSolverState, set_currentSolverState] = useState({...vanilla, hash: undefined}); + const [currentSolverState, set_currentSolverState] = useState({...vanilla, hash: undefined}); const [isLoading, set_isLoading] = useState(false); + async function handleUpdateSolver({request, quote, solver, ctx}: TUpdateSolverHandler): Promise { + if (quote.status !== 'fulfilled') { + return; + } + + const requestHash = await hash(JSON.stringify({...request, solver, expectedOut: quote.value.raw.toString()})); + performBatchedUpdates((): void => { + set_currentSolverState({...ctx, quote: quote.value, hash: requestHash}); + set_isLoading(false); + }); + } + /* 🔵 - Yearn Finance ************************************************************************** ** Based on the currentSolver, we initialize the solver with the required parameters. **********************************************************************************************/ - const onUpdateSolver = useCallback(async (): Promise => { - if (!actionParams?.selectedOptionFrom || !actionParams?.selectedOptionTo || !actionParams?.amount) { + const onUpdateSolver = useCallback(async (): Promise => { + if (!actionParams?.selectedOptionFrom || !actionParams?.selectedOptionTo || !actionParams?.amount.raw) { return; } set_isLoading(true); const request: TInitSolverArgs = { from: toAddress(address || ''), - inputToken: actionParams?.selectedOptionFrom, - outputToken: actionParams?.selectedOptionTo, - inputAmount: actionParams?.amount.raw, + inputToken: actionParams.selectedOptionFrom, + outputToken: actionParams.selectedOptionTo, + inputAmount: actionParams.amount.raw, isDepositing: isDepositing }; + const isValidSolver = ({quote, solver}: { quote: PromiseSettledResult; solver: Solver }): boolean => { + return quote.status === 'fulfilled' && quote?.value.raw?.gt(0) && !isSolverDisabled[solver]; + }; + switch (currentSolver) { case Solver.WIDO: case Solver.PORTALS: case Solver.COWSWAP: { - const promises = [ + const [widoQuote, cowswapQuote, portalsQuote] = await Promise.allSettled([ wido.init(request, currentSolver === Solver.WIDO), cowswap.init(request, currentSolver === Solver.COWSWAP), portals.init(request, currentSolver === Solver.PORTALS) - ]; - const [widoQuote, cowswapQuote, portalsQuote] = await Promise.allSettled(promises); - - /************************************************************** - ** Logic is to use the primary solver (Wido) and check if a - ** quote is available. If not, we fallback to the secondary - ** solver (Cowswap). If neither are available, we set the - ** quote to 0. - **************************************************************/ - if (currentSolver === Solver.WIDO && !isSolverDisabled[Solver.WIDO]) { - if (widoQuote.status === 'fulfilled' && widoQuote?.value.raw?.gt(0)) { - const requestHash = await hash(JSON.stringify({...request, solver: Solver.WIDO, expectedOut: widoQuote.value.raw.toString()})); - performBatchedUpdates((): void => { - set_currentSolverState({...wido, quote: widoQuote.value, hash: requestHash}); - set_isLoading(false); - }); - } else if (cowswapQuote.status === 'fulfilled' && cowswapQuote.value.raw?.gt(0) && !isSolverDisabled[Solver.COWSWAP]) { - const requestHash = await hash(JSON.stringify({...request, solver: Solver.COWSWAP, expectedOut: cowswapQuote.value.raw.toString()})); - performBatchedUpdates((): void => { - set_currentSolverState({...cowswap, quote: cowswapQuote.value, hash: requestHash}); - set_isLoading(false); - }); - } else if (portalsQuote.status === 'fulfilled' && portalsQuote.value.raw?.gt(0) && !isSolverDisabled[Solver.PORTALS]) { - const requestHash = await hash(JSON.stringify({...request, solver: Solver.PORTALS, expectedOut: portalsQuote.value.raw.toString()})); - performBatchedUpdates((): void => { - set_currentSolverState({...portals, quote: portalsQuote.value, hash: requestHash}); - set_isLoading(false); - }); - } else { - const requestHash = await hash(JSON.stringify({...request, solver: 'NONE', expectedOut: '0'})); - performBatchedUpdates((): void => { - set_currentSolverState({...cowswap, quote: toNormalizedBN(0), hash: requestHash}); - set_isLoading(false); - }); + ]); + + const solvers: { + [key in Solver]?: { quote: PromiseSettledResult; ctx: TSolverContext }; + } = {}; + + [ + {solver: Solver.WIDO, quote: widoQuote, ctx: wido}, + {solver: Solver.COWSWAP, quote: cowswapQuote, ctx: cowswap}, + {solver: Solver.PORTALS, quote: portalsQuote, ctx: portals} + ].forEach(({solver, quote, ctx}): void => { + if (isValidSolver({quote, solver})) { + solvers[solver] = {quote, ctx}; } - return; - } + }); - /************************************************************** - ** Logic is to use the primary solver (Cowswap) and check if a - ** quote is available. If not, we fallback to the secondary - ** solver (Wido). If neither are available, we set the - ** quote to 0. - **************************************************************/ - if (currentSolver === Solver.COWSWAP && !isSolverDisabled[Solver.COWSWAP]) { - if (cowswapQuote.status === 'fulfilled' && cowswapQuote.value.raw?.gt(0)) { - const requestHash = await hash(JSON.stringify({...request, solver: Solver.COWSWAP, expectedOut: cowswapQuote.value.raw.toString()})); - performBatchedUpdates((): void => { - set_currentSolverState({...cowswap, quote: cowswapQuote.value, hash: requestHash}); - set_isLoading(false); - }); - } else if (widoQuote.status === 'fulfilled' && widoQuote.value.raw?.gt(0) && !isSolverDisabled[Solver.WIDO]) { - const requestHash = await hash(JSON.stringify({...request, solver: Solver.WIDO, expectedOut: widoQuote.value.raw.toString()})); - performBatchedUpdates((): void => { - set_currentSolverState({...wido, quote: widoQuote.value, hash: requestHash}); - set_isLoading(false); - }); - } else if (portalsQuote.status === 'fulfilled' && portalsQuote.value.raw?.gt(0) && !isSolverDisabled[Solver.PORTALS]) { - const requestHash = await hash(JSON.stringify({...request, solver: Solver.PORTALS, expectedOut: portalsQuote.value.raw.toString()})); - performBatchedUpdates((): void => { - set_currentSolverState({...portals, quote: portalsQuote.value, hash: requestHash}); - set_isLoading(false); - }); - } else { - const requestHash = await hash(JSON.stringify({...request, solver: 'NONE', expectedOut: '0'})); - performBatchedUpdates((): void => { - set_currentSolverState({...wido, quote: toNormalizedBN(0), hash: requestHash}); - set_isLoading(false); - }); - } - } + solvers[Solver.NONE] = {quote: {status: 'fulfilled', value: toNormalizedBN(0)}, ctx: vanilla}; + + const solverPriority = [Solver.WIDO, Solver.COWSWAP, Solver.PORTALS, Solver.NONE]; + + const newSolverPriority = [currentSolver, ...solverPriority.filter((solver): boolean => solver !== currentSolver)]; - /************************************************************** - ** Logic is to use the primary solver (Portals) and check if a - ** quote is available. If not, we fallback to the secondary - ** solver (Wido). If neither are available, we set the - ** quote to 0. - **************************************************************/ - if (currentSolver === Solver.PORTALS && !isSolverDisabled[Solver.PORTALS]) { - if (portalsQuote.status === 'fulfilled' && portalsQuote.value.raw?.gt(0)) { - const requestHash = await hash(JSON.stringify({...request, solver: Solver.PORTALS, expectedOut: portalsQuote.value.raw.toString()})); - performBatchedUpdates((): void => { - set_currentSolverState({...portals, quote: portalsQuote.value, hash: requestHash}); - set_isLoading(false); - }); - } else if (widoQuote.status === 'fulfilled' && widoQuote.value.raw?.gt(0) && !isSolverDisabled[Solver.WIDO]) { - const requestHash = await hash(JSON.stringify({...request, solver: Solver.WIDO, expectedOut: widoQuote.value.raw.toString()})); - performBatchedUpdates((): void => { - set_currentSolverState({...wido, quote: widoQuote.value, hash: requestHash}); - set_isLoading(false); - }); - } else if (cowswapQuote.status === 'fulfilled' && cowswapQuote.value.raw?.gt(0) && !isSolverDisabled[Solver.COWSWAP]) { - const requestHash = await hash(JSON.stringify({...request, solver: Solver.COWSWAP, expectedOut: cowswapQuote.value.raw.toString()})); - performBatchedUpdates((): void => { - set_currentSolverState({...cowswap, quote: cowswapQuote.value, hash: requestHash}); - set_isLoading(false); - }); - } else { - const requestHash = await hash(JSON.stringify({...request, solver: 'NONE', expectedOut: '0'})); - performBatchedUpdates((): void => { - set_currentSolverState({...wido, quote: toNormalizedBN(0), hash: requestHash}); - set_isLoading(false); - }); + for (const solver of newSolverPriority) { + if (!solvers[solver]) { + continue; } - } - set_isLoading(false); + const {quote, ctx} = solvers[solver] ?? solvers[Solver.NONE]; + await handleUpdateSolver({request, quote, solver, ctx}); + return; + } break; } case Solver.CHAIN_COIN: { - const quote = await chainCoin.init(request); - const requestHash = await hash(JSON.stringify({...request, solver: Solver.CHAIN_COIN, expectedOut: quote.raw.toString()})); - performBatchedUpdates((): void => { - set_currentSolverState({...chainCoin, quote, hash: requestHash}); - set_isLoading(false); - }); + const [quote] = await Promise.allSettled([chainCoin.init(request)]); + await handleUpdateSolver({request, quote, solver: Solver.CHAIN_COIN, ctx: chainCoin}); break; } case Solver.PARTNER_CONTRACT: { - const quote = await partnerContract.init(request); - const requestHash = await hash(JSON.stringify({...request, solver: Solver.PARTNER_CONTRACT, expectedOut: quote.raw.toString()})); - performBatchedUpdates((): void => { - set_currentSolverState({...partnerContract, quote, hash: requestHash}); - set_isLoading(false); - }); + const [quote] = await Promise.allSettled([partnerContract.init(request)]); + await handleUpdateSolver({request, quote, solver: Solver.PARTNER_CONTRACT, ctx: partnerContract}); break; } case Solver.INTERNAL_MIGRATION: { request.migrator = currentVault.migration.contract; - const quote = await internalMigration.init(request); - const requestHash = await hash(JSON.stringify({...request, solver: Solver.INTERNAL_MIGRATION, expectedOut: quote.raw.toString()})); - performBatchedUpdates((): void => { - set_currentSolverState({...internalMigration, quote, hash: requestHash}); - set_isLoading(false); - }); + const [quote] = await Promise.allSettled([internalMigration.init(request)]); + await handleUpdateSolver({request, quote, solver: Solver.INTERNAL_MIGRATION, ctx: internalMigration}); break; } default: { - const quote = await vanilla.init(request); - const requestHash = await hash(JSON.stringify({...request, solver: Solver.VANILLA, expectedOut: quote.raw.toString()})); - performBatchedUpdates((): void => { - set_currentSolverState({...vanilla, quote, hash: requestHash}); - set_isLoading(false); - }); + const [quote] = await Promise.allSettled([vanilla.init(request)]); + await handleUpdateSolver({request, quote, solver: Solver.VANILLA, ctx: vanilla}); } } - // eslint-disable-next-line react-hooks/exhaustive-deps + + // eslint-disable-next-line react-hooks/exhaustive-deps }, [address, actionParams, currentSolver, cowswap.init, vanilla.init, wido.init, internalMigration.init, isDepositing, currentVault.migration.contract]); //Ignore the warning, it's a false positive useDebouncedEffect((): void => { onUpdateSolver(); }, [onUpdateSolver], 0); - const contextValue = useDeepCompareMemo((): TWithSolver => ({ + const contextValue = useDeepCompareMemo((): TWithSolver => ({ currentSolver: currentSolver, effectiveSolver: currentSolverState?.type, expectedOut: currentSolverState?.quote || toNormalizedBN(0), @@ -264,5 +198,6 @@ function WithSolverContextApp({children}: {children: React.ReactElement}): React ); } + export {WithSolverContextApp}; export const useSolver = (): TWithSolver => useContext(WithSolverContext);