Skip to content

Commit 582821f

Browse files
authored
Merge branch 'main' into predict/bump-sell-slippage
2 parents 285aba9 + 10b85d3 commit 582821f

36 files changed

+3209
-421
lines changed

.github/CODEOWNERS

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ app/core/Engine/controllers/remote-feature-flag-controller/ @MetaMask/mobile-pla
4343
app/core/DeeplinkManager @MetaMask/mobile-platform
4444
scripts/build.sh @MetaMask/mobile-platform
4545
scripts/update-expo-channel.js @MetaMask/mobile-admins
46+
certs/certificate.pem @MetaMask/mobile-admins
4647

4748
# Platform & Snaps Code Fencing File
4849
metro.transform.js @MetaMask/mobile-platform @MetaMask/core-platform

android/app/build.gradle

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -187,8 +187,8 @@ android {
187187
applicationId "io.metamask"
188188
minSdkVersion rootProject.ext.minSdkVersion
189189
targetSdkVersion rootProject.ext.targetSdkVersion
190-
versionName "7.61.0"
191-
versionCode 2993
190+
versionName "7.61.99"
191+
versionCode 3092
192192
testBuildType System.getProperty('testBuildType', 'debug')
193193
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
194194
manifestPlaceholders.MM_BRANCH_KEY_TEST = "$System.env.MM_BRANCH_KEY_TEST"

app.config.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,11 @@ module.exports = {
4646
owner: 'metamask-test',
4747
runtimeVersion: RUNTIME_VERSION,
4848
updates: {
49+
codeSigningCertificate: './certs/certificate.pem',
50+
codeSigningMetadata: {
51+
keyid: 'main',
52+
alg: 'rsa-v1_5-sha256',
53+
},
4954
url: UPDATE_URL,
5055
// Channel is set by requestHeaders, will be overridden with build script
5156
requestHeaders: {

app/components/UI/NetworkManager/index.test.tsx

Lines changed: 216 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import configureStore from 'redux-mock-store';
55

66
import NetworkManager from './index';
77
import { useNetworksByNamespace } from '../../hooks/useNetworksByNamespace/useNetworksByNamespace';
8+
import { useNetworkEnablement } from '../../hooks/useNetworkEnablement/useNetworkEnablement';
89
import Engine from '../../../core/Engine';
910

1011
// Create mock functions that we can spy on
@@ -147,16 +148,40 @@ jest.mock('../../hooks/useNetworksByNamespace/useNetworksByNamespace', () => ({
147148
},
148149
}));
149150

151+
const mockEnableNetwork = jest.fn();
152+
150153
jest.mock('../../hooks/useNetworkEnablement/useNetworkEnablement', () => ({
151-
useNetworkEnablement: () => ({
154+
useNetworkEnablement: jest.fn(() => ({
152155
disableNetwork: mockDisableNetwork,
156+
enableNetwork: mockEnableNetwork,
153157
enabledNetworksByNamespace: {
154158
eip155: {
155159
'0x1': true,
156160
'0x89': true,
157161
},
158162
},
159-
}),
163+
})),
164+
}));
165+
166+
jest.mock('../../hooks/useNetworksToUse/useNetworksToUse', () => ({
167+
useNetworksToUse: jest.fn(() => ({
168+
networksToUse: [
169+
{
170+
id: 'eip155:1',
171+
name: 'Ethereum Mainnet',
172+
caipChainId: 'eip155:1',
173+
isSelected: true,
174+
imageSource: { uri: 'ethereum.png' },
175+
},
176+
{
177+
id: 'eip155:137',
178+
name: 'Polygon Mainnet',
179+
caipChainId: 'eip155:137',
180+
isSelected: false,
181+
imageSource: { uri: 'polygon.png' },
182+
},
183+
],
184+
})),
160185
}));
161186

162187
jest.mock('../../../util/device', () => ({
@@ -1011,4 +1036,193 @@ describe('NetworkManager Component', () => {
10111036
});
10121037
});
10131038
});
1039+
1040+
describe('Enabled Networks Processing', () => {
1041+
const mockUseNetworkEnablement =
1042+
useNetworkEnablement as jest.MockedFunction<typeof useNetworkEnablement>;
1043+
1044+
beforeEach(() => {
1045+
jest.clearAllMocks();
1046+
});
1047+
1048+
it('should correctly process enabled networks from flat structure', () => {
1049+
mockUseNetworkEnablement.mockReturnValue({
1050+
disableNetwork: mockDisableNetwork,
1051+
enableNetwork: mockEnableNetwork,
1052+
enabledNetworksByNamespace: {
1053+
eip155: {
1054+
'0x1': true,
1055+
'0x89': true,
1056+
'0xa': false,
1057+
},
1058+
},
1059+
namespace: 'eip155',
1060+
enabledNetworksForCurrentNamespace: {},
1061+
networkEnablementController: {} as never,
1062+
isNetworkEnabled: jest.fn(),
1063+
hasOneEnabledNetwork: false,
1064+
enableAllPopularNetworks: jest.fn(),
1065+
tryEnableEvmNetwork: jest.fn(),
1066+
enabledNetworksForAllNamespaces: {
1067+
'0x1': true,
1068+
'0x89': true,
1069+
'0xa': false,
1070+
},
1071+
});
1072+
1073+
// The component internally processes enabledNetworksByNamespace
1074+
// We verify it renders without errors and has correct tab state
1075+
const { getByTestId } = renderComponent();
1076+
1077+
// Verify component renders successfully with processed networks
1078+
expect(getByTestId('main-bottom-sheet')).toBeOnTheScreen();
1079+
});
1080+
1081+
it('should correctly process enabled networks from nested structure', () => {
1082+
mockUseNetworkEnablement.mockReturnValue({
1083+
disableNetwork: mockDisableNetwork,
1084+
enableNetwork: mockEnableNetwork,
1085+
enabledNetworksByNamespace: {
1086+
eip155: {
1087+
'0x1': true,
1088+
'0x89': false,
1089+
},
1090+
bip122: {
1091+
'0x1': true,
1092+
},
1093+
} as never,
1094+
namespace: 'eip155',
1095+
enabledNetworksForCurrentNamespace: {},
1096+
networkEnablementController: {} as never,
1097+
isNetworkEnabled: jest.fn(),
1098+
hasOneEnabledNetwork: false,
1099+
enableAllPopularNetworks: jest.fn(),
1100+
tryEnableEvmNetwork: jest.fn(),
1101+
enabledNetworksForAllNamespaces: {
1102+
'0x1': true,
1103+
'0x89': false,
1104+
'0xa': false,
1105+
},
1106+
});
1107+
1108+
// The component should handle nested namespace structures
1109+
const { getByTestId } = renderComponent();
1110+
1111+
expect(getByTestId('main-bottom-sheet')).toBeOnTheScreen();
1112+
});
1113+
1114+
it('should handle empty enabled networks gracefully', () => {
1115+
mockUseNetworkEnablement.mockReturnValue({
1116+
disableNetwork: mockDisableNetwork,
1117+
enableNetwork: mockEnableNetwork,
1118+
enabledNetworksByNamespace: {},
1119+
namespace: 'eip155',
1120+
enabledNetworksForCurrentNamespace: {},
1121+
networkEnablementController: {} as never,
1122+
isNetworkEnabled: jest.fn(),
1123+
hasOneEnabledNetwork: false,
1124+
enableAllPopularNetworks: jest.fn(),
1125+
tryEnableEvmNetwork: jest.fn(),
1126+
enabledNetworksForAllNamespaces: {},
1127+
});
1128+
1129+
const { getByTestId } = renderComponent();
1130+
1131+
expect(getByTestId('main-bottom-sheet')).toBeOnTheScreen();
1132+
});
1133+
1134+
it('should filter out disabled networks correctly', () => {
1135+
mockUseNetworkEnablement.mockReturnValue({
1136+
disableNetwork: mockDisableNetwork,
1137+
enableNetwork: mockEnableNetwork,
1138+
enabledNetworksByNamespace: {
1139+
eip155: {
1140+
'0x1': true,
1141+
'0x89': false,
1142+
'0xa': false,
1143+
'0xa4b1': true,
1144+
},
1145+
},
1146+
namespace: 'eip155',
1147+
enabledNetworksForCurrentNamespace: {},
1148+
networkEnablementController: {} as never,
1149+
isNetworkEnabled: jest.fn(),
1150+
hasOneEnabledNetwork: false,
1151+
enableAllPopularNetworks: jest.fn(),
1152+
tryEnableEvmNetwork: jest.fn(),
1153+
enabledNetworksForAllNamespaces: {
1154+
'0x1': true,
1155+
'0x89': false,
1156+
'0xa': false,
1157+
'0xa4b1': true,
1158+
},
1159+
});
1160+
1161+
// Component should only include enabled (true) networks
1162+
const { getByTestId } = renderComponent();
1163+
1164+
expect(getByTestId('main-bottom-sheet')).toBeOnTheScreen();
1165+
});
1166+
1167+
it('should enable and disable networks correctly during deletion', async () => {
1168+
const otherNetwork = {
1169+
id: 'eip155:137',
1170+
name: 'Polygon Mainnet',
1171+
caipChainId: 'eip155:137',
1172+
isSelected: false,
1173+
imageSource: { uri: 'polygon.png' },
1174+
};
1175+
1176+
(useNetworksByNamespace as jest.Mock).mockImplementation((args) => {
1177+
if (args.networkType === 'custom') {
1178+
return {
1179+
networks: [otherNetwork],
1180+
areAllNetworksSelected: false,
1181+
};
1182+
}
1183+
return { selectedCount: 2 };
1184+
});
1185+
1186+
mockUseNetworkEnablement.mockReturnValue({
1187+
disableNetwork: mockDisableNetwork,
1188+
enableNetwork: mockEnableNetwork,
1189+
enabledNetworksByNamespace: {
1190+
eip155: {
1191+
'0x1': true,
1192+
'0x89': true,
1193+
},
1194+
},
1195+
namespace: 'eip155',
1196+
enabledNetworksForCurrentNamespace: {},
1197+
networkEnablementController: {} as never,
1198+
isNetworkEnabled: jest.fn(),
1199+
hasOneEnabledNetwork: false,
1200+
enableAllPopularNetworks: jest.fn(),
1201+
tryEnableEvmNetwork: jest.fn(),
1202+
enabledNetworksForAllNamespaces: {
1203+
'0x1': true,
1204+
'0x89': true,
1205+
},
1206+
});
1207+
1208+
const { getByTestId } = renderComponent();
1209+
1210+
// Open modal and trigger delete confirmation
1211+
const openModalButton = getByTestId('open-modal-button');
1212+
fireEvent.press(openModalButton);
1213+
1214+
await waitFor(() => {
1215+
const deleteButton = getByTestId('account-action-app_settings.delete');
1216+
fireEvent.press(deleteButton);
1217+
});
1218+
1219+
// Confirm deletion
1220+
const confirmButton = getByTestId('footer-button-app_settings.delete');
1221+
fireEvent.press(confirmButton);
1222+
1223+
// Should enable the other network and disable the deleted one
1224+
expect(mockEnableNetwork).toHaveBeenCalledWith('eip155:137');
1225+
expect(mockDisableNetwork).toHaveBeenCalledWith('eip155:1');
1226+
});
1227+
});
10141228
});

