Skip to content
Closed
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
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ import { getNetworkOrderControllerMessenger } from './network-order-controller-m
describe('getNetworkOrderControllerMessenger', () => {
it('returns a restricted messenger', () => {
const messenger = new Messenger<never, never>();
const nftControllerMessenger =
const networkOrderControllerMessenger =
getNetworkOrderControllerMessenger(messenger);
expect(nftControllerMessenger).toBeInstanceOf(RestrictedMessenger);
expect(networkOrderControllerMessenger).toBeInstanceOf(RestrictedMessenger);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { Messenger } from '@metamask/base-controller';
import {
NetworkControllerGetStateAction,
NetworkControllerNetworkRemovedEvent,
NetworkControllerNetworkAddedEvent,
NetworkControllerSetActiveNetworkAction,
NetworkControllerStateChangeEvent,
} from '@metamask/network-controller';
Expand All @@ -11,7 +12,8 @@ type Actions =
| NetworkControllerSetActiveNetworkAction;
type Events =
| NetworkControllerStateChangeEvent
| NetworkControllerNetworkRemovedEvent;
| NetworkControllerNetworkRemovedEvent
| NetworkControllerNetworkAddedEvent;

export type NetworkOrderControllerMessenger = ReturnType<
typeof getNetworkOrderControllerMessenger
Expand All @@ -25,6 +27,7 @@ export function getNetworkOrderControllerMessenger(
allowedEvents: [
'NetworkController:stateChange',
'NetworkController:networkRemoved',
'NetworkController:networkAdded',
],
allowedActions: [
'NetworkController:getState',
Expand Down
112 changes: 111 additions & 1 deletion app/scripts/controllers/network-order.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import {
NetworkConfiguration,
NetworkControllerGetStateAction,
NetworkControllerNetworkRemovedEvent,
NetworkControllerNetworkAddedEvent,
NetworkControllerSetActiveNetworkAction,
NetworkControllerStateChangeEvent,
NetworkState,
Expand Down Expand Up @@ -134,6 +135,112 @@ describe('NetworkOrderController - constructor', () => {
});
});

it('enables featured network when NetworkController:networkAdded event is emitted', () => {
const mocks = arrangeMockMessenger();
const controller = new NetworkOrderController({
messenger: mocks.messenger,
state: {
orderedNetworkList: [],
enabledNetworkMap: {
[KnownCaipNamespace.Eip155]: {
[CHAIN_IDS.MAINNET]: true,
},
[KnownCaipNamespace.Solana]: {
[SolScope.Mainnet]: true,
},
[KnownCaipNamespace.Bip122]: {},
},
},
});

// Test featured EVM network addition (Base is in FEATURED_NETWORK_CHAIN_IDS)
mocks.globalMessenger.publish('NetworkController:networkAdded', {
chainId: CHAIN_IDS.BASE,
} as unknown as NetworkConfiguration);

expect(controller.state.enabledNetworkMap).toStrictEqual({
[KnownCaipNamespace.Eip155]: {
[CHAIN_IDS.MAINNET]: true,
[CHAIN_IDS.BASE]: true, // Base has been enabled
},
[KnownCaipNamespace.Solana]: {
[SolScope.Mainnet]: true,
},
[KnownCaipNamespace.Bip122]: {},
});
});

it('enables only custom network and disables others when custom NetworkController:networkAdded event is emitted', () => {
const mocks = arrangeMockMessenger();
const controller = new NetworkOrderController({
messenger: mocks.messenger,
state: {
orderedNetworkList: [],
enabledNetworkMap: {
[KnownCaipNamespace.Eip155]: {
[CHAIN_IDS.MAINNET]: true,
[CHAIN_IDS.POLYGON]: true,
[CHAIN_IDS.BASE]: true,
},
[KnownCaipNamespace.Solana]: {
[SolScope.Mainnet]: true,
},
[KnownCaipNamespace.Bip122]: {},
},
},
});

// Test custom EVM network addition (0x1337 is NOT in FEATURED_NETWORK_CHAIN_IDS)
mocks.globalMessenger.publish('NetworkController:networkAdded', {
chainId: '0x1337',
} as unknown as NetworkConfiguration);

// Custom networks should disable all other EVM networks and enable only the custom network
expect(controller.state.enabledNetworkMap).toStrictEqual({
[KnownCaipNamespace.Eip155]: {
'0x1337': true, // Only the custom network is enabled
// All other EVM networks (Mainnet, Polygon, Base) are disabled
},
[KnownCaipNamespace.Solana]: {
[SolScope.Mainnet]: true, // Non-EVM networks remain unchanged
},
[KnownCaipNamespace.Bip122]: {},
});
});

it('enables non-EVM network when NetworkController:networkAdded event is emitted', () => {
const mocks = arrangeMockMessenger();
const controller = new NetworkOrderController({
messenger: mocks.messenger,
state: {
orderedNetworkList: [],
enabledNetworkMap: {
[KnownCaipNamespace.Eip155]: {
[CHAIN_IDS.MAINNET]: true,
},
[KnownCaipNamespace.Solana]: {},
[KnownCaipNamespace.Bip122]: {},
},
},
});

// Test non-EVM network addition (using a Bitcoin testnet CAIP chain ID)
const bitcoinTestnetChainId = 'bip122:000000000933ea01ad0ee984209779ba';
mocks.globalMessenger.publish('NetworkController:networkAdded', {
chainId: bitcoinTestnetChainId,
} as unknown as NetworkConfiguration);

expect(controller.state.enabledNetworkMap).toStrictEqual({
[KnownCaipNamespace.Eip155]: {
[CHAIN_IDS.MAINNET]: true,
},
[KnownCaipNamespace.Solana]: {},
[KnownCaipNamespace.Bip122]: {
[bitcoinTestnetChainId]: true, // Bitcoin testnet has been enabled
},
});
});

it('updates network order when NetworkController:stateChange event is emitted with new networks', () => {
const mocks = arrangeMockMessenger();
const controller = new NetworkOrderController({
Expand Down Expand Up @@ -341,14 +448,17 @@ describe('NetworkOrderController - constructor', () => {
function arrangeMockMessenger() {
const globalMessenger = new Messenger<
NetworkControllerGetStateAction | NetworkControllerSetActiveNetworkAction,
NetworkControllerStateChangeEvent | NetworkControllerNetworkRemovedEvent
| NetworkControllerStateChangeEvent
| NetworkControllerNetworkRemovedEvent
| NetworkControllerNetworkAddedEvent
>();
const messenger: NetworkOrderControllerMessenger =
globalMessenger.getRestricted({
name: 'NetworkOrderController',
allowedEvents: [
'NetworkController:stateChange',
'NetworkController:networkRemoved',
'NetworkController:networkAdded',
],
allowedActions: [
'NetworkController:getState',
Expand Down
50 changes: 48 additions & 2 deletions app/scripts/controllers/network-order.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,17 @@ import {
NetworkControllerStateChangeEvent,
NetworkState,
NetworkControllerNetworkRemovedEvent,
NetworkControllerNetworkAddedEvent,
NetworkControllerGetStateAction,
} from '@metamask/network-controller';
import { toEvmCaipChainId } from '@metamask/multichain-network-controller';
import type { CaipChainId, CaipNamespace, Hex } from '@metamask/utils';
import type { Patch } from 'immer';
import { CHAIN_IDS, TEST_CHAINS } from '../../../shared/constants/network';
import {
CHAIN_IDS,
FEATURED_NETWORK_CHAIN_IDS,
TEST_CHAINS,
} from '../../../shared/constants/network';

// Unique name for the controller
const controllerName = 'NetworkOrderController';
Expand Down Expand Up @@ -66,7 +71,8 @@ type AllowedActions =

type AllowedEvents =
| NetworkControllerStateChangeEvent
| NetworkControllerNetworkRemovedEvent;
| NetworkControllerNetworkRemovedEvent
| NetworkControllerNetworkAddedEvent;

// Type for the messenger of NetworkOrderController
export type NetworkOrderControllerMessenger = RestrictedMessenger<
Expand Down Expand Up @@ -156,6 +162,13 @@ export class NetworkOrderController extends BaseController<
this.onNetworkRemoved(removedNetwork.chainId);
},
);

this.messagingSystem.subscribe(
'NetworkController:networkAdded',
(addedNetwork) => {
this.onNetworkAdded(addedNetwork.chainId);
},
);
}

/**
Expand Down Expand Up @@ -203,6 +216,39 @@ export class NetworkOrderController extends BaseController<
});
}

onNetworkAdded(networkId: Hex) {
const caipId: CaipChainId = isCaipChainId(networkId)
? networkId
: toEvmCaipChainId(networkId);

const { namespace } = parseCaipChainId(caipId);

if (namespace === (KnownCaipNamespace.Eip155 as string)) {
// For EVM networks, check if it's a featured/additional network
const isFeaturedOrAdditionalNetwork =
FEATURED_NETWORK_CHAIN_IDS.includes(networkId);

if (isFeaturedOrAdditionalNetwork) {
this.update((state) => {
// For featured/additional networks, add to existing enabled networks
state.enabledNetworkMap[namespace][networkId] = true;
});
} else {
this.update((state) => {
// For custom networks, enable ONLY the custom network (disable all others)
state.enabledNetworkMap[namespace] = {
[networkId]: true,
};
});
}
} else {
this.update((state) => {
// For non-EVM networks, add to existing enabled networks
state.enabledNetworkMap[namespace][caipId] = true;
});
}
}
Copy link

Choose a reason for hiding this comment

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

Bug: Network Map Access and Type Mismatch

In onNetworkAdded, accessing state.enabledNetworkMap[namespace] without ensuring the namespace key exists can cause a TypeError when adding new or uninitialized network namespaces. Additionally, the networkId parameter is typed as Hex but also handles CAIP chain IDs, which is inaccurate.

Fix in Cursor Fix in Web


onNetworkRemoved(networkId: Hex) {
const caipId: CaipChainId = isCaipChainId(networkId)
? networkId
Expand Down
18 changes: 8 additions & 10 deletions test/e2e/tests/network/switch-network.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,22 +45,20 @@ describe('Switch network - ', function (this: Suite) {
await loginWithBalanceValidation(driver);
const homePage = new HomePage(driver);

// Validate the switch network functionality to Ethereum
await switchToNetworkFromSendFlow(driver, 'Ethereum');
await homePage.checkLocalNodeBalanceIsDisplayed();
// Test starts with a custom network localhost:8545

// Validate the switch network functionality to test network
await switchToNetworkFromSendFlow(driver, 'Localhost 8545');
await homePage.checkLocalNodeBalanceIsDisplayed();
// In the send flow we use Ethereum token which changes the network to Ethereum on the home page becuase custom network was previously selected
await switchToNetworkFromSendFlow(driver, 'Ethereum');
await homePage.checkExpectedBalanceIsDisplayed('25', 'ETH');

// Add Arbitrum network and perform the switch network functionality
// Add Arbitrum network
await searchAndSwitchToNetworkFromGlobalMenuFlow(driver, 'Arbitrum');
await homePage.checkLocalNodeBalanceIsDisplayed();
await homePage.checkExpectedBalanceIsDisplayed('$85,000.00', 'USD');

// Validate the switch network functionality back to Ethereum
// Switching network in send flow should not affect the home page network filter
await switchToNetworkFromSendFlow(driver, 'Ethereum');
await homePage.checkPageIsLoaded();
await homePage.checkLocalNodeBalanceIsDisplayed();
await homePage.checkExpectedBalanceIsDisplayed('$85,000.00', 'USD');
},
);
});
Expand Down
Loading