diff --git a/app/scripts/controller-init/controller-list.ts b/app/scripts/controller-init/controller-list.ts index baac067eefb2..5f5e74bab628 100644 --- a/app/scripts/controller-init/controller-list.ts +++ b/app/scripts/controller-init/controller-list.ts @@ -46,6 +46,7 @@ import { RemoteFeatureFlagController } from '@metamask/remote-feature-flag-contr import { AccountTreeController } from '@metamask/account-tree-controller'; import { SeedlessOnboardingController } from '@metamask/seedless-onboarding-controller'; import { EncryptionKey } from '@metamask/browser-passworder'; +import { GatorPermissionsController } from '@metamask/gator-permissions-controller'; import { ShieldController } from '@metamask/shield-controller'; import OnboardingController from '../controllers/onboarding'; import { PreferencesController } from '../controllers/preferences-controller'; @@ -65,6 +66,7 @@ export type Controller = | DeFiPositionsController | ExecutionService | GasFeeController + | GatorPermissionsController | JsonSnapsRegistry | KeyringController | MetaMetricsController @@ -114,6 +116,7 @@ export type ControllerFlatState = AccountsController['state'] & DeFiPositionsController['state'] & DelegationController['state'] & GasFeeController['state'] & + GatorPermissionsController['state'] & JsonSnapsRegistry['state'] & KeyringController['state'] & MultichainAssetsController['state'] & diff --git a/app/scripts/controller-init/gator-permissions/gator-permissions-controller-init.test.ts b/app/scripts/controller-init/gator-permissions/gator-permissions-controller-init.test.ts new file mode 100644 index 000000000000..f2a7bcc34da4 --- /dev/null +++ b/app/scripts/controller-init/gator-permissions/gator-permissions-controller-init.test.ts @@ -0,0 +1,161 @@ +import { GatorPermissionsController } from '@metamask/gator-permissions-controller'; +import { Messenger } from '@metamask/base-controller'; +import { buildControllerInitRequestMock } from '../test/utils'; +import type { ControllerInitRequest } from '../types'; +import { isGatorPermissionsFeatureEnabled } from '../../../../shared/modules/environment'; +import { + getGatorPermissionsControllerMessenger, + GatorPermissionsControllerMessenger, +} from '../messengers/gator-permissions'; +import { GatorPermissionsControllerInit } from './gator-permissions-controller-init'; + +jest.mock('@metamask/gator-permissions-controller'); +jest.mock('../../../../shared/modules/environment'); + +function buildInitRequestMock(): jest.Mocked< + ControllerInitRequest +> { + const baseControllerMessenger = new Messenger(); + + return { + ...buildControllerInitRequestMock(), + controllerMessenger: getGatorPermissionsControllerMessenger( + baseControllerMessenger, + ), + initMessenger: undefined, + }; +} + +describe('GatorPermissionsControllerInit', () => { + const MOCK_GATOR_PERMISSIONS_PROVIDER_SNAP_ID = 'npm:mock-snap-id'; + const GatorPermissionsControllerClassMock = jest.mocked( + GatorPermissionsController, + ); + const originalGatorPermissionProviderSnapId = + process.env.GATOR_PERMISSIONS_PROVIDER_SNAP_ID; + + beforeEach(() => { + jest.resetAllMocks(); + jest.mocked(isGatorPermissionsFeatureEnabled).mockReturnValue(true); + process.env.GATOR_PERMISSIONS_PROVIDER_SNAP_ID = + MOCK_GATOR_PERMISSIONS_PROVIDER_SNAP_ID; + }); + + afterEach(() => { + if (originalGatorPermissionProviderSnapId) { + process.env.GATOR_PERMISSIONS_PROVIDER_SNAP_ID = + originalGatorPermissionProviderSnapId; + } else { + delete process.env.GATOR_PERMISSIONS_PROVIDER_SNAP_ID; + } + }); + + it('returns controller instance', () => { + const requestMock = buildInitRequestMock(); + expect( + GatorPermissionsControllerInit(requestMock).controller, + ).toBeInstanceOf(GatorPermissionsController); + }); + + it('initializes with correct messenger and state(gator permissions feature enabled)', () => { + const requestMock = buildInitRequestMock(); + jest.mocked(isGatorPermissionsFeatureEnabled).mockReturnValue(true); + GatorPermissionsControllerInit(requestMock); + + expect(GatorPermissionsControllerClassMock).toHaveBeenCalledWith({ + messenger: requestMock.controllerMessenger, + state: { + isGatorPermissionsEnabled: true, + gatorPermissionsProviderSnapId: MOCK_GATOR_PERMISSIONS_PROVIDER_SNAP_ID, + ...requestMock.persistedState.GatorPermissionsController, + }, + }); + }); + + it('initializes with correct messenger and state(gator permissions feature disabled)', () => { + const requestMock = buildInitRequestMock(); + jest.mocked(isGatorPermissionsFeatureEnabled).mockReturnValue(false); + GatorPermissionsControllerInit(requestMock); + + expect(GatorPermissionsControllerClassMock).toHaveBeenCalledWith({ + messenger: requestMock.controllerMessenger, + state: { + isGatorPermissionsEnabled: false, + gatorPermissionsProviderSnapId: MOCK_GATOR_PERMISSIONS_PROVIDER_SNAP_ID, + ...requestMock.persistedState.GatorPermissionsController, + }, + }); + }); + + it('returns correct API methods', () => { + const requestMock = buildInitRequestMock(); + const result = GatorPermissionsControllerInit(requestMock); + + expect(result.api).toEqual({ + fetchAndUpdateGatorPermissions: expect.any(Function), + }); + }); + + it('handles undefined persistedState.GatorPermissionsController', () => { + const requestMock = buildInitRequestMock(); + requestMock.persistedState.GatorPermissionsController = undefined; + jest.mocked(isGatorPermissionsFeatureEnabled).mockReturnValue(true); + + GatorPermissionsControllerInit(requestMock); + + expect(GatorPermissionsControllerClassMock).toHaveBeenCalledWith({ + messenger: requestMock.controllerMessenger, + state: { + isGatorPermissionsEnabled: true, + gatorPermissionsProviderSnapId: MOCK_GATOR_PERMISSIONS_PROVIDER_SNAP_ID, + }, + }); + }); + + it('resolves the default when GATOR_PERMISSIONS_PROVIDER_SNAP_ID is not specified', () => { + const requestMock = buildInitRequestMock(); + + delete process.env.GATOR_PERMISSIONS_PROVIDER_SNAP_ID; + + GatorPermissionsControllerInit(requestMock); + + expect(GatorPermissionsControllerClassMock).toHaveBeenCalledWith({ + messenger: requestMock.controllerMessenger, + state: { + isGatorPermissionsEnabled: true, + }, + }); + + const calledWithState = + GatorPermissionsControllerClassMock.mock.calls[0][0].state; + + expect(calledWithState).toEqual({ + isGatorPermissionsEnabled: true, + }); + + // GatorPermissionsController requires that the key does not exist if the snap id is not specified + expect( + Object.prototype.hasOwnProperty.call( + calledWithState, + 'gatorPermissionsProviderSnapId', + ), + ).toBe(false); + }); + + describe('GATOR_PERMISSIONS_PROVIDER_SNAP_ID incorrectly specified', () => { + ['', ' ', 'invalid-snap-id'].forEach((invalidSnapId) => { + it(`throws when provided invalid GATOR_PERMISSIONS_PROVIDER_SNAP_ID: ${invalidSnapId}`, () => { + const requestMock = buildInitRequestMock(); + + process.env.GATOR_PERMISSIONS_PROVIDER_SNAP_ID = + invalidSnapId as string; + + expect(() => GatorPermissionsControllerInit(requestMock)).toThrow( + 'GATOR_PERMISSIONS_PROVIDER_SNAP_ID must be set to a valid snap id', + ); + + expect(GatorPermissionsControllerClassMock).not.toHaveBeenCalled(); + }); + }); + }); +}); diff --git a/app/scripts/controller-init/gator-permissions/gator-permissions-controller-init.ts b/app/scripts/controller-init/gator-permissions/gator-permissions-controller-init.ts new file mode 100644 index 000000000000..6b59ed975abb --- /dev/null +++ b/app/scripts/controller-init/gator-permissions/gator-permissions-controller-init.ts @@ -0,0 +1,64 @@ +import { + GatorPermissionsController, + GatorPermissionsControllerState, +} from '@metamask/gator-permissions-controller'; +import { assertIsValidSnapId } from '@metamask/snaps-utils'; +import { ControllerInitFunction } from '../types'; +import { isGatorPermissionsFeatureEnabled } from '../../../../shared/modules/environment'; +import { GatorPermissionsControllerMessenger } from '../messengers/gator-permissions'; + +const generateDefaultGatorPermissionsControllerState = + (): Partial => { + const gatorPermissionsProviderSnapId = + process.env.GATOR_PERMISSIONS_PROVIDER_SNAP_ID; + + // if GATOR_PERMISSIONS_PROVIDER_SNAP_ID is not specified, GatorPermissionsController will initialize it's default + if (gatorPermissionsProviderSnapId !== undefined) { + try { + assertIsValidSnapId(gatorPermissionsProviderSnapId); + } catch (error) { + throw new Error( + 'GATOR_PERMISSIONS_PROVIDER_SNAP_ID must be set to a valid snap id', + { + cause: error, + }, + ); + } + } + + const isGatorPermissionsEnabled = isGatorPermissionsFeatureEnabled(); + + const state: Partial = { + isGatorPermissionsEnabled, + }; + + if (gatorPermissionsProviderSnapId) { + state.gatorPermissionsProviderSnapId = gatorPermissionsProviderSnapId; + } + + return state; + }; + +export const GatorPermissionsControllerInit: ControllerInitFunction< + GatorPermissionsController, + GatorPermissionsControllerMessenger +> = ({ controllerMessenger, persistedState }) => { + const controller = new GatorPermissionsController({ + // Type mismatch due to different BaseController versions, GatorPermissionsController uses 8.3.0 while extension uses 8.2.0. + // We can remove once extension BaseController version is updated to 8.3.0. + // eslint-disable-next-line @typescript-eslint/no-explicit-any + messenger: controllerMessenger as any, + state: { + ...generateDefaultGatorPermissionsControllerState(), + ...persistedState.GatorPermissionsController, + }, + }); + + return { + controller, + api: { + fetchAndUpdateGatorPermissions: + controller.fetchAndUpdateGatorPermissions.bind(controller), + }, + }; +}; diff --git a/app/scripts/controller-init/messengers/gator-permissions/gator-permissions-controller-messenger.test.ts b/app/scripts/controller-init/messengers/gator-permissions/gator-permissions-controller-messenger.test.ts new file mode 100644 index 000000000000..f3258505c71a --- /dev/null +++ b/app/scripts/controller-init/messengers/gator-permissions/gator-permissions-controller-messenger.test.ts @@ -0,0 +1,14 @@ +import { Messenger, RestrictedMessenger } from '@metamask/base-controller'; +import { getGatorPermissionsControllerMessenger } from './gator-permissions-controller-messenger'; + +describe('getGatorPermissionsControllerMessenger', () => { + it('returns a restricted messenger', () => { + const messenger = new Messenger(); + const gatorPermissionsControllerMessenger = + getGatorPermissionsControllerMessenger(messenger); + + expect(gatorPermissionsControllerMessenger).toBeInstanceOf( + RestrictedMessenger, + ); + }); +}); diff --git a/app/scripts/controller-init/messengers/gator-permissions/gator-permissions-controller-messenger.ts b/app/scripts/controller-init/messengers/gator-permissions/gator-permissions-controller-messenger.ts new file mode 100644 index 000000000000..0fe2bf2534cf --- /dev/null +++ b/app/scripts/controller-init/messengers/gator-permissions/gator-permissions-controller-messenger.ts @@ -0,0 +1,27 @@ +import { Messenger } from '@metamask/base-controller'; +import { GatorPermissionsControllerStateChangeEvent } from '@metamask/gator-permissions-controller'; +import { HandleSnapRequest, HasSnap } from '@metamask/snaps-controllers'; + +export type GatorPermissionsControllerMessenger = ReturnType< + typeof getGatorPermissionsControllerMessenger +>; + +type MessengerActions = HandleSnapRequest | HasSnap; +type MessengerEvents = GatorPermissionsControllerStateChangeEvent; + +/** + * Get a restricted messenger for the Gator Permissions controller. This is scoped to the + * actions and events that the Gator Permissions controller is allowed to handle. + * + * @param messenger - The messenger to restrict. + * @returns The restricted messenger. + */ +export function getGatorPermissionsControllerMessenger( + messenger: Messenger, +) { + return messenger.getRestricted({ + name: 'GatorPermissionsController', + allowedActions: ['SnapController:handleRequest', 'SnapController:has'], + allowedEvents: [], + }); +} diff --git a/app/scripts/controller-init/messengers/gator-permissions/index.ts b/app/scripts/controller-init/messengers/gator-permissions/index.ts new file mode 100644 index 000000000000..e30717ce11db --- /dev/null +++ b/app/scripts/controller-init/messengers/gator-permissions/index.ts @@ -0,0 +1,2 @@ +export { getGatorPermissionsControllerMessenger } from './gator-permissions-controller-messenger'; +export type { GatorPermissionsControllerMessenger } from './gator-permissions-controller-messenger'; diff --git a/app/scripts/controller-init/messengers/index.ts b/app/scripts/controller-init/messengers/index.ts index 73d18937636b..0c6b163b89cb 100644 --- a/app/scripts/controller-init/messengers/index.ts +++ b/app/scripts/controller-init/messengers/index.ts @@ -63,6 +63,7 @@ import { getShieldControllerInitMessenger, getShieldControllerMessenger, } from './shield/shield-controller-messenger'; +import { getGatorPermissionsControllerMessenger } from './gator-permissions/gator-permissions-controller-messenger'; export const CONTROLLER_MESSENGERS = { AuthenticationController: { @@ -85,6 +86,10 @@ export const CONTROLLER_MESSENGERS = { getMessenger: getExecutionServiceMessenger, getInitMessenger: noop, }, + GatorPermissionsController: { + getMessenger: getGatorPermissionsControllerMessenger, + getInitMessenger: noop, + }, InstitutionalSnapController: { getMessenger: getInstitutionalSnapControllerMessenger, getInitMessenger: noop, diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index 1120875cb491..122090fd0004 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -437,6 +437,7 @@ import { isSendBundleSupported, } from './lib/transaction/sentinel-api'; import { ShieldControllerInit } from './controller-init/shield/shield-controller-init'; +import { GatorPermissionsControllerInit } from './controller-init/gator-permissions/gator-permissions-controller-init'; import { forwardRequestToSnap } from './lib/forwardRequestToSnap'; @@ -1962,6 +1963,7 @@ export default class MetamaskController extends EventEmitter { SeedlessOnboardingController: SeedlessOnboardingControllerInit, NetworkOrderController: NetworkOrderControllerInit, ShieldController: ShieldControllerInit, + GatorPermissionsController: GatorPermissionsControllerInit, }; const { @@ -2022,6 +2024,8 @@ export default class MetamaskController extends EventEmitter { controllersByName.SeedlessOnboardingController; this.networkOrderController = controllersByName.NetworkOrderController; this.shieldController = controllersByName.ShieldController; + this.gatorPermissionsController = + controllersByName.GatorPermissionsController; this.getSecurityAlertsConfig = () => { return async (url) => { @@ -2228,6 +2232,7 @@ export default class MetamaskController extends EventEmitter { NetworkOrderController: this.networkOrderController, AccountOrderController: this.accountOrderController, GasFeeController: this.gasFeeController, + GatorPermissionsController: this.gatorPermissionsController, TokenListController: this.tokenListController, TokensController: this.tokensController, TokenBalancesController: this.tokenBalancesController, @@ -3441,6 +3446,7 @@ export default class MetamaskController extends EventEmitter { ensController, tokenListController, gasFeeController, + gatorPermissionsController, metaMetricsController, networkController, multichainNetworkController, @@ -3901,6 +3907,12 @@ export default class MetamaskController extends EventEmitter { syncSeedPhrases: this.syncSeedPhrases.bind(this), changePassword: this.changePassword.bind(this), + // GatorPermissionsController + fetchAndUpdateGatorPermissions: + gatorPermissionsController.fetchAndUpdateGatorPermissions.bind( + gatorPermissionsController, + ), + // KeyringController setLocked: this.setLocked.bind(this), createNewVaultAndKeychain: this.createNewVaultAndKeychain.bind(this), diff --git a/builds.yml b/builds.yml index 99c2b2f79ce6..2e3b8467ca2b 100644 --- a/builds.yml +++ b/builds.yml @@ -108,9 +108,6 @@ buildTypes: - GOOGLE_CLIENT_ID_REF: GOOGLE_FLASK_CLIENT_ID - APPLE_CLIENT_ID_REF: APPLE_FLASK_CLIENT_ID - # EIP-7715 Readable Permissions - - PERMISSIONS_KERNEL_SNAP_ID: '' - - GATOR_PERMISSIONS_ENABLED: false isPrerelease: true manifestOverrides: ./app/build-types/flask/manifest/ buildNameOverride: MetaMask Flask @@ -330,8 +327,9 @@ env: - METAMASK_RAMP_API_CONTENT_BASE_URL: https://on-ramp-content.api.cx.metamask.io # EIP-7715 Readable Permissions - - PERMISSIONS_KERNEL_SNAP_ID: '' - GATOR_PERMISSIONS_ENABLED: false + - PERMISSIONS_KERNEL_SNAP_ID: 'npm:@metamask/permissions-kernel-snap' + - GATOR_PERMISSIONS_PROVIDER_SNAP_ID: 'npm:@metamask/gator-permissions-snap' ### # Meta variables diff --git a/lavamoat/browserify/beta/policy.json b/lavamoat/browserify/beta/policy.json index c5e24582397d..32048972472d 100644 --- a/lavamoat/browserify/beta/policy.json +++ b/lavamoat/browserify/beta/policy.json @@ -1323,6 +1323,13 @@ "uuid": true } }, + "@metamask/gator-permissions-controller": { + "packages": { + "@metamask/base-controller": true, + "@metamask/snaps-utils": true, + "@metamask/utils": true + } + }, "@metamask/jazzicon": { "globals": { "document.createElement": true, diff --git a/lavamoat/browserify/experimental/policy.json b/lavamoat/browserify/experimental/policy.json index c5e24582397d..32048972472d 100644 --- a/lavamoat/browserify/experimental/policy.json +++ b/lavamoat/browserify/experimental/policy.json @@ -1323,6 +1323,13 @@ "uuid": true } }, + "@metamask/gator-permissions-controller": { + "packages": { + "@metamask/base-controller": true, + "@metamask/snaps-utils": true, + "@metamask/utils": true + } + }, "@metamask/jazzicon": { "globals": { "document.createElement": true, diff --git a/lavamoat/browserify/flask/policy.json b/lavamoat/browserify/flask/policy.json index c5e24582397d..32048972472d 100644 --- a/lavamoat/browserify/flask/policy.json +++ b/lavamoat/browserify/flask/policy.json @@ -1323,6 +1323,13 @@ "uuid": true } }, + "@metamask/gator-permissions-controller": { + "packages": { + "@metamask/base-controller": true, + "@metamask/snaps-utils": true, + "@metamask/utils": true + } + }, "@metamask/jazzicon": { "globals": { "document.createElement": true, diff --git a/lavamoat/browserify/main/policy.json b/lavamoat/browserify/main/policy.json index c5e24582397d..32048972472d 100644 --- a/lavamoat/browserify/main/policy.json +++ b/lavamoat/browserify/main/policy.json @@ -1323,6 +1323,13 @@ "uuid": true } }, + "@metamask/gator-permissions-controller": { + "packages": { + "@metamask/base-controller": true, + "@metamask/snaps-utils": true, + "@metamask/utils": true + } + }, "@metamask/jazzicon": { "globals": { "document.createElement": true, diff --git a/package.json b/package.json index 825b9a0fb3a0..b96d089b50d1 100644 --- a/package.json +++ b/package.json @@ -296,6 +296,7 @@ "@metamask/eth-trezor-keyring": "^9.0.0", "@metamask/etherscan-link": "^3.0.0", "@metamask/gas-fee-controller": "^24.0.0", + "@metamask/gator-permissions-controller": "^0.1.0", "@metamask/institutional-wallet-snap": "1.3.4", "@metamask/jazzicon": "^2.0.0", "@metamask/json-rpc-engine": "^10.0.0", diff --git a/test/e2e/tests/metrics/state-snapshots/errors-after-init-opt-in-background-state.json b/test/e2e/tests/metrics/state-snapshots/errors-after-init-opt-in-background-state.json index cd3cce4129cf..76979d9c7f08 100644 --- a/test/e2e/tests/metrics/state-snapshots/errors-after-init-opt-in-background-state.json +++ b/test/e2e/tests/metrics/state-snapshots/errors-after-init-opt-in-background-state.json @@ -127,6 +127,7 @@ "gasEstimateType": "none", "nonRPCGasFeeApisDisabled": "boolean" }, + "GatorPermissionsController": "object", "InstitutionalSnapController": "object", "KeyringController": { "isUnlocked": true, diff --git a/test/e2e/tests/metrics/state-snapshots/errors-after-init-opt-in-ui-state.json b/test/e2e/tests/metrics/state-snapshots/errors-after-init-opt-in-ui-state.json index c4f8ecc425d6..93e5499b1b49 100644 --- a/test/e2e/tests/metrics/state-snapshots/errors-after-init-opt-in-ui-state.json +++ b/test/e2e/tests/metrics/state-snapshots/errors-after-init-opt-in-ui-state.json @@ -382,7 +382,11 @@ "accountTree": "object", "accountGroupsMetadata": "object", "accountWalletsMetadata": "object", - "srpSessionData": "object" + "srpSessionData": "object", + "isGatorPermissionsEnabled": "boolean", + "gatorPermissionsMapSerialized": "string", + "gatorPermissionsProviderSnapId": "string", + "isFetchingGatorPermissions": "boolean" }, "ramps": "object", "send": "object", diff --git a/ui/store/controller-actions/gator-permissions-controller.ts b/ui/store/controller-actions/gator-permissions-controller.ts new file mode 100644 index 000000000000..e7919c333154 --- /dev/null +++ b/ui/store/controller-actions/gator-permissions-controller.ts @@ -0,0 +1,10 @@ +import { GatorPermissionsMap } from '@metamask/gator-permissions-controller'; +import { submitRequestToBackground } from '../background-connection'; + +export const fetchAndUpdateGatorPermissions = + async (): Promise => { + return await submitRequestToBackground( + 'fetchAndUpdateGatorPermissions', + [], + ); + }; diff --git a/yarn.lock b/yarn.lock index 04f7f77e94a8..f040596bc9c7 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5254,6 +5254,13 @@ __metadata: languageName: node linkType: hard +"@metamask/7715-permission-types@npm:^0.3.0": + version: 0.3.0 + resolution: "@metamask/7715-permission-types@npm:0.3.0" + checksum: 10/d30e2a12142555752a60ac1284a094cd0092c2cb1fde93467bb93adc34ed6485e4ac956af90a72e314c19faf69826737003431536fe4e89cb73cd407a34e1c8c + languageName: node + linkType: hard + "@metamask/abi-utils@npm:^2.0.3": version: 2.0.4 resolution: "@metamask/abi-utils@npm:2.0.4" @@ -6240,6 +6247,21 @@ __metadata: languageName: node linkType: hard +"@metamask/gator-permissions-controller@npm:^0.1.0": + version: 0.1.0 + resolution: "@metamask/gator-permissions-controller@npm:0.1.0" + dependencies: + "@metamask/7715-permission-types": "npm:^0.3.0" + "@metamask/base-controller": "npm:^8.3.0" + "@metamask/snaps-sdk": "npm:^9.0.0" + "@metamask/snaps-utils": "npm:^11.0.0" + "@metamask/utils": "npm:^11.4.2" + peerDependencies: + "@metamask/snaps-controllers": ^14.0.1 + checksum: 10/71867daa085800c5a042a50a6394d84ff7b631d533e250a782cb8d698028f8bf32896ac6c1b899de3961c746b59c8c7f04d855e65d8d122d33844ee482482f2a + languageName: node + linkType: hard + "@metamask/institutional-wallet-snap@npm:1.3.4": version: 1.3.4 resolution: "@metamask/institutional-wallet-snap@npm:1.3.4" @@ -31804,6 +31826,7 @@ __metadata: "@metamask/forwarder": "npm:^1.1.0" "@metamask/foundryup": "npm:^1.0.1" "@metamask/gas-fee-controller": "npm:^24.0.0" + "@metamask/gator-permissions-controller": "npm:^0.1.0" "@metamask/institutional-wallet-snap": "npm:1.3.4" "@metamask/jazzicon": "npm:^2.0.0" "@metamask/json-rpc-engine": "npm:^10.0.0"