Skip to content

feat: add discover button to portfolio discover network page #30777

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

Merged
merged 22 commits into from
Mar 26, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
5a0e5ea
feat: add discover button to portfolio discover network page
Julink-eth Mar 5, 2025
be50272
fix: extra slash
Julink-eth Mar 6, 2025
0cf4c62
chore: refine network discover button
stanleyyconsensys Mar 7, 2025
d7b16b4
chore: remove un used method
stanleyyconsensys Mar 7, 2025
70eff65
chore: add network list menu item hook deps
stanleyyconsensys Mar 10, 2025
e7de77a
chore: fix network list menu test
stanleyyconsensys Mar 10, 2025
b664f12
chore: update test not using it.each
stanleyyconsensys Mar 10, 2025
05cee54
chore: update test title
stanleyyconsensys Mar 10, 2025
a00beb7
chore: remove unnecessary casting
stanleyyconsensys Mar 10, 2025
42cd199
chore: remove feature flag
stanleyyconsensys Mar 11, 2025
d2c2e15
chore: fix unit test
stanleyyconsensys Mar 11, 2025
b07a229
Merge branch 'main' into add-discover-network-button
Julink-eth Mar 11, 2025
edc3b35
fix: remove unused selector
Julink-eth Mar 11, 2025
4980350
chore: add discover translation to en_GB
Julink-eth Mar 11, 2025
fa39f42
Merge branch 'main' into add-discover-network-button
Julink-eth Mar 13, 2025
4ce842f
Merge branch 'main' into add-discover-network-button
Julink-eth Mar 18, 2025
2e83629
feat: add feature flag nePortfolioDiscoverButton
Julink-eth Mar 25, 2025
b4ad95d
Merge branch 'main' into add-discover-network-button
Julink-eth Mar 25, 2025
751972f
Merge branch 'main' into add-discover-network-button
Julink-eth Mar 26, 2025
f349f5a
Merge branch 'main' into add-discover-network-button
Julink-eth Mar 26, 2025
5b50bec
Merge branch 'main' into add-discover-network-button
Julink-eth Mar 26, 2025
ff4eeba
chore: convert renderButton function into useCallback
Julink-eth Mar 26, 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
3 changes: 3 additions & 0 deletions app/_locales/en/messages.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions app/_locales/en_GB/messages.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 8 additions & 0 deletions shared/constants/network.ts
Original file line number Diff line number Diff line change
Expand Up @@ -975,6 +975,14 @@ export const CHAIN_ID_TOKEN_IMAGE_MAP = {
[CHAINLIST_CHAIN_IDS_MAP.UNICHAIN_SEPOLIA]: ETH_TOKEN_IMAGE_URL,
} as const;

/**
* A mapping for networks with enabled profolio landing page to their URLs.
*/
export const CHAIN_ID_PROFOLIO_LANDING_PAGE_URL_MAP: Record<Hex, string> = {
[CHAIN_IDS.LINEA_MAINNET]:
'https://portfolio.metamask.io/explore/networks/linea',
} as const;

export const INFURA_BLOCKED_KEY = 'countryBlocked';

