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