Skip to content

Commit

Permalink
feat(suite): show zero balance tokens (#13572)
Browse files Browse the repository at this point in the history
  • Loading branch information
AdamSchinzel authored Jul 31, 2024
1 parent 9f2a159 commit 29a03df
Show file tree
Hide file tree
Showing 14 changed files with 801 additions and 529 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import {
selectAccountHasStaked,
} from '@suite-common/wallet-core';
import { getTokens } from 'src/utils/wallet/tokenUtils';
import { selectIsDebugModeActive } from 'src/reducers/suite/suiteReducer';

interface AccountSectionProps {
account: Account;
Expand All @@ -35,24 +34,23 @@ export const AccountSection = ({

const coinDefinitions = useSelector(state => selectCoinDefinitions(state, symbol));
const hasStaked = useSelector(state => selectAccountHasStaked(state, account));
const isDebug = useSelector(selectIsDebugModeActive);

const isStakeShown = isSupportedEthStakingNetworkSymbol(symbol) && hasStaked;

const showGroup = ['ethereum', 'solana', 'cardano'].includes(networkType);

const tokens = getTokens(accountTokens, account.symbol, isDebug, coinDefinitions);
const tokens = getTokens(accountTokens, account.symbol, coinDefinitions);

const dataTestKey = `@account-menu/${symbol}/${accountType}/${index}`;

return showGroup && (isStakeShown || tokens.shown.length) ? (
return showGroup && (isStakeShown || tokens.shownWithBalance.length) ? (
<AccountItemsGroup
key={`${descriptor}-${symbol}`}
account={account}
accountLabel={accountLabel}
selected={selected}
showStaking={isStakeShown}
tokens={tokens.shown}
tokens={tokens.shownWithBalance}
dataTestKey={dataTestKey}
/>
) : (
Expand All @@ -64,7 +62,7 @@ export const AccountSection = ({
onClick={onItemClick}
accountLabel={accountLabel}
formattedBalance={formattedBalance}
tokens={tokens.shown}
tokens={tokens.shownWithBalance}
dataTestKey={dataTestKey}
/>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import styled, { useTheme } from 'styled-components';
import { Translation } from 'src/components/suite';
import { Icon } from '@trezor/components';
import { Account } from 'src/types/wallet';
import { AnimationWrapper } from './AnimationWrapper';
import { AnimationWrapper } from '../../AnimationWrapper';
import { spacingsPx, typography } from '@trezor/theme';

const Container = styled.div`
Expand Down
4 changes: 4 additions & 0 deletions packages/suite/src/support/messages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8758,6 +8758,10 @@ export default defineMessages({
id: 'TR_TX_CONFIRMING',
defaultMessage: 'Confirming transaction',
},
ZERO_BALANCE_TOKENS: {
id: 'ZERO_BALANCE_TOKENS',
defaultMessage: 'Zero balance tokens ({count})',
},
TR_STAKE_ADDING_TO_POOL: {
id: 'TR_STAKE_ADDING_TO_POOL',
defaultMessage: 'Adding to staking pool',
Expand Down
142 changes: 142 additions & 0 deletions packages/suite/src/utils/wallet/__fixtures__/tokenUtils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
import { EnhancedTokenInfo, TokenDefinition } from '@suite-common/token-definitions';
import { NetworkSymbol } from '@suite-common/wallet-config';

export const getTokensFixtures = [
{
testName: 'tokens with definitions and balance',
tokens: [
{ contract: '0x1', balance: '100' } as EnhancedTokenInfo,
{ contract: '0x2', balance: '200' } as EnhancedTokenInfo,
],
symbol: 'eth' as NetworkSymbol,
coinDefinitions: {
error: false,
isLoading: false,
data: ['0x1', '0x2'],
hide: [],
show: [],
} as TokenDefinition,
searchQuery: '',
result: {
shownWithBalance: [
{ contract: '0x1', balance: '100' } as EnhancedTokenInfo,
{ contract: '0x2', balance: '200' } as EnhancedTokenInfo,
],
shownWithoutBalance: [],
hiddenWithBalance: [],
hiddenWithoutBalance: [],
unverifiedWithBalance: [],
unverifiedWithoutBalance: [],
},
},
{
testName: 'hidden token with legit tokens',
tokens: [
{ contract: '0x1', balance: '100' } as EnhancedTokenInfo,
{ contract: '0x2', balance: '200' } as EnhancedTokenInfo,
{ contract: '0x3', balance: '0' } as EnhancedTokenInfo,
],
symbol: 'eth' as NetworkSymbol,
coinDefinitions: {
error: false,
isLoading: false,
data: ['0x1', '0x2', '0x3'],
hide: ['0x2'],
show: [],
} as TokenDefinition,
searchQuery: '',
result: {
shownWithBalance: [{ contract: '0x1', balance: '100' } as EnhancedTokenInfo],
shownWithoutBalance: [{ contract: '0x3', balance: '0' } as EnhancedTokenInfo],
hiddenWithBalance: [{ contract: '0x2', balance: '200' } as EnhancedTokenInfo],
hiddenWithoutBalance: [],
unverifiedWithBalance: [],
unverifiedWithoutBalance: [],
},
},
{
testName: 'unverified tokens with legit token',
tokens: [
{ contract: '0x1', balance: '100' } as EnhancedTokenInfo,
{ contract: '0x2', balance: '200' } as EnhancedTokenInfo,
{ contract: '0x3', balance: '0' } as EnhancedTokenInfo,
],
symbol: 'eth' as NetworkSymbol,
coinDefinitions: {
error: false,
isLoading: false,
data: ['0x1'],
hide: [],
show: [],
} as TokenDefinition,
searchQuery: '',
result: {
shownWithBalance: [{ contract: '0x1', balance: '100' } as EnhancedTokenInfo],
shownWithoutBalance: [],
hiddenWithBalance: [],
hiddenWithoutBalance: [],
unverifiedWithBalance: [{ contract: '0x2', balance: '200' } as EnhancedTokenInfo],
unverifiedWithoutBalance: [{ contract: '0x3', balance: '0' } as EnhancedTokenInfo],
},
},
{
testName: 'mix of shown, hidden, and unverified tokens',
tokens: [
{ contract: '0x1', balance: '100' } as EnhancedTokenInfo,
{ contract: '0x2', balance: '200' } as EnhancedTokenInfo,
{ contract: '0x3', balance: '300' } as EnhancedTokenInfo,
{ contract: '0x4', balance: '0' } as EnhancedTokenInfo,
{ contract: '0x5', balance: '0' } as EnhancedTokenInfo,
],
symbol: 'eth' as NetworkSymbol,
coinDefinitions: {
error: false,
isLoading: false,
data: ['0x1', '0x2', '0x3'],
hide: ['0x2'],
show: ['0x3'],
} as TokenDefinition,
searchQuery: '',
result: {
shownWithBalance: [
{ contract: '0x1', balance: '100' } as EnhancedTokenInfo,
{ contract: '0x3', balance: '300' } as EnhancedTokenInfo,
],
shownWithoutBalance: [],
hiddenWithBalance: [{ contract: '0x2', balance: '200' } as EnhancedTokenInfo],
hiddenWithoutBalance: [],
unverifiedWithBalance: [],
unverifiedWithoutBalance: [
{ contract: '0x4', balance: '0' } as EnhancedTokenInfo,
{ contract: '0x5', balance: '0' } as EnhancedTokenInfo,
],
},
},
{
testName: 'legitokens search',
tokens: [
{ contract: '0x1', balance: '100', symbol: 'ABC' } as EnhancedTokenInfo,
{ contract: '0x2', balance: '200', symbol: 'DEF' } as EnhancedTokenInfo,
{ contract: '0x3', balance: '0', symbol: 'GHI' } as EnhancedTokenInfo,
],
symbol: 'eth' as NetworkSymbol,
coinDefinitions: {
error: false,
isLoading: false,
data: ['0x1', '0x2', '0x3'],
hide: [],
show: [],
} as TokenDefinition,
searchQuery: 'AB',
result: {
shownWithBalance: [
{ contract: '0x1', balance: '100', symbol: 'ABC' } as EnhancedTokenInfo,
],
shownWithoutBalance: [],
hiddenWithBalance: [],
hiddenWithoutBalance: [],
unverifiedWithBalance: [],
unverifiedWithoutBalance: [],
},
},
];
14 changes: 14 additions & 0 deletions packages/suite/src/utils/wallet/__tests__/tokenUtils.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { getTokensFixtures } from '../__fixtures__/tokenUtils';
import { getTokens } from '../tokenUtils';

describe('getTokens', () => {
getTokensFixtures.forEach(
({ testName, tokens, symbol, coinDefinitions, searchQuery, result }) => {
test(testName, () => {
expect(getTokens(tokens, symbol, coinDefinitions, searchQuery)).toStrictEqual(
result,
);
});
},
);
});
42 changes: 31 additions & 11 deletions packages/suite/src/utils/wallet/tokenUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,19 +69,19 @@ export const formatTokenSymbol = (symbol: string) => {
export const getTokens = (
tokens: EnhancedTokenInfo[] | TokenInfo[],
symbol: NetworkSymbol,
isDebug: boolean,
coinDefinitions?: TokenDefinition,
searchQuery?: string,
) => {
const hasCoinDefinitions = getNetworkFeatures(symbol).includes('coin-definitions');

const shown: EnhancedTokenInfo[] = [];
const hidden: EnhancedTokenInfo[] = [];
const unverified: EnhancedTokenInfo[] = [];
const shownWithBalance: EnhancedTokenInfo[] = [];
const shownWithoutBalance: EnhancedTokenInfo[] = [];
const hiddenWithBalance: EnhancedTokenInfo[] = [];
const hiddenWithoutBalance: EnhancedTokenInfo[] = [];
const unverifiedWithBalance: EnhancedTokenInfo[] = [];
const unverifiedWithoutBalance: EnhancedTokenInfo[] = [];

tokens.forEach(token => {
if (token.balance === '0' && !isDebug) return false;

const isKnown = isTokenDefinitionKnown(coinDefinitions?.data, symbol, token.contract);
const isHidden = coinDefinitions?.hide.includes(token.contract);
const isShown = coinDefinitions?.show.includes(token.contract);
Expand All @@ -90,16 +90,36 @@ export const getTokens = (

if (searchQuery && !isTokenMatchesSearch(token, query)) return;

const hasBalance = new BigNumber(token?.balance || '0').gt(0);

const pushToArray = (
arrayWithBalance: EnhancedTokenInfo[],
arrayWithoutBalance: EnhancedTokenInfo[],
) => {
if (hasBalance) {
arrayWithBalance.push(token);
} else {
arrayWithoutBalance.push(token);
}
};

if (isShown) {
shown.push(token);
pushToArray(shownWithBalance, shownWithoutBalance);
} else if (hasCoinDefinitions && !isKnown) {
unverified.push(token);
pushToArray(unverifiedWithBalance, unverifiedWithoutBalance);
} else if (isHidden) {
hidden.push(token);
pushToArray(hiddenWithBalance, hiddenWithoutBalance);
} else {
shown.push(token);
pushToArray(shownWithBalance, shownWithoutBalance);
}
});

return { shown, hidden, unverified };
return {
shownWithBalance,
shownWithoutBalance,
hiddenWithBalance,
hiddenWithoutBalance,
unverifiedWithBalance,
unverifiedWithoutBalance,
};
};
Original file line number Diff line number Diff line change
Expand Up @@ -46,30 +46,30 @@ export const buildTokenOptions = (
];

if (accountTokens) {
const tokens = getTokens(accountTokens, symbol, false, coinDefinitions);
const tokens = getTokens(accountTokens, symbol, coinDefinitions);

tokens.shown.forEach(token => {
tokens.shownWithBalance.forEach(token => {
result[0].options.push({
value: token.contract,
label: formatTokenSymbol(token.symbol || token.contract),
});
});

if (tokens.hidden.length) {
if (tokens.hiddenWithBalance.length) {
result.push({
label: (
<UnrecognizedTokensHeading>
<Translation id="TR_HIDDEN_TOKENS" />
</UnrecognizedTokensHeading>
),
options: tokens.hidden.map(token => ({
options: tokens.hiddenWithBalance.map(token => ({
value: token.contract,
label: formatTokenSymbol(token.symbol || token.contract),
})),
});
}

if (tokens.unverified.length) {
if (tokens.unverifiedWithBalance.length) {
result.push({
label: (
<UnrecognizedTokensHeading>
Expand All @@ -79,7 +79,7 @@ export const buildTokenOptions = (
/>
</UnrecognizedTokensHeading>
),
options: tokens.unverified.map(token => ({
options: tokens.unverifiedWithBalance.map(token => ({
value: token.contract,
label: formatTokenSymbol(token.symbol || token.contract),
})),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,8 @@ import {
} from 'src/utils/wallet/tokenUtils';
import { useSelector } from 'src/hooks/suite';
import { NoTokens } from '../../common/NoTokens';
import { TokenList } from '../../common/TokenList';
import { TokenList } from '../../common/TokensList/TokenList';
import { Translation } from 'src/components/suite';
import { selectIsDebugModeActive } from 'src/reducers/suite/suiteReducer';

interface CoinsTableProps {
selectedAccount: SelectedAccountLoaded;
Expand All @@ -27,7 +26,6 @@ export const CoinsTable = ({ selectedAccount, searchQuery }: CoinsTableProps) =>
const { account, network } = selectedAccount;

const coinDefinitions = useSelector(state => selectCoinDefinitions(state, account.symbol));
const isDebug = useSelector(selectIsDebugModeActive);

const tokensWithRates = enhanceTokensWithRates(
account.tokens,
Expand All @@ -37,26 +35,30 @@ export const CoinsTable = ({ selectedAccount, searchQuery }: CoinsTableProps) =>
);
const sortedTokens = tokensWithRates.sort(sortTokensWithRates);

const tokens = getTokens(sortedTokens, account.symbol, isDebug, coinDefinitions, searchQuery);
const tokens = getTokens(sortedTokens, account.symbol, coinDefinitions, searchQuery);
const hiddenTokensCount =
tokens.unverifiedWithBalance.length +
tokens.hiddenWithBalance.length +
tokens.unverifiedWithoutBalance.length +
tokens.hiddenWithoutBalance.length;

return tokens.shown.length > 0 || searchQuery ? (
return tokens.shownWithBalance.length > 0 ||
tokens.shownWithoutBalance.length > 0 ||
searchQuery ? (
<TokenList
account={account}
hideRates={isTestnet(account.symbol)}
tokenStatusType={TokenManagementAction.HIDE}
tokens={tokens.shown}
tokensWithBalance={tokens.shownWithBalance}
tokensWithoutBalance={tokens.shownWithoutBalance}
network={network}
searchQuery={searchQuery}
/>
) : (
<NoTokens
title={
<Translation
id={
tokens.unverified.length + tokens.hidden.length > 0
? 'TR_TOKENS_EMPTY_CHECK_HIDDEN'
: 'TR_TOKENS_EMPTY'
}
id={hiddenTokensCount > 0 ? 'TR_TOKENS_EMPTY_CHECK_HIDDEN' : 'TR_TOKENS_EMPTY'}
/>
}
/>
Expand Down
Loading

0 comments on commit 29a03df

Please sign in to comment.