Skip to content
This repository has been archived by the owner on Jun 24, 2022. It is now read-only.

Commit

Permalink
fix(L2): enable optimism token list when switched to optimism (#2036)
Browse files Browse the repository at this point in the history
* add title text and opacity variability to token lists

* add optimism token list

* show tokens from active lists

* sort up token lists
with tokens on the current chain

* fix up some type issues

prune out chainId changes

* clean up leftover any

* refactor token count mechanism

* handle plurals in title text string

* new combineMaps implementation

* remove custom plural

* address a couple nits

* show the number of tokens on current chain

Co-authored-by: Jordan Frankfurt <[email protected]>
Co-authored-by: Justin Domingue <[email protected]>
  • Loading branch information
3 people authored Jul 13, 2021
1 parent aa06db7 commit a186833
Show file tree
Hide file tree
Showing 6 changed files with 94 additions and 55 deletions.
2 changes: 1 addition & 1 deletion src/components/NetworkAlert/NetworkAlert.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ export const OptimismWrapperBackgroundLightMode = css`
background: radial-gradient(92% 105% at 50% 7%, rgba(255, 58, 212, 0.04) 0%, rgba(255, 255, 255, 0.03) 100%),
radial-gradient(100% 97% at 0% 12%, rgba(235, 0, 255, 0.1) 0%, rgba(243, 19, 19, 0.1) 100%), hsla(0, 0%, 100%, 0.5);
`
const RootWrapper = styled.div<{ chainId: number; darkMode: boolean; logoUrl: string }>`
const RootWrapper = styled.div<{ chainId: SupportedChainId; darkMode: boolean; logoUrl: string }>`
${({ chainId, darkMode }) =>
[SupportedChainId.OPTIMISM, SupportedChainId.OPTIMISTIC_KOVAN].includes(chainId)
? darkMode
Expand Down
104 changes: 66 additions & 38 deletions src/components/SearchModal/ManageLists.tsx
Original file line number Diff line number Diff line change
@@ -1,36 +1,34 @@
import { memo, useCallback, useMemo, useRef, useState, useEffect } from 'react'
import { Settings, CheckCircle } from 'react-feather'
import { t, Trans } from '@lingui/macro'
import { TokenList } from '@uniswap/token-lists'
import Card from 'components/Card'
import { UNSUPPORTED_LIST_URLS } from 'constants/lists'
import { useListColor } from 'hooks/useColor'
import { useActiveWeb3React } from 'hooks/web3'
import { memo, useCallback, useEffect, useMemo, useRef, useState } from 'react'
import { CheckCircle, Settings } from 'react-feather'
import ReactGA from 'react-ga'
import { useAppDispatch, useAppSelector } from 'state/hooks'
import { usePopper } from 'react-popper'
import { useAppDispatch, useAppSelector } from 'state/hooks'
import styled from 'styled-components/macro'
import { useFetchListCallback } from '../../hooks/useFetchListCallback'
import { useOnClickOutside } from '../../hooks/useOnClickOutside'
import { TokenList } from '@uniswap/token-lists'
import { t, Trans } from '@lingui/macro'

import useTheme from '../../hooks/useTheme'
import useToggle from '../../hooks/useToggle'
import { acceptListUpdate, removeList, disableList, enableList } from '../../state/lists/actions'
import { useIsListActive, useAllLists, useActiveListUrls } from '../../state/lists/hooks'
import { ExternalLink, LinkStyledButton, TYPE, IconWrapper } from '../../theme'
import { acceptListUpdate, disableList, enableList, removeList } from '../../state/lists/actions'
import { useActiveListUrls, useAllLists, useIsListActive } from '../../state/lists/hooks'
import { ExternalLink, IconWrapper, LinkStyledButton, TYPE } from '../../theme'
import listVersionLabel from '../../utils/listVersionLabel'
import { parseENSAddress } from '../../utils/parseENSAddress'
import uriToHttp from '../../utils/uriToHttp'
import { ButtonEmpty, ButtonPrimary } from '../Button'

import Column, { AutoColumn } from '../Column'
import ListLogo from '../ListLogo'
import Row, { RowFixed, RowBetween } from '../Row'
import { PaddedColumn, SearchInput, Separator, SeparatorDark } from './styleds'
import { useListColor } from 'hooks/useColor'
import useTheme from '../../hooks/useTheme'
import Row, { RowBetween, RowFixed } from '../Row'
import ListToggle from '../Toggle/ListToggle'
import Card from 'components/Card'
import { CurrencyModalView } from './CurrencySearchModal'
import { UNSUPPORTED_LIST_URLS } from 'constants/lists'
import { PaddedColumn, SearchInput, Separator, SeparatorDark } from './styleds'

const Wrapper = styled(Column)`
width: 100%;
height: 100%;
`

Expand Down Expand Up @@ -80,8 +78,9 @@ const StyledListUrlText = styled(TYPE.main)<{ active: boolean }>`
color: ${({ theme, active }) => (active ? theme.white : theme.text2)};
`

const RowWrapper = styled(Row)<{ bgColor: string; active: boolean }>`
const RowWrapper = styled(Row)<{ bgColor: string; active: boolean; hasActiveTokens: boolean }>`
background-color: ${({ bgColor, active, theme }) => (active ? bgColor ?? 'transparent' : theme.bg2)};
opacity: ${({ hasActiveTokens }) => (hasActiveTokens ? 1 : 0.4)};
transition: 200ms;
align-items: center;
padding: 1rem;
Expand All @@ -93,10 +92,18 @@ function listUrlRowHTMLId(listUrl: string) {
}

const ListRow = memo(function ListRow({ listUrl }: { listUrl: string }) {
const { chainId } = useActiveWeb3React()
const listsByUrl = useAppSelector((state) => state.lists.byUrl)
const dispatch = useAppDispatch()
const { current: list, pendingUpdate: pending } = listsByUrl[listUrl]

const activeTokensOnThisChain = useMemo(() => {
if (!list || !chainId) {
return 0
}
return list.tokens.reduce((acc, cur) => (cur.chainId === chainId ? acc + 1 : acc), 0)
}, [chainId, list])

const theme = useTheme()
const listColor = useListColor(list?.logoURI)
const isActive = useIsListActive(listUrl)
Expand Down Expand Up @@ -130,7 +137,7 @@ const ListRow = memo(function ListRow({ listUrl }: { listUrl: string }) {
action: 'Start Remove List',
label: listUrl,
})
if (window.prompt(`Please confirm you would like to remove this list by typing REMOVE`) === `REMOVE`) {
if (window.prompt(t`Please confirm you would like to remove this list by typing REMOVE`) === `REMOVE`) {
ReactGA.event({
category: 'Lists',
action: 'Confirm Remove List',
Expand Down Expand Up @@ -161,7 +168,13 @@ const ListRow = memo(function ListRow({ listUrl }: { listUrl: string }) {
if (!list) return null

return (
<RowWrapper active={isActive} bgColor={listColor} key={listUrl} id={listUrlRowHTMLId(listUrl)}>
<RowWrapper
active={isActive}
hasActiveTokens={activeTokensOnThisChain > 0}
bgColor={listColor}
key={listUrl}
id={listUrlRowHTMLId(listUrl)}
>
{list.logoURI ? (
<ListLogo size="40px" style={{ marginRight: '1rem' }} logoURI={list.logoURI} alt={`${list.name} list logo`} />
) : (
Expand All @@ -173,7 +186,7 @@ const ListRow = memo(function ListRow({ listUrl }: { listUrl: string }) {
</Row>
<RowFixed mt="4px">
<StyledListUrlText active={isActive} mr="6px">
<Trans>{list.tokens.length} tokens</Trans>
<Trans>{activeTokensOnThisChain} tokens</Trans>
</StyledListUrlText>
<StyledMenu ref={node as any}>
<ButtonEmpty onClick={toggle} ref={setReferenceElement} padding="0">
Expand Down Expand Up @@ -226,20 +239,29 @@ export function ManageLists({
setImportList: (list: TokenList) => void
setListUrl: (url: string) => void
}) {
const { chainId } = useActiveWeb3React()
const theme = useTheme()

const [listUrlInput, setListUrlInput] = useState<string>('')

const lists = useAllLists()

const tokenCountByListName = useMemo<Record<string, number>>(
() =>
Object.values(lists).reduce((acc, { current: list }) => {
if (!list) {
return acc
}
return {
...acc,
[list.name]: list.tokens.reduce((count: number, token) => (token.chainId === chainId ? count + 1 : count), 0),
}
}, {}),
[chainId, lists]
)

// sort by active but only if not visible
const activeListUrls = useActiveListUrls()
const [activeCopy, setActiveCopy] = useState<string[] | undefined>()
useEffect(() => {
if (!activeCopy && activeListUrls) {
setActiveCopy(activeListUrls)
}
}, [activeCopy, activeListUrls])

const handleInput = useCallback((e) => {
setListUrlInput(e.target.value)
Expand All @@ -258,30 +280,36 @@ export function ManageLists({
// only show loaded lists, hide unsupported lists
return Boolean(lists[listUrl].current) && !Boolean(UNSUPPORTED_LIST_URLS.includes(listUrl))
})
.sort((u1, u2) => {
const { current: l1 } = lists[u1]
const { current: l2 } = lists[u2]
.sort((listUrlA, listUrlB) => {
const { current: listA } = lists[listUrlA]
const { current: listB } = lists[listUrlB]

// first filter on active lists
if (activeCopy?.includes(u1) && !activeCopy?.includes(u2)) {
if (activeListUrls?.includes(listUrlA) && !activeListUrls?.includes(listUrlB)) {
return -1
}
if (!activeCopy?.includes(u1) && activeCopy?.includes(u2)) {
if (!activeListUrls?.includes(listUrlA) && activeListUrls?.includes(listUrlB)) {
return 1
}

if (l1 && l2) {
return l1.name.toLowerCase() < l2.name.toLowerCase()
if (listA && listB) {
if (tokenCountByListName[listA.name] > tokenCountByListName[listB.name]) {
return -1
}
if (tokenCountByListName[listA.name] < tokenCountByListName[listB.name]) {
return 1
}
return listA.name.toLowerCase() < listB.name.toLowerCase()
? -1
: l1.name.toLowerCase() === l2.name.toLowerCase()
: listA.name.toLowerCase() === listB.name.toLowerCase()
? 0
: 1
}
if (l1) return -1
if (l2) return 1
if (listA) return -1
if (listB) return 1
return 0
})
}, [lists, activeCopy])
}, [lists, activeListUrls, tokenCountByListName])

// temporary fetched list for import flow
const [tempList, setTempList] = useState<TokenList>()
Expand Down
2 changes: 1 addition & 1 deletion src/constants/lists.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ export const UNSUPPORTED_LIST_URLS: string[] = [BA_LIST]
export const DEFAULT_LIST_OF_LISTS: string[] = [
COMPOUND_LIST,
AAVE_LIST,
OPTIMISM_LIST,
CMC_ALL_LIST,
CMC_STABLECOIN,
UMA_LIST,
Expand All @@ -28,6 +27,7 @@ export const DEFAULT_LIST_OF_LISTS: string[] = [
ROLL_LIST,
COINGECKO_LIST,
KLEROS_LIST,
OPTIMISM_LIST,
GEMINI_LIST,
...UNSUPPORTED_LIST_URLS, // need to load unsupported tokens as well
]
Expand Down
5 changes: 5 additions & 0 deletions src/constants/routing.ts
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,12 @@ export const COMMON_BASES: ChainCurrencyList = {
ExtendedEther.onChain(SupportedChainId.ARBITRUM_ONE),
WETH9_EXTENDED[SupportedChainId.ARBITRUM_ONE],
],
[SupportedChainId.ARBITRUM_RINKEBY]: [
ExtendedEther.onChain(SupportedChainId.ARBITRUM_RINKEBY),
WETH9_EXTENDED[SupportedChainId.ARBITRUM_RINKEBY],
],
[SupportedChainId.OPTIMISM]: [ExtendedEther.onChain(SupportedChainId.OPTIMISM)],
[SupportedChainId.OPTIMISTIC_KOVAN]: [ExtendedEther.onChain(SupportedChainId.OPTIMISTIC_KOVAN)],
}

// used to construct the list of all pairs we consider by default in the frontend
Expand Down
17 changes: 9 additions & 8 deletions src/state/lists/hooks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,14 +46,15 @@ export function useAllLists(): AppState['lists']['byUrl'] {
return useAppSelector((state) => state.lists.byUrl)
}

function combineMaps(map1: TokenAddressMap, map2: TokenAddressMap): TokenAddressMap {
return {
[1]: { ...map1[1], ...map2[1] },
[4]: { ...map1[4], ...map2[4] },
[3]: { ...map1[3], ...map2[3] },
[42]: { ...map1[42], ...map2[42] },
[5]: { ...map1[5], ...map2[5] },
}
export function combineMaps(map1: TokenAddressMap, map2: TokenAddressMap): TokenAddressMap {
const chainIds = Object.keys({ ...map1, ...map2 }).map((id) => parseInt(id))
return chainIds.reduce(
(acc, chainId) => ({
...acc,
[chainId]: { ...map2[chainId], ...map1[chainId] },
}),
{}
)
}

// merge tokens contained within lists from urls
Expand Down
19 changes: 12 additions & 7 deletions src/state/lists/updater.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,18 @@
import { useAllLists } from 'state/lists/hooks'
import { getVersionUpgrade, minVersionBump, VersionUpgrade } from '@uniswap/token-lists'
import { SupportedChainId } from 'constants/chains'
import { OPTIMISM_LIST, UNSUPPORTED_LIST_URLS } from 'constants/lists'
import { useCallback, useEffect } from 'react'

import { useActiveWeb3React } from '../../hooks/web3'
import { useAppDispatch } from 'state/hooks'
import { useAllLists } from 'state/lists/hooks'
import { useFetchListCallback } from '../../hooks/useFetchListCallback'
import useInterval from '../../hooks/useInterval'
import useIsWindowVisible from '../../hooks/useIsWindowVisible'
import { acceptListUpdate } from './actions'
import { useActiveWeb3React } from '../../hooks/web3'
import { acceptListUpdate, enableList } from './actions'
import { useActiveListUrls } from './hooks'
import { UNSUPPORTED_LIST_URLS } from 'constants/lists'
import { useAppDispatch } from 'state/hooks'

export default function Updater(): null {
const { library } = useActiveWeb3React()
const { chainId, library } = useActiveWeb3React()
const dispatch = useAppDispatch()
const isWindowVisible = useIsWindowVisible()

Expand All @@ -28,6 +28,11 @@ export default function Updater(): null {
)
}, [fetchList, isWindowVisible, lists])

useEffect(() => {
if (chainId && [SupportedChainId.OPTIMISM, SupportedChainId.OPTIMISTIC_KOVAN].includes(chainId)) {
dispatch(enableList(OPTIMISM_LIST))
}
}, [chainId, dispatch])
// fetch all lists every 10 minutes, but only after we initialize library
useInterval(fetchAllListsCallback, library ? 1000 * 60 * 10 : null)

Expand Down

0 comments on commit a186833

Please sign in to comment.