Skip to content

Commit

Permalink
feat(lists): allow selecting and adding token lists (#1023)
Browse files Browse the repository at this point in the history
* more list stuff

Use the selected list instead of the default list, but also use the default list

start list selection code

* move token warning to a modal, fix the install issue

* add/remove/enter key

* handle enter on currency select for ETHER

* change slippage tolerance to be a slider

* make ui closer to the mocks

* commit slider changes

* back to tabs

* copy changes

* bump list version

* some styling for the list select

* bump uniswap default list version

* use contract calls to get ens names and addresses

* show list logo

* fix failing integration test

* .eth.link

* list introduction screen

* remove showSendWithSwap

* fix integration and unit tests

* resolve ENS names

* logos from ens

* fix the lint errors

* some refactoring to better support using a the library provider from the user for resolving ENS names

* load list info from the list url for the introduction page

* make it slightly harder to remove a list

* minor clean up, some help text and links

* remove icon from list update popup

* show added/removed tokens

* add GA everywhere, don't debounce contenthash lookups

* show tags

* fix tag key

* tag display, list rendering, needs optimization

* fix list fetching in firefox, style issue in safari

* sort the lists, clean up styling

* use client provider when possible

* show token warning for url loaded tokens

* improve the warning modal

* some refactoring to fix the list fetching on networks other than mainnet

* fix tests

* some minor improvements

* increase timeout to maybe fix integration tests which pass locally

* build for tests using the dev network url

* reset the lists if we deleted the other two copies

* improve how we handle updating the default list of lists

* fix integration test

* Update token list selection styles

* fix external links, reuse the on click outside code, show add errors

* show the list origin instead of the full url

* fix update list link

* show host instead of hostname
do not automatically dismiss major version upgrades for lists

* fix link to tokenlists.org

* add uma

* clean up styling in list rows

* bump token list version

* bump token list version again

* hover symbol to see currency name

* bump version

* add cmc lists, dharma list

Co-authored-by: Callil Capuozzo <callil.capuozzo@gmail.com>
  • Loading branch information
moodysalem and callil authored Aug 26, 2020
1 parent 09b5457 commit 7cf25ac
Show file tree
Hide file tree
Showing 84 changed files with 3,797 additions and 1,075 deletions.
2 changes: 2 additions & 0 deletions .github/workflows/tests.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ jobs:
- run: yarn install --frozen-lockfile
- run: yarn cypress install
- run: yarn build
env:
REACT_APP_NETWORK_URL: "https://mainnet.infura.io/v3/4bf032f2d38a4ed6bb975b80d6340847"
- run: yarn integration-test

unit-tests:
Expand Down
3 changes: 2 additions & 1 deletion cypress.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,6 @@
"pluginsFile": false,
"fixturesFolder": false,
"supportFile": "cypress/support/index.js",
"video": false
"video": false,
"defaultCommandTimeout": 10000
}
11 changes: 11 additions & 0 deletions cypress/integration/lists.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
describe('Swap', () => {
beforeEach(() => cy.visit('/swap'))

it('list selection persists', () => {
cy.get('#swap-currency-output .open-currency-select-button').click()
cy.get('#select-default-uniswap-list .select-button').click()
cy.reload()
cy.get('#swap-currency-output .open-currency-select-button').click()
cy.get('#select-default-uniswap-list').should('not.exist')
})
})
6 changes: 5 additions & 1 deletion cypress/integration/swap.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
describe('Swap', () => {
beforeEach(() => cy.visit('/swap'))
beforeEach(() => {
cy.clearLocalStorage()
cy.visit('/swap')
})
it('can enter an amount into input', () => {
cy.get('#swap-currency-input .token-amount-input')
.type('0.001', { delay: 200 })
Expand Down Expand Up @@ -32,6 +35,7 @@ describe('Swap', () => {

it('can swap ETH for DAI', () => {
cy.get('#swap-currency-output .open-currency-select-button').click()
cy.get('#select-default-uniswap-list .select-button').click()
cy.get('.token-item-0xc7AD46e0b8a400Bb3C915120d284AafbA8fc4735').should('be.visible')
cy.get('.token-item-0xc7AD46e0b8a400Bb3C915120d284AafbA8fc4735').click({ force: true })
cy.get('#swap-currency-input .token-amount-input').should('be.visible')
Expand Down
11 changes: 4 additions & 7 deletions cypress/integration/token-warning.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,11 @@ describe('Warning', () => {
it('Check that warning is displayed', () => {
cy.get('.token-warning-container').should('be.visible')
})
it('Check that warning hides after button dismissal.', () => {
it('Check that warning hides after button dismissal', () => {
cy.get('.token-dismiss-button').should('be.disabled')
cy.get('.understand-checkbox').click()
cy.get('.token-dismiss-button').should('not.be.disabled')
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')
})
})
9 changes: 8 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,20 +11,23 @@
"@reduxjs/toolkit": "^1.3.5",
"@types/jest": "^25.2.1",
"@types/lodash.flatmap": "^4.5.6",
"@types/multicodec": "^1.0.0",
"@types/node": "^13.13.5",
"@types/qs": "^6.9.2",
"@types/react": "^16.9.34",
"@types/react-dom": "^16.9.7",
"@types/react-redux": "^7.1.8",
"@types/react-router-dom": "^5.0.0",
"@types/react-virtualized-auto-sizer": "^1.0.0",
"@types/react-window": "^1.8.2",
"@types/rebass": "^4.0.5",
"@types/styled-components": "^5.1.0",
"@types/testing-library__cypress": "^5.0.5",
"@typescript-eslint/eslint-plugin": "^2.31.0",
"@typescript-eslint/parser": "^2.31.0",
"@uniswap/default-token-list": "^1.3.0",
"@uniswap/sdk": "3.0.3-beta.1",
"@uniswap/token-lists": "^1.0.0-beta.11",
"@uniswap/token-lists": "^1.0.0-beta.14",
"@uniswap/v2-core": "1.0.0",
"@uniswap/v2-periphery": "^1.1.0-beta.0",
"@web3-react/core": "^6.0.9",
Expand All @@ -34,6 +37,7 @@
"@web3-react/walletconnect-connector": "^6.1.1",
"@web3-react/walletlink-connector": "^6.0.9",
"ajv": "^6.12.3",
"cids": "^1.0.0",
"copy-to-clipboard": "^3.2.0",
"cross-env": "^7.0.2",
"cypress": "^4.11.0",
Expand All @@ -49,6 +53,8 @@
"inter-ui": "^3.13.1",
"jazzicon": "^1.5.0",
"lodash.flatmap": "^4.5.0",
"multicodec": "^2.0.0",
"multihashes": "^3.0.1",
"polished": "^3.3.2",
"prettier": "^1.17.0",
"qs": "^6.9.4",
Expand All @@ -64,6 +70,7 @@
"react-scripts": "^3.4.1",
"react-spring": "^8.0.27",
"react-use-gesture": "^6.0.14",
"react-virtualized-auto-sizer": "^1.0.2",
"react-window": "^1.8.5",
"rebass": "^4.0.7",
"redux-localstorage-simple": "^2.2.0",
Expand Down
4 changes: 2 additions & 2 deletions src/components/Card/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,11 @@ export const LightCard = styled(Card)`
`

export const GreyCard = styled(Card)`
background-color: ${({ theme }) => theme.advancedBG};
background-color: ${({ theme }) => theme.bg3};
`

export const OutlineCard = styled(Card)`
border: 1px solid ${({ theme }) => theme.advancedBG};
border: 1px solid ${({ theme }) => theme.bg3};
`

export const YellowCard = styled(Card)`
Expand Down
5 changes: 1 addition & 4 deletions src/components/CurrencyInputPanel/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,6 @@ interface CurrencyInputPanelProps {
hideBalance?: boolean
pair?: Pair | null
hideInput?: boolean
showSendWithSwap?: boolean
otherCurrency?: Currency | null
id: string
showCommonBases?: boolean
Expand All @@ -144,7 +143,6 @@ export default function CurrencyInputPanel({
hideBalance = false,
pair = null, // used for double token logo
hideInput = false,
showSendWithSwap = false,
otherCurrency = null,
id,
showCommonBases
Expand Down Expand Up @@ -238,8 +236,7 @@ export default function CurrencyInputPanel({
isOpen={modalOpen}
onDismiss={handleDismissSearch}
onCurrencySelect={onCurrencySelect}
showSendWithSwap={showSendWithSwap}
hiddenCurrency={currency}
selectedCurrency={currency}
otherSelectedCurrency={otherCurrency}
showCommonBases={showCommonBases}
/>
Expand Down
80 changes: 20 additions & 60 deletions src/components/CurrencyLogo/index.tsx
Original file line number Diff line number Diff line change
@@ -1,94 +1,54 @@
import { Currency, ETHER, Token } from '@uniswap/sdk'
import React, { useState } from 'react'
import React, { useMemo } from 'react'
import styled from 'styled-components'

import EthereumLogo from '../../assets/images/ethereum-logo.png'
import useHttpLocations from '../../hooks/useHttpLocations'
import { WrappedTokenInfo } from '../../state/lists/hooks'
import uriToHttp from '../../utils/uriToHttp'
import Logo from '../Logo'

const getTokenLogoURL = address =>
const getTokenLogoURL = (address: string) =>
`https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/${address}/logo.png`
const BAD_URIS: { [tokenAddress: string]: true } = {}

const Image = styled.img<{ size: string }>`
const StyledEthereumLogo = styled.img<{ size: string }>`
width: ${({ size }) => size};
height: ${({ size }) => size};
background-color: white;
border-radius: 1rem;
box-shadow: 0px 6px 10px rgba(0, 0, 0, 0.075);
border-radius: 24px;
`

const Emoji = styled.span<{ size?: string }>`
display: flex;
align-items: center;
justify-content: center;
font-size: ${({ size }) => size};
width: ${({ size }) => size};
height: ${({ size }) => size};
margin-bottom: -4px;
`

const StyledEthereumLogo = styled.img<{ size: string }>`
const StyledLogo = styled(Logo)<{ size: string }>`
width: ${({ size }) => size};
height: ${({ size }) => size};
box-shadow: 0px 6px 10px rgba(0, 0, 0, 0.075);
border-radius: 24px;
`

export default function CurrencyLogo({
currency,
size = '24px',
...rest
style
}: {
currency?: Currency
size?: string
style?: React.CSSProperties
}) {
const [, refresh] = useState<number>(0)

if (currency === ETHER) {
return <StyledEthereumLogo src={EthereumLogo} size={size} {...rest} />
}
const uriLocations = useHttpLocations(currency instanceof WrappedTokenInfo ? currency.logoURI : undefined)

if (currency instanceof Token) {
let uri: string | undefined
const srcs: string[] = useMemo(() => {
if (currency === ETHER) return []

if (currency instanceof WrappedTokenInfo) {
if (currency.logoURI && !BAD_URIS[currency.logoURI]) {
uri = uriToHttp(currency.logoURI).filter(s => !BAD_URIS[s])[0]
if (currency instanceof Token) {
if (currency instanceof WrappedTokenInfo) {
return [...uriLocations, getTokenLogoURL(currency.address)]
}
}

if (!uri) {
const defaultUri = getTokenLogoURL(currency.address)
if (!BAD_URIS[defaultUri]) {
uri = defaultUri
}
return [getTokenLogoURL(currency.address)]
}
return []
}, [currency, uriLocations])

if (uri) {
return (
<Image
{...rest}
alt={`${currency.name} Logo`}
src={uri}
size={size}
onError={() => {
if (currency instanceof Token) {
BAD_URIS[uri] = true
}
refresh(i => i + 1)
}}
/>
)
}
if (currency === ETHER) {
return <StyledEthereumLogo src={EthereumLogo} size={size} style={style} />
}

return (
<Emoji {...rest} size={size}>
<span role="img" aria-label="Thinking">
🤔
</span>
</Emoji>
)
return <StyledLogo size={size} srcs={srcs} alt={`${currency?.symbol ?? 'token'} logo`} style={style} />
}
26 changes: 26 additions & 0 deletions src/components/ListLogo/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import React from 'react'
import styled from 'styled-components'
import useHttpLocations from '../../hooks/useHttpLocations'

import Logo from '../Logo'

const StyledListLogo = styled(Logo)<{ size: string }>`
width: ${({ size }) => size};
height: ${({ size }) => size};
`

export default function ListLogo({
logoURI,
style,
size = '24px',
alt
}: {
logoURI: string
size?: string
style?: React.CSSProperties
alt?: string
}) {
const srcs: string[] = useHttpLocations(logoURI)

return <StyledListLogo alt={alt} size={size} srcs={srcs} style={style} />
}
34 changes: 34 additions & 0 deletions src/components/Logo/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import React, { useState } from 'react'
import { AlertTriangle } from 'react-feather'
import { ImageProps } from 'rebass'

const BAD_SRCS: { [tokenAddress: string]: true } = {}

export interface LogoProps extends Pick<ImageProps, 'style' | 'alt' | 'className'> {
srcs: string[]
}

/**
* Renders an image by sequentially trying a list of URIs, and then eventually a fallback triangle alert
*/
export default function Logo({ srcs, alt, ...rest }: LogoProps) {
const [, refresh] = useState<number>(0)

const src: string | undefined = srcs.find(src => !BAD_SRCS[src])

if (src) {
return (
<img
{...rest}
alt={alt}
src={src}
onError={() => {
if (src) BAD_SRCS[src] = true
refresh(i => i + 1)
}}
/>
)
}

return <AlertTriangle {...rest} />
}
22 changes: 3 additions & 19 deletions src/components/Menu/index.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import React, { useRef, useEffect } from 'react'
import React, { useRef } from 'react'
import { Info, BookOpen, Code, PieChart, MessageCircle } from 'react-feather'
import styled from 'styled-components'
import { ReactComponent as MenuIcon } from '../../assets/images/menu.svg'
import { useOnClickOutside } from '../../hooks/useOnClickOutside'
import useToggle from '../../hooks/useToggle'

import { ExternalLink } from '../../theme'
Expand Down Expand Up @@ -83,24 +84,7 @@ export default function Menu() {
const node = useRef<HTMLDivElement>()
const [open, toggle] = useToggle(false)

useEffect(() => {
const handleClickOutside = e => {
if (node.current?.contains(e.target) ?? false) {
return
}
toggle()
}

if (open) {
document.addEventListener('mousedown', handleClickOutside)
} else {
document.removeEventListener('mousedown', handleClickOutside)
}

return () => {
document.removeEventListener('mousedown', handleClickOutside)
}
}, [open, toggle])
useOnClickOutside(node, open ? toggle : undefined)

return (
<StyledMenu ref={node}>
Expand Down
1 change: 1 addition & 0 deletions src/components/Modal/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ const StyledDialogContent = styled(({ minHeight, maxHeight, mobile, isOpen, ...r
box-shadow: 0 4px 8px 0 ${({ theme }) => transparentize(0.95, theme.shadow1)};
padding: 0px;
width: 50vw;
overflow: hidden;
align-self: ${({ mobile }) => (mobile ? 'flex-end' : 'center')};
Expand Down
Loading

1 comment on commit 7cf25ac

@vercel
Copy link

@vercel vercel bot commented on 7cf25ac Aug 26, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.