feat: use permit when valid on approvals (#3354)
* use permit when valid on approvals * fix broken check for permit sig * update conditionals * update text * remove unneeded else * move permit and approve logic to combined hook * update comment * split txn and approval state, code clean * organize disable conditions * small changes * update conditional check
This commit is contained in:
parent
4f6173675d
commit
8703013b2d
@ -1,18 +1,17 @@
|
||||
import { Trans } from '@lingui/macro'
|
||||
import { Token } from '@uniswap/sdk-core'
|
||||
import { useERC20PermitFromTrade } from 'hooks/useERC20Permit'
|
||||
import { useUpdateAtom } from 'jotai/utils'
|
||||
import { WrapErrorText } from 'lib/components/Swap/WrapErrorText'
|
||||
import { useSwapCurrencyAmount, useSwapInfo, useSwapTradeType } from 'lib/hooks/swap'
|
||||
import useSwapApproval, {
|
||||
ApprovalState,
|
||||
import {
|
||||
ApproveOrPermitState,
|
||||
useApproveOrPermit,
|
||||
useSwapApprovalOptimizedTrade,
|
||||
useSwapRouterAddress,
|
||||
} from 'lib/hooks/swap/useSwapApproval'
|
||||
import { useSwapCallback } from 'lib/hooks/swap/useSwapCallback'
|
||||
import useWrapCallback, { WrapError, WrapType } from 'lib/hooks/swap/useWrapCallback'
|
||||
import { useAddTransaction } from 'lib/hooks/transactions'
|
||||
import { usePendingApproval } from 'lib/hooks/transactions'
|
||||
import { useAddTransaction, usePendingApproval } from 'lib/hooks/transactions'
|
||||
import useActiveWeb3React from 'lib/hooks/useActiveWeb3React'
|
||||
import useTransactionDeadline from 'lib/hooks/useTransactionDeadline'
|
||||
import { Spinner } from 'lib/icons'
|
||||
@ -68,79 +67,86 @@ export default function SwapButton({ disabled }: SwapButtonProps) {
|
||||
useSwapApprovalOptimizedTrade(trade.trade, allowedSlippage, useIsPendingApproval) || trade.trade
|
||||
|
||||
const approvalCurrencyAmount = useSwapCurrencyAmount(Field.INPUT)
|
||||
const [approval, getApproval] = useSwapApproval(
|
||||
|
||||
const { approvalState, signatureData, handleApproveOrPermit } = useApproveOrPermit(
|
||||
optimizedTrade,
|
||||
allowedSlippage,
|
||||
useIsPendingApproval,
|
||||
approvalCurrencyAmount
|
||||
)
|
||||
|
||||
const approvalHash = usePendingApproval(
|
||||
inputCurrency?.isToken ? inputCurrency : undefined,
|
||||
useSwapRouterAddress(optimizedTrade)
|
||||
)
|
||||
|
||||
const addTransaction = useAddTransaction()
|
||||
const addApprovalTransaction = useCallback(() => {
|
||||
getApproval().then((transaction) => {
|
||||
const onApprove = useCallback(() => {
|
||||
handleApproveOrPermit().then((transaction) => {
|
||||
if (transaction) {
|
||||
addTransaction({ type: TransactionType.APPROVAL, ...transaction })
|
||||
}
|
||||
})
|
||||
}, [addTransaction, getApproval])
|
||||
}, [addTransaction, handleApproveOrPermit])
|
||||
|
||||
const { type: wrapType, callback: wrapCallback, error: wrapError, loading: wrapLoading } = useWrapCallback()
|
||||
|
||||
const disableSwap = useMemo(
|
||||
() =>
|
||||
disabled ||
|
||||
!chainId ||
|
||||
wrapLoading ||
|
||||
(wrapType !== WrapType.NOT_APPLICABLE && wrapError) ||
|
||||
approvalState === ApproveOrPermitState.PENDING_SIGNATURE ||
|
||||
!(inputTradeCurrencyAmount && inputCurrencyBalance) ||
|
||||
inputCurrencyBalance.lessThan(inputTradeCurrencyAmount),
|
||||
[disabled, chainId, wrapLoading, wrapType, wrapError, approvalState, inputTradeCurrencyAmount, inputCurrencyBalance]
|
||||
)
|
||||
|
||||
const actionProps = useMemo((): Partial<ActionButtonProps> | undefined => {
|
||||
if (disabled || wrapLoading) return { disabled: true }
|
||||
if (!disabled && chainId) {
|
||||
const hasSufficientInputForTrade =
|
||||
inputTradeCurrencyAmount && inputCurrencyBalance && !inputCurrencyBalance.lessThan(inputTradeCurrencyAmount)
|
||||
if (approval === ApprovalState.NOT_APPROVED) {
|
||||
const currency = inputCurrency || approvalCurrencyAmount?.currency
|
||||
invariant(currency)
|
||||
return {
|
||||
action: {
|
||||
message: <Trans>Approve {currency.symbol} first</Trans>,
|
||||
onClick: addApprovalTransaction,
|
||||
children: <Trans>Approve</Trans>,
|
||||
},
|
||||
}
|
||||
} else if (approval === ApprovalState.PENDING) {
|
||||
return {
|
||||
disabled: true,
|
||||
action: {
|
||||
message: (
|
||||
<EtherscanLink type={ExplorerDataType.TRANSACTION} data={approvalHash}>
|
||||
<Trans>Approval pending</Trans>
|
||||
</EtherscanLink>
|
||||
if (disableSwap) {
|
||||
return { disabled: true }
|
||||
}
|
||||
if (
|
||||
wrapType === WrapType.NOT_APPLICABLE &&
|
||||
(approvalState === ApproveOrPermitState.REQUIRES_APPROVAL ||
|
||||
approvalState === ApproveOrPermitState.REQUIRES_SIGNATURE)
|
||||
) {
|
||||
const currency = inputCurrency || approvalCurrencyAmount?.currency
|
||||
invariant(currency)
|
||||
return {
|
||||
action: {
|
||||
message:
|
||||
approvalState === ApproveOrPermitState.REQUIRES_SIGNATURE ? (
|
||||
<Trans>Allow {currency.symbol} first</Trans>
|
||||
) : (
|
||||
<Trans>Approve {currency.symbol} first</Trans>
|
||||
),
|
||||
icon: Spinner,
|
||||
onClick: addApprovalTransaction,
|
||||
children: <Trans>Approve</Trans>,
|
||||
},
|
||||
}
|
||||
} else if (hasSufficientInputForTrade || (wrapType !== WrapType.NOT_APPLICABLE && !wrapError)) {
|
||||
return {}
|
||||
onClick: onApprove,
|
||||
children:
|
||||
approvalState === ApproveOrPermitState.REQUIRES_SIGNATURE ? <Trans>Allow</Trans> : <Trans>Approve</Trans>,
|
||||
},
|
||||
}
|
||||
}
|
||||
return { disabled: true }
|
||||
}, [
|
||||
addApprovalTransaction,
|
||||
approval,
|
||||
approvalCurrencyAmount?.currency,
|
||||
approvalHash,
|
||||
chainId,
|
||||
disabled,
|
||||
inputCurrency,
|
||||
inputCurrencyBalance,
|
||||
inputTradeCurrencyAmount,
|
||||
wrapError,
|
||||
wrapLoading,
|
||||
wrapType,
|
||||
])
|
||||
if (approvalState === ApproveOrPermitState.PENDING_APPROVAL) {
|
||||
return {
|
||||
disabled: true,
|
||||
action: {
|
||||
message: (
|
||||
<EtherscanLink type={ExplorerDataType.TRANSACTION} data={approvalHash}>
|
||||
<Trans>Approval pending</Trans>
|
||||
</EtherscanLink>
|
||||
),
|
||||
icon: Spinner,
|
||||
onClick: () => void 0, // @TODO: should not require an onclick
|
||||
children: <Trans>Approve</Trans>,
|
||||
},
|
||||
}
|
||||
}
|
||||
return {}
|
||||
}, [approvalCurrencyAmount?.currency, approvalHash, approvalState, disableSwap, inputCurrency, onApprove, wrapType])
|
||||
|
||||
const deadline = useTransactionDeadline()
|
||||
const { signatureData } = useERC20PermitFromTrade(optimizedTrade, allowedSlippage, deadline)
|
||||
|
||||
// the callback to execute the swap
|
||||
const { callback: swapCallback } = useSwapCallback({
|
||||
|
@ -4,7 +4,9 @@ import { Pair, Route as V2Route, Trade as V2Trade } from '@uniswap/v2-sdk'
|
||||
import { Pool, Route as V3Route, Trade as V3Trade } from '@uniswap/v3-sdk'
|
||||
import { SWAP_ROUTER_ADDRESSES, V2_ROUTER_ADDRESS, V3_ROUTER_ADDRESS } from 'constants/addresses'
|
||||
import useActiveWeb3React from 'hooks/useActiveWeb3React'
|
||||
import { useMemo } from 'react'
|
||||
import { useERC20PermitFromTrade, UseERC20PermitState } from 'hooks/useERC20Permit'
|
||||
import useTransactionDeadline from 'lib/hooks/useTransactionDeadline'
|
||||
import { useCallback, useMemo } from 'react'
|
||||
import invariant from 'tiny-invariant'
|
||||
import { getTxOptimizedSwapRouter, SwapRouterVersion } from 'utils/getTxOptimizedSwapRouter'
|
||||
|
||||
@ -134,3 +136,78 @@ export function useSwapApprovalOptimizedTrade(
|
||||
}
|
||||
}, [trade, optimizedSwapRouter])
|
||||
}
|
||||
|
||||
export enum ApproveOrPermitState {
|
||||
REQUIRES_APPROVAL,
|
||||
PENDING_APPROVAL,
|
||||
REQUIRES_SIGNATURE,
|
||||
PENDING_SIGNATURE,
|
||||
APPROVED,
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all relevant statuses and callback functions for approvals.
|
||||
* Considers both standard approval and ERC20 permit.
|
||||
*/
|
||||
export const useApproveOrPermit = (
|
||||
trade:
|
||||
| V2Trade<Currency, Currency, TradeType>
|
||||
| V3Trade<Currency, Currency, TradeType>
|
||||
| Trade<Currency, Currency, TradeType>
|
||||
| undefined,
|
||||
allowedSlippage: Percent,
|
||||
useIsPendingApproval: (token?: Token, spender?: string) => boolean,
|
||||
amount?: CurrencyAmount<Currency> // defaults to trade.maximumAmountIn(allowedSlippage)
|
||||
) => {
|
||||
const deadline = useTransactionDeadline()
|
||||
|
||||
// Check approvals on ERC20 contract based on amount.
|
||||
const [approval, getApproval] = useSwapApproval(trade, allowedSlippage, useIsPendingApproval, amount)
|
||||
|
||||
// Check status of permit and whether token supports it.
|
||||
const {
|
||||
state: signatureState,
|
||||
signatureData,
|
||||
gatherPermitSignature,
|
||||
} = useERC20PermitFromTrade(trade, allowedSlippage, deadline)
|
||||
|
||||
const notApproved = approval === ApprovalState.NOT_APPROVED && !(signatureState === UseERC20PermitState.SIGNED)
|
||||
|
||||
// If permit is supported, trigger a signature, if not create approval transaction.
|
||||
const handleApproveOrPermit = useCallback(async () => {
|
||||
if (signatureState === UseERC20PermitState.NOT_SIGNED && gatherPermitSignature) {
|
||||
try {
|
||||
return await gatherPermitSignature()
|
||||
} catch (error) {
|
||||
// Try to approve if gatherPermitSignature failed for any reason other than the user rejecting it.
|
||||
if (error?.code !== 4001) {
|
||||
return getApproval()
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return getApproval()
|
||||
}
|
||||
}, [signatureState, gatherPermitSignature, getApproval])
|
||||
|
||||
const approvalState = useMemo(() => {
|
||||
if (approval === ApprovalState.PENDING) {
|
||||
return ApproveOrPermitState.PENDING_APPROVAL
|
||||
}
|
||||
if (signatureState === UseERC20PermitState.LOADING) {
|
||||
return ApproveOrPermitState.PENDING_SIGNATURE
|
||||
}
|
||||
if (notApproved && Boolean(gatherPermitSignature)) {
|
||||
return ApproveOrPermitState.REQUIRES_SIGNATURE
|
||||
}
|
||||
if (notApproved) {
|
||||
return ApproveOrPermitState.REQUIRES_APPROVAL
|
||||
}
|
||||
return ApproveOrPermitState.APPROVED
|
||||
}, [approval, gatherPermitSignature, notApproved, signatureState])
|
||||
|
||||
return {
|
||||
approvalState,
|
||||
signatureData,
|
||||
handleApproveOrPermit,
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user