fix: update permit2 to match widget implementation (#5821)

* refactor: usePermit2->usePermit2Allowance

* fix: update permit2 logic to match widgets

* fix: lint issues
This commit is contained in:
Zach Pomerantz 2023-01-12 15:16:07 -08:00 committed by GitHub
parent f312a148d0
commit ef3407f299
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 263 additions and 232 deletions

41
src/abis/permit2.json Normal file

@ -0,0 +1,41 @@
[
{
"inputs": [
{
"internalType": "address",
"name": "",
"type": "address"
},
{
"internalType": "address",
"name": "",
"type": "address"
},
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"name": "allowance",
"outputs": [
{
"internalType": "uint160",
"name": "amount",
"type": "uint160"
},
{
"internalType": "uint48",
"name": "expiration",
"type": "uint48"
},
{
"internalType": "uint48",
"name": "nonce",
"type": "uint48"
}
],
"stateMutability": "view",
"type": "function"
}
]

@ -1,140 +0,0 @@
import { ContractTransaction } from '@ethersproject/contracts'
import { PERMIT2_ADDRESS } from '@uniswap/permit2-sdk'
import { CurrencyAmount, Token } from '@uniswap/sdk-core'
import { useWeb3React } from '@web3-react/core'
import { AVERAGE_L1_BLOCK_TIME } from 'constants/chainInfo'
import useInterval from 'lib/hooks/useInterval'
import { useCallback, useEffect, useMemo, useState } from 'react'
import { useHasPendingApproval } from 'state/transactions/hooks'
import { ApproveTransactionInfo } from 'state/transactions/types'
import { PermitSignature, usePermitAllowance, useUpdatePermitAllowance } from './usePermitAllowance'
import { useTokenAllowance, useUpdateTokenAllowance } from './useTokenAllowance'
enum SyncState {
PENDING,
SYNCING,
SYNCED,
}
export enum PermitState {
INVALID,
LOADING,
APPROVAL_OR_PERMIT_NEEDED,
APPROVAL_LOADING,
APPROVED_AND_PERMITTED,
}
export interface Permit {
state: PermitState
signature?: PermitSignature
callback?: () => Promise<{
response: ContractTransaction
info: ApproveTransactionInfo
} | void>
}
export default function usePermit(amount?: CurrencyAmount<Token>, spender?: string): Permit {
const { account } = useWeb3React()
const { tokenAllowance, isSyncing: isApprovalSyncing } = useTokenAllowance(amount?.currency, account, PERMIT2_ADDRESS)
const updateTokenAllowance = useUpdateTokenAllowance(amount, PERMIT2_ADDRESS)
const isAllowed = useMemo(
() => amount && (tokenAllowance?.greaterThan(amount) || tokenAllowance?.equalTo(amount)),
[amount, tokenAllowance]
)
const permitAllowance = usePermitAllowance(amount?.currency, spender)
const [permitAllowanceAmount, setPermitAllowanceAmount] = useState(permitAllowance?.amount)
useEffect(() => setPermitAllowanceAmount(permitAllowance?.amount), [permitAllowance?.amount])
const isPermitted = useMemo(
() => amount && permitAllowanceAmount?.gte(amount.quotient.toString()),
[amount, permitAllowanceAmount]
)
const [signature, setSignature] = useState<PermitSignature>()
const updatePermitAllowance = useUpdatePermitAllowance(
amount?.currency,
spender,
permitAllowance?.nonce,
setSignature
)
const isSigned = useMemo(
() => amount && signature?.details.token === amount?.currency.address && signature?.spender === spender,
[amount, signature?.details.token, signature?.spender, spender]
)
// Trigger a re-render if either tokenAllowance or signature expire.
useInterval(
() => {
// Calculate now such that the signature will still be valid for the next block.
const now = (Date.now() - AVERAGE_L1_BLOCK_TIME) / 1000
if (signature && signature.sigDeadline < now) {
setSignature(undefined)
}
if (permitAllowance && permitAllowance.expiration < now) {
setPermitAllowanceAmount(undefined)
}
},
AVERAGE_L1_BLOCK_TIME,
true
)
// Permit2 should be marked syncing from the time approval is submitted (pending) until it is
// synced in tokenAllowance, to avoid re-prompting the user for an already-submitted approval.
const [syncState, setSyncState] = useState(SyncState.SYNCED)
const isApprovalLoading = syncState !== SyncState.SYNCED
const hasPendingApproval = useHasPendingApproval(amount?.currency, PERMIT2_ADDRESS)
useEffect(() => {
if (hasPendingApproval) {
setSyncState(SyncState.PENDING)
} else {
setSyncState((state) => {
if (state === SyncState.PENDING && isApprovalSyncing) {
return SyncState.SYNCING
} else if (state === SyncState.SYNCING && !isApprovalSyncing) {
return SyncState.SYNCED
} else {
return state
}
})
}
}, [hasPendingApproval, isApprovalSyncing])
const callback = useCallback(async () => {
let info
if (!isAllowed && !hasPendingApproval) {
info = await updateTokenAllowance()
}
if (!isPermitted && !isSigned) {
await updatePermitAllowance()
}
return info
}, [hasPendingApproval, isAllowed, isPermitted, isSigned, updatePermitAllowance, updateTokenAllowance])
return useMemo(() => {
if (!amount) {
return { state: PermitState.INVALID }
} else if (!tokenAllowance || !permitAllowance) {
return { state: PermitState.LOADING }
} else if (!(isPermitted || isSigned)) {
return { state: PermitState.APPROVAL_OR_PERMIT_NEEDED, callback }
} else if (!isAllowed) {
return {
state: isApprovalLoading ? PermitState.APPROVAL_LOADING : PermitState.APPROVAL_OR_PERMIT_NEEDED,
callback,
}
} else {
return { state: PermitState.APPROVED_AND_PERMITTED, signature: isPermitted ? undefined : signature }
}
}, [
amount,
callback,
isAllowed,
isApprovalLoading,
isPermitted,
isSigned,
permitAllowance,
signature,
tokenAllowance,
])
}

@ -0,0 +1,123 @@
import { PERMIT2_ADDRESS } from '@uniswap/permit2-sdk'
import { CurrencyAmount, Token } from '@uniswap/sdk-core'
import { useWeb3React } from '@web3-react/core'
import { AVERAGE_L1_BLOCK_TIME } from 'constants/chainInfo'
import { PermitSignature, usePermitAllowance, useUpdatePermitAllowance } from 'hooks/usePermitAllowance'
import { useTokenAllowance, useUpdateTokenAllowance } from 'hooks/useTokenAllowance'
import useInterval from 'lib/hooks/useInterval'
import { useCallback, useEffect, useMemo, useState } from 'react'
import { useHasPendingApproval, useTransactionAdder } from 'state/transactions/hooks'
enum ApprovalState {
PENDING,
SYNCING,
SYNCED,
}
export enum AllowanceState {
LOADING,
REQUIRED,
ALLOWED,
}
interface AllowanceRequired {
state: AllowanceState.REQUIRED
token: Token
isApprovalLoading: boolean
approveAndPermit: () => Promise<void>
}
type Allowance =
| { state: AllowanceState.LOADING }
| {
state: AllowanceState.ALLOWED
permitSignature?: PermitSignature
}
| AllowanceRequired
export default function usePermit2Allowance(amount?: CurrencyAmount<Token>, spender?: string): Allowance {
const { account } = useWeb3React()
const token = amount?.currency
const { tokenAllowance, isSyncing: isApprovalSyncing } = useTokenAllowance(token, account, PERMIT2_ADDRESS)
const updateTokenAllowance = useUpdateTokenAllowance(amount, PERMIT2_ADDRESS)
const isApproved = useMemo(() => {
if (!amount || !tokenAllowance) return false
return tokenAllowance.greaterThan(amount) || tokenAllowance.equalTo(amount)
}, [amount, tokenAllowance])
// Marks approval as loading from the time it is submitted (pending), until it has confirmed and another block synced.
// This avoids re-prompting the user for an already-submitted but not-yet-observed approval, by marking it loading
// until it has been re-observed. It wll sync immediately, because confirmation fast-forwards the block number.
const [approvalState, setApprovalState] = useState(ApprovalState.SYNCED)
const isApprovalLoading = approvalState !== ApprovalState.SYNCED
const isApprovalPending = useHasPendingApproval(token, PERMIT2_ADDRESS)
useEffect(() => {
if (isApprovalPending) {
setApprovalState(ApprovalState.PENDING)
} else {
setApprovalState((state) => {
if (state === ApprovalState.PENDING && isApprovalSyncing) {
return ApprovalState.SYNCING
} else if (state === ApprovalState.SYNCING && !isApprovalSyncing) {
return ApprovalState.SYNCED
}
return state
})
}
}, [isApprovalPending, isApprovalSyncing])
// Signature and PermitAllowance will expire, so they should be rechecked at an interval.
const [now, setNow] = useState(Date.now())
// Calculate now such that the signature will still be valid for the submitting block.
useInterval(() => setNow((Date.now() + AVERAGE_L1_BLOCK_TIME) / 1000), AVERAGE_L1_BLOCK_TIME, true)
const [signature, setSignature] = useState<PermitSignature>()
const isSigned = useMemo(() => {
if (!amount || !signature) return false
return signature.details.token === token?.address && signature.spender === spender && signature.sigDeadline >= now
}, [amount, now, signature, spender, token?.address])
const { permitAllowance, expiration: permitExpiration, nonce } = usePermitAllowance(token, account, spender)
const updatePermitAllowance = useUpdatePermitAllowance(token, spender, nonce, setSignature)
const isPermitted = useMemo(() => {
if (!amount || !permitAllowance || !permitExpiration) return false
return (permitAllowance.greaterThan(amount) || permitAllowance.equalTo(amount)) && permitExpiration >= now
}, [amount, now, permitAllowance, permitExpiration])
const shouldRequestApproval = !(isApproved || isApprovalLoading)
const shouldRequestSignature = !(isPermitted || isSigned)
const addTransaction = useTransactionAdder()
const approveAndPermit = useCallback(async () => {
if (shouldRequestApproval) {
const { response, info } = await updateTokenAllowance()
addTransaction(response, info)
}
if (shouldRequestSignature) {
await updatePermitAllowance()
}
}, [addTransaction, shouldRequestApproval, shouldRequestSignature, updatePermitAllowance, updateTokenAllowance])
return useMemo(() => {
if (token) {
if (!tokenAllowance || !permitAllowance) {
return { state: AllowanceState.LOADING }
} else if (!(isPermitted || isSigned)) {
return { token, state: AllowanceState.REQUIRED, isApprovalLoading: false, approveAndPermit }
} else if (!isApproved) {
return { token, state: AllowanceState.REQUIRED, isApprovalLoading, approveAndPermit }
}
}
return { token, state: AllowanceState.ALLOWED, permitSignature: !isPermitted && isSigned ? signature : undefined }
}, [
approveAndPermit,
isApprovalLoading,
isApproved,
isPermitted,
isSigned,
permitAllowance,
signature,
token,
tokenAllowance,
])
}

@ -1,14 +1,10 @@
import {
AllowanceData,
AllowanceProvider,
AllowanceTransfer,
MaxAllowanceTransferAmount,
PERMIT2_ADDRESS,
PermitSingle,
} from '@uniswap/permit2-sdk'
import { Token } from '@uniswap/sdk-core'
import { AllowanceTransfer, MaxAllowanceTransferAmount, PERMIT2_ADDRESS, PermitSingle } from '@uniswap/permit2-sdk'
import { CurrencyAmount, Token } from '@uniswap/sdk-core'
import { useWeb3React } from '@web3-react/core'
import useBlockNumber from 'lib/hooks/useBlockNumber'
import PERMIT2_ABI from 'abis/permit2.json'
import { Permit2 } from 'abis/types'
import { useContract } from 'hooks/useContract'
import { useSingleCallResult } from 'lib/hooks/multicall'
import ms from 'ms.macro'
import { useCallback, useEffect, useMemo, useState } from 'react'
@ -19,35 +15,28 @@ function toDeadline(expiration: number): number {
return Math.floor((Date.now() + expiration) / 1000)
}
export function usePermitAllowance(token?: Token, spender?: string) {
const { account, provider } = useWeb3React()
const allowanceProvider = useMemo(() => provider && new AllowanceProvider(provider, PERMIT2_ADDRESS), [provider])
const [allowanceData, setAllowanceData] = useState<AllowanceData>()
export function usePermitAllowance(token?: Token, owner?: string, spender?: string) {
const contract = useContract<Permit2>(PERMIT2_ADDRESS, PERMIT2_ABI)
const inputs = useMemo(() => [owner, token?.address, spender], [owner, spender, token?.address])
// If there is no allowanceData, recheck every block so a submitted allowance is immediately observed.
const blockNumber = useBlockNumber()
const shouldUpdate = allowanceData ? false : blockNumber
// If there is no allowance yet, re-check next observed block.
// This guarantees that the permitAllowance is synced upon submission and updated upon being synced.
const [blocksPerFetch, setBlocksPerFetch] = useState<1>()
const result = useSingleCallResult(contract, 'allowance', inputs, {
blocksPerFetch,
}).result as Awaited<ReturnType<Permit2['allowance']>> | undefined
useEffect(() => {
if (!account || !token || !spender) return
const rawAmount = result?.amount.toString() // convert to a string before using in a hook, to avoid spurious rerenders
const allowance = useMemo(
() => (token && rawAmount ? CurrencyAmount.fromRawAmount(token, rawAmount) : undefined),
[token, rawAmount]
)
useEffect(() => setBlocksPerFetch(allowance?.equalTo(0) ? 1 : undefined), [allowance])
allowanceProvider
?.getAllowanceData(token.address, account, spender)
.then((data) => {
if (stale) return
setAllowanceData(data)
})
.catch((e) => {
console.warn(`Failed to fetch allowance data: ${e}`)
})
let stale = false
return () => {
stale = true
}
}, [account, allowanceProvider, shouldUpdate, spender, token])
return allowanceData
return useMemo(
() => ({ permitAllowance: allowance, expiration: result?.expiration, nonce: result?.nonce }),
[allowance, result?.expiration, result?.nonce]
)
}
interface Permit extends PermitSingle {
@ -91,7 +80,7 @@ export function useUpdatePermitAllowance(
return
} catch (e: unknown) {
const symbol = token?.symbol ?? 'Token'
throw new Error(`${symbol} permit failed: ${e instanceof Error ? e.message : e}`)
throw new Error(`${symbol} permit allowance failed: ${e instanceof Error ? e.message : e}`)
}
}, [account, chainId, nonce, onPermitSignature, provider, spender, token])
}

@ -2,6 +2,7 @@ import { Trade } from '@uniswap/router-sdk'
import { Currency, Percent, TradeType } from '@uniswap/sdk-core'
import { useWeb3React } from '@web3-react/core'
import { usePermit2Enabled } from 'featureFlags/flags/permit2'
import { PermitSignature } from 'hooks/usePermitAllowance'
import { SwapCallbackState, useSwapCallback as useLibSwapCallBack } from 'lib/hooks/swap/useSwapCallback'
import { ReactNode, useMemo } from 'react'
@ -10,7 +11,6 @@ import { TransactionType } from '../state/transactions/types'
import { currencyId } from '../utils/currencyId'
import useENS from './useENS'
import { SignatureData } from './useERC20Permit'
import { Permit } from './usePermit2'
import useTransactionDeadline from './useTransactionDeadline'
import { useUniversalRouterSwapCallback } from './useUniversalRouter'
@ -21,7 +21,7 @@ export function useSwapCallback(
allowedSlippage: Percent, // in bips
recipientAddressOrName: string | null, // the ENS name or address of the recipient of the trade, or null if swap should be returned to sender
signatureData: SignatureData | undefined | null,
permit: Permit | undefined
permitSignature: PermitSignature | undefined
): { state: SwapCallbackState; callback: null | (() => Promise<string>); error: ReactNode | null } {
const { account } = useWeb3React()
@ -47,7 +47,7 @@ export function useSwapCallback(
const universalRouterSwapCallback = useUniversalRouterSwapCallback(permit2Enabled ? trade : undefined, {
slippageTolerance: allowedSlippage,
deadline,
permit: permit?.signature,
permit: permitSignature,
})
const swapCallback = permit2Enabled ? universalRouterSwapCallback : libCallback

@ -1,13 +1,12 @@
import { BigNumberish } from '@ethersproject/bignumber'
import { ContractTransaction } from '@ethersproject/contracts'
import { CurrencyAmount, MaxUint256, Token } from '@uniswap/sdk-core'
import { useTokenContract } from 'hooks/useContract'
import { useSingleCallResult } from 'lib/hooks/multicall'
import { useCallback, useMemo } from 'react'
import { useCallback, useEffect, useMemo, useState } from 'react'
import { ApproveTransactionInfo, TransactionType } from 'state/transactions/types'
import { calculateGasMargin } from 'utils/calculateGasMargin'
import { useTokenContract } from './useContract'
export function useTokenAllowance(
token?: Token,
owner?: string,
@ -17,23 +16,33 @@ export function useTokenAllowance(
isSyncing: boolean
} {
const contract = useTokenContract(token?.address, false)
const inputs = useMemo(() => [owner, spender], [owner, spender])
const { result, syncing: isSyncing } = useSingleCallResult(contract, 'allowance', inputs)
return useMemo(() => {
const tokenAllowance = token && result && CurrencyAmount.fromRawAmount(token, result.toString())
return { tokenAllowance, isSyncing }
}, [isSyncing, result, token])
// If there is no allowance yet, re-check next observed block.
// This guarantees that the tokenAllowance is marked isSyncing upon approval and updated upon being synced.
const [blocksPerFetch, setBlocksPerFetch] = useState<1>()
const { result, syncing: isSyncing } = useSingleCallResult(contract, 'allowance', inputs, { blocksPerFetch }) as {
result: Awaited<ReturnType<NonNullable<typeof contract>['allowance']>> | undefined
syncing: boolean
}
const rawAmount = result?.toString() // convert to a string before using in a hook, to avoid spurious rerenders
const allowance = useMemo(
() => (token && rawAmount ? CurrencyAmount.fromRawAmount(token, rawAmount) : undefined),
[token, rawAmount]
)
useEffect(() => setBlocksPerFetch(allowance?.equalTo(0) ? 1 : undefined), [allowance])
return useMemo(() => ({ tokenAllowance: allowance, isSyncing }), [allowance, isSyncing])
}
export function useUpdateTokenAllowance(amount: CurrencyAmount<Token> | undefined, spender: string) {
export function useUpdateTokenAllowance(
amount: CurrencyAmount<Token> | undefined,
spender: string
): () => Promise<{ response: ContractTransaction; info: ApproveTransactionInfo }> {
const contract = useTokenContract(amount?.currency.address)
return useCallback(async (): Promise<{
response: ContractTransaction
info: ApproveTransactionInfo
}> => {
return useCallback(async () => {
try {
if (!amount) throw new Error('missing amount')
if (!contract) throw new Error('missing contract')
@ -58,7 +67,7 @@ export function useUpdateTokenAllowance(amount: CurrencyAmount<Token> | undefine
}
} catch (e: unknown) {
const symbol = amount?.currency.symbol ?? 'Token'
throw new Error(`${symbol} approval failed: ${e instanceof Error ? e.message : e}`)
throw new Error(`${symbol} token allowance failed: ${e instanceof Error ? e.message : e}`)
}
}, [amount, contract, spender])
}

@ -1,7 +1,7 @@
import { TransactionReceipt } from '@ethersproject/abstract-provider'
import { useWeb3React } from '@web3-react/core'
import { SupportedChainId } from 'constants/chains'
import useBlockNumber from 'lib/hooks/useBlockNumber'
import useBlockNumber, { useFastForwardBlockNumber } from 'lib/hooks/useBlockNumber'
import ms from 'ms.macro'
import { useCallback, useEffect } from 'react'
import { retry, RetryableError, RetryOptions } from 'utils/retry'
@ -48,6 +48,7 @@ export default function Updater({ pendingTransactions, onCheck, onReceipt }: Upd
const { chainId, provider } = useWeb3React()
const lastBlockNumber = useBlockNumber()
const fastForwardBlockNumber = useFastForwardBlockNumber()
const getReceipt = useCallback(
(hash: string) => {
@ -78,6 +79,7 @@ export default function Updater({ pendingTransactions, onCheck, onReceipt }: Upd
promise
.then((receipt) => {
if (receipt) {
fastForwardBlockNumber(receipt.blockNumber)
onReceipt({ chainId, hash, receipt })
} else {
onCheck({ chainId, hash, blockNumber: lastBlockNumber })
@ -94,7 +96,7 @@ export default function Updater({ pendingTransactions, onCheck, onReceipt }: Upd
return () => {
cancels.forEach((cancel) => cancel())
}
}, [chainId, provider, lastBlockNumber, getReceipt, onReceipt, onCheck, pendingTransactions])
}, [chainId, provider, lastBlockNumber, getReceipt, onReceipt, onCheck, pendingTransactions, fastForwardBlockNumber])
return null
}

@ -6,6 +6,7 @@ const MISSING_PROVIDER = Symbol()
const BlockNumberContext = createContext<
| {
value?: number
fastForward(block: number): void
}
| typeof MISSING_PROVIDER
>(MISSING_PROVIDER)
@ -23,6 +24,10 @@ export default function useBlockNumber(): number | undefined {
return useBlockNumberContext().value
}
export function useFastForwardBlockNumber(): (block: number) => void {
return useBlockNumberContext().fastForward
}
export function BlockNumberProvider({ children }: { children: ReactNode }) {
const { chainId: activeChainId, provider } = useWeb3React()
const [{ chainId, block }, setChainBlock] = useState<{ chainId?: number; block?: number }>({ chainId: activeChainId })
@ -68,7 +73,16 @@ export function BlockNumberProvider({ children }: { children: ReactNode }) {
return void 0
}, [activeChainId, provider, onBlock, setChainBlock, windowVisible])
const blockValue = useMemo(() => (chainId === activeChainId ? block : undefined), [activeChainId, block, chainId])
const value = useMemo(() => ({ value: blockValue }), [blockValue])
const value = useMemo(
() => ({
value: chainId === activeChainId ? block : undefined,
fastForward: (update: number) => {
if (block && update > block) {
setChainBlock({ chainId: activeChainId, block: update })
}
},
}),
[activeChainId, block, chainId]
)
return <BlockNumberContext.Provider value={value}>{children}</BlockNumberContext.Provider>
}

@ -21,7 +21,7 @@ import TokenSafetyModal from 'components/TokenSafety/TokenSafetyModal'
import { MouseoverTooltip } from 'components/Tooltip'
import { isSupportedChain } from 'constants/chains'
import { usePermit2Enabled } from 'featureFlags/flags/permit2'
import usePermit, { PermitState } from 'hooks/usePermit2'
import usePermit2Allowance, { AllowanceState } from 'hooks/usePermit2Allowance'
import { useSwapCallback } from 'hooks/useSwapCallback'
import useTransactionDeadline from 'hooks/useTransactionDeadline'
import JSBI from 'jsbi'
@ -34,8 +34,8 @@ import { Text } from 'rebass'
import { useToggleWalletModal } from 'state/application/hooks'
import { InterfaceTrade } from 'state/routing/types'
import { TradeState } from 'state/routing/types'
import { useTransactionAdder } from 'state/transactions/hooks'
import styled, { useTheme } from 'styled-components/macro'
import invariant from 'tiny-invariant'
import { currencyAmountToPreciseFloat, formatTransactionAmount } from 'utils/formatNumbers'
import AddressInputPanel from '../../components/AddressInputPanel'
@ -302,36 +302,31 @@ export default function Swap({ className }: { className?: string }) {
const maximumAmountIn = trade?.maximumAmountIn(allowedSlippage)
return maximumAmountIn?.currency.isToken ? (maximumAmountIn as CurrencyAmount<Token>) : undefined
}, [allowedSlippage, trade])
const permit = usePermit(
const allowance = usePermit2Allowance(
permit2Enabled ? maximumAmountIn : undefined,
permit2Enabled && chainId ? UNIVERSAL_ROUTER_ADDRESS(chainId) : undefined
)
const isApprovalLoading = permit.state === PermitState.APPROVAL_LOADING
const [isPermitPending, setIsPermitPending] = useState(false)
const [isPermitFailed, setIsPermitFailed] = useState(false)
const addTransaction = useTransactionAdder()
const updatePermit = useCallback(async () => {
setIsPermitPending(true)
const isApprovalLoading = allowance.state === AllowanceState.REQUIRED && allowance.isApprovalLoading
const [isAllowancePending, setIsAllowancePending] = useState(false)
const [isAllowanceFailed, setIsAllowanceFailed] = useState(false)
const updateAllowance = useCallback(async () => {
invariant(allowance.state === AllowanceState.REQUIRED)
setIsAllowancePending(true)
try {
const approval = await permit.callback?.()
if (approval) {
sendAnalyticsEvent(InterfaceEventName.APPROVE_TOKEN_TXN_SUBMITTED, {
chain_id: chainId,
token_symbol: maximumAmountIn?.currency.symbol,
token_address: maximumAmountIn?.currency.address,
})
const { response, info } = approval
addTransaction(response, info)
}
setIsPermitFailed(false)
await allowance.approveAndPermit()
sendAnalyticsEvent(InterfaceEventName.APPROVE_TOKEN_TXN_SUBMITTED, {
chain_id: chainId,
token_symbol: maximumAmountIn?.currency.symbol,
token_address: maximumAmountIn?.currency.address,
})
setIsAllowanceFailed(false)
} catch (e) {
console.error(e)
setIsPermitFailed(true)
setIsAllowanceFailed(true)
} finally {
setIsPermitPending(false)
setIsAllowancePending(false)
}
}, [addTransaction, chainId, maximumAmountIn?.currency.address, maximumAmountIn?.currency.symbol, permit])
}, [allowance, chainId, maximumAmountIn?.currency.address, maximumAmountIn?.currency.symbol])
// check whether the user has approved the router on the input token
const [approvalState, approveCallback] = useApproveCallbackFromTrade(
@ -394,7 +389,7 @@ export default function Swap({ className }: { className?: string }) {
allowedSlippage,
recipient,
signatureData,
permit
allowance.state === AllowanceState.ALLOWED ? allowance.permitSignature : undefined
)
const handleSwap = useCallback(() => {
@ -790,22 +785,20 @@ export default function Swap({ className }: { className?: string }) {
</ButtonError>
</AutoColumn>
</AutoRow>
) : isValid &&
(permit.state === PermitState.APPROVAL_OR_PERMIT_NEEDED ||
permit.state === PermitState.APPROVAL_LOADING) ? (
) : isValid && allowance.state === AllowanceState.REQUIRED ? (
<ButtonYellow
onClick={updatePermit}
disabled={isPermitPending || isApprovalLoading}
onClick={updateAllowance}
disabled={isAllowancePending || isApprovalLoading}
style={{ gap: 14 }}
>
{isPermitPending ? (
{isAllowancePending ? (
<>
<Loader size="20px" stroke={theme.accentWarning} />
<ThemedText.SubHeader color="accentWarning">
<Trans>Approve in your wallet</Trans>
</ThemedText.SubHeader>
</>
) : isPermitFailed ? (
) : isAllowanceFailed ? (
<>
<AlertTriangle size={20} stroke={theme.accentWarning} />
<ThemedText.SubHeader color="accentWarning">
@ -860,7 +853,7 @@ export default function Swap({ className }: { className?: string }) {
routeIsSyncing ||
routeIsLoading ||
priceImpactTooHigh ||
(permit2Enabled ? permit.state === PermitState.LOADING : Boolean(swapCallbackError))
(permit2Enabled ? allowance.state !== AllowanceState.ALLOWED : Boolean(swapCallbackError))
}
error={isValid && priceImpactSeverity > 2 && (permit2Enabled || !swapCallbackError)}
>