import { MaxUint256 } from '@ethersproject/constants' import { TransactionResponse } from '@ethersproject/providers' import { CurrencyAmount, Percent, Currency, TradeType } from '@uniswap/sdk-core' import { Trade as V2Trade } from '@uniswap/v2-sdk' import { Trade as V3Trade } from '@uniswap/v3-sdk' import { useCallback, useMemo } from 'react' import { SWAP_ROUTER_ADDRESSES, V2_ROUTER_ADDRESS } from '../constants/addresses' import { useTransactionAdder, useHasPendingApproval } from '../state/transactions/hooks' import { calculateGasMargin } from '../utils/calculateGasMargin' import { useTokenContract } from './useContract' import { useActiveWeb3React } from './web3' import { useTokenAllowance } from './useTokenAllowance' export enum ApprovalState { UNKNOWN = 'UNKNOWN', NOT_APPROVED = 'NOT_APPROVED', PENDING = 'PENDING', APPROVED = 'APPROVED', } // returns a variable indicating the state of the approval and a function which approves if necessary or early returns export function useApproveCallback( amountToApprove?: CurrencyAmount, spender?: string ): [ApprovalState, () => Promise] { const { account } = useActiveWeb3React() const token = amountToApprove?.currency?.isToken ? amountToApprove.currency : undefined const currentAllowance = useTokenAllowance(token, account ?? undefined, spender) const pendingApproval = useHasPendingApproval(token?.address, spender) // check the current approval status const approvalState: ApprovalState = useMemo(() => { if (!amountToApprove || !spender) return ApprovalState.UNKNOWN if (amountToApprove.currency.isNative) return ApprovalState.APPROVED // we might not have enough data to know whether or not we need to approve if (!currentAllowance) return ApprovalState.UNKNOWN // amountToApprove will be defined if currentAllowance is return currentAllowance.lessThan(amountToApprove) ? pendingApproval ? ApprovalState.PENDING : ApprovalState.NOT_APPROVED : ApprovalState.APPROVED }, [amountToApprove, currentAllowance, pendingApproval, spender]) const tokenContract = useTokenContract(token?.address) const addTransaction = useTransactionAdder() const approve = useCallback(async (): Promise => { if (approvalState !== ApprovalState.NOT_APPROVED) { console.error('approve was called unnecessarily') return } if (!token) { console.error('no token') return } if (!tokenContract) { console.error('tokenContract is null') return } if (!amountToApprove) { console.error('missing amount to approve') return } if (!spender) { console.error('no spender') return } let useExact = false const estimatedGas = await tokenContract.estimateGas.approve(spender, MaxUint256).catch(() => { // general fallback for tokens who restrict approval amounts useExact = true return tokenContract.estimateGas.approve(spender, amountToApprove.quotient.toString()) }) return tokenContract .approve(spender, useExact ? amountToApprove.quotient.toString() : MaxUint256, { gasLimit: calculateGasMargin(estimatedGas), }) .then((response: TransactionResponse) => { addTransaction(response, { summary: 'Approve ' + amountToApprove.currency.symbol, approval: { tokenAddress: token.address, spender: spender }, }) }) .catch((error: Error) => { console.debug('Failed to approve token', error) throw error }) }, [approvalState, token, tokenContract, amountToApprove, spender, addTransaction]) return [approvalState, approve] } // wraps useApproveCallback in the context of a swap export function useApproveCallbackFromTrade( trade: V2Trade | V3Trade | undefined, allowedSlippage: Percent ) { const { chainId } = useActiveWeb3React() const v3SwapRouterAddress = chainId ? SWAP_ROUTER_ADDRESSES[chainId] : undefined const amountToApprove = useMemo( () => (trade && trade.inputAmount.currency.isToken ? trade.maximumAmountIn(allowedSlippage) : undefined), [trade, allowedSlippage] ) return useApproveCallback( amountToApprove, chainId ? trade instanceof V2Trade ? V2_ROUTER_ADDRESS[chainId] : trade instanceof V3Trade ? v3SwapRouterAddress : undefined : undefined ) }