fix: bring wrap ui to spec (#3577)
This commit is contained in:
parent
6c4f7ab9a1
commit
76cbd82cb7
@ -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) {
|
||||
<SwapSummary input={tx.info.inputCurrencyAmount} output={tx.info.outputCurrencyAmount} />
|
||||
) : null}
|
||||
</StatusHeader>
|
||||
<Rule />
|
||||
<TransactionRow flex>
|
||||
<ThemedText.ButtonSmall>
|
||||
<EtherscanLink type={ExplorerDataType.TRANSACTION} data={tx.info.response.hash}>
|
||||
|
@ -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<ActionButtonProps> | 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 <WrapErrorText wrapError={wrapError} />
|
||||
}
|
||||
switch (wrapType) {
|
||||
case WrapType.UNWRAP:
|
||||
return <Trans>Unwrap</Trans>
|
||||
return <Trans>Unwrap {inputCurrency?.symbol}</Trans>
|
||||
case WrapType.WRAP:
|
||||
return <Trans>Wrap</Trans>
|
||||
case WrapType.NOT_APPLICABLE:
|
||||
return <Trans>Wrap {inputCurrency?.symbol}</Trans>
|
||||
case WrapType.NONE:
|
||||
default:
|
||||
return <Trans>Review swap</Trans>
|
||||
}
|
||||
}, [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,
|
||||
|
@ -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 <Caption caption={<Trans>Unsupported network - switch to another to trade.</Trans>} />
|
||||
return <Caption caption={<Trans>Unsupported network - switch to another to trade</Trans>} />
|
||||
}
|
||||
|
||||
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 ? <Trans>Wrapping native currency.</Trans> : <Trans>Wrap native currency.</Trans>
|
||||
}
|
||||
return loading ? <Trans>Unwrapping native currency.</Trans> : <Trans>Unwrap native currency.</Trans>
|
||||
}, [loading, wrapType])
|
||||
export function WrapCurrency({ inputCurrency, outputCurrency }: { inputCurrency: Currency; outputCurrency: Currency }) {
|
||||
const Text = useCallback(
|
||||
() => (
|
||||
<Trans>
|
||||
Convert {inputCurrency.symbol} to {outputCurrency.symbol} with no slippage
|
||||
</Trans>
|
||||
),
|
||||
[inputCurrency.symbol, outputCurrency.symbol]
|
||||
)
|
||||
|
||||
return <Caption icon={Info} caption={<WrapText />} />
|
||||
return <Caption icon={Info} caption={<Text />} />
|
||||
}
|
||||
|
||||
export function Trade({
|
||||
|
@ -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 <Caption.ConnectWallet />
|
||||
@ -38,8 +38,11 @@ export default memo(function Toolbar({ disabled }: { disabled?: boolean }) {
|
||||
}
|
||||
|
||||
if (inputCurrency && outputCurrency && isAmountPopulated) {
|
||||
if (wrapType !== WrapType.NOT_APPLICABLE) {
|
||||
return <Caption.WrapCurrency wrapType={wrapType} loading={wrapLoading} />
|
||||
if (inputBalance && inputAmount?.greaterThan(inputBalance)) {
|
||||
return <Caption.InsufficientBalance currency={inputCurrency} />
|
||||
}
|
||||
if (wrapType !== WrapType.NONE) {
|
||||
return <Caption.WrapCurrency inputCurrency={inputCurrency} outputCurrency={outputCurrency} />
|
||||
}
|
||||
if (isRouteLoading) {
|
||||
return <Caption.LoadingTrade />
|
||||
@ -47,9 +50,6 @@ export default memo(function Toolbar({ disabled }: { disabled?: boolean }) {
|
||||
if (!trade?.swaps) {
|
||||
return <Caption.InsufficientLiquidity />
|
||||
}
|
||||
if (balance && trade?.inputAmount.greaterThan(balance)) {
|
||||
return <Caption.InsufficientBalance currency={trade.inputAmount.currency} />
|
||||
}
|
||||
if (trade.inputAmount && trade.outputAmount) {
|
||||
return <Caption.Trade trade={trade} outputUSDC={outputUSDC} impact={impact} />
|
||||
}
|
||||
@ -57,17 +57,17 @@ export default memo(function Toolbar({ disabled }: { disabled?: boolean }) {
|
||||
|
||||
return <Caption.Empty />
|
||||
}, [
|
||||
balance,
|
||||
chainId,
|
||||
disabled,
|
||||
impact,
|
||||
inputAmount,
|
||||
inputBalance,
|
||||
inputCurrency,
|
||||
isAmountPopulated,
|
||||
isRouteLoading,
|
||||
outputCurrency,
|
||||
outputUSDC,
|
||||
trade,
|
||||
wrapLoading,
|
||||
wrapType,
|
||||
])
|
||||
|
||||
|
@ -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 <Trans>Enter {native?.symbol} amount</Trans>
|
||||
case WrapError.ENTER_WRAPPED_AMOUNT:
|
||||
return <Trans>Enter {wrapped?.symbol} amount</Trans>
|
||||
case WrapError.INSUFFICIENT_NATIVE_BALANCE:
|
||||
return <Trans>Insufficient {native?.symbol} balance</Trans>
|
||||
case WrapError.INSUFFICIENT_WRAPPED_BALANCE:
|
||||
return <Trans>Insufficient {wrapped?.symbol} balance</Trans>
|
||||
case WrapError.NO_ERROR:
|
||||
default:
|
||||
return null
|
||||
}
|
||||
}
|
@ -72,7 +72,7 @@ export default function useClientSideSmartOrderRouterTrade<TTradeType extends Tr
|
||||
const { type: wrapType } = useWrapCallback()
|
||||
|
||||
const getQuoteResult = useCallback(async (): Promise<{ data?: GetQuoteResult; error?: unknown }> => {
|
||||
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)
|
||||
|
@ -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<ContractTransaction>
|
||||
error: WrapError
|
||||
loading: boolean
|
||||
callback: () => Promise<ContractTransaction | undefined>
|
||||
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<WrapState>({
|
||||
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])
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user