Commit b50d10cb authored by Ian Lapham's avatar Ian Lapham Committed by GitHub

feat: update swap hooks and add swap txn submission (#3187)

* update swap hooks to add swap txn confirmations

* fix: remove uneeded comments

* update with latest

* update utils to separate swap callback hooks

* create generic swap callabck to be used by both app and widget

* update app swap callback to use logic from lib

* update big number import
parent ce96873a
...@@ -3,12 +3,12 @@ import { Trans } from '@lingui/macro' ...@@ -3,12 +3,12 @@ import { Trans } from '@lingui/macro'
import { CurrencyAmount, Token } from '@uniswap/sdk-core' import { CurrencyAmount, Token } from '@uniswap/sdk-core'
import { Pair } from '@uniswap/v2-sdk' import { Pair } from '@uniswap/v2-sdk'
import useActiveWeb3React from 'hooks/useActiveWeb3React' import useActiveWeb3React from 'hooks/useActiveWeb3React'
import { useV2LiquidityTokenPermit } from 'hooks/useV2LiquidityTokenPermit'
import { useCallback, useState } from 'react' import { useCallback, useState } from 'react'
import styled from 'styled-components/macro' import styled from 'styled-components/macro'
import { ApprovalState, useApproveCallback } from '../../hooks/useApproveCallback' import { ApprovalState, useApproveCallback } from '../../hooks/useApproveCallback'
import { usePairContract, useStakingContract, useV2RouterContract } from '../../hooks/useContract' import { usePairContract, useStakingContract, useV2RouterContract } from '../../hooks/useContract'
import { useV2LiquidityTokenPermit } from '../../hooks/useERC20Permit'
import useTransactionDeadline from '../../hooks/useTransactionDeadline' import useTransactionDeadline from '../../hooks/useTransactionDeadline'
import { StakingInfo, useDerivedStakeInfo } from '../../state/stake/hooks' import { StakingInfo, useDerivedStakeInfo } from '../../state/stake/hooks'
import { TransactionType } from '../../state/transactions/actions' import { TransactionType } from '../../state/transactions/actions'
......
import { BigNumber } from '@ethersproject/bignumber'
import { splitSignature } from '@ethersproject/bytes' import { splitSignature } from '@ethersproject/bytes'
import { Trade } from '@uniswap/router-sdk' import { Trade } from '@uniswap/router-sdk'
import { Currency, CurrencyAmount, Percent, Token, TradeType } from '@uniswap/sdk-core' import { Currency, CurrencyAmount, Percent, TradeType } from '@uniswap/sdk-core'
import { Trade as V2Trade } from '@uniswap/v2-sdk' import { Trade as V2Trade } from '@uniswap/v2-sdk'
import { Trade as V3Trade } from '@uniswap/v3-sdk' import { Trade as V3Trade } from '@uniswap/v3-sdk'
import useActiveWeb3React from 'hooks/useActiveWeb3React' import useActiveWeb3React from 'hooks/useActiveWeb3React'
...@@ -12,9 +13,8 @@ import { SWAP_ROUTER_ADDRESSES, V3_ROUTER_ADDRESS } from '../constants/addresses ...@@ -12,9 +13,8 @@ import { SWAP_ROUTER_ADDRESSES, V3_ROUTER_ADDRESS } from '../constants/addresses
import { DAI, UNI, USDC } from '../constants/tokens' import { DAI, UNI, USDC } from '../constants/tokens'
import { useEIP2612Contract } from './useContract' import { useEIP2612Contract } from './useContract'
import useIsArgentWallet from './useIsArgentWallet' import useIsArgentWallet from './useIsArgentWallet'
import useTransactionDeadline from './useTransactionDeadline'
enum PermitType { export enum PermitType {
AMOUNT = 1, AMOUNT = 1,
ALLOWED = 2, ALLOWED = 2,
} }
...@@ -22,7 +22,7 @@ enum PermitType { ...@@ -22,7 +22,7 @@ enum PermitType {
// 20 minutes to submit after signing // 20 minutes to submit after signing
const PERMIT_VALIDITY_BUFFER = 20 * 60 const PERMIT_VALIDITY_BUFFER = 20 * 60
interface PermitInfo { export interface PermitInfo {
type: PermitType type: PermitType
name: string name: string
// version is optional, and if omitted, will not be included in the domain // version is optional, and if omitted, will not be included in the domain
...@@ -116,9 +116,10 @@ const PERMIT_ALLOWED_TYPE = [ ...@@ -116,9 +116,10 @@ const PERMIT_ALLOWED_TYPE = [
{ name: 'allowed', type: 'bool' }, { name: 'allowed', type: 'bool' },
] ]
function useERC20Permit( export function useERC20Permit(
currencyAmount: CurrencyAmount<Currency> | null | undefined, currencyAmount: CurrencyAmount<Currency> | null | undefined,
spender: string | null | undefined, spender: string | null | undefined,
transactionDeadline: BigNumber | undefined,
overridePermitInfo: PermitInfo | undefined | null overridePermitInfo: PermitInfo | undefined | null
): { ): {
signatureData: SignatureData | null signatureData: SignatureData | null
...@@ -126,7 +127,6 @@ function useERC20Permit( ...@@ -126,7 +127,6 @@ function useERC20Permit(
gatherPermitSignature: null | (() => Promise<void>) gatherPermitSignature: null | (() => Promise<void>)
} { } {
const { account, chainId, library } = useActiveWeb3React() const { account, chainId, library } = useActiveWeb3React()
const transactionDeadline = useTransactionDeadline()
const tokenAddress = currencyAmount?.currency?.isToken ? currencyAmount.currency.address : undefined const tokenAddress = currencyAmount?.currency?.isToken ? currencyAmount.currency.address : undefined
const eip2612Contract = useEIP2612Contract(tokenAddress) const eip2612Contract = useEIP2612Contract(tokenAddress)
const isArgentWallet = useIsArgentWallet() const isArgentWallet = useIsArgentWallet()
...@@ -259,26 +259,14 @@ function useERC20Permit( ...@@ -259,26 +259,14 @@ function useERC20Permit(
]) ])
} }
const REMOVE_V2_LIQUIDITY_PERMIT_INFO: PermitInfo = {
version: '1',
name: 'Uniswap V2',
type: PermitType.AMOUNT,
}
export function useV2LiquidityTokenPermit(
liquidityAmount: CurrencyAmount<Token> | null | undefined,
spender: string | null | undefined
) {
return useERC20Permit(liquidityAmount, spender, REMOVE_V2_LIQUIDITY_PERMIT_INFO)
}
export function useERC20PermitFromTrade( export function useERC20PermitFromTrade(
trade: trade:
| V2Trade<Currency, Currency, TradeType> | V2Trade<Currency, Currency, TradeType>
| V3Trade<Currency, Currency, TradeType> | V3Trade<Currency, Currency, TradeType>
| Trade<Currency, Currency, TradeType> | Trade<Currency, Currency, TradeType>
| undefined, | undefined,
allowedSlippage: Percent allowedSlippage: Percent,
transactionDeadline: BigNumber | undefined
) { ) {
const { chainId } = useActiveWeb3React() const { chainId } = useActiveWeb3React()
const swapRouterAddress = chainId const swapRouterAddress = chainId
...@@ -294,5 +282,5 @@ export function useERC20PermitFromTrade( ...@@ -294,5 +282,5 @@ export function useERC20PermitFromTrade(
[trade, allowedSlippage] [trade, allowedSlippage]
) )
return useERC20Permit(amountToApprove, swapRouterAddress, null) 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 { Router as V2SwapRouter, Trade as V2Trade } from '@uniswap/v2-sdk'
import { SwapRouter as V3SwapRouter, Trade as V3Trade } from '@uniswap/v3-sdk'
import { SWAP_ROUTER_ADDRESSES, V3_ROUTER_ADDRESS } from 'constants/addresses'
import useActiveWeb3React from 'hooks/useActiveWeb3React'
import { useMemo } from 'react'
import approveAmountCalldata from 'utils/approveAmountCalldata'
import { useArgentWalletContract } from './useArgentWalletContract'
import { useV2RouterContract } from './useContract'
import useENS from './useENS'
import { SignatureData } from './useERC20Permit'
export type AnyTrade =
| V2Trade<Currency, Currency, TradeType>
| V3Trade<Currency, Currency, TradeType>
| Trade<Currency, Currency, TradeType>
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: AnyTrade | undefined,
allowedSlippage: Percent,
recipientAddressOrName: string | null | undefined,
signatureData: SignatureData | null | undefined,
deadline: BigNumber | undefined
): SwapCall[] {
const { account, chainId, library } = useActiveWeb3React()
const { address: recipientAddress } = useENS(recipientAddressOrName)
const recipient = recipientAddressOrName === null ? account : recipientAddress
const routerContract = useV2RouterContract()
const argentWalletContract = useArgentWalletContract()
return useMemo(() => {
if (!trade || !recipient || !library || !account || !chainId || !deadline) return []
if (trade instanceof V2Trade) {
if (!routerContract) return []
const swapMethods = []
swapMethods.push(
V2SwapRouter.swapCallParameters(trade, {
feeOnTransfer: false,
allowedSlippage,
recipient,
deadline: deadline.toNumber(),
})
)
if (trade.tradeType === TradeType.EXACT_INPUT) {
swapMethods.push(
V2SwapRouter.swapCallParameters(trade, {
feeOnTransfer: true,
allowedSlippage,
recipient,
deadline: deadline.toNumber(),
})
)
}
return swapMethods.map(({ methodName, args, value }) => {
if (argentWalletContract && trade.inputAmount.currency.isToken) {
return {
address: argentWalletContract.address,
calldata: argentWalletContract.interface.encodeFunctionData('wc_multiCall', [
[
approveAmountCalldata(trade.maximumAmountIn(allowedSlippage), routerContract.address),
{
to: routerContract.address,
value,
data: routerContract.interface.encodeFunctionData(methodName, args),
},
],
]),
value: '0x0',
}
} else {
return {
address: routerContract.address,
calldata: routerContract.interface.encodeFunctionData(methodName, args),
value,
}
}
})
} else {
// swap options shared by v3 and v2+v3 swap routers
const sharedSwapOptions = {
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,
},
}
: {}),
}
const swapRouterAddress = chainId
? trade instanceof V3Trade
? V3_ROUTER_ADDRESS[chainId]
: SWAP_ROUTER_ADDRESSES[chainId]
: undefined
if (!swapRouterAddress) return []
const { value, calldata } =
trade instanceof V3Trade
? V3SwapRouter.swapCallParameters(trade, {
...sharedSwapOptions,
deadline: deadline.toString(),
})
: SwapRouter.swapCallParameters(trade, {
...sharedSwapOptions,
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,
},
]
}
}, [
trade,
recipient,
library,
account,
chainId,
deadline,
routerContract,
allowedSlippage,
argentWalletContract,
signatureData,
])
}
import { BigNumber } from '@ethersproject/bignumber'
// eslint-disable-next-line no-restricted-imports // eslint-disable-next-line no-restricted-imports
import { t, Trans } from '@lingui/macro' import { Percent, TradeType } from '@uniswap/sdk-core'
import { SwapRouter, Trade } from '@uniswap/router-sdk'
import { Currency, Percent, TradeType } from '@uniswap/sdk-core'
import { Router as V2SwapRouter, Trade as V2Trade } from '@uniswap/v2-sdk'
import { SwapRouter as V3SwapRouter, Trade as V3Trade } from '@uniswap/v3-sdk'
import useActiveWeb3React from 'hooks/useActiveWeb3React' import useActiveWeb3React from 'hooks/useActiveWeb3React'
import { SwapCallbackState, useSwapCallback as useLibSwapCallBack } from 'lib/hooks/swap/useSwapCallback'
import { ReactNode, useMemo } from 'react' import { ReactNode, useMemo } from 'react'
import { SWAP_ROUTER_ADDRESSES, V3_ROUTER_ADDRESS } from '../constants/addresses'
import { TransactionType } from '../state/transactions/actions' import { TransactionType } from '../state/transactions/actions'
import { useTransactionAdder } from '../state/transactions/hooks' import { useTransactionAdder } from '../state/transactions/hooks'
import approveAmountCalldata from '../utils/approveAmountCalldata'
import { calculateGasMargin } from '../utils/calculateGasMargin'
import { currencyId } from '../utils/currencyId' import { currencyId } from '../utils/currencyId'
import isZero from '../utils/isZero'
import { useArgentWalletContract } from './useArgentWalletContract'
import { useV2RouterContract } from './useContract'
import useENS from './useENS' import useENS from './useENS'
import { SignatureData } from './useERC20Permit' import { SignatureData } from './useERC20Permit'
import { AnyTrade } from './useSwapCallArguments'
import useTransactionDeadline from './useTransactionDeadline' import useTransactionDeadline from './useTransactionDeadline'
type AnyTrade =
| V2Trade<Currency, Currency, TradeType>
| V3Trade<Currency, Currency, TradeType>
| Trade<Currency, Currency, TradeType>
enum SwapCallbackState {
INVALID,
LOADING,
VALID,
}
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
}
/**
* 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
*/
function useSwapCallArguments(
trade: AnyTrade | undefined, // trade to execute, required
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 | null | undefined
): SwapCall[] {
const { account, chainId, library } = useActiveWeb3React()
const { address: recipientAddress } = useENS(recipientAddressOrName)
const recipient = recipientAddressOrName === null ? account : recipientAddress
const deadline = useTransactionDeadline()
const routerContract = useV2RouterContract()
const argentWalletContract = useArgentWalletContract()
return useMemo(() => {
if (!trade || !recipient || !library || !account || !chainId || !deadline) return []
if (trade instanceof V2Trade) {
if (!routerContract) return []
const swapMethods = []
swapMethods.push(
V2SwapRouter.swapCallParameters(trade, {
feeOnTransfer: false,
allowedSlippage,
recipient,
deadline: deadline.toNumber(),
})
)
if (trade.tradeType === TradeType.EXACT_INPUT) {
swapMethods.push(
V2SwapRouter.swapCallParameters(trade, {
feeOnTransfer: true,
allowedSlippage,
recipient,
deadline: deadline.toNumber(),
})
)
}
return swapMethods.map(({ methodName, args, value }) => {
if (argentWalletContract && trade.inputAmount.currency.isToken) {
return {
address: argentWalletContract.address,
calldata: argentWalletContract.interface.encodeFunctionData('wc_multiCall', [
[
approveAmountCalldata(trade.maximumAmountIn(allowedSlippage), routerContract.address),
{
to: routerContract.address,
value,
data: routerContract.interface.encodeFunctionData(methodName, args),
},
],
]),
value: '0x0',
}
} else {
return {
address: routerContract.address,
calldata: routerContract.interface.encodeFunctionData(methodName, args),
value,
}
}
})
} else {
// swap options shared by v3 and v2+v3 swap routers
const sharedSwapOptions = {
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,
},
}
: {}),
}
const swapRouterAddress = chainId
? trade instanceof V3Trade
? V3_ROUTER_ADDRESS[chainId]
: SWAP_ROUTER_ADDRESSES[chainId]
: undefined
if (!swapRouterAddress) return []
const { value, calldata } =
trade instanceof V3Trade
? V3SwapRouter.swapCallParameters(trade, {
...sharedSwapOptions,
deadline: deadline.toString(),
})
: SwapRouter.swapCallParameters(trade, {
...sharedSwapOptions,
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,
},
]
}
}, [
trade,
recipient,
library,
account,
chainId,
deadline,
routerContract,
allowedSlippage,
argentWalletContract,
signatureData,
])
}
/**
* This is hacking out the revert reason from the ethers provider thrown error however it can.
* This object seems to be undocumented by ethers.
* @param error an error from the ethers provider
*/
function swapErrorToUserReadableMessage(error: any): ReactNode {
let reason: string | undefined
while (Boolean(error)) {
reason = error.reason ?? error.message ?? reason
error = error.error ?? error.data?.originalError
}
if (reason?.indexOf('execution reverted: ') === 0) reason = reason.substr('execution reverted: '.length)
switch (reason) {
case 'UniswapV2Router: EXPIRED':
return (
<Trans>
The transaction could not be sent because the deadline has passed. Please check that your transaction deadline
is not too low.
</Trans>
)
case 'UniswapV2Router: INSUFFICIENT_OUTPUT_AMOUNT':
case 'UniswapV2Router: EXCESSIVE_INPUT_AMOUNT':
return (
<Trans>
This transaction will not succeed either due to price movement or fee on transfer. Try increasing your
slippage tolerance.
</Trans>
)
case 'TransferHelper: TRANSFER_FROM_FAILED':
return <Trans>The input token cannot be transferred. There may be an issue with the input token.</Trans>
case 'UniswapV2: TRANSFER_FAILED':
return <Trans>The output token cannot be transferred. There may be an issue with the output token.</Trans>
case 'UniswapV2: K':
return (
<Trans>
The Uniswap invariant x*y=k was not satisfied by the swap. This usually means one of the tokens you are
swapping incorporates custom behavior on transfer.
</Trans>
)
case 'Too little received':
case 'Too much requested':
case 'STF':
return (
<Trans>
This transaction will not succeed due to price movement. Try increasing your slippage tolerance. Note: fee on
transfer and rebase tokens are incompatible with Uniswap V3.
</Trans>
)
case 'TF':
return (
<Trans>
The output token cannot be transferred. There may be an issue with the output token. Note: fee on transfer and
rebase tokens are incompatible with Uniswap V3.
</Trans>
)
default:
if (reason?.indexOf('undefined is not an object') !== -1) {
console.error(error, reason)
return (
<Trans>
An error occurred when trying to execute this swap. You may need to increase your slippage tolerance. If
that does not work, there may be an incompatibility with the token you are trading. Note: fee on transfer
and rebase tokens are incompatible with Uniswap V3.
</Trans>
)
}
return (
<Trans>
Unknown error{reason ? `: "${reason}"` : ''}. Try increasing your slippage tolerance. Note: fee on transfer
and rebase tokens are incompatible with Uniswap V3.
</Trans>
)
}
}
// returns a function that will execute a swap, if the parameters are all valid // 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 // and the user has approved the slippage adjusted input amount for the trade
export function useSwapCallback( export function useSwapCallback(
...@@ -292,139 +20,56 @@ export function useSwapCallback( ...@@ -292,139 +20,56 @@ export function useSwapCallback(
recipientAddressOrName: string | null, // the ENS name or address of the recipient of the trade, or null if swap should be returned to sender 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 signatureData: SignatureData | undefined | null
): { state: SwapCallbackState; callback: null | (() => Promise<string>); error: ReactNode | null } { ): { state: SwapCallbackState; callback: null | (() => Promise<string>); error: ReactNode | null } {
const { account, chainId, library } = useActiveWeb3React() const { account } = useActiveWeb3React()
const swapCalls = useSwapCallArguments(trade, allowedSlippage, recipientAddressOrName, signatureData) const deadline = useTransactionDeadline()
const addTransaction = useTransactionAdder() const addTransaction = useTransactionAdder()
const { address: recipientAddress } = useENS(recipientAddressOrName) const { address: recipientAddress } = useENS(recipientAddressOrName)
const recipient = recipientAddressOrName === null ? account : recipientAddress const recipient = recipientAddressOrName === null ? account : recipientAddress
return useMemo(() => { const {
if (!trade || !library || !account || !chainId) { state,
return { state: SwapCallbackState.INVALID, callback: null, error: <Trans>Missing dependencies</Trans> } callback: libCallback,
} error,
if (!recipient) { } = useLibSwapCallBack(trade, allowedSlippage, recipient, signatureData, deadline)
if (recipientAddressOrName !== null) {
return { state: SwapCallbackState.INVALID, callback: null, error: <Trans>Invalid recipient</Trans> }
} else {
return { state: SwapCallbackState.LOADING, callback: null, error: null }
}
}
return {
state: SwapCallbackState.VALID,
callback: async function onSwap(): Promise<string> {
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 library const callback = useMemo(() => {
.estimateGas(tx) if (!libCallback || !trade) {
.then((gasEstimate) => { return null
return { }
call, return () =>
gasEstimate, libCallback().then((response) => {
} addTransaction(
}) response,
.catch((gasError) => { trade.tradeType === TradeType.EXACT_INPUT
console.debug('Gas estimate failed, trying eth_call to extract error', call) ? {
type: TransactionType.SWAP,
return library tradeType: TradeType.EXACT_INPUT,
.call(tx) inputCurrencyId: currencyId(trade.inputAmount.currency),
.then((result) => { inputCurrencyAmountRaw: trade.inputAmount.quotient.toString(),
console.debug('Unexpected successful call after failed estimate gas', call, gasError, result) expectedOutputCurrencyAmountRaw: trade.outputAmount.quotient.toString(),
return { call, error: <Trans>Unexpected issue with estimating the gas. Please try again.</Trans> } outputCurrencyId: currencyId(trade.outputAmount.currency),
}) minimumOutputCurrencyAmountRaw: trade.minimumAmountOut(allowedSlippage).quotient.toString(),
.catch((callError) => { }
console.debug('Call threw error', call, callError) : {
return { call, error: swapErrorToUserReadableMessage(callError) } type: TransactionType.SWAP,
}) tradeType: TradeType.EXACT_OUTPUT,
}) inputCurrencyId: currencyId(trade.inputAmount.currency),
}) maximumInputCurrencyAmountRaw: trade.maximumAmountIn(allowedSlippage).quotient.toString(),
) outputCurrencyId: currencyId(trade.outputAmount.currency),
outputCurrencyAmountRaw: trade.outputAmount.quotient.toString(),
// a successful estimation is a bignumber gas estimate and the next call is also a bignumber gas estimate expectedInputCurrencyAmountRaw: trade.inputAmount.quotient.toString(),
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])
) )
return response.hash
})
}, [addTransaction, allowedSlippage, libCallback, trade])
// check if any calls errored with a recognizable error return {
if (!bestCallOption) { state,
const errorCalls = estimatedCalls.filter((call): call is FailedCall => 'error' in call) callback,
if (errorCalls.length > 0) throw errorCalls[errorCalls.length - 1].error 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 library
.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) => {
addTransaction(
response,
trade.tradeType === TradeType.EXACT_INPUT
? {
type: TransactionType.SWAP,
tradeType: TradeType.EXACT_INPUT,
inputCurrencyId: currencyId(trade.inputAmount.currency),
inputCurrencyAmountRaw: trade.inputAmount.quotient.toString(),
expectedOutputCurrencyAmountRaw: trade.outputAmount.quotient.toString(),
outputCurrencyId: currencyId(trade.outputAmount.currency),
minimumOutputCurrencyAmountRaw: trade.minimumAmountOut(allowedSlippage).quotient.toString(),
}
: {
type: TransactionType.SWAP,
tradeType: TradeType.EXACT_OUTPUT,
inputCurrencyId: currencyId(trade.inputAmount.currency),
maximumInputCurrencyAmountRaw: trade.maximumAmountIn(allowedSlippage).quotient.toString(),
outputCurrencyId: currencyId(trade.outputAmount.currency),
outputCurrencyAmountRaw: trade.outputAmount.quotient.toString(),
expectedInputCurrencyAmountRaw: trade.inputAmount.quotient.toString(),
}
)
return response.hash
})
.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)
throw new Error(t`Swap failed: ${swapErrorToUserReadableMessage(error)}`)
}
})
},
error: null,
}
}, [trade, library, account, chainId, recipient, recipientAddressOrName, swapCalls, addTransaction, allowedSlippage])
} }
import { CurrencyAmount, Token } from '@uniswap/sdk-core'
import { PermitInfo, PermitType, useERC20Permit } from './useERC20Permit'
import useTransactionDeadline from './useTransactionDeadline'
const REMOVE_V2_LIQUIDITY_PERMIT_INFO: PermitInfo = {
version: '1',
name: 'Uniswap V2',
type: PermitType.AMOUNT,
}
export function useV2LiquidityTokenPermit(
liquidityAmount: CurrencyAmount<Token> | null | undefined,
spender: string | null | undefined
) {
const transactionDeadline = useTransactionDeadline()
return useERC20Permit(liquidityAmount, spender, transactionDeadline, REMOVE_V2_LIQUIDITY_PERMIT_INFO)
}
import { BigNumber } from '@ethersproject/bignumber'
import { Trans } from '@lingui/macro' import { Trans } from '@lingui/macro'
import { Token } from '@uniswap/sdk-core' import { Token } from '@uniswap/sdk-core'
import { CHAIN_INFO } from 'constants/chainInfo' import { CHAIN_INFO } from 'constants/chainInfo'
import useCurrentBlockTimestamp from 'hooks/useCurrentBlockTimestamp'
import { useERC20PermitFromTrade } from 'hooks/useERC20Permit'
import { useAtomValue } from 'jotai/utils'
import { useSwapInfo } from 'lib/hooks/swap' import { useSwapInfo } from 'lib/hooks/swap'
import useSwapApproval, { import useSwapApproval, {
ApprovalState, ApprovalState,
useSwapApprovalOptimizedTrade, useSwapApprovalOptimizedTrade,
useSwapRouterAddress, useSwapRouterAddress,
} from 'lib/hooks/swap/useSwapApproval' } from 'lib/hooks/swap/useSwapApproval'
import { useSwapCallback } from 'lib/hooks/swap/useSwapCallback'
import { useAddTransaction } from 'lib/hooks/transactions' import { useAddTransaction } from 'lib/hooks/transactions'
import { usePendingApproval } from 'lib/hooks/transactions' import { usePendingApproval } from 'lib/hooks/transactions'
import useActiveWeb3React from 'lib/hooks/useActiveWeb3React' import useActiveWeb3React from 'lib/hooks/useActiveWeb3React'
import { Link, Spinner } from 'lib/icons' import { Link, Spinner } from 'lib/icons'
import { transactionTtlAtom } from 'lib/state/settings'
import { Field } from 'lib/state/swap' import { Field } from 'lib/state/swap'
import { TransactionType } from 'lib/state/transactions' import { TransactionType } from 'lib/state/transactions'
import styled from 'lib/theme' import styled from 'lib/theme'
...@@ -35,7 +41,8 @@ function useIsPendingApproval(token?: Token, spender?: string): boolean { ...@@ -35,7 +41,8 @@ function useIsPendingApproval(token?: Token, spender?: string): boolean {
} }
export default function SwapButton({ disabled }: SwapButtonProps) { export default function SwapButton({ disabled }: SwapButtonProps) {
const { chainId } = useActiveWeb3React() const { account, chainId } = useActiveWeb3React()
const { const {
trade, trade,
allowedSlippage, allowedSlippage,
...@@ -103,9 +110,35 @@ export default function SwapButton({ disabled }: SwapButtonProps) { ...@@ -103,9 +110,35 @@ export default function SwapButton({ disabled }: SwapButtonProps) {
return { disabled: true } return { disabled: true }
}, [approval, approvalHash, chainId, disabled, inputCurrencyAmount, inputCurrencyBalance]) }, [approval, approvalHash, chainId, disabled, inputCurrencyAmount, inputCurrencyBalance])
// @TODO(ianlapham): connect deadline from state instead of passing undefined.
const { signatureData } = useERC20PermitFromTrade(optimizedTrade, allowedSlippage, undefined)
const currentBlockTimestamp = useCurrentBlockTimestamp()
const userDeadline = useAtomValue(transactionTtlAtom)
const deadline = currentBlockTimestamp?.add(BigNumber.from(userDeadline))
// the callback to execute the swap
const { callback: swapCallback } = useSwapCallback(
optimizedTrade,
allowedSlippage,
account ?? null,
signatureData,
deadline
)
//@TODO(ianlapham): add a loading state, process errors
const onConfirm = useCallback(() => { const onConfirm = useCallback(() => {
// TODO(zzmp): Transact the trade. swapCallback?.()
}, []) .then((transactionResponse) => {
// TODO(ianlapham): Add the swap tx to transactionsAtom
// TODO(ianlapham): Add the pending swap tx to a new swap state
console.log(transactionResponse)
})
.catch((error) => {
//@TODO(ianlapham): add error handling
console.log(error)
})
}, [swapCallback])
return ( return (
<> <>
......
import { BigNumber } from '@ethersproject/bignumber'
import { TransactionResponse, Web3Provider } from '@ethersproject/providers'
// eslint-disable-next-line no-restricted-imports
import { t, Trans } from '@lingui/macro'
import { Trade } from '@uniswap/router-sdk'
import { Currency, TradeType } from '@uniswap/sdk-core'
import { Trade as V2Trade } from '@uniswap/v2-sdk'
import { Trade as V3Trade } from '@uniswap/v3-sdk'
import { useMemo } from 'react'
import { calculateGasMargin } from 'utils/calculateGasMargin'
import isZero from 'utils/isZero'
import { swapErrorToUserReadableMessage } from 'utils/swapErrorToUserReadableMessage'
type AnyTrade =
| V2Trade<Currency, Currency, TradeType>
| V3Trade<Currency, Currency, TradeType>
| Trade<Currency, Currency, TradeType>
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
}
// 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,
library: Web3Provider | undefined,
trade: AnyTrade | undefined, // trade to execute, required
swapCalls: SwapCall[]
): { callback: null | (() => Promise<TransactionResponse>) } {
return useMemo(() => {
if (!trade || !library || !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 library
.estimateGas(tx)
.then((gasEstimate) => {
return {
call,
gasEstimate,
}
})
.catch((gasError) => {
console.debug('Gas estimate failed, trying eth_call to extract error', call)
return library
.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 library
.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) => {
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)
throw new Error(t`Swap failed: ${swapErrorToUserReadableMessage(error)}`)
}
})
},
}
}, [account, chainId, library, swapCalls, trade])
}
// eslint-disable-next-line no-restricted-imports
import { BigNumber } from '@ethersproject/bignumber'
import { TransactionResponse } from '@ethersproject/providers'
import { Trans } from '@lingui/macro'
import { Percent } from '@uniswap/sdk-core'
import useActiveWeb3React from 'hooks/useActiveWeb3React'
import useENS from 'hooks/useENS'
import { SignatureData } from 'hooks/useERC20Permit'
import { AnyTrade, useSwapCallArguments } from 'hooks/useSwapCallArguments'
import { ReactNode, useMemo } from 'react'
import useSendSwapTransaction from './useSendSwapTransaction'
export enum SwapCallbackState {
INVALID,
LOADING,
VALID,
}
// 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: AnyTrade | 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
): { state: SwapCallbackState; callback: null | (() => Promise<TransactionResponse>); error: ReactNode | null } {
const { account, chainId, library } = useActiveWeb3React()
const swapCalls = useSwapCallArguments(trade, allowedSlippage, recipientAddressOrName, signatureData, deadline)
const { callback } = useSendSwapTransaction(account, chainId, library, trade, swapCalls)
const { address: recipientAddress } = useENS(recipientAddressOrName)
const recipient = recipientAddressOrName === null ? account : recipientAddress
return useMemo(() => {
if (!trade || !library || !account || !chainId || !callback) {
return { state: SwapCallbackState.INVALID, callback: null, error: <Trans>Missing dependencies</Trans> }
}
if (!recipient) {
if (recipientAddressOrName !== null) {
return { state: SwapCallbackState.INVALID, callback: null, error: <Trans>Invalid recipient</Trans> }
} else {
return { state: SwapCallbackState.LOADING, callback: null, error: null }
}
}
return {
state: SwapCallbackState.VALID,
callback: async function onSwap(): Promise<TransactionResponse> {
return callback().then((response) => {
return response
})
},
error: null,
}
}, [trade, library, account, chainId, callback, recipient, recipientAddressOrName])
}
...@@ -14,7 +14,7 @@ interface Settings { ...@@ -14,7 +14,7 @@ interface Settings {
const initialSettings: Settings = { const initialSettings: Settings = {
maxSlippage: 'auto', maxSlippage: 'auto',
transactionTtl: undefined, transactionTtl: TRANSACTION_TTL_DEFAULT,
mockTogglable: true, mockTogglable: true,
clientSideRouter: false, clientSideRouter: false,
} }
......
...@@ -18,6 +18,7 @@ import useCurrentBlockTimestamp from 'hooks/useCurrentBlockTimestamp' ...@@ -18,6 +18,7 @@ import useCurrentBlockTimestamp from 'hooks/useCurrentBlockTimestamp'
import { PoolState, usePool } from 'hooks/usePools' import { PoolState, usePool } from 'hooks/usePools'
import useTheme from 'hooks/useTheme' import useTheme from 'hooks/useTheme'
import useTransactionDeadline from 'hooks/useTransactionDeadline' import useTransactionDeadline from 'hooks/useTransactionDeadline'
import { useV2LiquidityTokenPermit } from 'hooks/useV2LiquidityTokenPermit'
import JSBI from 'jsbi' import JSBI from 'jsbi'
import { NEVER_RELOAD, useSingleCallResult } from 'lib/hooks/multicall' import { NEVER_RELOAD, useSingleCallResult } from 'lib/hooks/multicall'
import { ReactNode, useCallback, useEffect, useMemo, useState } from 'react' import { ReactNode, useCallback, useEffect, useMemo, useState } from 'react'
...@@ -41,7 +42,6 @@ import { V2_FACTORY_ADDRESSES } from '../../constants/addresses' ...@@ -41,7 +42,6 @@ import { V2_FACTORY_ADDRESSES } from '../../constants/addresses'
import { WRAPPED_NATIVE_CURRENCY } from '../../constants/tokens' import { WRAPPED_NATIVE_CURRENCY } from '../../constants/tokens'
import { useToken } from '../../hooks/Tokens' import { useToken } from '../../hooks/Tokens'
import { usePairContract, useV2MigratorContract } from '../../hooks/useContract' import { usePairContract, useV2MigratorContract } from '../../hooks/useContract'
import { useV2LiquidityTokenPermit } from '../../hooks/useERC20Permit'
import useIsArgentWallet from '../../hooks/useIsArgentWallet' import useIsArgentWallet from '../../hooks/useIsArgentWallet'
import { useTotalSupply } from '../../hooks/useTotalSupply' import { useTotalSupply } from '../../hooks/useTotalSupply'
import { TransactionType } from '../../state/transactions/actions' import { TransactionType } from '../../state/transactions/actions'
......
...@@ -4,6 +4,7 @@ import { TransactionResponse } from '@ethersproject/providers' ...@@ -4,6 +4,7 @@ import { TransactionResponse } from '@ethersproject/providers'
import { Trans } from '@lingui/macro' import { Trans } from '@lingui/macro'
import { Currency, Percent } from '@uniswap/sdk-core' import { Currency, Percent } from '@uniswap/sdk-core'
import useActiveWeb3React from 'hooks/useActiveWeb3React' import useActiveWeb3React from 'hooks/useActiveWeb3React'
import { useV2LiquidityTokenPermit } from 'hooks/useV2LiquidityTokenPermit'
import { useCallback, useContext, useMemo, useState } from 'react' import { useCallback, useContext, useMemo, useState } from 'react'
import { ArrowDown, Plus } from 'react-feather' import { ArrowDown, Plus } from 'react-feather'
import ReactGA from 'react-ga' import ReactGA from 'react-ga'
...@@ -28,7 +29,6 @@ import { useCurrency } from '../../hooks/Tokens' ...@@ -28,7 +29,6 @@ import { useCurrency } from '../../hooks/Tokens'
import { ApprovalState, useApproveCallback } from '../../hooks/useApproveCallback' import { ApprovalState, useApproveCallback } from '../../hooks/useApproveCallback'
import { usePairContract, useV2RouterContract } from '../../hooks/useContract' import { usePairContract, useV2RouterContract } from '../../hooks/useContract'
import useDebouncedChangeHandler from '../../hooks/useDebouncedChangeHandler' import useDebouncedChangeHandler from '../../hooks/useDebouncedChangeHandler'
import { useV2LiquidityTokenPermit } from '../../hooks/useERC20Permit'
import useTransactionDeadline from '../../hooks/useTransactionDeadline' import useTransactionDeadline from '../../hooks/useTransactionDeadline'
import { useWalletModalToggle } from '../../state/application/hooks' import { useWalletModalToggle } from '../../state/application/hooks'
import { Field } from '../../state/burn/actions' import { Field } from '../../state/burn/actions'
......
...@@ -8,6 +8,8 @@ import SwapDetailsDropdown from 'components/swap/SwapDetailsDropdown' ...@@ -8,6 +8,8 @@ import SwapDetailsDropdown from 'components/swap/SwapDetailsDropdown'
import UnsupportedCurrencyFooter from 'components/swap/UnsupportedCurrencyFooter' import UnsupportedCurrencyFooter from 'components/swap/UnsupportedCurrencyFooter'
import { MouseoverTooltip } from 'components/Tooltip' import { MouseoverTooltip } from 'components/Tooltip'
import useActiveWeb3React from 'hooks/useActiveWeb3React' import useActiveWeb3React from 'hooks/useActiveWeb3React'
import { useSwapCallback } from 'hooks/useSwapCallback'
import useTransactionDeadline from 'hooks/useTransactionDeadline'
import JSBI from 'jsbi' import JSBI from 'jsbi'
import { useCallback, useContext, useEffect, useMemo, useState } from 'react' import { useCallback, useContext, useEffect, useMemo, useState } from 'react'
import { ArrowDown, CheckCircle, HelpCircle } from 'react-feather' import { ArrowDown, CheckCircle, HelpCircle } from 'react-feather'
...@@ -37,7 +39,6 @@ import useENSAddress from '../../hooks/useENSAddress' ...@@ -37,7 +39,6 @@ import useENSAddress from '../../hooks/useENSAddress'
import { useERC20PermitFromTrade, UseERC20PermitState } from '../../hooks/useERC20Permit' import { useERC20PermitFromTrade, UseERC20PermitState } from '../../hooks/useERC20Permit'
import useIsArgentWallet from '../../hooks/useIsArgentWallet' import useIsArgentWallet from '../../hooks/useIsArgentWallet'
import { useIsSwapUnsupported } from '../../hooks/useIsSwapUnsupported' import { useIsSwapUnsupported } from '../../hooks/useIsSwapUnsupported'
import { useSwapCallback } from '../../hooks/useSwapCallback'
import { useUSDCValue } from '../../hooks/useUSDCPrice' import { useUSDCValue } from '../../hooks/useUSDCPrice'
import useWrapCallback, { WrapErrorText, WrapType } from '../../hooks/useWrapCallback' import useWrapCallback, { WrapErrorText, WrapType } from '../../hooks/useWrapCallback'
import { useWalletModalToggle } from '../../state/application/hooks' import { useWalletModalToggle } from '../../state/application/hooks'
...@@ -204,11 +205,12 @@ export default function Swap({ history }: RouteComponentProps) { ...@@ -204,11 +205,12 @@ export default function Swap({ history }: RouteComponentProps) {
// check whether the user has approved the router on the input token // check whether the user has approved the router on the input token
const [approvalState, approveCallback] = useApproveCallbackFromTrade(approvalOptimizedTrade, allowedSlippage) const [approvalState, approveCallback] = useApproveCallbackFromTrade(approvalOptimizedTrade, allowedSlippage)
const transactionDeadline = useTransactionDeadline()
const { const {
state: signatureState, state: signatureState,
signatureData, signatureData,
gatherPermitSignature, gatherPermitSignature,
} = useERC20PermitFromTrade(approvalOptimizedTrade, allowedSlippage) } = useERC20PermitFromTrade(approvalOptimizedTrade, allowedSlippage, transactionDeadline)
const handleApprove = useCallback(async () => { const handleApprove = useCallback(async () => {
if (signatureState === UseERC20PermitState.NOT_SIGNED && gatherPermitSignature) { if (signatureState === UseERC20PermitState.NOT_SIGNED && gatherPermitSignature) {
......
import { Trans } from '@lingui/macro'
import { ReactNode } from 'react'
/**
* This is hacking out the revert reason from the ethers provider thrown error however it can.
* This object seems to be undocumented by ethers.
* @param error an error from the ethers provider
*/
export function swapErrorToUserReadableMessage(error: any): ReactNode {
let reason: string | undefined
while (Boolean(error)) {
reason = error.reason ?? error.message ?? reason
error = error.error ?? error.data?.originalError
}
if (reason?.indexOf('execution reverted: ') === 0) reason = reason.substr('execution reverted: '.length)
switch (reason) {
case 'UniswapV2Router: EXPIRED':
return (
<Trans>
The transaction could not be sent because the deadline has passed. Please check that your transaction deadline
is not too low.
</Trans>
)
case 'UniswapV2Router: INSUFFICIENT_OUTPUT_AMOUNT':
case 'UniswapV2Router: EXCESSIVE_INPUT_AMOUNT':
return (
<Trans>
This transaction will not succeed either due to price movement or fee on transfer. Try increasing your
slippage tolerance.
</Trans>
)
case 'TransferHelper: TRANSFER_FROM_FAILED':
return <Trans>The input token cannot be transferred. There may be an issue with the input token.</Trans>
case 'UniswapV2: TRANSFER_FAILED':
return <Trans>The output token cannot be transferred. There may be an issue with the output token.</Trans>
case 'UniswapV2: K':
return (
<Trans>
The Uniswap invariant x*y=k was not satisfied by the swap. This usually means one of the tokens you are
swapping incorporates custom behavior on transfer.
</Trans>
)
case 'Too little received':
case 'Too much requested':
case 'STF':
return (
<Trans>
This transaction will not succeed due to price movement. Try increasing your slippage tolerance. Note: fee on
transfer and rebase tokens are incompatible with Uniswap V3.
</Trans>
)
case 'TF':
return (
<Trans>
The output token cannot be transferred. There may be an issue with the output token. Note: fee on transfer and
rebase tokens are incompatible with Uniswap V3.
</Trans>
)
default:
if (reason?.indexOf('undefined is not an object') !== -1) {
console.error(error, reason)
return (
<Trans>
An error occurred when trying to execute this swap. You may need to increase your slippage tolerance. If
that does not work, there may be an incompatibility with the token you are trading. Note: fee on transfer
and rebase tokens are incompatible with Uniswap V3.
</Trans>
)
}
return (
<Trans>
Unknown error{reason ? `: "${reason}"` : ''}. Try increasing your slippage tolerance. Note: fee on transfer
and rebase tokens are incompatible with Uniswap V3.
</Trans>
)
}
}
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