Skip to content

Commit

Permalink
feat: Add Chain Permissions (#10650)
Browse files Browse the repository at this point in the history
<!--
Please submit this PR as a draft initially.
Do not mark it as "Ready for review" until the template has been
completely filled out, and PR status checks have passed at least once.
-->

## **Description**

This PR introduces the Chain Permissions feature, allowing users to
grant dapps explicit permissions to switch to specific networks. When
the `MM_CHAIN_PERMISSIONS` feature flag is enabled, dapps requesting
network changes via `wallet_addEthereumChain` or
`wallet_switchEthereumChain` will prompt the user to add permission to
access the specified chain IDs and, if granted, subsequent
`wallet_switchEthereumChain` requests made to that chainId will not
require user confirmation.

- Added a new permission `endowment:permitted-chains` with the caveat
`restrictNetworkSwitching`.
- Updated `wallet_addEthereumChain` and `wallet_switchEthereumChain`
methods to integrate with the new chain permissions.
- Introduced environment variables `MM_CHAIN_PERMISSIONS` to toggle the
feature.
- Changed `MULTICHAIN_V1` env variable to `MM_PER_DAPP_SELECTED_NETWORK`
(more specific/accurate name)
- Implemented validation functions for chain IDs and network parameters.

## **Related issues**

Fixes: MetaMask/MetaMask-planning#2883

## **Manual testing steps**

1. Enable the Chain Permissions feature by setting
`MM_CHAIN_PERMISSIONS` **and the new UI** by setting
`MM_MULTICHAIN_V1_ENABLED` with the watch script:
`MM_CHAIN_PERMISSIONS=1 MM_MULTICHAIN_V1_ENABLED=1 yarn watch:clean`
3. Navigate to a dapp and request a network switch to a chain ID not yet
permitted.
4. Observe that a permission prompt appears asking to grant access to
the requested network.
5. Approve the permission and verify that the network switch occurs
successfully.
6. Repeat the network switch request and confirm that no prompt appears,
indicating the permission is saved.
7. Attempt to switch to a different, unpermitted network and deny the
permission. Verify that the network does not switch.

## **Screenshots/Recordings**

<!-- If applicable, add screenshots and/or recordings to visualize the
before and after of your change. -->

## **Before**

### Switch Flow:


https://github.com/user-attachments/assets/cc0ecee6-79c1-40ed-85a7-cb3effed4acb


### Add Flow


https://github.com/user-attachments/assets/4d988a80-a020-49b4-89a5-527884a16558


## **After**

### Connection + Switch Flow

The initial connection flow suggests that your granting network
permissions too but that is not wired up yet.


https://github.com/user-attachments/assets/4a5ba93a-4de4-411e-97c3-abd4794e0539


### Add Flow


https://github.com/user-attachments/assets/41e22694-6aa5-444b-8231-7fccad942465




## **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.
  • Loading branch information
adonesky1 authored Oct 18, 2024
1 parent 076fa31 commit 583c309
Show file tree
Hide file tree
Showing 20 changed files with 1,490 additions and 385 deletions.
7 changes: 5 additions & 2 deletions .js.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,10 @@ export MM_ENABLE_SETTINGS_PAGE_DEV_OPTIONS="true"
# The endpoint used to submit errors and tracing data to Sentry for dev environment.
# export MM_SENTRY_DSN_DEV=

# Multichain Feature flag
export MULTICHAIN_V1=""
# Per dapp selected network (Amon Hen) feature flag
export MM_PER_DAPP_SELECTED_NETWORK=""

export MM_CHAIN_PERMISSIONS=""

#Multichain feature flag specific to UI changes
export MM_MULTICHAIN_V1_ENABLED=""
18 changes: 18 additions & 0 deletions app/components/UI/PermissionsSummary/PermissionsSummary.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,24 @@ const mockInitialState = {
};

describe('PermissionsSummary', () => {
it('should render correctly for network switch', () => {
const { toJSON } = renderWithProvider(
<PermissionsSummary
currentPageInformation={{
currentEnsName: '',
icon: '',
url: 'https://app.uniswap.org/',
}}
customNetworkInformation={{
chainName: 'Sepolia',
chainId: '0x1',
}}
isNetworkSwitch
/>,
{ state: mockInitialState },
);
expect(toJSON()).toMatchSnapshot();
});
it('should render correctly', () => {
const { toJSON } = renderWithProvider(
<PermissionsSummary
Expand Down
70 changes: 51 additions & 19 deletions app/components/UI/PermissionsSummary/PermissionsSummary.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
import React, { useCallback } from 'react';
import StyledButton from '../StyledButton';
import { SafeAreaView, TouchableOpacity, View } from 'react-native';
import {
ImageSourcePropType,
SafeAreaView,
TouchableOpacity,
View,
} from 'react-native';
import { useNavigation } from '@react-navigation/native';
import { strings } from '../../../../locales/i18n';
import { useTheme } from '../../../util/theme';
Expand Down Expand Up @@ -30,19 +35,21 @@ import useSelectedAccount from '../Tabs/TabThumbnail/useSelectedAccount';
import styleSheet from './PermissionsSummary.styles';
import { useStyles } from '../../../component-library/hooks';
import { PermissionsSummaryProps } from './PermissionsSummary.types';
import { useSelector } from 'react-redux';
import { selectNetworkName } from '../../../selectors/networkInfos';
import { USER_INTENT } from '../../../constants/permissions';
import Routes from '../../../constants/navigation/Routes';
import ButtonIcon, {
ButtonIconSizes,
} from '../../../component-library/components/Buttons/ButtonIcon';
import { getNetworkImageSource } from '../../../util/networks';

const PermissionsSummary = ({
currentPageInformation,
customNetworkInformation,
onEdit,
onEditNetworks,
onBack,
onCancel,
onConfirm,
onUserAction,
showActionButtons = true,
isAlreadyConnected = true,
Expand All @@ -54,14 +61,26 @@ const PermissionsSummary = ({
const { styles } = useStyles(styleSheet, { isRenderedAsBottomSheet });
const { navigate } = useNavigation();
const selectedAccount = useSelectedAccount();
const networkName = useSelector(selectNetworkName);

// if network switch, we get the chain name from the customNetworkInformation
let chainName = '';
let chainImage: ImageSourcePropType;
if (isNetworkSwitch && customNetworkInformation?.chainId) {
chainName = customNetworkInformation?.chainName;
// @ts-expect-error getNetworkImageSource is not implemented in typescript
chainImage = getNetworkImageSource({
chainId: customNetworkInformation?.chainId,
});
}

const confirm = () => {
onUserAction?.(USER_INTENT.Confirm);
onConfirm?.();
};

const cancel = () => {
onUserAction?.(USER_INTENT.Cancel);
onCancel?.();
};

const handleEditAccountsButtonPress = () => {
Expand Down Expand Up @@ -208,21 +227,33 @@ const PermissionsSummary = ({
{strings('permissions.use_enabled_networks')}
</TextComponent>
<View style={styles.permissionRequestNetworkInfo}>
<View style={styles.permissionRequestNetworkName}>
<TextComponent numberOfLines={1} ellipsizeMode="tail">
<TextComponent variant={TextVariant.BodySM}>
{strings('permissions.requesting_for')}
</TextComponent>
<TextComponent variant={TextVariant.BodySMMedium}>
{networkName}
</TextComponent>
</TextComponent>
</View>
<View style={styles.avatarGroup}>
<AvatarGroup
avatarPropsList={SAMPLE_AVATARGROUP_PROPS.avatarPropsList}
/>
</View>
{isNetworkSwitch && (
<>
<View style={styles.permissionRequestNetworkName}>
<TextComponent numberOfLines={1} ellipsizeMode="tail">
<TextComponent variant={TextVariant.BodySM}>
{strings('permissions.requesting_for')}
</TextComponent>
<TextComponent variant={TextVariant.BodySMMedium}>
{chainName}
</TextComponent>
</TextComponent>
</View>
<Avatar
variant={AvatarVariant.Network}
size={AvatarSize.Xs}
name={chainName}
imageSource={chainImage}
/>
</>
)}
{!isNetworkSwitch && (
<View style={styles.avatarGroup}>
<AvatarGroup
avatarPropsList={SAMPLE_AVATARGROUP_PROPS.avatarPropsList}
/>
</View>
)}
</View>
</View>
{!isNetworkSwitch && renderEndAccessory()}
Expand All @@ -247,6 +278,7 @@ const PermissionsSummary = ({
})}
</TextComponent>
</View>
{/*TODO These should be conditional upon which permissions are being requested*/}
{!isNetworkSwitch && renderAccountPermissionsRequestInfoCard()}
{renderNetworkPermissionsRequestInfoCard()}
</View>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,16 @@ export interface PermissionsSummaryProps {
onEdit?: () => void;
onEditNetworks?: () => void;
onBack?: () => void;
onCancel?: () => void;
onConfirm?: () => void;
onUserAction?: React.Dispatch<React.SetStateAction<USER_INTENT>>;
showActionButtons?: boolean;
isAlreadyConnected?: boolean;
isRenderedAsBottomSheet?: boolean;
isDisconnectAllShown?: boolean;
isNetworkSwitch?: boolean;
customNetworkInformation?: {
chainName: string;
chainId: string;
};
}
Loading

0 comments on commit 583c309

Please sign in to comment.