From 2053533c06ce1dca0aa64fa7edccc74f4f0e316a Mon Sep 17 00:00:00 2001 From: Gauthier Petetin Date: Wed, 29 Jan 2025 19:11:17 -0300 Subject: [PATCH 1/6] fix(action): the next semver version can not be a patch version (#29951) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## **Description** There was a bug in our script that determines the next semver version. It was taking the highest release version and incrementing the minor version number to deduce the next semver version number to apply on PRs when they get merged, which works in most cases, but not when the highest version number is a patch version. By incrementing the minor version number of a patch version, we end up with a new patch version number, which doesn't even exist by the way. This is not expected as the next semver version shall never be a patch version. For example: all these PRs have been incorrectly labeled (release 12.11.1 doesn't even exist): /~https://github.com/MetaMask/metamask-extension/issues?q=label%3Arelease-12.11.1+is%3Aclosed [![Open in GitHub Codespaces](/~https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/29951?quickstart=1) [Same PR for Mobile repo](/~https://github.com/MetaMask/metamask-mobile/pull/13232) ## **Related issues** - None ## **Manual testing steps** I tested it on this [test repo](/~https://github.com/gauthierpetetin-test/repo_test/branches/all?query=release%2F), where the current highest version number is 12.12.166 - I merged this [PR](/~https://github.com/gauthierpetetin-test/repo_test/pull/217) before fixing the bug, and it got labeled with `release-12.13.166` - I merged this [PR](/~https://github.com/gauthierpetetin-test/repo_test/pull/218) after fixing the bug, and it got labeled with `release-12.13.0` ## **Screenshots/Recordings** - None ## **Pre-merge author checklist** - [x] I've followed [MetaMask Contributor Docs](/~https://github.com/MetaMask/contributor-docs) and [MetaMask Extension Coding Standards](/~https://github.com/MetaMask/metamask-extension/blob/main/.github/guidelines/CODING_GUIDELINES.md). - [x] I've completed the PR template to the best of my ability - [x] I’ve included tests if applicable - [x] I’ve documented my code using [JSDoc](https://jsdoc.app/) format if applicable - [x] I’ve applied the right labels on the PR (see [labeling guidelines](/~https://github.com/MetaMask/metamask-extension/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. --- development/get-next-semver-version.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/development/get-next-semver-version.sh b/development/get-next-semver-version.sh index bdf4b93dcfcf..8217860755a4 100755 --- a/development/get-next-semver-version.sh +++ b/development/get-next-semver-version.sh @@ -21,7 +21,7 @@ VERSION_PACKAGE=$(node -p "require('./package.json').version") # Compare versions and keep the highest one HIGHEST_VERSION=$(printf "%s\n%s\n%s" "$VERSION_BRANCHES" "$VERSION_TAGS" "$VERSION_PACKAGE" | sort --version-sort | tail -n 1) -# Increment the minor version of the highest version found -NEXT_VERSION=$(echo "$HIGHEST_VERSION" | awk -F. -v OFS=. '{$2++; print}') +# Increment the minor version of the highest version found and reset the patch version to 0 +NEXT_VERSION=$(echo "$HIGHEST_VERSION" | awk -F. -v OFS=. '{$2++; $3=0; print}') echo "NEXT_SEMVER_VERSION=${NEXT_VERSION}" >> "$GITHUB_ENV" From e041dc899e90c8af620c5377d4293fab25e24175 Mon Sep 17 00:00:00 2001 From: ffmcgee <51971598+ffmcgee725@users.noreply.github.com> Date: Wed, 29 Jan 2025 23:29:10 +0100 Subject: [PATCH 2/6] refactor: connection Flow to use CAIP25 Permission format (#29824) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit For part of our collaboration with the Snaps team to enable Non EVM multichain API we are refactoring the connection flow UI. The first step to enable this to modify what [the connection confirmation UI components](/~https://github.com/MetaMask/metamask-extension/blob/cdd603c56211d1d483d87e5ee3fea27aadd66fda/ui/pages/permissions-connect/connect-page/connect-page.tsx#L59) receive from the ApprovalController. Currently they receive an object like this: ```json { "eth_accounts": { "caveats": [ { "type": "restrictReturnedAccounts", "value": [] } ] }, "endowment:permitted-chains": { "caveats": [ { "type": "restrictNetworkSwitching", "value": [] } ] } } ``` Basically the params for a `wallet_requestPermission` request. We want to change this so that instead they receive (and know how to interpret) a CAIP25 formatted request like: ```json { "eip155:1": { "accounts": [] // there may be accounts here but often will not be }, "eip155:10": { "accounts": [] }, } ``` ## **Description** [![Open in GitHub Codespaces](/~https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/29824?quickstart=1) ## **Related issues** Fixes: ## **Manual testing steps** 1. Send the following request ``` await window.ethereum.request({ "method": "wallet_requestPermissions", "params": [ { "eth_accounts": { "caveats": [ { "type": "restrictReturnedAccounts", "value": [] } ] }, "endowment:permitted-chains": { "caveats": [ { "type": "restrictNetworkSwitching", "value": [] } ] } } ], }); 
 ``` 2. Wallet extension UI should prompt user to give permissions to accounts / chains and edit if desired or confirm to proceed. ## **Screenshots/Recordings** /~https://github.com/user-attachments/assets/a877e2ad-3800-47c2-a577-4f1c76d546be NOTE: Don't mind different screens in the UI, this vid is outdated and all UI screens are same as `main` branch ### **Before** ### **After** ## **Pre-merge author checklist** - [X] I've followed [MetaMask Contributor Docs](/~https://github.com/MetaMask/contributor-docs) and [MetaMask Extension Coding Standards](/~https://github.com/MetaMask/metamask-extension/blob/main/.github/guidelines/CODING_GUIDELINES.md). - [X] I've completed the PR template to the best of my ability - [X] I’ve included tests if applicable - [X] I’ve documented my code using [JSDoc](https://jsdoc.app/) format if applicable - [X] I’ve applied the right labels on the PR (see [labeling guidelines](/~https://github.com/MetaMask/metamask-extension/blob/main/.github/guidelines/LABELING_GUIDELINES.md)). Not required for external contributors. ## **Pre-merge reviewer checklist** - [X] I've manually tested the PR (e.g. pull and build branch, run the app, test code being changed). - [X] 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. --------- Co-authored-by: Alex Donesky --- .../controllers/permissions/background-api.js | 60 ++-- .../permissions/background-api.test.js | 68 ++-- app/scripts/metamask-controller.js | 80 +++-- app/scripts/metamask-controller.test.js | 332 ++++++++++++++---- .../permission-page-container.component.js | 40 ++- ui/helpers/utils/permission.js | 33 +- .../connect-page/connect-page.test.tsx | 33 +- .../connect-page/connect-page.tsx | 46 ++- .../connect-page/utils.test.ts | 130 +++++++ .../permissions-connect/connect-page/utils.ts | 79 +++++ .../permissions-connect.component.js | 34 +- .../permissions-connect.container.js | 13 +- 12 files changed, 681 insertions(+), 267 deletions(-) create mode 100644 ui/pages/permissions-connect/connect-page/utils.test.ts create mode 100644 ui/pages/permissions-connect/connect-page/utils.ts diff --git a/app/scripts/controllers/permissions/background-api.js b/app/scripts/controllers/permissions/background-api.js index 62b32a072e68..bb4c50dcef38 100644 --- a/app/scripts/controllers/permissions/background-api.js +++ b/app/scripts/controllers/permissions/background-api.js @@ -12,8 +12,6 @@ import { setPermittedEthChainIds, } from '@metamask/multichain'; import { isSnapId } from '@metamask/snaps-utils'; -import { RestrictedMethods } from '../../../../shared/constants/permissions'; -import { PermissionNames } from './specifications'; export function getPermissionBackgroundApiMethods({ permissionController, @@ -102,14 +100,15 @@ export function getPermissionBackgroundApiMethods({ }; const requestAccountsAndChainPermissions = async (origin, id) => { - // Note that we are purposely requesting an approval from the ApprovalController - // and then manually forming the permission that is then granted via the - // PermissionController rather than calling the PermissionController.requestPermissions() - // directly because the Approval UI is still dependent on the notion of there - // being separate "eth_accounts" and "endowment:permitted-chains" permissions. - // After that depedency is refactored, we can move to requesting "endowment:caip25" - // directly from the PermissionController instead. - const legacyApproval = await approvalController.addAndShowApprovalRequest({ + /** + * Note that we are purposely requesting an approval from the ApprovalController + * and then manually forming the permission that is then granted via the + * PermissionController rather than calling the PermissionController.requestPermissions() + * directly because the CAIP-25 permission is missing the factory method implementation. + * After the factory method is added, we can move to requesting "endowment:caip25" + * directly from the PermissionController instead. + */ + const { permissions } = await approvalController.addAndShowApprovalRequest({ id, origin, requestData: { @@ -118,41 +117,26 @@ export function getPermissionBackgroundApiMethods({ origin, }, permissions: { - [RestrictedMethods.eth_accounts]: {}, - [PermissionNames.permittedChains]: {}, + [Caip25EndowmentPermissionName]: { + caveats: [ + { + type: Caip25CaveatType, + value: { + requiredScopes: {}, + optionalScopes: {}, + isMultichainOrigin: false, + }, + }, + ], + }, }, }, type: MethodNames.RequestPermissions, }); - const newCaveatValue = { - requiredScopes: {}, - optionalScopes: {}, - isMultichainOrigin: false, - }; - - const caveatValueWithChains = setPermittedEthChainIds( - newCaveatValue, - legacyApproval.approvedChainIds, - ); - - const caveatValueWithAccounts = setEthAccounts( - caveatValueWithChains, - legacyApproval.approvedAccounts, - ); - permissionController.grantPermissions({ subject: { origin }, - approvedPermissions: { - [Caip25EndowmentPermissionName]: { - caveats: [ - { - type: Caip25CaveatType, - value: caveatValueWithAccounts, - }, - ], - }, - }, + approvedPermissions: permissions, }); }; diff --git a/app/scripts/controllers/permissions/background-api.test.js b/app/scripts/controllers/permissions/background-api.test.js index 74a357f35f52..b469ce120cb9 100644 --- a/app/scripts/controllers/permissions/background-api.test.js +++ b/app/scripts/controllers/permissions/background-api.test.js @@ -6,10 +6,8 @@ import { Caip25CaveatType, Caip25EndowmentPermissionName, } from '@metamask/multichain'; -import { RestrictedMethods } from '../../../../shared/constants/permissions'; import { flushPromises } from '../../../../test/lib/timer-helpers'; import { getPermissionBackgroundApiMethods } from './background-api'; -import { PermissionNames } from './specifications'; describe('permission background API methods', () => { afterEach(() => { @@ -466,13 +464,35 @@ describe('permission background API methods', () => { }); describe('requestAccountsAndChainPermissionsWithId', () => { + const approvedPermissions = { + [Caip25EndowmentPermissionName]: { + caveats: [ + { + type: Caip25CaveatType, + value: { + requiredScopes: {}, + optionalScopes: { + 'eip155:1': { + accounts: ['eip155:1:0xdeadbeef'], + }, + 'eip155:5': { + accounts: ['eip155:5:0xdeadbeef'], + }, + }, + isMultichainOrigin: false, + }, + }, + ], + }, + }; + it('requests eth_accounts and permittedChains approval and returns the request id', async () => { const approvalController = { addAndShowApprovalRequest: jest.fn().mockResolvedValue({ - approvedChainIds: ['0x1', '0x5'], - approvedAccounts: ['0xdeadbeef'], + permissions: approvedPermissions, }), }; + const permissionController = { grantPermissions: jest.fn(), }; @@ -496,8 +516,18 @@ describe('permission background API methods', () => { origin: 'foo.com', }, permissions: { - [RestrictedMethods.eth_accounts]: {}, - [PermissionNames.permittedChains]: {}, + [Caip25EndowmentPermissionName]: { + caveats: [ + { + type: Caip25CaveatType, + value: { + requiredScopes: {}, + optionalScopes: {}, + isMultichainOrigin: false, + }, + }, + ], + }, }, }, type: MethodNames.RequestPermissions, @@ -508,10 +538,10 @@ describe('permission background API methods', () => { it('grants a legacy CAIP-25 permission (isMultichainOrigin: false) with the approved eip155 chainIds and accounts', async () => { const approvalController = { addAndShowApprovalRequest: jest.fn().mockResolvedValue({ - approvedChainIds: ['0x1', '0x5'], - approvedAccounts: ['0xdeadbeef'], + permissions: approvedPermissions, }), }; + const permissionController = { grantPermissions: jest.fn(), }; @@ -527,27 +557,7 @@ describe('permission background API methods', () => { subject: { origin: 'foo.com', }, - approvedPermissions: { - [Caip25EndowmentPermissionName]: { - caveats: [ - { - type: Caip25CaveatType, - value: { - requiredScopes: {}, - optionalScopes: { - 'eip155:1': { - accounts: ['eip155:1:0xdeadbeef'], - }, - 'eip155:5': { - accounts: ['eip155:5:0xdeadbeef'], - }, - }, - isMultichainOrigin: false, - }, - }, - ], - }, - }, + approvedPermissions, }); }); }); diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index 7e5f2763dcca..abb43e42d60f 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -5318,6 +5318,15 @@ export default class MetamaskController extends EventEmitter { * @param {Hex} chainId - The chainId to add incrementally. */ async requestApprovalPermittedChainsPermission(origin, chainId) { + const caveatValueWithChains = setPermittedEthChainIds( + { + requiredScopes: {}, + optionalScopes: {}, + isMultichainOrigin: false, + }, + [chainId], + ); + const id = nanoid(); await this.approvalController.addAndShowApprovalRequest({ id, @@ -5328,15 +5337,16 @@ export default class MetamaskController extends EventEmitter { origin, }, permissions: { - [PermissionNames.permittedChains]: { + [Caip25EndowmentPermissionName]: { caveats: [ { - type: CaveatTypes.restrictNetworkSwitching, - value: [chainId], + type: Caip25CaveatType, + value: caveatValueWithChains, }, ], }, }, + isLegacySwitchEthereumChain: true, }, type: MethodNames.RequestPermissions, }); @@ -5468,20 +5478,15 @@ export default class MetamaskController extends EventEmitter { delete permissions[PermissionNames.permittedChains]; } - const id = nanoid(); - const legacyApproval = - await this.approvalController.addAndShowApprovalRequest({ - id, - origin, - requestData: { - metadata: { - id, - origin, - }, - permissions, - }, - type: MethodNames.RequestPermissions, - }); + const requestedChains = + permissions[PermissionNames.permittedChains]?.caveats?.find( + (caveat) => caveat.type === CaveatTypes.restrictNetworkSwitching, + )?.value ?? []; + + const requestedAccounts = + permissions[PermissionNames.eth_accounts]?.caveats?.find( + (caveat) => caveat.type === CaveatTypes.restrictReturnedAccounts, + )?.value ?? []; const newCaveatValue = { requiredScopes: {}, @@ -5495,26 +5500,41 @@ export default class MetamaskController extends EventEmitter { const caveatValueWithChains = setPermittedEthChainIds( newCaveatValue, - isSnapId(origin) ? [] : legacyApproval.approvedChainIds, + isSnapId(origin) ? [] : requestedChains, ); - const caveatValueWithAccounts = setEthAccounts( + const caveatValueWithAccountsAndChains = setEthAccounts( caveatValueWithChains, - legacyApproval.approvedAccounts, + requestedAccounts, ); - return { - [Caip25EndowmentPermissionName]: { - caveats: [ - { - type: Caip25CaveatType, - value: caveatValueWithAccounts, + const id = nanoid(); + + const { permissions: approvedPermissions } = + await this.approvalController.addAndShowApprovalRequest({ + id, + origin, + requestData: { + metadata: { + id, + origin, }, - ], - }, - }; - } + permissions: { + [Caip25EndowmentPermissionName]: { + caveats: [ + { + type: Caip25CaveatType, + value: caveatValueWithAccountsAndChains, + }, + ], + }, + }, + }, + type: MethodNames.RequestPermissions, + }); + return approvedPermissions; + } // --------------------------------------------------------------------------- // Identity Management (signature operations) diff --git a/app/scripts/metamask-controller.test.js b/app/scripts/metamask-controller.test.js index 464a4b017d7d..72435e8f6bd3 100644 --- a/app/scripts/metamask-controller.test.js +++ b/app/scripts/metamask-controller.test.js @@ -56,7 +56,8 @@ import { ENVIRONMENT } from '../../development/build/constants'; import { SECOND } from '../../shared/constants/time'; import { CaveatTypes, - RestrictedMethods, + EndowmentTypes, + RestrictedEthMethods, } from '../../shared/constants/permissions'; import { deferredPromise } from './lib/util'; import { METAMASK_COOKIE_HANDLER } from './constants/stream'; @@ -966,8 +967,7 @@ describe('MetaMaskController', () => { 'addAndShowApprovalRequest', ) .mockResolvedValue({ - approvedAccounts: [], - approvedChainIds: [], + permissions: {}, }); jest .spyOn(metamaskController.permissionController, 'grantPermissions') @@ -1008,8 +1008,7 @@ describe('MetaMaskController', () => { 'addAndShowApprovalRequest', ) .mockResolvedValue({ - approvedAccounts: [], - approvedChainIds: [], + permissions: {}, }); jest .spyOn(metamaskController.permissionController, 'grantPermissions') @@ -1042,15 +1041,22 @@ describe('MetaMaskController', () => { origin: 'test.com', }, permissions: { - [RestrictedMethods.eth_accounts]: { + [Caip25EndowmentPermissionName]: { caveats: [ { - type: CaveatTypes.restrictReturnedAccounts, - value: ['foo'], + type: Caip25CaveatType, + value: { + requiredScopes: {}, + optionalScopes: { + 'wallet:eip155': { + accounts: ['wallet:eip155:foo'], + }, + }, + isMultichainOrigin: false, + }, }, ], }, - [PermissionNames.permittedChains]: {}, }, }, type: 'wallet_requestPermissions', @@ -1065,8 +1071,7 @@ describe('MetaMaskController', () => { 'addAndShowApprovalRequest', ) .mockResolvedValue({ - approvedAccounts: [], - approvedChainIds: [], + permissions: {}, }); jest .spyOn(metamaskController.permissionController, 'grantPermissions') @@ -1099,12 +1104,22 @@ describe('MetaMaskController', () => { origin: 'test.com', }, permissions: { - [RestrictedMethods.eth_accounts]: {}, - [PermissionNames.permittedChains]: { + [Caip25EndowmentPermissionName]: { caveats: [ { - type: CaveatTypes.restrictNetworkSwitching, - value: ['0x64'], + type: Caip25CaveatType, + value: { + requiredScopes: {}, + optionalScopes: { + 'wallet:eip155': { + accounts: [], + }, + 'eip155:100': { + accounts: [], + }, + }, + isMultichainOrigin: false, + }, }, ], }, @@ -1122,8 +1137,7 @@ describe('MetaMaskController', () => { 'addAndShowApprovalRequest', ) .mockResolvedValue({ - approvedAccounts: [], - approvedChainIds: [], + permissions: {}, }); jest .spyOn(metamaskController.permissionController, 'grantPermissions') @@ -1164,19 +1178,22 @@ describe('MetaMaskController', () => { origin: 'test.com', }, permissions: { - [PermissionNames.eth_accounts]: { - caveats: [ - { - type: CaveatTypes.restrictReturnedAccounts, - value: ['foo'], - }, - ], - }, - [PermissionNames.permittedChains]: { + [Caip25EndowmentPermissionName]: { caveats: [ { - type: CaveatTypes.restrictNetworkSwitching, - value: ['0x64'], + type: Caip25CaveatType, + value: { + requiredScopes: {}, + optionalScopes: { + 'wallet:eip155': { + accounts: ['wallet:eip155:foo'], + }, + 'eip155:100': { + accounts: ['eip155:100:foo'], + }, + }, + isMultichainOrigin: false, + }, }, ], }, @@ -1194,8 +1211,7 @@ describe('MetaMaskController', () => { 'addAndShowApprovalRequest', ) .mockResolvedValue({ - approvedAccounts: [], - approvedChainIds: [], + permissions: {}, }); jest .spyOn(metamaskController.permissionController, 'grantPermissions') @@ -1228,11 +1244,19 @@ describe('MetaMaskController', () => { origin: 'npm:snap', }, permissions: { - [RestrictedMethods.eth_accounts]: { + [Caip25EndowmentPermissionName]: { caveats: [ { - type: CaveatTypes.restrictReturnedAccounts, - value: ['foo'], + type: Caip25CaveatType, + value: { + requiredScopes: {}, + optionalScopes: { + 'wallet:eip155': { + accounts: ['wallet:eip155:foo'], + }, + }, + isMultichainOrigin: false, + }, }, ], }, @@ -1250,8 +1274,7 @@ describe('MetaMaskController', () => { 'addAndShowApprovalRequest', ) .mockResolvedValue({ - approvedAccounts: [], - approvedChainIds: [], + permissions: {}, }); jest .spyOn(metamaskController.permissionController, 'grantPermissions') @@ -1284,7 +1307,22 @@ describe('MetaMaskController', () => { origin: 'npm:snap', }, permissions: { - [PermissionNames.eth_accounts]: {}, + [Caip25EndowmentPermissionName]: { + caveats: [ + { + type: Caip25CaveatType, + value: { + requiredScopes: {}, + optionalScopes: { + 'wallet:eip155': { + accounts: [], + }, + }, + isMultichainOrigin: false, + }, + }, + ], + }, }, }, type: 'wallet_requestPermissions', @@ -1299,8 +1337,7 @@ describe('MetaMaskController', () => { 'addAndShowApprovalRequest', ) .mockResolvedValue({ - approvedAccounts: [], - approvedChainIds: [], + permissions: {}, }); jest .spyOn(metamaskController.permissionController, 'grantPermissions') @@ -1341,11 +1378,19 @@ describe('MetaMaskController', () => { origin: 'npm:snap', }, permissions: { - [PermissionNames.eth_accounts]: { + [Caip25EndowmentPermissionName]: { caveats: [ { - type: CaveatTypes.restrictReturnedAccounts, - value: ['foo'], + type: Caip25CaveatType, + value: { + requiredScopes: {}, + optionalScopes: { + 'wallet:eip155': { + accounts: ['wallet:eip155:foo'], + }, + }, + isMultichainOrigin: false, + }, }, ], }, @@ -1371,58 +1416,115 @@ describe('MetaMaskController', () => { ).rejects.toThrow(new Error('approval rejected')); }); - it('returns the CAIP-25 approval with eth accounts, chainIds, and isMultichainOrigin: false if origin is not snapId', async () => { + it('requests CAIP-25 approval with accounts and chainIds specified from `eth_accounts` and `endowment:permittedChains` permissions caveats, and isMultichainOrigin: false if origin is not snapId', async () => { + const origin = 'test.com'; + jest .spyOn( metamaskController.approvalController, 'addAndShowApprovalRequest', ) .mockResolvedValue({ - approvedChainIds: ['0x1', '0x5'], - approvedAccounts: ['0xdeadbeef'], + permissions: { + [Caip25EndowmentPermissionName]: { + caveats: [ + { + type: Caip25CaveatType, + value: { + requiredScopes: {}, + optionalScopes: {}, + isMultichainOrigin: false, + }, + }, + ], + }, + }, }); - const result = await metamaskController.requestCaip25Approval( - 'test.com', - {}, - ); - - expect(result).toStrictEqual({ - [Caip25EndowmentPermissionName]: { + await metamaskController.requestCaip25Approval('test.com', { + [RestrictedEthMethods.eth_accounts]: { caveats: [ { - type: Caip25CaveatType, - value: { - requiredScopes: {}, - optionalScopes: { - 'wallet:eip155': { - accounts: ['wallet:eip155:0xdeadbeef'], - }, - 'eip155:1': { - accounts: ['eip155:1:0xdeadbeef'], - }, - 'eip155:5': { - accounts: ['eip155:5:0xdeadbeef'], - }, - }, - isMultichainOrigin: false, - }, + type: 'restrictReturnedAccounts', + value: ['0xdeadbeef'], + }, + ], + }, + [EndowmentTypes.permittedChains]: { + caveats: [ + { + type: 'restrictNetworkSwitching', + value: ['0x1', '0x5'], }, ], }, }); + + expect( + metamaskController.approvalController.addAndShowApprovalRequest, + ).toHaveBeenCalledWith( + expect.objectContaining({ + id: expect.stringMatching(/.{21}/u), + origin, + requestData: { + metadata: { + id: expect.stringMatching(/.{21}/u), + origin, + }, + permissions: { + [Caip25EndowmentPermissionName]: { + caveats: [ + { + type: Caip25CaveatType, + value: { + requiredScopes: {}, + optionalScopes: { + 'wallet:eip155': { + accounts: ['wallet:eip155:0xdeadbeef'], + }, + 'eip155:1': { + accounts: ['eip155:1:0xdeadbeef'], + }, + 'eip155:5': { + accounts: ['eip155:5:0xdeadbeef'], + }, + }, + isMultichainOrigin: false, + }, + }, + ], + }, + }, + }, + type: 'wallet_requestPermissions', + }), + ); }); - it('returns the CAIP-25 approval with approved accounts for the `wallet:eip155` scope (and no approved chainIds) with isMultichainOrigin: false if origin is snapId', async () => { + it('requests CAIP-25 approval with approved accounts for the `wallet:eip155` scope (and no approved chainIds) with isMultichainOrigin: false if origin is snapId', async () => { + const origin = 'npm:snap'; jest .spyOn( metamaskController.approvalController, 'addAndShowApprovalRequest', ) .mockResolvedValue({ - approvedChainIds: ['0x1', '0x5'], - approvedAccounts: ['0xdeadbeef'], + permissions: { + [Caip25EndowmentPermissionName]: { + caveats: [ + { + type: Caip25CaveatType, + value: { + requiredScopes: {}, + optionalScopes: {}, + isMultichainOrigin: false, + }, + }, + ], + }, + }, }); + jest .spyOn(metamaskController.permissionController, 'grantPermissions') .mockReturnValue({ @@ -1431,12 +1533,62 @@ describe('MetaMaskController', () => { }, }); - const result = await metamaskController.requestCaip25Approval( - 'npm:snap', - {}, + await metamaskController.requestCaip25Approval(origin, { + [RestrictedEthMethods.eth_accounts]: { + caveats: [ + { + type: 'restrictReturnedAccounts', + value: ['0xdeadbeef'], + }, + ], + }, + [EndowmentTypes.permittedChains]: { + caveats: [ + { + type: 'restrictNetworkSwitching', + value: ['0x1', '0x5'], + }, + ], + }, + }); + + expect( + metamaskController.approvalController.addAndShowApprovalRequest, + ).toHaveBeenCalledWith( + expect.objectContaining({ + id: expect.stringMatching(/.{21}/u), + origin, + requestData: expect.objectContaining({ + metadata: { + id: expect.stringMatching(/.{21}/u), + origin, + }, + permissions: { + [Caip25EndowmentPermissionName]: { + caveats: [ + { + type: Caip25CaveatType, + value: { + requiredScopes: {}, + optionalScopes: { + 'wallet:eip155': { + accounts: ['wallet:eip155:0xdeadbeef'], + }, + }, + isMultichainOrigin: false, + }, + }, + ], + }, + }, + }), + type: 'wallet_requestPermissions', + }), ); + }); - expect(result).toStrictEqual({ + it('should return sessions scopes returned from calling ApprovalController.addAndShowApprovalRequest', async () => { + const expectedPermissions = { [Caip25EndowmentPermissionName]: { caveats: [ { @@ -1453,7 +1605,23 @@ describe('MetaMaskController', () => { }, ], }, - }); + }; + + jest + .spyOn( + metamaskController.approvalController, + 'addAndShowApprovalRequest', + ) + .mockResolvedValue({ + permissions: expectedPermissions, + }); + + const result = await metamaskController.requestCaip25Approval( + 'test.com', + {}, + ); + + expect(result).toStrictEqual(expectedPermissions); }); }); @@ -1483,11 +1651,19 @@ describe('MetaMaskController', () => { origin: 'test.com', }, permissions: { - [PermissionNames.permittedChains]: { + [Caip25EndowmentPermissionName]: { caveats: [ { - type: CaveatTypes.restrictNetworkSwitching, - value: ['0x1'], + type: Caip25CaveatType, + value: { + requiredScopes: {}, + optionalScopes: { + 'eip155:1': { + accounts: [], + }, + }, + isMultichainOrigin: false, + }, }, ], }, diff --git a/ui/components/app/permission-page-container/permission-page-container.component.js b/ui/components/app/permission-page-container/permission-page-container.component.js index 6e0ff41cd461..253497359412 100644 --- a/ui/components/app/permission-page-container/permission-page-container.component.js +++ b/ui/components/app/permission-page-container/permission-page-container.component.js @@ -4,26 +4,30 @@ import { SnapCaveatType, WALLET_SNAP_PERMISSION_KEY, } from '@metamask/snaps-rpc-methods'; +import { + Caip25EndowmentPermissionName, + getPermittedEthChainIds, +} from '@metamask/multichain'; import { SubjectType } from '@metamask/permission-controller'; import { MetaMetricsEventCategory } from '../../../../shared/constants/metametrics'; import { PageContainerFooter } from '../../ui/page-container'; import PermissionsConnectFooter from '../permissions-connect-footer'; -import { - CaveatTypes, - RestrictedMethods, -} from '../../../../shared/constants/permissions'; +import { RestrictedMethods } from '../../../../shared/constants/permissions'; import SnapPrivacyWarning from '../snaps/snap-privacy-warning'; import { getDedupedSnaps } from '../../../helpers/utils/util'; -import { containsEthPermissionsAndNonEvmAccount } from '../../../helpers/utils/permissions'; + import { BackgroundColor, Display, FlexDirection, } from '../../../helpers/constants/design-system'; import { Box } from '../../component-library'; -// eslint-disable-next-line import/no-restricted-paths -import { PermissionNames } from '../../../../app/scripts/controllers/permissions'; +import { + getRequestedSessionScopes, + getCaip25PermissionsResponse, +} from '../../../pages/permissions-connect/connect-page/utils'; +import { containsEthPermissionsAndNonEvmAccount } from '../../../helpers/utils/permissions'; import { PermissionPageContainerContent } from '.'; export default class PermissionPageContainer extends Component { @@ -147,19 +151,17 @@ export default class PermissionPageContainer extends Component { (selectedAccount) => selectedAccount.address, ); - const permittedChainsPermission = - _request.permissions?.[PermissionNames.permittedChains]; - const approvedChainIds = permittedChainsPermission?.caveats?.find( - (caveat) => caveat.type === CaveatTypes.restrictNetworkSwitching, - )?.value; + const requestedSessionsScopes = getRequestedSessionScopes( + _request.permission, + ); + const approvedChainIds = getPermittedEthChainIds(requestedSessionsScopes); const request = { ..._request, - permissions: { ..._request.permissions }, - ...(_request.permissions?.eth_accounts && { approvedAccounts }), - ...(_request.permissions?.[PermissionNames.permittedChains] && { - approvedChainIds, - }), + permissions: { + ..._request.permissions, + ...getCaip25PermissionsResponse(approvedAccounts, approvedChainIds), + }, }; if (Object.keys(request.permissions).length > 0) { @@ -171,7 +173,7 @@ export default class PermissionPageContainer extends Component { onLeftFooterClick = () => { const requestedPermissions = this.getRequestedPermissions(); - if (requestedPermissions[PermissionNames.permittedChains] === undefined) { + if (requestedPermissions[Caip25EndowmentPermissionName] === undefined) { this.goBack(); } else { this.onCancel(); @@ -201,7 +203,7 @@ export default class PermissionPageContainer extends Component { }; const footerLeftActionText = requestedPermissions[ - PermissionNames.permittedChains + Caip25EndowmentPermissionName ] ? this.context.t('cancel') : this.context.t('back'); diff --git a/ui/helpers/utils/permission.js b/ui/helpers/utils/permission.js index 576116bd9893..0cf6abf8b1cc 100644 --- a/ui/helpers/utils/permission.js +++ b/ui/helpers/utils/permission.js @@ -8,7 +8,10 @@ import { getSnapDerivationPathName, } from '@metamask/snaps-utils'; import { isNonEmptyArray } from '@metamask/controller-utils'; -import { Caip25EndowmentPermissionName } from '@metamask/multichain'; +import { + Caip25EndowmentPermissionName, + getEthAccounts, +} from '@metamask/multichain'; import { RestrictedMethods, EndowmentPermissions, @@ -30,6 +33,7 @@ import { // TODO: Remove restricted import // eslint-disable-next-line import/no-restricted-paths import { PermissionNames } from '../../../app/scripts/controllers/permissions'; +import { getRequestedSessionScopes } from '../../pages/permissions-connect/connect-page/utils'; import { getURLHost } from './util'; const UNKNOWN_PERMISSION = Symbol('unknown'); @@ -50,13 +54,28 @@ function getSnapNameComponent(snapName) { ); } -export const PERMISSION_DESCRIPTIONS = deepFreeze({ +const PERMISSION_DESCRIPTIONS = deepFreeze({ // "endowment:caip25" entry is needed for the Snaps Permissions Review UI - [Caip25EndowmentPermissionName]: ({ t }) => ({ - label: t('permission_ethereumAccounts'), - leftIcon: IconName.Eye, - weight: PermissionWeight.eth_accounts, - }), + [Caip25EndowmentPermissionName]: ({ t, permissionValue }) => { + const caveatValue = getRequestedSessionScopes({ + permissions: permissionValue, + }); + const requestedAccounts = getEthAccounts(caveatValue); + const isLegacySwitchChain = requestedAccounts.length === 0; + + if (isLegacySwitchChain) { + return { + label: t('permission_walletSwitchEthereumChain'), + leftIcon: IconName.Wifi, + weight: PermissionWeight.permittedChains, + }; + } + return { + label: t('permission_ethereumAccounts'), + leftIcon: IconName.Eye, + weight: PermissionWeight.eth_accounts, + }; + }, // "eth_accounts" entry is needed for the Snaps Permissions Grant UI [RestrictedMethods.eth_accounts]: ({ t }) => ({ label: t('permission_ethereumAccounts'), diff --git a/ui/pages/permissions-connect/connect-page/connect-page.test.tsx b/ui/pages/permissions-connect/connect-page/connect-page.test.tsx index 86fa769c3206..e720bb4781fc 100644 --- a/ui/pages/permissions-connect/connect-page/connect-page.test.tsx +++ b/ui/pages/permissions-connect/connect-page/connect-page.test.tsx @@ -1,12 +1,11 @@ import React from 'react'; +import { + Caip25CaveatType, + Caip25EndowmentPermissionName, +} from '@metamask/multichain'; import { renderWithProvider } from '../../../../test/jest/rendering'; import mockState from '../../../../test/data/mock-state.json'; import configureStore from '../../../store/store'; -import { - CaveatTypes, - EndowmentTypes, - RestrictedMethods, -} from '../../../../shared/constants/permissions'; import { overrideAccountsFromMockState } from '../../../../test/jest/mocks'; import { MOCK_ACCOUNT_BIP122_P2WPKH, @@ -95,19 +94,21 @@ describe('ConnectPage', () => { id: '1', origin: mockTestDappUrl, permissions: { - [RestrictedMethods.eth_accounts]: { - caveats: [ - { - type: CaveatTypes.restrictReturnedAccounts, - value: ['0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc'], - }, - ], - }, - [EndowmentTypes.permittedChains]: { + [Caip25EndowmentPermissionName]: { caveats: [ { - type: CaveatTypes.restrictNetworkSwitching, - value: ['0x1'], + type: Caip25CaveatType, + value: { + requiredScopes: {}, + optionalScopes: { + 'eip155:1': { + accounts: [ + 'eip155:1:0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc', + ], + }, + }, + isMultichainOrigin: false, + }, }, ], }, diff --git a/ui/pages/permissions-connect/connect-page/connect-page.tsx b/ui/pages/permissions-connect/connect-page/connect-page.tsx index 0baa9f393b77..2a0335363c2e 100644 --- a/ui/pages/permissions-connect/connect-page/connect-page.tsx +++ b/ui/pages/permissions-connect/connect-page/connect-page.tsx @@ -3,6 +3,8 @@ import { useSelector } from 'react-redux'; import { InternalAccount } from '@metamask/keyring-internal-api'; import { isEvmAccountType } from '@metamask/keyring-api'; import { NetworkConfiguration } from '@metamask/network-controller'; +import { getEthAccounts, getPermittedEthChainIds } from '@metamask/multichain'; +import { Hex } from '@metamask/utils'; import { useI18nContext } from '../../../hooks/useI18nContext'; import { getSelectedInternalAccount, @@ -32,20 +34,17 @@ import { } from '../../../helpers/constants/design-system'; import { TEST_CHAINS } from '../../../../shared/constants/network'; import PermissionsConnectFooter from '../../../components/app/permissions-connect-footer'; -import { - CaveatTypes, - EndowmentTypes, - RestrictedMethods, -} from '../../../../shared/constants/permissions'; import { getMultichainNetwork } from '../../../selectors/multichain'; +import { + getRequestedSessionScopes, + getCaip25PermissionsResponse, + PermissionsRequest, +} from './utils'; export type ConnectPageRequest = { id: string; origin: string; - permissions?: Record< - string, - { caveats?: { type: string; value: string[] }[] } - >; + permissions?: PermissionsRequest; }; export type ConnectPageProps = { @@ -64,19 +63,11 @@ export const ConnectPage: React.FC = ({ }) => { const t = useI18nContext(); - const ethAccountsPermission = - request?.permissions?.[RestrictedMethods.eth_accounts]; - const requestedAccounts = - ethAccountsPermission?.caveats?.find( - (caveat) => caveat.type === CaveatTypes.restrictReturnedAccounts, - )?.value || []; - - const permittedChainsPermission = - request?.permissions?.[EndowmentTypes.permittedChains]; - const requestedChainIds = - permittedChainsPermission?.caveats?.find( - (caveat) => caveat.type === CaveatTypes.restrictNetworkSwitching, - )?.value || []; + const requestedSessionsScopes = getRequestedSessionScopes( + request.permissions, + ); + const requestedAccounts = getEthAccounts(requestedSessionsScopes); + const requestedChainIds = getPermittedEthChainIds(requestedSessionsScopes); const networkConfigurations = useSelector(getNetworkConfigurationsByChainId); const [nonTestNetworks, testNetworks] = useMemo( @@ -96,7 +87,7 @@ export const ConnectPage: React.FC = ({ const currentlySelectedNetwork = useSelector(getMultichainNetwork); const currentlySelectedNetworkChainId = currentlySelectedNetwork.network.chainId; - // If globally selected network is a test network, include that in the default selcted networks for connection request + // If globally selected network is a test network, include that in the default selected networks for connection request const selectedTestNetwork = testNetworks.find( (network: { chainId: string }) => network.chainId === currentlySelectedNetworkChainId, @@ -133,8 +124,13 @@ export const ConnectPage: React.FC = ({ const onConfirm = () => { const _request = { ...request, - approvedAccounts: selectedAccountAddresses, - approvedChainIds: selectedChainIds, + permissions: { + ...request.permissions, + ...getCaip25PermissionsResponse( + selectedAccountAddresses as Hex[], + selectedChainIds, + ), + }, }; approveConnection(_request); }; diff --git a/ui/pages/permissions-connect/connect-page/utils.test.ts b/ui/pages/permissions-connect/connect-page/utils.test.ts new file mode 100644 index 000000000000..fc494eb576cb --- /dev/null +++ b/ui/pages/permissions-connect/connect-page/utils.test.ts @@ -0,0 +1,130 @@ +import { + Caip25CaveatType, + Caip25EndowmentPermissionName, +} from '@metamask/multichain'; +import { Hex } from '@metamask/utils'; +import { CHAIN_IDS } from '../../../../shared/constants/network'; +import { getCaip25PermissionsResponse } from './utils'; + +describe('getCaip25PermissionsResponse', () => { + describe('No accountAddresses or chainIds requested', () => { + it(`should construct a valid ${Caip25EndowmentPermissionName} empty permission`, () => { + const result = getCaip25PermissionsResponse([], []); + + expect(result).toEqual({ + [Caip25EndowmentPermissionName]: { + caveats: [ + { + type: Caip25CaveatType, + value: { + requiredScopes: {}, + optionalScopes: { + 'wallet:eip155': { + accounts: [], + }, + }, + isMultichainOrigin: false, + }, + }, + ], + }, + }); + }); + }); + + describe('Request approval for chainIds', () => { + it(`should construct a valid ${Caip25EndowmentPermissionName} permission from the passed chainIds`, () => { + const hexChainIds: Hex[] = [CHAIN_IDS.ARBITRUM]; + const result = getCaip25PermissionsResponse([], hexChainIds); + + expect(result).toEqual({ + [Caip25EndowmentPermissionName]: { + caveats: [ + { + type: Caip25CaveatType, + value: { + requiredScopes: {}, + optionalScopes: { + 'wallet:eip155': { + accounts: [], + }, + 'eip155:42161': { + accounts: [], + }, + }, + isMultichainOrigin: false, + }, + }, + ], + }, + }); + }); + }); + describe('Request approval for accountAddresses', () => { + it(`should construct a valid ${Caip25EndowmentPermissionName} permission from the passed accountAddresses`, () => { + const addresses: Hex[] = ['0x4c286da233db3d63d44dc2ec8adc8b6dfb595cb4']; + + const result = getCaip25PermissionsResponse(addresses, []); + + expect(result).toEqual({ + [Caip25EndowmentPermissionName]: { + caveats: [ + { + type: Caip25CaveatType, + value: { + requiredScopes: {}, + optionalScopes: { + 'wallet:eip155': { + accounts: [ + 'wallet:eip155:0x4c286da233db3d63d44dc2ec8adc8b6dfb595cb4', + ], + }, + }, + isMultichainOrigin: false, + }, + }, + ], + }, + }); + }); + }); + describe('Request approval for accountAddresses and chainIds', () => { + it(`should construct a valid ${Caip25EndowmentPermissionName} permission from the passed accountAddresses and chainIds`, () => { + const addresses: Hex[] = ['0x4c286da233db3d63d44dc2ec8adc8b6dfb595cb4']; + const hexChainIds: Hex[] = [CHAIN_IDS.ARBITRUM, CHAIN_IDS.LINEA_MAINNET]; + + const result = getCaip25PermissionsResponse(addresses, hexChainIds); + + expect(result).toEqual({ + [Caip25EndowmentPermissionName]: { + caveats: [ + { + type: Caip25CaveatType, + value: { + requiredScopes: {}, + optionalScopes: { + 'wallet:eip155': { + accounts: [ + 'wallet:eip155:0x4c286da233db3d63d44dc2ec8adc8b6dfb595cb4', + ], + }, + 'eip155:42161': { + accounts: [ + 'eip155:42161:0x4c286da233db3d63d44dc2ec8adc8b6dfb595cb4', + ], + }, + 'eip155:59144': { + accounts: [ + 'eip155:59144:0x4c286da233db3d63d44dc2ec8adc8b6dfb595cb4', + ], + }, + }, + isMultichainOrigin: false, + }, + }, + ], + }, + }); + }); + }); +}); diff --git a/ui/pages/permissions-connect/connect-page/utils.ts b/ui/pages/permissions-connect/connect-page/utils.ts new file mode 100644 index 000000000000..75b66d790f63 --- /dev/null +++ b/ui/pages/permissions-connect/connect-page/utils.ts @@ -0,0 +1,79 @@ +import { Hex } from '@metamask/utils'; +import { + Caip25CaveatType, + Caip25CaveatValue, + Caip25EndowmentPermissionName, + setEthAccounts, + setPermittedEthChainIds, +} from '@metamask/multichain'; + +export type PermissionsRequest = Record< + string, + { caveats?: { type: string; value: Caip25CaveatValue }[] } +>; + +/** + * Takes in an incoming {@link PermissionsRequest} and attempts to return the {@link Caip25CaveatValue} with the Ethereum accounts set. + * + * @param permissions - The {@link PermissionsRequest} with the target name of the {@link Caip25EndowmentPermissionName}. + * @returns The {@link Caip25CaveatValue} with the Ethereum accounts set. + */ +export function getRequestedSessionScopes( + permissions?: PermissionsRequest, +): Pick { + return ( + permissions?.[Caip25EndowmentPermissionName]?.caveats?.find( + (caveat) => caveat.type === Caip25CaveatType, + )?.value ?? { + optionalScopes: {}, + requiredScopes: {}, + } + ); +} + +/** + * Parses the CAIP-25 authorized permissions object after UI confirmation. + * + * @param addresses - The list of permitted addresses. + * @param hexChainIds - The list of permitted chains. + * @returns The granted permissions with the target name of the {@link Caip25EndowmentPermissionName}. + */ +export function getCaip25PermissionsResponse( + addresses: Hex[], + hexChainIds: Hex[], +): { + [Caip25EndowmentPermissionName]: { + caveats: [{ type: string; value: Caip25CaveatValue }]; + }; +} { + const caveatValue: Caip25CaveatValue = { + requiredScopes: {}, + optionalScopes: { + 'wallet:eip155': { + accounts: [], + }, + }, + isMultichainOrigin: false, + }; + + const caveatValueWithChains = setPermittedEthChainIds( + caveatValue, + hexChainIds, + ); + + const caveatValueWithAccounts = setEthAccounts( + caveatValueWithChains, + addresses, + ); + + return { + [Caip25EndowmentPermissionName]: { + caveats: [ + { + type: Caip25CaveatType, + value: caveatValueWithAccounts, + }, + ], + }, + }; +} diff --git a/ui/pages/permissions-connect/permissions-connect.component.js b/ui/pages/permissions-connect/permissions-connect.component.js index e9f4f5f8f592..302346846fb4 100644 --- a/ui/pages/permissions-connect/permissions-connect.component.js +++ b/ui/pages/permissions-connect/permissions-connect.component.js @@ -4,6 +4,7 @@ import { Switch, Route } from 'react-router-dom'; import { providerErrors, serializeError } from '@metamask/rpc-errors'; import { SubjectType } from '@metamask/permission-controller'; import { isSnapId } from '@metamask/snaps-utils'; +import { getEthAccounts, getPermittedEthChainIds } from '@metamask/multichain'; // TODO: Remove restricted import // eslint-disable-next-line import/no-restricted-paths import { isEthAddress } from '../../../app/scripts/lib/multichain/address'; @@ -13,13 +14,6 @@ import PermissionPageContainer from '../../components/app/permission-page-contai import { Box } from '../../components/component-library'; import SnapAuthorshipHeader from '../../components/app/snaps/snap-authorship-header/snap-authorship-header'; import PermissionConnectHeader from '../../components/app/permission-connect-header'; -import { - CaveatTypes, - RestrictedMethods, -} from '../../../shared/constants/permissions'; -// TODO: Remove restricted import -// eslint-disable-next-line import/no-restricted-paths -import { PermissionNames } from '../../../app/scripts/controllers/permissions'; import ChooseAccount from './choose-account'; import PermissionsRedirect from './redirect'; import SnapsConnect from './snaps/snaps-connect'; @@ -27,17 +21,15 @@ import SnapInstall from './snaps/snap-install'; import SnapUpdate from './snaps/snap-update'; import SnapResult from './snaps/snap-result'; import { ConnectPage } from './connect-page/connect-page'; +import { getRequestedSessionScopes } from './connect-page/utils'; const APPROVE_TIMEOUT = MILLISECOND * 1200; -function getDefaultSelectedAccounts(currentAddress, permissionsRequest) { - const permission = - permissionsRequest?.permissions?.[RestrictedMethods.eth_accounts]; - const requestedAccounts = permission?.caveats?.find( - (caveat) => caveat.type === CaveatTypes.restrictReturnedAccounts, - )?.value; +function getDefaultSelectedAccounts(currentAddress, permissions) { + const requestedSessionsScopes = getRequestedSessionScopes(permissions); + const requestedAccounts = getEthAccounts(requestedSessionsScopes); - if (requestedAccounts) { + if (requestedAccounts.length > 0) { return new Set( requestedAccounts .map((address) => address.toLowerCase()) @@ -50,9 +42,9 @@ function getDefaultSelectedAccounts(currentAddress, permissionsRequest) { return new Set(isEthAddress(currentAddress) ? [currentAddress] : []); } -function getRequestedChainIds(permissionsRequest) { - return permissionsRequest?.permissions?.[PermissionNames.permittedChains] - ?.caveats[0]?.value; +function getRequestedChainIds(permissions) { + const requestedSessionsScopes = getRequestedSessionScopes(permissions); + return getPermittedEthChainIds(requestedSessionsScopes); } export default class PermissionConnect extends Component { @@ -128,7 +120,7 @@ export default class PermissionConnect extends Component { redirecting: false, selectedAccountAddresses: getDefaultSelectedAccounts( this.props.currentAddress, - this.props.permissionsRequest, + this.props.permissionsRequest?.permissions, ), permissionsApproved: null, origin: this.props.origin, @@ -380,7 +372,7 @@ export default class PermissionConnect extends Component { this.cancelPermissionsRequest(requestId) } activeTabOrigin={this.state.origin} - request={permissionsRequest} + request={permissionsRequest || {}} permissionsRequestId={permissionsRequestId} approveConnection={this.approveConnection} /> @@ -403,7 +395,9 @@ export default class PermissionConnect extends Component { selectedAccounts={accounts.filter((account) => selectedAccountAddresses.has(account.address), )} - requestedChainIds={getRequestedChainIds(permissionsRequest)} + requestedChainIds={getRequestedChainIds( + permissionsRequest?.permissions, + )} targetSubjectMetadata={targetSubjectMetadata} history={history} connectPath={connectPath} diff --git a/ui/pages/permissions-connect/permissions-connect.container.js b/ui/pages/permissions-connect/permissions-connect.container.js index 075d14d172ef..d4c080c6be05 100644 --- a/ui/pages/permissions-connect/permissions-connect.container.js +++ b/ui/pages/permissions-connect/permissions-connect.container.js @@ -3,6 +3,7 @@ import { WALLET_SNAP_PERMISSION_KEY } from '@metamask/snaps-rpc-methods'; import { connect } from 'react-redux'; import PropTypes from 'prop-types'; import { isEvmAccountType } from '@metamask/keyring-api'; +import { Caip25EndowmentPermissionName } from '@metamask/multichain'; import { getAccountsWithLabels, getLastConnectedInfo, @@ -54,14 +55,16 @@ const mapStateToProps = (state, ownProps) => { (req) => req.metadata.id === permissionsRequestId, ); - const isRequestingAccounts = Boolean( - permissionsRequest?.permissions?.eth_accounts, - ); - - const { metadata = {} } = permissionsRequest || {}; + const { metadata = {}, isLegacySwitchEthereumChain } = + permissionsRequest || {}; const { origin } = metadata; const nativeCurrency = getNativeCurrency(state); + const isRequestingAccounts = Boolean( + permissionsRequest?.permissions?.[Caip25EndowmentPermissionName] && + !isLegacySwitchEthereumChain, + ); + const targetSubjectMetadata = getTargetSubjectMetadata(state, origin) ?? { name: getURLHostName(origin) || origin, origin, From c00289d7654c1e6178dbdd49142d36e06eb60f06 Mon Sep 17 00:00:00 2001 From: Mark Stacey Date: Wed, 29 Jan 2025 19:02:59 -0330 Subject: [PATCH 3/6] ci: Take DOM snapshots of all windows on failure (#29983) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## **Description** Our e2e test tooling takes a DOM snapshot on test failure to help with debugging. Unfortunately this is not overly useful right now because the driver is always set to the last window handle when this snapshot is taken, which is typically the offscreen document (at least on Chrome). Even on Firefox where there is no offscreen document, it may end up taking a snapshot of the wrong page when there are multiple open. The DOM snapshot step has been updated to take snapshots of all open pages, rather than just the "current" one. [![Open in GitHub Codespaces](/~https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/29983?quickstart=1) ## **Related issues** N/A ## **Manual testing steps** See the DOM snapshot artifacts in CircleCI upon failure ## **Screenshots/Recordings** N/A ## **Pre-merge author checklist** - [x] I've followed [MetaMask Contributor Docs](/~https://github.com/MetaMask/contributor-docs) and [MetaMask Extension Coding Standards](/~https://github.com/MetaMask/metamask-extension/blob/main/.github/guidelines/CODING_GUIDELINES.md). - [x] I've completed the PR template to the best of my ability - [x] I’ve included tests if applicable - [x] I’ve documented my code using [JSDoc](https://jsdoc.app/) format if applicable - [x] I’ve applied the right labels on the PR (see [labeling guidelines](/~https://github.com/MetaMask/metamask-extension/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. --- test/e2e/webdriver/driver.js | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/test/e2e/webdriver/driver.js b/test/e2e/webdriver/driver.js index 705ebb1b7f0d..580b9fa56968 100644 --- a/test/e2e/webdriver/driver.js +++ b/test/e2e/webdriver/driver.js @@ -1227,12 +1227,13 @@ class Driver { const filepathBase = `${artifactDir(title)}/test-failure`; await fs.mkdir(artifactDir(title), { recursive: true }); + + const windowHandles = await this.driver.getAllWindowHandles(); // On occasion there may be a bug in the offscreen document which does // not render visibly to the user and therefore no screenshot can be // taken. In this case we skip the screenshot and log the error. try { // If there's more than one tab open, we want to iterate through all of them and take a screenshot with a unique name - const windowHandles = await this.driver.getAllWindowHandles(); for (const handle of windowHandles) { await this.driver.switchTo().window(handle); const windowTitle = await this.driver.getTitle(); @@ -1252,12 +1253,24 @@ class Driver { } catch (e) { console.error('Failed to take screenshot', e); } - const htmlSource = await this.driver.getPageSource(); - await fs.writeFile(`${filepathBase}-dom.html`, htmlSource); + + try { + for (const handle of windowHandles) { + const windowNumber = windowHandles.indexOf(handle) + 1; + await this.driver.switchTo().window(handle); + + const htmlSource = await this.driver.getPageSource(); + await fs.writeFile( + `${filepathBase}-dom-${windowNumber}.html`, + htmlSource, + ); + } + } catch (e) { + console.error('Failed to capture DOM snapshot', e); + } // We want to take a state snapshot of the app if possible, this is useful for debugging try { - const windowHandles = await this.driver.getAllWindowHandles(); for (const handle of windowHandles) { await this.driver.switchTo().window(handle); const uiState = await this.driver.executeScript( From 180641ba02dfa5a97e489f1ba485535292462fc0 Mon Sep 17 00:00:00 2001 From: OGPoyraz Date: Thu, 30 Jan 2025 00:29:44 +0100 Subject: [PATCH 4/6] fix: Fix send flow max value issue (#29960) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## **Description** This PR aims to fix send max value issue for redesigned confirmations. [![Open in GitHub Codespaces](/~https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/29960?quickstart=1) ## **Related issues** Fixes: /~https://github.com/MetaMask/metamask-extension/issues/29903 ## **Manual testing steps** 1. Initiate a native send flow from wallet 2. Click max in the amount picker 3. Go to next step (confirmation) 4. See that value changes when gas is changed ## **Screenshots/Recordings** /~https://github.com/user-attachments/assets/14a85e29-b7d5-4bfe-b013-25f1954103cc ### **Before** ### **After** ## **Pre-merge author checklist** - [X] I've followed [MetaMask Contributor Docs](/~https://github.com/MetaMask/contributor-docs) and [MetaMask Extension Coding Standards](/~https://github.com/MetaMask/metamask-extension/blob/main/.github/guidelines/CODING_GUIDELINES.md). - [X] I've completed the PR template to the best of my ability - [X] I’ve included tests if applicable - [X] I’ve documented my code using [JSDoc](https://jsdoc.app/) format if applicable - [X] I’ve applied the right labels on the PR (see [labeling guidelines](/~https://github.com/MetaMask/metamask-extension/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. --------- Co-authored-by: Harika <153644847+hjetpoluru@users.noreply.github.com> --- test/data/mock-state.json | 3 +- .../info/hooks/useFeeCalculations.test.ts | 4 + .../confirm/info/hooks/useFeeCalculations.ts | 4 +- .../info/hooks/useMaxValueRefresher.test.ts | 83 +++++++++++++++++++ .../info/hooks/useMaxValueRefresher.ts | 46 ++++++++++ .../info/native-transfer/native-transfer.tsx | 4 +- ui/selectors/confirm-transaction.js | 7 ++ 7 files changed, 148 insertions(+), 3 deletions(-) create mode 100644 ui/pages/confirmations/components/confirm/info/hooks/useMaxValueRefresher.test.ts create mode 100644 ui/pages/confirmations/components/confirm/info/hooks/useMaxValueRefresher.ts diff --git a/test/data/mock-state.json b/test/data/mock-state.json index 67e304b1e2cf..b17435c72514 100644 --- a/test/data/mock-state.json +++ b/test/data/mock-state.json @@ -44,7 +44,8 @@ "gas": "0x153e2", "value": "0x0" } - } + }, + "maxValueMode": {} }, "history": { "mostRecentOverviewPage": "/mostRecentOverviewPage" diff --git a/ui/pages/confirmations/components/confirm/info/hooks/useFeeCalculations.test.ts b/ui/pages/confirmations/components/confirm/info/hooks/useFeeCalculations.test.ts index 894c01c612a6..124b80d6c6ca 100644 --- a/ui/pages/confirmations/components/confirm/info/hooks/useFeeCalculations.test.ts +++ b/ui/pages/confirmations/components/confirm/info/hooks/useFeeCalculations.test.ts @@ -33,6 +33,7 @@ describe('useFeeCalculations', () => { "maxFeeFiat": "< $0.01", "maxFeeFiatWith18SignificantDigits": "0", "maxFeeNative": "0 ETH", + "preciseNativeFeeInHex": "0x0", } `); }); @@ -61,6 +62,7 @@ describe('useFeeCalculations', () => { "maxFeeFiat": "$0.07", "maxFeeFiatWith18SignificantDigits": null, "maxFeeNative": "0.0001 ETH", + "preciseNativeFeeInHex": "0x3be226d2d900", } `); }); @@ -92,6 +94,7 @@ describe('useFeeCalculations', () => { "maxFeeFiat": "$0.07", "maxFeeFiatWith18SignificantDigits": null, "maxFeeNative": "0.0001 ETH", + "preciseNativeFeeInHex": "0x364ba3e2d900", } `); }); @@ -122,6 +125,7 @@ describe('useFeeCalculations', () => { "maxFeeFiat": "$0.07", "maxFeeFiatWith18SignificantDigits": null, "maxFeeNative": "0.0001 ETH", + "preciseNativeFeeInHex": "0x103be226d2d900", } `); }); diff --git a/ui/pages/confirmations/components/confirm/info/hooks/useFeeCalculations.ts b/ui/pages/confirmations/components/confirm/info/hooks/useFeeCalculations.ts index 2b57ba03e8fb..6842823a4671 100644 --- a/ui/pages/confirmations/components/confirm/info/hooks/useFeeCalculations.ts +++ b/ui/pages/confirmations/components/confirm/info/hooks/useFeeCalculations.ts @@ -1,6 +1,6 @@ import { GasFeeEstimates } from '@metamask/gas-fee-controller'; import { TransactionMeta } from '@metamask/transaction-controller'; -import { Hex } from '@metamask/utils'; +import { add0x, Hex } from '@metamask/utils'; import { useCallback, useMemo } from 'react'; import { useSelector } from 'react-redux'; import { EtherDenomination } from '../../../../../../../shared/constants/common'; @@ -80,6 +80,7 @@ export function useFeeCalculations(transactionMeta: TransactionMeta) { currentCurrencyFee, currentCurrencyFeeWith18SignificantDigits, nativeCurrencyFee, + preciseNativeFeeInHex: add0x(hexFee), }; }, [conversionRate, currentCurrency, fiatFormatter], @@ -188,5 +189,6 @@ export function useFeeCalculations(transactionMeta: TransactionMeta) { maxFeeFiat, maxFeeFiatWith18SignificantDigits, maxFeeNative, + preciseNativeFeeInHex: estimatedFees.preciseNativeFeeInHex, }; } diff --git a/ui/pages/confirmations/components/confirm/info/hooks/useMaxValueRefresher.test.ts b/ui/pages/confirmations/components/confirm/info/hooks/useMaxValueRefresher.test.ts new file mode 100644 index 000000000000..0515d9567de4 --- /dev/null +++ b/ui/pages/confirmations/components/confirm/info/hooks/useMaxValueRefresher.test.ts @@ -0,0 +1,83 @@ +import { renderHook } from '@testing-library/react-hooks'; +import { useDispatch, useSelector } from 'react-redux'; +import { TransactionType } from '@metamask/transaction-controller'; +import { updateEditableParams } from '../../../../../../store/actions'; +import { useConfirmContext } from '../../../../context/confirm'; +import { useFeeCalculations } from './useFeeCalculations'; +import { useMaxValueRefresher } from './useMaxValueRefresher'; + +jest.mock('react-redux', () => ({ + useDispatch: jest.fn(), + useSelector: jest.fn(), +})); + +jest.mock('../../../../../../store/actions', () => ({ + updateEditableParams: jest.fn(), +})); + +jest.mock('../../../../context/confirm', () => ({ + useConfirmContext: jest.fn(), +})); + +jest.mock('../hooks/useFeeCalculations', () => ({ + useFeeCalculations: jest.fn(), +})); + +describe('useMaxValueRefresher', () => { + const dispatchMock = jest.fn(); + const simpleSendTransactionMetaMock = { + id: '1', + type: TransactionType.simpleSend, + }; + + beforeEach(() => { + (useDispatch as jest.Mock).mockReturnValue(dispatchMock); + (useConfirmContext as jest.Mock).mockReturnValue({ + currentConfirmation: simpleSendTransactionMetaMock, + }); + jest.clearAllMocks(); + }); + + it('updates transaction value in max amount mode for simpleSend', () => { + const balance = '0x111'; + const preciseNativeFeeInHex = '0x001'; + const newValue = '0x110'; + + (useSelector as jest.Mock) + .mockReturnValueOnce(balance) + .mockReturnValueOnce(true); + + (useFeeCalculations as jest.Mock).mockReturnValue({ + preciseNativeFeeInHex, + }); + + renderHook(() => useMaxValueRefresher()); + + expect(dispatchMock).toHaveBeenCalledWith( + updateEditableParams(simpleSendTransactionMetaMock.id, { + value: newValue, + }), + ); + }); + + it('does not update transaction value if not in max amount mode', () => { + (useSelector as jest.Mock) + .mockReturnValueOnce('0x111') + .mockReturnValueOnce(false); + + renderHook(() => useMaxValueRefresher()); + + expect(dispatchMock).not.toHaveBeenCalled(); + }); + + it('does not update transaction value if transaction type is not simpleSend', () => { + const transactionMeta = { id: '1', type: 'otherType' }; + (useConfirmContext as jest.Mock).mockReturnValue({ + currentConfirmation: transactionMeta, + }); + + renderHook(() => useMaxValueRefresher()); + + expect(dispatchMock).not.toHaveBeenCalled(); + }); +}); diff --git a/ui/pages/confirmations/components/confirm/info/hooks/useMaxValueRefresher.ts b/ui/pages/confirmations/components/confirm/info/hooks/useMaxValueRefresher.ts new file mode 100644 index 000000000000..ea39f433e412 --- /dev/null +++ b/ui/pages/confirmations/components/confirm/info/hooks/useMaxValueRefresher.ts @@ -0,0 +1,46 @@ +import { useEffect } from 'react'; +import { useDispatch, useSelector } from 'react-redux'; +import { + TransactionType, + type TransactionMeta, +} from '@metamask/transaction-controller'; +import { add0x } from '@metamask/utils'; + +import { + getSelectedAccountCachedBalance, + selectMaxValueModeForTransaction, +} from '../../../../../../selectors'; +import { subtractHexes } from '../../../../../../../shared/modules/conversion.utils'; +import { updateEditableParams } from '../../../../../../store/actions'; +import { useConfirmContext } from '../../../../context/confirm'; +import { useFeeCalculations } from './useFeeCalculations'; + +// This hook is used to refresh the max value of the transaction +// when the user is in max amount mode only for the transaction type simpleSend +// It subtracts the native fee from the balance and updates the value of the transaction +export const useMaxValueRefresher = () => { + const { currentConfirmation: transactionMeta } = + useConfirmContext(); + const dispatch = useDispatch(); + const { preciseNativeFeeInHex } = useFeeCalculations(transactionMeta); + const balance = useSelector(getSelectedAccountCachedBalance); + const isMaxAmountMode = useSelector((state) => + selectMaxValueModeForTransaction(state, transactionMeta?.id), + ); + + useEffect(() => { + if ( + !isMaxAmountMode || + transactionMeta.type !== TransactionType.simpleSend + ) { + return; + } + + const newValue = subtractHexes(balance, preciseNativeFeeInHex); + const newValueInHex = add0x(newValue); + + dispatch( + updateEditableParams(transactionMeta.id, { value: newValueInHex }), + ); + }, [isMaxAmountMode, balance, preciseNativeFeeInHex]); +}; diff --git a/ui/pages/confirmations/components/confirm/info/native-transfer/native-transfer.tsx b/ui/pages/confirmations/components/confirm/info/native-transfer/native-transfer.tsx index cf321336be0c..87f2df11741d 100644 --- a/ui/pages/confirmations/components/confirm/info/native-transfer/native-transfer.tsx +++ b/ui/pages/confirmations/components/confirm/info/native-transfer/native-transfer.tsx @@ -1,5 +1,5 @@ -import { TransactionMeta } from '@metamask/transaction-controller'; import React from 'react'; +import { TransactionMeta } from '@metamask/transaction-controller'; import { useConfirmContext } from '../../../../context/confirm'; import { SimulationDetails } from '../../../simulation-details'; import { AdvancedDetails } from '../shared/advanced-details/advanced-details'; @@ -7,10 +7,12 @@ import { GasFeesSection } from '../shared/gas-fees-section/gas-fees-section'; import NativeSendHeading from '../shared/native-send-heading/native-send-heading'; import { TokenDetailsSection } from '../token-transfer/token-details-section'; import { TransactionFlowSection } from '../token-transfer/transaction-flow-section'; +import { useMaxValueRefresher } from '../hooks/useMaxValueRefresher'; const NativeTransferInfo = () => { const { currentConfirmation: transactionMeta } = useConfirmContext(); + useMaxValueRefresher(); const isWalletInitiated = transactionMeta.origin === 'metamask'; diff --git a/ui/selectors/confirm-transaction.js b/ui/selectors/confirm-transaction.js index c6c2d706b237..7218f55ba106 100644 --- a/ui/selectors/confirm-transaction.js +++ b/ui/selectors/confirm-transaction.js @@ -368,3 +368,10 @@ export const selectTransactionValue = createSelector( (isMaxValueEnabled, maxValue, transactionMetadata) => isMaxValueEnabled ? maxValue : transactionMetadata?.txParams?.value, ); + +const maxValueModeSelector = (state) => state.confirmTransaction.maxValueMode; + +export function selectMaxValueModeForTransaction(state, transactionId) { + const maxValueModes = maxValueModeSelector(state); + return maxValueModes[transactionId]; +} From 642fb082d7367d7ae9356f66d0971eabe3e7e803 Mon Sep 17 00:00:00 2001 From: weizman Date: Thu, 30 Jan 2025 10:48:16 +0200 Subject: [PATCH 5/6] chore(lavamoat/lavadome): bump to v0.0.20 (#29691) * /~https://github.com/LavaMoat/LavaDome/pull/59 * /~https://github.com/LavaMoat/LavaDome/pull/38 * /~https://github.com/LavaMoat/LavaDome/pull/49 * /~https://github.com/LavaMoat/LavaDome/pull/42 * /~https://github.com/LavaMoat/snow/pull/162 * /~https://github.com/LavaMoat/snow/pull/161 --- lavamoat/browserify/beta/policy.json | 7 ++++- lavamoat/browserify/flask/policy.json | 7 ++++- lavamoat/browserify/main/policy.json | 7 ++++- lavamoat/browserify/mmi/policy.json | 7 ++++- package.json | 6 ++-- yarn.lock | 43 +++++++++++---------------- 6 files changed, 45 insertions(+), 32 deletions(-) diff --git a/lavamoat/browserify/beta/policy.json b/lavamoat/browserify/beta/policy.json index 35332269cbd5..b3fb7d6af7ad 100644 --- a/lavamoat/browserify/beta/policy.json +++ b/lavamoat/browserify/beta/policy.json @@ -672,9 +672,14 @@ "Document.prototype": true, "DocumentFragment.prototype": true, "Element.prototype": true, + "Event.prototype": true, + "EventTarget.prototype": true, + "NavigateEvent.prototype": true, + "NavigationDestination.prototype": true, "Node.prototype": true, "console.warn": true, - "document": true + "document": true, + "navigation": true }, "packages": { "react": true diff --git a/lavamoat/browserify/flask/policy.json b/lavamoat/browserify/flask/policy.json index 79934423e13c..9e1c35538925 100644 --- a/lavamoat/browserify/flask/policy.json +++ b/lavamoat/browserify/flask/policy.json @@ -672,9 +672,14 @@ "Document.prototype": true, "DocumentFragment.prototype": true, "Element.prototype": true, + "Event.prototype": true, + "EventTarget.prototype": true, + "NavigateEvent.prototype": true, + "NavigationDestination.prototype": true, "Node.prototype": true, "console.warn": true, - "document": true + "document": true, + "navigation": true }, "packages": { "react": true diff --git a/lavamoat/browserify/main/policy.json b/lavamoat/browserify/main/policy.json index 35332269cbd5..b3fb7d6af7ad 100644 --- a/lavamoat/browserify/main/policy.json +++ b/lavamoat/browserify/main/policy.json @@ -672,9 +672,14 @@ "Document.prototype": true, "DocumentFragment.prototype": true, "Element.prototype": true, + "Event.prototype": true, + "EventTarget.prototype": true, + "NavigateEvent.prototype": true, + "NavigationDestination.prototype": true, "Node.prototype": true, "console.warn": true, - "document": true + "document": true, + "navigation": true }, "packages": { "react": true diff --git a/lavamoat/browserify/mmi/policy.json b/lavamoat/browserify/mmi/policy.json index 9e3248e669d0..c8c493729b65 100644 --- a/lavamoat/browserify/mmi/policy.json +++ b/lavamoat/browserify/mmi/policy.json @@ -672,9 +672,14 @@ "Document.prototype": true, "DocumentFragment.prototype": true, "Element.prototype": true, + "Event.prototype": true, + "EventTarget.prototype": true, + "NavigateEvent.prototype": true, + "NavigationDestination.prototype": true, "Node.prototype": true, "console.warn": true, - "document": true + "document": true, + "navigation": true }, "packages": { "react": true diff --git a/package.json b/package.json index 1d97361a15b7..89643a990c34 100644 --- a/package.json +++ b/package.json @@ -272,8 +272,8 @@ "@fortawesome/fontawesome-free": "^5.13.0", "@keystonehq/bc-ur-registry-eth": "^0.19.1", "@keystonehq/metamask-airgapped-keyring": "^0.14.1", - "@lavamoat/lavadome-react": "0.0.17", - "@lavamoat/snow": "^2.0.2", + "@lavamoat/lavadome-react": "0.0.20", + "@lavamoat/snow": "^2.0.3", "@material-ui/core": "^4.11.0", "@metamask-institutional/custody-controller": "^0.3.0", "@metamask-institutional/custody-keyring": "^2.1.1", @@ -455,7 +455,7 @@ "@babel/register": "^7.25.9", "@jest/globals": "^29.7.0", "@lavamoat/allow-scripts": "^3.3.1", - "@lavamoat/lavadome-core": "0.0.10", + "@lavamoat/lavadome-core": "0.0.20", "@lavamoat/lavapack": "^7.0.5", "@lgbot/madge": "^6.2.0", "@lydell/node-pty": "^1.0.1", diff --git a/yarn.lock b/yarn.lock index 9ca2cebd5b66..381117dbec76 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4251,32 +4251,25 @@ __metadata: languageName: node linkType: hard -"@lavamoat/lavadome-core@npm:0.0.10": - version: 0.0.10 - resolution: "@lavamoat/lavadome-core@npm:0.0.10" - checksum: 10/79011976d5643112b3930210d56f933a575102ff200338c1436c102f5f0f3f6efea9cd44ef673f5859e0832980b83ad7a2162d95db299f5877e8a408cf16bd6c - languageName: node - linkType: hard - -"@lavamoat/lavadome-core@npm:^0.0.17": - version: 0.0.17 - resolution: "@lavamoat/lavadome-core@npm:0.0.17" +"@lavamoat/lavadome-core@npm:0.0.20, @lavamoat/lavadome-core@npm:^0.0.20": + version: 0.0.20 + resolution: "@lavamoat/lavadome-core@npm:0.0.20" dependencies: "@lavamoat/preinstall-always-fail": "npm:^2.0.0" - checksum: 10/9628bc4662a495c4213d82b4651642670558e29e6d614cd92bcc4eba9356834030216309adabad0ad7870bb770206bde05d584e605297486ab9bab53e83e7ecb + checksum: 10/3940e4ba5ea03a9313cdc4bbb757632f586cb4f3c42dfe67a5c5073efc80f48cc9c6f2c6fc2142717a66489b29b4ffde9edb31bea7ac2e8cf3a4decdf8f2dd06 languageName: node linkType: hard -"@lavamoat/lavadome-react@npm:0.0.17": - version: 0.0.17 - resolution: "@lavamoat/lavadome-react@npm:0.0.17" +"@lavamoat/lavadome-react@npm:0.0.20": + version: 0.0.20 + resolution: "@lavamoat/lavadome-react@npm:0.0.20" dependencies: - "@lavamoat/lavadome-core": "npm:^0.0.17" + "@lavamoat/lavadome-core": "npm:^0.0.20" "@lavamoat/preinstall-always-fail": "npm:^2.0.0" peerDependencies: - react: ^16.12.0 - react-dom: ^16.12.0 - checksum: 10/62c1047115f7b566af87abb1c21364236af9d79ad655f280ac5424c3e839efd9f120cbf042123a88e59c7b98660ad6ea0e9a54e9143f853b372419e5efa92835 + react: ^16.12.0 || ^17.0.2 || ^18.2.0 + react-dom: ^16.12.0 || ^17.0.2 || ^18.2.0 + checksum: 10/acef4a279d6897272562f293e7b755581ea4d300b23b2341e3818d27236de47aa4a7c7615d3a3414dc2c6e62c7fb1e345e5d07f6d1998a4e03cda508d7ba1473 languageName: node linkType: hard @@ -4303,10 +4296,10 @@ __metadata: languageName: node linkType: hard -"@lavamoat/snow@npm:^2.0.2": - version: 2.0.2 - resolution: "@lavamoat/snow@npm:2.0.2" - checksum: 10/613d4b7f42a80fb0c447f124ef0b7497afc7e5a5f81edd961975e14f35e9cfcbde04f649155f5d2fdcc334d5a190246ae90e23d53ca4e126e672e229c26895ae +"@lavamoat/snow@npm:^2.0.3": + version: 2.0.3 + resolution: "@lavamoat/snow@npm:2.0.3" + checksum: 10/aace8f93fe98f357ebffbcee88d20129a541b566208f7370e79eaba9242a9bbde571d331e802bf8d58193d723d0c1ed6d7783deca46e31d96b7fd1339e1d42f9 languageName: node linkType: hard @@ -26656,10 +26649,10 @@ __metadata: "@keystonehq/bc-ur-registry-eth": "npm:^0.19.1" "@keystonehq/metamask-airgapped-keyring": "npm:^0.14.1" "@lavamoat/allow-scripts": "npm:^3.3.1" - "@lavamoat/lavadome-core": "npm:0.0.10" - "@lavamoat/lavadome-react": "npm:0.0.17" + "@lavamoat/lavadome-core": "npm:0.0.20" + "@lavamoat/lavadome-react": "npm:0.0.20" "@lavamoat/lavapack": "npm:^7.0.5" - "@lavamoat/snow": "npm:^2.0.2" + "@lavamoat/snow": "npm:^2.0.3" "@lgbot/madge": "npm:^6.2.0" "@lydell/node-pty": "npm:^1.0.1" "@material-ui/core": "npm:^4.11.0" From 6ca9116e9ef45ddfc5901db101bab188a8cb04e0 Mon Sep 17 00:00:00 2001 From: Norbert Elter <72046715+itsyoboieltr@users.noreply.github.com> Date: Thu, 30 Jan 2025 12:50:15 +0400 Subject: [PATCH 6/6] fix: storybook-deployment (#29984) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## **Description** [![Open in GitHub Codespaces](/~https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/29984?quickstart=1) Fixes a bug in the deployment step of the newly introduced build-storybook workflow. ## **Related issues** Fixes: https://consensys.slack.com/archives/CTQAGKY5V/p1738162973016659 ## **Manual testing steps** 1. Remove the branch filter for testing, and see the changes in /~https://github.com/MetaMask/metamask-storybook ## **Screenshots/Recordings** Not applicable ## **Pre-merge author checklist** - [x] I've followed [MetaMask Contributor Docs](/~https://github.com/MetaMask/contributor-docs) and [MetaMask Extension Coding Standards](/~https://github.com/MetaMask/metamask-extension/blob/main/.github/guidelines/CODING_GUIDELINES.md). - [x] I've completed the PR template to the best of my ability - [x] I’ve included tests if applicable - [x] I’ve documented my code using [JSDoc](https://jsdoc.app/) format if applicable - [x] I’ve applied the right labels on the PR (see [labeling guidelines](/~https://github.com/MetaMask/metamask-extension/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. --- .github/workflows/build-storybook.yml | 5 ++++- .github/workflows/main.yml | 2 ++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build-storybook.yml b/.github/workflows/build-storybook.yml index fc3fa3bdda28..e28b32556dbe 100644 --- a/.github/workflows/build-storybook.yml +++ b/.github/workflows/build-storybook.yml @@ -2,6 +2,9 @@ name: Build storybook on: workflow_call: + secrets: + STORYBOOK_TOKEN: + required: true jobs: build-storybook: @@ -30,5 +33,5 @@ jobs: # For a `push` event, the branch is `github.ref_name`. if: ${{ (github.head_ref || github.ref_name) == 'main' }} run: | - git remote add storybook git@github.com:MetaMask/metamask-storybook.git + git remote add storybook https://${{ secrets.STORYBOOK_TOKEN }}@github.com/MetaMask/metamask-storybook.git yarn storybook:deploy diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 94b8256c1ddd..eaa85079a7f3 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -82,6 +82,8 @@ jobs: build-storybook: name: Build storybook uses: ./.github/workflows/build-storybook.yml + secrets: + STORYBOOK_TOKEN: ${{ secrets.STORYBOOK_TOKEN }} permissions: contents: read # id-token permission is required for uploading to s3