Commit e6676154 authored by Noah Zinsmeister's avatar Noah Zinsmeister Committed by GitHub

support proposed new FoT-capable methods (#866)

* support new FoT-capable methods

short-circuit modal footer rendering if no trade

improve clarity of tx modal flow

* update router address + ABI
parent 4ab61fae
...@@ -10,8 +10,8 @@ import { ButtonPrimary } from '../Button' ...@@ -10,8 +10,8 @@ import { ButtonPrimary } from '../Button'
import { AutoColumn, ColumnCenter } from '../Column' import { AutoColumn, ColumnCenter } from '../Column'
import Circle from '../../assets/images/blue-loader.svg' import Circle from '../../assets/images/blue-loader.svg'
import { useActiveWeb3React } from '../../hooks'
import { getEtherscanLink } from '../../utils' import { getEtherscanLink } from '../../utils'
import { useActiveWeb3React } from '../../hooks'
const Wrapper = styled.div` const Wrapper = styled.div`
width: 100%; width: 100%;
...@@ -42,7 +42,6 @@ interface ConfirmationModalProps { ...@@ -42,7 +42,6 @@ interface ConfirmationModalProps {
topContent: () => React.ReactChild topContent: () => React.ReactChild
bottomContent: () => React.ReactChild bottomContent: () => React.ReactChild
attemptingTxn: boolean attemptingTxn: boolean
pendingConfirmation: boolean
pendingText: string pendingText: string
title?: string title?: string
} }
...@@ -50,33 +49,22 @@ interface ConfirmationModalProps { ...@@ -50,33 +49,22 @@ interface ConfirmationModalProps {
export default function ConfirmationModal({ export default function ConfirmationModal({
isOpen, isOpen,
onDismiss, onDismiss,
hash,
topContent, topContent,
bottomContent, bottomContent,
attemptingTxn, attemptingTxn,
pendingConfirmation, hash,
pendingText, pendingText,
title = '' title = ''
}: ConfirmationModalProps) { }: ConfirmationModalProps) {
const { chainId } = useActiveWeb3React() const { chainId } = useActiveWeb3React()
const theme = useContext(ThemeContext) const theme = useContext(ThemeContext)
return ( const transactionBroadcast = !!hash
<Modal isOpen={isOpen} onDismiss={onDismiss} maxHeight={90}>
{!attemptingTxn ? ( // waiting for user to confirm/reject tx _or_ showing info on a tx that has been broadcast
<Wrapper> if (attemptingTxn || transactionBroadcast) {
<Section> return (
<RowBetween> <Modal isOpen={isOpen} onDismiss={onDismiss} maxHeight={90}>
<Text fontWeight={500} fontSize={20}>
{title}
</Text>
<CloseIcon onClick={onDismiss} />
</RowBetween>
{topContent()}
</Section>
<BottomSection gap="12px">{bottomContent()}</BottomSection>
</Wrapper>
) : (
<Wrapper> <Wrapper>
<Section> <Section>
<RowBetween> <RowBetween>
...@@ -84,22 +72,23 @@ export default function ConfirmationModal({ ...@@ -84,22 +72,23 @@ export default function ConfirmationModal({
<CloseIcon onClick={onDismiss} /> <CloseIcon onClick={onDismiss} />
</RowBetween> </RowBetween>
<ConfirmedIcon> <ConfirmedIcon>
{pendingConfirmation ? ( {transactionBroadcast ? (
<CustomLightSpinner src={Circle} alt="loader" size={'90px'} />
) : (
<ArrowUpCircle strokeWidth={0.5} size={90} color={theme.primary1} /> <ArrowUpCircle strokeWidth={0.5} size={90} color={theme.primary1} />
) : (
<CustomLightSpinner src={Circle} alt="loader" size={'90px'} />
)} )}
</ConfirmedIcon> </ConfirmedIcon>
<AutoColumn gap="12px" justify={'center'}> <AutoColumn gap="12px" justify={'center'}>
<Text fontWeight={500} fontSize={20}> <Text fontWeight={500} fontSize={20}>
{!pendingConfirmation ? 'Transaction Submitted' : 'Waiting For Confirmation'} {transactionBroadcast ? 'Transaction Submitted' : 'Waiting For Confirmation'}
</Text> </Text>
<AutoColumn gap="12px" justify={'center'}> <AutoColumn gap="12px" justify={'center'}>
<Text fontWeight={600} fontSize={14} color="" textAlign="center"> <Text fontWeight={600} fontSize={14} color="" textAlign="center">
{pendingText} {pendingText}
</Text> </Text>
</AutoColumn> </AutoColumn>
{!pendingConfirmation && (
{transactionBroadcast ? (
<> <>
<ExternalLink href={getEtherscanLink(chainId, hash, 'transaction')}> <ExternalLink href={getEtherscanLink(chainId, hash, 'transaction')}>
<Text fontWeight={500} fontSize={14} color={theme.primary1}> <Text fontWeight={500} fontSize={14} color={theme.primary1}>
...@@ -112,9 +101,7 @@ export default function ConfirmationModal({ ...@@ -112,9 +101,7 @@ export default function ConfirmationModal({
</Text> </Text>
</ButtonPrimary> </ButtonPrimary>
</> </>
)} ) : (
{pendingConfirmation && (
<Text fontSize={12} color="#565A69" textAlign="center"> <Text fontSize={12} color="#565A69" textAlign="center">
Confirm this transaction in your wallet Confirm this transaction in your wallet
</Text> </Text>
...@@ -122,7 +109,25 @@ export default function ConfirmationModal({ ...@@ -122,7 +109,25 @@ export default function ConfirmationModal({
</AutoColumn> </AutoColumn>
</Section> </Section>
</Wrapper> </Wrapper>
)} </Modal>
)
}
// confirmation screen
return (
<Modal isOpen={isOpen} onDismiss={onDismiss} maxHeight={90}>
<Wrapper>
<Section>
<RowBetween>
<Text fontWeight={500} fontSize={20}>
{title}
</Text>
<CloseIcon onClick={onDismiss} />
</RowBetween>
{topContent()}
</Section>
<BottomSection gap="12px">{bottomContent()}</BottomSection>
</Wrapper>
</Modal> </Modal>
) )
} }
...@@ -37,6 +37,11 @@ export default function SwapModalFooter({ ...@@ -37,6 +37,11 @@ export default function SwapModalFooter({
confirmText: string confirmText: string
}) { }) {
const theme = useContext(ThemeContext) const theme = useContext(ThemeContext)
if (!trade) {
return null
}
return ( return (
<> <>
<AutoColumn gap="0px"> <AutoColumn gap="0px">
......
...@@ -11,29 +11,30 @@ import TokenLogo from '../TokenLogo' ...@@ -11,29 +11,30 @@ import TokenLogo from '../TokenLogo'
import { TruncatedText } from './styleds' import { TruncatedText } from './styleds'
export default function SwapModalHeader({ export default function SwapModalHeader({
formattedAmounts,
tokens, tokens,
formattedAmounts,
slippageAdjustedAmounts, slippageAdjustedAmounts,
priceImpactSeverity, priceImpactSeverity,
independentField independentField
}: { }: {
formattedAmounts?: { [field in Field]?: string } tokens: { [field in Field]?: Token }
tokens?: { [field in Field]?: Token } formattedAmounts: { [field in Field]?: string }
slippageAdjustedAmounts?: { [field in Field]?: TokenAmount } slippageAdjustedAmounts: { [field in Field]?: TokenAmount }
priceImpactSeverity: number priceImpactSeverity: number
independentField: Field independentField: Field
}) { }) {
const theme = useContext(ThemeContext) const theme = useContext(ThemeContext)
return ( return (
<AutoColumn gap={'md'} style={{ marginTop: '20px' }}> <AutoColumn gap={'md'} style={{ marginTop: '20px' }}>
<RowBetween align="flex-end"> <RowBetween align="flex-end">
<TruncatedText fontSize={24} fontWeight={500}> <TruncatedText fontSize={24} fontWeight={500}>
{!!formattedAmounts[Field.INPUT] && formattedAmounts[Field.INPUT]} {formattedAmounts[Field.INPUT]}
</TruncatedText> </TruncatedText>
<RowFixed gap="4px"> <RowFixed gap="4px">
<TokenLogo address={tokens[Field.INPUT]?.address} size={'24px'} /> <TokenLogo address={tokens[Field.INPUT]?.address} size={'24px'} />
<Text fontSize={24} fontWeight={500} style={{ marginLeft: '10px' }}> <Text fontSize={24} fontWeight={500} style={{ marginLeft: '10px' }}>
{tokens[Field.INPUT]?.symbol || ''} {tokens[Field.INPUT]?.symbol}
</Text> </Text>
</RowFixed> </RowFixed>
</RowBetween> </RowBetween>
...@@ -42,12 +43,12 @@ export default function SwapModalHeader({ ...@@ -42,12 +43,12 @@ export default function SwapModalHeader({
</RowFixed> </RowFixed>
<RowBetween align="flex-end"> <RowBetween align="flex-end">
<TruncatedText fontSize={24} fontWeight={500} color={priceImpactSeverity > 2 ? theme.red1 : ''}> <TruncatedText fontSize={24} fontWeight={500} color={priceImpactSeverity > 2 ? theme.red1 : ''}>
{!!formattedAmounts[Field.OUTPUT] && formattedAmounts[Field.OUTPUT]} {formattedAmounts[Field.OUTPUT]}
</TruncatedText> </TruncatedText>
<RowFixed gap="4px"> <RowFixed gap="4px">
<TokenLogo address={tokens[Field.OUTPUT]?.address} size={'24px'} /> <TokenLogo address={tokens[Field.OUTPUT]?.address} size={'24px'} />
<Text fontSize={24} fontWeight={500} style={{ marginLeft: '10px' }}> <Text fontSize={24} fontWeight={500} style={{ marginLeft: '10px' }}>
{tokens[Field.OUTPUT]?.symbol || ''} {tokens[Field.OUTPUT]?.symbol}
</Text> </Text>
</RowFixed> </RowFixed>
</RowBetween> </RowBetween>
...@@ -56,7 +57,7 @@ export default function SwapModalHeader({ ...@@ -56,7 +57,7 @@ export default function SwapModalHeader({
<TYPE.italic textAlign="left" style={{ width: '100%' }}> <TYPE.italic textAlign="left" style={{ width: '100%' }}>
{`Output is estimated. You will receive at least `} {`Output is estimated. You will receive at least `}
<b> <b>
{slippageAdjustedAmounts[Field.OUTPUT]?.toSignificant(6)} {tokens[Field.OUTPUT]?.symbol}{' '} {slippageAdjustedAmounts[Field.OUTPUT]?.toSignificant(6)} {tokens[Field.OUTPUT]?.symbol}
</b> </b>
{' or the transaction will revert.'} {' or the transaction will revert.'}
</TYPE.italic> </TYPE.italic>
......
...@@ -2,7 +2,7 @@ import { ChainId, JSBI, Percent, Token, WETH, Pair, TokenAmount } from '@uniswap ...@@ -2,7 +2,7 @@ import { ChainId, JSBI, Percent, Token, WETH, Pair, TokenAmount } from '@uniswap
import { fortmatic, injected, portis, walletconnect, walletlink } from '../connectors' import { fortmatic, injected, portis, walletconnect, walletlink } from '../connectors'
export const ROUTER_ADDRESS = '0xf164fC0Ec4E93095b804a4795bBe1e041497b92a' export const ROUTER_ADDRESS = '0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D'
// used to construct intermediary pairs for trading // used to construct intermediary pairs for trading
export const BASES_TO_CHECK_TRADES_AGAINST: { readonly [chainId in ChainId]: Token[] } = { export const BASES_TO_CHECK_TRADES_AGAINST: { readonly [chainId in ChainId]: Token[] } = {
......
...@@ -90,11 +90,13 @@ export function useSwapCallback( ...@@ -90,11 +90,13 @@ export function useSwapCallback(
chainId as ChainId chainId as ChainId
) )
let estimate, method: Function, args: Array<string | string[] | number>, value: BigNumber | null // let estimate: Function, method: Function,
let methodNames: string[],
args: Array<string | string[] | number>,
value: BigNumber | null = null
switch (swapType) { switch (swapType) {
case SwapType.EXACT_TOKENS_FOR_TOKENS: case SwapType.EXACT_TOKENS_FOR_TOKENS:
estimate = routerContract.estimateGas.swapExactTokensForTokens methodNames = ['swapExactTokensForTokens', 'swapExactTokensForTokensSupportingFeeOnTransferTokens']
method = routerContract.swapExactTokensForTokens
args = [ args = [
slippageAdjustedInput.raw.toString(), slippageAdjustedInput.raw.toString(),
slippageAdjustedOutput.raw.toString(), slippageAdjustedOutput.raw.toString(),
...@@ -102,11 +104,9 @@ export function useSwapCallback( ...@@ -102,11 +104,9 @@ export function useSwapCallback(
recipient, recipient,
deadlineFromNow deadlineFromNow
] ]
value = null
break break
case SwapType.TOKENS_FOR_EXACT_TOKENS: case SwapType.TOKENS_FOR_EXACT_TOKENS:
estimate = routerContract.estimateGas.swapTokensForExactTokens methodNames = ['swapTokensForExactTokens']
method = routerContract.swapTokensForExactTokens
args = [ args = [
slippageAdjustedOutput.raw.toString(), slippageAdjustedOutput.raw.toString(),
slippageAdjustedInput.raw.toString(), slippageAdjustedInput.raw.toString(),
...@@ -114,17 +114,14 @@ export function useSwapCallback( ...@@ -114,17 +114,14 @@ export function useSwapCallback(
recipient, recipient,
deadlineFromNow deadlineFromNow
] ]
value = null
break break
case SwapType.EXACT_ETH_FOR_TOKENS: case SwapType.EXACT_ETH_FOR_TOKENS:
estimate = routerContract.estimateGas.swapExactETHForTokens methodNames = ['swapExactETHForTokens', 'swapExactETHForTokensSupportingFeeOnTransferTokens']
method = routerContract.swapExactETHForTokens
args = [slippageAdjustedOutput.raw.toString(), path, recipient, deadlineFromNow] args = [slippageAdjustedOutput.raw.toString(), path, recipient, deadlineFromNow]
value = BigNumber.from(slippageAdjustedInput.raw.toString()) value = BigNumber.from(slippageAdjustedInput.raw.toString())
break break
case SwapType.TOKENS_FOR_EXACT_ETH: case SwapType.TOKENS_FOR_EXACT_ETH:
estimate = routerContract.estimateGas.swapTokensForExactETH methodNames = ['swapTokensForExactETH']
method = routerContract.swapTokensForExactETH
args = [ args = [
slippageAdjustedOutput.raw.toString(), slippageAdjustedOutput.raw.toString(),
slippageAdjustedInput.raw.toString(), slippageAdjustedInput.raw.toString(),
...@@ -132,11 +129,9 @@ export function useSwapCallback( ...@@ -132,11 +129,9 @@ export function useSwapCallback(
recipient, recipient,
deadlineFromNow deadlineFromNow
] ]
value = null
break break
case SwapType.EXACT_TOKENS_FOR_ETH: case SwapType.EXACT_TOKENS_FOR_ETH:
estimate = routerContract.estimateGas.swapExactTokensForETH methodNames = ['swapExactTokensForETH', 'swapExactTokensForETHSupportingFeeOnTransferTokens']
method = routerContract.swapExactTokensForETH
args = [ args = [
slippageAdjustedInput.raw.toString(), slippageAdjustedInput.raw.toString(),
slippageAdjustedOutput.raw.toString(), slippageAdjustedOutput.raw.toString(),
...@@ -144,58 +139,99 @@ export function useSwapCallback( ...@@ -144,58 +139,99 @@ export function useSwapCallback(
recipient, recipient,
deadlineFromNow deadlineFromNow
] ]
value = null
break break
case SwapType.ETH_FOR_EXACT_TOKENS: case SwapType.ETH_FOR_EXACT_TOKENS:
estimate = routerContract.estimateGas.swapETHForExactTokens methodNames = ['swapETHForExactTokens']
method = routerContract.swapETHForExactTokens
args = [slippageAdjustedOutput.raw.toString(), path, recipient, deadlineFromNow] args = [slippageAdjustedOutput.raw.toString(), path, recipient, deadlineFromNow]
value = BigNumber.from(slippageAdjustedInput.raw.toString()) value = BigNumber.from(slippageAdjustedInput.raw.toString())
break break
} }
return estimate(...args, value ? { value } : {}) const safeGasEstimates = await Promise.all(
.then(estimatedGasLimit => methodNames.map(methodName =>
method(...args, { routerContract.estimateGas[methodName](...args, value ? { value } : {})
...(value ? { value } : {}), .then(calculateGasMargin)
gasLimit: calculateGasMargin(estimatedGasLimit) .catch(error => {
}) console.error(`estimateGas failed for ${methodName}`, error)
)
.then(response => {
if (recipient === account) {
addTransaction(response, {
summary:
'Swap ' +
slippageAdjustedInput.toSignificant(3) +
' ' +
trade.inputAmount.token.symbol +
' for ' +
slippageAdjustedOutput.toSignificant(3) +
' ' +
trade.outputAmount.token.symbol
}) })
} else { )
addTransaction(response, { )
summary:
'Swap ' +
slippageAdjustedInput.toSignificant(3) +
' ' +
trade.inputAmount.token.symbol +
' for ' +
slippageAdjustedOutput.toSignificant(3) +
' ' +
trade.outputAmount.token.symbol +
' to ' +
(ensName ?? recipient)
})
}
return response.hash const indexOfSuccessfulEstimation = safeGasEstimates.findIndex(safeGasEstimate =>
}) BigNumber.isBigNumber(safeGasEstimate)
.catch(error => { )
console.error(`Swap or gas estimate failed`, error)
throw error // all estimations failed...
if (indexOfSuccessfulEstimation === -1) {
// if only 1 method exists, either:
// a) the token is doing something weird not related to FoT (e.g. enforcing a whitelist)
// b) the token is FoT and the user specified an exact output, which is not allowed
if (methodNames.length === 1) {
throw Error(
`An error occurred. If either of the tokens you're swapping take a fee on transfer, you must specify an exact input amount.`
)
}
// if 2 methods exists, either:
// a) the token is doing something weird not related to FoT (e.g. enforcing a whitelist)
// b) the token is FoT and is taking more than the specified slippage
else if (methodNames.length === 2) {
throw Error(
`An error occurred. If either of the tokens you're swapping take a fee on transfer, you must specify a slippage tolerance higher than the fee.`
)
} else {
throw Error('This transaction would fail. Please contact support.')
}
} else {
const methodName = methodNames[indexOfSuccessfulEstimation]
const safeGasEstimate = safeGasEstimates[indexOfSuccessfulEstimation]
return routerContract[methodName](...args, {
gasLimit: safeGasEstimate,
...(value ? { value } : {})
}) })
.then((response: any) => {
if (recipient === account) {
addTransaction(response, {
summary:
'Swap ' +
slippageAdjustedInput.toSignificant(3) +
' ' +
trade.inputAmount.token.symbol +
' for ' +
slippageAdjustedOutput.toSignificant(3) +
' ' +
trade.outputAmount.token.symbol
})
} else {
addTransaction(response, {
summary:
'Swap ' +
slippageAdjustedInput.toSignificant(3) +
' ' +
trade.inputAmount.token.symbol +
' for ' +
slippageAdjustedOutput.toSignificant(3) +
' ' +
trade.outputAmount.token.symbol +
' to ' +
(ensName ?? recipient)
})
}
return response.hash
})
.catch((error: any) => {
// if the user rejected the tx, pass this along
if (error?.code === 4001) {
throw error
}
// otherwise, the error was unexpected and we need to convey that
else {
console.error(`swap failed for ${methodName}`, error)
throw Error('An error occurred while swapping. Please contact support.')
}
})
}
} }
}, [account, allowedSlippage, addTransaction, chainId, deadline, inputAllowance, library, trade, ensName, recipient]) }, [account, allowedSlippage, addTransaction, chainId, deadline, inputAllowance, library, trade, ensName, recipient])
} }
...@@ -36,7 +36,7 @@ import { useApproveCallback, ApprovalState } from '../../hooks/useApproveCallbac ...@@ -36,7 +36,7 @@ import { useApproveCallback, ApprovalState } from '../../hooks/useApproveCallbac
import { useWalletModalToggle } from '../../state/application/hooks' import { useWalletModalToggle } from '../../state/application/hooks'
import AdvancedSwapDetailsDropdown from '../../components/swap/AdvancedSwapDetailsDropdown' import AdvancedSwapDetailsDropdown from '../../components/swap/AdvancedSwapDetailsDropdown'
export default function AddLiquidity({ match: { params }, history }: RouteComponentProps<{ tokens: string }>) { export default function AddLiquidity({ match: { params } }: RouteComponentProps<{ tokens: string }>) {
useDefaultsFromURLMatchParams(params) useDefaultsFromURLMatchParams(params)
const { account, chainId, library } = useActiveWeb3React() const { account, chainId, library } = useActiveWeb3React()
...@@ -64,13 +64,12 @@ export default function AddLiquidity({ match: { params }, history }: RouteCompon ...@@ -64,13 +64,12 @@ export default function AddLiquidity({ match: { params }, history }: RouteCompon
const isValid = !error const isValid = !error
// modal and loading // modal and loading
const [showConfirm, setShowConfirm] = useState<boolean>(false) const [showAdvanced, setShowAdvanced] = useState<boolean>(false) // toggling slippage, deadline, etc. on and off
const [showAdvanced, setShowAdvanced] = useState<boolean>(false) const [showConfirm, setShowConfirm] = useState<boolean>(false) // show confirmation modal
const [attemptingTxn, setAttemptingTxn] = useState<boolean>(false) // clicked confirm const [attemptingTxn, setAttemptingTxn] = useState<boolean>(false) // waiting for user confirmaion/rejection
const [pendingConfirmation, setPendingConfirmation] = useState<boolean>(true) // waiting for user confirmation
// txn values
const [txHash, setTxHash] = useState<string>('') const [txHash, setTxHash] = useState<string>('')
// tx parameters
const [deadline, setDeadline] = useState<number>(DEFAULT_DEADLINE_FROM_NOW) const [deadline, setDeadline] = useState<number>(DEFAULT_DEADLINE_FROM_NOW)
const [allowedSlippage, setAllowedSlippage] = useState<number>(INITIAL_ALLOWED_SLIPPAGE) const [allowedSlippage, setAllowedSlippage] = useState<number>(INITIAL_ALLOWED_SLIPPAGE)
...@@ -114,8 +113,6 @@ export default function AddLiquidity({ match: { params }, history }: RouteCompon ...@@ -114,8 +113,6 @@ export default function AddLiquidity({ match: { params }, history }: RouteCompon
const addTransaction = useTransactionAdder() const addTransaction = useTransactionAdder()
async function onAdd() { async function onAdd() {
setAttemptingTxn(true)
const router = getRouterContract(chainId, library, account) const router = getRouterContract(chainId, library, account)
const amountsMin = { const amountsMin = {
...@@ -155,12 +152,15 @@ export default function AddLiquidity({ match: { params }, history }: RouteCompon ...@@ -155,12 +152,15 @@ export default function AddLiquidity({ match: { params }, history }: RouteCompon
value = null value = null
} }
setAttemptingTxn(true)
await estimate(...args, value ? { value } : {}) await estimate(...args, value ? { value } : {})
.then(estimatedGasLimit => .then(estimatedGasLimit =>
method(...args, { method(...args, {
...(value ? { value } : {}), ...(value ? { value } : {}),
gasLimit: calculateGasMargin(estimatedGasLimit) gasLimit: calculateGasMargin(estimatedGasLimit)
}).then(response => { }).then(response => {
setAttemptingTxn(false)
addTransaction(response, { addTransaction(response, {
summary: summary:
'Add ' + 'Add ' +
...@@ -174,7 +174,6 @@ export default function AddLiquidity({ match: { params }, history }: RouteCompon ...@@ -174,7 +174,6 @@ export default function AddLiquidity({ match: { params }, history }: RouteCompon
}) })
setTxHash(response.hash) setTxHash(response.hash)
setPendingConfirmation(false)
ReactGA.event({ ReactGA.event({
category: 'Liquidity', category: 'Liquidity',
...@@ -183,12 +182,12 @@ export default function AddLiquidity({ match: { params }, history }: RouteCompon ...@@ -183,12 +182,12 @@ export default function AddLiquidity({ match: { params }, history }: RouteCompon
}) })
}) })
) )
.catch((e: Error) => { .catch(error => {
console.error(e)
setPendingConfirmation(true)
setAttemptingTxn(false) setAttemptingTxn(false)
setShowConfirm(false) // we only care if the error is something _other_ than the user rejected the tx
setShowAdvanced(false) if (error?.code !== 4001) {
console.error(error)
}
}) })
} }
...@@ -311,17 +310,15 @@ export default function AddLiquidity({ match: { params }, history }: RouteCompon ...@@ -311,17 +310,15 @@ export default function AddLiquidity({ match: { params }, history }: RouteCompon
<ConfirmationModal <ConfirmationModal
isOpen={showConfirm} isOpen={showConfirm}
onDismiss={() => { onDismiss={() => {
if (attemptingTxn) {
history.push('/pool')
return
}
setPendingConfirmation(true)
setAttemptingTxn(false)
setShowConfirm(false) setShowConfirm(false)
// if there was a tx hash, we want to clear the input
if (txHash) {
onUserInput(Field.TOKEN_A, '')
}
setTxHash('')
}} }}
attemptingTxn={attemptingTxn} attemptingTxn={attemptingTxn}
pendingConfirmation={pendingConfirmation} hash={txHash}
hash={txHash ? txHash : ''}
topContent={() => modalHeader()} topContent={() => modalHeader()}
bottomContent={modalBottom} bottomContent={modalBottom}
pendingText={pendingText} pendingText={pendingText}
......
...@@ -34,8 +34,9 @@ import { useDerivedBurnInfo, useBurnState } from '../../state/burn/hooks' ...@@ -34,8 +34,9 @@ import { useDerivedBurnInfo, useBurnState } from '../../state/burn/hooks'
import AdvancedSwapDetailsDropdown from '../../components/swap/AdvancedSwapDetailsDropdown' import AdvancedSwapDetailsDropdown from '../../components/swap/AdvancedSwapDetailsDropdown'
import { Field } from '../../state/burn/actions' import { Field } from '../../state/burn/actions'
import { useWalletModalToggle } from '../../state/application/hooks' import { useWalletModalToggle } from '../../state/application/hooks'
import { BigNumber } from '@ethersproject/bignumber'
export default function RemoveLiquidity({ match: { params }, history }: RouteComponentProps<{ tokens: string }>) { export default function RemoveLiquidity({ match: { params } }: RouteComponentProps<{ tokens: string }>) {
useDefaultsFromURLMatchParams(params) useDefaultsFromURLMatchParams(params)
const { account, chainId, library } = useActiveWeb3React() const { account, chainId, library } = useActiveWeb3React()
...@@ -51,14 +52,13 @@ export default function RemoveLiquidity({ match: { params }, history }: RouteCom ...@@ -51,14 +52,13 @@ export default function RemoveLiquidity({ match: { params }, history }: RouteCom
const isValid = !error const isValid = !error
// modal and loading // modal and loading
const [showConfirm, setShowConfirm] = useState<boolean>(false) const [showDetailed, setShowDetailed] = useState<boolean>(false) // toggling detailed view
const [showAdvanced, setShowAdvanced] = useState<boolean>(false) const [showAdvanced, setShowAdvanced] = useState<boolean>(false) // toggling slippage, deadline, etc. on and off
const [showDetailed, setShowDetailed] = useState<boolean>(false) const [showConfirm, setShowConfirm] = useState<boolean>(false) // show confirmation modal
const [attemptingTxn, setAttemptingTxn] = useState(false) // clicked confirm const [attemptingTxn, setAttemptingTxn] = useState<boolean>(false) // waiting for user confirmaion/rejection
const [pendingConfirmation, setPendingConfirmation] = useState(true) // waiting for
// txn values
const [txHash, setTxHash] = useState<string>('') const [txHash, setTxHash] = useState<string>('')
// tx parameters
const [deadline, setDeadline] = useState<number>(DEFAULT_DEADLINE_FROM_NOW) const [deadline, setDeadline] = useState<number>(DEFAULT_DEADLINE_FROM_NOW)
const [allowedSlippage, setAllowedSlippage] = useState<number>(INITIAL_ALLOWED_SLIPPAGE) const [allowedSlippage, setAllowedSlippage] = useState<number>(INITIAL_ALLOWED_SLIPPAGE)
...@@ -144,17 +144,9 @@ export default function RemoveLiquidity({ match: { params }, history }: RouteCom ...@@ -144,17 +144,9 @@ export default function RemoveLiquidity({ match: { params }, history }: RouteCom
}) })
} }
function resetModalState() {
setSignatureData(null)
setAttemptingTxn(false)
setPendingConfirmation(true)
}
// tx sending // tx sending
const addTransaction = useTransactionAdder() const addTransaction = useTransactionAdder()
async function onRemove() { async function onRemove() {
setAttemptingTxn(true)
const router = getRouterContract(chainId, library, account) const router = getRouterContract(chainId, library, account)
const amountsMin = { const amountsMin = {
...@@ -167,13 +159,12 @@ export default function RemoveLiquidity({ match: { params }, history }: RouteCom ...@@ -167,13 +159,12 @@ export default function RemoveLiquidity({ match: { params }, history }: RouteCom
const deadlineFromNow = Math.ceil(Date.now() / 1000) + deadline const deadlineFromNow = Math.ceil(Date.now() / 1000) + deadline
let estimate, method: Function, args: Array<string | string[] | number | boolean> let methodNames: string[], args: Array<string | string[] | number | boolean>
// we have approval, use normal remove liquidity // we have approval, use normal remove liquidity
if (approval === ApprovalState.APPROVED) { if (approval === ApprovalState.APPROVED) {
// removeLiquidityETH // removeLiquidityETH
if (oneTokenIsETH) { if (oneTokenIsETH) {
estimate = router.estimateGas.removeLiquidityETH methodNames = ['removeLiquidityETH', 'removeLiquidityETHSupportingFeeOnTransferTokens']
method = router.removeLiquidityETH
args = [ args = [
tokens[tokenBIsETH ? Field.TOKEN_A : Field.TOKEN_B].address, tokens[tokenBIsETH ? Field.TOKEN_A : Field.TOKEN_B].address,
parsedAmounts[Field.LIQUIDITY].raw.toString(), parsedAmounts[Field.LIQUIDITY].raw.toString(),
...@@ -185,8 +176,7 @@ export default function RemoveLiquidity({ match: { params }, history }: RouteCom ...@@ -185,8 +176,7 @@ export default function RemoveLiquidity({ match: { params }, history }: RouteCom
} }
// removeLiquidity // removeLiquidity
else { else {
estimate = router.estimateGas.removeLiquidity methodNames = ['removeLiquidity']
method = router.removeLiquidity
args = [ args = [
tokens[Field.TOKEN_A].address, tokens[Field.TOKEN_A].address,
tokens[Field.TOKEN_B].address, tokens[Field.TOKEN_B].address,
...@@ -202,8 +192,7 @@ export default function RemoveLiquidity({ match: { params }, history }: RouteCom ...@@ -202,8 +192,7 @@ export default function RemoveLiquidity({ match: { params }, history }: RouteCom
else if (signatureData !== null) { else if (signatureData !== null) {
// removeLiquidityETHWithPermit // removeLiquidityETHWithPermit
if (oneTokenIsETH) { if (oneTokenIsETH) {
estimate = router.estimateGas.removeLiquidityETHWithPermit methodNames = ['removeLiquidityETHWithPermit', 'removeLiquidityETHWithPermitSupportingFeeOnTransferTokens']
method = router.removeLiquidityETHWithPermit
args = [ args = [
tokens[tokenBIsETH ? Field.TOKEN_A : Field.TOKEN_B].address, tokens[tokenBIsETH ? Field.TOKEN_A : Field.TOKEN_B].address,
parsedAmounts[Field.LIQUIDITY].raw.toString(), parsedAmounts[Field.LIQUIDITY].raw.toString(),
...@@ -219,8 +208,7 @@ export default function RemoveLiquidity({ match: { params }, history }: RouteCom ...@@ -219,8 +208,7 @@ export default function RemoveLiquidity({ match: { params }, history }: RouteCom
} }
// removeLiquidityETHWithPermit // removeLiquidityETHWithPermit
else { else {
estimate = router.estimateGas.removeLiquidityWithPermit methodNames = ['removeLiquidityWithPermit']
method = router.removeLiquidityWithPermit
args = [ args = [
tokens[Field.TOKEN_A].address, tokens[Field.TOKEN_A].address,
tokens[Field.TOKEN_B].address, tokens[Field.TOKEN_B].address,
...@@ -236,14 +224,37 @@ export default function RemoveLiquidity({ match: { params }, history }: RouteCom ...@@ -236,14 +224,37 @@ export default function RemoveLiquidity({ match: { params }, history }: RouteCom
] ]
} }
} else { } else {
console.error('Attempting to confirm without approval or a signature.') console.error('Attempting to confirm without approval or a signature. Please contact support.')
} }
await estimate(...args) const safeGasEstimates = await Promise.all(
.then(estimatedGasLimit => methodNames.map(methodName =>
method(...args, { router.estimateGas[methodName](...args)
gasLimit: calculateGasMargin(estimatedGasLimit) .then(calculateGasMargin)
}).then(response => { .catch(error => {
console.error(`estimateGas failed for ${methodName}`, error)
})
)
)
const indexOfSuccessfulEstimation = safeGasEstimates.findIndex(safeGasEstimate =>
BigNumber.isBigNumber(safeGasEstimate)
)
// all estimations failed...
if (indexOfSuccessfulEstimation === -1) {
console.error('This transaction would fail. Please contact support.')
} else {
const methodName = methodNames[indexOfSuccessfulEstimation]
const safeGasEstimate = safeGasEstimates[indexOfSuccessfulEstimation]
setAttemptingTxn(true)
await router[methodName](...args, {
gasLimit: safeGasEstimate
})
.then(response => {
setAttemptingTxn(false)
addTransaction(response, { addTransaction(response, {
summary: summary:
'Remove ' + 'Remove ' +
...@@ -257,7 +268,6 @@ export default function RemoveLiquidity({ match: { params }, history }: RouteCom ...@@ -257,7 +268,6 @@ export default function RemoveLiquidity({ match: { params }, history }: RouteCom
}) })
setTxHash(response.hash) setTxHash(response.hash)
setPendingConfirmation(false)
ReactGA.event({ ReactGA.event({
category: 'Liquidity', category: 'Liquidity',
...@@ -265,13 +275,14 @@ export default function RemoveLiquidity({ match: { params }, history }: RouteCom ...@@ -265,13 +275,14 @@ export default function RemoveLiquidity({ match: { params }, history }: RouteCom
label: [tokens[Field.TOKEN_A]?.symbol, tokens[Field.TOKEN_B]?.symbol].join('/') label: [tokens[Field.TOKEN_A]?.symbol, tokens[Field.TOKEN_B]?.symbol].join('/')
}) })
}) })
) .catch(error => {
.catch(e => { setAttemptingTxn(false)
console.error(e) // we only care if the error is something _other_ than the user rejected the tx
resetModalState() if (error?.code !== 4001) {
setShowConfirm(false) console.error(error)
setShowAdvanced(false) }
}) })
}
} }
function modalHeader() { function modalHeader() {
...@@ -398,16 +409,15 @@ export default function RemoveLiquidity({ match: { params }, history }: RouteCom ...@@ -398,16 +409,15 @@ export default function RemoveLiquidity({ match: { params }, history }: RouteCom
<ConfirmationModal <ConfirmationModal
isOpen={showConfirm} isOpen={showConfirm}
onDismiss={() => { onDismiss={() => {
if (attemptingTxn) {
history.push('/pool')
return
}
resetModalState()
setShowConfirm(false) setShowConfirm(false)
setShowAdvanced(false) setSignatureData(null) // important that we clear signature data to avoid bad sigs
// if there was a tx hash, we want to clear the input
if (txHash) {
onUserInput(Field.LIQUIDITY_PERCENT, '0')
}
setTxHash('')
}} }}
attemptingTxn={attemptingTxn} attemptingTxn={attemptingTxn}
pendingConfirmation={pendingConfirmation}
hash={txHash ? txHash : ''} hash={txHash ? txHash : ''}
topContent={modalHeader} topContent={modalHeader}
bottomContent={modalBottom} bottomContent={modalBottom}
......
...@@ -74,13 +74,12 @@ export default function Send({ location: { search } }: RouteComponentProps) { ...@@ -74,13 +74,12 @@ export default function Send({ location: { search } }: RouteComponentProps) {
const dependentField: Field = independentField === Field.INPUT ? Field.OUTPUT : Field.INPUT const dependentField: Field = independentField === Field.INPUT ? Field.OUTPUT : Field.INPUT
// modal and loading // modal and loading
const [showConfirm, setShowConfirm] = useState<boolean>(false) const [showAdvanced, setShowAdvanced] = useState<boolean>(false) // toggling slippage, deadline, etc. on and off
const [showAdvanced, setShowAdvanced] = useState<boolean>(false) const [showConfirm, setShowConfirm] = useState<boolean>(false) // show confirmation modal
const [attemptingTxn, setAttemptingTxn] = useState<boolean>(false) // clicked confirmed const [attemptingTxn, setAttemptingTxn] = useState<boolean>(false) // waiting for user confirmaion/rejection
const [pendingConfirmation, setPendingConfirmation] = useState<boolean>(true) // waiting for user confirmation
// txn values
const [txHash, setTxHash] = useState<string>('') const [txHash, setTxHash] = useState<string>('')
// tx parameters
const [deadline, setDeadline] = useState<number>(DEFAULT_DEADLINE_FROM_NOW) const [deadline, setDeadline] = useState<number>(DEFAULT_DEADLINE_FROM_NOW)
const [allowedSlippage, setAllowedSlippage] = useState<number>(INITIAL_ALLOWED_SLIPPAGE) const [allowedSlippage, setAllowedSlippage] = useState<number>(INITIAL_ALLOWED_SLIPPAGE)
...@@ -137,17 +136,6 @@ export default function Send({ location: { search } }: RouteComponentProps) { ...@@ -137,17 +136,6 @@ export default function Send({ location: { search } }: RouteComponentProps) {
const atMaxAmountInput: boolean = const atMaxAmountInput: boolean =
!!maxAmountInput && !!parsedAmounts[Field.INPUT] ? maxAmountInput.equalTo(parsedAmounts[Field.INPUT]) : undefined !!maxAmountInput && !!parsedAmounts[Field.INPUT] ? maxAmountInput.equalTo(parsedAmounts[Field.INPUT]) : undefined
// reset modal state when closed
function resetModal() {
// clear input if txn submitted
if (!pendingConfirmation) {
onUserInput(Field.INPUT, '')
}
setPendingConfirmation(true)
setAttemptingTxn(false)
setShowAdvanced(false)
}
const swapCallback = useSwapCallback(bestTrade, allowedSlippage, deadline, recipient) const swapCallback = useSwapCallback(bestTrade, allowedSlippage, deadline, recipient)
function onSwap() { function onSwap() {
...@@ -156,16 +144,24 @@ export default function Send({ location: { search } }: RouteComponentProps) { ...@@ -156,16 +144,24 @@ export default function Send({ location: { search } }: RouteComponentProps) {
} }
setAttemptingTxn(true) setAttemptingTxn(true)
swapCallback().then(hash => { swapCallback()
setTxHash(hash) .then(hash => {
setPendingConfirmation(false) setAttemptingTxn(false)
setTxHash(hash)
ReactGA.event({
category: 'Send', ReactGA.event({
action: recipient === account ? 'Swap w/o Send' : 'Swap w/ Send', category: 'Send',
label: [bestTrade.inputAmount.token.symbol, bestTrade.outputAmount.token.symbol].join(';') action: recipient === account ? 'Swap w/o Send' : 'Swap w/ Send',
label: [bestTrade.inputAmount.token.symbol, bestTrade.outputAmount.token.symbol].join(';')
})
})
.catch(error => {
setAttemptingTxn(false)
// we only care if the error is something _other_ than the user rejected the tx
if (error?.code !== 4001) {
console.error(error)
}
}) })
})
} }
const sendCallback = useSendCallback(parsedAmounts?.[Field.INPUT], recipient) const sendCallback = useSendCallback(parsedAmounts?.[Field.INPUT], recipient)
...@@ -173,16 +169,19 @@ export default function Send({ location: { search } }: RouteComponentProps) { ...@@ -173,16 +169,19 @@ export default function Send({ location: { search } }: RouteComponentProps) {
async function onSend() { async function onSend() {
setAttemptingTxn(true) setAttemptingTxn(true)
sendCallback() sendCallback()
.then(hash => { .then(hash => {
setAttemptingTxn(false)
setTxHash(hash) setTxHash(hash)
ReactGA.event({ category: 'Send', action: 'Send', label: tokens[Field.INPUT]?.symbol }) ReactGA.event({ category: 'Send', action: 'Send', label: tokens[Field.INPUT]?.symbol })
setPendingConfirmation(false)
}) })
.catch(() => { .catch(error => {
resetModal() setAttemptingTxn(false)
setShowConfirm(false) // we only care if the error is something _other_ than the user rejected the tx
if (error?.code !== 4001) {
console.error(error)
}
}) })
} }
...@@ -306,11 +305,13 @@ export default function Send({ location: { search } }: RouteComponentProps) { ...@@ -306,11 +305,13 @@ export default function Send({ location: { search } }: RouteComponentProps) {
isOpen={showConfirm} isOpen={showConfirm}
title={sendingWithSwap ? 'Confirm swap and send' : 'Confirm Send'} title={sendingWithSwap ? 'Confirm swap and send' : 'Confirm Send'}
onDismiss={() => { onDismiss={() => {
resetModal()
setShowConfirm(false) setShowConfirm(false)
if (txHash) {
onUserInput(Field.INPUT, '')
}
setTxHash('')
}} }}
attemptingTxn={attemptingTxn} attemptingTxn={attemptingTxn}
pendingConfirmation={pendingConfirmation}
hash={txHash} hash={txHash}
topContent={modalHeader} topContent={modalHeader}
bottomContent={modalBottom} bottomContent={modalBottom}
......
...@@ -55,13 +55,12 @@ export default function Swap({ location: { search } }: RouteComponentProps) { ...@@ -55,13 +55,12 @@ export default function Swap({ location: { search } }: RouteComponentProps) {
const dependentField: Field = independentField === Field.INPUT ? Field.OUTPUT : Field.INPUT const dependentField: Field = independentField === Field.INPUT ? Field.OUTPUT : Field.INPUT
// modal and loading // modal and loading
const [showConfirm, setShowConfirm] = useState<boolean>(false) const [showAdvanced, setShowAdvanced] = useState<boolean>(false) // toggling slippage, deadline, etc. on and off
const [showAdvanced, setShowAdvanced] = useState<boolean>(false) const [showConfirm, setShowConfirm] = useState<boolean>(false) // show confirmation modal
const [attemptingTxn, setAttemptingTxn] = useState<boolean>(false) // clicked confirmed const [attemptingTxn, setAttemptingTxn] = useState<boolean>(false) // waiting for user confirmaion/rejection
const [pendingConfirmation, setPendingConfirmation] = useState<boolean>(true) // waiting for user confirmation
// txn values
const [txHash, setTxHash] = useState<string>('') const [txHash, setTxHash] = useState<string>('')
// tx parameters
const [deadline, setDeadline] = useState<number>(DEFAULT_DEADLINE_FROM_NOW) const [deadline, setDeadline] = useState<number>(DEFAULT_DEADLINE_FROM_NOW)
const [allowedSlippage, setAllowedSlippage] = useState<number>(INITIAL_ALLOWED_SLIPPAGE) const [allowedSlippage, setAllowedSlippage] = useState<number>(INITIAL_ALLOWED_SLIPPAGE)
...@@ -114,17 +113,6 @@ export default function Swap({ location: { search } }: RouteComponentProps) { ...@@ -114,17 +113,6 @@ export default function Swap({ location: { search } }: RouteComponentProps) {
const slippageAdjustedAmounts = computeSlippageAdjustedAmounts(bestTrade, allowedSlippage) const slippageAdjustedAmounts = computeSlippageAdjustedAmounts(bestTrade, allowedSlippage)
// reset modal state when closed
function resetModal() {
// clear input if txn submitted
if (!pendingConfirmation) {
onUserInput(Field.INPUT, '')
}
setPendingConfirmation(true)
setAttemptingTxn(false)
setShowAdvanced(false)
}
// the callback to execute the swap // the callback to execute the swap
const swapCallback = useSwapCallback(bestTrade, allowedSlippage, deadline) const swapCallback = useSwapCallback(bestTrade, allowedSlippage, deadline)
...@@ -136,16 +124,24 @@ export default function Swap({ location: { search } }: RouteComponentProps) { ...@@ -136,16 +124,24 @@ export default function Swap({ location: { search } }: RouteComponentProps) {
} }
setAttemptingTxn(true) setAttemptingTxn(true)
swapCallback().then(hash => { swapCallback()
setTxHash(hash) .then(hash => {
setPendingConfirmation(false) setAttemptingTxn(false)
setTxHash(hash)
ReactGA.event({
category: 'Swap', ReactGA.event({
action: 'Swap w/o Send', category: 'Swap',
label: [bestTrade.inputAmount.token.symbol, bestTrade.outputAmount.token.symbol].join('/') action: 'Swap w/o Send',
label: [bestTrade.inputAmount.token.symbol, bestTrade.outputAmount.token.symbol].join('/')
})
})
.catch(error => {
setAttemptingTxn(false)
// we only care if the error is something _other_ than the user rejected the tx
if (error?.code !== 4001) {
console.error(error)
}
}) })
})
} }
// errors // errors
...@@ -157,11 +153,11 @@ export default function Swap({ location: { search } }: RouteComponentProps) { ...@@ -157,11 +153,11 @@ export default function Swap({ location: { search } }: RouteComponentProps) {
function modalHeader() { function modalHeader() {
return ( return (
<SwapModalHeader <SwapModalHeader
independentField={independentField}
priceImpactSeverity={priceImpactSeverity}
tokens={tokens} tokens={tokens}
formattedAmounts={formattedAmounts} formattedAmounts={formattedAmounts}
slippageAdjustedAmounts={slippageAdjustedAmounts} slippageAdjustedAmounts={slippageAdjustedAmounts}
priceImpactSeverity={priceImpactSeverity}
independentField={independentField}
/> />
) )
} }
...@@ -197,11 +193,14 @@ export default function Swap({ location: { search } }: RouteComponentProps) { ...@@ -197,11 +193,14 @@ export default function Swap({ location: { search } }: RouteComponentProps) {
isOpen={showConfirm} isOpen={showConfirm}
title="Confirm Swap" title="Confirm Swap"
onDismiss={() => { onDismiss={() => {
resetModal()
setShowConfirm(false) setShowConfirm(false)
// if there was a tx hash, we want to clear the input
if (txHash) {
onUserInput(Field.INPUT, '')
}
setTxHash('')
}} }}
attemptingTxn={attemptingTxn} attemptingTxn={attemptingTxn}
pendingConfirmation={pendingConfirmation}
hash={txHash} hash={txHash}
topContent={modalHeader} topContent={modalHeader}
bottomContent={modalBottom} bottomContent={modalBottom}
......
...@@ -4,7 +4,7 @@ import { AddressZero } from '@ethersproject/constants' ...@@ -4,7 +4,7 @@ import { AddressZero } from '@ethersproject/constants'
import { JsonRpcSigner, Web3Provider } from '@ethersproject/providers' import { JsonRpcSigner, Web3Provider } from '@ethersproject/providers'
import { BigNumber } from '@ethersproject/bignumber' import { BigNumber } from '@ethersproject/bignumber'
import { abi as IUniswapV2PairABI } from '@uniswap/v2-core/build/IUniswapV2Pair.json' import { abi as IUniswapV2PairABI } from '@uniswap/v2-core/build/IUniswapV2Pair.json'
import { abi as IUniswapV2Router01ABI } from '@uniswap/v2-periphery/build/IUniswapV2Router01.json' import { abi as IUniswapV2Router02ABI } from '@uniswap/v2-periphery/build/IUniswapV2Router02.json'
import { ROUTER_ADDRESS } from '../constants' import { ROUTER_ADDRESS } from '../constants'
import { ChainId, JSBI, Percent, TokenAmount } from '@uniswap/sdk' import { ChainId, JSBI, Percent, TokenAmount } from '@uniswap/sdk'
...@@ -88,8 +88,8 @@ export function getContract(address: string, ABI: any, library: Web3Provider, ac ...@@ -88,8 +88,8 @@ export function getContract(address: string, ABI: any, library: Web3Provider, ac
} }
// account is optional // account is optional
export function getRouterContract(chainId: number, library: Web3Provider, account?: string) { export function getRouterContract(_: number, library: Web3Provider, account?: string) {
return getContract(ROUTER_ADDRESS, IUniswapV2Router01ABI, library, account) return getContract(ROUTER_ADDRESS, IUniswapV2Router02ABI, library, account)
} }
// account is optional // account is optional
......
...@@ -2813,10 +2813,10 @@ ...@@ -2813,10 +2813,10 @@
resolved "https://registry.yarnpkg.com/@uniswap/v2-core/-/v2-core-1.0.0.tgz#e0fab91a7d53e8cafb5326ae4ca18351116b0844" resolved "https://registry.yarnpkg.com/@uniswap/v2-core/-/v2-core-1.0.0.tgz#e0fab91a7d53e8cafb5326ae4ca18351116b0844"
integrity sha512-BJiXrBGnN8mti7saW49MXwxDBRFiWemGetE58q8zgfnPPzQKq55ADltEILqOt6VFZ22kVeVKbF8gVd8aY3l7pA== integrity sha512-BJiXrBGnN8mti7saW49MXwxDBRFiWemGetE58q8zgfnPPzQKq55ADltEILqOt6VFZ22kVeVKbF8gVd8aY3l7pA==
"@uniswap/v2-periphery@1.0.0-beta.0": "@uniswap/v2-periphery@^1.1.0-beta.0":
version "1.0.0-beta.0" version "1.1.0-beta.0"
resolved "https://registry.yarnpkg.com/@uniswap/v2-periphery/-/v2-periphery-1.0.0-beta.0.tgz#53ccbd5f6a3e43fd37f04660625873dc7d5c57b2" resolved "https://registry.yarnpkg.com/@uniswap/v2-periphery/-/v2-periphery-1.1.0-beta.0.tgz#20a4ccfca22f1a45402303aedb5717b6918ebe6d"
integrity sha512-r0Iuk7L3gZzbmlZeNhMdLmG0fOqfHCoxmWqYQ9OX4r3w0lJ6dyErJ8kVdTy8PtairkuHkXWeH3OdyzzzBtyRpw== integrity sha512-6dkwAMKza8nzqYiXEr2D86dgW3TTavUvCR0w2Tu33bAbM8Ah43LKAzH7oKKPRT5VJQaMi1jtkGs1E8JPor1n5g==
dependencies: dependencies:
"@uniswap/lib" "1.1.1" "@uniswap/lib" "1.1.1"
"@uniswap/v2-core" "1.0.0" "@uniswap/v2-core" "1.0.0"
......
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