From ff739b5cfb7d7b94413403105ba49ee9ece21afc Mon Sep 17 00:00:00 2001 From: Owen Craston Date: Wed, 7 Jun 2023 18:07:16 -0400 Subject: [PATCH] [Flask] Use permissions controller as source of truth for Snaps Permissions UI (#6533) * create custom component for snaps cell * rendering permissions from permissions controller * bip32 * testing * use a switch statement * dont check for the types * handler functions * fix snapsettings test * cleanup * function to check the types * readme update --- .../Views/Snaps/SnapSettings/SnapSettings.tsx | 5 +- .../SnapSettings/test/SnapSettings.test.tsx | 95 ++- .../test/SnapSettingsList.test.tsx | 4 - .../SnapPermissionCell.styles.ts | 41 ++ .../SnapPermissionCell/SnapPermissionCell.tsx | 69 ++ .../components/SnapPermissionCell/index.ts | 4 + .../test/SnapPermissionCell.test.tsx | 51 ++ .../components/SnapPermissions/README.md | 143 +++- .../SnapPermissions/SnapPermissions.styles.ts | 32 +- .../SnapPermissions/SnapPermissions.tsx | 309 ++++---- .../test/SnapPermission.test.tsx | 658 +++++++++++++----- app/core/Permissions/constants.ts | 2 +- locales/languages/en.json | 3 +- package.json | 4 - 14 files changed, 1023 insertions(+), 397 deletions(-) create mode 100644 app/components/Views/Snaps/components/SnapPermissionCell/SnapPermissionCell.styles.ts create mode 100644 app/components/Views/Snaps/components/SnapPermissionCell/SnapPermissionCell.tsx create mode 100644 app/components/Views/Snaps/components/SnapPermissionCell/index.ts create mode 100644 app/components/Views/Snaps/components/SnapPermissionCell/test/SnapPermissionCell.test.tsx diff --git a/app/components/Views/Snaps/SnapSettings/SnapSettings.tsx b/app/components/Views/Snaps/SnapSettings/SnapSettings.tsx index 769a81176c5..d85222cd845 100644 --- a/app/components/Views/Snaps/SnapSettings/SnapSettings.tsx +++ b/app/components/Views/Snaps/SnapSettings/SnapSettings.tsx @@ -69,10 +69,7 @@ const SnapSettings = () => { /> - + diff --git a/app/components/Views/Snaps/SnapSettings/test/SnapSettings.test.tsx b/app/components/Views/Snaps/SnapSettings/test/SnapSettings.test.tsx index 2394c1cbf3e..e82daf1b0a3 100644 --- a/app/components/Views/Snaps/SnapSettings/test/SnapSettings.test.tsx +++ b/app/components/Views/Snaps/SnapSettings/test/SnapSettings.test.tsx @@ -1,5 +1,5 @@ import React from 'react'; -import { render, fireEvent, waitFor } from '@testing-library/react-native'; +import { fireEvent, waitFor } from '@testing-library/react-native'; import { SemVerVersion, Status } from '@metamask/snaps-utils'; import SnapSettings from '../SnapSettings'; import { @@ -9,6 +9,11 @@ import { SNAP_SETTINGS_REMOVE_BUTTON, } from '../../../../../constants/test-ids'; import Engine from '../../../../../core/Engine'; +import renderWithProvider from '../../../../../util/test/renderWithProvider'; +import { + PermissionConstraint, + SubjectPermissions, +} from '@metamask/permission-controller'; jest.mock('../../../../../core/Engine', () => ({ context: { @@ -93,6 +98,67 @@ jest.mock('../../../../../util/navigation/navUtils', () => ({ createNavigationDetails: jest.fn(), })); +const mockDate = 1684964145490; +const mockDate2 = 1686081721987; + +const mockPermissions: SubjectPermissions = { + 'endowment:network-access': { + id: 'Bjj3InYtb6U4ak-uja0f_', + parentCapability: 'endowment:network-access', + invoker: 'npm:@chainsafe/filsnap', + caveats: null, + date: mockDate, + }, + 'endowment:rpc': { + id: 'Zma-vejrSvLtHmLrbSBAX', + parentCapability: 'endowment:rpc', + invoker: 'npm:@chainsafe/filsnap', + caveats: [ + { + type: 'rpcOrigin', + value: { + dapps: true, + snaps: true, + }, + }, + ], + date: mockDate2, + }, + snap_confirm: { + id: 'tVtSEUjc48Ab-gF6UI7X3', + parentCapability: 'snap_confirm', + invoker: 'npm:@chainsafe/filsnap', + caveats: null, + date: mockDate2, + }, + snap_manageState: { + id: 'BKbg3uDSHHu0D1fCUTOmS', + parentCapability: 'snap_manageState', + invoker: 'npm:@chainsafe/filsnap', + caveats: null, + date: mockDate2, + }, + snap_getBip44Entropy: { + id: 'MuqnOW-7BRg94sRDmVnDK', + parentCapability: 'snap_getBip44Entropy', + invoker: 'npm:@chainsafe/filsnap', + caveats: [ + { + type: 'permittedCoinTypes', + value: [ + { + coinType: 1, + }, + { + coinType: 461, + }, + ], + }, + ], + date: mockDate2, + }, +}; + const mockGoBack = jest.fn(); jest.mock('@react-navigation/native', () => { const actualReactNavigation = jest.requireActual('@react-navigation/native'); @@ -105,9 +171,28 @@ jest.mock('@react-navigation/native', () => { }; }); +const engineState = { + engine: { + backgroundState: { + PermissionController: { + subjects: { + 'npm:@chainsafe/filsnap': { + permissions: mockPermissions, + }, + }, + }, + }, + }, +}; + describe('SnapSettings', () => { it('renders correctly', () => { - const { getByTestId, getAllByTestId } = render(); + const { getAllByTestId, getByTestId } = renderWithProvider( + , + { + state: engineState, + }, + ); const removeButton = getByTestId(SNAP_SETTINGS_REMOVE_BUTTON); const description = getByTestId(SNAP_DETAILS_CELL); @@ -116,14 +201,16 @@ describe('SnapSettings', () => { expect(removeButton).toBeTruthy(); expect(description).toBeTruthy(); expect(permissionContainer).toBeTruthy(); - expect(permissions.length).toBe(5); + expect(permissions.length).toBe(7); expect(removeButton.props.children[1].props.children).toBe( 'Remove Filsnap', ); }); it('remove snap and goes back when Remove button is pressed', async () => { - const { getByTestId } = render(); + const { getByTestId } = renderWithProvider(, { + state: engineState, + }); const removeButton = getByTestId(SNAP_SETTINGS_REMOVE_BUTTON); fireEvent(removeButton, 'onPress'); diff --git a/app/components/Views/Snaps/SnapsSettingsList/test/SnapSettingsList.test.tsx b/app/components/Views/Snaps/SnapsSettingsList/test/SnapSettingsList.test.tsx index bd75b7bf9d2..615ee0a0993 100644 --- a/app/components/Views/Snaps/SnapsSettingsList/test/SnapSettingsList.test.tsx +++ b/app/components/Views/Snaps/SnapsSettingsList/test/SnapSettingsList.test.tsx @@ -6,10 +6,6 @@ import renderWithProvider from '../../../../../util/test/renderWithProvider'; import { SNAP_ElEMENT } from '../../../../../constants/test-ids'; import { createSnapSettingsNavDetails } from '../../SnapSettings/SnapSettings'; -jest.mock('react-redux', () => ({ - useSelector: jest.fn(), -})); - const mockNavigate = jest.fn(); jest.mock('@react-navigation/native', () => ({ ...jest.requireActual('@react-navigation/native'), diff --git a/app/components/Views/Snaps/components/SnapPermissionCell/SnapPermissionCell.styles.ts b/app/components/Views/Snaps/components/SnapPermissionCell/SnapPermissionCell.styles.ts new file mode 100644 index 00000000000..f192d5ac9b9 --- /dev/null +++ b/app/components/Views/Snaps/components/SnapPermissionCell/SnapPermissionCell.styles.ts @@ -0,0 +1,41 @@ +import { StyleSheet } from 'react-native'; +import { Theme } from '../../../../../util/theme/models'; + +/** + * + * @param params Style sheet params. + * @param params.theme App theme from ThemeContext. + * @param params.vars Inputs that the style sheet depends on. + * @returns StyleSheet object. + */ +const styleSheet = (params: { theme: Theme }) => { + const { theme } = params; + const { colors } = theme; + return StyleSheet.create({ + permissionCell: { + borderRadius: 10, + borderWidth: 0, + }, + cellBase: { + flexDirection: 'row', + }, + cellBaseInfo: { + flex: 1, + alignItems: 'flex-start', + }, + secondaryText: { + color: colors.text.alternative, + }, + iconWrapper: { + marginTop: 16, + marginRight: 16, + width: 32, + height: 32, + borderRadius: 16, + backgroundColor: colors.background.alternative, + justifyContent: 'center', + alignItems: 'center', + }, + }); +}; +export default styleSheet; diff --git a/app/components/Views/Snaps/components/SnapPermissionCell/SnapPermissionCell.tsx b/app/components/Views/Snaps/components/SnapPermissionCell/SnapPermissionCell.tsx new file mode 100644 index 00000000000..51a79ec8543 --- /dev/null +++ b/app/components/Views/Snaps/components/SnapPermissionCell/SnapPermissionCell.tsx @@ -0,0 +1,69 @@ +import React, { useMemo } from 'react'; +import { View } from 'react-native'; +import { useStyles } from '../../../../hooks/useStyles'; +import stylesheet from './SnapPermissionCell.styles'; +import { + SNAP_PERMISSIONS_DATE, + SNAP_PERMISSIONS_TITLE, + SNAP_PERMISSION_CELL, +} from '../../../../../constants/test-ids'; +import Icon, { + IconColor, + IconName, + IconSize, +} from '../../../../../component-library/components/Icons/Icon'; +import Card from '../../../../../component-library/components/Cards/Card'; +import Text, { + TextVariant, +} from '../../../../../component-library/components/Texts/Text'; +import { strings } from '../../../../../../locales/i18n'; +import { toDateFormat } from '../../../../../util/date'; + +interface SnapPermissionCellProps { + title: string; + date: number; +} + +const SnapPermissionCell = ({ title, date }: SnapPermissionCellProps) => { + const snapInstalledDate: string = useMemo( + () => + strings('app_settings.snaps.snap_permissions.approved_date', { + date: toDateFormat(date), + }), + [date], + ); + + const { styles } = useStyles(stylesheet, {}); + return ( + + + + + + + + {title} + + + {snapInstalledDate} + + + + + ); +}; + +export default React.memo(SnapPermissionCell); diff --git a/app/components/Views/Snaps/components/SnapPermissionCell/index.ts b/app/components/Views/Snaps/components/SnapPermissionCell/index.ts new file mode 100644 index 00000000000..8514a4b9546 --- /dev/null +++ b/app/components/Views/Snaps/components/SnapPermissionCell/index.ts @@ -0,0 +1,4 @@ +/* eslint-disable import/prefer-default-export */ +import SnapPermissionCell from './SnapPermissionCell'; + +export { SnapPermissionCell }; diff --git a/app/components/Views/Snaps/components/SnapPermissionCell/test/SnapPermissionCell.test.tsx b/app/components/Views/Snaps/components/SnapPermissionCell/test/SnapPermissionCell.test.tsx new file mode 100644 index 00000000000..6b43247b56c --- /dev/null +++ b/app/components/Views/Snaps/components/SnapPermissionCell/test/SnapPermissionCell.test.tsx @@ -0,0 +1,51 @@ +import React from 'react'; +import { render } from '@testing-library/react-native'; +import SnapPermissionCell from '../SnapPermissionCell'; +import { + SNAP_PERMISSIONS_DATE, + SNAP_PERMISSIONS_TITLE, + SNAP_PERMISSION_CELL, +} from '../../../../../../constants/test-ids'; + +describe('SnapPermissionCell', () => { + const defaultProps = { + title: 'Permission Title', + date: 1686005090788, + }; + + const setup = (props = defaultProps) => { + const utils = render(); + const permissionCell = utils.getByTestId(SNAP_PERMISSION_CELL); + const permissionTitle = utils.getByTestId(SNAP_PERMISSIONS_TITLE); + const permissionDate = utils.getByTestId(SNAP_PERMISSIONS_DATE); + + return { + ...utils, + permissionCell, + permissionTitle, + permissionDate, + }; + }; + + test('renders correctly', () => { + const { permissionCell, permissionTitle, permissionDate } = setup(); + + const expectedDate = 'Approved on Jun 5 at 6:44 pm'; + + expect(permissionCell).toBeDefined(); + expect(permissionTitle.props.children).toEqual(defaultProps.title); + expect(permissionDate.props.children).toEqual(expectedDate); + }); + + test('displays custom title and secondary text', () => { + const customProps = { + title: 'Custom Title', + date: 1686005090788, + }; + const expectedDate = 'Approved on Jun 5 at 6:44 pm'; + const { permissionTitle, permissionDate } = setup(customProps); + + expect(permissionTitle.props.children).toEqual(customProps.title); + expect(permissionDate.props.children).toEqual(expectedDate); + }); +}); diff --git a/app/components/Views/Snaps/components/SnapPermissions/README.md b/app/components/Views/Snaps/components/SnapPermissions/README.md index 651a6602c43..5bcfed580d8 100644 --- a/app/components/Views/Snaps/components/SnapPermissions/README.md +++ b/app/components/Views/Snaps/components/SnapPermissions/README.md @@ -1,71 +1,144 @@ # SnapPermissions + + The SnapPermissions component is used to display permissions granted to a certain Snap. All permissions will be rendered as a list with each permission displayed as a Card with a title and the installation date of the Snap. + + ## Props -The SnapPermissions component expects two props: + - 1. `permissions`: An object of permissions granted to the Snap. The - object is expected to conform to the [SnapPermissions from @metamask/snaps-utils](https://github.com/MetaMask/snaps/blob/ef885f4cf80a19ecc5b5e523f12e4c4c248cc766/packages/snaps-utils/src/manifest/validation.ts#L160). -2. `installedAt`: A number representing the Unix timestamp when the Snap was installed. +The SnapPermissions component expects one prop: -## Functionality +1. `snapId`: A string that the component will use to query the permission controller for the specified snap. + + + +## Functionality + This component derives human-readable titles for the provided permissions list and maps them to their corresponding cards. The snap methods/permissions and their human-readable counterparts can be found in [this document](https://www.notion.so/bac3299d2c5241c599d2e5e7986e72f7?v=ef742a61bd844435b7171bd2e90b447e). -### Protocol Derivation + + +### Protocol Derivation + There are a few snap permissions that are protocol specific. Each of these protocols are displayed as a separate permission in the list and contain the human-readable protocol name in the title. -#### These permissions are: -- `snap_getBip44Entropy` -- `snap_getBip32Entropy'` -- `snap_getBip32PublicKey` + + +#### These permissions are: + +- `snap_getBip44Entropy` + +- `snap_getBip32Entropy'` + +- `snap_getBip32PublicKey` + + + +#### SLIP-0044 -#### SLIP-0044 The `Bip44` permission follows the [slip-0044](https://github.com/satoshilabs/slips/blob/master/slip-0044.md#registered-coin-types) standard and maps `coin type` as a number to human-readable symbols and names. These mapping can be found [here](https://github.com/satoshilabs/slips/blob/master/slip-0044.md#registered-coin-types). MetaMask uses the [@metamask/slip44](https://github.com/MetaMask/slip44) utility library to maintain these mappings. -##### Example + +##### Example + The permission... + ```JSON { - snap_getBip44Entropy: [ - { coinType: 0 }, - { coinType: 2 }, - ] + "snap_getBip44Entropy": { + "id": "TDDl8FTScrkgzs-sQ4ep0", + "parentCapability": "snap_getBip44Entropy", + "invoker": "npm:@chainsafe/filsnap", + "caveats": [ + { + "type": "permittedCoinTypes", + "value": [ + { + "coinType": 1 + }, + { + "coinType": 2 + } + ] + } + ], + "date": 1686015956781 + } } ``` -renders the titles `Control your Bitcoin accounts and assets` and `Control your Litecoin accounts and assets` respectively. -#### BIP 32 -The `snap_getBip32Entropy` and `snap_getBip32PublicKey` rely on a different mapping to derive their protocols. For these permissions, we compare their `path` and `curve` to the [SNAPS_DERIVATION_PATHS](https://github.com/MetaMask/metamask-extension/blob/49f8052b157374370ac71373708933c6e639944e/shared/constants/snaps.ts#L52). The mobile app uses a copy of this object that can be found in `app/constants/snaps.ts`. -##### Example +renders the titles `Control your Bitcoin accounts and assets` and `Control your Litecoin accounts and assets` respectively. + + + +#### BIP 32 + +The `snap_getBip32Entropy` and `snap_getBip32PublicKey` rely on a different mapping to derive their protocols. For these permissions, we compare their `path` and `curve` to the [SNAPS_DERIVATION_PATHS](https://github.com/MetaMask/metamask-extension/blob/49f8052b157374370ac71373708933c6e639944e/shared/constants/snaps.ts#L52). The mobile app uses a copy of this object that can be found in `app/constants/snaps.ts`. + +##### Example + The permission... + ```json { - snap_getBip32Entropy: [ - { - path: ['m', `44'`, `0'`], - curve: 'ed25519', - }, - { - path: ['m', `44'`, `1'`], - curve: 'secp256k1', - }, - ] + "snap_getBip32Entropy": { + "id": "j8TJuxqEtJZbIqjd2bqsq", + "parentCapability": "snap_getBip32Entropy", + "invoker": "npm:@metamask/test-snap-bip32", + "caveats": [ + { + "type": "permittedDerivationPaths", + "value": [ + { + "path": [ + "m", + "44'", + "0'" + ], + "curve": "secp256k1" + }, + { + "path": [ + "m", + "44'", + "0'" + ], + "curve": "ed25519" + } + ] + } + ], + "date": 1686083278257 + } } + ``` -renders the titles `Control your Test BIP-32 Path (ed25519) accounts and assets` and `Control your Test BIP-32 Path (secp256k1) accounts and assets` respectively. -## Usage +renders the titles `Control your Bitcoin Legacy accounts and assets` and `Control your Test BIP-32 Path (ed25519) accounts and assets` respectively. + + + +## Usage + + The `SnapPermissions` component can be used as a regular React component in JSX: + ```jsx - + + + ``` -Where permissions is an object of the `SnapPermissions` type and installedAt is a Unix timestamp representing the installation time of the Snap. -## Testing + + +## Testing + All of this complex logic is tested in the `SnapPermissions/test/SnapPermission.test.tsx` file and can be run with the command `yarn jest SnapPermissions/test/SnapPermission.test.tsx` from the root of the mobile directory. \ No newline at end of file diff --git a/app/components/Views/Snaps/components/SnapPermissions/SnapPermissions.styles.ts b/app/components/Views/Snaps/components/SnapPermissions/SnapPermissions.styles.ts index 3944ddb8b1b..460585a0336 100644 --- a/app/components/Views/Snaps/components/SnapPermissions/SnapPermissions.styles.ts +++ b/app/components/Views/Snaps/components/SnapPermissions/SnapPermissions.styles.ts @@ -1,5 +1,4 @@ import { StyleSheet } from 'react-native'; -import { Theme } from '../../../../../util/theme/models'; /** * @@ -8,37 +7,10 @@ import { Theme } from '../../../../../util/theme/models'; * @param params.vars Inputs that the style sheet depends on. * @returns StyleSheet object. */ -const styleSheet = (params: { theme: Theme }) => { - const { theme } = params; - const { colors } = theme; - return StyleSheet.create({ +const styleSheet = () => + StyleSheet.create({ section: { paddingTop: 32, }, - permissionCell: { - borderRadius: 10, - borderWidth: 0, - }, - cellBase: { - flexDirection: 'row', - }, - cellBaseInfo: { - flex: 1, - alignItems: 'flex-start', - }, - secondaryText: { - color: colors.text.alternative, - }, - iconWrapper: { - marginTop: 16, - marginRight: 16, - width: 32, - height: 32, - borderRadius: 16, - backgroundColor: colors.background.alternative, - justifyContent: 'center', - alignItems: 'center', - }, }); -}; export default styleSheet; diff --git a/app/components/Views/Snaps/components/SnapPermissions/SnapPermissions.tsx b/app/components/Views/Snaps/components/SnapPermissions/SnapPermissions.tsx index ad26cfa44f1..b8410e88246 100644 --- a/app/components/Views/Snaps/components/SnapPermissions/SnapPermissions.tsx +++ b/app/components/Views/Snaps/components/SnapPermissions/SnapPermissions.tsx @@ -1,23 +1,10 @@ import React, { useCallback, useMemo } from 'react'; import { View } from 'react-native'; -import { SnapPermissions as SnapPermissionsType } from '@metamask/snaps-utils'; import slip44 from '@metamask/slip44'; import type { SupportedCurve } from '@metamask/key-tree'; import stylesheet from './SnapPermissions.styles'; -import { toDateFormat } from '../../../../../util/date'; -import { - SNAP_PERMISSIONS, - SNAP_PERMISSIONS_DATE, - SNAP_PERMISSIONS_TITLE, - SNAP_PERMISSION_CELL, -} from '../../../../../constants/test-ids'; +import { SNAP_PERMISSIONS } from '../../../../../constants/test-ids'; import { strings } from '../../../../../../locales/i18n'; -import Icon, { - IconColor, - IconName, - IconSize, -} from '../../../../../component-library/components/Icons/Icon'; -import Card from '../../../../../component-library/components/Cards/Card'; import Text, { TextVariant, } from '../../../../../component-library/components/Texts/Text'; @@ -28,25 +15,36 @@ import { } from '../../../../../constants/snaps'; import lodash from 'lodash'; import { useStyles } from '../../../../../component-library/hooks'; +import { SnapPermissionCell } from '../SnapPermissionCell'; +import { useSelector } from 'react-redux'; +import { + SubjectPermissions, + PermissionConstraint, +} from '@metamask/permission-controller'; +import { RestrictedMethods } from '../../../../../core/Permissions/constants'; +import { EndowmentPermissions } from '../../../../../constants/permissions'; interface SnapPermissionsProps { - permissions: SnapPermissionsType; - installedAt: number; + snapId: string; } -const SnapPermissions = ({ - permissions, - installedAt, -}: SnapPermissionsProps) => { +const SnapPermissions = ({ snapId }: SnapPermissionsProps) => { const { styles } = useStyles(stylesheet, {}); - const snapInstalledDate: string = useMemo( - () => - strings('app_settings.snaps.snap_permissions.approved_date', { - date: toDateFormat(installedAt), - }), - [installedAt], + + const permissionsState = useSelector( + (state: any) => state.engine.backgroundState.PermissionController, ); + function getPermissionSubjects(state: any) { + return state.subjects || {}; + } + + function getPermissions(state: any, origin: any) { + return getPermissionSubjects(state)[origin]?.permissions; + } + + const permissionsFromController = getPermissions(permissionsState, snapId); + /** * Gets the name of the SLIP-44 protocol corresponding to the specified * `coin_type`. @@ -56,8 +54,8 @@ const SnapPermissions = ({ * to retrieve. * @returns {string | undefined} The name of the protocol if found. */ - const coinTypeToProtocolName = (coinType: string): string | undefined => { - if (coinType === '1') { + const coinTypeToProtocolName = (coinType: number): string | undefined => { + if (coinType === 1) { return 'Test Networks'; } return slip44[coinType]?.name; @@ -82,6 +80,105 @@ const SnapPermissions = ({ return pathMetadata?.name ?? null; }; + interface SnapPermissionData { + label: string; + date: number; + } + + const handleRPCPermissionTitles = useCallback( + ( + permissionsList: SubjectPermissions, + key: typeof EndowmentPermissions['endowment:rpc'], + ) => { + const rpcPermissionsData: SnapPermissionData[] = []; + const rpcPermissionsCaveats = permissionsList[key].caveats; + const date = permissionsList[key].date; + if (rpcPermissionsCaveats) { + for (const caveat of rpcPermissionsCaveats) { + const rpcPermissions = caveat.value as Record; + for (const rpcKey in rpcPermissions) { + if (rpcPermissions[rpcKey] === true) { + const title = strings( + `app_settings.snaps.snap_permissions.human_readable_permission_titles.endowment:rpc.${rpcKey}`, + ); + rpcPermissionsData.push({ label: title, date }); + } + } + } + } + return rpcPermissionsData; + }, + [], + ); + + const handleBip44EntropyPermissionTitles = useCallback( + ( + permissionsList: SubjectPermissions, + key: typeof RestrictedMethods.snap_getBip44Entropy, + ) => { + const bip44EntropyData: SnapPermissionData[] = []; + const coinTypeCaveats = permissionsList[key].caveats; + const date = permissionsList[key].date; + if (coinTypeCaveats) { + for (const caveat of coinTypeCaveats) { + const coinTypes = caveat.value as { coinType: number }[]; + for (const coinType of coinTypes) { + const protocolName = coinTypeToProtocolName(coinType.coinType); + if (protocolName) { + const title = strings( + 'app_settings.snaps.snap_permissions.human_readable_permission_titles.snap_getBip44Entropy', + { protocol: protocolName }, + ); + bip44EntropyData.push({ label: title, date }); + } + } + } + } + return bip44EntropyData; + }, + [], + ); + + const isSnapsDerivationPath = (object: any): object is SnapsDerivationPath => + typeof object === 'object' && + object !== null && + 'path' in object && + 'curve' in object; + + const handleBip32PermissionTitles = useCallback( + ( + permissionsList: SubjectPermissions, + key: + | typeof RestrictedMethods.snap_getBip32Entropy + | typeof RestrictedMethods.snap_getBip32PublicKey, + ) => { + const bip32Data: SnapPermissionData[] = []; + const permittedDerivationPaths = permissionsList[key].caveats?.[0].value; + const date = permissionsList[key].date; + if (permittedDerivationPaths && Array.isArray(permittedDerivationPaths)) { + for (const permittedPath of permittedDerivationPaths) { + if (isSnapsDerivationPath(permittedPath)) { + const derivedProtocolName = getSnapDerivationPathName( + permittedPath.path, + permittedPath.curve, + ); + const protocolName = + derivedProtocolName ?? + `${permittedPath.path.join('/')} (${permittedPath.curve})`; + + const title = strings( + `app_settings.snaps.snap_permissions.human_readable_permission_titles.${key}`, + { protocol: protocolName }, + ); + bip32Data.push({ label: title, date }); + } + } + } + return bip32Data; + }, + [], + ); + /** * Derives human-readable titles for the provided permissions list. * The derived titles are based on the permission key and specific permission scenarios. @@ -98,131 +195,57 @@ const SnapPermissions = ({ * * @returns An array of strings, where each string is a human-readable title for a permission. */ - const derivePermissionsTitles = useCallback( - (permissionsList: SnapPermissionsType) => { - const rpcPermission = 'endowment:rpc'; - const getBip44EntropyPermission = 'snap_getBip44Entropy'; - const getBip32EntropyPermission = 'snap_getBip32Entropy'; - const getBip32PublicKeyPermission = 'snap_getBip32PublicKey'; - const permissionsStrings: string[] = []; + const derivePermissionsTitles: ( + permissionsList: SubjectPermissions, + ) => SnapPermissionData[] = useCallback( + (permissionsList: SubjectPermissions) => { + const permissionsData: SnapPermissionData[] = []; for (const key in permissionsList) { - if (key === rpcPermission) { - const rpcPermissions = permissionsList[key] as { - [key: string]: boolean | undefined; - }; - for (const rpcKey in rpcPermissions) { - if (rpcPermissions[rpcKey] === true) { - const title = strings( - `app_settings.snaps.snap_permissions.human_readable_permission_titles.endowment:rpc.${rpcKey}`, - ); - permissionsStrings.push(title); - } + const date = permissionsList[key].date; + + switch (key) { + case EndowmentPermissions['endowment:rpc']: { + permissionsData.push( + ...handleRPCPermissionTitles(permissionsList, key), + ); + break; } - } else if (key === getBip44EntropyPermission) { - for (const coinType in permissionsList[key]) { - const protocolName = coinTypeToProtocolName(coinType); - if (protocolName) { - const title = strings( - 'app_settings.snaps.snap_permissions.human_readable_permission_titles.snap_getBip44Entropy', - { protocol: protocolName }, - ); - permissionsStrings.push(title); - } + case RestrictedMethods.snap_getBip44Entropy: { + permissionsData.push( + ...handleBip44EntropyPermissionTitles(permissionsList, key), + ); + break; } - } else if (key === getBip32EntropyPermission && permissionsList[key]) { - const bip32PermissionsArray = permissionsList[key]; - if (bip32PermissionsArray) { - for (const bip32Permissions of bip32PermissionsArray) { - const derivationPath = bip32Permissions as SnapsDerivationPath; - const derivedProtocolName = getSnapDerivationPathName( - derivationPath.path, - bip32Permissions.curve, - ); - const protocolName = - derivedProtocolName ?? - `${derivationPath.path.join('/')} (${bip32Permissions.curve})`; - const title = strings( - 'app_settings.snaps.snap_permissions.human_readable_permission_titles.snap_getBip32Entropy', - { protocol: protocolName }, - ); - permissionsStrings.push(title); - } + case RestrictedMethods.snap_getBip32Entropy: + case RestrictedMethods.snap_getBip32PublicKey: { + permissionsData.push( + ...handleBip32PermissionTitles(permissionsList, key), + ); + break; } - } else if ( - key === getBip32PublicKeyPermission && - permissionsList[key] - ) { - const bip32PermissionsArray = permissionsList[key]; - if (bip32PermissionsArray) { - for (const bip32Permissions of bip32PermissionsArray) { - const derivationPath = bip32Permissions as SnapsDerivationPath; - const derivedProtocolName = getSnapDerivationPathName( - derivationPath.path, - bip32Permissions.curve, - ); - const protocolName = - derivedProtocolName ?? - `${derivationPath.path.join('/')} (${bip32Permissions.curve})`; - const title = strings( - 'app_settings.snaps.snap_permissions.human_readable_permission_titles.snap_getBip32PublicKey', - { protocol: protocolName }, - ); - permissionsStrings.push(title); - } + default: { + const title = strings( + `app_settings.snaps.snap_permissions.human_readable_permission_titles.${key}`, + ); + permissionsData.push({ label: title, date }); } - } else { - const title = strings( - `app_settings.snaps.snap_permissions.human_readable_permission_titles.${key}`, - ); - permissionsStrings.push(title); } } - return permissionsStrings; + return permissionsData; }, - [], - ); - - const permissionsToRender: string[] = useMemo( - () => derivePermissionsTitles(permissions), - [derivePermissionsTitles, permissions], + [ + handleBip32PermissionTitles, + handleBip44EntropyPermissionTitles, + handleRPCPermissionTitles, + ], ); - const renderPermissionCell = ( - title: string, - secondaryText: string, - key: number, - ) => ( - - - - - - - - {title} - - - {secondaryText} - - - - + const permissionsToRender: SnapPermissionData[] = useMemo( + () => derivePermissionsTitles(permissionsFromController), + [derivePermissionsTitles, permissionsFromController], ); return ( @@ -232,9 +255,9 @@ const SnapPermissions = ({ 'app_settings.snaps.snap_permissions.permission_section_title', )} - {permissionsToRender.map((item, key) => - renderPermissionCell(item, snapInstalledDate, key), - )} + {permissionsToRender.map((item, index) => ( + + ))} ); }; diff --git a/app/components/Views/Snaps/components/SnapPermissions/test/SnapPermission.test.tsx b/app/components/Views/Snaps/components/SnapPermissions/test/SnapPermission.test.tsx index c6e46e3d60f..fdb1845a70c 100644 --- a/app/components/Views/Snaps/components/SnapPermissions/test/SnapPermission.test.tsx +++ b/app/components/Views/Snaps/components/SnapPermissions/test/SnapPermission.test.tsx @@ -1,5 +1,4 @@ import React from 'react'; -import { render } from '@testing-library/react-native'; import SnapPermissions from '../SnapPermissions'; import { SNAP_PERMISSIONS_DATE, @@ -7,9 +6,16 @@ import { SNAP_PERMISSION_CELL, } from '../../../../../../constants/test-ids'; import { SnapPermissions as SnapPermissionsType } from '@metamask/snaps-utils'; +import renderWithProvider from '../../../../../../util/test/renderWithProvider'; +import { + SubjectPermissions, + PermissionConstraint, +} from '@metamask/permission-controller'; describe('SnapPermissions', () => { + const mockSnapId = '@metamask/mock-snap'; const mockDate = 1684964145490; + const mockDate2 = 1686081721987; const longRunningTitle = 'Run indefinitely'; const networkAccessTitle = 'Access the internet'; const transactionInsightTitle = 'Display transaction insights'; @@ -29,6 +35,59 @@ describe('SnapPermissions', () => { const snapGetEntropyTitle = 'Derive arbitrary keys unique to this snap'; const endowmentKeyringTitle = 'endowment:keyring'; + const mockCoinTypes = [ + { coinType: 0 }, // Bitcoin + { coinType: 1 }, // Testnet (all coins) + { coinType: 2 }, // Litecoin + { coinType: 3 }, // Dogecoin + { coinType: 4 }, // Reddcoin + { coinType: 5 }, // Dash + { coinType: 6 }, // Peercoin + { coinType: 7 }, // Namecoin + { coinType: 8 }, // Feathercoin + { coinType: 9 }, // Counterparty + { coinType: 10 }, // 0x8000000a BLK Blackcoin + { coinType: 11 }, // 0x8000000b NSR NuShares + { coinType: 12 }, // 0x8000000c NBT NuBits + { coinType: 13 }, // 0x8000000d MZC Mazacoin + { coinType: 14 }, // 0x8000000e VIA Viacoin + { coinType: 15 }, // 0x8000000f XCH ClearingHouse + { coinType: 16 }, // 0x80000010 RBY Rubycoin + { coinType: 17 }, // 0x80000011 GRS Groestlcoin + { coinType: 18 }, // 0x80000012 DGC Digitalcoin + { coinType: 19 }, // 0x80000013 CCN Cannacoin + { coinType: 20 }, // 0x80000014 DGB DigiByte + { coinType: 21 }, // 0x80000015 Open Assets + { coinType: 22 }, // 0x80000016 MONA Monacoin + { coinType: 23 }, // 0x80000017 CLAM Clams + { coinType: 24 }, // 0x80000018 XPM Primecoin + { coinType: 25 }, // 0x80000019 NEOS Neoscoin + { coinType: 26 }, // 0x8000001a JBS Jumbucks + { coinType: 27 }, // 0x8000001b ZRC ziftrCOIN + { coinType: 28 }, // 0x8000001c VTC Vertcoin + { coinType: 29 }, //0x8000001d NXT NXT + { coinType: 30 }, // 0x8000001e BURST Burst + { coinType: 31 }, // 0x8000001f MUE MonetaryUnit + { coinType: 32 }, // 0x80000020 ZOOM Zoom + { coinType: 33 }, // 0x80000021 VASH Virtual Cash + { coinType: 34 }, // 0x80000022 CDN Canada eCoin + { coinType: 35 }, // 0x80000023 SDC ShadowCash + { coinType: 36 }, // 0x80000024 PKB ParkByte + { coinType: 37 }, // 0x80000025 PND Pandacoin + { coinType: 38 }, // 0x80000026 START StartCOIN + { coinType: 39 }, // 0x80000027 MOIN MOIN + { coinType: 40 }, // 0x80000028 EXP Expanse + { coinType: 41 }, // 0x80000029 EMC2 Einsteinium + { coinType: 42 }, // 0x8000002a DCR Decred + { coinType: 43 }, // 0x8000002b XEM NEM + { coinType: 44 }, // 0x8000002c PART Particl + { coinType: 45 }, // 0x8000002d ARG Argentum (dead) + { coinType: 46 }, // 0x8000002e Libertas + { coinType: 47 }, // 0x8000002f Posw coin + { coinType: 48 }, // 0x80000030 SHR Shreeji + { coinType: 49 }, // 0x80000031 GCR Global Currency Reserve (GCRcoin) + ]; + const mockCurves: | SnapPermissionsType['snap_getBip32Entropy'] | SnapPermissionsType['snap_getBip32PublicKey'] = [ @@ -118,126 +177,338 @@ describe('SnapPermissions', () => { jest.clearAllMocks(); }); + const mockEngineState = ( + mockPermissions: SubjectPermissions, + ) => ({ + engine: { + backgroundState: { + PermissionController: { + subjects: { + '@metamask/mock-snap': { + permissions: mockPermissions, + }, + }, + }, + }, + }, + }); + it('renders permissions correctly', () => { - const mockPermissions: SnapPermissionsType = { - 'endowment:long-running': {}, - 'endowment:network-access': {}, + const mockPermissions: SubjectPermissions = { + 'endowment:long-running': { + id: 'Bjj3InYtb6U4ak-uja0f_', + parentCapability: 'endowment:long-running', + invoker: 'npm:@chainsafe/filsnap', + caveats: null, + date: mockDate, + }, + 'endowment:network-access': { + id: 'Bjj3InYtb6U4ak-uja0f_', + parentCapability: 'endowment:network-access', + invoker: 'npm:@chainsafe/filsnap', + caveats: null, + date: mockDate, + }, 'endowment:transaction-insight': { - allowTransactionOrigin: true, + id: '_6zTUtmw1BQAF-Iospl_m', + parentCapability: 'endowment:transaction-insight', + invoker: 'npm:@metamask/test-snap-insights', + caveats: null, + date: mockDate, }, 'endowment:cronjob': { - jobs: [ + id: '_6zTUtmw1BQAF-Iospl_m', + parentCapability: 'endowment:cronjob', + invoker: 'npm:@metamask/test-snap-insights', + caveats: [ { - expression: '* * * * *', - request: { - method: 'GET', - params: {}, - id: '1', - jsonrpc: '2.0', + type: 'jobs', + value: { + jobs: [ + { + expression: '* * * * *', + request: { + method: 'GET', + params: {}, + id: '1', + jsonrpc: '2.0', + }, + }, + ], }, }, ], + date: mockDate, }, 'endowment:rpc': { - dapps: true, - snaps: true, + id: 'Zma-vejrSvLtHmLrbSBAX', + parentCapability: 'endowment:rpc', + invoker: 'npm:@chainsafe/filsnap', + caveats: [ + { + type: 'rpcOrigin', + value: { + dapps: true, + snaps: true, + }, + }, + ], + date: mockDate2, + }, + snap_confirm: { + id: 'tVtSEUjc48Ab-gF6UI7X3', + parentCapability: 'snap_confirm', + invoker: 'npm:@chainsafe/filsnap', + caveats: null, + date: mockDate2, + }, + snap_manageState: { + id: 'BKbg3uDSHHu0D1fCUTOmS', + parentCapability: 'snap_manageState', + invoker: 'npm:@metamask/test-snap-managestate', + caveats: null, + date: mockDate2, + }, + snap_notify: { + id: '2JfZ78WEwQT3qvB20EyJR', + parentCapability: 'snap_notify', + invoker: 'npm:@metamask/test-snap-notification', + caveats: null, + date: mockDate2, + }, + snap_getBip32Entropy: { + id: 'j8TJuxqEtJZbIqjd2bqsq', + parentCapability: 'snap_getBip32Entropy', + invoker: 'npm:@metamask/test-snap-bip32', + caveats: [ + { + type: 'permittedDerivationPaths', + value: [ + { + path: ['m', "44'", "0'"], + curve: 'secp256k1', + }, + ], + }, + ], + date: mockDate2, + }, + snap_getBip32PublicKey: { + id: 'BmILTjG7zK4YKzjq-JMYM', + parentCapability: 'snap_getBip32PublicKey', + invoker: 'npm:@metamask/test-snap-bip32', + caveats: [ + { + type: 'permittedDerivationPaths', + value: [ + { + path: ['m', "44'", "0'"], + curve: 'secp256k1', + }, + ], + }, + ], + date: mockDate2, + }, + snap_getBip44Entropy: { + id: 'MuqnOW-7BRg94sRDmVnDK', + parentCapability: 'snap_getBip44Entropy', + invoker: 'npm:@metamask/test-snap-bip44', + caveats: [ + { + type: 'permittedCoinTypes', + value: [ + { + coinType: 0, + }, + ], + }, + ], + date: mockDate2, + }, + snap_getEntropy: { + id: 'MuqnOW-7BRg94sRDmVnDK', + parentCapability: 'snap_getEntropy', + invoker: 'npm:@metamask/test-snap-bip44', + caveats: null, + date: mockDate2, }, - snap_confirm: {}, - snap_manageState: {}, - snap_notify: {}, - snap_getBip32Entropy: [ - { - path: ['m', "44'", "0'"], - curve: 'secp256k1', - }, - ], - snap_getBip32PublicKey: [ - { - path: ['m', "44'", "0'"], - curve: 'secp256k1', - }, - ], - snap_getBip44Entropy: [ - { - coinType: 1, - }, - ], - snap_getEntropy: {}, 'endowment:keyring': { - namespaces: { - mockNamespace: { - chains: [ + id: 'MuqnOW-7BRg94sRDmVnDK', + parentCapability: 'snap_getEntropy', + invoker: 'npm:@metamask/test-snap-bip44', + caveats: [ + { + type: 'mockNamespace', + value: [ { - name: 'Mock Chain', - id: 'mock-chain-id', + mockNamespace: { + chains: [ + { + name: 'Mock Chain', + id: 'mock-chain-id', + }, + ], + methods: ['mockMethod1', 'mockMethod2'], + events: ['mockEvent1', 'mockEvent2'], + }, }, ], - methods: ['mockMethod1', 'mockMethod2'], - events: ['mockEvent1', 'mockEvent2'], + }, + ], + date: mockDate2, + }, + }; + + const engineState = { + engine: { + backgroundState: { + PermissionController: { + subjects: { + '@metamask/mock-snap': { + permissions: mockPermissions, + }, + }, }, }, }, }; - const { getAllByTestId } = render( - , + const { getAllByTestId } = renderWithProvider( + , + { state: engineState }, ); const permissionCells = getAllByTestId(SNAP_PERMISSION_CELL); const permissionCellTitles = getAllByTestId(SNAP_PERMISSIONS_TITLE); + const permissionCellDates = getAllByTestId(SNAP_PERMISSIONS_DATE); expect(permissionCells.length).toBe(14); expect(permissionCellTitles[0].props.children).toBe(longRunningTitle); + expect(permissionCellDates[0].props.children).toBe( + 'Approved on May 24 at 5:35 pm', + ); expect(permissionCellTitles[1].props.children).toBe(networkAccessTitle); + expect(permissionCellDates[1].props.children).toBe( + 'Approved on May 24 at 5:35 pm', + ); expect(permissionCellTitles[2].props.children).toBe( transactionInsightTitle, ); + expect(permissionCellDates[2].props.children).toBe( + 'Approved on May 24 at 5:35 pm', + ); expect(permissionCellTitles[3].props.children).toBe(cronjobTitle); + expect(permissionCellDates[3].props.children).toBe( + 'Approved on May 24 at 5:35 pm', + ); expect(permissionCellTitles[4].props.children).toBe(rpcDappsTitle); + expect(permissionCellDates[4].props.children).toBe( + 'Approved on Jun 6 at 4:02 pm', + ); expect(permissionCellTitles[5].props.children).toBe(rpcSnapsTitle); + expect(permissionCellDates[5].props.children).toBe( + 'Approved on Jun 6 at 4:02 pm', + ); expect(permissionCellTitles[6].props.children).toBe(snapConfirmTitle); + expect(permissionCellDates[6].props.children).toBe( + 'Approved on Jun 6 at 4:02 pm', + ); expect(permissionCellTitles[7].props.children).toBe(snapManageStateTitle); + expect(permissionCellDates[7].props.children).toBe( + 'Approved on Jun 6 at 4:02 pm', + ); expect(permissionCellTitles[8].props.children).toBe(snapNotifyTitle); + expect(permissionCellDates[8].props.children).toBe( + 'Approved on Jun 6 at 4:02 pm', + ); expect(permissionCellTitles[9].props.children).toBe( snapGetBip32EntropyTitle('Bitcoin Legacy'), ); + expect(permissionCellDates[9].props.children).toBe( + 'Approved on Jun 6 at 4:02 pm', + ); expect(permissionCellTitles[10].props.children).toBe( snapGetBip32PublicKeyTitle('Bitcoin Legacy'), ); + expect(permissionCellDates[10].props.children).toBe( + 'Approved on Jun 6 at 4:02 pm', + ); expect(permissionCellTitles[11].props.children).toBe( snapGetBip44EntropyTitle('Bitcoin'), ); + expect(permissionCellDates[11].props.children).toBe( + 'Approved on Jun 6 at 4:02 pm', + ); expect(permissionCellTitles[12].props.children).toBe(snapGetEntropyTitle); + expect(permissionCellDates[12].props.children).toBe( + 'Approved on Jun 6 at 4:02 pm', + ); expect(permissionCellTitles[13].props.children).toBe(endowmentKeyringTitle); + expect(permissionCellDates[13].props.children).toBe( + 'Approved on Jun 6 at 4:02 pm', + ); }); it('renders correct installed date', () => { - const permissions: SnapPermissionsType = { - 'endowment:network-access': {}, + const mockPermissions: SubjectPermissions = { + 'endowment:long-running': { + id: 'Bjj3InYtb6U4ak-uja0f_', + parentCapability: 'endowment:long-running', + invoker: 'npm:@chainsafe/filsnap', + caveats: null, + date: mockDate, + }, 'endowment:rpc': { - dapps: true, - snaps: true, + id: 'Zma-vejrSvLtHmLrbSBAX', + parentCapability: 'endowment:rpc', + invoker: 'npm:@chainsafe/filsnap', + caveats: [ + { + type: 'rpcOrigin', + value: { + dapps: true, + snaps: true, + }, + }, + ], + date: mockDate2, }, }; - const { getAllByTestId } = render( - , + + const { getAllByTestId } = renderWithProvider( + , + { state: mockEngineState(mockPermissions) }, ); const permissionCellDates = getAllByTestId(SNAP_PERMISSIONS_DATE); - const expectedDate = 'Approved on May 24 at 5:35 pm'; + const expectedDate1 = 'Approved on May 24 at 5:35 pm'; + const expectedDate2 = 'Approved on Jun 6 at 4:02 pm'; - expect(permissionCellDates[0].props.children).toBe(expectedDate); - expect(permissionCellDates[1].props.children).toBe(expectedDate); + expect(permissionCellDates[0].props.children).toBe(expectedDate1); + expect(permissionCellDates[1].props.children).toBe(expectedDate2); }); it('renders correct permissions cells for endowment:rpc when both dapps and snaps are permitted', () => { - const permissions: SnapPermissionsType = { + const mockPermissions: SubjectPermissions = { 'endowment:rpc': { - dapps: true, - snaps: true, + id: 'Zma-vejrSvLtHmLrbSBAX', + parentCapability: 'endowment:rpc', + invoker: 'npm:@chainsafe/filsnap', + caveats: [ + { + type: 'rpcOrigin', + value: { + dapps: true, + snaps: true, + }, + }, + ], + date: mockDate2, }, }; - const { getAllByTestId } = render( - , + const { getAllByTestId } = renderWithProvider( + , + { state: mockEngineState(mockPermissions) }, ); const permissionCells = getAllByTestId(SNAP_PERMISSIONS_TITLE); @@ -247,14 +518,26 @@ describe('SnapPermissions', () => { }); it('only renders snap rpc permission when only snaps are permitted', () => { - const permissions: SnapPermissionsType = { + const mockPermissions: SubjectPermissions = { 'endowment:rpc': { - dapps: false, - snaps: true, + id: 'Zma-vejrSvLtHmLrbSBAX', + parentCapability: 'endowment:rpc', + invoker: 'npm:@chainsafe/filsnap', + caveats: [ + { + type: 'rpcOrigin', + value: { + snaps: true, + }, + }, + ], + date: mockDate2, }, }; - const { getAllByTestId } = render( - , + + const { getAllByTestId } = renderWithProvider( + , + { state: mockEngineState(mockPermissions) }, ); const permissionCells = getAllByTestId(SNAP_PERMISSIONS_TITLE); @@ -263,14 +546,26 @@ describe('SnapPermissions', () => { }); it('only renders dapps rpc permission when only dapps are permitted', () => { - const permissions: SnapPermissionsType = { + const mockPermissions: SubjectPermissions = { 'endowment:rpc': { - dapps: true, - snaps: false, + id: 'Zma-vejrSvLtHmLrbSBAX', + parentCapability: 'endowment:rpc', + invoker: 'npm:@chainsafe/filsnap', + caveats: [ + { + type: 'rpcOrigin', + value: { + dapps: true, + }, + }, + ], + date: mockDate2, }, }; - const { getAllByTestId } = render( - , + + const { getAllByTestId } = renderWithProvider( + , + { state: mockEngineState(mockPermissions) }, ); const permissionCells = getAllByTestId(SNAP_PERMISSIONS_TITLE); @@ -279,14 +574,27 @@ describe('SnapPermissions', () => { }); it('does not render rpc permissions when both snaps and dapps are false', () => { - const permissions: SnapPermissionsType = { + const mockPermissions: SubjectPermissions = { 'endowment:rpc': { - dapps: false, - snaps: false, + id: 'Zma-vejrSvLtHmLrbSBAX', + parentCapability: 'endowment:rpc', + invoker: 'npm:@chainsafe/filsnap', + caveats: [ + { + type: 'rpcOrigin', + value: { + dapps: false, + snaps: false, + }, + }, + ], + date: mockDate2, }, }; - const { queryAllByTestId } = render( - , + + const { queryAllByTestId } = renderWithProvider( + , + { state: mockEngineState(mockPermissions) }, ); const permissionCells = queryAllByTestId(SNAP_PERMISSIONS_TITLE); @@ -294,62 +602,24 @@ describe('SnapPermissions', () => { }); it('renders the correct permissions snap_getBip44Entropy with specified protocols', () => { - const mockPermissions: SnapPermissionsType = { - snap_getBip44Entropy: [ - { coinType: 0 }, // Bitcoin - { coinType: 1 }, // Testnet (all coins) - { coinType: 2 }, // Litecoin - { coinType: 3 }, // Dogecoin - { coinType: 4 }, // Reddcoin - { coinType: 5 }, // Dash - { coinType: 6 }, // Peercoin - { coinType: 7 }, // Namecoin - { coinType: 8 }, // Feathercoin - { coinType: 9 }, // Counterparty - { coinType: 10 }, // 0x8000000a BLK Blackcoin - { coinType: 11 }, // 0x8000000b NSR NuShares - { coinType: 12 }, // 0x8000000c NBT NuBits - { coinType: 13 }, // 0x8000000d MZC Mazacoin - { coinType: 14 }, // 0x8000000e VIA Viacoin - { coinType: 15 }, // 0x8000000f XCH ClearingHouse - { coinType: 16 }, // 0x80000010 RBY Rubycoin - { coinType: 17 }, // 0x80000011 GRS Groestlcoin - { coinType: 18 }, // 0x80000012 DGC Digitalcoin - { coinType: 19 }, // 0x80000013 CCN Cannacoin - { coinType: 20 }, // 0x80000014 DGB DigiByte - { coinType: 21 }, // 0x80000015 Open Assets - { coinType: 22 }, // 0x80000016 MONA Monacoin - { coinType: 23 }, // 0x80000017 CLAM Clams - { coinType: 24 }, // 0x80000018 XPM Primecoin - { coinType: 25 }, // 0x80000019 NEOS Neoscoin - { coinType: 26 }, // 0x8000001a JBS Jumbucks - { coinType: 27 }, // 0x8000001b ZRC ziftrCOIN - { coinType: 28 }, // 0x8000001c VTC Vertcoin - { coinType: 29 }, //0x8000001d NXT NXT - { coinType: 30 }, // 0x8000001e BURST Burst - { coinType: 31 }, // 0x8000001f MUE MonetaryUnit - { coinType: 32 }, // 0x80000020 ZOOM Zoom - { coinType: 33 }, // 0x80000021 VASH Virtual Cash - { coinType: 34 }, // 0x80000022 CDN Canada eCoin - { coinType: 35 }, // 0x80000023 SDC ShadowCash - { coinType: 36 }, // 0x80000024 PKB ParkByte - { coinType: 37 }, // 0x80000025 PND Pandacoin - { coinType: 38 }, // 0x80000026 START StartCOIN - { coinType: 39 }, // 0x80000027 MOIN MOIN - { coinType: 40 }, // 0x80000028 EXP Expanse - { coinType: 41 }, // 0x80000029 EMC2 Einsteinium - { coinType: 42 }, // 0x8000002a DCR Decred - { coinType: 43 }, // 0x8000002b XEM NEM - { coinType: 44 }, // 0x8000002c PART Particl - { coinType: 45 }, // 0x8000002d ARG Argentum (dead) - { coinType: 46 }, // 0x8000002e Libertas - { coinType: 47 }, // 0x8000002f Posw coin - { coinType: 48 }, // 0x80000030 SHR Shreeji - { coinType: 49 }, // 0x80000031 GCR Global Currency Reserve (GCRcoin) - ], + const mockPermissions: SubjectPermissions = { + snap_getBip44Entropy: { + id: 'MuqnOW-7BRg94sRDmVnDK', + parentCapability: 'snap_getBip44Entropy', + invoker: 'npm:@metamask/test-snap-bip44', + caveats: [ + { + type: 'permittedCoinTypes', + value: mockCoinTypes, + }, + ], + date: mockDate2, + }, }; - const { getAllByTestId } = render( - , + + const { getAllByTestId } = renderWithProvider( + , + { state: mockEngineState(mockPermissions) }, ); const permissionCellTitles = getAllByTestId(SNAP_PERMISSIONS_TITLE); @@ -507,11 +777,24 @@ describe('SnapPermissions', () => { }); it('renders the correct permissions titles for snap_getBip32Entropy with specified protocols', () => { - const mockPermissions: SnapPermissionsType = { - snap_getBip32Entropy: mockCurves, + const mockPermissions: SubjectPermissions = { + snap_getBip32Entropy: { + id: 'j8TJuxqEtJZbIqjd2bqsq', + parentCapability: 'snap_getBip32Entropy', + invoker: 'npm:@metamask/test-snap-bip32', + caveats: [ + { + type: 'permittedDerivationPaths', + value: mockCurves, + }, + ], + date: mockDate2, + }, }; - const { getAllByTestId } = render( - , + + const { getAllByTestId } = renderWithProvider( + , + { state: mockEngineState(mockPermissions) }, ); const permissionCellTitles = getAllByTestId(SNAP_PERMISSIONS_TITLE); @@ -579,23 +862,33 @@ describe('SnapPermissions', () => { }); it('renders the correct default text for snap_getBip32Entropy with invalid curves', () => { - const invalidMockPermissions: SnapPermissionsType = { - snap_getBip32Entropy: [ - { - path: ['m', "44'", "0'", '0'], - curve: 'secp256k1', - }, - { - path: ['m', "44'", "0'", '3'], - curve: 'ed25519', - }, - ], + const invalidMockPermissions: SubjectPermissions = { + snap_getBip32Entropy: { + id: 'j8TJuxqEtJZbIqjd2bqsq', + parentCapability: 'snap_getBip32Entropy', + invoker: 'npm:@metamask/test-snap-bip32', + caveats: [ + { + type: 'permittedDerivationPaths', + value: [ + { + path: ['m', "44'", "0'", '0'], + curve: 'secp256k1', + }, + { + path: ['m', "44'", "0'", '3'], + curve: 'ed25519', + }, + ], + }, + ], + date: mockDate2, + }, }; - const { getAllByTestId } = render( - , + + const { getAllByTestId } = renderWithProvider( + , + { state: mockEngineState(invalidMockPermissions) }, ); const permissionCellTitles = getAllByTestId(SNAP_PERMISSIONS_TITLE); @@ -610,23 +903,33 @@ describe('SnapPermissions', () => { }); it('renders the correct default text for snap_getBip32PublicKey with invalid curves', () => { - const invalidMockPermissions: SnapPermissionsType = { - snap_getBip32PublicKey: [ - { - path: ['m', "44'", "0'", '0'], - curve: 'secp256k1', - }, - { - path: ['m', "44'", "0'", '3'], - curve: 'ed25519', - }, - ], + const mockPermissions: SubjectPermissions = { + snap_getBip32PublicKey: { + id: 'j8TJuxqEtJZbIqjd2bqsq', + parentCapability: 'snap_getBip32Entropy', + invoker: 'npm:@metamask/test-snap-bip32', + caveats: [ + { + type: 'permittedDerivationPaths', + value: [ + { + path: ['m', "44'", "0'", '0'], + curve: 'secp256k1', + }, + { + path: ['m', "44'", "0'", '3'], + curve: 'ed25519', + }, + ], + }, + ], + date: mockDate2, + }, }; - const { getAllByTestId } = render( - , + + const { getAllByTestId } = renderWithProvider( + , + { state: mockEngineState(mockPermissions) }, ); const permissionCellTitles = getAllByTestId(SNAP_PERMISSIONS_TITLE); @@ -641,11 +944,24 @@ describe('SnapPermissions', () => { }); it('renders the correct permissions titles for snap_getBip32PublicKey with specified protocols', () => { - const mockPermissions: SnapPermissionsType = { - snap_getBip32PublicKey: mockCurves, + const mockPermissions: SubjectPermissions = { + snap_getBip32PublicKey: { + id: 'j8TJuxqEtJZbIqjd2bqsq', + parentCapability: 'snap_getBip32Entropy', + invoker: 'npm:@metamask/test-snap-bip32', + caveats: [ + { + type: 'permittedDerivationPaths', + value: mockCurves, + }, + ], + date: mockDate2, + }, }; - const { getAllByTestId } = render( - , + + const { getAllByTestId } = renderWithProvider( + , + { state: mockEngineState(mockPermissions) }, ); const permissionCellTitles = getAllByTestId(SNAP_PERMISSIONS_TITLE); @@ -713,9 +1029,9 @@ describe('SnapPermissions', () => { }); it('renders correctly with no permissions', () => { - const permissions = {}; - const { queryByTestId } = render( - , + const { queryByTestId } = renderWithProvider( + , + { state: mockEngineState({}) }, ); expect(queryByTestId(SNAP_PERMISSION_CELL)).toBeNull(); }); diff --git a/app/core/Permissions/constants.ts b/app/core/Permissions/constants.ts index 9f51c621608..752abe74b8b 100644 --- a/app/core/Permissions/constants.ts +++ b/app/core/Permissions/constants.ts @@ -12,5 +12,5 @@ export const RestrictedMethods = Object.freeze({ snap_getBip32Entropy: 'snap_getBip32Entropy', snap_getBip44Entropy: 'snap_getBip44Entropy', snap_getEntropy: 'snap_getEntropy', - 'wallet_snap_*': 'wallet_snap_*', + wallet_snap: 'wallet_snap', }); diff --git a/locales/languages/en.json b/locales/languages/en.json index d3b7933477f..0606de8db70 100644 --- a/locales/languages/en.json +++ b/locales/languages/en.json @@ -780,7 +780,8 @@ "snap_getBip32PublicKey": "View your public key for {{protocol}}", "snap_getBip44Entropy": "Control your {{protocol}} accounts and assets", "snap_getEntropy": "Derive arbitrary keys unique to this snap", - "endowment:keyring": "endowment:keyring" + "endowment:keyring": "endowment:keyring", + "wallet_snap": "Connect to {{otherSnapName}}" } } } diff --git a/package.json b/package.json index 4424c18acff..071a5c01fbc 100644 --- a/package.json +++ b/package.json @@ -167,15 +167,11 @@ "@metamask/swaps-controller": "^6.8.0", "@metamask/transaction-controller": "4.0.0", "@metamask/post-message-stream": "6.0.0", - "@metamask/preferences-controller": "^2.1.0", "@metamask/rpc-methods": "0.26.2", - "@metamask/sdk-communication-layer": "0.2.2", "@metamask/slip44": "3.0.0", "@metamask/snaps-controllers": "0.26.2", "@metamask/snaps-utils": "0.26.2", "@metamask/subject-metadata-controller": "^1.0.0", - "@metamask/swaps-controller": "^6.8.0", - "@metamask/transaction-controller": "4.0.0", "@metamask/utils": "^5.0.0", "@ngraveio/bc-ur": "^1.1.6", "@react-native-async-storage/async-storage": "1.17.10",