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 && }