feat: implementing permit2 with pay with any token (#5926)

* feat: implementing permit2 with pay with any token

* permit2 hook

* usePayWithAnyTokenHook

* removing ternary operators

* weird export type bug

* resolving merge

* fixing nft test

* styles

* refactoring styles

* reformatting

* price impact warnings

* forgot trans tag

* responding to comments

* fixes

* disabling pay with any token when on the wrong chain

* missing enabled flag

* vertically centering button
This commit is contained in:
Jack Short 2023-02-09 13:41:30 -05:00 committed by GitHub
parent 6df2f3677e
commit 3eaeb65b07
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 365 additions and 83 deletions

@ -1,7 +1,24 @@
import { UNIVERSAL_ROUTER_ADDRESS } from '@uniswap/universal-router-sdk'
import { useWeb3React } from '@web3-react/core'
import { BaseVariant, FeatureFlag, useBaseFlag } from '../index'
export function usePayWithAnyTokenFlag(): BaseVariant {
return useBaseFlag(FeatureFlag.payWithAnyToken)
}
export function usePayWithAnyTokenEnabled(): boolean {
const flagEnabled = usePayWithAnyTokenFlag() === BaseVariant.Enabled
const { chainId } = useWeb3React()
try {
// Detect if the Universal Router is not yet deployed to chainId.
// This is necessary so that we can fallback correctly on chains without a Universal Router deployment.
// It will be removed once Universal Router is deployed on all supported chains.
chainId && UNIVERSAL_ROUTER_ADDRESS(chainId)
return flagEnabled
} catch {
return false
}
}
export { BaseVariant as PayWithAnyTokenVariant }

@ -579,7 +579,6 @@ export type PortfolioTokensTotalDenominatedValueChangeArgs = {
export type Query = {
__typename?: 'Query';
assetActivities?: Maybe<Array<Maybe<AssetActivity>>>;
nftAssets?: Maybe<NftAssetConnection>;
nftBalances?: Maybe<NftBalanceConnection>;
nftCollections?: Maybe<NftCollectionConnection>;
@ -595,13 +594,6 @@ export type Query = {
};
export type QueryAssetActivitiesArgs = {
address: Scalars['String'];
page?: InputMaybe<Scalars['Int']>;
pageSize?: InputMaybe<Scalars['Int']>;
};
export type QueryNftAssetsArgs = {
address: Scalars['String'];
after?: InputMaybe<Scalars['String']>;

@ -1,23 +1,28 @@
import { BigNumber } from '@ethersproject/bignumber'
import { formatEther } from '@ethersproject/units'
import { parseEther } from '@ethersproject/units'
import { Trans } from '@lingui/macro'
import { t, Trans } from '@lingui/macro'
import { TraceEvent } from '@uniswap/analytics'
import { BrowserEvent, InterfaceElementName, NFTEventName } from '@uniswap/analytics-events'
import { Currency, TradeType } from '@uniswap/sdk-core'
import { formatPriceImpact } from '@uniswap/conedison/format'
import { Currency, CurrencyAmount, Percent, Token, TradeType } from '@uniswap/sdk-core'
import { useWeb3React } from '@web3-react/core'
import Column from 'components/Column'
import Loader from 'components/Loader'
import CurrencyLogo from 'components/Logo/CurrencyLogo'
import Row from 'components/Row'
import CurrencySearchModal from 'components/SearchModal/CurrencySearchModal'
import { LoadingBubble } from 'components/Tokens/loading'
import { MouseoverTooltip } from 'components/Tooltip'
import { SupportedChainId } from 'constants/chains'
import { PayWithAnyTokenVariant, usePayWithAnyTokenFlag } from 'featureFlags/flags/payWithAnyToken'
import { usePayWithAnyTokenEnabled } from 'featureFlags/flags/payWithAnyToken'
import { useCurrency } from 'hooks/Tokens'
import { useBestTrade } from 'hooks/useBestTrade'
import { AllowanceState } from 'hooks/usePermit2Allowance'
import { useStablecoinValue } from 'hooks/useStablecoinPrice'
import tryParseCurrencyAmount from 'lib/utils/tryParseCurrencyAmount'
import { useBag } from 'nft/hooks/useBag'
import usePayWithAnyTokenSwap from 'nft/hooks/usePayWithAnyTokenSwap'
import usePermit2Approval from 'nft/hooks/usePermit2Approval'
import { useTokenInput } from 'nft/hooks/useTokenInput'
import { useWalletBalance } from 'nft/hooks/useWalletBalance'
import { BagStatus } from 'nft/types'
@ -25,11 +30,16 @@ import { ethNumberStandardFormatter, formatWeiToDecimal } from 'nft/utils'
import { PropsWithChildren, useMemo, useState } from 'react'
import { AlertTriangle, ChevronDown } from 'react-feather'
import { useToggleWalletModal } from 'state/application/hooks'
import { TradeState } from 'state/routing/types'
import { InterfaceTrade, TradeState } from 'state/routing/types'
import styled, { useTheme } from 'styled-components/macro'
import { ThemedText } from 'theme'
import { computeFiatValuePriceImpact } from 'utils/computeFiatValuePriceImpact'
import { warningSeverity } from 'utils/prices'
import { switchChain } from 'utils/switchChain'
const LOW_SEVERITY_THRESHOLD = 1
const MEDIUM_SEVERITY_THRESHOLD = 3
const FooterContainer = styled.div`
padding: 0px 12px;
`
@ -45,14 +55,12 @@ const Footer = styled.div`
border-bottom-right-radius: 12px;
`
const FooterHeader = styled(Column)<{ warningText?: boolean }>`
const FooterHeader = styled(Column)<{ usingPayWithAnyToken?: boolean }>`
padding-top: 8px;
padding-bottom: ${({ warningText }) => (warningText ? '8px' : '20px')};
padding-bottom: ${({ usingPayWithAnyToken }) => (usingPayWithAnyToken ? '16px' : '20px')};
`
const CurrencyRow = styled(Row)<{ warningText?: boolean }>`
padding-top: 4px;
padding-bottom: ${({ warningText }) => (warningText ? '8px' : '20px')};
const CurrencyRow = styled(Row)`
justify-content: space-between;
align-items: start;
`
@ -71,17 +79,26 @@ const WarningText = styled(ThemedText.BodyPrimary)`
color: ${({ theme }) => theme.accentWarning};
display: flex;
justify-content: center;
margin: 12px 0 !important;
margin-bottom: 10px !important;
text-align: center;
`
const HelperText = styled(ThemedText.Caption)<{ $color: string }>`
color: ${({ $color }) => $color};
display: flex;
justify-content: center;
text-align: center;
margin-bottom: 10px !important;
`
const CurrencyInput = styled(Row)`
gap: 8px;
cursor: pointer;
`
const PayButton = styled(Row)<{ disabled?: boolean }>`
background: ${({ theme }) => theme.accentAction};
const PayButton = styled.button<{ $backgroundColor: string }>`
display: flex;
background: ${({ $backgroundColor }) => $backgroundColor};
color: ${({ theme }) => theme.accentTextLightPrimary};
font-weight: 600;
line-height: 24px;
@ -91,18 +108,41 @@ const PayButton = styled(Row)<{ disabled?: boolean }>`
border: none;
border-radius: 12px;
padding: 12px 0px;
opacity: ${({ disabled }) => (disabled ? 0.6 : 1)};
cursor: ${({ disabled }) => (disabled ? 'auto' : 'pointer')};
cursor: pointer;
align-items: center;
&:disabled {
opacity: 0.6;
cursor: auto;
}
`
const FiatLoadingBubble = styled(LoadingBubble)`
border-radius: 4px;
width: 4rem;
height: 1rem;
align-self: end;
`
const PriceImpactContainer = styled(Row)`
align-items: center;
gap: 8px;
width: 100%;
justify-content: flex-end;
`
const PriceImpactRow = styled(Row)`
align-items: center;
gap: 8px;
`
interface ActionButtonProps {
disabled?: boolean
onClick: () => void
backgroundColor: string
}
const ActionButton = ({ disabled, children, onClick }: PropsWithChildren<ActionButtonProps>) => {
const ActionButton = ({ disabled, children, onClick, backgroundColor }: PropsWithChildren<ActionButtonProps>) => {
return (
<PayButton disabled={disabled} onClick={onClick}>
<PayButton disabled={disabled} onClick={onClick} $backgroundColor={backgroundColor}>
{children}
</PayButton>
)
@ -120,6 +160,97 @@ const Warning = ({ children }: PropsWithChildren<unknown>) => {
)
}
interface HelperTextProps {
color: string
}
const Helper = ({ children, color }: PropsWithChildren<HelperTextProps>) => {
if (!children) {
return null
}
return (
<HelperText lineHeight="16px" $color={color}>
{children}
</HelperText>
)
}
// TODO: ask design about no route found
const InputCurrencyValue = ({
usingPayWithAnyToken,
totalEthPrice,
activeCurrency,
tradeState,
trade,
}: {
usingPayWithAnyToken: boolean
totalEthPrice: BigNumber
activeCurrency: Currency | undefined | null
tradeState: TradeState
trade: InterfaceTrade<Currency, Currency, TradeType> | undefined
}) => {
if (!usingPayWithAnyToken) {
return (
<ThemedText.BodyPrimary lineHeight="20px" fontWeight="500">
{formatWeiToDecimal(totalEthPrice.toString())}
&nbsp;{activeCurrency?.symbol ?? 'ETH'}
</ThemedText.BodyPrimary>
)
}
if (tradeState === TradeState.VALID || tradeState === TradeState.SYNCING) {
return (
<ThemedText.BodyPrimary
lineHeight="20px"
fontWeight="500"
color={tradeState === TradeState.VALID ? 'textPrimary' : 'textTertiary'}
>
{ethNumberStandardFormatter(trade?.inputAmount.toExact())}
</ThemedText.BodyPrimary>
)
}
return (
<ThemedText.BodyPrimary color="textTertiary" lineHeight="20px" fontWeight="500">
<Trans>Fetching price...</Trans>
</ThemedText.BodyPrimary>
)
}
const FiatValue = ({
usdcValue,
priceImpact,
priceImpactColor,
}: {
usdcValue: CurrencyAmount<Token> | null
priceImpact: Percent | undefined
priceImpactColor: string | undefined
}) => {
if (!usdcValue) {
return <FiatLoadingBubble />
}
return (
<PriceImpactContainer>
{priceImpact && priceImpactColor && (
<>
<MouseoverTooltip text={t`The estimated difference between the USD values of input and output amounts.`}>
<PriceImpactRow>
<AlertTriangle color={priceImpactColor} size="16px" />
<ThemedText.BodySmall style={{ color: priceImpactColor }} lineHeight="20px">
(<Trans>{formatPriceImpact(priceImpact)}</Trans>)
</ThemedText.BodySmall>
</PriceImpactRow>
</MouseoverTooltip>
</>
)}
<ThemedText.BodySmall color="textTertiary" lineHeight="20px">
{`${ethNumberStandardFormatter(usdcValue?.toExact(), true)}`}
</ThemedText.BodySmall>
</PriceImpactContainer>
)
}
interface BagFooterProps {
totalEthPrice: BigNumber
bagStatus: BagStatus
@ -139,7 +270,7 @@ export const BagFooter = ({ totalEthPrice, bagStatus, fetchAssets, eventProperti
const theme = useTheme()
const { account, chainId, connector } = useWeb3React()
const connected = Boolean(account && chainId)
const shouldUsePayWithAnyToken = usePayWithAnyTokenFlag() === PayWithAnyTokenVariant.Enabled
const shouldUsePayWithAnyToken = usePayWithAnyTokenEnabled()
const inputCurrency = useTokenInput((state) => state.inputCurrency)
const setInputCurrency = useTokenInput((state) => state.setInputCurrency)
const defaultCurrency = useCurrency('ETH')
@ -155,11 +286,52 @@ export const BagFooter = ({ totalEthPrice, bagStatus, fetchAssets, eventProperti
return parseEther(balanceInEth).gte(totalEthPrice)
}, [connected, chainId, balanceInEth, totalEthPrice])
const { buttonText, disabled, warningText, handleClick } = useMemo(() => {
const isPending = PENDING_BAG_STATUSES.includes(bagStatus)
const activeCurrency = inputCurrency ?? defaultCurrency
const usingPayWithAnyToken = !!inputCurrency && shouldUsePayWithAnyToken
const parsedOutputAmount = useMemo(() => {
return tryParseCurrencyAmount(formatEther(totalEthPrice.toString()), defaultCurrency ?? undefined)
}, [defaultCurrency, totalEthPrice])
const { state: tradeState, trade, maximumAmountIn } = usePayWithAnyTokenSwap(inputCurrency, parsedOutputAmount)
const { allowance, isAllowancePending, isApprovalLoading, updateAllowance } = usePermit2Approval(
trade?.inputAmount.currency.isToken ? (trade?.inputAmount as CurrencyAmount<Token>) : undefined,
maximumAmountIn,
shouldUsePayWithAnyToken
)
const fiatValueTradeInput = useStablecoinValue(trade?.inputAmount)
const fiatValueTradeOutput = useStablecoinValue(parsedOutputAmount)
const usdcValue = usingPayWithAnyToken ? fiatValueTradeInput : fiatValueTradeOutput
const stablecoinPriceImpact = useMemo(
() =>
tradeState === TradeState.SYNCING || !trade
? undefined
: computeFiatValuePriceImpact(fiatValueTradeInput, fiatValueTradeOutput),
[fiatValueTradeInput, fiatValueTradeOutput, tradeState, trade]
)
const { priceImpactWarning, priceImpactColor } = useMemo(() => {
const severity = warningSeverity(stablecoinPriceImpact)
if (severity < LOW_SEVERITY_THRESHOLD) {
return { priceImpactWarning: false, priceImpactColor: undefined }
}
if (severity < MEDIUM_SEVERITY_THRESHOLD) {
return { priceImpactWarning: false, priceImpactColor: theme.accentWarning }
}
return { priceImpactWarning: true, priceImpactColor: theme.accentCritical }
}, [stablecoinPriceImpact, theme.accentCritical, theme.accentWarning])
const { buttonText, disabled, warningText, helperText, helperTextColor, handleClick, buttonColor } = useMemo(() => {
let handleClick = fetchAssets
let buttonText = <Trans>Something went wrong</Trans>
let disabled = true
let warningText = null
let warningText = undefined
let helperText = undefined
let helperTextColor = theme.textSecondary
let buttonColor = theme.accentAction
if (connected && chainId !== SupportedChainId.MAINNET) {
handleClick = () => switchChain(connector, SupportedChainId.MAINNET)
@ -179,34 +351,62 @@ export const BagFooter = ({ totalEthPrice, bagStatus, fetchAssets, eventProperti
}
disabled = false
buttonText = <Trans>Connect wallet</Trans>
} else if (usingPayWithAnyToken && tradeState !== TradeState.VALID) {
disabled = true
buttonText = <Trans>Fetching Route</Trans>
} else if (allowance.state === AllowanceState.REQUIRED || allowance.state === AllowanceState.LOADING) {
handleClick = () => updateAllowance()
disabled = isAllowancePending || isApprovalLoading || allowance.state === AllowanceState.LOADING
if (allowance.state === AllowanceState.LOADING) {
buttonText = <Trans>Loading Allowance</Trans>
} else if (isAllowancePending) {
buttonText = <Trans>Approve in your wallet</Trans>
} else if (isApprovalLoading) {
buttonText = <Trans>Approval pending</Trans>
} else {
helperText = <Trans>An approval is needed to use this token. </Trans>
buttonText = <Trans>Approve</Trans>
}
} else if (bagStatus === BagStatus.FETCHING_FINAL_ROUTE || bagStatus === BagStatus.CONFIRMING_IN_WALLET) {
disabled = true
buttonText = <Trans>Proceed in wallet</Trans>
} else if (bagStatus === BagStatus.PROCESSING_TRANSACTION) {
disabled = true
buttonText = <Trans>Transaction pending</Trans>
} else if (priceImpactWarning && priceImpactColor) {
disabled = false
buttonColor = priceImpactColor
helperText = <Trans>Price impact warning</Trans>
helperTextColor = priceImpactColor
buttonText = <Trans>Pay Anyway</Trans>
} else if (sufficientBalance === true) {
disabled = false
buttonText = <Trans>Pay</Trans>
}
return { buttonText, disabled, warningText, handleClick }
}, [bagStatus, chainId, connected, connector, fetchAssets, setBagExpanded, sufficientBalance, toggleWalletModal])
return { buttonText, disabled, warningText, helperText, helperTextColor, handleClick, buttonColor }
}, [
fetchAssets,
theme.textSecondary,
theme.accentAction,
connected,
chainId,
sufficientBalance,
bagStatus,
usingPayWithAnyToken,
tradeState,
allowance.state,
priceImpactWarning,
priceImpactColor,
connector,
toggleWalletModal,
setBagExpanded,
isAllowancePending,
isApprovalLoading,
updateAllowance,
])
const isPending = PENDING_BAG_STATUSES.includes(bagStatus)
const activeCurrency = inputCurrency ?? defaultCurrency
const parsedAmount = useMemo(() => {
return tryParseCurrencyAmount(formatEther(totalEthPrice.toString()), defaultCurrency ?? undefined)
}, [defaultCurrency, totalEthPrice])
const { state: swapState, trade: swapTrade } = useBestTrade(
TradeType.EXACT_OUTPUT,
parsedAmount,
inputCurrency ?? undefined
)
const usdcValue = useStablecoinValue(inputCurrency ? swapTrade?.inputAmount : parsedAmount)
const traceEventProperties = {
usd_value: usdcValue?.toExact(),
...eventProperties,
@ -216,55 +416,50 @@ export const BagFooter = ({ totalEthPrice, bagStatus, fetchAssets, eventProperti
<FooterContainer>
<Footer>
{shouldUsePayWithAnyToken && (
<CurrencyRow>
<Column gap="xs">
<ThemedText.SubHeaderSmall>
<Trans>Pay with</Trans>
</ThemedText.SubHeaderSmall>
<CurrencyInput onClick={() => setTokenSelectorOpen(true)}>
<CurrencyLogo currency={activeCurrency} size="24px" />
<ThemedText.HeadlineSmall fontWeight={500} lineHeight="24px">
{activeCurrency?.symbol}
</ThemedText.HeadlineSmall>
<ChevronDown size={20} color={theme.textSecondary} />
</CurrencyInput>
</Column>
<TotalColumn gap="xs">
<ThemedText.SubHeaderSmall marginBottom="4px">
<Trans>Total</Trans>
</ThemedText.SubHeaderSmall>
<ThemedText.HeadlineSmall>
{inputCurrency
? swapState !== TradeState.VALID
? '-'
: ethNumberStandardFormatter(swapTrade?.inputAmount.toExact())
: formatWeiToDecimal(totalEthPrice.toString())}
&nbsp;{activeCurrency?.symbol ?? 'ETH'}
</ThemedText.HeadlineSmall>
<ThemedText.BodySmall color="textSecondary" lineHeight="20px">
{`${ethNumberStandardFormatter(usdcValue?.toExact(), true)}`}
</ThemedText.BodySmall>
</TotalColumn>
</CurrencyRow>
<FooterHeader gap="xs" usingPayWithAnyToken={shouldUsePayWithAnyToken}>
<CurrencyRow>
<Column gap="xs">
<ThemedText.SubHeaderSmall>
<Trans>Pay with</Trans>
</ThemedText.SubHeaderSmall>
<CurrencyInput onClick={() => setTokenSelectorOpen(true)}>
<CurrencyLogo currency={activeCurrency} size="24px" />
<ThemedText.HeadlineSmall fontWeight={500} lineHeight="24px">
{activeCurrency?.symbol}
</ThemedText.HeadlineSmall>
<ChevronDown size={20} color={theme.textSecondary} />
</CurrencyInput>
</Column>
<TotalColumn gap="xs">
<ThemedText.SubHeaderSmall marginBottom="4px">
<Trans>Total</Trans>
</ThemedText.SubHeaderSmall>
<InputCurrencyValue
usingPayWithAnyToken={usingPayWithAnyToken}
totalEthPrice={totalEthPrice}
activeCurrency={activeCurrency}
tradeState={tradeState}
trade={trade}
/>
</TotalColumn>
</CurrencyRow>
<FiatValue usdcValue={usdcValue} priceImpact={stablecoinPriceImpact} priceImpactColor={priceImpactColor} />
</FooterHeader>
)}
{!shouldUsePayWithAnyToken && (
<FooterHeader gap="xs" warningText={!!warningText}>
<FooterHeader gap="xs">
<Row justify="space-between">
<div>
<ThemedText.HeadlineSmall>Total</ThemedText.HeadlineSmall>
</div>
<div>
<ThemedText.HeadlineSmall>
{formatWeiToDecimal(totalEthPrice.toString())}&nbsp;ETH
{formatWeiToDecimal(totalEthPrice.toString())}
&nbsp;{activeCurrency?.symbol ?? 'ETH'}
</ThemedText.HeadlineSmall>
</div>
</Row>
<Row justify="flex-end">
<ThemedText.BodySmall color="textSecondary" lineHeight="20px">{`${ethNumberStandardFormatter(
usdcValue?.toExact(),
true
)}`}</ThemedText.BodySmall>
</Row>
<FiatValue usdcValue={usdcValue} priceImpact={stablecoinPriceImpact} priceImpactColor={priceImpactColor} />
</FooterHeader>
)}
<TraceEvent
@ -275,7 +470,8 @@ export const BagFooter = ({ totalEthPrice, bagStatus, fetchAssets, eventProperti
shouldLogImpression={connected && !disabled}
>
<Warning>{warningText}</Warning>
<ActionButton onClick={handleClick} disabled={disabled}>
<Helper color={helperTextColor}>{helperText}</Helper>
<ActionButton onClick={handleClick} disabled={disabled} backgroundColor={buttonColor}>
{isPending && <Loader size="20px" stroke="white" />}
{buttonText}
</ActionButton>

@ -0,0 +1,29 @@
import { Currency, CurrencyAmount, NativeCurrency, Token, TradeType } from '@uniswap/sdk-core'
import useAutoSlippageTolerance from 'hooks/useAutoSlippageTolerance'
import { useBestTrade } from 'hooks/useBestTrade'
import { useMemo } from 'react'
import { InterfaceTrade, TradeState } from 'state/routing/types'
export default function usePayWithAnyTokenSwap(
inputCurrency?: Currency,
parsedOutputAmount?: CurrencyAmount<NativeCurrency | Token>
): {
state: TradeState
trade: InterfaceTrade<Currency, Currency, TradeType> | undefined
maximumAmountIn: CurrencyAmount<Token> | undefined
} {
const { state, trade } = useBestTrade(TradeType.EXACT_OUTPUT, parsedOutputAmount, inputCurrency ?? undefined)
const allowedSlippage = useAutoSlippageTolerance(trade)
const maximumAmountIn = useMemo(() => {
const maximumAmountIn = trade?.maximumAmountIn(allowedSlippage)
return maximumAmountIn?.currency.isToken ? (maximumAmountIn as CurrencyAmount<Token>) : undefined
}, [allowedSlippage, trade])
return useMemo(() => {
return {
state,
trade,
maximumAmountIn,
}
}, [maximumAmountIn, state, trade])
}

@ -0,0 +1,48 @@
import { sendAnalyticsEvent } from '@uniswap/analytics'
import { InterfaceEventName } from '@uniswap/analytics-events'
import { CurrencyAmount, Token } from '@uniswap/sdk-core'
import { UNIVERSAL_ROUTER_ADDRESS } from '@uniswap/universal-router-sdk'
import { useWeb3React } from '@web3-react/core'
import usePermit2Allowance, { AllowanceState } from 'hooks/usePermit2Allowance'
import { useCallback, useMemo, useState } from 'react'
import invariant from 'tiny-invariant'
export default function usePermit2Approval(
amount?: CurrencyAmount<Token>,
maximumAmount?: CurrencyAmount<Token>,
enabled?: boolean
) {
const { chainId } = useWeb3React()
const allowance = usePermit2Allowance(
enabled ? maximumAmount ?? (amount?.currency.isToken ? (amount as CurrencyAmount<Token>) : undefined) : undefined,
enabled && chainId ? UNIVERSAL_ROUTER_ADDRESS(chainId) : undefined
)
const isApprovalLoading = allowance.state === AllowanceState.REQUIRED && allowance.isApprovalLoading
const [isAllowancePending, setIsAllowancePending] = useState(false)
const updateAllowance = useCallback(async () => {
invariant(allowance.state === AllowanceState.REQUIRED)
setIsAllowancePending(true)
try {
await allowance.approveAndPermit()
sendAnalyticsEvent(InterfaceEventName.APPROVE_TOKEN_TXN_SUBMITTED, {
chain_id: chainId,
token_symbol: maximumAmount?.currency.symbol,
token_address: maximumAmount?.currency.address,
})
} catch (e) {
console.error(e)
} finally {
setIsAllowancePending(false)
}
}, [allowance, chainId, maximumAmount?.currency.address, maximumAmount?.currency.symbol])
return useMemo(() => {
return {
allowance,
isApprovalLoading,
isAllowancePending,
updateAllowance,
}
}, [allowance, isAllowancePending, isApprovalLoading, updateAllowance])
}