Skip to content
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
0e3d705
chore: remove legacy accounts component code (pre BIP-44)
gantunesr Jan 19, 2026
d9c66a1
Merge branch 'main' of https://github.com/MetaMask/metamask-mobile in…
gantunesr Jan 19, 2026
f0751b4
test: add BIP-44 state 2 fixture
gantunesr Jan 19, 2026
27e2977
Merge branch 'main' of https://github.com/MetaMask/metamask-mobile in…
gantunesr Jan 26, 2026
1a8da44
Merge branch 'main' into gar/chore/remove-legacy-bip44/components
gantunesr Jan 26, 2026
79744c0
Merge branch 'main' into gar/chore/remove-legacy-bip44/components
gantunesr Jan 27, 2026
a6c61f5
chore: revert E2E changes
gantunesr Jan 27, 2026
c09108b
Merge branch 'main' into gar/chore/remove-legacy-bip44/components
gantunesr Jan 27, 2026
fd86470
Merge branch 'main' into gar/chore/remove-legacy-bip44/components
gantunesr Jan 27, 2026
c5cd450
chore: remove unused hook
gantunesr Jan 27, 2026
99b4b82
test: update selector
gantunesr Jan 27, 2026
f2e329d
Merge branch 'gar/chore/remove-legacy-bip44/components' of https://gi…
gantunesr Jan 27, 2026
671ab67
Merge branch 'main' into gar/chore/remove-legacy-bip44/components
gantunesr Jan 28, 2026
63f90b0
chore: update AssetOverview
gantunesr Jan 28, 2026
238aa97
test: revert changes
gantunesr Jan 28, 2026
16519e0
chore: review feedback
gantunesr Jan 29, 2026
599c491
chore: remove redundant branch
gantunesr Jan 29, 2026
ccde54c
test: solve test failures
gantunesr Jan 29, 2026
24450ff
Merge branch 'main' into gar/chore/remove-legacy-bip44/components
gantunesr Jan 29, 2026
5d0d3b8
Merge branch 'main' into gar/chore/remove-legacy-bip44/components
gantunesr Jan 29, 2026
0910923
chore: revert AssetOverview
gantunesr Jan 30, 2026
1d5030f
Merge branch 'gar/chore/remove-legacy-bip44/components' of https://gi…
gantunesr Jan 30, 2026
68452f0
test: update snapshots
gantunesr Jan 30, 2026
0516e6e
Merge branch 'main' into gar/chore/remove-legacy-bip44/components
gantunesr Jan 30, 2026
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
25 changes: 4 additions & 21 deletions app/components/UI/AddressCopy/AddressCopy.test.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,8 @@
import React from 'react';
import { InternalAccount } from '@metamask/keyring-internal-api';

import AddressCopy from './AddressCopy';
import { WalletViewSelectorsIDs } from '../../Views/Wallet/WalletView.testIds';
import renderWithProvider from '../../../util/test/renderWithProvider';
import { createMockInternalAccount } from '../../../util/test/accountsControllerTestUtils';
import { ToastContext } from '../../../component-library/components/Toast';

