Commit 4806c690 authored by Zach Pomerantz's avatar Zach Pomerantz Committed by GitHub

fix: map wrapped tokens to native tokens for top/trending (#4855)

* fix: map wrapped to native for trending tokens

* fix: close top tokens subscription

* fix: map wrapped to native for top tokens

* fix: link to native from native

* refactor: unwrapToken.ts

* fix: mv query to effect

* fix: native token logos

* fix: use NATIVE_CHAIN_ID

* fix: rm todo

* fix: include NATIVE_CHAIN_ID

* fix: NATIVE_CHAIN_ID import
parent c9f33300
......@@ -64,8 +64,6 @@ export enum WALLET_CONNECTION_RESULT {
FAILED = 'Failed',
}
export const NATIVE_CHAIN_ID = 'NATIVE'
export enum SWAP_PRICE_UPDATE_USER_RESPONSE {
ACCEPTED = 'Accepted',
REJECTED = 'Rejected',
......
import { Currency, CurrencyAmount, Percent, Token } from '@uniswap/sdk-core'
import { NATIVE_CHAIN_ID } from './constants'
import { NATIVE_CHAIN_ID } from 'constants/tokens'
export const getDurationUntilTimestampSeconds = (futureTimestampInSecondsSinceEpoch?: number): number | undefined => {
if (!futureTimestampInSecondsSinceEpoch) return undefined
......
......@@ -10,11 +10,10 @@ import { PriceDurations, PricePoint, SingleTokenData } from 'graphql/data/Token'
import { TopToken } from 'graphql/data/TopTokens'
import { CHAIN_NAME_TO_CHAIN_ID, TimePeriod } from 'graphql/data/util'
import { useAtomValue } from 'jotai/utils'
import useCurrencyLogoURIs, { getTokenLogoURI } from 'lib/hooks/useCurrencyLogoURIs'
import useCurrencyLogoURIs from 'lib/hooks/useCurrencyLogoURIs'
import { useMemo } from 'react'
import styled from 'styled-components/macro'
import { textFadeIn } from 'theme/animations'
import { isAddress } from 'utils'
import { filterTimeAtom, useIsFavorited, useToggleFavorite } from '../state'
import { ClickFavorited, FavoriteIcon, L2NetworkLogo, LogoContainer } from '../TokenTable/TokenRow'
......@@ -61,13 +60,12 @@ export function useTokenLogoURI(
token: NonNullable<SingleTokenData> | NonNullable<TopToken>,
nativeCurrency?: Token | NativeCurrency
) {
const checksummedAddress = isAddress(token.address)
const chainId = CHAIN_NAME_TO_CHAIN_ID[token.chain]
return (
useCurrencyLogoURIs(nativeCurrency)[0] ??
(checksummedAddress && getTokenLogoURI(checksummedAddress, chainId)) ??
token.project?.logoUrl
)
return [
...useCurrencyLogoURIs(nativeCurrency),
...useCurrencyLogoURIs({ ...token, chainId }),
token.project?.logoUrl,
][0]
}
export default function ChartSection({
......
......@@ -3,6 +3,7 @@ import { TokenInfo } from '@uniswap/token-lists'
import store from '../state'
import { UNI_EXTENDED_LIST, UNI_LIST, UNSUPPORTED_LIST_URLS } from './lists'
import brokenTokenList from './tokenLists/broken.tokenlist.json'
import { NATIVE_CHAIN_ID } from './tokens'
export enum TOKEN_LIST_TYPES {
UNI_DEFAULT = 1,
......@@ -47,7 +48,7 @@ class TokenSafetyLookupTable {
if (!this.dict) {
this.dict = this.createMap()
}
if (address === 'native') {
if (address === NATIVE_CHAIN_ID.toLowerCase()) {
return TOKEN_LIST_TYPES.UNI_DEFAULT
}
return this.dict[address] ?? TOKEN_LIST_TYPES.UNKNOWN
......
......@@ -4,6 +4,8 @@ import invariant from 'tiny-invariant'
import { UNI_ADDRESS } from './addresses'
import { SupportedChainId } from './chains'
export const NATIVE_CHAIN_ID = 'NATIVE'
export const USDC_MAINNET = new Token(
SupportedChainId.MAINNET,
'0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48',
......
......@@ -15,7 +15,7 @@ import { fetchQuery, useLazyLoadQuery, useRelayEnvironment } from 'react-relay'
import type { Chain, TopTokens100Query } from './__generated__/TopTokens100Query.graphql'
import { TopTokensSparklineQuery } from './__generated__/TopTokensSparklineQuery.graphql'
import { filterPrices, PricePoint } from './Token'
import { toHistoryDuration } from './util'
import { CHAIN_NAME_TO_CHAIN_ID, toHistoryDuration, unwrapToken } from './util'
const topTokens100Query = graphql`
query TopTokens100Query($duration: HistoryDuration!, $chain: Chain!) {
......@@ -66,13 +66,11 @@ const tokenSparklineQuery = graphql`
export type PrefetchedTopToken = NonNullable<TopTokens100Query['response']['topTokens']>[number]
function useSortedTokens(tokens: TopTokens100Query['response']['topTokens'] | undefined) {
function useSortedTokens(tokens: NonNullable<TopTokens100Query['response']['topTokens']>) {
const sortMethod = useAtomValue(sortMethodAtom)
const sortAscending = useAtomValue(sortAscendingAtom)
return useMemo(() => {
if (!tokens) return []
let tokenArray = Array.from(tokens)
switch (sortMethod) {
case TokenSortMethod.PRICE:
......@@ -97,7 +95,7 @@ function useSortedTokens(tokens: TopTokens100Query['response']['topTokens'] | un
}, [tokens, sortMethod, sortAscending])
}
function useFilteredTokens(tokens: NonNullable<TopTokens100Query['response']>['topTokens']) {
function useFilteredTokens(tokens: NonNullable<TopTokens100Query['response']['topTokens']>) {
const filterString = useAtomValue(filterStringAtom)
const favorites = useAtomValue(favoritesAtom)
const showFavorites = useAtomValue(showFavoritesAtom)
......@@ -105,10 +103,6 @@ function useFilteredTokens(tokens: NonNullable<TopTokens100Query['response']>['t
const lowercaseFilterString = useMemo(() => filterString.toLowerCase(), [filterString])
return useMemo(() => {
if (!tokens) {
return []
}
let returnTokens = tokens
if (showFavorites) {
returnTokens = returnTokens?.filter((token) => token?.address && favorites.includes(token.address))
......@@ -136,35 +130,35 @@ interface UseTopTokensReturnValue {
}
export function useTopTokens(chain: Chain): UseTopTokensReturnValue {
const chainId = CHAIN_NAME_TO_CHAIN_ID[chain]
const duration = toHistoryDuration(useAtomValue(filterTimeAtom))
const environment = useRelayEnvironment()
const [sparklines, setSparklines] = useState<SparklineMap>({})
useMemo(() => {
fetchQuery<TopTokensSparklineQuery>(environment, tokenSparklineQuery, {
duration,
chain,
}).subscribe({
next(data) {
const map: SparklineMap = {}
data.topTokens?.forEach(
(current) => current?.address && (map[current.address] = filterPrices(current?.market?.priceHistory))
)
setSparklines(map)
},
})
}, [chain, duration, environment])
useEffect(() => {
const subscription = fetchQuery<TopTokensSparklineQuery>(environment, tokenSparklineQuery, { duration, chain })
.map((data) => ({
topTokens: data.topTokens?.map((token) => unwrapToken(chainId, token)),
}))
.subscribe({
next(data) {
const map: SparklineMap = {}
data.topTokens?.forEach(
(current) => current?.address && (map[current.address] = filterPrices(current?.market?.priceHistory))
)
setSparklines(map)
},
})
return () => subscription.unsubscribe()
}, [chain, chainId, duration, environment])
useEffect(() => {
setSparklines({})
}, [duration])
const tokens = useFilteredTokens(
useLazyLoadQuery<TopTokens100Query>(topTokens100Query, { duration, chain }).topTokens
)
return {
tokens: useSortedTokens(tokens),
sparklines,
}
const { topTokens } = useLazyLoadQuery<TopTokens100Query>(topTokens100Query, { duration, chain })
const mappedTokens = useMemo(() => topTokens?.map((token) => unwrapToken(chainId, token)) ?? [], [chainId, topTokens])
const filteredTokens = useFilteredTokens(mappedTokens)
const sortedTokens = useSortedTokens(filteredTokens)
return useMemo(() => ({ tokens: sortedTokens, sparklines }), [sortedTokens, sparklines])
}
import { NATIVE_CHAIN_ID } from 'analytics/constants'
import { SupportedChainId } from 'constants/chains'
import { ZERO_ADDRESS } from 'constants/misc'
import { NATIVE_CHAIN_ID, nativeOnChain, WRAPPED_NATIVE_CURRENCY } from 'constants/tokens'
import { Chain, HistoryDuration } from './__generated__/TokenQuery.graphql'
......@@ -88,3 +88,14 @@ export function getTokenDetailsURL(address: string, chainName?: Chain, chainId?:
return ''
}
}
export function unwrapToken<T extends { address: string | null } | null>(chainId: number, token: T): T {
if (!token?.address) return token
const address = token.address.toLowerCase()
const nativeAddress = WRAPPED_NATIVE_CURRENCY[chainId]?.address.toLowerCase()
if (address !== nativeAddress) return token
const nativeToken = nativeOnChain(chainId)
return { ...token, ...nativeToken, address: NATIVE_CHAIN_ID }
}
......@@ -4,7 +4,7 @@ import { Weth } from 'abis/types'
import WETH_ABI from 'abis/weth.json'
import { ALL_SUPPORTED_CHAIN_IDS, isSupportedChain, SupportedChainId, TESTNET_CHAIN_IDS } from 'constants/chains'
import { RPC_PROVIDERS } from 'constants/providers'
import { nativeOnChain, WRAPPED_NATIVE_CURRENCY } from 'constants/tokens'
import { NATIVE_CHAIN_ID, nativeOnChain, WRAPPED_NATIVE_CURRENCY } from 'constants/tokens'
import { BaseVariant, FeatureFlag, useBaseFlag } from 'featureFlags'
import { useCallback, useEffect, useState } from 'react'
import { getContract } from 'utils'
......@@ -56,7 +56,7 @@ export function useMultiNetworkAddressBalances({ ownerAddress, tokenAddress }: u
}
const isConnecteToTestnet = connectedChainId ? TESTNET_CHAIN_IDS.includes(connectedChainId) : false
setLoading(true)
const isNative = tokenAddress === 'NATIVE'
const isNative = tokenAddress === NATIVE_CHAIN_ID
const promises: Promise<any>[] = []
const isWrappedNative = ALL_SUPPORTED_CHAIN_IDS.some(
......
import { Currency } from '@uniswap/sdk-core'
import { SupportedChainId } from 'constants/chains'
import useHttpLocations from 'hooks/useHttpLocations'
import { useMemo } from 'react'
import { WrappedTokenInfo } from 'state/lists/wrappedTokenInfo'
import { isAddress } from 'utils'
import EthereumLogo from '../../assets/images/ethereum-logo.png'
import CeloLogo from '../../assets/svg/celo_logo.svg'
import MaticLogo from '../../assets/svg/matic-token-icon.svg'
import { isCelo, nativeOnChain } from '../../constants/tokens'
import { isCelo, NATIVE_CHAIN_ID, nativeOnChain } from '../../constants/tokens'
type Network = 'ethereum' | 'arbitrum' | 'optimism' | 'polygon'
......@@ -54,15 +53,27 @@ export function getTokenLogoURI(address: string, chainId: SupportedChainId = Sup
}
}
export default function useCurrencyLogoURIs(currency?: Currency | null): string[] {
const locations = useHttpLocations(currency instanceof WrappedTokenInfo ? currency.logoURI : undefined)
export default function useCurrencyLogoURIs(
currency:
| {
isNative?: boolean
isToken?: boolean
address?: string
chainId: number
logoURI?: string
}
| null
| undefined
): string[] {
const locations = useHttpLocations(currency?.logoURI)
return useMemo(() => {
const logoURIs = [...locations]
if (currency) {
if (currency.isNative) {
if (currency.isNative || currency.address === NATIVE_CHAIN_ID) {
logoURIs.push(getNativeLogoURI(currency.chainId))
} else if (currency.isToken) {
const logoURI = getTokenLogoURI(currency.address, currency.chainId)
} else if (currency.isToken || currency.address) {
const checksummedAddress = isAddress(currency.address)
const logoURI = checksummedAddress && getTokenLogoURI(checksummedAddress, currency.chainId)
if (logoURI) {
logoURIs.push(logoURI)
}
......
import { unwrapToken } from 'graphql/data/util'
import { FungibleToken } from '../../types'
export const fetchTrendingTokens = async (numTokens?: number): Promise<FungibleToken[]> => {
......@@ -10,7 +12,6 @@ export const fetchTrendingTokens = async (numTokens?: number): Promise<FungibleT
},
})
const data = await r.json()
return data.data
const { data } = (await r.json()) as { data: FungibleToken[] }
return data.map((token) => unwrapToken(token.chainId, token))
}
......@@ -11,7 +11,7 @@ import StatsSection from 'components/Tokens/TokenDetails/StatsSection'
import TokenSafetyMessage from 'components/TokenSafety/TokenSafetyMessage'
import TokenSafetyModal from 'components/TokenSafety/TokenSafetyModal'
import Widget, { WIDGET_WIDTH } from 'components/Widget'
import { isCelo, nativeOnChain } from 'constants/tokens'
import { isCelo, NATIVE_CHAIN_ID, nativeOnChain } from 'constants/tokens'
import { checkWarning } from 'constants/tokenSafety'
import { Chain } from 'graphql/data/__generated__/TokenQuery.graphql'
import { useTokenQuery } from 'graphql/data/Token'
......@@ -72,7 +72,7 @@ export default function TokenDetails() {
const pageChainId = CHAIN_NAME_TO_CHAIN_ID[currentChainName]
const nativeCurrency = nativeOnChain(pageChainId)
const timePeriod = useAtomValue(filterTimeAtom)
const isNative = tokenAddressParam === 'NATIVE'
const isNative = tokenAddressParam === NATIVE_CHAIN_ID
const tokenQueryAddress = isNative ? nativeCurrency.wrapped.address : tokenAddressParam
const [tokenQueryData, prices] = useTokenQuery(tokenQueryAddress ?? '', currentChainName, timePeriod)
......
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