const defaultEtherscanDomain = 'etherscan.io';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ export const NetworkListItemMenu = ({
onClose,
onEditClick,
onDeleteClick,
onDiscoverClick,
isOpen,
}) => {
const t = useI18nContext();
Expand All @@ -38,6 +39,18 @@ export const NetworkListItemMenu = ({
>
<ModalFocus restoreFocus initialFocusRef={anchorElement}>
<Box>
{onDiscoverClick ? (
<MenuItem
iconName={IconName.Eye}
onClick={(e) => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can this be a useCallback as well?

Copy link
Contributor Author

@Julink-eth Julink-eth Mar 24, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey David!
I can add it but in that case should we add useCallBack for onEditClick and onDeleteClick as well ?

Copy link
Contributor

@stanleyyconsensys stanleyyconsensys Mar 25, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i think the onDiscoverClick is already having useCallback at getItemCallbacks in network-list-menu.tsx

then the function pass to network-list-item and finally landed in this component, which should have the same behaviour as other functions; e.g: onEditClick, onDeleteClick, no?

e.stopPropagation();
onDiscoverClick();
}}
data-testid="network-list-item-options-discover"
>
<Text>{t('discover')}</Text>
</MenuItem>
) : null}
{onEditClick ? (
<MenuItem
iconName={IconName.Edit}
Expand Down Expand Up @@ -86,6 +99,10 @@ NetworkListItemMenu.propTypes = {
* Function that executes when the Delete menu item is closed
*/
onDeleteClick: PropTypes.func,
/**
* Function that executes when the Discover menu item is clicked
*/
onDiscoverClick: PropTypes.func,
/**
* Represents if the menu is open or not
*
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,10 @@
import React, { ReactNode, useEffect, useRef, useState } from 'react';
import React, {
ReactNode,
useCallback,
useEffect,
useRef,
useState,
} from 'react';
import classnames from 'classnames';
import PropTypes from 'prop-types';
import {
Expand Down Expand Up @@ -45,6 +51,7 @@ export const NetworkListItem = ({
onClick,
onDeleteClick,
onEditClick,
onDiscoverClick,
onRpcEndpointClick,
startAccessory,
endAccessory,
Expand All @@ -62,6 +69,7 @@ export const NetworkListItem = ({
onRpcEndpointClick?: () => void;
onDeleteClick?: () => void;
onEditClick?: () => void;
onDiscoverClick?: () => void;
focus?: boolean;
startAccessory?: ReactNode;
endAccessory?: ReactNode;
Expand All @@ -82,8 +90,8 @@ export const NetworkListItem = ({
};
const [networkOptionsMenuOpen, setNetworkOptionsMenuOpen] = useState(false);

const renderButton = () => {
return onDeleteClick || onEditClick ? (
const renderButton = useCallback(() => {
return onDeleteClick || onEditClick || onDiscoverClick ? (
<ButtonIcon
iconName={IconName.MoreVertical}
ref={setNetworkListItemMenuRef}
Expand All @@ -96,7 +104,15 @@ export const NetworkListItem = ({
size={ButtonIconSize.Sm}
/>
) : null;
};
}, [
onDeleteClick,
onEditClick,
onDiscoverClick,
chainId,
t,
setNetworkListItemMenuRef,
setNetworkOptionsMenuOpen,
]);
useEffect(() => {
if (networkRef.current && focus) {
networkRef.current.focus();
Expand Down Expand Up @@ -217,6 +233,7 @@ export const NetworkListItem = ({
isOpen={networkOptionsMenuOpen}
onDeleteClick={onDeleteClick}
onEditClick={onEditClick}
onDiscoverClick={onDiscoverClick}
onClose={() => setNetworkOptionsMenuOpen(false)}
/>
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {
BNB_DISPLAY_NAME,
LINEA_SEPOLIA_DISPLAY_NAME,
} from '../../../../shared/constants/network';
import { hexToDecimal } from '../../../../shared/modules/conversion.utils';
import { NetworkListMenu } from '.';

const mockSetShowTestNetworks = jest.fn();
Expand Down Expand Up @@ -46,6 +47,7 @@ const render = ({
selectedTabOriginInDomainsState = true,
isAddingNewNetwork = false,
editedNetwork = undefined,
nePortfolioDiscoverButton = false,
} = {}) => {
const state = {
appState: {
Expand Down Expand Up @@ -80,6 +82,8 @@ const render = ({
networkClientId: 'linea-mainnet',
},
],
portfolioDiscoverUrl:
'https://portfolio.metamask.io/explore/networks/linea',
},
'0x38': {
nativeCurrency: 'BNB',
Expand Down Expand Up @@ -148,6 +152,9 @@ const render = ({
? { [origin]: selectedNetworkClientId }
: {}),
},
remoteFeatureFlags: {
nePortfolioDiscoverButton,
},
},
activeTab: {
origin: selectedTabOriginInDomainsState ? origin : undefined,
Expand Down Expand Up @@ -261,7 +268,7 @@ describe('NetworkListMenu', () => {
expect(queryByText('Add a custom network')).toBeEnabled();
});

it('enables the "AAdd a custom network" button when MetaMask is true', () => {
it('enables the "Add a custom network" button when MetaMask is true', () => {
const { queryByText } = render({ isUnlocked: true });
expect(queryByText('Add a custom network')).toBeEnabled();
});
Expand All @@ -273,6 +280,58 @@ describe('NetworkListMenu', () => {
).toHaveLength(0);
});

// For now, we only have Linea Mainnet enabled for the discover button.
it('enables the "Discover" button when the Feature Flag `nePortfolioDiscoverButton` is true and the network is supported', () => {
const { queryByTestId } = render({
nePortfolioDiscoverButton: true,
});

const menuButton = queryByTestId(
`network-list-item-options-button-eip155:${hexToDecimal(
CHAIN_IDS.LINEA_MAINNET,
)}`,
);
fireEvent.click(menuButton);

expect(
queryByTestId('network-list-item-options-discover'),
).toBeInTheDocument();
});

it('disables the "Discover" button when the Feature Flag `nePortfolioDiscoverButton` is false even if the network is supported', () => {
const { queryByTestId } = render({
nePortfolioDiscoverButton: false,
});

const menuButton = queryByTestId(
`network-list-item-options-button-eip155:${hexToDecimal(
CHAIN_IDS.LINEA_MAINNET,
)}`,
);
fireEvent.click(menuButton);

expect(
queryByTestId('network-list-item-options-discover'),
).not.toBeInTheDocument();
});

it('disables the "Discover" button when the network is not in the list of `CHAIN_ID_PROFOLIO_LANDING_PAGE_URL_MAP`', () => {
const { queryByTestId } = render({
nePortfolioDiscoverButton: true,
});

const menuButton = queryByTestId(
`network-list-item-options-button-eip155:${hexToDecimal(
CHAIN_IDS.MAINNET,
)}`,
);
fireEvent.click(menuButton);

expect(
queryByTestId('network-list-item-options-discover'),
).not.toBeInTheDocument();
});

describe('selectedTabOrigin is connected to wallet', () => {
it('fires setNetworkClientIdForDomain when network item is clicked', () => {
const { getByText } = render();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import {
import { type MultichainNetworkConfiguration } from '@metamask/multichain-network-controller';
import {
type CaipChainId,
type Hex,
parseCaipChainId,
KnownCaipNamespace,
} from '@metamask/utils';
Expand All @@ -47,6 +48,7 @@ import {
import {
FEATURED_RPCS,
TEST_CHAINS,
CHAIN_ID_PROFOLIO_LANDING_PAGE_URL_MAP,
} from '../../../../shared/constants/network';
import { MULTICHAIN_NETWORK_TO_NICKNAME } from '../../../../shared/constants/multichain/networks';
import {
Expand All @@ -64,6 +66,7 @@ import {
getPreferences,
getMultichainNetworkConfigurationsByChainId,
getSelectedMultichainNetworkChainId,
getIsPortfolioDiscoverButtonEnabled,
getAllChainsToPoll,
} from '../../../selectors';
import ToggleButton from '../../ui/toggle-button';
Expand Down Expand Up @@ -109,6 +112,7 @@ import {
} from '../../../ducks/metamask/metamask';
import NetworksForm from '../../../pages/settings/networks-tab/networks-form';
import { useNetworkFormState } from '../../../pages/settings/networks-tab/networks-form/networks-form-state';
import { openWindow } from '../../../helpers/utils/window';
import PopularNetworkList from './popular-network-list/popular-network-list';
import NetworkListSearch from './network-list-search/network-list-search';
import AddRpcUrlModal from './add-rpc-url-modal/add-rpc-url-modal';
Expand Down Expand Up @@ -148,6 +152,11 @@ export const NetworkListMenu = ({ onClose }: { onClose: () => void }) => {
const completedOnboarding = useSelector(getCompletedOnboarding);
const onboardedInThisUISession = useSelector(getOnboardedInThisUISession);
const showNetworkBanner = useSelector(getShowNetworkBanner);
// This selector provides the indication if the "Discover" button
// is enabled based on the remote feature flag.
const isPortfolioDiscoverButtonEnabled = useSelector(
getIsPortfolioDiscoverButtonEnabled,
);
// This selector provides an array with two elements.
// 1 - All network configurations including EVM and non-EVM with the data type
// MultichainNetworkConfiguration from @metamask/multichain-network-controller
Expand Down Expand Up @@ -382,6 +391,18 @@ export const NetworkListMenu = ({ onClose }: { onClose: () => void }) => {
});
};

const isDiscoverBtnEnabled = useCallback(
(hexChainId: Hex): boolean => {
// For now, the "Discover" button should be enabled only for Linea network base on
// the feature flag and the constants `CHAIN_ID_PROFOLIO_LANDING_PAGE_URL_MAP`.
return (
isPortfolioDiscoverButtonEnabled &&
CHAIN_ID_PROFOLIO_LANDING_PAGE_URL_MAP[hexChainId] !== undefined
);
},
[isPortfolioDiscoverButtonEnabled],
);

const hasMultiRpcOptions = useCallback(
(network: MultichainNetworkConfiguration): boolean =>
network.isEvm &&
Expand Down Expand Up @@ -435,6 +456,14 @@ export const NetworkListMenu = ({ onClose }: { onClose: () => void }) => {
);
setActionMode(ACTION_MODE.ADD_EDIT);
},
onDiscoverClick: isDiscoverBtnEnabled(hexChainId)
? () => {
openWindow(
CHAIN_ID_PROFOLIO_LANDING_PAGE_URL_MAP[hexChainId],
'_blank',
);
}
: undefined,
onRpcConfigEdit: hasMultiRpcOptions(network)
? () => {
setActionMode(ACTION_MODE.SELECT_RPC);
Expand All @@ -447,7 +476,13 @@ export const NetworkListMenu = ({ onClose }: { onClose: () => void }) => {
: undefined,
};
},
[currentChainId, dispatch, hasMultiRpcOptions, isUnlocked],
[
currentChainId,
dispatch,
hasMultiRpcOptions,
isUnlocked,
isDiscoverBtnEnabled,
],
);

// Renders a network in the network list
Expand All @@ -456,7 +491,8 @@ export const NetworkListMenu = ({ onClose }: { onClose: () => void }) => {
) => {
const { chainId } = network;
const isCurrentNetwork = chainId === currentChainId;
const { onDelete, onEdit, onRpcConfigEdit } = getItemCallbacks(network);
const { onDelete, onEdit, onDiscoverClick, onRpcConfigEdit } =
getItemCallbacks(network);
const iconSrc = getNetworkIcon(network);

return (
Expand All @@ -478,6 +514,7 @@ export const NetworkListMenu = ({ onClose }: { onClose: () => void }) => {
}}
onDeleteClick={onDelete}
onEditClick={onEdit}
onDiscoverClick={onDiscoverClick}
onRpcEndpointClick={onRpcConfigEdit}
disabled={!isNetworkEnabled(network)}
/>
Expand Down
12 changes: 12 additions & 0 deletions ui/selectors/selectors.js
Original file line number Diff line number Diff line change
Expand Up @@ -2880,6 +2880,18 @@ export function getIsCustomNetwork(state) {
return !CHAIN_ID_TO_RPC_URL_MAP[chainId];
}

/**
* Get the state of the `nePortfolioDiscoverButton` remote feature flag.
* This flag determines whether the user should see a `Discover` button on the network menu list.
*
* @param {*} state
* @returns The state of the `nePortfolioDiscoverButton` remote feature flag.
*/
export function getIsPortfolioDiscoverButtonEnabled(state) {
const { nePortfolioDiscoverButton } = getRemoteFeatureFlags(state);
return Boolean(nePortfolioDiscoverButton);
}

export function getBlockExplorerLinkText(
state,
accountDetailsModalComponent = false,
Expand Down
Loading