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

refactor: asset-list and token-list #29886

Merged
merged 26 commits into from
Feb 7, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
06f4bfa
chore: Create consolidateTokenBalances utility function
gambinish Jan 23, 2025
2eecfcf
fix: Continue to break out utility files
gambinish Jan 23, 2025
439b107
Merge branch 'main' into refactor/mmassets-492_chain-agnostic-asset-list
gambinish Feb 5, 2025
09a8195
refactor: consolidateBalances to getTokenBalancesEvm selector
gambinish Feb 5, 2025
cbfd264
Merge branch 'main' into refactor/mmassets-492_chain-agnostic-asset-list
gambinish Feb 5, 2025
2811cd7
Merge branch 'refactor/mmassets-492_chain-agnostic-asset-list' of git…
gambinish Feb 5, 2025
b2febcc
chore: Remove consolidatedBalances util in favor of fleshed out selector
gambinish Feb 5, 2025
ec9c6bb
chore: cleanup
gambinish Feb 5, 2025
746f67d
chore: Move useFilteredAccountTokens to selector
gambinish Feb 6, 2025
f17ec92
refactor: Breakout redundant business logic into reusable hooks
gambinish Feb 6, 2025
e7c3ab8
refactor: Intorudce useTokenDetection hook and AssetListFundingModals
gambinish Feb 6, 2025
c70d298
Lint and cleanup importAllDetectedTokens method
gambinish Feb 6, 2025
65eb517
fix: Lint file imports
gambinish Feb 6, 2025
fb12981
fix: unit test
gambinish Feb 6, 2025
655c9b1
Merge branch 'main' into refactor/mmassets-492_chain-agnostic-asset-list
gambinish Feb 6, 2025
b298110
fix: Update circular deps
gambinish Feb 6, 2025
75c4e28
Merge branch 'main' into refactor/mmassets-492_chain-agnostic-asset-list
gambinish Feb 6, 2025
2c19b46
chore: Reorganize types
gambinish Feb 6, 2025
b4c45ee
Merge branch 'refactor/mmassets-492_chain-agnostic-asset-list' of git…
gambinish Feb 6, 2025
b375512
fix: Update circular deps
gambinish Feb 6, 2025
fa06c79
Lint
gambinish Feb 6, 2025
8dd574d
fix: Update flask code fencing
gambinish Feb 6, 2025
17a718a
fix: Rename action dispatcher handler
gambinish Feb 6, 2025
1885f1e
fix: Cleanup
gambinish Feb 6, 2025
78b2643
Merge branch 'main' into refactor/mmassets-492_chain-agnostic-asset-list
gambinish Feb 6, 2025
3df8360
Merge branch 'main' into refactor/mmassets-492_chain-agnostic-asset-list
gambinish Feb 7, 2025
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
8 changes: 0 additions & 8 deletions development/circular-deps.jsonc
Original file line number Diff line number Diff line change
Expand Up @@ -43,14 +43,6 @@
"ui/components/app/assets/asset-list/native-token/native-token.tsx",
"ui/components/app/assets/asset-list/native-token/use-native-token-balance.ts"
],
[
"ui/components/app/assets/token-list/token-list.tsx",
"ui/components/app/assets/util/calculateTokenBalance.ts"
],
[
"ui/components/app/assets/token-list/token-list.tsx",
"ui/components/app/assets/util/calculateTokenFiatAmount.ts"
],
gambinish marked this conversation as resolved.
Show resolved Hide resolved
[
"ui/components/app/name/name-details/name-details.tsx",
"ui/components/app/name/name.tsx"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import React, { useState } from 'react';
import { useSelector } from 'react-redux';
import { ReceiveModal } from '../../../../multichain';
import { FundingMethodModal } from '../../../../multichain/funding-method-modal/funding-method-modal';
import { useI18nContext } from '../../../../../hooks/useI18nContext';
import { getSelectedAccount } from '../../../../../selectors';
///: BEGIN:ONLY_INCLUDE_IF(build-main,build-beta,build-flask)
import {
getMultichainIsBitcoin,
getMultichainSelectedAccountCachedBalanceIsZero,
} from '../../../../../selectors/multichain';
import { getIsNativeTokenBuyable } from '../../../../../ducks/ramps';
import { RampsCard } from '../../../../multichain/ramps-card';
import { RAMPS_CARD_VARIANT_TYPES } from '../../../../multichain/ramps-card/ramps-card';
///: END:ONLY_INCLUDE_IF

const AssetListFundingModals = () => {
const t = useI18nContext();
const selectedAccount = useSelector(getSelectedAccount);

const [showFundingMethodModal, setShowFundingMethodModal] = useState(false);
const [showReceiveModal, setShowReceiveModal] = useState(false);

const onClickReceive = () => {
setShowFundingMethodModal(false);
setShowReceiveModal(true);
};

///: BEGIN:ONLY_INCLUDE_IF(build-main,build-beta,build-flask)
const balanceIsZero = useSelector(
getMultichainSelectedAccountCachedBalanceIsZero,
);
const isBuyableChain = useSelector(getIsNativeTokenBuyable);
const shouldShowBuy = isBuyableChain && balanceIsZero;
const isBtc = useSelector(getMultichainIsBitcoin);
///: END:ONLY_INCLUDE_IF

return (
<>
{
///: BEGIN:ONLY_INCLUDE_IF(build-main,build-beta,build-flask)
shouldShowBuy ? (
<RampsCard
variant={
isBtc
? RAMPS_CARD_VARIANT_TYPES.BTC
: RAMPS_CARD_VARIANT_TYPES.TOKEN
}
handleOnClick={
isBtc ? undefined : () => setShowFundingMethodModal(true)
}
/>
) : null
///: END:ONLY_INCLUDE_IF
}
{showReceiveModal && selectedAccount?.address && (
<ReceiveModal
address={selectedAccount.address}
onClose={() => setShowReceiveModal(false)}
/>
)}
{showFundingMethodModal && (
<FundingMethodModal
isOpen={showFundingMethodModal}
onClose={() => setShowFundingMethodModal(false)}
title={t('fundingMethod')}
onClickReceive={onClickReceive}
/>
)}
</>
);
};

export default AssetListFundingModals;
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default } from './asset-list-funding-modals';
212 changes: 10 additions & 202 deletions ui/components/app/assets/asset-list/asset-list.tsx
Original file line number Diff line number Diff line change
@@ -1,56 +1,18 @@
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 React, { useContext } from 'react';
import { useSelector } from 'react-redux';
import TokenList from '../token-list';
import { PRIMARY } from '../../../../helpers/constants/common';
import { useUserPreferencedCurrency } from '../../../../hooks/useUserPreferencedCurrency';
import {
getAllDetectedTokensForSelectedAddress,
getDetectedTokensInCurrentNetwork,
getIsTokenNetworkFilterEqualCurrentNetwork,
getSelectedAccount,
getSelectedAddress,
getUseTokenDetection,
} from '../../../../selectors';
import {
getMultichainIsEvm,
getMultichainSelectedAccountCachedBalance,
///: BEGIN:ONLY_INCLUDE_IF(build-main,build-beta,build-flask)
getMultichainIsBitcoin,
getMultichainSelectedAccountCachedBalanceIsZero,
///: END:ONLY_INCLUDE_IF
} from '../../../../selectors/multichain';
import { useCurrencyDisplay } from '../../../../hooks/useCurrencyDisplay';
import { getMultichainIsEvm } from '../../../../selectors/multichain';
import { MetaMetricsContext } from '../../../../contexts/metametrics';
import {
MetaMetricsEventCategory,
MetaMetricsEventName,
MetaMetricsTokenEventSource,
} from '../../../../../shared/constants/metametrics';
import DetectedToken from '../../detected-token/detected-token';
import { ReceiveModal } from '../../../multichain';
import { useI18nContext } from '../../../../hooks/useI18nContext';
import { FundingMethodModal } from '../../../multichain/funding-method-modal/funding-method-modal';
///: BEGIN:ONLY_INCLUDE_IF(build-main,build-beta,build-flask)
import {
RAMPS_CARD_VARIANT_TYPES,
RampsCard,
} from '../../../multichain/ramps-card/ramps-card';
import { getIsNativeTokenBuyable } from '../../../../ducks/ramps';
///: END:ONLY_INCLUDE_IF
import {
getCurrentChainId,
getNetworkConfigurationsByChainId,
getSelectedNetworkClientId,
} from '../../../../../shared/modules/selectors/networks';
import { addImportedTokens } from '../../../../store/actions';
import {
AssetType,
TokenStandard,
} from '../../../../../shared/constants/transaction';
import useAssetListTokenDetection from '../hooks/useAssetListTokenDetection';
import usePrimaryCurrencyProperties from '../hooks/usePrimaryCurrencyProperties';
import AssetListControlBar from './asset-list-control-bar';
import NativeToken from './native-token';
import AssetListFundingModals from './asset-list-funding-modals';

