From 60ec8229171d63410490c5a01aeec9db67dca003 Mon Sep 17 00:00:00 2001
From: EtherWizard33 <165834542+EtherWizard33@users.noreply.github.com>
Date: Thu, 21 Nov 2024 11:02:05 -0500
Subject: [PATCH] feat: non-permissioned networks, when a dapp finds itself on
a global network for which it doesn't have a granted network permission
(#12212)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
non-permissioned networks, when a dapp finds itself on a global network
for which it doesn't have a granted network permission
## **Description**
Figma:
https://www.figma.com/design/njDnVDROVuIwLbjUjkUYuO/MC-Mobile-Amon-Hen?node-id=3109-157108&node-type=canvas&m=dev
## **Related issues**
Contributes to:
https://github.com/MetaMask/MetaMask-planning/issues/3598
## **Manual testing steps**
1. Start with a global network and a dapp connected to it
2. From the home screen, switch the network
3. Go back to the dapp, the bottom sheet will come up.
This works also with multiple dapps open at once, the bottom sheet comes
up the next time the dapp is in focus.
## **Screenshots/Recordings**
| Before (switch to non permitted change was not handled) | After
(handled) |
|--------------|--------------|
|
|
|
## **Pre-merge author checklist**
- [ ] I’ve followed [MetaMask Contributor
Docs](https://github.com/MetaMask/contributor-docs) and [MetaMask Mobile
Coding
Standards](https://github.com/MetaMask/metamask-mobile/blob/main/.github/guidelines/CODING_GUIDELINES.md).
- [ ] I've completed the PR template to the best of my ability
- [ ] I’ve included tests if applicable
- [ ] I’ve documented my code using [JSDoc](https://jsdoc.app/) format
if applicable
- [ ] I’ve applied the right labels on the PR (see [labeling
guidelines](https://github.com/MetaMask/metamask-mobile/blob/main/.github/guidelines/LABELING_GUIDELINES.md)).
Not required for external contributors.
## **Pre-merge reviewer checklist**
- [ ] I've manually tested the PR (e.g. pull and build branch, run the
app, test code being changed).
- [ ] I confirm that this PR addresses all acceptance criteria described
in the ticket it closes and includes the necessary testing evidence such
as recordings and or screenshots.
---
app/components/Nav/App/index.js | 5 +
.../NetworkSelectorList.styles.ts | 4 -
.../NetworkSelectorList.tsx | 2 -
.../PermissionsSummary.styles.ts | 15 +-
.../PermissionsSummary/PermissionsSummary.tsx | 92 ++++++--
.../PermissionsSummary.types.ts | 3 +
.../PermissionsSummary.test.tsx.snap | 36 ++-
.../AccountPermissions/AccountPermissions.tsx | 178 +++++++++++++-
.../AccountPermissions.types.ts | 2 +
.../ConnectionDetails/ConnectionDetails.tsx | 4 +-
.../NetworkPermissionsConnected.styles.ts | 50 ++++
.../NetworkPermissionsConnected.tsx | 220 ++++++++++++++++++
.../NetworkPermissionsConnected.types.ts | 24 ++
.../NetworkPermissionsConnected/index.ts | 2 +
.../PermittedNetworksInfoSheet.styles.ts | 26 +++
.../PermittedNetworksInfoSheet.tsx | 57 +++++
.../PermittedNetworksInfoSheet/index.ts | 1 +
app/components/Views/Browser/index.js | 23 +-
app/components/Views/BrowserTab/index.js | 73 ++++++
.../NetworkConnectMultiSelector.styles.ts | 3 +-
.../NetworkConnectMultiSelector.tsx | 46 +++-
.../NetworkConnectMultiSelector.types.ts | 1 +
app/constants/navigation/Routes.ts | 1 +
.../RPCMethods/lib/ethereum-chain-utils.js | 9 +-
.../wallet_addEthereumChain.test.js | 10 +-
.../wallet_switchEthereumChain.test.js | 4 +-
app/util/networks/index.js | 9 +-
bitrise.yml | 3 +
locales/languages/en.json | 10 +-
sonar-project.properties | 2 +-
30 files changed, 847 insertions(+), 68 deletions(-)
create mode 100644 app/components/Views/AccountPermissions/NetworkPermissionsConnected/NetworkPermissionsConnected.styles.ts
create mode 100644 app/components/Views/AccountPermissions/NetworkPermissionsConnected/NetworkPermissionsConnected.tsx
create mode 100644 app/components/Views/AccountPermissions/NetworkPermissionsConnected/NetworkPermissionsConnected.types.ts
create mode 100644 app/components/Views/AccountPermissions/NetworkPermissionsConnected/index.ts
create mode 100644 app/components/Views/AccountPermissions/PermittedNetworksInfoSheet/PermittedNetworksInfoSheet.styles.ts
create mode 100644 app/components/Views/AccountPermissions/PermittedNetworksInfoSheet/PermittedNetworksInfoSheet.tsx
create mode 100644 app/components/Views/AccountPermissions/PermittedNetworksInfoSheet/index.ts
diff --git a/app/components/Nav/App/index.js b/app/components/Nav/App/index.js
index 0ad4fa844e6..8763bf589d3 100644
--- a/app/components/Nav/App/index.js
+++ b/app/components/Nav/App/index.js
@@ -118,6 +118,7 @@ import OnboardingAssetsSettings from '../../Views/OnboardingSuccess/OnboardingAs
import OnboardingSecuritySettings from '../../Views/OnboardingSuccess/OnboardingSecuritySettings';
import BasicFunctionalityModal from '../../UI/BasicFunctionality/BasicFunctionalityModal/BasicFunctionalityModal';
import ProfileSyncingModal from '../../UI/ProfileSyncing/ProfileSyncingModal/ProfileSyncingModal';
+import PermittedNetworksInfoSheet from '../../Views/AccountPermissions/PermittedNetworksInfoSheet/PermittedNetworksInfoSheet';
import ResetNotificationsModal from '../../UI/Notification/ResetNotificationsModal';
import NFTAutoDetectionModal from '../../../../app/components/Views/NFTAutoDetectionModal/NFTAutoDetectionModal';
import NftOptions from '../../../components/Views/NftOptions';
@@ -430,6 +431,10 @@ const RootModalFlow = () => (
name={Routes.SHEET.CONNECTION_DETAILS}
component={ConnectionDetails}
/>
+
StyleSheet.create({
- networkItemContainer: {
- paddingHorizontal: 10,
- paddingVertical: 14,
- },
networkAvatar: {
marginHorizontal: 10,
},
diff --git a/app/components/UI/NetworkSelectorList/NetworkSelectorList.tsx b/app/components/UI/NetworkSelectorList/NetworkSelectorList.tsx
index 152348b8573..551146856e0 100644
--- a/app/components/UI/NetworkSelectorList/NetworkSelectorList.tsx
+++ b/app/components/UI/NetworkSelectorList/NetworkSelectorList.tsx
@@ -67,7 +67,6 @@ const NetworkSelectorList = ({
size: AvatarSize.Sm,
}}
disabled={isDisabled}
- style={styles.networkItemContainer}
>
{renderRightAccessory?.(id, name)}
@@ -79,7 +78,6 @@ const NetworkSelectorList = ({
renderRightAccessory,
isSelectionDisabled,
onSelectNetwork,
- styles,
isMultiSelect,
],
);
diff --git a/app/components/UI/PermissionsSummary/PermissionsSummary.styles.ts b/app/components/UI/PermissionsSummary/PermissionsSummary.styles.ts
index 3c25f4830a7..26b3b380eec 100644
--- a/app/components/UI/PermissionsSummary/PermissionsSummary.styles.ts
+++ b/app/components/UI/PermissionsSummary/PermissionsSummary.styles.ts
@@ -31,12 +31,13 @@ const createStyles = (params: {
marginLeft: 24,
},
bottomButtonsContainer: {
- marginTop: 16,
+ marginTop: 8,
},
actionButtonsContainer: {
flex: 0,
flexDirection: 'row',
paddingHorizontal: 24,
+ marginTop: 8,
},
buttonPositioning: {
flex: 1,
@@ -59,6 +60,9 @@ const createStyles = (params: {
justifyContent: 'center',
alignItems: 'center',
},
+ logoContainerNonDapp: {
+ marginTop: 8,
+ },
domainLogoContainer: {
width: 32,
height: 32,
@@ -81,6 +85,7 @@ const createStyles = (params: {
maxWidth: '75%',
},
permissionRequestNetworkName: {
+ marginRight: 4,
maxWidth: '75%',
},
avatarGroup: {
@@ -122,6 +127,7 @@ const createStyles = (params: {
dataIcon: { alignSelf: 'flex-start' },
disconnectAllContainer: {
marginHorizontal: 24,
+ marginTop: 8,
flexDirection: 'row',
},
disconnectButton: { flex: 1 },
@@ -129,6 +135,13 @@ const createStyles = (params: {
width: 56,
alignItems: 'center',
},
+ nonDappNetworkSwitchButtons: {
+ gap: 16,
+ },
+ description: {
+ marginHorizontal: 24,
+ marginBottom: 16,
+ },
});
};
diff --git a/app/components/UI/PermissionsSummary/PermissionsSummary.tsx b/app/components/UI/PermissionsSummary/PermissionsSummary.tsx
index f48936e74ef..1d31aec69d9 100644
--- a/app/components/UI/PermissionsSummary/PermissionsSummary.tsx
+++ b/app/components/UI/PermissionsSummary/PermissionsSummary.tsx
@@ -1,4 +1,4 @@
-import React, { useCallback } from 'react';
+import React, { useCallback, useMemo } from 'react';
import StyledButton from '../StyledButton';
import {
ImageSourcePropType,
@@ -42,6 +42,9 @@ import ButtonIcon, {
import { getNetworkImageSource } from '../../../util/networks';
import Engine from '../../../core/Engine';
import { SDKSelectorsIDs } from '../../../../e2e/selectors/Settings/SDK.selectors';
+import { useSelector } from 'react-redux';
+import { selectProviderConfig } from '../../../selectors/networkController';
+import { useNetworkInfo } from '../../../selectors/selectedNetworkController';
const PermissionsSummary = ({
currentPageInformation,
@@ -57,16 +60,24 @@ const PermissionsSummary = ({
isRenderedAsBottomSheet = true,
isDisconnectAllShown = true,
isNetworkSwitch = false,
+ isNonDappNetworkSwitch = false,
accountAddresses = [],
accounts = [],
networkAvatars = [],
+ onAddNetwork = () => undefined,
+ onChooseFromPermittedNetworks = () => undefined,
}: PermissionsSummaryProps) => {
const { colors } = useTheme();
const { styles } = useStyles(styleSheet, { isRenderedAsBottomSheet });
const { navigate } = useNavigation();
const selectedAccount = useSelectedAccount();
+ const providerConfig = useSelector(selectProviderConfig);
- const hostname = new URL(currentPageInformation.url).hostname;
+ const hostname = useMemo(
+ () => new URL(currentPageInformation.url).hostname,
+ [currentPageInformation.url],
+ );
+ const networkInfo = useNetworkInfo(hostname);
// if network switch, we get the chain name from the customNetworkInformation
let chainName = '';
@@ -117,7 +128,7 @@ const PermissionsSummary = ({
return (
- {onBack && (
+ {onBack && !isNonDappNetworkSwitch && (
- {renderTopIcon()}
+
+ {renderTopIcon()}
+
{!isRenderedAsBottomSheet && (
- {isNetworkSwitch && (
+ {(isNetworkSwitch || isNonDappNetworkSwitch) && (
<>
@@ -329,19 +347,32 @@ const PermissionsSummary = ({
{strings('permissions.requesting_for')}
- {chainName}
+ {isNonDappNetworkSwitch
+ ? networkInfo?.networkName || providerConfig.nickname
+ : chainName}
>
)}
- {!isNetworkSwitch && (
+ {!isNetworkSwitch && !isNonDappNetworkSwitch && (
<>
@@ -363,7 +394,7 @@ const PermissionsSummary = ({
)}
- {!isNetworkSwitch && renderEndAccessory()}
+ {!isNetworkSwitch && !isNonDappNetworkSwitch && renderEndAccessory()}
);
@@ -376,7 +407,9 @@ const PermissionsSummary = ({
{renderHeader()}
- {!isAlreadyConnected || isNetworkSwitch
+ {isNonDappNetworkSwitch
+ ? strings('permissions.title_add_network_permission')
+ : !isAlreadyConnected || isNetworkSwitch
? strings('permissions.title_dapp_url_wants_to', {
dappUrl: hostname,
})
@@ -385,7 +418,14 @@ const PermissionsSummary = ({
})}
- {/*TODO These should be conditional upon which permissions are being requested*/}
+ {isNonDappNetworkSwitch && (
+
+ {strings('permissions.non_permitted_network_description')}
+
+ )}
{!isNetworkSwitch && renderAccountPermissionsRequestInfoCard()}
{renderNetworkPermissionsRequestInfoCard()}
@@ -405,7 +445,7 @@ const PermissionsSummary = ({
/>
)}
- {showActionButtons && (
+ {showActionButtons && !isNonDappNetworkSwitch && (
)}
+ {isNonDappNetworkSwitch && (
+
+
+
+
+
+
+
+
+ )}
diff --git a/app/components/UI/PermissionsSummary/PermissionsSummary.types.ts b/app/components/UI/PermissionsSummary/PermissionsSummary.types.ts
index 3bf71acd277..a0abf555430 100644
--- a/app/components/UI/PermissionsSummary/PermissionsSummary.types.ts
+++ b/app/components/UI/PermissionsSummary/PermissionsSummary.types.ts
@@ -13,6 +13,7 @@ export interface PermissionsSummaryProps {
onCancel?: () => void;
onConfirm?: () => void;
onUserAction?: React.Dispatch>;
+ onAddNetwork?: () => void;
showActionButtons?: boolean;
isAlreadyConnected?: boolean;
isRenderedAsBottomSheet?: boolean;
@@ -25,4 +26,6 @@ export interface PermissionsSummaryProps {
accounts?: Account[];
accountAddresses?: string[];
networkAvatars?: ({ name: string; imageSource: string } | null)[];
+ isNonDappNetworkSwitch?: boolean;
+ onChooseFromPermittedNetworks?: () => void;
}
diff --git a/app/components/UI/PermissionsSummary/__snapshots__/PermissionsSummary.test.tsx.snap b/app/components/UI/PermissionsSummary/__snapshots__/PermissionsSummary.test.tsx.snap
index 85a2f958df1..03876a53b3b 100644
--- a/app/components/UI/PermissionsSummary/__snapshots__/PermissionsSummary.test.tsx.snap
+++ b/app/components/UI/PermissionsSummary/__snapshots__/PermissionsSummary.test.tsx.snap
@@ -41,11 +41,14 @@ exports[`PermissionsSummary should render correctly 1`] = `
/>
@@ -575,6 +579,7 @@ exports[`PermissionsSummary should render correctly 1`] = `
{
"flexDirection": "row",
"marginHorizontal": 24,
+ "marginTop": 8,
}
}
>
@@ -636,6 +641,7 @@ exports[`PermissionsSummary should render correctly 1`] = `
{
"flex": 0,
"flexDirection": "row",
+ "marginTop": 8,
"paddingHorizontal": 24,
}
}
@@ -808,11 +814,14 @@ exports[`PermissionsSummary should render correctly for network switch 1`] = `
/>
@@ -1075,6 +1085,7 @@ exports[`PermissionsSummary should render correctly for network switch 1`] = `
{
"flexDirection": "row",
"marginHorizontal": 24,
+ "marginTop": 8,
}
}
>
@@ -1136,6 +1147,7 @@ exports[`PermissionsSummary should render correctly for network switch 1`] = `
{
"flex": 0,
"flexDirection": "row",
+ "marginTop": 8,
"paddingHorizontal": 24,
}
}
diff --git a/app/components/Views/AccountPermissions/AccountPermissions.tsx b/app/components/Views/AccountPermissions/AccountPermissions.tsx
index 42101ac25ef..86d58938feb 100755
--- a/app/components/Views/AccountPermissions/AccountPermissions.tsx
+++ b/app/components/Views/AccountPermissions/AccountPermissions.tsx
@@ -65,6 +65,8 @@ import { PermissionKeys } from '../../../core/Permissions/specifications';
import { CaveatTypes } from '../../../core/Permissions/constants';
import { NetworkConfiguration } from '@metamask/network-controller';
import { AvatarVariant } from '../../../component-library/components/Avatars/Avatar';
+import { useNetworkInfo } from '../../../selectors/selectedNetworkController';
+import NetworkPermissionsConnected from './NetworkPermissionsConnected';
const AccountPermissions = (props: AccountPermissionsProps) => {
const navigation = useNavigation();
@@ -75,6 +77,7 @@ const AccountPermissions = (props: AccountPermissionsProps) => {
},
isRenderedAsBottomSheet = true,
initialScreen = AccountPermissionsScreens.Connected,
+ isNonDappNetworkSwitch = false,
} = props.route.params;
const accountAvatarType = useSelector((state: RootState) =>
state.settings.useBlockieIcon
@@ -118,7 +121,11 @@ const AccountPermissions = (props: AccountPermissionsProps) => {
const sheetRef = useRef(null);
const [permissionsScreen, setPermissionsScreen] =
- useState(initialScreen);
+ useState(
+ isNonDappNetworkSwitch && isMultichainVersion1Enabled
+ ? AccountPermissionsScreens.PermissionsSummary
+ : initialScreen,
+ );
const { accounts, ensByAccountAddress } = useAccounts({
isLoading,
});
@@ -132,6 +139,8 @@ const AccountPermissions = (props: AccountPermissionsProps) => {
USER_INTENT.None,
);
+ const { chainId } = useNetworkInfo(hostname);
+
useEffect(() => {
let currentlyPermittedChains: string[] = [];
try {
@@ -413,7 +422,14 @@ const AccountPermissions = (props: AccountPermissionsProps) => {
}
if (networkSelectorUserIntent === USER_INTENT.Confirm) {
- setPermissionsScreen(AccountPermissionsScreens.PermissionsSummary);
+ if (isNonDappNetworkSwitch) {
+ setPermissionsScreen(
+ AccountPermissionsScreens.ChooseFromPermittedNetworks,
+ );
+ } else {
+ setPermissionsScreen(AccountPermissionsScreens.PermissionsSummary);
+ }
+
setNetworkSelectorUserIntent(USER_INTENT.None);
const networkToastProps: ToastOptions = {
variant: ToastVariants.Network,
@@ -427,7 +443,13 @@ const AccountPermissions = (props: AccountPermissionsProps) => {
};
toastRef?.current?.showToast(networkToastProps);
}
- }, [networkSelectorUserIntent, hideSheet, faviconSource, toastRef]);
+ }, [
+ networkSelectorUserIntent,
+ hideSheet,
+ faviconSource,
+ toastRef,
+ isNonDappNetworkSwitch,
+ ]);
useEffect(() => {
if (userIntent === USER_INTENT.None) return;
@@ -653,9 +675,14 @@ const AccountPermissions = (props: AccountPermissionsProps) => {
urlWithProtocol={urlWithProtocol}
hostname={hostname}
onBack={() =>
- setPermissionsScreen(AccountPermissionsScreens.PermissionsSummary)
+ setPermissionsScreen(
+ isNonDappNetworkSwitch
+ ? AccountPermissionsScreens.ChooseFromPermittedNetworks
+ : AccountPermissionsScreens.PermissionsSummary,
+ )
}
isRenderedAsBottomSheet={isRenderedAsBottomSheet}
+ hideActiveNetwork={isNonDappNetworkSwitch}
/>
),
[
@@ -664,6 +691,7 @@ const AccountPermissions = (props: AccountPermissionsProps) => {
urlWithProtocol,
hostname,
isRenderedAsBottomSheet,
+ isNonDappNetworkSwitch,
],
);
@@ -696,6 +724,135 @@ const AccountPermissions = (props: AccountPermissionsProps) => {
],
);
+ const renderChooseFromPermittedNetworksScreen = useCallback(
+ () => (
+
+ ),
+ [
+ ensByAccountAddress,
+ activeAddress,
+ isLoading,
+ accountsFilteredByPermissions,
+ setSelectedAddresses,
+ setPermissionsScreen,
+ hideSheet,
+ faviconSource,
+ hostname,
+ urlWithProtocol,
+ secureIcon,
+ accountAvatarType,
+ ],
+ );
+
+ const renderNetworkPermissionSummaryScreen = useCallback(() => {
+ const permissionsSummaryProps: PermissionsSummaryProps = {
+ currentPageInformation: {
+ currentEnsName: '',
+ icon: faviconSource as string,
+ url: urlWithProtocol,
+ },
+ onEdit: () => {
+ setPermissionsScreen(AccountPermissionsScreens.EditAccountsPermissions);
+ setSelectedAddresses(
+ permittedAccountsByHostname.map(toChecksumHexAddress),
+ );
+ },
+ onEditNetworks: () =>
+ setPermissionsScreen(AccountPermissionsScreens.ConnectMoreNetworks),
+ onUserAction: setUserIntent,
+ onAddNetwork: () => {
+ let currentlyPermittedChains: string[] = [];
+ try {
+ const caveat = Engine.context.PermissionController.getCaveat(
+ hostname,
+ PermissionKeys.permittedChains,
+ CaveatTypes.restrictNetworkSwitching,
+ );
+ if (Array.isArray(caveat?.value)) {
+ currentlyPermittedChains = caveat.value.filter(
+ (item): item is string => typeof item === 'string',
+ );
+ }
+ } catch (e) {
+ // noop
+ }
+
+ // Add current chainId if no chains are permitted yet
+ if (chainId) {
+ currentlyPermittedChains = [chainId, ...currentlyPermittedChains];
+ } else {
+ throw new Error('No chainId provided');
+ }
+
+ Engine.context.PermissionController.updateCaveat(
+ hostname,
+ PermissionKeys.permittedChains,
+ CaveatTypes.restrictNetworkSwitching,
+ currentlyPermittedChains,
+ );
+
+ const networkToastProps: ToastOptions = {
+ variant: ToastVariants.Network,
+ labelOptions: [
+ {
+ label: strings('toast.network_permissions_updated'),
+ },
+ ],
+ hasNoTimeout: false,
+ networkImageSource: faviconSource,
+ };
+ toastRef?.current?.showToast(networkToastProps);
+
+ hideSheet();
+ },
+ onBack: () =>
+ isRenderedAsBottomSheet
+ ? setPermissionsScreen(AccountPermissionsScreens.Connected)
+ : navigation.navigate('PermissionsManager'),
+ isRenderedAsBottomSheet,
+ accountAddresses: permittedAccountsByHostname.map(toChecksumHexAddress),
+ accounts,
+ networkAvatars,
+ isNetworkSwitch: true,
+ showActionButtons: false,
+ isDisconnectAllShown: false,
+ isNonDappNetworkSwitch: true,
+ onChooseFromPermittedNetworks: () => {
+ setPermissionsScreen(
+ AccountPermissionsScreens.ChooseFromPermittedNetworks,
+ );
+ },
+ };
+
+ return ;
+ }, [
+ faviconSource,
+ urlWithProtocol,
+ isRenderedAsBottomSheet,
+ navigation,
+ permittedAccountsByHostname,
+ setSelectedAddresses,
+ networkAvatars,
+ accounts,
+ chainId,
+ hideSheet,
+ hostname,
+ toastRef,
+ ]);
+
const renderPermissionsScreens = useCallback(() => {
switch (permissionsScreen) {
case AccountPermissionsScreens.Connected:
@@ -708,21 +865,30 @@ const AccountPermissions = (props: AccountPermissionsProps) => {
return renderConnectNetworksScreen();
case AccountPermissionsScreens.Revoke:
return renderRevokeScreen();
+ case AccountPermissionsScreens.ChooseFromPermittedNetworks:
+ return renderChooseFromPermittedNetworksScreen();
case AccountPermissionsScreens.PermissionsSummary:
- return renderPermissionsSummaryScreen();
+ return isNonDappNetworkSwitch
+ ? renderNetworkPermissionSummaryScreen()
+ : renderPermissionsSummaryScreen();
}
}, [
permissionsScreen,
+ isNonDappNetworkSwitch,
renderConnectedScreen,
renderConnectMoreAccountsScreen,
renderEditAccountsPermissionsScreen,
renderConnectNetworksScreen,
renderRevokeScreen,
+ renderChooseFromPermittedNetworksScreen,
renderPermissionsSummaryScreen,
+ renderNetworkPermissionSummaryScreen,
]);
return isRenderedAsBottomSheet ? (
- {renderPermissionsScreens()}
+
+ {renderPermissionsScreens()}
+
) : (
renderPermissionsScreens()
);
diff --git a/app/components/Views/AccountPermissions/AccountPermissions.types.ts b/app/components/Views/AccountPermissions/AccountPermissions.types.ts
index 0a7c0a4e897..fb536e2c5d4 100644
--- a/app/components/Views/AccountPermissions/AccountPermissions.types.ts
+++ b/app/components/Views/AccountPermissions/AccountPermissions.types.ts
@@ -8,6 +8,7 @@ export enum AccountPermissionsScreens {
ConnectMoreNetworks = 'ConnectMoreNetworks',
Revoke = 'Revoke',
PermissionsSummary = 'PermissionsSummary',
+ ChooseFromPermittedNetworks = 'ChooseFromPermittedNetworks',
}
/**
@@ -24,6 +25,7 @@ export interface AccountPermissionsProps {
};
isRenderedAsBottomSheet?: boolean;
initialScreen?: AccountPermissionsScreens;
+ isNonDappNetworkSwitch?: boolean;
};
};
}
diff --git a/app/components/Views/AccountPermissions/ConnectionDetails/ConnectionDetails.tsx b/app/components/Views/AccountPermissions/ConnectionDetails/ConnectionDetails.tsx
index 49463cd91de..3521a375e7f 100644
--- a/app/components/Views/AccountPermissions/ConnectionDetails/ConnectionDetails.tsx
+++ b/app/components/Views/AccountPermissions/ConnectionDetails/ConnectionDetails.tsx
@@ -26,7 +26,7 @@ interface ConnectionDetailsProps {
};
}
-const AccountPermissionsConfirmRevokeAll = (props: ConnectionDetailsProps) => {
+const ConnectionDetails = (props: ConnectionDetailsProps) => {
const { connectionDateTime = 123456789 } = props.route.params;
const { styles } = useStyles(styleSheet, {});
@@ -73,4 +73,4 @@ const AccountPermissionsConfirmRevokeAll = (props: ConnectionDetailsProps) => {
);
};
-export default AccountPermissionsConfirmRevokeAll;
+export default ConnectionDetails;
diff --git a/app/components/Views/AccountPermissions/NetworkPermissionsConnected/NetworkPermissionsConnected.styles.ts b/app/components/Views/AccountPermissions/NetworkPermissionsConnected/NetworkPermissionsConnected.styles.ts
new file mode 100644
index 00000000000..998a7efb9a4
--- /dev/null
+++ b/app/components/Views/AccountPermissions/NetworkPermissionsConnected/NetworkPermissionsConnected.styles.ts
@@ -0,0 +1,50 @@
+// Third party dependencies.
+import { StyleSheet } from 'react-native';
+
+/**
+ * Style sheet function for AccountPermissionsConnected screen.
+ * @returns StyleSheet object.
+ */
+const styleSheet = StyleSheet.create({
+ body: {
+ paddingHorizontal: 16,
+ },
+ networkPicker: {
+ marginVertical: 16,
+ },
+ sheetActionContainer: {
+ marginVertical: 16,
+ },
+ ctaButtonsContainer: {
+ marginTop: 24,
+ flexDirection: 'row',
+ },
+ button: { flex: 1 },
+ buttonSeparator: {
+ width: 16,
+ },
+ downCaretContainer: { justifyContent: 'center', flex: 1 },
+ disabled: {
+ opacity: 0.5,
+ },
+ sectionTitle: { marginBottom: 16 },
+ header: {
+ flexDirection: 'row',
+ alignItems: 'center',
+ justifyContent: 'center',
+ margin: 16,
+ },
+ managePermissionsButton: { marginHorizontal: 16, marginTop: 16 },
+ sectionTitleContainer: {
+ flexDirection: 'row',
+ alignItems: 'center',
+ justifyContent: 'center',
+ },
+ infoButtonContainer: { alignSelf: 'flex-start', marginLeft: 4 },
+ networkSelectorListContainer: {
+ marginBottom: 16,
+ marginTop: 4,
+ },
+});
+
+export default styleSheet;
diff --git a/app/components/Views/AccountPermissions/NetworkPermissionsConnected/NetworkPermissionsConnected.tsx b/app/components/Views/AccountPermissions/NetworkPermissionsConnected/NetworkPermissionsConnected.tsx
new file mode 100644
index 00000000000..1a1821c10b3
--- /dev/null
+++ b/app/components/Views/AccountPermissions/NetworkPermissionsConnected/NetworkPermissionsConnected.tsx
@@ -0,0 +1,220 @@
+// Third party dependencies.
+import React, { useCallback } from 'react';
+import { View } from 'react-native';
+import { useSelector } from 'react-redux';
+import { useNavigation } from '@react-navigation/native';
+
+// External dependencies.
+import SheetHeader from '../../../../component-library/components/Sheet/SheetHeader';
+import { strings } from '../../../../../locales/i18n';
+import TagUrl from '../../../../component-library/components/Tags/TagUrl';
+import PickerNetwork from '../../../../component-library/components/Pickers/PickerNetwork';
+import {
+ getDecimalChainId,
+ isMultichainVersion1Enabled,
+ getNetworkImageSource,
+ handleNetworkSwitch,
+} from '../../../../util/networks';
+import { AccountPermissionsScreens } from '../AccountPermissions.types';
+import { MetaMetricsEvents } from '../../../../core/Analytics';
+import Routes from '../../../../constants/navigation/Routes';
+import {
+ selectProviderConfig,
+ ProviderConfig,
+ selectNetworkConfigurations,
+} from '../../../../selectors/networkController';
+import { useNetworkInfo } from '../../../../selectors/selectedNetworkController';
+import { ConnectedAccountsSelectorsIDs } from '../../../../../e2e/selectors/Browser/ConnectedAccountModal.selectors';
+import {
+ IconColor,
+ IconName,
+} from '../../../../component-library/components/Icons/Icon';
+import ButtonIcon, {
+ ButtonIconSizes,
+} from '../../../../component-library/components/Buttons/ButtonIcon';
+import NetworkSelectorList from '../../../../components/UI/NetworkSelectorList/NetworkSelectorList';
+import Engine from '../../../../core/Engine';
+import { PermissionKeys } from '../../../../core/Permissions/specifications';
+import { CaveatTypes } from '../../../../core/Permissions/constants';
+
+// Internal dependencies.
+import { NetworkPermissionsConnectedProps } from './NetworkPermissionsConnected.types';
+import styles from './NetworkPermissionsConnected.styles';
+import { useMetrics } from '../../../../components/hooks/useMetrics';
+import Text, {
+ TextVariant,
+} from '../../../../component-library/components/Texts/Text';
+import Avatar, {
+ AvatarSize,
+ AvatarVariant,
+} from '../../../../component-library/components/Avatars/Avatar';
+import Button, {
+ ButtonSize,
+ ButtonVariants,
+ ButtonWidthTypes,
+} from '../../../../component-library/components/Buttons/Button';
+
+const AccountPermissionsConnected = ({
+ onSetPermissionsScreen,
+ onDismissSheet,
+ hostname,
+ favicon,
+ secureIcon,
+ urlWithProtocol,
+}: NetworkPermissionsConnectedProps) => {
+ const { navigate } = useNavigation();
+ const { trackEvent } = useMetrics();
+
+ const providerConfig: ProviderConfig = useSelector(selectProviderConfig);
+
+ const { networkName, networkImageSource } = useNetworkInfo(hostname);
+
+ const openRevokePermissions = () =>
+ onSetPermissionsScreen(AccountPermissionsScreens.Revoke);
+
+ const switchNetwork = useCallback(() => {
+ navigate(Routes.MODAL.ROOT_MODAL_FLOW, {
+ screen: Routes.SHEET.NETWORK_SELECTOR,
+ });
+
+ trackEvent(MetaMetricsEvents.NETWORK_SELECTOR_PRESSED, {
+ chain_id: getDecimalChainId(providerConfig.chainId),
+ });
+ }, [providerConfig.chainId, navigate, trackEvent]);
+
+ const networkConfigurations = useSelector(selectNetworkConfigurations);
+
+ // Get permitted chain IDs
+ const getPermittedChainIds = () => {
+ try {
+ const caveat = Engine.context.PermissionController.getCaveat(
+ hostname,
+ PermissionKeys.permittedChains,
+ CaveatTypes.restrictNetworkSwitching,
+ );
+ if (Array.isArray(caveat?.value)) {
+ return caveat.value.filter(
+ (item): item is string => typeof item === 'string',
+ );
+ }
+ } catch (e) {
+ // noop
+ }
+ // If no permitted chains found, default to current chain
+ return providerConfig?.chainId ? [providerConfig.chainId] : [];
+ };
+
+ const permittedChainIds = getPermittedChainIds();
+
+ // Filter networks to only show permitted ones, excluding the active network
+ const networks = Object.entries(networkConfigurations)
+ .filter(
+ ([key]) =>
+ permittedChainIds.includes(key) && key !== providerConfig?.chainId,
+ )
+ .map(([key, network]) => ({
+ id: key,
+ name: network.name,
+ rpcUrl: network.rpcEndpoints[network.defaultRpcEndpointIndex].url,
+ isSelected: false,
+ //@ts-expect-error - The utils/network file is still JS and this function expects a networkType, and should be optional
+ imageSource: getNetworkImageSource({
+ chainId: network?.chainId,
+ }),
+ }));
+
+ return (
+ <>
+ {!isMultichainVersion1Enabled && (
+
+ )}
+ {isMultichainVersion1Enabled && (
+
+
+
+ )}
+
+ {!isMultichainVersion1Enabled && (
+
+ )}
+ {isMultichainVersion1Enabled && (
+
+
+ {strings('permissions.permitted_networks')}
+
+
+ {
+ navigate(Routes.MODAL.ROOT_MODAL_FLOW, {
+ screen: Routes.SHEET.PERMITTED_NETWORKS_INFO_SHEET,
+ });
+ }}
+ />
+
+
+ )}
+ {!isMultichainVersion1Enabled && (
+
+ )}
+
+
+ {
+ const theNetworkName = handleNetworkSwitch(
+ getDecimalChainId(chainId),
+ );
+
+ if (theNetworkName) {
+ trackEvent(MetaMetricsEvents.NETWORK_SWITCHED, {
+ chain_id: getDecimalChainId(chainId),
+ from_network: providerConfig?.nickname || theNetworkName,
+ to_network: theNetworkName,
+ });
+ onDismissSheet();
+ }
+ }}
+ selectedChainIds={[]}
+ isMultiSelect={false}
+ />
+
+ {isMultichainVersion1Enabled && (
+