Skip to content

Commit

Permalink
feat: add detected tokens automatically
Browse files Browse the repository at this point in the history
  • Loading branch information
sahar-fehri committed Dec 20, 2024
1 parent e9c1617 commit 4def657
Show file tree
Hide file tree
Showing 2 changed files with 104 additions and 70 deletions.
70 changes: 2 additions & 68 deletions app/components/UI/Tokens/TokenList/TokenListFooter/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,6 @@ import Text, {
import { WalletViewSelectorsIDs } from '../../../../../../e2e/selectors/wallet/WalletView.selectors';
import { strings } from '../../../../../../locales/i18n';
import { useSelector } from 'react-redux';
import {
selectDetectedTokens,
selectAllDetectedTokensFlat,
} from '../../../../../selectors/tokensController';
import { isZero } from '../../../../../util/lodash';
import useRampNetwork from '../../../Ramp/hooks/useRampNetwork';
import { createBuyNavigationDetails } from '../../../Ramp/routes/utils';
Expand All @@ -25,62 +21,27 @@ import {
MetaMetricsEvents,
useMetrics,
} from '../../../../../components/hooks/useMetrics';
import {
getDecimalChainId,
isPortfolioViewEnabled,
} from '../../../../../util/networks';
import {
selectChainId,
selectIsAllNetworks,
selectIsPopularNetwork,
} from '../../../../../selectors/networkController';
import { getDecimalChainId } from '../../../../../util/networks';
import { selectChainId } from '../../../../../selectors/networkController';
import { TokenI } from '../../types';
import { selectUseTokenDetection } from '../../../../../selectors/preferencesController';

interface TokenListFooterProps {
tokens: TokenI[];
goToAddToken: () => void;
showDetectedTokens: () => void;
isAddTokenEnabled: boolean;
}

const getDetectedTokensCount = (
isPortfolioEnabled: boolean,
isAllNetworksSelected: boolean,
allTokens: TokenI[],
filteredTokens: TokenI[] | undefined,
isPopularNetworks: boolean,
): number => {
if (!isPortfolioEnabled) {
return filteredTokens?.length ?? 0;
}

return isAllNetworksSelected && isPopularNetworks
? allTokens.length
: filteredTokens?.length ?? 0;
};

export const TokenListFooter = ({
tokens,
goToAddToken,
showDetectedTokens,
isAddTokenEnabled,
}: TokenListFooterProps) => {
const navigation = useNavigation();
const { colors } = useTheme();
const { trackEvent, createEventBuilder } = useMetrics();
const [isNetworkRampSupported, isNativeTokenRampSupported] = useRampNetwork();

const detectedTokens = useSelector(selectDetectedTokens) as TokenI[];
const allDetectedTokens = useSelector(
selectAllDetectedTokensFlat,
) as TokenI[];

const isTokenDetectionEnabled = useSelector(selectUseTokenDetection);
const chainId = useSelector(selectChainId);
const isAllNetworks = useSelector(selectIsAllNetworks);
const isPopularNetworks = useSelector(selectIsPopularNetwork);

const styles = createStyles(colors);

const mainToken = tokens.find(({ isETH }) => isETH);
Expand All @@ -103,35 +64,8 @@ export const TokenListFooter = ({
);
};

const tokenCount = getDetectedTokensCount(
isPortfolioViewEnabled(),
isAllNetworks,
allDetectedTokens,
detectedTokens,
isPopularNetworks,
);

const areTokensDetected = tokenCount > 0;

return (
<>
{/* renderTokensDetectedSection */}
{areTokensDetected && isTokenDetectionEnabled && (
<TouchableOpacity
style={styles.tokensDetectedButton}
onPress={showDetectedTokens}
>
<Text
style={styles.tokensDetectedText}
testID={WalletViewSelectorsIDs.WALLET_TOKEN_DETECTION_LINK_BUTTON}
>
{strings('wallet.tokens_detected_in_account', {
tokenCount,
tokensLabel: tokenCount > 1 ? 'tokens' : 'token',
})}
</Text>
</TouchableOpacity>
)}
{/* render buy button */}
{isBuyableToken && (
<View style={styles.buy}>
Expand Down
104 changes: 102 additions & 2 deletions app/components/Views/Wallet/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,9 @@ import {
} from '../../../util/networks';
import {
selectChainId,
selectIsAllNetworks,
selectIsPopularNetwork,
selectNetworkClientId,
selectNetworkConfigurations,
selectProviderConfig,
selectTicker,
Expand All @@ -52,6 +55,8 @@ import {
selectNetworkImageSource,
} from '../../../selectors/networkInfos';
import {
selectAllDetectedTokensFlat,
selectDetectedTokens,
selectTokens,
selectTokensByChainIdAndAddress,
} from '../../../selectors/tokensController';
Expand All @@ -72,7 +77,10 @@ import Text, {
import { useMetrics } from '../../../components/hooks/useMetrics';
import { RootState } from '../../../reducers';
import usePrevious from '../../hooks/usePrevious';
import { selectSelectedInternalAccount } from '../../../selectors/accountsController';
import {
selectSelectedInternalAccount,
selectSelectedInternalAccountFormattedAddress,
} from '../../../selectors/accountsController';
import { selectAccountBalanceByChainId } from '../../../selectors/accountTrackerController';
import {
hideNftFetchingLoadingIndicator as hideNftFetchingLoadingIndicatorAction,
Expand All @@ -92,7 +100,13 @@ import { PortfolioBalance } from '../../UI/Tokens/TokenList/PortfolioBalance';
import useCheckNftAutoDetectionModal from '../../hooks/useCheckNftAutoDetectionModal';
import useCheckMultiRpcModal from '../../hooks/useCheckMultiRpcModal';
import { selectContractBalances } from '../../../selectors/tokenBalancesController';
import { selectTokenNetworkFilter } from '../../../selectors/preferencesController';
import {
selectTokenNetworkFilter,
selectUseTokenDetection,
} from '../../../selectors/preferencesController';
import { TokenI } from '../../UI/Tokens/types';
import { Hex } from '@metamask/utils';
import { Token } from '@metamask/assets-controllers';

const createStyles = ({ colors, typography }: Theme) =>
StyleSheet.create({
Expand Down Expand Up @@ -311,6 +325,24 @@ const Wallet = ({
const networkImageSource = useSelector(selectNetworkImageSource);
const tokenNetworkFilter = useSelector(selectTokenNetworkFilter);

const isAllNetworks = useSelector(selectIsAllNetworks);
const isTokenDetectionEnabled = useSelector(selectUseTokenDetection);
const isPopularNetworks = useSelector(selectIsPopularNetwork);
const detectedTokens = useSelector(selectDetectedTokens) as TokenI[];

const allDetectedTokens = useSelector(
selectAllDetectedTokensFlat,
) as TokenI[];
const currentDetectedTokens =
isPortfolioViewEnabled() && isAllNetworks && isPopularNetworks
? allDetectedTokens
: detectedTokens;
const allNetworks = useSelector(selectNetworkConfigurations);
const selectedNetworkClientId = useSelector(selectNetworkClientId);
const selectedAddress = useSelector(
selectSelectedInternalAccountFormattedAddress,
);

/**
* Shows Nft auto detect modal if the user is on mainnet, never saw the modal and have nft detection off
*/
Expand Down Expand Up @@ -444,6 +476,74 @@ const Wallet = ({
readNotificationCount,
]);

useEffect(() => {
const importAllDetectedTokens = async () => {
// If autodetect tokens toggle is OFF, return
if (!isTokenDetectionEnabled) {
return;
}
const { TokensController } = Engine.context;
if (currentDetectedTokens.length > 0) {
if (isPortfolioViewEnabled()) {
// Group tokens by their `chainId` using a plain object
const tokensByChainId: Record<Hex, Token[]> = {};

for (const token of currentDetectedTokens) {
const tokenChainId: Hex =
(token as TokenI & { chainId: Hex }).chainId ?? chainId;

if (!tokensByChainId[tokenChainId]) {
tokensByChainId[tokenChainId] = [];
}

tokensByChainId[tokenChainId].push(token);
}

// Process grouped tokens in parallel
const importPromises = Object.entries(tokensByChainId).map(
async ([networkId, allTokens]) => {
const chainConfig = allNetworks[networkId as Hex];
const { defaultRpcEndpointIndex } = chainConfig;
const { networkClientId: networkInstanceId } =
chainConfig.rpcEndpoints[defaultRpcEndpointIndex];

await TokensController.addTokens(allTokens, networkInstanceId);
},
);

await Promise.all(importPromises);
} else {
await TokensController.addTokens(
currentDetectedTokens,
selectedNetworkClientId,
);
}

currentDetectedTokens.forEach(({ address, symbol }) =>
trackEvent(
createEventBuilder(MetaMetricsEvents.TOKEN_ADDED)
.addProperties({
token_address: address,
token_symbol: symbol,
chain_id: getDecimalChainId(chainId),
source: 'detected',
})
.build(),
),
);
}
};
importAllDetectedTokens();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [
selectedAddress,
isTokenDetectionEnabled,
allNetworks,
chainId,
currentDetectedTokens,
selectedNetworkClientId,
]);

const renderTabBar = useCallback(
(props) => (
<View style={styles.base}>
Expand Down

0 comments on commit 4def657

Please sign in to comment.