// Mock navigation before importing renderWithProvider
jest.mock('@react-navigation/native', () => ({
Expand All @@ -15,32 +12,18 @@ jest.mock('@react-navigation/native', () => ({
}),
}));

const mockShowToast = jest.fn();
const mockCloseToast = jest.fn();
const mockToastRef = {
current: { showToast: mockShowToast, closeToast: mockCloseToast },
};

const renderWithAddressCopy = (account: InternalAccount) =>
renderWithProvider(
<ToastContext.Provider value={{ toastRef: mockToastRef }}>
<AddressCopy account={account} />
</ToastContext.Provider>,
);
const renderAddressCopy = () => renderWithProvider(<AddressCopy />);

describe('AddressCopy', () => {
beforeEach(() => {
jest.clearAllMocks();
});

it('renders correctly the component', () => {
const component = renderWithAddressCopy(
createMockInternalAccount('0xaddress', 'Account 1'),
);
it('renders the copy button', () => {
const { getByTestId } = renderAddressCopy();

expect(component).toBeDefined();
expect(
component.getByTestId(WalletViewSelectorsIDs.ACCOUNT_COPY_BUTTON),
getByTestId(WalletViewSelectorsIDs.ACCOUNT_COPY_BUTTON),
).toBeDefined();
});
});
78 changes: 4 additions & 74 deletions app/components/UI/AddressCopy/AddressCopy.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// Third parties dependencies
import React, { useCallback, useContext } from 'react';
import { useSelector, useDispatch } from 'react-redux';
import React, { useCallback } from 'react';
import { useSelector } from 'react-redux';
import { View } from 'react-native';
import { useNavigation } from '@react-navigation/native';
import { AccountGroupId } from '@metamask/account-api';
Expand All @@ -11,27 +11,15 @@ import {
ButtonIconSize,
IconName,
} from '@metamask/design-system-react-native';
import ClipboardManager from '../../../core/ClipboardManager';
import { protectWalletModalVisible } from '../../../actions/user';
import {
ToastContext,
ToastVariants,
} from '../../../component-library/components/Toast';
import { IconName as ComponentLibraryIconName } from '../../../component-library/components/Icons/Icon';

import { strings } from '../../../../locales/i18n';
import { MetaMetricsEvents } from '../../../core/Analytics';
import { useStyles } from '../../../component-library/hooks';
import { WalletViewSelectorsIDs } from '../../Views/Wallet/WalletView.testIds';
import { selectMultichainAccountsState2Enabled } from '../../../selectors/featureFlagController/multichainAccounts/enabledMultichainAccounts';
import { selectSelectedAccountGroupId } from '../../../selectors/multichainAccounts/accountTreeController';
import { createAddressListNavigationDetails } from '../../Views/MultichainAccounts/AddressList';

// Internal dependencies
import styleSheet from './AddressCopy.styles';
import { useMetrics } from '../../../components/hooks/useMetrics';
import { useTheme } from '../../../util/theme';
import { getFormattedAddressFromInternalAccount } from '../../../core/Multichain/utils';
import type { AddressCopyProps } from './AddressCopy.types';
import {
endTrace,
Expand All @@ -40,59 +28,13 @@ import {
TraceOperation,
} from '../../../util/trace';

const AddressCopy = ({ account, iconColor, hitSlop }: AddressCopyProps) => {
const AddressCopy = ({ iconColor, hitSlop }: AddressCopyProps) => {
const { styles } = useStyles(styleSheet, {});
const { navigate } = useNavigation();
const { colors } = useTheme();

const dispatch = useDispatch();
const { trackEvent, createEventBuilder } = useMetrics();
const { toastRef } = useContext(ToastContext);

const isMultichainAccountsState2Enabled = useSelector(
selectMultichainAccountsState2Enabled,
);
const selectedAccountGroupId = useSelector(selectSelectedAccountGroupId);

const handleProtectWalletModalVisible = useCallback(
() => dispatch(protectWalletModalVisible()),
[dispatch],
);

/**
* A string that represents the selected address
*/

const copyAccountToClipboard = useCallback(async () => {
await ClipboardManager.setString(
getFormattedAddressFromInternalAccount(account),
);
toastRef?.current?.showToast({
variant: ToastVariants.Icon,
iconName: ComponentLibraryIconName.CheckBold,
iconColor: colors.accent03.dark,
backgroundColor: colors.accent03.normal,
labelOptions: [
{ label: strings('account_details.account_copied_to_clipboard') },
],
hasNoTimeout: false,
});
setTimeout(() => handleProtectWalletModalVisible(), 2000);

trackEvent(
createEventBuilder(MetaMetricsEvents.WALLET_COPIED_ADDRESS).build(),
);
}, [
account,
colors.accent03.dark,
colors.accent03.normal,
createEventBuilder,
handleProtectWalletModalVisible,
toastRef,
trackEvent,
]);

const navigateToAddressList = useCallback(() => {
const handleOnPress = useCallback(() => {
// Start the trace before navigating to the address list to include the
// navigation and render times in the trace.
trace({
Expand All @@ -116,18 +58,6 @@ const AddressCopy = ({ account, iconColor, hitSlop }: AddressCopyProps) => {
);
}, [navigate, selectedAccountGroupId]);

const handleOnPress = useCallback(() => {
if (isMultichainAccountsState2Enabled) {
navigateToAddressList();
} else {
copyAccountToClipboard();
}
}, [
copyAccountToClipboard,
isMultichainAccountsState2Enabled,
navigateToAddressList,
]);

return (
<View style={styles.address}>
<ButtonIcon
Expand Down
2 changes: 0 additions & 2 deletions app/components/UI/AddressCopy/AddressCopy.types.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
import { InternalAccount } from '@metamask/keyring-internal-api';
import { IconColor } from '@metamask/design-system-react-native';

export interface AddressCopyProps {
account: InternalAccount;
iconColor?: IconColor;
hitSlop?: {
top?: number;
Expand Down
26 changes: 3 additions & 23 deletions app/components/UI/AssetOverview/AssetOverview.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -260,13 +260,6 @@ jest.mock('../../../components/hooks/useMetrics', () => {
};
});

jest.mock(
'../../../selectors/featureFlagController/multichainAccounts',
() => ({
selectMultichainAccountsState2Enabled: () => false,
}),
);

const mockAddPopularNetwork = jest
.fn()
.mockImplementation(() => Promise.resolve());
Expand Down Expand Up @@ -1622,36 +1615,23 @@ describe('AssetOverview', () => {

const secondaryBalance = getByTestId(TOKEN_AMOUNT_BALANCE_TEST_ID);

// Should display formatted Solana balance
expect(secondaryBalance.props.children).toBe('123.45679 SOL');
// Balance is displayed directly from the asset
expect(secondaryBalance.props.children).toBe('123.456789 SOL');
});
});

it('should not render Balance component when balance is undefined', () => {
// Given an asset with undefined balance
it('does not render Balance component when balance is undefined', () => {
const assetWithNoBalance = {
...asset,
balance: undefined as unknown as string,
};

// Override the mock to enable state2 so balance stays undefined
const mockModule = jest.requireMock(
'../../../selectors/featureFlagController/multichainAccounts',
);
const originalMock = mockModule.selectMultichainAccountsState2Enabled;
mockModule.selectMultichainAccountsState2Enabled = jest
.fn()
.mockReturnValue(true);

const { queryByTestId } = renderWithProvider(
<AssetOverview asset={assetWithNoBalance} />,
{ state: mockInitialState },
);

expect(queryByTestId(BALANCE_TEST_ID)).toBeNull();

// Restore original mock
mockModule.selectMultichainAccountsState2Enabled = originalMock;
});

describe('Exchange Rate Fetching', () => {
Expand Down
68 changes: 4 additions & 64 deletions app/components/UI/AssetOverview/AssetOverview.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import {
isCaipChainId,
///: END:ONLY_INCLUDE_IF
} from '@metamask/utils';
import I18n, { strings } from '../../../../locales/i18n';
import { strings } from '../../../../locales/i18n';
import { TokenOverviewSelectorsIDs } from './TokenOverview.testIds';
import { newAssetTransaction } from '../../../actions/transaction';
import AppConstants from '../../../core/AppConstants';
Expand All @@ -31,21 +31,10 @@ import {
selectCurrentCurrency,
selectCurrencyRates,
} from '../../../selectors/currencyRateController';
import { selectAccountsByChainId } from '../../../selectors/accountTrackerController';
import { selectTokensBalances } from '../../../selectors/tokenBalancesController';
import {
selectSelectedInternalAccount,
selectSelectedInternalAccountFormattedAddress,
} from '../../../selectors/accountsController';
import { selectSelectedInternalAccount } from '../../../selectors/accountsController';
import Logger from '../../../util/Logger';
import { safeToChecksumAddress } from '../../../util/address';
import {
renderFromTokenMinimalUnit,
renderFromWei,
toHexadecimal,
addCurrencySymbol,
balanceToFiatNumber,
} from '../../../util/number';
import { addCurrencySymbol, balanceToFiatNumber } from '../../../util/number';
import { getEther } from '../../../util/transactions';
import Text from '../../Base/Text';
import { createWebviewNavDetails } from '../../Views/SimpleWebview';
Expand Down Expand Up @@ -79,7 +68,6 @@ import {
isAssetFromSearch,
selectTokenDisplayData,
} from '../../../selectors/tokenSearchDiscoveryDataController';
import { formatWithThreshold } from '../../../util/assets';
import {
useSwapBridgeNavigation,
SwapBridgeNavigationLocation,
Expand All @@ -93,7 +81,6 @@ import {
import { TraceName, endTrace } from '../../../util/trace';
///: BEGIN:ONLY_INCLUDE_IF(keyring-snaps)
import { selectMultichainAssetsRates } from '../../../selectors/multichain';
import { isEvmAccountType, KeyringAccountType } from '@metamask/keyring-api';
import { useSendNonEvmAsset } from '../../hooks/useSendNonEvmAsset';
///: END:ONLY_INCLUDE_IF
import { calculateAssetPrice } from './utils/calculateAssetPrice';
Expand All @@ -103,7 +90,6 @@ import {
} from '@metamask/bridge-controller';
import { InitSendLocation } from '../../Views/confirmations/constants/send';
import { useSendNavigation } from '../../Views/confirmations/hooks/useSendNavigation';
import { selectMultichainAccountsState2Enabled } from '../../../selectors/featureFlagController/multichainAccounts';
import parseRampIntent from '../Ramp/utils/parseRampIntent';
///: BEGIN:ONLY_INCLUDE_IF(tron)
import TronEnergyBandwidthDetail from './TronEnergyBandwidthDetail/TronEnergyBandwidthDetail';
Expand Down Expand Up @@ -214,15 +200,10 @@ const AssetOverview: React.FC<AssetOverviewProps> = ({
const navigation = useNavigation();
const [timePeriod, setTimePeriod] = React.useState<TimePeriod>('1d');
const selectedInternalAccount = useSelector(selectSelectedInternalAccount);
const selectedInternalAccountAddress = selectedInternalAccount?.address;
const selectedAccountGroup = useSelector(selectSelectedAccountGroup);
const getAccountByScope = useSelector(selectSelectedInternalAccountByScope);
const conversionRateByTicker = useSelector(selectCurrencyRates);
const currentCurrency = useSelector(selectCurrentCurrency);
const accountsByChainId = useSelector(selectAccountsByChainId);
const selectedAddress = useSelector(
selectSelectedInternalAccountFormattedAddress,
);
const { trackEvent, createEventBuilder } = useMetrics();
const allTokenMarketData = useSelector(selectTokenMarketData);
const selectedChainId = useSelector(selectEvmChainId);
Expand All @@ -241,11 +222,6 @@ const AssetOverview: React.FC<AssetOverviewProps> = ({
selectNativeCurrencyByChainId(state, asset.chainId as Hex),
);

const multiChainTokenBalance = useSelector(selectTokensBalances);
const isMultichainAccountsState2Enabled = useSelector(
selectMultichainAccountsState2Enabled,
);

const chainId = asset.chainId as Hex;
const ticker = nativeCurrency;
const selectedNetworkClientId = useSelector(selectSelectedNetworkClientId);
Expand Down Expand Up @@ -614,10 +590,6 @@ const AssetOverview: React.FC<AssetOverviewProps> = ({
const exchangeRate = marketDataRate ?? fetchedRate;

let balance;
const minimumDisplayThreshold = 0.00001;

const isMultichainAsset = isNonEvmAsset;
const isEthOrNative = asset.isETH || asset.isNative;

///: BEGIN:ONLY_INCLUDE_IF(tron)
const isTronNative =
Expand All @@ -637,40 +609,8 @@ const AssetOverview: React.FC<AssetOverviewProps> = ({
}
///: END:ONLY_INCLUDE_IF

if (isMultichainAccountsState2Enabled && balanceSource != null) {
// When state2 is enabled and asset has balance, use it directly
if (balanceSource != null) {
balance = balanceSource;
} else if (isMultichainAsset) {
balance = balanceSource
? formatWithThreshold(
parseFloat(balanceSource),
minimumDisplayThreshold,
I18n.locale,
{ minimumFractionDigits: 0, maximumFractionDigits: 5 },
)
: undefined;
} else if (isEthOrNative) {
balance = renderFromWei(
// @ts-expect-error - This should be fixed at the accountsController selector level, ongoing discussion
accountsByChainId[toHexadecimal(chainId)]?.[selectedAddress]?.balance,
);
} else {
const multiChainTokenBalanceHex =
itemAddress &&
multiChainTokenBalance?.[selectedInternalAccountAddress as Hex]?.[
chainId as Hex
]?.[itemAddress as Hex];
const tokenBalanceHex = multiChainTokenBalanceHex;
if (
!isEvmAccountType(selectedInternalAccount?.type as KeyringAccountType)
) {
balance = asset.balance ?? undefined;
} else {
balance =
itemAddress && tokenBalanceHex
? renderFromTokenMinimalUnit(tokenBalanceHex, asset.decimals)
: (asset.balance ?? undefined);
}
}

const convertedMultichainAssetRates =
Expand Down
5 changes: 1 addition & 4 deletions app/components/UI/Navbar/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -995,10 +995,7 @@ export function getWalletNavbarOptions(
<View
testID={WalletViewSelectorsIDs.NAVBAR_ADDRESS_COPY_BUTTON}
>
<AddressCopy
account={selectedInternalAccount}
hitSlop={innerStyles.touchAreaSlop}
/>
<AddressCopy hitSlop={innerStyles.touchAreaSlop} />
</View>
{shouldDisplayCardButton && (
<CardButton
Expand Down
Loading
Loading