fix: render invalid address warnings on token details (#5260)
* initial commit * polishing loading state * small refactors * polished on chain token network switching * fixed commited merge conflict * small refactor * fixed merge conflicts * PR comments and refactors * fix unintented trace property change * second round of comments
This commit is contained in:
parent
1536e18784
commit
9662344e24
4
src/assets/svg/eye.svg
Normal file
4
src/assets/svg/eye.svg
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
<svg width="54" height="40" viewBox="0 0 54 40" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M1.3335 19.9997C1.3335 19.9997 10.6668 1.33301 27.0002 1.33301C43.3335 1.33301 52.6668 19.9997 52.6668 19.9997C52.6668 19.9997 43.3335 38.6663 27.0002 38.6663C10.6668 38.6663 1.3335 19.9997 1.3335 19.9997Z" stroke="#98A1C0" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<path d="M27.0002 26.9997C30.8662 26.9997 34.0002 23.8657 34.0002 19.9997C34.0002 16.1337 30.8662 12.9997 27.0002 12.9997C23.1342 12.9997 20.0002 16.1337 20.0002 19.9997C20.0002 23.8657 23.1342 26.9997 27.0002 26.9997Z" stroke="#98A1C0" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 705 B |
54
src/components/Tokens/TokenDetails/InvalidTokenDetails.tsx
Normal file
54
src/components/Tokens/TokenDetails/InvalidTokenDetails.tsx
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
import { Trans } from '@lingui/macro'
|
||||||
|
import { useNavigate } from 'react-router-dom'
|
||||||
|
import styled from 'styled-components/macro'
|
||||||
|
|
||||||
|
import { ReactComponent as EyeIcon } from '../../../assets/svg/eye.svg'
|
||||||
|
|
||||||
|
const InvalidDetailsContainer = styled.div`
|
||||||
|
padding-top: 128px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
`
|
||||||
|
|
||||||
|
const InvalidDetailsText = styled.span`
|
||||||
|
margin-top: 28px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
|
||||||
|
text-align: center;
|
||||||
|
|
||||||
|
color: ${({ theme }) => theme.textSecondary};
|
||||||
|
font-size: 20px;
|
||||||
|
font-weight: 500;
|
||||||
|
line-height: 28px;
|
||||||
|
`
|
||||||
|
|
||||||
|
const TokenExploreButton = styled.button`
|
||||||
|
border: none;
|
||||||
|
border-radius: 12px;
|
||||||
|
background-color: ${({ theme }) => theme.accentAction};
|
||||||
|
padding: 12px 16px;
|
||||||
|
|
||||||
|
color: ${({ theme }) => theme.textPrimary};
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 600;
|
||||||
|
`
|
||||||
|
|
||||||
|
export default function InvalidTokenDetails({ chainName }: { chainName?: string }) {
|
||||||
|
const navigate = useNavigate()
|
||||||
|
return (
|
||||||
|
<InvalidDetailsContainer>
|
||||||
|
<EyeIcon />
|
||||||
|
<InvalidDetailsText>
|
||||||
|
{chainName ? (
|
||||||
|
<Trans>{`This token doesn't exist on ${chainName}`}</Trans>
|
||||||
|
) : (
|
||||||
|
<Trans>This token doesn't exist</Trans>
|
||||||
|
)}
|
||||||
|
</InvalidDetailsText>
|
||||||
|
<TokenExploreButton onClick={() => navigate('/tokens')}>
|
||||||
|
<Trans>Explore tokens</Trans>
|
||||||
|
</TokenExploreButton>
|
||||||
|
</InvalidDetailsContainer>
|
||||||
|
)
|
||||||
|
}
|
@ -114,9 +114,7 @@ export function PriceChart({ width, height, prices, timePeriod }: PriceChartProp
|
|||||||
|
|
||||||
// set display price to ending price when prices have changed.
|
// set display price to ending price when prices have changed.
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (prices) {
|
|
||||||
setDisplayPrice(endingPrice)
|
setDisplayPrice(endingPrice)
|
||||||
}
|
|
||||||
}, [prices, endingPrice])
|
}, [prices, endingPrice])
|
||||||
const [crosshair, setCrosshair] = useState<number | null>(null)
|
const [crosshair, setCrosshair] = useState<number | null>(null)
|
||||||
|
|
||||||
|
@ -1,7 +1,8 @@
|
|||||||
import { Trans } from '@lingui/macro'
|
import { Trans } from '@lingui/macro'
|
||||||
import { Trace } from '@uniswap/analytics'
|
import { Trace } from '@uniswap/analytics'
|
||||||
import { PageName } from '@uniswap/analytics-events'
|
import { PageName } from '@uniswap/analytics-events'
|
||||||
import { Currency, Token } from '@uniswap/sdk-core'
|
import { Currency } from '@uniswap/sdk-core'
|
||||||
|
import { useWeb3React } from '@web3-react/core'
|
||||||
import CurrencyLogo from 'components/Logo/CurrencyLogo'
|
import CurrencyLogo from 'components/Logo/CurrencyLogo'
|
||||||
import { AboutSection } from 'components/Tokens/TokenDetails/About'
|
import { AboutSection } from 'components/Tokens/TokenDetails/About'
|
||||||
import AddressSection from 'components/Tokens/TokenDetails/AddressSection'
|
import AddressSection from 'components/Tokens/TokenDetails/AddressSection'
|
||||||
@ -24,21 +25,24 @@ import TokenSafetyMessage from 'components/TokenSafety/TokenSafetyMessage'
|
|||||||
import TokenSafetyModal from 'components/TokenSafety/TokenSafetyModal'
|
import TokenSafetyModal from 'components/TokenSafety/TokenSafetyModal'
|
||||||
import Widget from 'components/Widget'
|
import Widget from 'components/Widget'
|
||||||
import { getChainInfo } from 'constants/chainInfo'
|
import { getChainInfo } from 'constants/chainInfo'
|
||||||
import { DEFAULT_ERC20_DECIMALS, NATIVE_CHAIN_ID, nativeOnChain } from 'constants/tokens'
|
import { NATIVE_CHAIN_ID, nativeOnChain } from 'constants/tokens'
|
||||||
import { checkWarning } from 'constants/tokenSafety'
|
import { checkWarning } from 'constants/tokenSafety'
|
||||||
import { TokenPriceQuery } from 'graphql/data/__generated__/TokenPriceQuery.graphql'
|
import { TokenPriceQuery } from 'graphql/data/__generated__/TokenPriceQuery.graphql'
|
||||||
import { Chain, TokenQuery } from 'graphql/data/Token'
|
import { Chain, TokenQuery, TokenQueryData } from 'graphql/data/Token'
|
||||||
import { QueryToken, tokenQuery } from 'graphql/data/Token'
|
import { QueryToken, tokenQuery } from 'graphql/data/Token'
|
||||||
import { CHAIN_NAME_TO_CHAIN_ID } from 'graphql/data/util'
|
import { CHAIN_NAME_TO_CHAIN_ID, getTokenDetailsURL } from 'graphql/data/util'
|
||||||
import { useIsUserAddedTokenOnChain } from 'hooks/Tokens'
|
import { useIsUserAddedTokenOnChain } from 'hooks/Tokens'
|
||||||
import { useOnGlobalChainSwitch } from 'hooks/useGlobalChainSwitch'
|
import { useOnGlobalChainSwitch } from 'hooks/useGlobalChainSwitch'
|
||||||
|
import { UNKNOWN_TOKEN_SYMBOL, useTokenFromActiveNetwork } from 'lib/hooks/useCurrency'
|
||||||
import { useCallback, useMemo, useState, useTransition } from 'react'
|
import { useCallback, useMemo, useState, useTransition } from 'react'
|
||||||
import { ArrowLeft } from 'react-feather'
|
import { ArrowLeft } from 'react-feather'
|
||||||
import { PreloadedQuery, usePreloadedQuery } from 'react-relay'
|
import { PreloadedQuery, usePreloadedQuery } from 'react-relay'
|
||||||
import { useNavigate } from 'react-router-dom'
|
import { useNavigate } from 'react-router-dom'
|
||||||
import styled from 'styled-components/macro'
|
import styled from 'styled-components/macro'
|
||||||
|
import { isAddress } from 'utils'
|
||||||
|
|
||||||
import { RefetchPricesFunction } from './ChartSection'
|
import { RefetchPricesFunction } from './ChartSection'
|
||||||
|
import InvalidTokenDetails from './InvalidTokenDetails'
|
||||||
|
|
||||||
const TokenSymbol = styled.span`
|
const TokenSymbol = styled.span`
|
||||||
text-transform: uppercase;
|
text-transform: uppercase;
|
||||||
@ -50,56 +54,100 @@ const TokenActions = styled.div`
|
|||||||
color: ${({ theme }) => theme.textSecondary};
|
color: ${({ theme }) => theme.textSecondary};
|
||||||
`
|
`
|
||||||
|
|
||||||
|
function useOnChainToken(address: string | undefined, skip: boolean) {
|
||||||
|
const token = useTokenFromActiveNetwork(skip || !address ? undefined : address)
|
||||||
|
|
||||||
|
if (skip || !address || (token && token?.symbol === UNKNOWN_TOKEN_SYMBOL)) {
|
||||||
|
return undefined
|
||||||
|
} else {
|
||||||
|
return token
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Selects most relevant token based on data available, preferring native > query > on-chain
|
||||||
|
// Token will be null if still loading from on-chain, and undefined if unavailable
|
||||||
|
function useRelevantToken(
|
||||||
|
address: string | undefined,
|
||||||
|
pageChainId: number,
|
||||||
|
tokenQueryData: TokenQueryData | undefined
|
||||||
|
) {
|
||||||
|
const { chainId: activeChainId } = useWeb3React()
|
||||||
|
const queryToken = useMemo(() => {
|
||||||
|
if (!address) return undefined
|
||||||
|
if (address === NATIVE_CHAIN_ID) return nativeOnChain(pageChainId)
|
||||||
|
if (tokenQueryData) return new QueryToken(tokenQueryData)
|
||||||
|
return undefined
|
||||||
|
}, [pageChainId, address, tokenQueryData])
|
||||||
|
// fetches on-chain token if query data is missing and page chain matches global chain (else fetch won't work)
|
||||||
|
const skipOnChainFetch = Boolean(queryToken) || pageChainId !== activeChainId
|
||||||
|
const onChainToken = useOnChainToken(address, skipOnChainFetch)
|
||||||
|
|
||||||
|
return useMemo(
|
||||||
|
() => ({ token: queryToken ?? onChainToken, didFetchFromChain: !queryToken }),
|
||||||
|
[onChainToken, queryToken]
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
type TokenDetailsProps = {
|
type TokenDetailsProps = {
|
||||||
tokenAddress: string | undefined
|
urlAddress: string | undefined
|
||||||
chain: Chain
|
chain: Chain
|
||||||
tokenQueryReference: PreloadedQuery<TokenQuery>
|
tokenQueryReference: PreloadedQuery<TokenQuery>
|
||||||
priceQueryReference: PreloadedQuery<TokenPriceQuery> | null | undefined
|
priceQueryReference: PreloadedQuery<TokenPriceQuery> | null | undefined
|
||||||
refetchTokenPrices: RefetchPricesFunction
|
refetchTokenPrices: RefetchPricesFunction
|
||||||
}
|
}
|
||||||
export default function TokenDetails({
|
export default function TokenDetails({
|
||||||
tokenAddress,
|
urlAddress,
|
||||||
chain,
|
chain,
|
||||||
tokenQueryReference,
|
tokenQueryReference,
|
||||||
priceQueryReference,
|
priceQueryReference,
|
||||||
refetchTokenPrices,
|
refetchTokenPrices,
|
||||||
}: TokenDetailsProps) {
|
}: TokenDetailsProps) {
|
||||||
if (!tokenAddress) {
|
if (!urlAddress) {
|
||||||
throw new Error(`Invalid token details route: tokenAddress param is undefined`)
|
throw new Error('Invalid token details route: tokenAddress param is undefined')
|
||||||
}
|
}
|
||||||
|
const address = useMemo(
|
||||||
|
() => (urlAddress === NATIVE_CHAIN_ID ? urlAddress : isAddress(urlAddress) || undefined),
|
||||||
|
[urlAddress]
|
||||||
|
)
|
||||||
|
|
||||||
const pageChainId = CHAIN_NAME_TO_CHAIN_ID[chain]
|
const pageChainId = CHAIN_NAME_TO_CHAIN_ID[chain]
|
||||||
const nativeCurrency = nativeOnChain(pageChainId)
|
|
||||||
const isNative = tokenAddress === NATIVE_CHAIN_ID
|
|
||||||
|
|
||||||
const tokenQueryData = usePreloadedQuery(tokenQuery, tokenQueryReference).tokens?.[0]
|
const tokenQueryData = usePreloadedQuery(tokenQuery, tokenQueryReference).tokens?.[0]
|
||||||
|
const crossChainMap = useMemo(
|
||||||
|
() =>
|
||||||
|
tokenQueryData?.project?.tokens.reduce((map, current) => {
|
||||||
|
if (current) map[current.chain] = current.address
|
||||||
|
return map
|
||||||
|
}, {} as { [key: string]: string | undefined }) ?? {},
|
||||||
|
[tokenQueryData]
|
||||||
|
)
|
||||||
|
|
||||||
const token = useMemo(() => {
|
const { token, didFetchFromChain } = useRelevantToken(address, pageChainId, tokenQueryData)
|
||||||
if (isNative) return nativeCurrency
|
|
||||||
if (tokenQueryData) return new QueryToken(tokenQueryData)
|
const tokenWarning = address ? checkWarning(address) : null
|
||||||
return new Token(pageChainId, tokenAddress, DEFAULT_ERC20_DECIMALS)
|
|
||||||
}, [isNative, nativeCurrency, pageChainId, tokenAddress, tokenQueryData])
|
|
||||||
const tokenWarning = tokenAddress ? checkWarning(tokenAddress) : null
|
|
||||||
const isBlockedToken = tokenWarning?.canProceed === false
|
const isBlockedToken = tokenWarning?.canProceed === false
|
||||||
|
|
||||||
const navigate = useNavigate()
|
const navigate = useNavigate()
|
||||||
|
|
||||||
// Wrapping navigate in a transition prevents Suspense from unnecessarily showing fallbacks again.
|
// Wrapping navigate in a transition prevents Suspense from unnecessarily showing fallbacks again.
|
||||||
const [isPending, startTokenTransition] = useTransition()
|
const [isPending, startTokenTransition] = useTransition()
|
||||||
const navigateToTokenForChain = useCallback(
|
const navigateToTokenForChain = useCallback(
|
||||||
(chain: Chain) => {
|
(update: Chain) => {
|
||||||
const chainName = chain.toLowerCase()
|
|
||||||
const token = tokenQueryData?.project?.tokens.find((token) => token.chain === chain && token.address)
|
|
||||||
const address = isNative ? NATIVE_CHAIN_ID : token?.address
|
|
||||||
if (!address) return
|
if (!address) return
|
||||||
startTokenTransition(() => navigate(`/tokens/${chainName}/${address}`))
|
const bridgedAddress = crossChainMap[update]
|
||||||
|
if (bridgedAddress) {
|
||||||
|
startTokenTransition(() => navigate(getTokenDetailsURL(bridgedAddress, update)))
|
||||||
|
} else if (didFetchFromChain || token?.isNative) {
|
||||||
|
startTokenTransition(() => navigate(getTokenDetailsURL(address, update)))
|
||||||
|
}
|
||||||
},
|
},
|
||||||
[isNative, navigate, startTokenTransition, tokenQueryData?.project?.tokens]
|
[address, crossChainMap, didFetchFromChain, navigate, token?.isNative]
|
||||||
)
|
)
|
||||||
useOnGlobalChainSwitch(navigateToTokenForChain)
|
useOnGlobalChainSwitch(navigateToTokenForChain)
|
||||||
|
|
||||||
const navigateToWidgetSelectedToken = useCallback(
|
const navigateToWidgetSelectedToken = useCallback(
|
||||||
(token: Currency) => {
|
(token: Currency) => {
|
||||||
const address = token.isNative ? NATIVE_CHAIN_ID : token.address
|
const address = token.isNative ? NATIVE_CHAIN_ID : token.address
|
||||||
startTokenTransition(() => navigate(`/tokens/${chain.toLowerCase()}/${address}`))
|
startTokenTransition(() => navigate(getTokenDetailsURL(address, chain)))
|
||||||
},
|
},
|
||||||
[chain, navigate]
|
[chain, navigate]
|
||||||
)
|
)
|
||||||
@ -107,7 +155,7 @@ export default function TokenDetails({
|
|||||||
const [continueSwap, setContinueSwap] = useState<{ resolve: (value: boolean | PromiseLike<boolean>) => void }>()
|
const [continueSwap, setContinueSwap] = useState<{ resolve: (value: boolean | PromiseLike<boolean>) => void }>()
|
||||||
|
|
||||||
// Show token safety modal if Swap-reviewing a warning token, at all times if the current token is blocked
|
// Show token safety modal if Swap-reviewing a warning token, at all times if the current token is blocked
|
||||||
const shouldShowSpeedbump = !useIsUserAddedTokenOnChain(tokenAddress, pageChainId) && tokenWarning !== null
|
const shouldShowSpeedbump = !useIsUserAddedTokenOnChain(address, pageChainId) && tokenWarning !== null
|
||||||
const onReviewSwapClick = useCallback(
|
const onReviewSwapClick = useCallback(
|
||||||
() => new Promise<boolean>((resolve) => (shouldShowSpeedbump ? setContinueSwap({ resolve }) : resolve(true))),
|
() => new Promise<boolean>((resolve) => (shouldShowSpeedbump ? setContinueSwap({ resolve }) : resolve(true))),
|
||||||
[shouldShowSpeedbump]
|
[shouldShowSpeedbump]
|
||||||
@ -123,10 +171,18 @@ export default function TokenDetails({
|
|||||||
|
|
||||||
const L2Icon = getChainInfo(pageChainId)?.circleLogoUrl
|
const L2Icon = getChainInfo(pageChainId)?.circleLogoUrl
|
||||||
|
|
||||||
|
// address will never be undefined if token is defined; address is checked here to appease typechecker
|
||||||
|
if (token === undefined || !address) {
|
||||||
|
return <InvalidTokenDetails chainName={address && getChainInfo(pageChainId)?.label} />
|
||||||
|
}
|
||||||
return (
|
return (
|
||||||
<Trace page={PageName.TOKEN_DETAILS_PAGE} properties={{ tokenAddress, tokenName: token?.name }} shouldLogImpression>
|
<Trace
|
||||||
|
page={PageName.TOKEN_DETAILS_PAGE}
|
||||||
|
properties={{ tokenAddress: address, tokenName: token?.name }}
|
||||||
|
shouldLogImpression
|
||||||
|
>
|
||||||
<TokenDetailsLayout>
|
<TokenDetailsLayout>
|
||||||
{tokenQueryData && !isPending ? (
|
{token && !isPending ? (
|
||||||
<LeftPanel>
|
<LeftPanel>
|
||||||
<BreadcrumbNavLink to={`/tokens/${chain.toLowerCase()}`}>
|
<BreadcrumbNavLink to={`/tokens/${chain.toLowerCase()}`}>
|
||||||
<ArrowLeft size={14} /> Tokens
|
<ArrowLeft size={14} /> Tokens
|
||||||
@ -137,32 +193,30 @@ export default function TokenDetails({
|
|||||||
<CurrencyLogo currency={token} size="32px" />
|
<CurrencyLogo currency={token} size="32px" />
|
||||||
<L2NetworkLogo networkUrl={L2Icon} size="16px" />
|
<L2NetworkLogo networkUrl={L2Icon} size="16px" />
|
||||||
</LogoContainer>
|
</LogoContainer>
|
||||||
{token?.name ?? <Trans>Name not found</Trans>}
|
{token.name ?? <Trans>Name not found</Trans>}
|
||||||
<TokenSymbol>{token?.symbol ?? <Trans>Symbol not found</Trans>}</TokenSymbol>
|
<TokenSymbol>{token.symbol ?? <Trans>Symbol not found</Trans>}</TokenSymbol>
|
||||||
</TokenNameCell>
|
</TokenNameCell>
|
||||||
<TokenActions>
|
<TokenActions>
|
||||||
{tokenQueryData?.name && tokenQueryData.symbol && tokenQueryData.address && (
|
|
||||||
<ShareButton currency={token} />
|
<ShareButton currency={token} />
|
||||||
)}
|
|
||||||
</TokenActions>
|
</TokenActions>
|
||||||
</TokenInfoContainer>
|
</TokenInfoContainer>
|
||||||
<ChartSection priceQueryReference={priceQueryReference} refetchTokenPrices={refetchTokenPrices} />
|
<ChartSection priceQueryReference={priceQueryReference} refetchTokenPrices={refetchTokenPrices} />
|
||||||
<StatsSection
|
<StatsSection
|
||||||
TVL={tokenQueryData.market?.totalValueLocked?.value}
|
TVL={tokenQueryData?.market?.totalValueLocked?.value}
|
||||||
volume24H={tokenQueryData.market?.volume24H?.value}
|
volume24H={tokenQueryData?.market?.volume24H?.value}
|
||||||
priceHigh52W={tokenQueryData.market?.priceHigh52W?.value}
|
priceHigh52W={tokenQueryData?.market?.priceHigh52W?.value}
|
||||||
priceLow52W={tokenQueryData.market?.priceLow52W?.value}
|
priceLow52W={tokenQueryData?.market?.priceLow52W?.value}
|
||||||
/>
|
/>
|
||||||
{!isNative && (
|
{!token.isNative && (
|
||||||
<>
|
<>
|
||||||
<Hr />
|
<Hr />
|
||||||
<AboutSection
|
<AboutSection
|
||||||
address={tokenQueryData.address ?? ''}
|
address={address}
|
||||||
description={tokenQueryData.project?.description}
|
description={tokenQueryData?.project?.description}
|
||||||
homepageUrl={tokenQueryData.project?.homepageUrl}
|
homepageUrl={tokenQueryData?.project?.homepageUrl}
|
||||||
twitterName={tokenQueryData.project?.twitterName}
|
twitterName={tokenQueryData?.project?.twitterName}
|
||||||
/>
|
/>
|
||||||
<AddressSection address={tokenQueryData.address ?? ''} />
|
<AddressSection address={address} />
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</LeftPanel>
|
</LeftPanel>
|
||||||
@ -172,25 +226,23 @@ export default function TokenDetails({
|
|||||||
|
|
||||||
<RightPanel>
|
<RightPanel>
|
||||||
<Widget
|
<Widget
|
||||||
token={token ?? nativeCurrency}
|
token={token ?? undefined}
|
||||||
onTokenChange={navigateToWidgetSelectedToken}
|
onTokenChange={navigateToWidgetSelectedToken}
|
||||||
onReviewSwapClick={onReviewSwapClick}
|
onReviewSwapClick={onReviewSwapClick}
|
||||||
/>
|
/>
|
||||||
{tokenWarning && <TokenSafetyMessage tokenAddress={tokenAddress ?? ''} warning={tokenWarning} />}
|
{tokenWarning && <TokenSafetyMessage tokenAddress={address} warning={tokenWarning} />}
|
||||||
{token && <BalanceSummary token={token} />}
|
{token && <BalanceSummary token={token} />}
|
||||||
</RightPanel>
|
</RightPanel>
|
||||||
{token && <MobileBalanceSummaryFooter token={token} />}
|
{token && <MobileBalanceSummaryFooter token={token} />}
|
||||||
|
|
||||||
{tokenAddress && (
|
|
||||||
<TokenSafetyModal
|
<TokenSafetyModal
|
||||||
isOpen={isBlockedToken || !!continueSwap}
|
isOpen={isBlockedToken || !!continueSwap}
|
||||||
tokenAddress={tokenAddress}
|
tokenAddress={address}
|
||||||
onContinue={() => onResolveSwap(true)}
|
onContinue={() => onResolveSwap(true)}
|
||||||
onBlocked={() => navigate(-1)}
|
onBlocked={() => navigate(-1)}
|
||||||
onCancel={() => onResolveSwap(false)}
|
onCancel={() => onResolveSwap(false)}
|
||||||
showCancel={true}
|
showCancel={true}
|
||||||
/>
|
/>
|
||||||
)}
|
|
||||||
</TokenDetailsLayout>
|
</TokenDetailsLayout>
|
||||||
</Trace>
|
</Trace>
|
||||||
)
|
)
|
||||||
|
@ -48,8 +48,8 @@ export const tokenQuery = graphql`
|
|||||||
twitterName
|
twitterName
|
||||||
logoUrl
|
logoUrl
|
||||||
tokens {
|
tokens {
|
||||||
chain
|
chain @required(action: LOG)
|
||||||
address
|
address @required(action: LOG)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -25,6 +25,9 @@ function parseStringOrBytes32(str: string | undefined, bytes32: string | undefin
|
|||||||
: defaultValue
|
: defaultValue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const UNKNOWN_TOKEN_SYMBOL = 'UNKNOWN'
|
||||||
|
export const UNKNOWN_TOKEN_NAME = 'Unknown Token'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns a Token from the tokenAddress.
|
* Returns a Token from the tokenAddress.
|
||||||
* Returns null if token is loading or null was passed.
|
* Returns null if token is loading or null was passed.
|
||||||
@ -51,11 +54,11 @@ export function useTokenFromActiveNetwork(tokenAddress: string | undefined): Tok
|
|||||||
const parsedDecimals = useMemo(() => decimals?.result?.[0] ?? DEFAULT_ERC20_DECIMALS, [decimals.result])
|
const parsedDecimals = useMemo(() => decimals?.result?.[0] ?? DEFAULT_ERC20_DECIMALS, [decimals.result])
|
||||||
|
|
||||||
const parsedSymbol = useMemo(
|
const parsedSymbol = useMemo(
|
||||||
() => parseStringOrBytes32(symbol.result?.[0], symbolBytes32.result?.[0], 'UNKNOWN'),
|
() => parseStringOrBytes32(symbol.result?.[0], symbolBytes32.result?.[0], UNKNOWN_TOKEN_SYMBOL),
|
||||||
[symbol.result, symbolBytes32.result]
|
[symbol.result, symbolBytes32.result]
|
||||||
)
|
)
|
||||||
const parsedName = useMemo(
|
const parsedName = useMemo(
|
||||||
() => parseStringOrBytes32(tokenName.result?.[0], tokenNameBytes32.result?.[0], 'Unknown Token'),
|
() => parseStringOrBytes32(tokenName.result?.[0], tokenNameBytes32.result?.[0], UNKNOWN_TOKEN_NAME),
|
||||||
[tokenName.result, tokenNameBytes32.result]
|
[tokenName.result, tokenNameBytes32.result]
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -46,7 +46,7 @@ export default function TokenDetailsPage() {
|
|||||||
return (
|
return (
|
||||||
<Suspense fallback={<TokenDetailsPageSkeleton />}>
|
<Suspense fallback={<TokenDetailsPageSkeleton />}>
|
||||||
<TokenDetails
|
<TokenDetails
|
||||||
tokenAddress={tokenAddress}
|
urlAddress={tokenAddress}
|
||||||
chain={chain}
|
chain={chain}
|
||||||
tokenQueryReference={tokenQueryReference}
|
tokenQueryReference={tokenQueryReference}
|
||||||
priceQueryReference={priceQueryReference}
|
priceQueryReference={priceQueryReference}
|
||||||
|
Loading…
Reference in New Issue
Block a user