Commit 585d67c4 authored by Vignesh Mohankumar's avatar Vignesh Mohankumar Committed by GitHub

refactor: always enable permit2 (#6068)

* refactor: always enable permit2

* rm from modal

* one more

* rm

* more

* more

* more

* rm useswapcallback

* rm useapprovecallback

* unused hooks

* unused hooks

* rm

* lint
parent 521b9b8e
...@@ -2,7 +2,6 @@ import { BaseVariant, FeatureFlag, featureFlagSettings, useUpdateFlag } from 'fe ...@@ -2,7 +2,6 @@ import { BaseVariant, FeatureFlag, featureFlagSettings, useUpdateFlag } from 'fe
import { GqlRoutingVariant, useGqlRoutingFlag } from 'featureFlags/flags/gqlRouting' import { GqlRoutingVariant, useGqlRoutingFlag } from 'featureFlags/flags/gqlRouting'
import { NftGraphqlVariant, useNftGraphqlFlag } from 'featureFlags/flags/nftlGraphql' import { NftGraphqlVariant, useNftGraphqlFlag } from 'featureFlags/flags/nftlGraphql'
import { PayWithAnyTokenVariant, usePayWithAnyTokenFlag } from 'featureFlags/flags/payWithAnyToken' import { PayWithAnyTokenVariant, usePayWithAnyTokenFlag } from 'featureFlags/flags/payWithAnyToken'
import { Permit2Variant, usePermit2Flag } from 'featureFlags/flags/permit2'
import { SwapWidgetVariant, useSwapWidgetFlag } from 'featureFlags/flags/swapWidget' import { SwapWidgetVariant, useSwapWidgetFlag } from 'featureFlags/flags/swapWidget'
import { TraceJsonRpcVariant, useTraceJsonRpcFlag } from 'featureFlags/flags/traceJsonRpc' import { TraceJsonRpcVariant, useTraceJsonRpcFlag } from 'featureFlags/flags/traceJsonRpc'
import { useAtomValue, useUpdateAtom } from 'jotai/utils' import { useAtomValue, useUpdateAtom } from 'jotai/utils'
...@@ -206,12 +205,6 @@ export default function FeatureFlagModal() { ...@@ -206,12 +205,6 @@ export default function FeatureFlagModal() {
<X size={24} /> <X size={24} />
</CloseButton> </CloseButton>
</Header> </Header>
<FeatureFlagOption
variant={Permit2Variant}
value={usePermit2Flag()}
featureFlag={FeatureFlag.permit2}
label="Permit 2 / Universal Router"
/>
<FeatureFlagOption <FeatureFlagOption
variant={PayWithAnyTokenVariant} variant={PayWithAnyTokenVariant}
value={usePayWithAnyTokenFlag()} value={usePayWithAnyTokenFlag()}
......
...@@ -16,7 +16,6 @@ import { ...@@ -16,7 +16,6 @@ import {
SwapWidgetSkeleton, SwapWidgetSkeleton,
} from '@uniswap/widgets' } from '@uniswap/widgets'
import { useWeb3React } from '@web3-react/core' import { useWeb3React } from '@web3-react/core'
import { usePermit2Enabled } from 'featureFlags/flags/permit2'
import { useActiveLocale } from 'hooks/useActiveLocale' import { useActiveLocale } from 'hooks/useActiveLocale'
import { import {
formatPercentInBasisPointsNumber, formatPercentInBasisPointsNumber,
...@@ -154,8 +153,6 @@ export default function Widget({ ...@@ -154,8 +153,6 @@ export default function Widget({
[initialQuoteDate, trace] [initialQuoteDate, trace]
) )
const permit2Enabled = usePermit2Enabled()
if (!(inputs.value.INPUT || inputs.value.OUTPUT)) { if (!(inputs.value.INPUT || inputs.value.OUTPUT)) {
return <WidgetSkeleton /> return <WidgetSkeleton />
} }
...@@ -166,7 +163,7 @@ export default function Widget({ ...@@ -166,7 +163,7 @@ export default function Widget({
<SwapWidget <SwapWidget
hideConnectionUI hideConnectionUI
brandedFooter={false} brandedFooter={false}
permit2={permit2Enabled} permit2
routerUrl={WIDGET_ROUTER_URL} routerUrl={WIDGET_ROUTER_URL}
locale={locale} locale={locale}
theme={theme} theme={theme}
......
...@@ -15,7 +15,6 @@ export const V2_ROUTER_ADDRESS: AddressMap = constructSameAddressMap('0x7a250d56 ...@@ -15,7 +15,6 @@ export const V2_ROUTER_ADDRESS: AddressMap = constructSameAddressMap('0x7a250d56
// celo v3 addresses // celo v3 addresses
const CELO_V3_CORE_FACTORY_ADDRESSES = '0xAfE208a311B21f13EF87E33A90049fC17A7acDEc' const CELO_V3_CORE_FACTORY_ADDRESSES = '0xAfE208a311B21f13EF87E33A90049fC17A7acDEc'
const CELO_ROUTER_ADDRESS = '0x5615CDAb10dc425a742d643d949a7F474C01abc4'
const CELO_V3_MIGRATOR_ADDRESSES = '0x3cFd4d48EDfDCC53D3f173F596f621064614C582' const CELO_V3_MIGRATOR_ADDRESSES = '0x3cFd4d48EDfDCC53D3f173F596f621064614C582'
const CELO_MULTICALL_ADDRESS = '0x633987602DE5C4F337e3DbF265303A1080324204' const CELO_MULTICALL_ADDRESS = '0x633987602DE5C4F337e3DbF265303A1080324204'
const CELO_QUOTER_ADDRESSES = '0x82825d0554fA07f7FC52Ab63c961F330fdEFa8E8' const CELO_QUOTER_ADDRESSES = '0x82825d0554fA07f7FC52Ab63c961F330fdEFa8E8'
...@@ -24,7 +23,6 @@ const CELO_TICK_LENS_ADDRESSES = '0x5f115D9113F88e0a0Db1b5033D90D4a9690AcD3D' ...@@ -24,7 +23,6 @@ const CELO_TICK_LENS_ADDRESSES = '0x5f115D9113F88e0a0Db1b5033D90D4a9690AcD3D'
// arbitrum goerli v3 addresses // arbitrum goerli v3 addresses
const ARBITRUM_GOERLI_V3_CORE_FACTORY_ADDRESSES = '0x4893376342d5D7b3e31d4184c08b265e5aB2A3f6' const ARBITRUM_GOERLI_V3_CORE_FACTORY_ADDRESSES = '0x4893376342d5D7b3e31d4184c08b265e5aB2A3f6'
const ARBITRUM_GOERLI_ROUTER_ADDRESS = '0xab7664500b19a7a2362Ab26081e6DfB971B6F1B0'
const ARBITRUM_GOERLI_V3_MIGRATOR_ADDRESSES = '0xA815919D2584Ac3F76ea9CB62E6Fd40a43BCe0C3' const ARBITRUM_GOERLI_V3_MIGRATOR_ADDRESSES = '0xA815919D2584Ac3F76ea9CB62E6Fd40a43BCe0C3'
const ARBITRUM_GOERLI_MULTICALL_ADDRESS = '0x8260CB40247290317a4c062F3542622367F206Ee' const ARBITRUM_GOERLI_MULTICALL_ADDRESS = '0x8260CB40247290317a4c062F3542622367F206Ee'
const ARBITRUM_GOERLI_QUOTER_ADDRESSES = '0x1dd92b83591781D0C6d98d07391eea4b9a6008FA' const ARBITRUM_GOERLI_QUOTER_ADDRESSES = '0x1dd92b83591781D0C6d98d07391eea4b9a6008FA'
...@@ -69,19 +67,6 @@ export const MULTICALL_ADDRESS: AddressMap = { ...@@ -69,19 +67,6 @@ export const MULTICALL_ADDRESS: AddressMap = {
[SupportedChainId.ARBITRUM_GOERLI]: ARBITRUM_GOERLI_MULTICALL_ADDRESS, [SupportedChainId.ARBITRUM_GOERLI]: ARBITRUM_GOERLI_MULTICALL_ADDRESS,
} }
export const SWAP_ROUTER_ADDRESSES: AddressMap = {
...constructSameAddressMap('0x68b3465833fb72A70ecDF485E0e4C7bD8665Fc45', [
SupportedChainId.OPTIMISM,
SupportedChainId.OPTIMISM_GOERLI,
SupportedChainId.ARBITRUM_ONE,
SupportedChainId.POLYGON,
SupportedChainId.POLYGON_MUMBAI,
]),
[SupportedChainId.CELO]: CELO_ROUTER_ADDRESS,
[SupportedChainId.CELO_ALFAJORES]: CELO_ROUTER_ADDRESS,
[SupportedChainId.ARBITRUM_GOERLI]: ARBITRUM_GOERLI_ROUTER_ADDRESS,
}
/** /**
* The oldest V0 governance address * The oldest V0 governance address
*/ */
......
import { UNIVERSAL_ROUTER_ADDRESS } from '@uniswap/universal-router-sdk'
import { useWeb3React } from '@web3-react/core'
import { BaseVariant, FeatureFlag, useBaseFlag } from '../index'
export function usePermit2Flag(): BaseVariant {
return useBaseFlag(FeatureFlag.permit2, BaseVariant.Enabled)
}
export function usePermit2Enabled(): boolean {
const flagEnabled = usePermit2Flag() === 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 Permit2Variant }
import { Trade } from '@uniswap/router-sdk' import { Currency, CurrencyAmount } from '@uniswap/sdk-core'
import { Currency, CurrencyAmount, Percent, TradeType } from '@uniswap/sdk-core'
import useSwapApproval from 'lib/hooks/swap/useSwapApproval'
import { ApprovalState, useApproval } from 'lib/hooks/useApproval' import { ApprovalState, useApproval } from 'lib/hooks/useApproval'
import { useCallback } from 'react' import { useCallback } from 'react'
...@@ -28,11 +26,3 @@ export function useApproveCallback( ...@@ -28,11 +26,3 @@ export function useApproveCallback(
const [approval, getApproval] = useApproval(amountToApprove, spender, useHasPendingApproval) const [approval, getApproval] = useApproval(amountToApprove, spender, useHasPendingApproval)
return [approval, useGetAndTrackApproval(getApproval)] return [approval, useGetAndTrackApproval(getApproval)]
} }
export function useApproveCallbackFromTrade(
trade: Trade<Currency, Currency, TradeType> | undefined,
allowedSlippage: Percent
): [ApprovalState, () => Promise<void>] {
const [approval, getApproval] = useSwapApproval(trade, allowedSlippage, useHasPendingApproval)
return [approval, useGetAndTrackApproval(getApproval)]
}
import { BigNumber } from '@ethersproject/bignumber' import { BigNumber } from '@ethersproject/bignumber'
import { splitSignature } from '@ethersproject/bytes' import { splitSignature } from '@ethersproject/bytes'
import { Trade } from '@uniswap/router-sdk' import { Currency, CurrencyAmount } from '@uniswap/sdk-core'
import { Currency, CurrencyAmount, Percent, TradeType } from '@uniswap/sdk-core'
import { useWeb3React } from '@web3-react/core' import { useWeb3React } from '@web3-react/core'
import { SupportedChainId } from 'constants/chains' import { SupportedChainId } from 'constants/chains'
import JSBI from 'jsbi' import JSBI from 'jsbi'
import { useSingleCallResult } from 'lib/hooks/multicall' import { useSingleCallResult } from 'lib/hooks/multicall'
import { useMemo, useState } from 'react' import { useMemo, useState } from 'react'
import { SWAP_ROUTER_ADDRESSES } from '../constants/addresses'
import { DAI, UNI, USDC_MAINNET } from '../constants/tokens' import { DAI, UNI, USDC_MAINNET } from '../constants/tokens'
import { useEIP2612Contract } from './useContract' import { useEIP2612Contract } from './useContract'
import useIsArgentWallet from './useIsArgentWallet' import useIsArgentWallet from './useIsArgentWallet'
...@@ -44,7 +42,7 @@ const PERMITTABLE_TOKENS: { ...@@ -44,7 +42,7 @@ const PERMITTABLE_TOKENS: {
}, },
} }
export enum UseERC20PermitState { enum UseERC20PermitState {
// returned for any reason, e.g. it is an argent wallet, or the currency does not support it // returned for any reason, e.g. it is an argent wallet, or the currency does not support it
NOT_APPLICABLE, NOT_APPLICABLE,
LOADING, LOADING,
...@@ -73,7 +71,7 @@ interface AllowedSignatureData extends BaseSignatureData { ...@@ -73,7 +71,7 @@ interface AllowedSignatureData extends BaseSignatureData {
allowed: true allowed: true
} }
export type SignatureData = StandardSignatureData | AllowedSignatureData type SignatureData = StandardSignatureData | AllowedSignatureData
const EIP712_DOMAIN_TYPE = [ const EIP712_DOMAIN_TYPE = [
{ name: 'name', type: 'string' }, { name: 'name', type: 'string' },
...@@ -247,18 +245,3 @@ export function useERC20Permit( ...@@ -247,18 +245,3 @@ export function useERC20Permit(
signatureData, signatureData,
]) ])
} }
export function useERC20PermitFromTrade(
trade: Trade<Currency, Currency, TradeType> | undefined,
allowedSlippage: Percent,
transactionDeadline: BigNumber | undefined
) {
const { chainId } = useWeb3React()
const swapRouterAddress = chainId ? SWAP_ROUTER_ADDRESSES[chainId] : undefined
const amountToApprove = useMemo(
() => (trade ? trade.maximumAmountIn(allowedSlippage) : undefined),
[trade, allowedSlippage]
)
return useERC20Permit(amountToApprove, swapRouterAddress, transactionDeadline, null)
}
import { BigNumber } from '@ethersproject/bignumber'
import { SwapRouter, Trade } from '@uniswap/router-sdk'
import { Currency, Percent, TradeType } from '@uniswap/sdk-core'
import { FeeOptions } from '@uniswap/v3-sdk'
import { useWeb3React } from '@web3-react/core'
import { SWAP_ROUTER_ADDRESSES } from 'constants/addresses'
import { useMemo } from 'react'
import approveAmountCalldata from 'utils/approveAmountCalldata'
import { useArgentWalletContract } from './useArgentWalletContract'
import useENS from './useENS'
import { SignatureData } from './useERC20Permit'
interface SwapCall {
address: string
calldata: string
value: string
}
/**
* Returns the swap calls that can be used to make the trade
* @param trade trade to execute
* @param allowedSlippage user allowed slippage
* @param recipientAddressOrName the ENS name or address of the recipient of the swap output
* @param signatureData the signature data of the permit of the input token amount, if available
*/
export function useSwapCallArguments(
trade: Trade<Currency, Currency, TradeType> | undefined,
allowedSlippage: Percent,
recipientAddressOrName: string | null | undefined,
signatureData: SignatureData | null | undefined,
deadline: BigNumber | undefined,
feeOptions: FeeOptions | undefined
): SwapCall[] {
const { account, chainId, provider } = useWeb3React()
const { address: recipientAddress } = useENS(recipientAddressOrName)
const recipient = recipientAddressOrName === null ? account : recipientAddress
const argentWalletContract = useArgentWalletContract()
return useMemo(() => {
if (!trade || !recipient || !provider || !account || !chainId || !deadline) return []
const swapRouterAddress = chainId ? SWAP_ROUTER_ADDRESSES[chainId] : undefined
if (!swapRouterAddress) return []
const { value, calldata } = SwapRouter.swapCallParameters(trade, {
fee: feeOptions,
recipient,
slippageTolerance: allowedSlippage,
...(signatureData
? {
inputTokenPermit:
'allowed' in signatureData
? {
expiry: signatureData.deadline,
nonce: signatureData.nonce,
s: signatureData.s,
r: signatureData.r,
v: signatureData.v as any,
}
: {
deadline: signatureData.deadline,
amount: signatureData.amount,
s: signatureData.s,
r: signatureData.r,
v: signatureData.v as any,
},
}
: {}),
deadlineOrPreviousBlockhash: deadline.toString(),
})
if (argentWalletContract && trade.inputAmount.currency.isToken) {
return [
{
address: argentWalletContract.address,
calldata: argentWalletContract.interface.encodeFunctionData('wc_multiCall', [
[
approveAmountCalldata(trade.maximumAmountIn(allowedSlippage), swapRouterAddress),
{
to: swapRouterAddress,
value,
data: calldata,
},
],
]),
value: '0x0',
},
]
}
return [
{
address: swapRouterAddress,
calldata,
value,
},
]
}, [
account,
allowedSlippage,
argentWalletContract,
chainId,
deadline,
feeOptions,
provider,
recipient,
signatureData,
trade,
])
}
import { Trade } from '@uniswap/router-sdk' import { Trade } from '@uniswap/router-sdk'
import { Currency, Percent, TradeType } from '@uniswap/sdk-core' 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 { PermitSignature } from 'hooks/usePermitAllowance'
import { SwapCallbackState, useSwapCallback as useLibSwapCallBack } from 'lib/hooks/swap/useSwapCallback' import { useMemo } from 'react'
import { ReactNode, useMemo } from 'react'
import { useTransactionAdder } from '../state/transactions/hooks' import { useTransactionAdder } from '../state/transactions/hooks'
import { TransactionType } from '../state/transactions/types' import { TransactionType } from '../state/transactions/types'
import { currencyId } from '../utils/currencyId' import { currencyId } from '../utils/currencyId'
import useENS from './useENS'
import { SignatureData } from './useERC20Permit'
import useTransactionDeadline from './useTransactionDeadline' import useTransactionDeadline from './useTransactionDeadline'
import { useUniversalRouterSwapCallback } from './useUniversalRouter' import { useUniversalRouterSwapCallback } from './useUniversalRouter'
...@@ -19,37 +14,18 @@ import { useUniversalRouterSwapCallback } from './useUniversalRouter' ...@@ -19,37 +14,18 @@ import { useUniversalRouterSwapCallback } from './useUniversalRouter'
export function useSwapCallback( export function useSwapCallback(
trade: Trade<Currency, Currency, TradeType> | undefined, // trade to execute, required trade: Trade<Currency, Currency, TradeType> | undefined, // trade to execute, required
allowedSlippage: Percent, // in bips 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,
permitSignature: PermitSignature | undefined permitSignature: PermitSignature | undefined
): { state: SwapCallbackState; callback: null | (() => Promise<string>); error: ReactNode | null } { ): { callback: null | (() => Promise<string>) } {
const { account } = useWeb3React()
const deadline = useTransactionDeadline() const deadline = useTransactionDeadline()
const addTransaction = useTransactionAdder() const addTransaction = useTransactionAdder()
const { address: recipientAddress } = useENS(recipientAddressOrName) const universalRouterSwapCallback = useUniversalRouterSwapCallback(trade, {
const recipient = recipientAddressOrName === null ? account : recipientAddress
const permit2Enabled = usePermit2Enabled()
const {
state,
callback: libCallback,
error,
} = useLibSwapCallBack({
trade: permit2Enabled ? undefined : trade,
allowedSlippage,
recipientAddressOrName: recipient,
signatureData,
deadline,
})
const universalRouterSwapCallback = useUniversalRouterSwapCallback(permit2Enabled ? trade : undefined, {
slippageTolerance: allowedSlippage, slippageTolerance: allowedSlippage,
deadline, deadline,
permit: permitSignature, permit: permitSignature,
}) })
const swapCallback = permit2Enabled ? universalRouterSwapCallback : libCallback const swapCallback = universalRouterSwapCallback
const callback = useMemo(() => { const callback = useMemo(() => {
if (!trade || !swapCallback) return null if (!trade || !swapCallback) return null
...@@ -82,8 +58,6 @@ export function useSwapCallback( ...@@ -82,8 +58,6 @@ export function useSwapCallback(
}, [addTransaction, allowedSlippage, swapCallback, trade]) }, [addTransaction, allowedSlippage, swapCallback, trade])
return { return {
state,
callback, callback,
error,
} }
} }
import { BigNumber } from '@ethersproject/bignumber'
import type { JsonRpcProvider, TransactionResponse } from '@ethersproject/providers'
// eslint-disable-next-line no-restricted-imports
import { t, Trans } from '@lingui/macro'
import { sendAnalyticsEvent } from '@uniswap/analytics'
import { SwapEventName } from '@uniswap/analytics-events'
import { Trade } from '@uniswap/router-sdk'
import { Currency, TradeType } from '@uniswap/sdk-core'
import { formatSwapSignedAnalyticsEventProperties } from 'lib/utils/analytics'
import { useMemo } from 'react'
import { calculateGasMargin } from 'utils/calculateGasMargin'
import isZero from 'utils/isZero'
import { swapErrorToUserReadableMessage } from 'utils/swapErrorToUserReadableMessage'
interface SwapCall {
address: string
calldata: string
value: string
}
interface SwapCallEstimate {
call: SwapCall
}
interface SuccessfulCall extends SwapCallEstimate {
call: SwapCall
gasEstimate: BigNumber
}
interface FailedCall extends SwapCallEstimate {
call: SwapCall
error: Error
}
class InvalidSwapError extends Error {}
// returns a function that will execute a swap, if the parameters are all valid
export default function useSendSwapTransaction(
account: string | null | undefined,
chainId: number | undefined,
provider: JsonRpcProvider | undefined,
trade: Trade<Currency, Currency, TradeType> | undefined, // trade to execute, required
swapCalls: SwapCall[]
): { callback: null | (() => Promise<TransactionResponse>) } {
return useMemo(() => {
if (!trade || !provider || !account || !chainId) {
return { callback: null }
}
return {
callback: async function onSwap(): Promise<TransactionResponse> {
const estimatedCalls: SwapCallEstimate[] = await Promise.all(
swapCalls.map((call) => {
const { address, calldata, value } = call
const tx =
!value || isZero(value)
? { from: account, to: address, data: calldata }
: {
from: account,
to: address,
data: calldata,
value,
}
return provider
.estimateGas(tx)
.then((gasEstimate) => {
return {
call,
gasEstimate,
}
})
.catch((gasError) => {
console.debug('Gas estimate failed, trying eth_call to extract error', call)
return provider
.call(tx)
.then((result) => {
console.debug('Unexpected successful call after failed estimate gas', call, gasError, result)
return { call, error: <Trans>Unexpected issue with estimating the gas. Please try again.</Trans> }
})
.catch((callError) => {
console.debug('Call threw error', call, callError)
return { call, error: swapErrorToUserReadableMessage(callError) }
})
})
})
)
// a successful estimation is a bignumber gas estimate and the next call is also a bignumber gas estimate
let bestCallOption: SuccessfulCall | SwapCallEstimate | undefined = estimatedCalls.find(
(el, ix, list): el is SuccessfulCall =>
'gasEstimate' in el && (ix === list.length - 1 || 'gasEstimate' in list[ix + 1])
)
// check if any calls errored with a recognizable error
if (!bestCallOption) {
const errorCalls = estimatedCalls.filter((call): call is FailedCall => 'error' in call)
if (errorCalls.length > 0) throw errorCalls[errorCalls.length - 1].error
const firstNoErrorCall = estimatedCalls.find<SwapCallEstimate>(
(call): call is SwapCallEstimate => !('error' in call)
)
if (!firstNoErrorCall) throw new Error(t`Unexpected error. Could not estimate gas for the swap.`)
bestCallOption = firstNoErrorCall
}
const {
call: { address, calldata, value },
} = bestCallOption
return provider
.getSigner()
.sendTransaction({
from: account,
to: address,
data: calldata,
// let the wallet try if we can't estimate the gas
...('gasEstimate' in bestCallOption ? { gasLimit: calculateGasMargin(bestCallOption.gasEstimate) } : {}),
...(value && !isZero(value) ? { value } : {}),
})
.then((response) => {
sendAnalyticsEvent(
SwapEventName.SWAP_SIGNED,
formatSwapSignedAnalyticsEventProperties({ trade, txHash: response.hash })
)
if (calldata !== response.data) {
sendAnalyticsEvent(SwapEventName.SWAP_MODIFIED_IN_WALLET, { txHash: response.hash })
throw new InvalidSwapError(
t`Your swap was modified through your wallet. If this was a mistake, please cancel immediately or risk losing your funds.`
)
}
return response
})
.catch((error) => {
// if the user rejected the tx, pass this along
if (error?.code === 4001) {
throw new Error(t`Transaction rejected`)
} else {
// otherwise, the error was unexpected and we need to convey that
console.error(`Swap failed`, error, address, calldata, value)
if (error instanceof InvalidSwapError) {
throw error
} else {
throw new Error(t`Swap failed: ${swapErrorToUserReadableMessage(error)}`)
}
}
})
},
}
}, [account, chainId, provider, swapCalls, trade])
}
import { Trade } from '@uniswap/router-sdk'
import { Currency, CurrencyAmount, Percent, Token, TradeType } from '@uniswap/sdk-core'
import { useWeb3React } from '@web3-react/core'
import { SWAP_ROUTER_ADDRESSES } from 'constants/addresses'
import { useMemo } from 'react'
import { useApproval } from '../useApproval'
// wraps useApproveCallback in the context of a swap
export default function useSwapApproval(
trade: Trade<Currency, Currency, TradeType> | undefined,
allowedSlippage: Percent,
useIsPendingApproval: (token?: Token, spender?: string) => boolean,
amount?: CurrencyAmount<Currency> // defaults to trade.maximumAmountIn(allowedSlippage)
) {
const { chainId } = useWeb3React()
const amountToApprove = useMemo(
() => amount || (trade && trade.inputAmount.currency.isToken ? trade.maximumAmountIn(allowedSlippage) : undefined),
[amount, trade, allowedSlippage]
)
const spender = chainId ? SWAP_ROUTER_ADDRESSES[chainId] : undefined
return useApproval(amountToApprove, spender, useIsPendingApproval)
}
// eslint-disable-next-line no-restricted-imports
import { BigNumber } from '@ethersproject/bignumber'
import type { TransactionResponse } from '@ethersproject/providers'
import { Trans } from '@lingui/macro'
import { Trade } from '@uniswap/router-sdk'
import { Currency, Percent, TradeType } from '@uniswap/sdk-core'
import { FeeOptions } from '@uniswap/v3-sdk'
import { useWeb3React } from '@web3-react/core'
import useENS from 'hooks/useENS'
import { SignatureData } from 'hooks/useERC20Permit'
import { useSwapCallArguments } from 'hooks/useSwapCallArguments'
import { ReactNode, useMemo } from 'react'
import useSendSwapTransaction from './useSendSwapTransaction'
export enum SwapCallbackState {
INVALID,
LOADING,
VALID,
}
interface UseSwapCallbackReturns {
state: SwapCallbackState
callback?: () => Promise<TransactionResponse>
error?: ReactNode
}
interface UseSwapCallbackArgs {
trade: Trade<Currency, Currency, TradeType> | undefined // trade to execute, required
allowedSlippage: Percent // in bips
recipientAddressOrName: string | null | undefined // the ENS name or address of the recipient of the trade, or null if swap should be returned to sender
signatureData: SignatureData | null | undefined
deadline: BigNumber | undefined
feeOptions?: FeeOptions
}
// returns a function that will execute a swap, if the parameters are all valid
// and the user has approved the slippage adjusted input amount for the trade
export function useSwapCallback({
trade,
allowedSlippage,
recipientAddressOrName,
signatureData,
deadline,
feeOptions,
}: UseSwapCallbackArgs): UseSwapCallbackReturns {
const { account, chainId, provider } = useWeb3React()
const swapCalls = useSwapCallArguments(
trade,
allowedSlippage,
recipientAddressOrName,
signatureData,
deadline,
feeOptions
)
const { callback } = useSendSwapTransaction(account, chainId, provider, trade, swapCalls)
const { address: recipientAddress } = useENS(recipientAddressOrName)
const recipient = recipientAddressOrName === null ? account : recipientAddress
return useMemo(() => {
if (!trade || !provider || !account || !chainId || !callback) {
return { state: SwapCallbackState.INVALID, error: <Trans>Missing dependencies</Trans> }
}
if (!recipient) {
if (recipientAddressOrName !== null) {
return { state: SwapCallbackState.INVALID, error: <Trans>Invalid recipient</Trans> }
} else {
return { state: SwapCallbackState.LOADING }
}
}
return {
state: SwapCallbackState.VALID,
callback: async () => callback(),
}
}, [trade, provider, account, chainId, callback, recipient, recipientAddressOrName])
}
...@@ -21,16 +21,15 @@ import TokenSafetyModal from 'components/TokenSafety/TokenSafetyModal' ...@@ -21,16 +21,15 @@ import TokenSafetyModal from 'components/TokenSafety/TokenSafetyModal'
import { MouseoverTooltip } from 'components/Tooltip' import { MouseoverTooltip } from 'components/Tooltip'
import Widget from 'components/Widget' import Widget from 'components/Widget'
import { isSupportedChain } from 'constants/chains' import { isSupportedChain } from 'constants/chains'
import { usePermit2Enabled } from 'featureFlags/flags/permit2'
import { useSwapWidgetEnabled } from 'featureFlags/flags/swapWidget' import { useSwapWidgetEnabled } from 'featureFlags/flags/swapWidget'
import useENSAddress from 'hooks/useENSAddress'
import usePermit2Allowance, { AllowanceState } from 'hooks/usePermit2Allowance' import usePermit2Allowance, { AllowanceState } from 'hooks/usePermit2Allowance'
import { useSwapCallback } from 'hooks/useSwapCallback' import { useSwapCallback } from 'hooks/useSwapCallback'
import useTransactionDeadline from 'hooks/useTransactionDeadline'
import JSBI from 'jsbi' import JSBI from 'jsbi'
import { formatSwapQuoteReceivedEventProperties } from 'lib/utils/analytics' import { formatSwapQuoteReceivedEventProperties } from 'lib/utils/analytics'
import { useCallback, useEffect, useMemo, useState } from 'react' import { useCallback, useEffect, useMemo, useState } from 'react'
import { ReactNode } from 'react' import { ReactNode } from 'react'
import { ArrowDown, CheckCircle, HelpCircle, Info } from 'react-feather' import { ArrowDown, Info } from 'react-feather'
import { useNavigate } from 'react-router-dom' import { useNavigate } from 'react-router-dom'
import { Text } from 'rebass' import { Text } from 'rebass'
import { useToggleWalletModal } from 'state/application/hooks' import { useToggleWalletModal } from 'state/application/hooks'
...@@ -41,7 +40,7 @@ import invariant from 'tiny-invariant' ...@@ -41,7 +40,7 @@ import invariant from 'tiny-invariant'
import { currencyAmountToPreciseFloat, formatTransactionAmount } from 'utils/formatNumbers' import { currencyAmountToPreciseFloat, formatTransactionAmount } from 'utils/formatNumbers'
import AddressInputPanel from '../../components/AddressInputPanel' import AddressInputPanel from '../../components/AddressInputPanel'
import { ButtonConfirmed, ButtonError, ButtonLight, ButtonPrimary } from '../../components/Button' import { ButtonError, ButtonLight, ButtonPrimary } from '../../components/Button'
import { GrayCard } from '../../components/Card' import { GrayCard } from '../../components/Card'
import { AutoColumn } from '../../components/Column' import { AutoColumn } from '../../components/Column'
import SwapCurrencyInputPanel from '../../components/CurrencyInputPanel/SwapCurrencyInputPanel' import SwapCurrencyInputPanel from '../../components/CurrencyInputPanel/SwapCurrencyInputPanel'
...@@ -54,10 +53,6 @@ import SwapHeader from '../../components/swap/SwapHeader' ...@@ -54,10 +53,6 @@ import SwapHeader from '../../components/swap/SwapHeader'
import { SwitchLocaleLink } from '../../components/SwitchLocaleLink' import { SwitchLocaleLink } from '../../components/SwitchLocaleLink'
import { TOKEN_SHORTHANDS } from '../../constants/tokens' import { TOKEN_SHORTHANDS } from '../../constants/tokens'
import { useAllTokens, useCurrency } from '../../hooks/Tokens' import { useAllTokens, useCurrency } from '../../hooks/Tokens'
import { ApprovalState, useApproveCallbackFromTrade } from '../../hooks/useApproveCallback'
import useENSAddress from '../../hooks/useENSAddress'
import { useERC20PermitFromTrade, UseERC20PermitState } from '../../hooks/useERC20Permit'
import useIsArgentWallet from '../../hooks/useIsArgentWallet'
import { useIsSwapUnsupported } from '../../hooks/useIsSwapUnsupported' import { useIsSwapUnsupported } from '../../hooks/useIsSwapUnsupported'
import { useStablecoinValue } from '../../hooks/useStablecoinPrice' import { useStablecoinValue } from '../../hooks/useStablecoinPrice'
import useWrapCallback, { WrapErrorText, WrapType } from '../../hooks/useWrapCallback' import useWrapCallback, { WrapErrorText, WrapType } from '../../hooks/useWrapCallback'
...@@ -303,19 +298,16 @@ export default function Swap({ className }: { className?: string }) { ...@@ -303,19 +298,16 @@ export default function Swap({ className }: { className?: string }) {
currencies[Field.INPUT] && currencies[Field.OUTPUT] && parsedAmounts[independentField]?.greaterThan(JSBI.BigInt(0)) currencies[Field.INPUT] && currencies[Field.OUTPUT] && parsedAmounts[independentField]?.greaterThan(JSBI.BigInt(0))
) )
const permit2Enabled = usePermit2Enabled()
const maximumAmountIn = useMemo(() => { const maximumAmountIn = useMemo(() => {
const maximumAmountIn = trade?.maximumAmountIn(allowedSlippage) const maximumAmountIn = trade?.maximumAmountIn(allowedSlippage)
return maximumAmountIn?.currency.isToken ? (maximumAmountIn as CurrencyAmount<Token>) : undefined return maximumAmountIn?.currency.isToken ? (maximumAmountIn as CurrencyAmount<Token>) : undefined
}, [allowedSlippage, trade]) }, [allowedSlippage, trade])
const allowance = usePermit2Allowance( const allowance = usePermit2Allowance(
permit2Enabled maximumAmountIn ??
? maximumAmountIn ?? (parsedAmounts[Field.INPUT]?.currency.isToken
(parsedAmounts[Field.INPUT]?.currency.isToken ? (parsedAmounts[Field.INPUT] as CurrencyAmount<Token>)
? (parsedAmounts[Field.INPUT] as CurrencyAmount<Token>) : undefined),
: undefined) chainId ? UNIVERSAL_ROUTER_ADDRESS(chainId) : undefined
: undefined,
permit2Enabled && chainId ? UNIVERSAL_ROUTER_ADDRESS(chainId) : undefined
) )
const isApprovalLoading = allowance.state === AllowanceState.REQUIRED && allowance.isApprovalLoading const isApprovalLoading = allowance.state === AllowanceState.REQUIRED && allowance.isApprovalLoading
const [isAllowancePending, setIsAllowancePending] = useState(false) const [isAllowancePending, setIsAllowancePending] = useState(false)
...@@ -336,55 +328,6 @@ export default function Swap({ className }: { className?: string }) { ...@@ -336,55 +328,6 @@ export default function Swap({ className }: { className?: string }) {
} }
}, [allowance, chainId, maximumAmountIn?.currency.address, maximumAmountIn?.currency.symbol]) }, [allowance, chainId, maximumAmountIn?.currency.address, maximumAmountIn?.currency.symbol])
// check whether the user has approved the router on the input token
const [approvalState, approveCallback] = useApproveCallbackFromTrade(
permit2Enabled ? undefined : trade,
allowedSlippage
)
const transactionDeadline = useTransactionDeadline()
const {
state: signatureState,
signatureData,
gatherPermitSignature,
} = useERC20PermitFromTrade(permit2Enabled ? undefined : trade, allowedSlippage, transactionDeadline)
const [approvalPending, setApprovalPending] = useState<boolean>(false)
const handleApprove = useCallback(async () => {
setApprovalPending(true)
try {
if (signatureState === UseERC20PermitState.NOT_SIGNED && gatherPermitSignature) {
try {
await gatherPermitSignature()
} catch (error) {
// try to approve if gatherPermitSignature failed for any reason other than the user rejecting it
if (error?.code !== 4001) {
await approveCallback()
}
}
} else {
await approveCallback()
sendEvent({
category: 'Swap',
action: 'Approve',
label: [TRADE_STRING, trade?.inputAmount?.currency.symbol].join('/'),
})
}
} finally {
setApprovalPending(false)
}
}, [signatureState, gatherPermitSignature, approveCallback, trade?.inputAmount?.currency.symbol])
// check if user has gone through approval process, used to show two step buttons, reset on token change
const [approvalSubmitted, setApprovalSubmitted] = useState<boolean>(false)
// mark when a user has submitted an approval, reset onTokenSelection for input field
useEffect(() => {
if (approvalState === ApprovalState.PENDING) {
setApprovalSubmitted(true)
}
}, [approvalState, approvalSubmitted])
const maxInputAmount: CurrencyAmount<Currency> | undefined = useMemo( const maxInputAmount: CurrencyAmount<Currency> | undefined = useMemo(
() => maxAmountSpend(currencyBalances[Field.INPUT]), () => maxAmountSpend(currencyBalances[Field.INPUT]),
[currencyBalances] [currencyBalances]
...@@ -392,11 +335,9 @@ export default function Swap({ className }: { className?: string }) { ...@@ -392,11 +335,9 @@ export default function Swap({ className }: { className?: string }) {
const showMaxButton = Boolean(maxInputAmount?.greaterThan(0) && !parsedAmounts[Field.INPUT]?.equalTo(maxInputAmount)) const showMaxButton = Boolean(maxInputAmount?.greaterThan(0) && !parsedAmounts[Field.INPUT]?.equalTo(maxInputAmount))
// the callback to execute the swap // the callback to execute the swap
const { callback: swapCallback, error: swapCallbackError } = useSwapCallback( const { callback: swapCallback } = useSwapCallback(
trade, trade,
allowedSlippage, allowedSlippage,
recipient,
signatureData,
allowance.state === AllowanceState.ALLOWED ? allowance.permitSignature : undefined allowance.state === AllowanceState.ALLOWED ? allowance.permitSignature : undefined
) )
...@@ -460,19 +401,6 @@ export default function Swap({ className }: { className?: string }) { ...@@ -460,19 +401,6 @@ export default function Swap({ className }: { className?: string }) {
return { priceImpactSeverity: warningSeverity(largerPriceImpact), largerPriceImpact } return { priceImpactSeverity: warningSeverity(largerPriceImpact), largerPriceImpact }
}, [stablecoinPriceImpact, trade]) }, [stablecoinPriceImpact, trade])
const isArgentWallet = useIsArgentWallet()
// show approve flow when: no error on inputs, not approved or pending, or approved in current session
// never show if price impact is above threshold in non expert mode
const showApproveFlow =
!permit2Enabled &&
!isArgentWallet &&
!swapInputError &&
(approvalState === ApprovalState.NOT_APPROVED ||
approvalState === ApprovalState.PENDING ||
(approvalSubmitted && approvalState === ApprovalState.APPROVED)) &&
!(priceImpactSeverity > 3 && !isExpertMode)
const handleConfirmDismiss = useCallback(() => { const handleConfirmDismiss = useCallback(() => {
setSwapState({ showConfirm: false, tradeToConfirm, attemptingTxn, swapErrorMessage, txHash }) setSwapState({ showConfirm: false, tradeToConfirm, attemptingTxn, swapErrorMessage, txHash })
// if there was a tx hash, we want to clear the input // if there was a tx hash, we want to clear the input
...@@ -487,7 +415,6 @@ export default function Swap({ className }: { className?: string }) { ...@@ -487,7 +415,6 @@ export default function Swap({ className }: { className?: string }) {
const handleInputSelect = useCallback( const handleInputSelect = useCallback(
(inputCurrency: Currency) => { (inputCurrency: Currency) => {
setApprovalSubmitted(false) // reset 2 step UI for approvals
onCurrencySelection(Field.INPUT, inputCurrency) onCurrencySelection(Field.INPUT, inputCurrency)
}, },
[onCurrencySelection] [onCurrencySelection]
...@@ -543,9 +470,6 @@ export default function Swap({ className }: { className?: string }) { ...@@ -543,9 +470,6 @@ export default function Swap({ className }: { className?: string }) {
setSwapQuoteReceivedDate, setSwapQuoteReceivedDate,
]) ])
const approveTokenButtonDisabled =
approvalState !== ApprovalState.NOT_APPROVED || approvalSubmitted || signatureState === UseERC20PermitState.SIGNED
const showDetailsDropdown = Boolean( const showDetailsDropdown = Boolean(
!showWrap && userHasSpecifiedInputOutput && (trade || routeIsLoading || routeIsSyncing) !showWrap && userHasSpecifiedInputOutput && (trade || routeIsLoading || routeIsSyncing)
) )
...@@ -623,7 +547,6 @@ export default function Swap({ className }: { className?: string }) { ...@@ -623,7 +547,6 @@ export default function Swap({ className }: { className?: string }) {
> >
<ArrowContainer <ArrowContainer
onClick={() => { onClick={() => {
setApprovalSubmitted(false) // reset 2 step UI for approvals
onSwitchTokens() onSwitchTokens()
}} }}
color={theme.textPrimary} color={theme.textPrimary}
...@@ -725,89 +648,6 @@ export default function Swap({ className }: { className?: string }) { ...@@ -725,89 +648,6 @@ export default function Swap({ className }: { className?: string }) {
<Trans>Insufficient liquidity for this trade.</Trans> <Trans>Insufficient liquidity for this trade.</Trans>
</ThemedText.DeprecatedMain> </ThemedText.DeprecatedMain>
</GrayCard> </GrayCard>
) : showApproveFlow ? (
<AutoRow style={{ flexWrap: 'nowrap', width: '100%' }}>
<AutoColumn style={{ width: '100%' }} gap="12px">
<ButtonConfirmed
fontWeight={600}
onClick={handleApprove}
disabled={approveTokenButtonDisabled}
width="100%"
altDisabledStyle={approvalState === ApprovalState.PENDING} // show solid button while waiting
confirmed={
approvalState === ApprovalState.APPROVED || signatureState === UseERC20PermitState.SIGNED
}
>
<AutoRow justify="space-between" style={{ flexWrap: 'nowrap' }} height="20px">
{/* we need to shorten this string on mobile */}
{approvalState === ApprovalState.APPROVED ||
signatureState === UseERC20PermitState.SIGNED ? (
<ThemedText.SubHeader width="100%" textAlign="center" color="textSecondary">
<Trans>You can now trade {currencies[Field.INPUT]?.symbol}</Trans>
</ThemedText.SubHeader>
) : (
<ThemedText.SubHeader width="100%" textAlign="center" color="white">
<Trans>Allow the Uniswap Protocol to use your {currencies[Field.INPUT]?.symbol}</Trans>
</ThemedText.SubHeader>
)}
{approvalPending || approvalState === ApprovalState.PENDING ? (
<Loader stroke={theme.white} />
) : (approvalSubmitted && approvalState === ApprovalState.APPROVED) ||
signatureState === UseERC20PermitState.SIGNED ? (
<CheckCircle size="20" color={theme.accentSuccess} />
) : (
<MouseoverTooltip
text={
<Trans>
You must give the Uniswap smart contracts permission to use your{' '}
{currencies[Field.INPUT]?.symbol}. You only have to do this once per token.
</Trans>
}
>
<HelpCircle size="20" color={theme.white} style={{ marginLeft: '8px' }} />
</MouseoverTooltip>
)}
</AutoRow>
</ButtonConfirmed>
<ButtonError
onClick={() => {
if (isExpertMode) {
handleSwap()
} else {
setSwapState({
tradeToConfirm: trade,
attemptingTxn: false,
swapErrorMessage: undefined,
showConfirm: true,
txHash: undefined,
})
}
}}
width="100%"
id="swap-button"
disabled={
!isValid ||
routeIsSyncing ||
routeIsLoading ||
(approvalState !== ApprovalState.APPROVED &&
signatureState !== UseERC20PermitState.SIGNED) ||
priceImpactTooHigh
}
error={isValid && priceImpactSeverity > 2}
>
<Text fontSize={16} fontWeight={600}>
{priceImpactTooHigh ? (
<Trans>High Price Impact</Trans>
) : trade && priceImpactSeverity > 2 ? (
<Trans>Swap Anyway</Trans>
) : (
<Trans>Swap</Trans>
)}
</Text>
</ButtonError>
</AutoColumn>
</AutoRow>
) : isValid && allowance.state === AllowanceState.REQUIRED ? ( ) : isValid && allowance.state === AllowanceState.REQUIRED ? (
<ButtonPrimary <ButtonPrimary
onClick={updateAllowance} onClick={updateAllowance}
...@@ -863,13 +703,9 @@ export default function Swap({ className }: { className?: string }) { ...@@ -863,13 +703,9 @@ export default function Swap({ className }: { className?: string }) {
routeIsSyncing || routeIsSyncing ||
routeIsLoading || routeIsLoading ||
priceImpactTooHigh || priceImpactTooHigh ||
(permit2Enabled ? allowance.state !== AllowanceState.ALLOWED : Boolean(swapCallbackError)) allowance.state !== AllowanceState.ALLOWED
}
error={
isValid &&
priceImpactSeverity > 2 &&
(permit2Enabled ? allowance.state === AllowanceState.ALLOWED : !swapCallbackError)
} }
error={isValid && priceImpactSeverity > 2 && allowance.state === AllowanceState.ALLOWED}
> >
<Text fontSize={20} fontWeight={600}> <Text fontSize={20} fontWeight={600}>
{swapInputError ? ( {swapInputError ? (
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment