Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore: Create consolidateTokenBalances utility function #29886

Draft
wants to merge 2 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
94 changes: 20 additions & 74 deletions ui/components/app/assets/asset-list/asset-list.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import React, { useContext, useEffect, useState } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { Token } from '@metamask/assets-controllers';

import { NetworkConfiguration } from '@metamask/network-controller';
import TokenList from '../token-list';
import { PRIMARY } from '../../../../helpers/constants/common';
Expand All @@ -26,7 +26,6 @@ import { MetaMetricsContext } from '../../../../contexts/metametrics';
import {
MetaMetricsEventCategory,
MetaMetricsEventName,
MetaMetricsTokenEventSource,
} from '../../../../../shared/constants/metametrics';
import DetectedToken from '../../detected-token/detected-token';
import { ReceiveModal } from '../../../multichain';
Expand All @@ -45,10 +44,8 @@ import {
getSelectedNetworkClientId,
} from '../../../../../shared/modules/selectors/networks';
import { addImportedTokens } from '../../../../store/actions';
import {
AssetType,
TokenStandard,
} from '../../../../../shared/constants/transaction';
import { Token } from '../token-list/token-list';
import { importAllDetectedTokens } from '../util/importAllDetectedTokens';
import AssetListControlBar from './asset-list-control-bar';
import NativeToken from './native-token';

Expand Down Expand Up @@ -90,17 +87,16 @@ const AssetList = ({ onClickAsset, showTokensLinks }: AssetListProps) => {

const detectedTokens = useSelector(getDetectedTokensInCurrentNetwork) || [];

const isTokenNetworkFilterEqualCurrentNetwork = useSelector(
getIsTokenNetworkFilterEqualCurrentNetwork,
);

const allNetworks: Record<`0x${string}`, NetworkConfiguration> = useSelector(
getNetworkConfigurationsByChainId,
);
const networkClientId = useSelector(getSelectedNetworkClientId);
const selectedAddress = useSelector(getSelectedAddress);
const useTokenDetection = useSelector(getUseTokenDetection);
const currentChainId = useSelector(getCurrentChainId);
const isOnCurrentNetwork = useSelector(
getIsTokenNetworkFilterEqualCurrentNetwork,
);

const [showFundingMethodModal, setShowFundingMethodModal] = useState(false);
const [showReceiveModal, setShowReceiveModal] = useState(false);
Expand All @@ -125,7 +121,7 @@ const AssetList = ({ onClickAsset, showTokensLinks }: AssetListProps) => {
const shouldShowTokensLinks = showTokensLinks ?? isEvm;

const detectedTokensMultichain: {
[key: `0x${string}`]: Token[];
[key: string]: Token[];
} = useSelector(getAllDetectedTokensForSelectedAddress);

const multichainDetectedTokensLength = Object.values(
Expand All @@ -134,70 +130,20 @@ const AssetList = ({ onClickAsset, showTokensLinks }: AssetListProps) => {

// Add detected tokens to sate
useEffect(() => {
const importAllDetectedTokens = async () => {
// If autodetect tokens toggle is OFF, return
if (!useTokenDetection) {
return;
}
// TODO add event for MetaMetricsEventName.TokenAdded

if (
process.env.PORTFOLIO_VIEW &&
!isTokenNetworkFilterEqualCurrentNetwork
) {
const importPromises = Object.entries(detectedTokensMultichain).map(
async ([networkId, tokens]) => {
const chainConfig = allNetworks[networkId as `0x${string}`];
const { defaultRpcEndpointIndex } = chainConfig;
const { networkClientId: networkInstanceId } =
chainConfig.rpcEndpoints[defaultRpcEndpointIndex];

await dispatch(
addImportedTokens(tokens as Token[], networkInstanceId),
);
tokens.forEach((importedToken) => {
trackEvent({
event: MetaMetricsEventName.TokenAdded,
category: MetaMetricsEventCategory.Wallet,
sensitiveProperties: {
token_symbol: importedToken.symbol,
token_contract_address: importedToken.address,
token_decimal_precision: importedToken.decimals,
source: MetaMetricsTokenEventSource.Detected,
token_standard: TokenStandard.ERC20,
asset_type: AssetType.token,
token_added_type: 'detected',
chain_id: chainConfig.chainId,
},
});
});
},
);

await Promise.all(importPromises);
} else if (detectedTokens.length > 0) {
await dispatch(addImportedTokens(detectedTokens, networkClientId));
detectedTokens.forEach((importedToken: Token) => {
trackEvent({
event: MetaMetricsEventName.TokenAdded,
category: MetaMetricsEventCategory.Wallet,
sensitiveProperties: {
token_symbol: importedToken.symbol,
token_contract_address: importedToken.address,
token_decimal_precision: importedToken.decimals,
source: MetaMetricsTokenEventSource.Detected,
token_standard: TokenStandard.ERC20,
asset_type: AssetType.token,
token_added_type: 'detected',
chain_id: currentChainId,
},
});
});
}
};
importAllDetectedTokens();
importAllDetectedTokens(
useTokenDetection,
isOnCurrentNetwork,
detectedTokensMultichain,
allNetworks,
networkClientId,
addImportedTokens,
currentChainId,
trackEvent,
detectedTokens,
dispatch,
);
}, [
isTokenNetworkFilterEqualCurrentNetwork,
isOnCurrentNetwork,
selectedAddress,
networkClientId,
detectedTokens.length,
Expand Down
10 changes: 0 additions & 10 deletions ui/components/app/assets/auto-detect-token/index.scss

This file was deleted.

67 changes: 10 additions & 57 deletions ui/components/app/assets/token-list/token-list.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,12 @@ import {
} from '../../../../selectors';
import { getConversionRate } from '../../../../ducks/metamask/metamask';
import { filterAssets } from '../util/filter';
import { calculateTokenBalance } from '../util/calculateTokenBalance';
import { calculateTokenFiatAmount } from '../util/calculateTokenFiatAmount';
import { endTrace, TraceName } from '../../../../../shared/lib/trace';
import { useTokenBalances } from '../../../../hooks/useTokenBalances';
import { setTokenNetworkFilter } from '../../../../store/actions';
import { useMultichainSelector } from '../../../../hooks/useMultichainSelector';
import { getMultichainShouldShowFiat } from '../../../../selectors/multichain';
import { consolidateTokenBalances } from '../util/consolidateTokenBalances';

type TokenListProps = {
onTokenClick: (chainId: string, address: string) => void;
Expand Down Expand Up @@ -130,62 +129,16 @@ export default function TokenList({
}
}, [Object.keys(allNetworks).length]);

const consolidatedBalances = () => {
const tokensWithBalance: TokenWithFiatAmount[] = [];
Object.entries(selectedAccountTokensChains).forEach(
([stringChainKey, tokens]) => {
const chainId = stringChainKey as Hex;
tokens.forEach((token: Token) => {
const { isNative, address, decimals } = token;
const balance =
calculateTokenBalance({
isNative,
chainId,
address,
decimals,
nativeBalances,
selectedAccountTokenBalancesAcrossChains,
}) || '0';

const tokenFiatAmount = calculateTokenFiatAmount({
token,
chainId,
balance,
marketData,
currencyRates,
});

// Respect the "hide zero balance" setting (when true):
// - Native tokens should always display with zero balance when on the current network filter.
// - Native tokens should not display with zero balance when on all networks filter
// - ERC20 tokens with zero balances should respect the setting on both the current and all networks.

// Respect the "hide zero balance" setting (when false):
// - Native tokens should always display with zero balance when on the current network filter.
// - Native tokens should always display with zero balance when on all networks filter
// - ERC20 tokens always display with zero balance on both the current and all networks filter.
if (
!hideZeroBalanceTokens ||
balance !== '0' ||
(token.isNative && isOnCurrentNetwork)
) {
tokensWithBalance.push({
...token,
balance,
tokenFiatAmount,
chainId,
string: String(balance),
});
}
});
},
);

return tokensWithBalance;
};

const sortedFilteredTokens = useMemo(() => {
const consolidatedTokensWithBalances = consolidatedBalances();
const consolidatedTokensWithBalances = consolidateTokenBalances(
selectedAccountTokensChains,
nativeBalances,
selectedAccountTokenBalancesAcrossChains,
marketData,
currencyRates,
hideZeroBalanceTokens,
isOnCurrentNetwork,
);
const filteredAssets = filterAssets(consolidatedTokensWithBalances, [
{
key: 'chainId',
Expand Down
2 changes: 1 addition & 1 deletion ui/components/app/assets/util/calculateTokenFiatAmount.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { Hex } from '@metamask/utils';
import { ChainAddressMarketData, Token } from '../token-list/token-list';

type SymbolCurrencyRateMapping = Record<string, Record<string, number>>;
export type SymbolCurrencyRateMapping = Record<string, Record<string, number>>;

type CalculateTokenFiatAmountParams = {
token: Token;
Expand Down
76 changes: 76 additions & 0 deletions ui/components/app/assets/util/consolidateTokenBalances.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import { Hex } from '@metamask/utils';
import {
ChainAddressMarketData,
Token,
TokenWithFiatAmount,
} from '../token-list/token-list';
import { calculateTokenBalance } from './calculateTokenBalance';
import {
SymbolCurrencyRateMapping,
calculateTokenFiatAmount,
} from './calculateTokenFiatAmount';

export const consolidateTokenBalances = (
selectedAccountTokensChains: Record<string, Token[]>,
nativeBalances: Record<Hex, Hex>,
selectedAccountTokenBalancesAcrossChains: Record<
`0x${string}`,
Record<`0x${string}`, `0x${string}`>
>,
marketData: ChainAddressMarketData,
currencyRates: SymbolCurrencyRateMapping,
hideZeroBalanceTokens: boolean,
isOnCurrentNetwork: boolean,
) => {
const tokensWithBalance: TokenWithFiatAmount[] = [];
Object.entries(selectedAccountTokensChains).forEach(
([stringChainKey, tokens]) => {
const chainId = stringChainKey as Hex;
tokens.forEach((token: Token) => {
const { isNative, address, decimals } = token;
const balance =
calculateTokenBalance({
isNative,
chainId,
address,
decimals,
nativeBalances,
selectedAccountTokenBalancesAcrossChains,
}) || '0';

const tokenFiatAmount = calculateTokenFiatAmount({
token,
chainId,
balance,
marketData,
currencyRates,
});

// Respect the "hide zero balance" setting (when true):
// - Native tokens should always display with zero balance when on the current network filter.
// - Native tokens should not display with zero balance when on all networks filter
// - ERC20 tokens with zero balances should respect the setting on both the current and all networks.

// Respect the "hide zero balance" setting (when false):
// - Native tokens should always display with zero balance when on the current network filter.
// - Native tokens should always display with zero balance when on all networks filter
// - ERC20 tokens always display with zero balance on both the current and all networks filter.
if (
!hideZeroBalanceTokens ||
balance !== '0' ||
(token.isNative && isOnCurrentNetwork)
) {
tokensWithBalance.push({
...token,
balance,
tokenFiatAmount,
chainId,
string: String(balance),
});
}
});
},
);

return tokensWithBalance;
};
Loading
Loading