diff --git a/packages/suite/src/views/wallet/tokens/components/TokenList.tsx b/packages/suite/src/views/wallet/tokens/components/TokenList.tsx index 1e85055d064..f0a393a4a5b 100644 --- a/packages/suite/src/views/wallet/tokens/components/TokenList.tsx +++ b/packages/suite/src/views/wallet/tokens/components/TokenList.tsx @@ -1,12 +1,20 @@ import { useMemo, Fragment } from 'react'; import styled, { css, useTheme } from 'styled-components'; import { variables, Icon, Card } from '@trezor/components'; -import { FiatValue, FormattedCryptoAmount, TrezorLink } from 'src/components/suite'; +import { + FiatValue, + FormattedCryptoAmount, + QuestionTooltip, + TrezorLink, +} from 'src/components/suite'; import { Account } from 'src/types/wallet'; import { useSelector } from 'src/hooks/suite'; import { enhanceTokensWithRates, sortTokensWithRates } from '@suite-common/wallet-utils'; -import { selectCoinsLegacy } from '@suite-common/wallet-core'; +import { selectCoinsLegacy, selectTokenDefinitions } from '@suite-common/wallet-core'; import { NoRatesTooltip } from 'src/components/suite/Ticker/NoRatesTooltip'; +import { FiatRates, TokenInfo } from '@trezor/blockchain-link-types'; +import { spacingsPx } from '@trezor/theme'; +import { NetworkSymbol, getNetworkFeatures } from '@suite-common/wallet-config'; const Wrapper = styled(Card)<{ isTestnet?: boolean }>` display: grid; @@ -68,25 +76,39 @@ const StyledNoRatesTooltip = styled(NoRatesTooltip)` justify-content: flex-end; `; +const StyledQuestionTooltip = styled(QuestionTooltip)<{ addMarginTop: boolean }>` + ${({ addMarginTop }) => + addMarginTop && + css` + margin-top: ${spacingsPx.xxl}; + `} + margin-bottom: ${spacingsPx.sm}; +`; + interface TokenListProps { tokens: Account['tokens']; networkType: Account['networkType']; explorerUrl: string; explorerUrlQueryString: string; isTestnet?: boolean; + networkSymbol: NetworkSymbol; } +type EnhancedTokenInfo = TokenInfo & { rates?: FiatRates }; + export const TokenList = ({ tokens, explorerUrl, explorerUrlQueryString, isTestnet, networkType, + networkSymbol, }: TokenListProps) => { const theme = useTheme(); const coins = useSelector(selectCoinsLegacy); + const tokenDefinitions = useSelector(state => selectTokenDefinitions(state, networkSymbol)); - const sortedTokens = useMemo(() => { + const sortedTokens: EnhancedTokenInfo[] = useMemo(() => { const tokensWithRates = enhanceTokensWithRates(tokens, coins); return tokensWithRates.sort(sortTokensWithRates); @@ -94,66 +116,99 @@ export const TokenList = ({ if (!tokens || tokens.length === 0) return null; + const hasNetworkFeatures = getNetworkFeatures(networkSymbol).includes('token-definitions'); + const { knownTokens, unknownTokens } = sortedTokens.reduce<{ + knownTokens: EnhancedTokenInfo[]; + unknownTokens: EnhancedTokenInfo[]; + }>( + (acc, token) => { + if (tokenDefinitions[token.contract]?.isTokenKnown || !hasNetworkFeatures) { + acc.knownTokens.push(token); + } else { + acc.unknownTokens.push(token); + } + return acc; + }, + { knownTokens: [], unknownTokens: [] }, + ); + return ( - - {sortedTokens.map(t => { - // In Cardano token name is optional and in there is no symbol. - // However, if Cardano token doesn't have a name on blockchain, its TokenInfo has both name - // and symbol props set to a token fingerprint (done in blockchain-link) and we - // don't want to render it twice. - // In ethereum we are fine with rendering symbol - name even if they are the same. - const symbolMatchesName = - networkType === 'cardano' && t.symbol?.toLowerCase() === t.name?.toLowerCase(); - const noSymbol = !t.symbol || symbolMatchesName; - - const isTokenWithRate = Boolean(t.rates && Object.keys(t.rates).length); - - return ( - - - {!noSymbol && {t.symbol}} - - {!noSymbol && ` - `} - {t.name} - - - - {t.balance && ( - - )} - - {!isTestnet && ( - - {t.balance && t.symbol && isTokenWithRate ? ( - - - - ) : ( - - )} - + <> + {[knownTokens, unknownTokens].map((tokens, groupIndex) => + tokens.length ? ( + + {groupIndex === 1 && ( + )} - - - - - + + {tokens.map(t => { + const symbolMatchesName = + networkType === 'cardano' && + t.symbol?.toLowerCase() === t.name?.toLowerCase(); + const noSymbol = !t.symbol || symbolMatchesName; + + const isTokenWithRate = Boolean( + t.rates && Object.keys(t.rates).length, + ); + + return ( + + + {!noSymbol && {t.symbol}} + + {!noSymbol && ` - `} + {t.name} + + + + {t.balance && ( + + )} + + {!isTestnet && ( + + {t.balance && t.symbol && isTokenWithRate ? ( + + + + ) : ( + + )} + + )} + + + + + + + ); + })} + - ); - })} - + ) : null, + )} + ); }; diff --git a/packages/suite/src/views/wallet/tokens/index.tsx b/packages/suite/src/views/wallet/tokens/index.tsx index 0b2fb211be4..905a918ebab 100644 --- a/packages/suite/src/views/wallet/tokens/index.tsx +++ b/packages/suite/src/views/wallet/tokens/index.tsx @@ -25,6 +25,7 @@ export const Tokens = () => { explorerUrlQueryString={explorerUrlQueryString} tokens={account.tokens} networkType={account.networkType} + networkSymbol={account.symbol} /> {!account.tokens?.length && }