export type TokenWithBalance = {
address: string;
Expand All @@ -68,142 +30,17 @@ export type AssetListProps = {
};

const AssetList = ({ onClickAsset, showTokensLinks }: AssetListProps) => {
const dispatch = useDispatch();
const [showDetectedTokens, setShowDetectedTokens] = useState(false);
const selectedAccount = useSelector(getSelectedAccount);
const t = useI18nContext();
const { showDetectedTokens, setShowDetectedTokens } =
useAssetListTokenDetection();
const trackEvent = useContext(MetaMetricsContext);
const balance = useSelector(getMultichainSelectedAccountCachedBalance);

const {
currency: primaryCurrency,
numberOfDecimals: primaryNumberOfDecimals,
} = useUserPreferencedCurrency(PRIMARY, {
ethNumberOfDecimals: 4,
shouldCheckShowNativeToken: true,
});

const [, primaryCurrencyProperties] = useCurrencyDisplay(balance, {
numberOfDecimals: primaryNumberOfDecimals,
currency: primaryCurrency,
});

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 [showFundingMethodModal, setShowFundingMethodModal] = useState(false);
const [showReceiveModal, setShowReceiveModal] = useState(false);

const onClickReceive = () => {
setShowFundingMethodModal(false);
setShowReceiveModal(true);
};

///: BEGIN:ONLY_INCLUDE_IF(build-main,build-beta,build-flask)
const balanceIsZero = useSelector(
getMultichainSelectedAccountCachedBalanceIsZero,
);
const isBuyableChain = useSelector(getIsNativeTokenBuyable);
const shouldShowBuy = isBuyableChain && balanceIsZero;
const isBtc = useSelector(getMultichainIsBitcoin);
///: END:ONLY_INCLUDE_IF
const { primaryCurrencyProperties } = usePrimaryCurrencyProperties();

const isEvm = useSelector(getMultichainIsEvm);
// NOTE: Since we can parametrize it now, we keep the original behavior
// for EVM assets
const shouldShowTokensLinks = showTokensLinks ?? isEvm;

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

const multichainDetectedTokensLength = Object.values(
detectedTokensMultichain || {},
).reduce((acc, tokens) => acc + tokens.length, 0);

// 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();
}, [
isTokenNetworkFilterEqualCurrentNetwork,
selectedAddress,
networkClientId,
detectedTokens.length,
multichainDetectedTokensLength,
]);