app/components/UI/NetworkManager/index.tsx

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ import { POPULAR_NETWORK_CHAIN_IDS } from '../../../constants/popular-networks';
6060
import RpcSelectionModal from '../../Views/NetworkSelector/RpcSelectionModal/RpcSelectionModal';
6161
import { isNonEvmChainId } from '../../../core/Multichain/utils';
6262
import { NetworkConfiguration } from '@metamask/network-controller';
63+
import { useNetworksToUse } from '../../hooks/useNetworksToUse/useNetworksToUse';
6364

6465
export const createNetworkManagerNavDetails = createNavigationDetails(
6566
Routes.MODAL.ROOT_MODAL_FLOW,
@@ -94,7 +95,16 @@ const NetworkManager = () => {
9495
const { selectedCount } = useNetworksByNamespace({
9596
networkType: NetworkType.Popular,
9697
});
97-
const { disableNetwork, enabledNetworksByNamespace } = useNetworkEnablement();
98+
const { networks, areAllNetworksSelected } = useNetworksByNamespace({
99+
networkType: NetworkType.Custom,
100+
});
101+
const { disableNetwork, enableNetwork, enabledNetworksByNamespace } =
102+
useNetworkEnablement();
103+
const { networksToUse } = useNetworksToUse({
104+
networks,
105+
networkType: NetworkType.Custom,
106+
areAllNetworksSelected,
107+
});
98108

99109
const isMultichainAccountsState2Enabled = useSelector(
100110
selectMultichainAccountsState2Enabled,
@@ -304,17 +314,24 @@ const NetworkManager = () => {
304314
const { NetworkController } = Engine.context;
305315
const rawChainId = parseCaipChainId(caipChainId).reference;
306316
const chainId = toHex(rawChainId);
317+
const otherNetwork = networksToUse.find(
318+
(network) => network.caipChainId !== caipChainId,
319+
);
307320

321+
// Remove the network from controller and disable it
308322
NetworkController.removeNetwork(chainId);
309-
disableNetwork(showConfirmDeleteModal.caipChainId);
310323

324+
if (otherNetwork?.caipChainId) {
325+
enableNetwork(otherNetwork.caipChainId);
326+
}
327+
disableNetwork(showConfirmDeleteModal.caipChainId);
311328
MetaMetrics.getInstance().addTraitsToUser(
312329
removeItemFromChainIdList(chainId),
313330
);
314331

315332
setShowConfirmDeleteModal(initialShowConfirmDeleteModal);
316333
}
317-
}, [showConfirmDeleteModal, disableNetwork]);
334+
}, [showConfirmDeleteModal, disableNetwork, networksToUse, enableNetwork]);
318335

319336
const cancelButtonProps: ButtonProps = useMemo(
320337
() => ({

0 commit comments

Comments
 (0)