Compare commits

...

6 Commits

Author SHA1 Message Date
Moody Salem
10ef04510a fix(fonts): font-display in non-font-variation-settings conditioned css 2020-07-31 12:12:42 -05:00
Moody Salem
e3b3d9e825 fix(swaps): band-aid fix for gas estimates to disable multihop for eth-ampl 2020-07-31 11:50:30 -05:00
Moody Salem
3050e967f7 fix(token lists): automatic updates to token lists 2020-07-30 18:32:14 -05:00
Ian Lapham
2150450760 improvement(token warnings): show better warnings for imported tokens (#1005)
* add updated ui warnings for imported tokens

* remove useless styling

* update to surpress on default tokens

* add integration tests for warning cards on token import

* remove callbacks as props in token warning card
2020-07-30 15:21:37 -04:00
Moody Salem
1b07e95885 fix(add liquidity): fix the mint hooks to return a price as well as return the dependent amount in the input currency (#1011) 2020-07-28 17:04:33 -05:00
Moody Salem
9bb50d6a7b unit tests for the uri to http method 2020-07-28 08:10:52 -05:00
15 changed files with 233 additions and 127 deletions

View File

@@ -0,0 +1,19 @@
describe('Warning', () => {
beforeEach(() => {
cy.clearLocalStorage()
cy.visit('/swap?outputCurrency=0x0a40f26d74274b7f22b28556a27b35d97ce08e0a')
})
it('Check that warning is displayed', () => {
cy.get('.token-warning-container').should('be.visible')
})
it('Check that warning hides after button dismissal.', () => {
cy.get('.token-dismiss-button').click()
cy.get('.token-warning-container').should('not.be.visible')
})
it('Check supression persists across sessions.', () => {
cy.get('.token-warning-container').should('be.visible')
cy.get('.token-dismiss-button').click()
cy.reload()
cy.get('.token-warning-container').should('not.be.visible')
})
})

View File

@@ -34,7 +34,7 @@
"@typescript-eslint/eslint-plugin": "^2.31.0",
"@typescript-eslint/parser": "^2.31.0",
"@uniswap/sdk": "3.0.3-beta.1",
"@uniswap/token-lists": "^1.0.0-beta.9",
"@uniswap/token-lists": "^1.0.0-beta.11",
"@uniswap/v2-core": "1.0.0",
"@uniswap/v2-periphery": "^1.1.0-beta.0",
"@web3-react/core": "^6.0.9",
@@ -46,7 +46,7 @@
"ajv": "^6.12.3",
"copy-to-clipboard": "^3.2.0",
"cross-env": "^7.0.2",
"cypress": "^4.5.0",
"cypress": "^4.11.0",
"eslint": "^6.8.0",
"eslint-config-prettier": "^6.11.0",
"eslint-plugin-prettier": "^3.1.3",

View File

@@ -2,66 +2,41 @@ import { Currency, Token } from '@uniswap/sdk'
import { transparentize } from 'polished'
import React, { useMemo } from 'react'
import styled from 'styled-components'
import { ReactComponent as Close } from '../../assets/images/x.svg'
import { useActiveWeb3React } from '../../hooks'
import { useAllTokens } from '../../hooks/Tokens'
import { useDefaultTokenList } from '../../state/lists/hooks'
import { Field } from '../../state/swap/actions'
import { useTokenWarningDismissal } from '../../state/user/hooks'
import { ExternalLink, TYPE } from '../../theme'
import { getEtherscanLink, isDefaultToken } from '../../utils'
import PropsOfExcluding from '../../utils/props-of-excluding'
import QuestionHelper from '../QuestionHelper'
import CurrencyLogo from '../CurrencyLogo'
import { AutoRow, RowBetween } from '../Row'
import { AutoColumn } from '../Column'
import { AlertTriangle } from 'react-feather'
import { ButtonError } from '../Button'
import { useTokenWarningDismissal } from '../../state/user/hooks'
const Wrapper = styled.div<{ error: boolean }>`
background: ${({ theme, error }) => transparentize(0.9, error ? theme.red1 : theme.yellow1)};
position: relative;
background: ${({ theme }) => transparentize(0.6, theme.white)};
padding: 0.75rem;
border-radius: 20px;
`
const WarningContainer = styled.div`
max-width: 420px;
width: 100%;
padding: 1rem;
/* border: 0.5px solid ${({ theme, error }) => transparentize(0.4, error ? theme.red1 : theme.yellow1)}; */
border-radius: 10px;
margin-bottom: 20px;
display: grid;
grid-template-rows: 14px auto auto;
grid-row-gap: 14px;
background: rgba(242, 150, 2, 0.05);
border: 1px solid #f3841e;
box-sizing: border-box;
border-radius: 20px;
margin-bottom: 2rem;
`
const Row = styled.div`
display: flex;
align-items: center;
justify-items: flex-start;
& > * {
margin-right: 6px;
}
const StyledWarningIcon = styled(AlertTriangle)`
stroke: ${({ theme }) => theme.red2};
`
const CloseColor = styled(Close)`
color: #aeaeae;
`
const CloseIcon = styled.div`
position: absolute;
right: 1rem;
top: 12px;
&:hover {
cursor: pointer;
opacity: 0.6;
}
& > * {
height: 16px;
width: 16px;
}
`
const HELP_TEXT = `
The Uniswap V2 smart contracts are designed to support any ERC20 token on Ethereum. Any token can be
loaded into the interface by entering its Ethereum address into the search field or passing it as a URL
parameter.
`
const DUPLICATE_NAME_HELP_TEXT = `${HELP_TEXT} This token has the same name or symbol as another token in your list.`
interface TokenWarningCardProps extends PropsOfExcluding<typeof Wrapper, 'error'> {
token?: Token
}
@@ -74,8 +49,6 @@ export default function TokenWarningCard({ token, ...rest }: TokenWarningCardPro
const tokenSymbol = token?.symbol?.toLowerCase() ?? ''
const tokenName = token?.name?.toLowerCase() ?? ''
const [dismissed, dismissTokenWarning] = useTokenWarningDismissal(chainId, token)
const allTokens = useAllTokens()
const duplicateNameOrSymbol = useMemo(() => {
@@ -90,52 +63,77 @@ export default function TokenWarningCard({ token, ...rest }: TokenWarningCardPro
})
}, [isDefault, token, chainId, allTokens, tokenSymbol, tokenName])
if (isDefault || !token || dismissed) return null
if (isDefault || !token) return null
return (
<Wrapper error={duplicateNameOrSymbol} {...rest}>
{duplicateNameOrSymbol ? null : (
<CloseIcon onClick={dismissTokenWarning}>
<CloseColor />
</CloseIcon>
)}
<Row>
<TYPE.subHeader>{duplicateNameOrSymbol ? 'Duplicate token name or symbol' : 'Imported token'}</TYPE.subHeader>
<QuestionHelper text={duplicateNameOrSymbol ? DUPLICATE_NAME_HELP_TEXT : HELP_TEXT} />
</Row>
<Row>
<CurrencyLogo currency={token} />
<div style={{ fontWeight: 500 }}>
{token && token.name && token.symbol && token.name !== token.symbol
? `${token.name} (${token.symbol})`
: token.name || token.symbol}
</div>
<ExternalLink style={{ fontWeight: 400 }} href={getEtherscanLink(chainId, token.address, 'token')}>
(View on Etherscan)
</ExternalLink>
</Row>
<Row>
<TYPE.italic>Verify this is the correct token before making any transactions.</TYPE.italic>
</Row>
<AutoRow gap="6px">
<AutoColumn gap="24px">
<CurrencyLogo currency={token} size={'16px'} />
<div> </div>
</AutoColumn>
<AutoColumn gap="10px" justify="flex-start">
<TYPE.main>
{token && token.name && token.symbol && token.name !== token.symbol
? `${token.name} (${token.symbol})`
: token.name || token.symbol}
</TYPE.main>
<ExternalLink style={{ fontWeight: 400 }} href={getEtherscanLink(chainId, token.address, 'token')}>
<TYPE.blue> (View on Etherscan)</TYPE.blue>
</ExternalLink>
</AutoColumn>
</AutoRow>
</Wrapper>
)
}
const WarningContainer = styled.div`
max-width: 420px;
width: 100%;
padding-left: 1rem;
padding-right: 1rem;
`
export function TokenWarningCards({ currencies }: { currencies: { [field in Field]?: Currency } }) {
const { chainId } = useActiveWeb3React()
const [dismissedToken0, dismissToken0] = useTokenWarningDismissal(chainId, currencies[Field.INPUT])
const [dismissedToken1, dismissToken1] = useTokenWarningDismissal(chainId, currencies[Field.OUTPUT])
return (
<WarningContainer>
{Object.keys(currencies).map(field =>
currencies[field] instanceof Token ? (
<TokenWarningCard style={{ marginBottom: 14 }} key={field} token={currencies[field]} />
) : null
)}
<WarningContainer className="token-warning-container">
<AutoColumn gap="lg">
<AutoRow gap="6px">
<StyledWarningIcon />
<TYPE.main color={'red2'}>Token imported</TYPE.main>
</AutoRow>
<TYPE.body color={'red2'}>
Anyone can create and name any ERC20 token on Ethereum, including creating fake versions of existing tokens
and tokens that claim to represent projects that do not have a token.
</TYPE.body>
<TYPE.body color={'red2'}>
Similar to Etherscan, this site can load arbitrary tokens via token addresses. Please do your own research
before interacting with any ERC20 token.
</TYPE.body>
{Object.keys(currencies).map(field => {
const dismissed = field === Field.INPUT ? dismissedToken0 : dismissedToken1
return currencies[field] instanceof Token && !dismissed ? (
<TokenWarningCard key={field} token={currencies[field]} />
) : null
})}
<RowBetween>
<div />
<ButtonError
error={true}
width={'140px'}
padding="0.5rem 1rem"
style={{
borderRadius: '10px'
}}
onClick={() => {
dismissToken0 && dismissToken0()
dismissToken1 && dismissToken1()
}}
>
<TYPE.body color="white" className="token-dismiss-button">
I understand
</TYPE.body>
</ButtonError>
<div />
</RowBetween>
</AutoColumn>
</WarningContainer>
)
}

View File

@@ -4,6 +4,7 @@ import { useMemo } from 'react'
import { BASES_TO_CHECK_TRADES_AGAINST } from '../constants'
import { PairState, usePairs } from '../data/Reserves'
import { maxHopsFor } from '../utils/maxHopsFor'
import { wrappedCurrency } from '../utils/wrappedCurrency'
import { useActiveWeb3React } from './index'
@@ -58,8 +59,9 @@ export function useTradeExactIn(currencyAmountIn?: CurrencyAmount, currencyOut?:
return useMemo(() => {
if (currencyAmountIn && currencyOut && allowedPairs.length > 0) {
const maxHops = maxHopsFor(currencyAmountIn.currency, currencyOut)
return (
Trade.bestTradeExactIn(allowedPairs, currencyAmountIn, currencyOut, { maxHops: 3, maxNumResults: 1 })[0] ?? null
Trade.bestTradeExactIn(allowedPairs, currencyAmountIn, currencyOut, { maxHops, maxNumResults: 1 })[0] ?? null
)
}
return null
@@ -74,9 +76,9 @@ export function useTradeExactOut(currencyIn?: Currency, currencyAmountOut?: Curr
return useMemo(() => {
if (currencyIn && currencyAmountOut && allowedPairs.length > 0) {
const maxHops = maxHopsFor(currencyIn, currencyAmountOut.currency)
return (
Trade.bestTradeExactOut(allowedPairs, currencyIn, currencyAmountOut, { maxHops: 3, maxNumResults: 1 })[0] ??
null
Trade.bestTradeExactOut(allowedPairs, currencyIn, currencyAmountOut, { maxHops, maxNumResults: 1 })[0] ?? null
)
}
return null

View File

@@ -311,7 +311,7 @@ export default function AddLiquidity({
}}
attemptingTxn={attemptingTxn}
hash={txHash}
topContent={() => modalHeader()}
topContent={modalHeader}
bottomContent={modalBottom}
pendingText={pendingText}
title={noLiquidity ? 'You are creating a pool' : 'You will receive'}

View File

@@ -1,7 +1,7 @@
import React from 'react'
import styled from 'styled-components'
export const BodyWrapper = styled.div`
export const BodyWrapper = styled.div<{ disabled?: boolean }>`
position: relative;
max-width: 420px;
width: 100%;
@@ -10,11 +10,13 @@ export const BodyWrapper = styled.div`
0px 24px 32px rgba(0, 0, 0, 0.01);
border-radius: 30px;
padding: 1rem;
opacity: ${({ disabled }) => (disabled ? '0.4' : '1')};
pointer-events: ${({ disabled }) => disabled && 'none'};
`
/**
* The styled container element that wraps the content of most pages and the tabs.
*/
export default function AppBody({ children }: { children: React.ReactNode }) {
return <BodyWrapper>{children}</BodyWrapper>
export default function AppBody({ children, disabled }: { children: React.ReactNode; disabled?: boolean }) {
return <BodyWrapper disabled={disabled}>{children}</BodyWrapper>
}

View File

@@ -37,7 +37,12 @@ import {
useSwapActionHandlers,
useSwapState
} from '../../state/swap/hooks'
import { useExpertModeManager, useUserDeadline, useUserSlippageTolerance } from '../../state/user/hooks'
import {
useExpertModeManager,
useUserDeadline,
useUserSlippageTolerance,
useTokenWarningDismissal
} from '../../state/user/hooks'
import { CursorPointer, LinkStyledButton, TYPE } from '../../theme'
import { maxAmountSpend } from '../../utils/maxAmountSpend'
import { computeSlippageAdjustedAmounts, computeTradePriceBreakdown, warningSeverity } from '../../utils/prices'
@@ -47,7 +52,7 @@ import { ClickableText } from '../Pool/styleds'
export default function Swap() {
useDefaultsFromURLSearch()
const { account } = useActiveWeb3React()
const { account, chainId } = useActiveWeb3React()
const theme = useContext(ThemeContext)
// toggle wallet when disconnected
@@ -241,10 +246,15 @@ export default function Swap() {
currencies[Field.INPUT]?.symbol
} for ${parsedAmounts[Field.OUTPUT]?.toSignificant(6)} ${currencies[Field.OUTPUT]?.symbol}`
const [dismissedToken0] = useTokenWarningDismissal(chainId, currencies[Field.INPUT])
const [dismissedToken1] = useTokenWarningDismissal(chainId, currencies[Field.OUTPUT])
const showWarning =
(!dismissedToken0 && !!currencies[Field.INPUT]) || (!dismissedToken1 && !!currencies[Field.OUTPUT])
return (
<>
<TokenWarningCards currencies={currencies} />
<AppBody>
{showWarning && <TokenWarningCards currencies={currencies} />}
<AppBody disabled={!!showWarning}>
<SwapPoolTabs active={'swap'} />
<Wrapper id="swap-page">
<ConfirmationModal
@@ -424,7 +434,6 @@ export default function Swap() {
</BottomGrouping>
</Wrapper>
</AppBody>
<AdvancedSwapDetailsDropdown trade={trade} />
</>
)

View File

@@ -1,4 +1,4 @@
import { Currency, CurrencyAmount, JSBI, Pair, Percent, Price, TokenAmount } from '@uniswap/sdk'
import { Currency, CurrencyAmount, ETHER, JSBI, Pair, Percent, Price, TokenAmount } from '@uniswap/sdk'
import { useCallback, useMemo } from 'react'
import { useDispatch, useSelector } from 'react-redux'
import { PairState, usePair } from '../../data/Reserves'
@@ -65,17 +65,24 @@ export function useDerivedMintInfo(
}
// amounts
const independentAmount = tryParseAmount(typedValue, currencies[independentField])
const dependentAmount = useMemo(() => {
if (noLiquidity && otherTypedValue && currencies[dependentField]) {
return tryParseAmount(otherTypedValue, currencies[dependentField])
const independentAmount: CurrencyAmount | undefined = tryParseAmount(typedValue, currencies[independentField])
const dependentAmount: CurrencyAmount | undefined = useMemo(() => {
if (noLiquidity) {
if (otherTypedValue && currencies[dependentField]) {
return tryParseAmount(otherTypedValue, currencies[dependentField])
}
return
} else if (independentAmount) {
// we wrap the currencies just to get the price in terms of the other token
const wrappedIndependentAmount = wrappedCurrencyAmount(independentAmount, chainId)
const [tokenA, tokenB] = [wrappedCurrency(currencyA, chainId), wrappedCurrency(currencyB, chainId)]
if (tokenA && tokenB && wrappedIndependentAmount && pair) {
return dependentField === Field.CURRENCY_B
? pair.priceOf(tokenA).quote(wrappedIndependentAmount)
: pair.priceOf(tokenB).quote(wrappedIndependentAmount)
const dependentCurrency = dependentField === Field.CURRENCY_B ? currencyB : currencyA
const dependentTokenAmount =
dependentField === Field.CURRENCY_B
? pair.priceOf(tokenA).quote(wrappedIndependentAmount)
: pair.priceOf(tokenB).quote(wrappedIndependentAmount)
return dependentCurrency === ETHER ? CurrencyAmount.ether(dependentTokenAmount.raw) : dependentTokenAmount
}
return
} else {
@@ -89,12 +96,11 @@ export function useDerivedMintInfo(
const price = useMemo(() => {
const { [Field.CURRENCY_A]: currencyAAmount, [Field.CURRENCY_B]: currencyBAmount } = parsedAmounts
if (noLiquidity && currencyAAmount && currencyBAmount) {
if (currencyAAmount && currencyBAmount) {
return new Price(currencyAAmount.currency, currencyBAmount.currency, currencyAAmount.raw, currencyBAmount.raw)
} else {
return
}
}, [noLiquidity, parsedAmounts])
return
}, [parsedAmounts])
// liquidity minted
const totalSupply = useTotalSupply(pair?.liquidityToken)

View File

@@ -1,4 +1,4 @@
import { ChainId, Pair, Token } from '@uniswap/sdk'
import { ChainId, Pair, Token, Currency } from '@uniswap/sdk'
import flatMap from 'lodash.flatmap'
import { useCallback, useMemo } from 'react'
import { shallowEqual, useDispatch, useSelector } from 'react-redux'
@@ -19,6 +19,8 @@ import {
updateUserExpertMode,
updateUserSlippageTolerance
} from './actions'
import { useDefaultTokenList } from '../lists/hooks'
import { isDefaultToken } from '../../utils'
function serializeToken(token: Token): SerializedToken {
return {
@@ -165,22 +167,30 @@ export function usePairAdder(): (pair: Pair) => void {
* Returns whether a token warning has been dismissed and a callback to dismiss it,
* iff it has not already been dismissed and is a valid token.
*/
export function useTokenWarningDismissal(chainId?: number, token?: Token): [boolean, null | (() => void)] {
export function useTokenWarningDismissal(chainId?: number, token?: Currency): [boolean, null | (() => void)] {
const dismissalState = useSelector<AppState, AppState['user']['dismissedTokenWarnings']>(
state => state.user.dismissedTokenWarnings
)
const dispatch = useDispatch<AppDispatch>()
// get default list, mark as dismissed if on list
const defaultList = useDefaultTokenList()
const isDefault = isDefaultToken(defaultList, token)
return useMemo(() => {
if (!chainId || !token) return [false, null]
const dismissed: boolean = dismissalState?.[chainId]?.[token.address] === true
const dismissed: boolean =
token instanceof Token ? dismissalState?.[chainId]?.[token.address] === true || isDefault : true
const callback = dismissed ? null : () => dispatch(dismissTokenWarning({ chainId, tokenAddress: token.address }))
const callback =
dismissed || !(token instanceof Token)
? null
: () => dispatch(dismissTokenWarning({ chainId, tokenAddress: token.address }))
return [dismissed, callback]
}, [chainId, token, dismissalState, dispatch])
}, [chainId, token, dismissalState, isDefault, dispatch])
}
/**

View File

@@ -75,6 +75,7 @@ export function colors(darkMode: boolean): Colors {
// other
red1: '#FF6871',
red2: '#F82D3A',
green1: '#27AE60',
yellow1: '#FFE270',
yellow2: '#F3841E'
@@ -171,11 +172,11 @@ export const FixedGlobalStyle = createGlobalStyle`
html, input, textarea, button {
font-family: 'Inter', sans-serif;
letter-spacing: -0.018em;
font-display: fallback;
}
@supports (font-variation-settings: normal) {
html, input, textarea, button {
font-family: 'Inter var', sans-serif;
font-display: fallback;
}
}

View File

@@ -39,6 +39,7 @@ export interface Colors {
// other
red1: Color
red2: Color
green1: Color
yellow1: Color
yellow2: Color

32
src/utils/maxHopsFor.ts Normal file
View File

@@ -0,0 +1,32 @@
import { ChainId, Currency, ETHER, Token, WETH } from '@uniswap/sdk'
function isEtherish(currency: Currency): boolean {
return currency === ETHER || (currency instanceof Token && WETH[currency.chainId].equals(currency))
}
const AMPL_TOKEN_ADDRESS = '0xD46bA6D942050d489DBd938a2C909A5d5039A161'
/**
* Band-aid on maxHops because some tokens seems to always fail with multihop swaps
* @param currencyIn currency in
* @param currencyOut currency out
*/
export function maxHopsFor(currencyIn: Currency, currencyOut: Currency): number {
if (
isEtherish(currencyIn) &&
currencyOut instanceof Token &&
currencyOut.chainId === ChainId.MAINNET &&
currencyOut.address === AMPL_TOKEN_ADDRESS
) {
return 1
} else if (
isEtherish(currencyOut) &&
currencyIn instanceof Token &&
currencyIn.chainId === ChainId.MAINNET &&
currencyIn.address === AMPL_TOKEN_ADDRESS
) {
return 1
}
return 3
}

View File

@@ -0,0 +1,28 @@
import uriToHttp from './uriToHttp'
describe('uriToHttp', () => {
it('returns .eth.link for ens names', () => {
expect(uriToHttp('t2crtokens.eth')).toEqual(['https://t2crtokens.eth.link'])
})
it('returns https first for http', () => {
expect(uriToHttp('http://test.com')).toEqual(['https://test.com', 'http://test.com'])
})
it('returns https for https', () => {
expect(uriToHttp('https://test.com')).toEqual(['https://test.com'])
})
it('returns ipfs gateways for ipfs:// urls', () => {
expect(uriToHttp('ipfs://QmV8AfDE8GFSGQvt3vck8EwAzsPuNTmtP8VcQJE3qxRPaZ')).toEqual([
'https://cloudflare-ipfs.com/ipfs/QmV8AfDE8GFSGQvt3vck8EwAzsPuNTmtP8VcQJE3qxRPaZ/',
'https://ipfs.io/ipfs/QmV8AfDE8GFSGQvt3vck8EwAzsPuNTmtP8VcQJE3qxRPaZ/'
])
})
it('returns ipns gateways for ipns:// urls', () => {
expect(uriToHttp('ipns://app.uniswap.org')).toEqual([
'https://cloudflare-ipfs.com/ipns/app.uniswap.org/',
'https://ipfs.io/ipns/app.uniswap.org/'
])
})
it('returns empty array for invalid scheme', () => {
expect(uriToHttp('blah:test')).toEqual([])
})
})

View File

@@ -10,20 +10,18 @@ export default function uriToHttp(uri: string): string[] {
} else if (parsed.protocol === 'https:') {
return [uri]
} else if (parsed.protocol === 'ipfs:') {
const hash = parsed.pathname.substring(2)
return [`https://cloudflare-ipfs.com/ipfs/${hash}/`, `https://ipfs.infura.io/ipfs/${hash}/`]
const hash = parsed.href.match(/^ipfs:(\/\/)?(.*)$/)?.[2]
return [`https://cloudflare-ipfs.com/ipfs/${hash}/`, `https://ipfs.io/ipfs/${hash}/`]
} else if (parsed.protocol === 'ipns:') {
const name = parsed.pathname.substring(2)
return [`https://cloudflare-ipfs.com/ipns/${name}/`, `https://ipfs.infura.io/ipns/${name}/`]
const name = parsed.href.match(/^ipns:(\/\/)?(.*)$/)?.[2]
return [`https://cloudflare-ipfs.com/ipns/${name}/`, `https://ipfs.io/ipns/${name}/`]
} else {
console.error('Unrecognized protocol', parsed)
return []
}
} catch (error) {
if (uri.toLowerCase().endsWith('.eth')) {
return [`https://${uri.toLowerCase()}.link`]
}
console.error('Failed to parse URI', error)
return []
}
}

View File

@@ -2681,10 +2681,10 @@
tiny-warning "^1.0.3"
toformat "^2.0.0"
"@uniswap/token-lists@^1.0.0-beta.9":
version "1.0.0-beta.10"
resolved "https://registry.yarnpkg.com/@uniswap/token-lists/-/token-lists-1.0.0-beta.10.tgz#9788fdd65c3720f1f7cc3ef262f507ea3f4ac062"
integrity sha512-NCdJC6Zlb62GN9oiYZoI0PMm26G5vu2CnBYAkR4rEPUqqKuxagdqntjOt2XfwklQaeSc9egwXUdbAOTU8wgjyA==
"@uniswap/token-lists@^1.0.0-beta.11":
version "1.0.0-beta.11"
resolved "https://registry.yarnpkg.com/@uniswap/token-lists/-/token-lists-1.0.0-beta.11.tgz#365dd55d536c67fa550554c0658391bfbc2930b7"
integrity sha512-dGHdb58d+rN7G164ziPP6omb1R0hwBVgs95er83OzXKkVRlLKE/FLSdgpDaTxLj1war+P/hZXw2/ToYcKFsobQ==
"@uniswap/v2-core@1.0.0":
version "1.0.0"
@@ -5690,7 +5690,7 @@ cyclist@^1.0.1:
resolved "https://registry.yarnpkg.com/cyclist/-/cyclist-1.0.1.tgz#596e9698fd0c80e12038c2b82d6eb1b35b6224d9"
integrity sha1-WW6WmP0MgOEgOMK4LW6xs1tiJNk=
cypress@*, cypress@^4.5.0:
cypress@*, cypress@^4.11.0:
version "4.11.0"
resolved "https://registry.yarnpkg.com/cypress/-/cypress-4.11.0.tgz#054b0b85fd3aea793f186249ee1216126d5f0a7e"
integrity sha512-6Yd598+KPATM+dU1Ig0g2hbA+R/o1MAKt0xIejw4nZBVLSplCouBzqeKve6XsxGU6n4HMSt/+QYsWfFcoQeSEw==