From 76cbd82cb7efd5072f59e06959e72a11a0bec24e Mon Sep 17 00:00:00 2001 From: Zach Pomerantz Date: Tue, 22 Mar 2022 15:30:12 -0400 Subject: [PATCH] fix: bring wrap ui to spec (#3577) --- .../components/Swap/Status/StatusDialog.tsx | 2 + src/lib/components/Swap/SwapButton.tsx | 60 ++++----- src/lib/components/Swap/Toolbar/Caption.tsx | 21 +-- src/lib/components/Swap/Toolbar/index.tsx | 18 +-- src/lib/components/Swap/WrapErrorText.tsx | 22 ---- .../useClientSideSmartOrderRouterTrade.ts | 2 +- src/lib/hooks/swap/useWrapCallback.tsx | 120 +++++------------- 7 files changed, 75 insertions(+), 170 deletions(-) delete mode 100644 src/lib/components/Swap/WrapErrorText.tsx diff --git a/src/lib/components/Swap/Status/StatusDialog.tsx b/src/lib/components/Swap/Status/StatusDialog.tsx index cdfbd8579a..eebe9262ca 100644 --- a/src/lib/components/Swap/Status/StatusDialog.tsx +++ b/src/lib/components/Swap/Status/StatusDialog.tsx @@ -1,6 +1,7 @@ import { Trans } from '@lingui/macro' import ErrorDialog, { StatusHeader } from 'lib/components/Error/ErrorDialog' import EtherscanLink from 'lib/components/EtherscanLink' +import Rule from 'lib/components/Rule' import SwapSummary from 'lib/components/Swap/Summary' import useInterval from 'lib/hooks/useInterval' import { CheckCircle, Clock, Spinner } from 'lib/icons' @@ -84,6 +85,7 @@ function TransactionStatus({ tx, onClose }: TransactionStatusProps) { ) : null} + diff --git a/src/lib/components/Swap/SwapButton.tsx b/src/lib/components/Swap/SwapButton.tsx index a8c06e0e9c..a22ffe577a 100644 --- a/src/lib/components/Swap/SwapButton.tsx +++ b/src/lib/components/Swap/SwapButton.tsx @@ -1,7 +1,6 @@ import { Trans } from '@lingui/macro' import { Token } from '@uniswap/sdk-core' import { useAtomValue, useUpdateAtom } from 'jotai/utils' -import { WrapErrorText } from 'lib/components/Swap/WrapErrorText' import { useSwapCurrencyAmount, useSwapInfo, useSwapTradeType } from 'lib/hooks/swap' import { ApproveOrPermitState, @@ -10,7 +9,7 @@ import { useSwapRouterAddress, } from 'lib/hooks/swap/useSwapApproval' import { useSwapCallback } from 'lib/hooks/swap/useSwapCallback' -import useWrapCallback, { WrapError, WrapType } from 'lib/hooks/swap/useWrapCallback' +import useWrapCallback, { WrapType } from 'lib/hooks/swap/useWrapCallback' import { useAddTransaction, usePendingApproval } from 'lib/hooks/transactions' import useActiveWeb3React from 'lib/hooks/useActiveWeb3React' import { useSetOldestValidBlock } from 'lib/hooks/useIsValidBlock' @@ -44,11 +43,11 @@ export default memo(function SwapButton({ disabled }: SwapButtonProps) { const { [Field.INPUT]: { currency: inputCurrency, - amount: inputTradeCurrencyAmount, + amount: inputCurrencyAmount, balance: inputCurrencyBalance, usdc: inputUSDC, }, - [Field.OUTPUT]: { amount: outputTradeCurrencyAmount, usdc: outputUSDC }, + [Field.OUTPUT]: { amount: outputCurrencyAmount, usdc: outputUSDC }, trade, slippage, impact, @@ -94,35 +93,23 @@ export default memo(function SwapButton({ disabled }: SwapButtonProps) { } }, [addTransaction, handleApproveOrPermit]) - const { type: wrapType, callback: wrapCallback, error: wrapError, loading: wrapLoading } = useWrapCallback() + const { type: wrapType, callback: wrapCallback } = useWrapCallback() const disableSwap = useMemo( () => disabled || - !optimizedTrade || + (wrapType === WrapType.NONE && !optimizedTrade) || !chainId || - wrapLoading || - (wrapType !== WrapType.NOT_APPLICABLE && wrapError) || - !(inputTradeCurrencyAmount && inputCurrencyBalance) || - inputCurrencyBalance.lessThan(inputTradeCurrencyAmount), - [ - disabled, - optimizedTrade, - chainId, - wrapLoading, - wrapType, - wrapError, - inputTradeCurrencyAmount, - inputCurrencyBalance, - ] + !(inputCurrencyAmount && inputCurrencyBalance) || + inputCurrencyBalance.lessThan(inputCurrencyAmount), + [disabled, wrapType, optimizedTrade, chainId, inputCurrencyAmount, inputCurrencyBalance] ) const actionProps = useMemo((): Partial | undefined => { - if (disableSwap) { - return { disabled: true } - } + if (disableSwap) return { disabled: true } + if ( - wrapType === WrapType.NOT_APPLICABLE && + wrapType === WrapType.NONE && (approvalState === ApproveOrPermitState.REQUIRES_APPROVAL || approvalState === ApproveOrPermitState.REQUIRES_SIGNATURE) ) { @@ -189,13 +176,13 @@ export default memo(function SwapButton({ disabled }: SwapButtonProps) { swapCallback?.() .then((response) => { setDisplayTxHash(response.hash) - invariant(inputTradeCurrencyAmount && outputTradeCurrencyAmount) + invariant(inputCurrencyAmount && outputCurrencyAmount) addTransaction({ response, type: TransactionType.SWAP, tradeType, - inputCurrencyAmount: inputTradeCurrencyAmount, - outputCurrencyAmount: outputTradeCurrencyAmount, + inputCurrencyAmount, + outputCurrencyAmount, }) // Set the block containing the response to the oldest valid block to ensure that the @@ -213,8 +200,8 @@ export default memo(function SwapButton({ disabled }: SwapButtonProps) { }) }, [ addTransaction, - inputTradeCurrencyAmount, - outputTradeCurrencyAmount, + inputCurrencyAmount, + outputCurrencyAmount, setDisplayTxHash, setOldestValidBlock, swapCallback, @@ -222,29 +209,28 @@ export default memo(function SwapButton({ disabled }: SwapButtonProps) { ]) const ButtonText = useCallback(() => { - if ((wrapType === WrapType.WRAP || wrapType === WrapType.UNWRAP) && wrapError !== WrapError.NO_ERROR) { - return - } switch (wrapType) { case WrapType.UNWRAP: - return Unwrap + return Unwrap {inputCurrency?.symbol} case WrapType.WRAP: - return Wrap - case WrapType.NOT_APPLICABLE: + return Wrap {inputCurrency?.symbol} + case WrapType.NONE: default: return Review swap } - }, [wrapError, wrapType]) + }, [inputCurrency?.symbol, wrapType]) const handleDialogClose = useCallback(() => { setActiveTrade(undefined) }, []) const handleActionButtonClick = useCallback(async () => { - if (wrapType === WrapType.NOT_APPLICABLE) { + if (wrapType === WrapType.NONE) { setActiveTrade(trade.trade) } else { const transaction = await wrapCallback() + if (!transaction) return + addTransaction({ response: transaction, type: TransactionType.WRAP, diff --git a/src/lib/components/Swap/Toolbar/Caption.tsx b/src/lib/components/Swap/Toolbar/Caption.tsx index a624eba226..c89b5aa67f 100644 --- a/src/lib/components/Swap/Toolbar/Caption.tsx +++ b/src/lib/components/Swap/Toolbar/Caption.tsx @@ -4,7 +4,6 @@ import Column from 'lib/components/Column' import Rule from 'lib/components/Rule' import Tooltip from 'lib/components/Tooltip' import { loadingCss } from 'lib/css/loading' -import { WrapType } from 'lib/hooks/swap/useWrapCallback' import { PriceImpact } from 'lib/hooks/useUSDCPriceImpact' import { AlertTriangle, Icon, Info, InlineSpinner } from 'lib/icons' import styled, { ThemedText } from 'lib/theme' @@ -38,7 +37,7 @@ export function ConnectWallet() { } export function UnsupportedNetwork() { - return Unsupported network - switch to another to trade.} /> + return Unsupported network - switch to another to trade} /> } export function InsufficientBalance({ currency }: { currency: Currency }) { @@ -66,15 +65,17 @@ export function LoadingTrade() { ) } -export function WrapCurrency({ loading, wrapType }: { loading: boolean; wrapType: WrapType.UNWRAP | WrapType.WRAP }) { - const WrapText = useCallback(() => { - if (wrapType === WrapType.WRAP) { - return loading ? Wrapping native currency. : Wrap native currency. - } - return loading ? Unwrapping native currency. : Unwrap native currency. - }, [loading, wrapType]) +export function WrapCurrency({ inputCurrency, outputCurrency }: { inputCurrency: Currency; outputCurrency: Currency }) { + const Text = useCallback( + () => ( + + Convert {inputCurrency.symbol} to {outputCurrency.symbol} with no slippage + + ), + [inputCurrency.symbol, outputCurrency.symbol] + ) - return } /> + return } /> } export function Trade({ diff --git a/src/lib/components/Swap/Toolbar/index.tsx b/src/lib/components/Swap/Toolbar/index.tsx index eb55619a7a..e9a884a310 100644 --- a/src/lib/components/Swap/Toolbar/index.tsx +++ b/src/lib/components/Swap/Toolbar/index.tsx @@ -20,14 +20,14 @@ const ToolbarRow = styled(Row)` export default memo(function Toolbar({ disabled }: { disabled?: boolean }) { const { chainId } = useActiveWeb3React() const { - [Field.INPUT]: { currency: inputCurrency, balance }, + [Field.INPUT]: { currency: inputCurrency, balance: inputBalance, amount: inputAmount }, [Field.OUTPUT]: { currency: outputCurrency, usdc: outputUSDC }, trade: { trade, state }, impact, } = useSwapInfo() const isRouteLoading = state === TradeState.SYNCING || state === TradeState.LOADING const isAmountPopulated = useIsAmountPopulated() - const { type: wrapType, loading: wrapLoading } = useWrapCallback() + const { type: wrapType } = useWrapCallback() const caption = useMemo(() => { if (disabled) { return @@ -38,8 +38,11 @@ export default memo(function Toolbar({ disabled }: { disabled?: boolean }) { } if (inputCurrency && outputCurrency && isAmountPopulated) { - if (wrapType !== WrapType.NOT_APPLICABLE) { - return + if (inputBalance && inputAmount?.greaterThan(inputBalance)) { + return + } + if (wrapType !== WrapType.NONE) { + return } if (isRouteLoading) { return @@ -47,9 +50,6 @@ export default memo(function Toolbar({ disabled }: { disabled?: boolean }) { if (!trade?.swaps) { return } - if (balance && trade?.inputAmount.greaterThan(balance)) { - return - } if (trade.inputAmount && trade.outputAmount) { return } @@ -57,17 +57,17 @@ export default memo(function Toolbar({ disabled }: { disabled?: boolean }) { return }, [ - balance, chainId, disabled, impact, + inputAmount, + inputBalance, inputCurrency, isAmountPopulated, isRouteLoading, outputCurrency, outputUSDC, trade, - wrapLoading, wrapType, ]) diff --git a/src/lib/components/Swap/WrapErrorText.tsx b/src/lib/components/Swap/WrapErrorText.tsx deleted file mode 100644 index 07e38825b1..0000000000 --- a/src/lib/components/Swap/WrapErrorText.tsx +++ /dev/null @@ -1,22 +0,0 @@ -import { Trans } from '@lingui/macro' -import { WrapError } from 'lib/hooks/swap/useWrapCallback' -import useNativeCurrency from 'lib/hooks/useNativeCurrency' - -export function WrapErrorText({ wrapError }: { wrapError: WrapError }) { - const native = useNativeCurrency() - const wrapped = native?.wrapped - - switch (wrapError) { - case WrapError.ENTER_NATIVE_AMOUNT: - return Enter {native?.symbol} amount - case WrapError.ENTER_WRAPPED_AMOUNT: - return Enter {wrapped?.symbol} amount - case WrapError.INSUFFICIENT_NATIVE_BALANCE: - return Insufficient {native?.symbol} balance - case WrapError.INSUFFICIENT_WRAPPED_BALANCE: - return Insufficient {wrapped?.symbol} balance - case WrapError.NO_ERROR: - default: - return null - } -} diff --git a/src/lib/hooks/routing/useClientSideSmartOrderRouterTrade.ts b/src/lib/hooks/routing/useClientSideSmartOrderRouterTrade.ts index 3d26b13c62..8fcb0ef9c8 100644 --- a/src/lib/hooks/routing/useClientSideSmartOrderRouterTrade.ts +++ b/src/lib/hooks/routing/useClientSideSmartOrderRouterTrade.ts @@ -72,7 +72,7 @@ export default function useClientSideSmartOrderRouterTrade => { - if (wrapType !== WrapType.NOT_APPLICABLE) return { error: undefined } + if (wrapType !== WrapType.NONE) return { error: undefined } if (!queryArgs || !params) return { error: undefined } try { return await getClientSideQuote(queryArgs, params, config) diff --git a/src/lib/hooks/swap/useWrapCallback.tsx b/src/lib/hooks/swap/useWrapCallback.tsx index c17bbf9664..ed9a1c92eb 100644 --- a/src/lib/hooks/swap/useWrapCallback.tsx +++ b/src/lib/hooks/swap/useWrapCallback.tsx @@ -1,133 +1,71 @@ import { ContractTransaction } from '@ethersproject/contracts' import { useWETHContract } from 'hooks/useContract' -import { atom, useAtom } from 'jotai' import { useAtomValue } from 'jotai/utils' import { Field, swapAtom } from 'lib/state/swap' import tryParseCurrencyAmount from 'lib/utils/tryParseCurrencyAmount' -import { useCallback, useEffect, useMemo } from 'react' +import { useCallback, useMemo } from 'react' import { WRAPPED_NATIVE_CURRENCY } from '../../../constants/tokens' import useActiveWeb3React from '../useActiveWeb3React' -import { useCurrencyBalances } from '../useCurrencyBalance' +import useCurrencyBalance from '../useCurrencyBalance' export enum WrapType { - NOT_APPLICABLE, + NONE, WRAP, UNWRAP, } interface UseWrapCallbackReturns { - callback: () => Promise - error: WrapError - loading: boolean + callback: () => Promise type: WrapType } -export enum WrapError { - NO_ERROR = 0, // must be equal to 0 so all other errors are truthy - ENTER_NATIVE_AMOUNT, - ENTER_WRAPPED_AMOUNT, - INSUFFICIENT_NATIVE_BALANCE, - INSUFFICIENT_WRAPPED_BALANCE, -} - -interface WrapState { - loading: boolean - error: WrapError -} - -const wrapState = atom({ - loading: false, - error: WrapError.NO_ERROR, -}) - export default function useWrapCallback(): UseWrapCallbackReturns { const { account, chainId } = useActiveWeb3React() - const [{ loading, error }, setWrapState] = useAtom(wrapState) const wrappedNativeCurrencyContract = useWETHContract() const { amount, [Field.INPUT]: inputCurrency, [Field.OUTPUT]: outputCurrency } = useAtomValue(swapAtom) const wrapType = useMemo(() => { - if (!inputCurrency || !outputCurrency || !chainId) { - return WrapType.NOT_APPLICABLE + if (chainId && inputCurrency && outputCurrency) { + if (inputCurrency.isNative && WRAPPED_NATIVE_CURRENCY[chainId]?.equals(outputCurrency)) { + return WrapType.WRAP + } + if (outputCurrency.isNative && WRAPPED_NATIVE_CURRENCY[chainId]?.equals(inputCurrency)) { + return WrapType.UNWRAP + } } - if (inputCurrency.isNative && WRAPPED_NATIVE_CURRENCY[chainId]?.equals(outputCurrency)) { - return WrapType.WRAP - } - if (WRAPPED_NATIVE_CURRENCY[chainId]?.equals(inputCurrency) && outputCurrency.isNative) { - return WrapType.UNWRAP - } - return WrapType.NOT_APPLICABLE + return WrapType.NONE }, [chainId, inputCurrency, outputCurrency]) const parsedAmountIn = useMemo( () => tryParseCurrencyAmount(amount, inputCurrency ?? undefined), [inputCurrency, amount] ) - - const relevantTokenBalances = useCurrencyBalances( - account, - useMemo(() => [inputCurrency ?? undefined, outputCurrency ?? undefined], [inputCurrency, outputCurrency]) - ) - const currencyBalances = useMemo( - () => ({ - [Field.INPUT]: relevantTokenBalances[0], - [Field.OUTPUT]: relevantTokenBalances[1], - }), - [relevantTokenBalances] - ) - - const hasInputAmount = Boolean(parsedAmountIn?.greaterThan('0')) - const sufficientBalance = parsedAmountIn && !currencyBalances[Field.INPUT]?.lessThan(parsedAmountIn) - - useEffect(() => { - if (sufficientBalance) { - setWrapState((state) => ({ ...state, error: WrapError.NO_ERROR })) - } else if (wrapType === WrapType.WRAP) { - setWrapState((state) => ({ - ...state, - error: hasInputAmount ? WrapError.INSUFFICIENT_NATIVE_BALANCE : WrapError.ENTER_NATIVE_AMOUNT, - })) - } else if (wrapType === WrapType.UNWRAP) { - setWrapState((state) => ({ - ...state, - error: hasInputAmount ? WrapError.INSUFFICIENT_WRAPPED_BALANCE : WrapError.ENTER_WRAPPED_AMOUNT, - })) - } - }, [hasInputAmount, setWrapState, sufficientBalance, wrapType]) + const balanceIn = useCurrencyBalance(account, inputCurrency) const callback = useCallback(async () => { + if (wrapType === WrapType.NONE) { + return Promise.reject('Wrapping not applicable to this asset.') + } if (!parsedAmountIn) { return Promise.reject('Must provide an input amount to wrap.') } - if (wrapType === WrapType.NOT_APPLICABLE) { - return Promise.reject('Wrapping not applicable to this asset.') - } - if (!sufficientBalance) { + if (!balanceIn || balanceIn.lessThan(parsedAmountIn)) { return Promise.reject('Insufficient balance to wrap desired amount.') } if (!wrappedNativeCurrencyContract) { return Promise.reject('Wrap contract not found.') } - setWrapState((state) => ({ ...state, loading: true })) - const result = await (wrapType === WrapType.WRAP - ? wrappedNativeCurrencyContract.deposit({ value: `0x${parsedAmountIn.quotient.toString(16)}` }) - : wrappedNativeCurrencyContract.withdraw(`0x${parsedAmountIn.quotient.toString(16)}`) - ).catch((e: unknown) => { - setWrapState((state) => ({ ...state, loading: false })) - throw e - }) - // resolve loading state after one confirmation - result.wait(1).finally(() => setWrapState((state) => ({ ...state, loading: false }))) - return Promise.resolve(result) - }, [wrappedNativeCurrencyContract, sufficientBalance, parsedAmountIn, wrapType, setWrapState]) - return useMemo( - () => ({ - callback, - error, - loading, - type: wrapType, - }), - [callback, error, loading, wrapType] - ) + try { + return await (wrapType === WrapType.WRAP + ? wrappedNativeCurrencyContract.deposit({ value: `0x${parsedAmountIn.quotient.toString(16)}` }) + : wrappedNativeCurrencyContract.withdraw(`0x${parsedAmountIn.quotient.toString(16)}`)) + } catch (e) { + // TODO(zzmp): add error handling + console.error(e) + return + } + }, [wrapType, parsedAmountIn, balanceIn, wrappedNativeCurrencyContract]) + + return useMemo(() => ({ callback, type: wrapType }), [callback, wrapType]) }