return (
<>
<AssetListControlBar showTokensLinks={shouldShowTokensLinks} />
Expand All @@ -223,39 +60,10 @@ const AssetList = ({ onClickAsset, showTokensLinks }: AssetListProps) => {
});
}}
/>
{
///: BEGIN:ONLY_INCLUDE_IF(build-main,build-beta,build-flask)
shouldShowBuy ? (
<RampsCard
variant={
isBtc
? RAMPS_CARD_VARIANT_TYPES.BTC
: RAMPS_CARD_VARIANT_TYPES.TOKEN
}
handleOnClick={
isBtc ? undefined : () => setShowFundingMethodModal(true)
}
/>
) : null
///: END:ONLY_INCLUDE_IF
}
{showDetectedTokens && (
<DetectedToken setShowDetectedTokens={setShowDetectedTokens} />
)}
{showReceiveModal && selectedAccount?.address && (
<ReceiveModal
address={selectedAccount.address}
onClose={() => setShowReceiveModal(false)}
/>
)}
{showFundingMethodModal && (
<FundingMethodModal
isOpen={showFundingMethodModal}
onClose={() => setShowFundingMethodModal(false)}
title={t('fundingMethod')}
onClickReceive={onClickReceive}
/>
)}
<AssetListFundingModals />
</>
);
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ import {
BackgroundColor,
TextColor,
} from '../../../../../helpers/constants/design-system';

import { getMultichainIsEvm } from '../../../../../selectors/multichain';

type AssetListControlBarProps = {
Expand Down
10 changes: 0 additions & 10 deletions ui/components/app/assets/auto-detect-token/index.scss

This file was deleted.

Loading
Loading