diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index be2f1d28a08f..c80ea715122d 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -216,6 +216,9 @@ app/core/OAuthService @MetaMask/web3auth app/components/Views/Onboarding @MetaMask/web3auth app/reducers/onboarding @MetaMask/web3auth +# Delegation team +app/core/Engine/controllers/gator-permissions-controller @MetaMask/delegation +app/core/Engine/messengers/gator-permissions-controller-messenger @MetaMask/delegation # QA Team - E2E Framework e2e/api-mocking/ @MetaMask/qa e2e/fixtures/ @MetaMask/qa diff --git a/app/core/Engine/Engine.test.ts b/app/core/Engine/Engine.test.ts index ae84702fa01c..8ee6aa0fb18d 100644 --- a/app/core/Engine/Engine.test.ts +++ b/app/core/Engine/Engine.test.ts @@ -128,6 +128,7 @@ describe('Engine', () => { expect(engine.context).toHaveProperty('DeFiPositionsController'); expect(engine.context).toHaveProperty('NetworkEnablementController'); expect(engine.context).toHaveProperty('PerpsController'); + expect(engine.context).toHaveProperty('GatorPermissionsController'); }); it('calling Engine.init twice returns the same instance', () => { @@ -205,6 +206,18 @@ describe('Engine', () => { previousMigrationVersion: 0, // This will be managed by the controller currentMigrationVersion, }, + GatorPermissionsController: { + gatorPermissionsMapSerialized: JSON.stringify({ + 'native-token-stream': {}, + 'native-token-periodic': {}, + 'erc20-token-stream': {}, + 'erc20-token-periodic': {}, + other: {}, + }), + gatorPermissionsProviderSnapId: 'npm:@metamask/gator-permissions-snap', + isFetchingGatorPermissions: false, + isGatorPermissionsEnabled: false, + }, }; expect(initialBackgroundState).toStrictEqual(expectedState); diff --git a/app/core/Engine/Engine.ts b/app/core/Engine/Engine.ts index 5f4851e6df17..717b93edd84d 100644 --- a/app/core/Engine/Engine.ts +++ b/app/core/Engine/Engine.ts @@ -242,8 +242,10 @@ import { seedlessOnboardingControllerInit } from './controllers/seedless-onboard import { perpsControllerInit } from './controllers/perps-controller'; import { selectUseTokenDetection } from '../../selectors/preferencesController'; import { rewardsControllerInit } from './controllers/rewards-controller'; +import { GatorPermissionsControllerInit } from './controllers/gator-permissions-controller'; import { RewardsDataService } from './controllers/rewards-controller/services/rewards-data-service'; import { selectAssetsAccountApiBalancesEnabled } from '../../selectors/featureFlagController/assetsAccountApiBalances'; +import type { GatorPermissionsController } from '@metamask/gator-permissions-controller'; const NON_EMPTY = 'NON_EMPTY'; @@ -296,6 +298,7 @@ export class Engine { accountsController: AccountsController; gasFeeController: GasFeeController; + gatorPermissionsController: GatorPermissionsController; keyringController: KeyringController; smartTransactionsController: SmartTransactionsController; transactionController: TransactionController; @@ -1152,6 +1155,7 @@ export class Engine { AppMetadataController: appMetadataControllerInit, ApprovalController: ApprovalControllerInit, GasFeeController: GasFeeControllerInit, + GatorPermissionsController: GatorPermissionsControllerInit, TransactionController: TransactionControllerInit, SignatureController: SignatureControllerInit, CurrencyRateController: currencyRateControllerInit, @@ -1198,6 +1202,8 @@ export class Engine { controllersByName.SeedlessOnboardingController; const perpsController = controllersByName.PerpsController; const rewardsController = controllersByName.RewardsController; + const gatorPermissionsController = + controllersByName.GatorPermissionsController; // Initialize and store RewardsDataService this.rewardsDataService = new RewardsDataService({ @@ -1213,6 +1219,7 @@ export class Engine { // Backwards compatibility for existing references this.accountsController = accountsController; this.gasFeeController = gasFeeController; + this.gatorPermissionsController = gatorPermissionsController; this.transactionController = transactionController; const multichainNetworkController = @@ -1517,6 +1524,7 @@ export class Engine { fetchEstimatedMultiLayerL1Fee, }), GasFeeController: this.gasFeeController, + GatorPermissionsController: gatorPermissionsController, ApprovalController: approvalController, PermissionController: permissionController, RemoteFeatureFlagController: remoteFeatureFlagController, diff --git a/app/core/Engine/controllers/gator-permissions-controller/gator-permissions-controller-init.test.ts b/app/core/Engine/controllers/gator-permissions-controller/gator-permissions-controller-init.test.ts new file mode 100644 index 000000000000..f7d655b83c7d --- /dev/null +++ b/app/core/Engine/controllers/gator-permissions-controller/gator-permissions-controller-init.test.ts @@ -0,0 +1,152 @@ +import { GatorPermissionsController } from '@metamask/gator-permissions-controller'; +import { buildControllerInitRequestMock } from '../../utils/test-utils'; +import type { ControllerInitRequest } from '../../types'; +import { isGatorPermissionsFeatureEnabled } from '../../../../util/environment'; +import { + getGatorPermissionsControllerMessenger, + GatorPermissionsControllerMessenger, +} from '../../messengers/gator-permissions-controller-messenger/gator-permissions-controller-messenger'; +import { GatorPermissionsControllerInit } from './gator-permissions-controller-init'; +import { ExtendedControllerMessenger } from '../../../ExtendedControllerMessenger'; + +jest.mock('@metamask/gator-permissions-controller'); +jest.mock('../../../../util/environment'); + +function buildInitRequestMock(): jest.Mocked< + ControllerInitRequest +> { + const baseControllerMessenger = new ExtendedControllerMessenger(); + + return { + ...buildControllerInitRequestMock(baseControllerMessenger), + 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('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/core/Engine/controllers/gator-permissions-controller/gator-permissions-controller-init.ts b/app/core/Engine/controllers/gator-permissions-controller/gator-permissions-controller-init.ts new file mode 100644 index 000000000000..53391603224f --- /dev/null +++ b/app/core/Engine/controllers/gator-permissions-controller/gator-permissions-controller-init.ts @@ -0,0 +1,55 @@ +import type { ControllerInitFunction } from '../../types'; +import type { GatorPermissionsControllerMessenger } from '../../messengers/gator-permissions-controller-messenger/gator-permissions-controller-messenger'; +import { + GatorPermissionsController, + GatorPermissionsControllerState, +} from '@metamask/gator-permissions-controller'; +import { isSnapId } from '@metamask/snaps-utils'; +import { isGatorPermissionsFeatureEnabled } from '../../../../util/environment'; + +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 && + !isSnapId(gatorPermissionsProviderSnapId) + ) { + throw new Error( + 'GATOR_PERMISSIONS_PROVIDER_SNAP_ID must be set to a valid snap id', + ); + } + + const isGatorPermissionsEnabled = isGatorPermissionsFeatureEnabled(); + + const state: Partial = { + isGatorPermissionsEnabled, + }; + + if (gatorPermissionsProviderSnapId) { + state.gatorPermissionsProviderSnapId = gatorPermissionsProviderSnapId; + } + + return state; + }; + +export const GatorPermissionsControllerInit: ControllerInitFunction< + GatorPermissionsController, + GatorPermissionsControllerMessenger +> = (request) => { + const { controllerMessenger: messenger, persistedState } = request; + + const controller = new GatorPermissionsController({ + messenger, + state: { + ...generateDefaultGatorPermissionsControllerState(), + ...persistedState.GatorPermissionsController, + }, + }); + + return { + controller, + }; +}; diff --git a/app/core/Engine/controllers/gator-permissions-controller/index.ts b/app/core/Engine/controllers/gator-permissions-controller/index.ts new file mode 100644 index 000000000000..8ef3afa0a230 --- /dev/null +++ b/app/core/Engine/controllers/gator-permissions-controller/index.ts @@ -0,0 +1 @@ +export * from './gator-permissions-controller-init'; diff --git a/app/core/Engine/messengers/gator-permissions-controller-messenger/gator-permissions-controller-messenger.ts b/app/core/Engine/messengers/gator-permissions-controller-messenger/gator-permissions-controller-messenger.ts new file mode 100644 index 000000000000..11e430f12230 --- /dev/null +++ b/app/core/Engine/messengers/gator-permissions-controller-messenger/gator-permissions-controller-messenger.ts @@ -0,0 +1,14 @@ +import type { GatorPermissionsControllerMessenger } from '@metamask/gator-permissions-controller'; +import { BaseControllerMessenger } from '../../types'; + +export type { GatorPermissionsControllerMessenger }; + +export function getGatorPermissionsControllerMessenger( + messenger: BaseControllerMessenger, +): GatorPermissionsControllerMessenger { + return messenger.getRestricted({ + name: 'GatorPermissionsController', + allowedActions: [], + allowedEvents: [], + }); +} diff --git a/app/core/Engine/messengers/gator-permissions-controller-messenger/index.ts b/app/core/Engine/messengers/gator-permissions-controller-messenger/index.ts new file mode 100644 index 000000000000..f6b08c92d3c0 --- /dev/null +++ b/app/core/Engine/messengers/gator-permissions-controller-messenger/index.ts @@ -0,0 +1 @@ +export * from './gator-permissions-controller-messenger'; diff --git a/app/core/Engine/messengers/index.ts b/app/core/Engine/messengers/index.ts index d1ce11af2cd1..43bad90aa268 100644 --- a/app/core/Engine/messengers/index.ts +++ b/app/core/Engine/messengers/index.ts @@ -42,6 +42,7 @@ import { getBridgeControllerMessenger } from './bridge-controller-messenger'; import { getBridgeStatusControllerMessenger } from './bridge-status-controller-messenger'; import { getMultichainAccountServiceMessenger } from './multichain-account-service-messenger/multichain-account-service-messenger'; import { getRewardsControllerMessenger } from './rewards-controller-messenger'; +import { getGatorPermissionsControllerMessenger } from './gator-permissions-controller-messenger'; /** * The messengers for the controllers that have been. */ @@ -166,4 +167,8 @@ export const CONTROLLER_MESSENGERS = { getMessenger: getRewardsControllerMessenger, getInitMessenger: noop, }, + GatorPermissionsController: { + getMessenger: getGatorPermissionsControllerMessenger, + getInitMessenger: noop, + }, } as const; diff --git a/app/core/Engine/types.ts b/app/core/Engine/types.ts index c02407d1c2ad..55512f233e66 100644 --- a/app/core/Engine/types.ts +++ b/app/core/Engine/types.ts @@ -314,6 +314,10 @@ import { MultichainAccountServiceActions, MultichainAccountServiceEvents, } from '@metamask/multichain-account-service'; +import { + GatorPermissionsController, + GatorPermissionsControllerState, +} from '@metamask/gator-permissions-controller'; /** * Controllers that area always instantiated @@ -555,6 +559,7 @@ export type Controllers = { RewardsController: RewardsController; RewardsDataService: RewardsDataService; SeedlessOnboardingController: SeedlessOnboardingController; + GatorPermissionsController: GatorPermissionsController; }; /** @@ -624,6 +629,7 @@ export type EngineState = { PerpsController: PerpsControllerState; RewardsController: RewardsControllerState; SeedlessOnboardingController: SeedlessOnboardingControllerState; + GatorPermissionsController: GatorPermissionsControllerState; }; /** Controller names */ @@ -686,7 +692,8 @@ export type ControllersToInitialize = | 'BridgeController' | 'BridgeStatusController' | 'NetworkEnablementController' - | 'RewardsController'; + | 'RewardsController' + | 'GatorPermissionsController'; /** * Callback that returns a controller messenger for a specific controller. diff --git a/app/core/Engine/utils/utils.test.ts b/app/core/Engine/utils/utils.test.ts index 34155e9bf936..ff14fd571d2a 100644 --- a/app/core/Engine/utils/utils.test.ts +++ b/app/core/Engine/utils/utils.test.ts @@ -49,6 +49,7 @@ import { snapsRegistryInit, } from '../controllers/snaps'; import { TransactionControllerInit } from '../controllers/transaction-controller'; +import { GatorPermissionsControllerInit } from '../controllers/gator-permissions-controller/gator-permissions-controller-init'; import { createMockControllerInitFunction } from './test-utils'; import { getControllerOrThrow, initModularizedControllers } from './utils'; import { AppMetadataController } from '@metamask/app-metadata-controller'; @@ -66,6 +67,7 @@ import { multichainAccountServiceInit } from '../controllers/multichain-account- import { networkEnablementControllerInit } from '../controllers/network-enablement-controller/network-enablement-controller-init'; import { rewardsControllerInit } from '../controllers/rewards-controller'; import { RewardsController } from '../controllers/rewards-controller/RewardsController'; +import { GatorPermissionsController } from '@metamask/gator-permissions-controller'; jest.mock('../controllers/accounts-controller'); jest.mock('../controllers/rewards-controller'); @@ -105,6 +107,9 @@ jest.mock('../controllers/bridge-controller/bridge-controller-init'); jest.mock( '../controllers/bridge-status-controller/bridge-status-controller-init', ); +jest.mock( + '../controllers/gator-permissions-controller/gator-permissions-controller-init', +); describe('initModularizedControllers', () => { const mockAccountsControllerInit = jest.mocked(accountsControllerInit); @@ -164,7 +169,9 @@ describe('initModularizedControllers', () => { networkEnablementControllerInit, ); const mockRewardsControllerInit = jest.mocked(rewardsControllerInit); - + const mockGatorPermissionsControllerInit = jest.mocked( + GatorPermissionsControllerInit, + ); function buildModularizedControllerRequest( overrides?: Record, ) { @@ -205,6 +212,7 @@ describe('initModularizedControllers', () => { BridgeController: mockBridgeControllerInit, BridgeStatusController: mockBridgeStatusControllerInit, RewardsController: mockRewardsControllerInit, + GatorPermissionsController: mockGatorPermissionsControllerInit, }, persistedState: {}, baseControllerMessenger: new ExtendedControllerMessenger(), @@ -290,6 +298,9 @@ describe('initModularizedControllers', () => { mockRewardsControllerInit.mockReturnValue({ controller: {} as unknown as RewardsController, }); + mockGatorPermissionsControllerInit.mockReturnValue({ + controller: {} as unknown as GatorPermissionsController, + }); }); it('initializes controllers', () => { diff --git a/app/util/environment.ts b/app/util/environment.ts index 7263835bc03c..8d7816cb4ca3 100644 --- a/app/util/environment.ts +++ b/app/util/environment.ts @@ -4,3 +4,6 @@ export const isProduction = (): boolean => // TODO: process.env.NODE_ENV === 'production' doesn't work with tests yet. Once we make it work, // we can remove the following line and use the code above instead. ({ ...process.env }?.NODE_ENV === 'production'); + +export const isGatorPermissionsFeatureEnabled = (): boolean => + process.env.GATOR_PERMISSIONS_ENABLED?.toString() === 'true'; diff --git a/babel.config.tests.js b/babel.config.tests.js index c70383df2981..5e8a94afdc19 100644 --- a/babel.config.tests.js +++ b/babel.config.tests.js @@ -15,6 +15,8 @@ const newOverrides = [ 'app/components/UI/Perps/selectors/featureFlags/index.ts', 'app/core/Engine/controllers/network-controller/utils.ts', 'app/core/Engine/controllers/network-controller/utils.test.ts', + 'app/core/Engine/controllers/gator-permissions-controller/gator-permissions-controller-init.ts', + 'app/core/Engine/controllers/gator-permissions-controller/gator-permissions-controller-init.test.ts', 'app/store/migrations/**', 'app/util/networks/customNetworks.tsx', ], diff --git a/package.json b/package.json index 9f9bb1b8b771..0ba940fe1cce 100644 --- a/package.json +++ b/package.json @@ -254,6 +254,7 @@ "@metamask/ethjs-query": "^0.7.1", "@metamask/ethjs-unit": "^0.3.0", "@metamask/gas-fee-controller": "^24.0.0", + "@metamask/gator-permissions-controller": "^0.2.0", "@metamask/json-rpc-engine": "^10.0.3", "@metamask/json-rpc-middleware-stream": "^8.0.7", "@metamask/key-tree": "^10.1.1", diff --git a/yarn.lock b/yarn.lock index ae9bb7fb7739..6b125970e32c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4870,6 +4870,11 @@ dependencies: source-map "^0.7.3" +"@metamask/7715-permission-types@^0.3.0": + version "0.3.0" + resolved "https://registry.yarnpkg.com/@metamask/7715-permission-types/-/7715-permission-types-0.3.0.tgz#ed11d8117809c6b042f86d06702b3c4ef9c826e9" + integrity sha512-+bqFNe6/YCe2LZapiVJO3/hn7BSJLExYT+cDJS1spJocPeVsTyURv3IjblZxRqTOv+igTwrYbh45LvdkgzxkZQ== + "@metamask/abi-utils@^2.0.3": version "2.0.4" resolved "https://registry.yarnpkg.com/@metamask/abi-utils/-/abi-utils-2.0.4.tgz#20908c1d910f7a17a89fdf5778a5c59d5cb8b8be" @@ -5132,6 +5137,20 @@ fast-deep-equal "^3.1.3" lodash "^4.17.21" +"@metamask/delegation-core@^0.2.0": + version "0.2.0" + resolved "https://registry.yarnpkg.com/@metamask/delegation-core/-/delegation-core-0.2.0.tgz#94db8748983742e2db79a6d649e80d76c3c7d471" + integrity sha512-leP40qoxhTmexfeY/neZ9XkhmUiq8fHh3ax4wmmTB5CG6VuXStn2LzLz875RiBtjwU3c4IqaZljxvwC1dUuWVw== + dependencies: + "@metamask/abi-utils" "^3.0.0" + "@metamask/utils" "^11.4.0" + "@noble/hashes" "^1.8.0" + +"@metamask/delegation-deployments@^0.12.0": + version "0.12.0" + resolved "https://registry.yarnpkg.com/@metamask/delegation-deployments/-/delegation-deployments-0.12.0.tgz#d2c33ca7901326df71382b340cc4017b38db7c73" + integrity sha512-9SB5DhLAnbpIWbFxP3UKPXLhCt8iiRjBBjZ7hqF5dMXy9buBEDpCRg+FJYRxYRecwporrTVrv+NfKUyCbL2ncw== + "@metamask/design-system-react-native@^0.4.0": version "0.4.0" resolved "https://registry.yarnpkg.com/@metamask/design-system-react-native/-/design-system-react-native-0.4.0.tgz#38441ef7d321f507aa007a4bdcbc35a97045f260" @@ -5457,6 +5476,19 @@ bn.js "^5.2.1" uuid "^8.3.2" +"@metamask/gator-permissions-controller@^0.2.0": + version "0.2.0" + resolved "https://registry.yarnpkg.com/@metamask/gator-permissions-controller/-/gator-permissions-controller-0.2.0.tgz#99d3b7a4a9151a470ff4b0dedde012f7ff7265ee" + integrity sha512-mndXueLyCcCaDhQUjasvpmWzwKV1Ko5164hryFrQdi8rH2WP5aDrnihp3OSKAipjhAeFrzyASkTXyPIsjT6Owg== + dependencies: + "@metamask/7715-permission-types" "^0.3.0" + "@metamask/base-controller" "^8.4.0" + "@metamask/delegation-core" "^0.2.0" + "@metamask/delegation-deployments" "^0.12.0" + "@metamask/snaps-sdk" "^9.0.0" + "@metamask/snaps-utils" "^11.0.0" + "@metamask/utils" "^11.8.0" + "@metamask/json-rpc-engine@^10.0.0", "@metamask/json-rpc-engine@^10.0.2", "@metamask/json-rpc-engine@^10.0.3": version "10.0.3" resolved "https://registry.yarnpkg.com/@metamask/json-rpc-engine/-/json-rpc-engine-10.0.3.tgz#9258c4718abe305121872414a5c828e43cfcc0f9"