From 02ea7c3a849fac61df449abd88f869aed16702bf Mon Sep 17 00:00:00 2001 From: Guillaume Roux Date: Tue, 16 Dec 2025 12:30:53 +0100 Subject: [PATCH 1/6] Add `StorageService` and handle preinstalled snap setup in `init` --- packages/snaps-controllers/package.json | 1 + .../src/snaps/SnapController.ts | 108 +++++++++++++++--- .../src/methods/hooks/get-snap.ts | 1 - packages/snaps-utils/src/snaps.ts | 9 +- yarn.lock | 11 ++ 5 files changed, 106 insertions(+), 24 deletions(-) diff --git a/packages/snaps-controllers/package.json b/packages/snaps-controllers/package.json index ee297aa185..bf55d13e53 100644 --- a/packages/snaps-controllers/package.json +++ b/packages/snaps-controllers/package.json @@ -94,6 +94,7 @@ "@metamask/snaps-rpc-methods": "workspace:^", "@metamask/snaps-sdk": "workspace:^", "@metamask/snaps-utils": "workspace:^", + "@metamask/storage-service": "^0.0.1", "@metamask/superstruct": "^3.2.1", "@metamask/utils": "^11.9.0", "@xstate/fsm": "^2.0.0", diff --git a/packages/snaps-controllers/src/snaps/SnapController.ts b/packages/snaps-controllers/src/snaps/SnapController.ts index eb16034a41..05877f7d82 100644 --- a/packages/snaps-controllers/src/snaps/SnapController.ts +++ b/packages/snaps-controllers/src/snaps/SnapController.ts @@ -111,6 +111,12 @@ import { OnAssetsConversionResponseStruct, OnAssetsMarketDataResponseStruct, } from '@metamask/snaps-utils'; +import type { + StorageServiceGetItemAction, + StorageServiceSetItemAction, + StorageServiceRemoveItemAction, + StorageServiceClearAction, +} from '@metamask/storage-service'; import type { Json, NonEmptyArray, @@ -670,7 +676,11 @@ export type AllowedActions = | Update | ResolveVersion | CreateInterface - | GetInterface; + | GetInterface + | StorageServiceSetItemAction + | StorageServiceGetItemAction + | StorageServiceRemoveItemAction + | StorageServiceClearAction; export type AllowedEvents = | ExecutionServiceEvents @@ -943,6 +953,8 @@ export class SnapController extends BaseController< readonly #ensureOnboardingComplete: () => Promise; + readonly #controllerSetup = createDeferredPromise(); + constructor({ closeAllConnections, messenger, @@ -991,7 +1003,6 @@ export class SnapController extends BaseController< return Object.values(snaps).reduce>>( (acc, snap) => { const snapCopy: Partial = { ...snap }; - delete snapCopy.sourceCode; delete snapCopy.auxiliaryFiles; acc[snap.id] = snapCopy; return acc; @@ -1115,10 +1126,6 @@ export class SnapController extends BaseController< this.#setupRuntime(snap.id), ); - if (this.#preinstalledSnaps) { - this.#handlePreinstalledSnaps(this.#preinstalledSnaps); - } - this.#trackSnapExport = throttleTracking( (snapId: SnapId, handler: string, success: boolean, origin: string) => { const snapMetadata = this.messenger.call( @@ -1215,8 +1222,9 @@ export class SnapController extends BaseController< * actions. */ #registerMessageHandlers(): void { - this.messenger.registerActionHandler(`${controllerName}:init`, (...args) => - this.init(...args), + this.messenger.registerActionHandler( + `${controllerName}:init`, + async (...args) => this.init(...args), ); this.messenger.registerActionHandler( @@ -1331,17 +1339,23 @@ export class SnapController extends BaseController< /** * Initialise the SnapController. * - * Currently this method calls the `onStart` lifecycle hook for all + * Currently this method sets up the preinstalled snaps and calls the `onStart` lifecycle hook for all * runnable Snaps. */ - init() { + async init() { // Lazily populate the `isReady` state. this.#ensureCanUsePlatform().catch(logWarning); + if (this.#preinstalledSnaps) { + await this.#handlePreinstalledSnaps(this.#preinstalledSnaps); + } + this.#callLifecycleHooks(METAMASK_ORIGIN, HandlerType.OnStart); + + this.#controllerSetup.resolve(); } - #handlePreinstalledSnaps(preinstalledSnaps: PreinstalledSnap[]) { + async #handlePreinstalledSnaps(preinstalledSnaps: PreinstalledSnap[]) { for (const { snapId, manifest, @@ -1413,7 +1427,7 @@ export class SnapController extends BaseController< }; // Add snap to the SnapController state - this.#set({ + await this.#set({ id: snapId, origin: METAMASK_ORIGIN, files: filesObject, @@ -1732,6 +1746,9 @@ export class SnapController extends BaseController< // Ensure the user has onboarded before allowing access to Snaps. await this.#ensureOnboardingComplete(); + // Ensure the controller has finished setting up. + await this.#controllerSetup.promise; + const flags = this.#getFeatureFlags(); // If the user has onboarded, the Snaps Platform is considered ready, @@ -1831,9 +1848,11 @@ export class SnapController extends BaseController< throw new Error(`Snap "${snapId}" is disabled.`); } + const sourceCode = await this.#getSourceCode(snapId); + await this.#startSnap({ snapId, - sourceCode: snap.sourceCode, + sourceCode, }); } @@ -2417,9 +2436,11 @@ export class SnapController extends BaseController< this.#snapsRuntimeData.clear(); this.#rollbackSnapshots.clear(); + await this.#clearStorageService(); + // We want to remove all snaps & permissions, except for preinstalled snaps if (this.#preinstalledSnaps) { - this.#handlePreinstalledSnaps(this.#preinstalledSnaps); + await this.#handlePreinstalledSnaps(this.#preinstalledSnaps); } } @@ -2470,6 +2491,8 @@ export class SnapController extends BaseController< delete state.unencryptedSnapStates[snapId]; }); + await this.#removeSourceCode(snapId); + // If the snap has been fully installed before, also emit snapUninstalled. if (snap.status !== SnapStatus.Installing) { this.messenger.publish(`SnapController:snapUninstalled`, truncated); @@ -3118,7 +3141,7 @@ export class SnapController extends BaseController< this.#transition(snapId, SnapStatusEvents.Update); - this.#set({ + await this.#set({ origin, id: snapId, files: newSnap, @@ -3373,7 +3396,7 @@ export class SnapController extends BaseController< * @param args - The add snap args. * @returns The resulting snap object. */ - #set(args: SetSnapArgs): PersistedSnap { + async #set(args: SetSnapArgs): Promise { const { id: snapId, origin, @@ -3446,7 +3469,6 @@ export class SnapController extends BaseController< initialPermissions: manifest.result.initialPermissions, manifest: manifest.result, status: this.#statusMachine.config.initial as StatusStates['value'], - sourceCode, version, versionHistory, auxiliaryFiles, @@ -3456,6 +3478,8 @@ export class SnapController extends BaseController< // If the snap was blocked, it isn't any longer delete snap.blockInformation; + await this.#setSourceCode(snapId, sourceCode); + // store the snap back in state const { inversePatches } = this.update((state: any) => { state.snaps[snapId] = snap; @@ -3463,6 +3487,7 @@ export class SnapController extends BaseController< // checking for isUpdate here as this function is also used in // the install flow, we do not care to create snapshots for installs + // @TODO (guillaumerx): Find a way to add the sourceCode to the rollback snapshot if (isUpdate) { const rollbackSnapshot = this.#getRollbackSnapshot(snapId); if (rollbackSnapshot !== undefined) { @@ -4664,4 +4689,53 @@ export class SnapController extends BaseController< runtime.state = undefined; } } + + /** + * Retrieve the source code for a snap from storage. + * + * @param snapId - The snap ID. + * @returns The source code for the snap. + */ + async #getSourceCode(snapId: SnapId) { + const { result } = await this.messenger.call( + 'StorageService:getItem', + this.name, + snapId, + ); + + assert(result, `Source code for snap "${snapId}" not found.`); + + return result as string; + } + + /** + * Store the source code for a snap in storage. + * + * @param snapId - The snap ID. + * @param sourceCode - The source code for the snap. + */ + async #setSourceCode(snapId: SnapId, sourceCode: string) { + await this.messenger.call( + 'StorageService:setItem', + this.name, + snapId, + sourceCode, + ); + } + + /** + * Remove the source code for a snap from storage. + * + * @param snapId - The snap ID. + */ + async #removeSourceCode(snapId: SnapId) { + await this.messenger.call('StorageService:removeItem', this.name, snapId); + } + + /** + * Clear all snap source code from storage. + */ + async #clearStorageService() { + await this.messenger.call('StorageService:clear', this.name); + } } diff --git a/packages/snaps-simulation/src/methods/hooks/get-snap.ts b/packages/snaps-simulation/src/methods/hooks/get-snap.ts index af622295ca..98db6bf31a 100644 --- a/packages/snaps-simulation/src/methods/hooks/get-snap.ts +++ b/packages/snaps-simulation/src/methods/hooks/get-snap.ts @@ -23,7 +23,6 @@ export function getGetSnapImplementation(preinstalled: boolean = true) { status: SnapStatus.Running, versionHistory: [], initialPermissions: {}, - sourceCode: '', manifest: { version: '0.1.0' as SemVerVersion, proposedName: 'Test Snap', diff --git a/packages/snaps-utils/src/snaps.ts b/packages/snaps-utils/src/snaps.ts index 2a47668107..e1af0bdf8c 100644 --- a/packages/snaps-utils/src/snaps.ts +++ b/packages/snaps-utils/src/snaps.ts @@ -91,7 +91,9 @@ export type SnapAuxiliaryFile = { */ export type SnapAuxilaryFile = SnapAuxiliaryFile; -export type PersistedSnap = Snap; +export type PersistedSnap = Snap & { + sourceCode: string; +}; /** * A Snap as it exists in {@link SnapController} state. @@ -107,11 +109,6 @@ export type Snap = TruncatedSnap & { */ initialPermissions: SnapPermissions; - /** - * The source code of the Snap. - */ - sourceCode: string; - /** * The Snap's manifest file. */ diff --git a/yarn.lock b/yarn.lock index 7e78e1edcf..5cdb2fcde8 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4211,6 +4211,7 @@ __metadata: "@metamask/snaps-sdk": "workspace:^" "@metamask/snaps-utils": "workspace:^" "@metamask/superstruct": "npm:^3.2.1" + "@metamask/storage-service": "npm:^0.0.1" "@metamask/utils": "npm:^11.9.0" "@noble/hashes": "npm:^1.7.1" "@swc/core": "npm:1.11.31" @@ -4623,6 +4624,16 @@ __metadata: languageName: unknown linkType: soft +"@metamask/storage-service@npm:^0.0.1": + version: 0.0.1 + resolution: "@metamask/storage-service@npm:0.0.1" + dependencies: + "@metamask/messenger": "npm:^0.3.0" + "@metamask/utils": "npm:^11.8.1" + checksum: 10/ba2443e4bab7a4ef64bf3e0a10403ef94d50225e5ae5931a6fd32a21bfa8e276916ab6dc377d2db30c15c50acaeaee74a3c0b418a563311da9f98b9172fba300 + languageName: node + linkType: hard + "@metamask/superstruct@npm:^3.1.0, @metamask/superstruct@npm:^3.2.1": version: 3.2.1 resolution: "@metamask/superstruct@npm:3.2.1" From a1d7dc6f12ebb53a3c49f6611d884ca8a3f3f146 Mon Sep 17 00:00:00 2001 From: Guillaume Roux Date: Thu, 18 Dec 2025 11:14:50 +0100 Subject: [PATCH 2/6] WIP --- .../src/snaps/SnapController.test.tsx | 534 +++++++++--------- .../src/snaps/SnapController.ts | 87 ++- .../src/test-utils/controller.tsx | 111 +++- 3 files changed, 448 insertions(+), 284 deletions(-) diff --git a/packages/snaps-controllers/src/snaps/SnapController.test.tsx b/packages/snaps-controllers/src/snaps/SnapController.test.tsx index ddabf00d96..c732de9815 100644 --- a/packages/snaps-controllers/src/snaps/SnapController.test.tsx +++ b/packages/snaps-controllers/src/snaps/SnapController.test.tsx @@ -158,7 +158,7 @@ describe('SnapController', () => { }); it('creates a snap controller and execution service', async () => { - const [snapController, service] = getSnapControllerWithEES(); + const [snapController, service] = await getSnapControllerWithEES(); expect(service).toBeDefined(); expect(snapController).toBeDefined(); snapController.destroy(); @@ -166,7 +166,7 @@ describe('SnapController', () => { }); it('adds a snap and uses its JSON-RPC api with a NodeThreadExecutionService', async () => { - const [snapController, service] = getSnapControllerWithEES( + const [snapController, service] = await getSnapControllerWithEES( getSnapControllerWithEESOptions({ state: { snaps: getPersistedSnapsState(), @@ -200,7 +200,7 @@ describe('SnapController', () => { getNodeEESMessenger(rootMessenger), ) as unknown as NodeThreadExecutionService; - const [snapController] = getSnapControllerWithEES( + const [snapController] = await getSnapControllerWithEES( getSnapControllerWithEESOptions({ rootMessenger, state: { @@ -232,7 +232,7 @@ describe('SnapController', () => { it('passes endowments to a snap when executing it', async () => { const rootMessenger = getControllerMessenger(); const messenger = getSnapControllerMessenger(rootMessenger); - const snapController = getSnapController( + const snapController = await getSnapController( getSnapControllerOptions({ environmentEndowmentPermissions: ['endowment:foo'], messenger, @@ -282,7 +282,7 @@ describe('SnapController', () => { it('errors if attempting to start a snap that was already started', async () => { const messenger = getSnapControllerMessenger(); - const snapController = getSnapController( + const snapController = await getSnapController( getSnapControllerOptions({ messenger, state: { @@ -317,7 +317,7 @@ describe('SnapController', () => { }, }); const { rootMessenger } = options; - const [snapController, service] = getSnapControllerWithEES(options); + const [snapController, service] = await getSnapControllerWithEES(options); const snap = snapController.getExpect(MOCK_SNAP_ID); await snapController.startSnap(snap.id); @@ -342,7 +342,7 @@ describe('SnapController', () => { }); it('adds a snap and uses its JSON-RPC API and then get stopped from idling too long', async () => { - const [snapController, service] = getSnapControllerWithEES( + const [snapController, service] = await getSnapControllerWithEES( getSnapControllerWithEESOptions({ idleTimeCheckInterval: 10, maxIdleTime: 50, @@ -377,7 +377,7 @@ describe('SnapController', () => { it('terminates a snap even if connection to worker has failed', async () => { const rootMessenger = getControllerMessenger(); - const [snapController, service] = getSnapControllerWithEES( + const [snapController, service] = await getSnapControllerWithEES( getSnapControllerWithEESOptions({ rootMessenger, idleTimeCheckInterval: 10, @@ -427,7 +427,7 @@ describe('SnapController', () => { }); it(`reads a snap's status after adding it`, async () => { - const [snapController, service] = getSnapControllerWithEES( + const [snapController, service] = await getSnapControllerWithEES( getSnapControllerWithEESOptions({ idleTimeCheckInterval: 1000, maxIdleTime: 2000, @@ -450,7 +450,7 @@ describe('SnapController', () => { }); it('adds a snap, stops it, and starts it again on-demand', async () => { - const [snapController, service] = getSnapControllerWithEES( + const [snapController, service] = await getSnapControllerWithEES( getSnapControllerWithEESOptions({ idleTimeCheckInterval: 1000, maxIdleTime: 2000, @@ -505,7 +505,7 @@ describe('SnapController', () => { }), }); - const snapController = getSnapController( + const snapController = await getSnapController( getSnapControllerOptions({ messenger, detectSnapLocation: loopbackDetect({ manifest }), @@ -558,7 +558,7 @@ describe('SnapController', () => { manifest: manifest.result, }); - const snapController = getSnapController( + const snapController = await getSnapController( getSnapControllerOptions({ messenger, state: { @@ -640,7 +640,7 @@ describe('SnapController', () => { manifest: manifest.result, }); - const snapController = getSnapController( + const snapController = await getSnapController( getSnapControllerOptions({ messenger, state: { @@ -719,7 +719,7 @@ describe('SnapController', () => { manifest: manifest.result, }); - const snapController = getSnapController( + const snapController = await getSnapController( getSnapControllerOptions({ messenger, state: { @@ -770,7 +770,7 @@ describe('SnapController', () => { it('installs a snap via installSnaps', async () => { const messenger = getSnapControllerMessenger(); - const snapController = getSnapController( + const snapController = await getSnapController( getSnapControllerOptions({ messenger, detectSnapLocation: loopbackDetect(), @@ -925,7 +925,7 @@ describe('SnapController', () => { }), }); - const snapController = getSnapController( + const snapController = await getSnapController( getSnapControllerOptions({ messenger, detectSnapLocation: loopbackDetect({ @@ -948,7 +948,7 @@ describe('SnapController', () => { }); it('throws an error if the installation is disabled during installSnaps', async () => { - const controller = getSnapController( + const controller = await getSnapController( getSnapControllerOptions({ featureFlags: { disableSnapInstallation: true, @@ -968,7 +968,7 @@ describe('SnapController', () => { }); it('throws an error if the platform is disabled during installSnaps', async () => { - const controller = getSnapController( + const controller = await getSnapController( getSnapControllerOptions({ getFeatureFlags: () => ({ disableSnaps: true }), }), @@ -986,7 +986,7 @@ describe('SnapController', () => { }); it('throws an error if the platform is disabled during handleRequest', async () => { - const controller = getSnapController( + const controller = await getSnapController( getSnapControllerOptions({ getFeatureFlags: () => ({ disableSnaps: true }), state: getPersistedSnapsState(), @@ -1008,7 +1008,7 @@ describe('SnapController', () => { }); it('throws an error on invalid semver range during installSnaps', async () => { - const controller = getSnapController(); + const controller = await getSnapController(); await expect( controller.installSnaps(MOCK_ORIGIN, { @@ -1022,7 +1022,7 @@ describe('SnapController', () => { }); it("throws an error if semver version range doesn't match downloaded version", async () => { - const controller = getSnapController( + const controller = await getSnapController( getSnapControllerOptions({ detectSnapLocation: loopbackDetect() }), ); @@ -1041,7 +1041,7 @@ describe('SnapController', () => { const registry = new MockSnapsRegistry(); const rootMessenger = getControllerMessenger(registry); const messenger = getSnapControllerMessenger(rootMessenger); - const controller = getSnapController( + const controller = await getSnapController( getSnapControllerOptions({ featureFlags: { requireAllowlist: true }, messenger, @@ -1070,7 +1070,7 @@ describe('SnapController', () => { const registry = new MockSnapsRegistry(); const rootMessenger = getControllerMessenger(registry); const messenger = getSnapControllerMessenger(rootMessenger); - const controller = getSnapController( + const controller = await getSnapController( getSnapControllerOptions({ featureFlags: { requireAllowlist: true }, messenger, @@ -1109,7 +1109,7 @@ describe('SnapController', () => { }), }); - const controller = getSnapController( + const controller = await getSnapController( getSnapControllerOptions({ featureFlags: { requireAllowlist: true }, detectSnapLocation: loopbackDetect({ @@ -1148,7 +1148,7 @@ describe('SnapController', () => { registry.resolveVersion.mockReturnValue('1.1.0'); - const controller = getSnapController( + const controller = await getSnapController( getSnapControllerOptions({ messenger, featureFlags: { requireAllowlist: true }, @@ -1175,7 +1175,7 @@ describe('SnapController', () => { const registry = new MockSnapsRegistry(); const rootMessenger = getControllerMessenger(registry); const messenger = getSnapControllerMessenger(rootMessenger); - const controller = getSnapController( + const controller = await getSnapController( getSnapControllerOptions({ messenger, detectSnapLocation: (_location, options) => @@ -1194,7 +1194,7 @@ describe('SnapController', () => { it('reuses an already installed snap if it satisfies the requested SemVer range', async () => { const messenger = getSnapControllerMessenger(); - const controller = getSnapController( + const controller = await getSnapController( getSnapControllerOptions({ messenger, state: { @@ -1218,7 +1218,7 @@ describe('SnapController', () => { it('fails to install snap if user rejects installation', async () => { const rootMessenger = getControllerMessenger(); const messenger = getSnapControllerMessenger(rootMessenger); - const controller = getSnapController( + const controller = await getSnapController( getSnapControllerOptions({ messenger, detectSnapLocation: loopbackDetect(), @@ -1287,7 +1287,7 @@ describe('SnapController', () => { it('removes a snap that errors during installation after being added', async () => { const rootMessenger = getControllerMessenger(); const messenger = getSnapControllerMessenger(rootMessenger); - const snapController = getSnapController( + const snapController = await getSnapController( getSnapControllerOptions({ messenger, detectSnapLocation: loopbackDetect(), @@ -1335,7 +1335,7 @@ describe('SnapController', () => { }); it('adds a snap, disable/enables it, and still gets a response from an RPC method', async () => { - const [snapController, service] = getSnapControllerWithEES( + const [snapController, service] = await getSnapControllerWithEES( getSnapControllerWithEESOptions({ idleTimeCheckInterval: 1000, maxRequestTime: 2000, @@ -1431,7 +1431,7 @@ describe('SnapController', () => { }, }); - const snapController = getSnapController(options); + const snapController = await getSnapController(options); const snap = snapController.getExpect(MOCK_SNAP_ID); rootMessenger.registerActionHandler( @@ -1480,7 +1480,7 @@ describe('SnapController', () => { }, }); - const snapController = getSnapController(options); + const snapController = await getSnapController(options); const snap = snapController.getExpect(MOCK_SNAP_ID); await snapController.startSnap(snap.id); @@ -1505,7 +1505,7 @@ describe('SnapController', () => { }, }); - const snapController = getSnapController(options); + const snapController = await getSnapController(options); const snap = snapController.getExpect(MOCK_SNAP_ID); rootMessenger.registerActionHandler( @@ -1600,7 +1600,7 @@ describe('SnapController', () => { getNodeEESMessenger(options.rootMessenger), setupSnapProvider, ); - const [snapController] = getSnapControllerWithEES(options, service); + const [snapController] = await getSnapControllerWithEES(options, service); const snap = snapController.getExpect(MOCK_SNAP_ID); await snapController.startSnap(snap.id); @@ -1675,7 +1675,7 @@ describe('SnapController', () => { getNodeEESMessenger(rootMessenger), setupSnapProvider, ); - const [snapController] = getSnapControllerWithEES(options, service); + const [snapController] = await getSnapControllerWithEES(options, service); const snap = snapController.getExpect(MOCK_SNAP_ID); rootMessenger.registerActionHandler( @@ -1730,7 +1730,7 @@ describe('SnapController', () => { }); const { rootMessenger } = options; - const [snapController] = getSnapControllerWithEES(options); + const [snapController] = await getSnapControllerWithEES(options); const snap = snapController.getExpect(MOCK_SNAP_ID); rootMessenger.registerActionHandler( @@ -1787,7 +1787,7 @@ describe('SnapController', () => { `, }); - const [snapController] = getSnapControllerWithEES( + const [snapController] = await getSnapControllerWithEES( getSnapControllerWithEESOptions({ detectSnapLocation: loopbackDetect({ manifest, @@ -1840,7 +1840,7 @@ describe('SnapController', () => { `, }); - const [snapController] = getSnapControllerWithEES( + const [snapController] = await getSnapControllerWithEES( getSnapControllerWithEESOptions({ detectSnapLocation: loopbackDetect({ manifest, @@ -1900,7 +1900,7 @@ describe('SnapController', () => { }); const rootMessenger = getControllerMessenger(); - const [snapController, service] = getSnapControllerWithEES( + const [snapController, service] = await getSnapControllerWithEES( getSnapControllerWithEESOptions({ maxRequestTime: 50, rootMessenger, @@ -1990,7 +1990,7 @@ describe('SnapController', () => { ), }, }); - const [snapController, service] = getSnapControllerWithEES(options); + const [snapController, service] = await getSnapControllerWithEES(options); const snap = snapController.getExpect(MOCK_SNAP_ID); @@ -2043,7 +2043,7 @@ describe('SnapController', () => { it(`shouldn't time out a long running snap on start up`, async () => { const rootMessenger = getControllerMessenger(); const messenger = getSnapControllerMessenger(rootMessenger); - const snapController = getSnapController( + const snapController = await getSnapController( getSnapControllerOptions({ messenger, maxRequestTime: 50, @@ -2090,7 +2090,7 @@ describe('SnapController', () => { const { messenger } = options; - const [snapController, service] = getSnapControllerWithEES( + const [snapController, service] = await getSnapControllerWithEES( options, new ExecutionEnvironmentStub( getNodeEESMessenger(options.rootMessenger), @@ -2159,7 +2159,7 @@ describe('SnapController', () => { .fn() .mockReturnValue(TEST_SECRET_RECOVERY_PHRASE_SEED_BYTES); - const snapController = getSnapController( + const snapController = await getSnapController( getSnapControllerOptions({ messenger, state: { @@ -2196,7 +2196,7 @@ describe('SnapController', () => { describe('handleRequest', () => { it('throws if the Snap is not installed', async () => { - const snapController = getSnapController(); + const snapController = await getSnapController(); await expect( snapController.handleRequest({ @@ -2225,7 +2225,7 @@ describe('SnapController', () => { async (handler) => { const rootMessenger = getControllerMessenger(); const messenger = getSnapControllerMessenger(rootMessenger); - const snapController = getSnapController( + const snapController = await getSnapController( getSnapControllerOptions({ messenger, state: { @@ -2260,7 +2260,7 @@ describe('SnapController', () => { it('does not throw if the snap uses a permitted handler', async () => { const rootMessenger = getControllerMessenger(); const messenger = getSnapControllerMessenger(rootMessenger); - const snapController = getSnapController( + const snapController = await getSnapController( getSnapControllerOptions({ messenger, state: { @@ -2294,7 +2294,7 @@ describe('SnapController', () => { it('allows MetaMask to send a JSON-RPC request', async () => { const rootMessenger = getControllerMessenger(); const messenger = getSnapControllerMessenger(rootMessenger); - const snapController = getSnapController( + const snapController = await getSnapController( getSnapControllerOptions({ messenger, state: { @@ -2341,7 +2341,7 @@ describe('SnapController', () => { it('allows MetaMask to send a keyring request', async () => { const rootMessenger = getControllerMessenger(); const messenger = getSnapControllerMessenger(rootMessenger); - const snapController = getSnapController( + const snapController = await getSnapController( getSnapControllerOptions({ messenger, state: { @@ -2388,7 +2388,7 @@ describe('SnapController', () => { it('allows a website origin if it is in the `allowedOrigins` list', async () => { const rootMessenger = getControllerMessenger(); const messenger = getSnapControllerMessenger(rootMessenger); - const snapController = getSnapController( + const snapController = await getSnapController( getSnapControllerOptions({ messenger, state: { @@ -2435,7 +2435,7 @@ describe('SnapController', () => { it('allows a website origin if it is in the `allowedOrigins` list for keyring requests', async () => { const rootMessenger = getControllerMessenger(); const messenger = getSnapControllerMessenger(rootMessenger); - const snapController = getSnapController( + const snapController = await getSnapController( getSnapControllerOptions({ messenger, state: { @@ -2482,7 +2482,7 @@ describe('SnapController', () => { it('allows a website origin if `dapps` is `true`', async () => { const rootMessenger = getControllerMessenger(); const messenger = getSnapControllerMessenger(rootMessenger); - const snapController = getSnapController( + const snapController = await getSnapController( getSnapControllerOptions({ messenger, state: { @@ -2530,7 +2530,7 @@ describe('SnapController', () => { it('allows a Snap origin if it is in the `allowedOrigins` list', async () => { const rootMessenger = getControllerMessenger(); const messenger = getSnapControllerMessenger(rootMessenger); - const snapController = getSnapController( + const snapController = await getSnapController( getSnapControllerOptions({ messenger, state: { @@ -2577,7 +2577,7 @@ describe('SnapController', () => { it('allows a Snap origin if `snaps` is `true`', async () => { const rootMessenger = getControllerMessenger(); const messenger = getSnapControllerMessenger(rootMessenger); - const snapController = getSnapController( + const snapController = await getSnapController( getSnapControllerOptions({ messenger, state: { @@ -2638,7 +2638,7 @@ describe('SnapController', () => { async (value: RpcOrigins) => { const rootMessenger = getControllerMessenger(); const messenger = getSnapControllerMessenger(rootMessenger); - const snapController = getSnapController( + const snapController = await getSnapController( getSnapControllerOptions({ messenger, state: { @@ -2691,7 +2691,7 @@ describe('SnapController', () => { const { promise, resolve } = createDeferredPromise(); const ensureOnboardingComplete = jest.fn().mockReturnValue(promise); - const snapController = getSnapController( + const snapController = await getSnapController( getSnapControllerOptions({ messenger, state: { @@ -2735,7 +2735,7 @@ describe('SnapController', () => { it('throws if the snap does not have permission to handle JSON-RPC requests from dapps', async () => { const rootMessenger = getControllerMessenger(); const messenger = getSnapControllerMessenger(rootMessenger); - const snapController = getSnapController( + const snapController = await getSnapController( getSnapControllerOptions({ messenger, state: { @@ -2775,7 +2775,7 @@ describe('SnapController', () => { it('throws if the snap does not have permission to handle JSON-RPC requests from snaps', async () => { const rootMessenger = getControllerMessenger(); const messenger = getSnapControllerMessenger(rootMessenger); - const snapController = getSnapController( + const snapController = await getSnapController( getSnapControllerOptions({ messenger, state: { @@ -2815,7 +2815,7 @@ describe('SnapController', () => { it('throws if the website origin is not in the `allowedOrigins` list for keyring requests', async () => { const rootMessenger = getControllerMessenger(); const messenger = getSnapControllerMessenger(rootMessenger); - const snapController = getSnapController( + const snapController = await getSnapController( getSnapControllerOptions({ messenger, state: { @@ -2864,7 +2864,7 @@ describe('SnapController', () => { it('injects context into onUserInput', async () => { const rootMessenger = getControllerMessenger(); const messenger = getSnapControllerMessenger(rootMessenger); - const snapController = getSnapController( + const snapController = await getSnapController( getSnapControllerOptions({ messenger, state: { @@ -2935,7 +2935,7 @@ describe('SnapController', () => { it('throws if onTransaction handler returns a phishing link', async () => { const rootMessenger = getControllerMessenger(); const messenger = getSnapControllerMessenger(rootMessenger); - const snapController = getSnapController( + const snapController = await getSnapController( getSnapControllerOptions({ messenger, state: { @@ -2997,7 +2997,7 @@ describe('SnapController', () => { it('throws if onTransaction returns an invalid value', async () => { const rootMessenger = getControllerMessenger(); const messenger = getSnapControllerMessenger(rootMessenger); - const snapController = getSnapController( + const snapController = await getSnapController( getSnapControllerOptions({ messenger, state: { @@ -3055,7 +3055,7 @@ describe('SnapController', () => { it("doesn't throw if onTransaction return value is valid", async () => { const rootMessenger = getControllerMessenger(); const messenger = getSnapControllerMessenger(rootMessenger); - const snapController = getSnapController( + const snapController = await getSnapController( getSnapControllerOptions({ messenger, state: { @@ -3109,7 +3109,7 @@ describe('SnapController', () => { it('throws if onTransaction return value is an invalid id', async () => { const rootMessenger = getControllerMessenger(); const messenger = getSnapControllerMessenger(rootMessenger); - const snapController = getSnapController( + const snapController = await getSnapController( getSnapControllerOptions({ messenger, state: { @@ -3163,7 +3163,7 @@ describe('SnapController', () => { it("doesn't throw if onTransaction return value is an id", async () => { const rootMessenger = getControllerMessenger(); const messenger = getSnapControllerMessenger(rootMessenger); - const snapController = getSnapController( + const snapController = await getSnapController( getSnapControllerOptions({ messenger, state: { @@ -3222,7 +3222,7 @@ describe('SnapController', () => { it('throws if onSignature handler returns a phishing link', async () => { const rootMessenger = getControllerMessenger(); const messenger = getSnapControllerMessenger(rootMessenger); - const snapController = getSnapController( + const snapController = await getSnapController( getSnapControllerOptions({ messenger, state: { @@ -3284,7 +3284,7 @@ describe('SnapController', () => { it('throws if onSignature returns an invalid value', async () => { const rootMessenger = getControllerMessenger(); const messenger = getSnapControllerMessenger(rootMessenger); - const snapController = getSnapController( + const snapController = await getSnapController( getSnapControllerOptions({ messenger, state: { @@ -3342,7 +3342,7 @@ describe('SnapController', () => { it('throws if onSignature return value is an invalid id', async () => { const rootMessenger = getControllerMessenger(); const messenger = getSnapControllerMessenger(rootMessenger); - const snapController = getSnapController( + const snapController = await getSnapController( getSnapControllerOptions({ messenger, state: { @@ -3396,7 +3396,7 @@ describe('SnapController', () => { it("doesn't throw if onSignature return value is valid", async () => { const rootMessenger = getControllerMessenger(); const messenger = getSnapControllerMessenger(rootMessenger); - const snapController = getSnapController( + const snapController = await getSnapController( getSnapControllerOptions({ messenger, state: { @@ -3451,7 +3451,7 @@ describe('SnapController', () => { it(`doesn't throw if onTransaction handler returns null`, async () => { const rootMessenger = getControllerMessenger(); const messenger = getSnapControllerMessenger(rootMessenger); - const snapController = getSnapController( + const snapController = await getSnapController( getSnapControllerOptions({ messenger, state: { @@ -3503,7 +3503,7 @@ describe('SnapController', () => { it(`doesn't throw if onSignature handler returns null`, async () => { const rootMessenger = getControllerMessenger(); const messenger = getSnapControllerMessenger(rootMessenger); - const snapController = getSnapController( + const snapController = await getSnapController( getSnapControllerOptions({ messenger, state: { @@ -3555,7 +3555,7 @@ describe('SnapController', () => { it('throws if onHomePage handler returns a phishing link', async () => { const rootMessenger = getControllerMessenger(); const messenger = getSnapControllerMessenger(rootMessenger); - const snapController = getSnapController( + const snapController = await getSnapController( getSnapControllerOptions({ messenger, state: { @@ -3617,7 +3617,7 @@ describe('SnapController', () => { it('throws if onHomePage return value is an invalid id', async () => { const rootMessenger = getControllerMessenger(); const messenger = getSnapControllerMessenger(rootMessenger); - const snapController = getSnapController( + const snapController = await getSnapController( getSnapControllerOptions({ messenger, state: { @@ -3671,7 +3671,7 @@ describe('SnapController', () => { it("doesn't throw if onHomePage return value is valid", async () => { const rootMessenger = getControllerMessenger(); const messenger = getSnapControllerMessenger(rootMessenger); - const snapController = getSnapController( + const snapController = await getSnapController( getSnapControllerOptions({ messenger, state: { @@ -3725,7 +3725,7 @@ describe('SnapController', () => { it('throws if onSettingsPage handler returns a phishing link', async () => { const rootMessenger = getControllerMessenger(); const messenger = getSnapControllerMessenger(rootMessenger); - const snapController = getSnapController( + const snapController = await getSnapController( getSnapControllerOptions({ messenger, state: { @@ -3787,7 +3787,7 @@ describe('SnapController', () => { it('throws if onSettingsPage return value is an invalid id', async () => { const rootMessenger = getControllerMessenger(); const messenger = getSnapControllerMessenger(rootMessenger); - const snapController = getSnapController( + const snapController = await getSnapController( getSnapControllerOptions({ messenger, state: { @@ -3841,7 +3841,7 @@ describe('SnapController', () => { it("doesn't throw if onSettingsPage return value is valid", async () => { const rootMessenger = getControllerMessenger(); const messenger = getSnapControllerMessenger(rootMessenger); - const snapController = getSnapController( + const snapController = await getSnapController( getSnapControllerOptions({ messenger, state: { @@ -3895,7 +3895,7 @@ describe('SnapController', () => { it('throws if onNameLookup returns an invalid value', async () => { const rootMessenger = getControllerMessenger(); const messenger = getSnapControllerMessenger(rootMessenger); - const snapController = getSnapController( + const snapController = await getSnapController( getSnapControllerOptions({ messenger, state: { @@ -3952,7 +3952,7 @@ describe('SnapController', () => { it("doesn't throw if onNameLookup return value is valid", async () => { const rootMessenger = getControllerMessenger(); const messenger = getSnapControllerMessenger(rootMessenger); - const snapController = getSnapController( + const snapController = await getSnapController( getSnapControllerOptions({ messenger, state: { @@ -4014,7 +4014,7 @@ describe('SnapController', () => { it(`doesn't throw if onNameLookup handler returns null`, async () => { const rootMessenger = getControllerMessenger(); const messenger = getSnapControllerMessenger(rootMessenger); - const snapController = getSnapController( + const snapController = await getSnapController( getSnapControllerOptions({ messenger, state: { @@ -4067,7 +4067,7 @@ describe('SnapController', () => { it('throws if `onAssetsLookup` handler returns an invalid response', async () => { const rootMessenger = getControllerMessenger(); const messenger = getSnapControllerMessenger(rootMessenger); - const snapController = getSnapController( + const snapController = await getSnapController( getSnapControllerOptions({ messenger, state: { @@ -4129,7 +4129,7 @@ describe('SnapController', () => { it('filters out assets that are out of scope for `onAssetsLookup`', async () => { const rootMessenger = getControllerMessenger(); const messenger = getSnapControllerMessenger(rootMessenger); - const snapController = getSnapController( + const snapController = await getSnapController( getSnapControllerOptions({ messenger, state: { @@ -4205,7 +4205,7 @@ describe('SnapController', () => { it('returns the value when `onAssetsLookup` returns a valid response for fungible assets', async () => { const rootMessenger = getControllerMessenger(); const messenger = getSnapControllerMessenger(rootMessenger); - const snapController = getSnapController( + const snapController = await getSnapController( getSnapControllerOptions({ messenger, state: { @@ -4297,7 +4297,7 @@ describe('SnapController', () => { it('returns the value when `onAssetsLookup` returns a valid response for non-fungible assets', async () => { const rootMessenger = getControllerMessenger(); const messenger = getSnapControllerMessenger(rootMessenger); - const snapController = getSnapController( + const snapController = await getSnapController( getSnapControllerOptions({ messenger, state: { @@ -4419,7 +4419,7 @@ describe('SnapController', () => { it('throws if `onAssetsConversion` handler returns an invalid response', async () => { const rootMessenger = getControllerMessenger(); const messenger = getSnapControllerMessenger(rootMessenger); - const snapController = getSnapController( + const snapController = await getSnapController( getSnapControllerOptions({ messenger, state: { @@ -4481,7 +4481,7 @@ describe('SnapController', () => { it('filters out assets that are out of scope for `onAssetsConversion`', async () => { const rootMessenger = getControllerMessenger(); const messenger = getSnapControllerMessenger(rootMessenger); - const snapController = getSnapController( + const snapController = await getSnapController( getSnapControllerOptions({ messenger, state: { @@ -4555,7 +4555,7 @@ describe('SnapController', () => { it('returns the value when `onAssetsConversion` returns a valid response', async () => { const rootMessenger = getControllerMessenger(); const messenger = getSnapControllerMessenger(rootMessenger); - const snapController = getSnapController( + const snapController = await getSnapController( getSnapControllerOptions({ messenger, state: { @@ -4640,7 +4640,7 @@ describe('SnapController', () => { it('throws if `onAssetsMarketData` handler returns an invalid response', async () => { const rootMessenger = getControllerMessenger(); const messenger = getSnapControllerMessenger(rootMessenger); - const snapController = getSnapController( + const snapController = await getSnapController( getSnapControllerOptions({ messenger, state: { @@ -4702,7 +4702,7 @@ describe('SnapController', () => { it('filters out assets that are out of scope for `onAssetsMarketData`', async () => { const rootMessenger = getControllerMessenger(); const messenger = getSnapControllerMessenger(rootMessenger); - const snapController = getSnapController( + const snapController = await getSnapController( getSnapControllerOptions({ messenger, state: { @@ -4775,7 +4775,7 @@ describe('SnapController', () => { it('returns the value when `onAssetsMarketData` returns a valid response for fungible assets', async () => { const rootMessenger = getControllerMessenger(); const messenger = getSnapControllerMessenger(rootMessenger); - const snapController = getSnapController( + const snapController = await getSnapController( getSnapControllerOptions({ messenger, state: { @@ -4860,7 +4860,7 @@ describe('SnapController', () => { it('returns the value when `onAssetsMarketData` returns a valid response for non-fungible assets', async () => { const rootMessenger = getControllerMessenger(); const messenger = getSnapControllerMessenger(rootMessenger); - const snapController = getSnapController( + const snapController = await getSnapController( getSnapControllerOptions({ messenger, state: { @@ -4984,7 +4984,7 @@ describe('SnapController', () => { it('throws if `onAssetHistoricalPrice` handler returns an invalid response', async () => { const rootMessenger = getControllerMessenger(); const messenger = getSnapControllerMessenger(rootMessenger); - const snapController = getSnapController( + const snapController = await getSnapController( getSnapControllerOptions({ messenger, state: { @@ -5046,7 +5046,7 @@ describe('SnapController', () => { it('returns the value when `onAssetHistoricalPrice` returns a valid response', async () => { const rootMessenger = getControllerMessenger(); const messenger = getSnapControllerMessenger(rootMessenger); - const snapController = getSnapController( + const snapController = await getSnapController( getSnapControllerOptions({ messenger, state: { @@ -5123,7 +5123,7 @@ describe('SnapController', () => { it('returns the value when `onClientRequest` returns a valid response', async () => { const rootMessenger = getControllerMessenger(); const messenger = getSnapControllerMessenger(rootMessenger); - const snapController = getSnapController( + const snapController = await getSnapController( getSnapControllerOptions({ messenger, state: { @@ -5166,7 +5166,7 @@ describe('SnapController', () => { it('throws if the origin is not "metamask"', async () => { const rootMessenger = getControllerMessenger(); const messenger = getSnapControllerMessenger(rootMessenger); - const snapController = getSnapController( + const snapController = await getSnapController( getSnapControllerOptions({ messenger, state: { @@ -5215,7 +5215,7 @@ describe('SnapController', () => { snaps: getPersistedSnapsState(), }, }); - const [snapController, service] = getSnapControllerWithEES(options); + const [snapController, service] = await getSnapControllerWithEES(options); rootMessenger.registerActionHandler( 'PermissionController:hasPermission', @@ -5259,7 +5259,7 @@ describe('SnapController', () => { it('handlers throw if the request has an invalid "jsonrpc" property', async () => { const fakeSnap = getPersistedSnapObject({ status: SnapStatus.Running }); const snapId = fakeSnap.id; - const snapController = getSnapController( + const snapController = await getSnapController( getSnapControllerOptions({ state: { snaps: { @@ -5292,7 +5292,7 @@ describe('SnapController', () => { it('handlers throw if the request is not valid JSON', async () => { const fakeSnap = getPersistedSnapObject({ status: SnapStatus.Running }); const snapId = fakeSnap.id; - const snapController = getSnapController( + const snapController = await getSnapController( getSnapControllerOptions({ state: { snaps: { @@ -5331,7 +5331,7 @@ describe('SnapController', () => { `, }); - const [snapController, service] = getSnapControllerWithEES( + const [snapController, service] = await getSnapControllerWithEES( getSnapControllerWithEESOptions({ detectSnapLocation: loopbackDetect({ manifest, @@ -5388,7 +5388,7 @@ describe('SnapController', () => { `, }); - const [snapController, service] = getSnapControllerWithEES( + const [snapController, service] = await getSnapControllerWithEES( getSnapControllerWithEESOptions({ idleTimeCheckInterval: 10, maxIdleTime: 50, @@ -5434,7 +5434,7 @@ describe('SnapController', () => { const snapObject = getPersistedSnapObject(); const truncatedSnap = getTruncatedSnap(); - const snapController = getSnapController( + const snapController = await getSnapController( getSnapControllerOptions({ messenger, state: { @@ -5470,7 +5470,7 @@ describe('SnapController', () => { shouldAlwaysReload: true, }); - const snapController = getSnapController( + const snapController = await getSnapController( getSnapControllerOptions({ messenger, state: { @@ -5619,7 +5619,7 @@ describe('SnapController', () => { .mockImplementationOnce(async () => Promise.resolve(manifest)) .mockImplementationOnce(async () => Promise.resolve(newManifest)); - const snapController = getSnapController( + const snapController = await getSnapController( getSnapControllerOptions({ messenger, detectSnapLocation: loopbackDetect(location), @@ -5851,7 +5851,7 @@ describe('SnapController', () => { shouldAlwaysReload: true, }); - const snapController = getSnapController( + const snapController = await getSnapController( getSnapControllerOptions({ messenger, state: { @@ -5927,7 +5927,7 @@ describe('SnapController', () => { shouldAlwaysReload: true, }); - const snapController = getSnapController( + const snapController = await getSnapController( getSnapControllerOptions({ messenger, state: { @@ -5973,7 +5973,7 @@ describe('SnapController', () => { }), }); - const snapController = getSnapController( + const snapController = await getSnapController( getSnapControllerOptions({ messenger, detectSnapLocation: loopbackDetect({ manifest }), @@ -6030,7 +6030,7 @@ describe('SnapController', () => { const snapId = `${MOCK_SNAP_ID}_foo`; - const snapController = getSnapController( + const snapController = await getSnapController( getSnapControllerOptions({ messenger, detectSnapLocation: loopbackDetect({ manifest }), @@ -6108,7 +6108,9 @@ describe('SnapController', () => { preinstalledSnaps, rootMessenger, }); - const [snapController] = getSnapControllerWithEES(snapControllerOptions); + const [snapController] = await getSnapControllerWithEES( + snapControllerOptions, + ); expect(snapControllerOptions.messenger.call).toHaveBeenCalledWith( 'PermissionController:grantPermissions', @@ -6218,7 +6220,9 @@ describe('SnapController', () => { preinstalledSnaps, rootMessenger, }); - const [snapController] = getSnapControllerWithEES(snapControllerOptions); + const [snapController] = await getSnapControllerWithEES( + snapControllerOptions, + ); expect(snapControllerOptions.messenger.call).not.toHaveBeenCalledWith( 'PermissionController:revokePermissions', @@ -6284,7 +6288,9 @@ describe('SnapController', () => { preinstalledSnaps, rootMessenger, }); - const [snapController] = getSnapControllerWithEES(snapControllerOptions); + const [snapController] = await getSnapControllerWithEES( + snapControllerOptions, + ); const approvedPermissions = { [WALLET_SNAP_PERMISSION_KEY]: { @@ -6359,7 +6365,9 @@ describe('SnapController', () => { preinstalledSnaps, rootMessenger, }); - const [snapController] = getSnapControllerWithEES(snapControllerOptions); + const [snapController] = await getSnapControllerWithEES( + snapControllerOptions, + ); expect(snapControllerOptions.messenger.call).toHaveBeenCalledWith( 'PermissionController:grantPermissions', @@ -6456,7 +6464,9 @@ describe('SnapController', () => { ), }, }); - const [snapController] = getSnapControllerWithEES(snapControllerOptions); + const [snapController] = await getSnapControllerWithEES( + snapControllerOptions, + ); expect(snapControllerOptions.messenger.call).toHaveBeenCalledWith( 'PermissionController:revokePermissions', @@ -6527,7 +6537,9 @@ describe('SnapController', () => { snaps: getPersistedSnapsState(), }, }); - const [snapController] = getSnapControllerWithEES(snapControllerOptions); + const [snapController] = await getSnapControllerWithEES( + snapControllerOptions, + ); expect(snapControllerOptions.messenger.call).toHaveBeenCalledTimes(0); @@ -6590,7 +6602,9 @@ describe('SnapController', () => { preinstalledSnaps, rootMessenger, }); - const [snapController] = getSnapControllerWithEES(snapControllerOptions); + const [snapController] = await getSnapControllerWithEES( + snapControllerOptions, + ); expect(snapControllerOptions.messenger.call).toHaveBeenCalledWith( 'PermissionController:grantPermissions', @@ -6688,7 +6702,9 @@ describe('SnapController', () => { rootMessenger, detectSnapLocation, }); - const [snapController] = getSnapControllerWithEES(snapControllerOptions); + const [snapController] = await getSnapControllerWithEES( + snapControllerOptions, + ); await expect( snapController.installSnaps(MOCK_ORIGIN, { @@ -6744,7 +6760,9 @@ describe('SnapController', () => { preinstalledSnaps, rootMessenger, }); - const [snapController] = getSnapControllerWithEES(snapControllerOptions); + const [snapController] = await getSnapControllerWithEES( + snapControllerOptions, + ); expect(snapController.get(MOCK_SNAP_ID)?.hidden).toBe(true); @@ -6796,7 +6814,9 @@ describe('SnapController', () => { preinstalledSnaps, rootMessenger, }); - const [snapController] = getSnapControllerWithEES(snapControllerOptions); + const [snapController] = await getSnapControllerWithEES( + snapControllerOptions, + ); expect(snapController.get(MOCK_SNAP_ID)?.hideSnapBranding).toBe(true); @@ -6836,7 +6856,9 @@ describe('SnapController', () => { preinstalledSnaps, rootMessenger, }); - const [snapController] = getSnapControllerWithEES(snapControllerOptions); + const [snapController] = await getSnapControllerWithEES( + snapControllerOptions, + ); expect(log).toHaveBeenCalledWith( 'The permissions for "npm:@metamask/example-snap" were out of sync and have been automatically restored. If you see this message, please file a bug report.', @@ -6924,7 +6946,9 @@ describe('SnapController', () => { preinstalledSnaps, rootMessenger, }); - const [snapController] = getSnapControllerWithEES(snapControllerOptions); + const [snapController] = await getSnapControllerWithEES( + snapControllerOptions, + ); expect(log).toHaveBeenCalledWith( 'The permissions for "npm:@metamask/example-snap" were out of sync and have been automatically restored. If you see this message, please file a bug report.', @@ -7013,7 +7037,7 @@ describe('SnapController', () => { ]; const messenger = getSnapControllerMessenger(rootMessenger); - const snapController = getSnapController( + const snapController = await getSnapController( getSnapControllerOptions({ messenger, preinstalledSnaps }), ); @@ -7071,7 +7095,7 @@ describe('SnapController', () => { ]; const messenger = getSnapControllerMessenger(rootMessenger); - const snapController = getSnapController( + const snapController = await getSnapController( getSnapControllerOptions({ messenger, preinstalledSnaps, @@ -7107,7 +7131,7 @@ describe('SnapController', () => { const manifest = getSnapManifest(); const rootMessenger = getControllerMessenger(); const messenger = getSnapControllerMessenger(rootMessenger); - const snapController = getSnapController( + const snapController = await getSnapController( getSnapControllerOptions({ messenger, detectSnapLocation: loopbackDetect({ manifest }), @@ -7241,7 +7265,7 @@ describe('SnapController', () => { }); const messenger = getSnapControllerMessenger(); - const controller = getSnapController( + const controller = await getSnapController( getSnapControllerOptions({ messenger, detectSnapLocation: loopbackDetect({ @@ -7273,7 +7297,7 @@ describe('SnapController', () => { }); const messenger = getSnapControllerMessenger(); - const controller = getSnapController( + const controller = await getSnapController( getSnapControllerOptions({ messenger, detectSnapLocation: loopbackDetect({ @@ -7302,7 +7326,7 @@ describe('SnapController', () => { }); const messenger = getSnapControllerMessenger(); - const controller = getSnapController( + const controller = await getSnapController( getSnapControllerOptions({ messenger, detectSnapLocation: loopbackDetect({ @@ -7331,7 +7355,7 @@ describe('SnapController', () => { }); const messenger = getSnapControllerMessenger(); - const controller = getSnapController( + const controller = await getSnapController( getSnapControllerOptions({ messenger, detectSnapLocation: loopbackDetect({ @@ -7363,7 +7387,7 @@ describe('SnapController', () => { }); const messenger = getSnapControllerMessenger(); - const controller = getSnapController( + const controller = await getSnapController( getSnapControllerOptions({ messenger, featureFlags: { @@ -7405,7 +7429,7 @@ describe('SnapController', () => { }); const messenger = getSnapControllerMessenger(); - const snapController = getSnapController( + const snapController = await getSnapController( getSnapControllerOptions({ messenger, detectSnapLocation: loopbackDetect({ @@ -7526,7 +7550,7 @@ describe('SnapController', () => { }); const messenger = getSnapControllerMessenger(); - const snapController = getSnapController( + const snapController = await getSnapController( getSnapControllerOptions({ messenger, detectSnapLocation: loopbackDetect({ @@ -7630,7 +7654,7 @@ describe('SnapController', () => { const rootMessenger = getControllerMessenger(); const messenger = getSnapControllerMessenger(rootMessenger); - const snapController = getSnapController( + const snapController = await getSnapController( getSnapControllerOptions({ messenger, state: { @@ -7710,7 +7734,7 @@ describe('SnapController', () => { const rootMessenger = getControllerMessenger(); const messenger = getSnapControllerMessenger(rootMessenger); - const snapController = getSnapController( + const snapController = await getSnapController( getSnapControllerOptions({ messenger, state: { @@ -7767,7 +7791,7 @@ describe('SnapController', () => { it('returns an error on invalid snap id', async () => { const snapId = 'foo'; const messenger = getSnapControllerMessenger(); - const controller = getSnapController( + const controller = await getSnapController( getSnapControllerOptions({ messenger }), ); await expect( @@ -7809,7 +7833,7 @@ describe('SnapController', () => { }), ); - const controller = getSnapController( + const controller = await getSnapController( getSnapControllerOptions({ messenger, detectSnapLocation: detectLocationMock, @@ -7979,7 +8003,7 @@ describe('SnapController', () => { manifest: manifest.result, }); const messenger = getSnapControllerMessenger(); - const controller = getSnapController( + const controller = await getSnapController( getSnapControllerOptions({ messenger, state: { @@ -8029,7 +8053,7 @@ describe('SnapController', () => { Promise.reject(new Error('foo')), ); const detect = loopbackDetect(location); - const controller = getSnapController( + const controller = await getSnapController( getSnapControllerOptions({ messenger, state: { @@ -8164,7 +8188,7 @@ describe('SnapController', () => { const { messenger } = options; - const [controller, service] = getSnapControllerWithEES(options); + const [controller, service] = await getSnapControllerWithEES(options); await controller.installSnaps(MOCK_ORIGIN, { [snapId1]: {} }); await controller.installSnaps(MOCK_ORIGIN, { [snapId2]: {} }); @@ -8265,7 +8289,7 @@ describe('SnapController', () => { detectSnapLocation: detect, }); const { messenger } = options; - const [controller, service] = getSnapControllerWithEES(options); + const [controller, service] = await getSnapControllerWithEES(options); const listener = jest.fn(); messenger.subscribe('SnapController:snapRolledback' as any, listener); @@ -8316,7 +8340,7 @@ describe('SnapController', () => { }), }); - const controller = getSnapController( + const controller = await getSnapController( getSnapControllerOptions({ detectSnapLocation: loopbackDetect({ manifest, @@ -8344,7 +8368,7 @@ describe('SnapController', () => { localizationFiles: [getMockLocalizationFile()], }); - const snapController = getSnapController( + const snapController = await getSnapController( getSnapControllerOptions({ messenger, detectSnapLocation: loopbackDetect({ @@ -8392,7 +8416,7 @@ describe('SnapController', () => { localizationFiles: [getMockLocalizationFile({ messages: {} })], }); - const snapController = getSnapController( + const snapController = await getSnapController( getSnapControllerOptions({ messenger, detectSnapLocation: loopbackDetect({ @@ -8415,7 +8439,7 @@ describe('SnapController', () => { it('installs a local Snap as preinstalled Snap when `forcePreinstalledSnaps` is enabled', async () => { const messenger = getSnapControllerMessenger(); - const snapController = getSnapController( + const snapController = await getSnapController( getSnapControllerOptions({ messenger, detectSnapLocation: loopbackDetect(), @@ -8455,7 +8479,7 @@ describe('SnapController', () => { }); const messenger = getSnapControllerMessenger(); - const snapController = getSnapController( + const snapController = await getSnapController( getSnapControllerOptions({ messenger, detectSnapLocation: loopbackDetect(location), @@ -8497,7 +8521,7 @@ describe('SnapController', () => { sourceCode: 'a'.repeat(64_000_001), }); - const snapController = getSnapController( + const snapController = await getSnapController( getSnapControllerOptions({ messenger, detectSnapLocation: loopbackDetect({ @@ -8529,7 +8553,7 @@ describe('SnapController', () => { manifest: manifest.result, }); const messenger = getSnapControllerMessenger(); - const controller = getSnapController( + const controller = await getSnapController( getSnapControllerOptions({ messenger, state: { @@ -8571,7 +8595,7 @@ describe('SnapController', () => { const detectSnapLocation = loopbackDetect({ manifest: manifest.result, }); - const controller = getSnapController( + const controller = await getSnapController( getSnapControllerOptions({ messenger, state: { @@ -8604,7 +8628,7 @@ describe('SnapController', () => { manifest: manifest.result, }); const messenger = getSnapControllerMessenger(); - const controller = getSnapController( + const controller = await getSnapController( getSnapControllerOptions({ messenger, state: { @@ -8666,7 +8690,7 @@ describe('SnapController', () => { }), ); const messenger = getSnapControllerMessenger(); - const controller = getSnapController( + const controller = await getSnapController( getSnapControllerOptions({ messenger, detectSnapLocation, @@ -8850,7 +8874,7 @@ describe('SnapController', () => { }), ); - const controller = getSnapController( + const controller = await getSnapController( getSnapControllerOptions({ messenger, detectSnapLocation, @@ -8966,7 +8990,7 @@ describe('SnapController', () => { }), ); - const controller = getSnapController( + const controller = await getSnapController( getSnapControllerOptions({ messenger, detectSnapLocation, @@ -9055,7 +9079,7 @@ describe('SnapController', () => { }), ); const messenger = getSnapControllerMessenger(); - const controller = getSnapController( + const controller = await getSnapController( getSnapControllerOptions({ messenger, state: { @@ -9104,7 +9128,7 @@ describe('SnapController', () => { manifest: manifest.result, }); const messenger = getSnapControllerMessenger(); - const controller = getSnapController( + const controller = await getSnapController( getSnapControllerOptions({ messenger, state: { @@ -9235,7 +9259,7 @@ describe('SnapController', () => { const detectSnapLocation = loopbackDetect({ manifest: manifest.result, }); - const controller = getSnapController( + const controller = await getSnapController( getSnapControllerOptions({ messenger, state: { @@ -9434,7 +9458,7 @@ describe('SnapController', () => { }), ); - const controller = getSnapController( + const controller = await getSnapController( getSnapControllerOptions({ messenger, detectSnapLocation: detect }), ); @@ -9650,7 +9674,7 @@ describe('SnapController', () => { }), }); - const snapController = getSnapController( + const snapController = await getSnapController( getSnapControllerOptions({ messenger, state: { @@ -9797,7 +9821,7 @@ describe('SnapController', () => { ); /* eslint-enable @typescript-eslint/naming-convention */ - const snapController = getSnapController( + const snapController = await getSnapController( getSnapControllerOptions({ messenger, detectSnapLocation: detect, @@ -9862,7 +9886,7 @@ describe('SnapController', () => { manifest: manifest.result, }); const messenger = getSnapControllerMessenger(); - const controller = getSnapController( + const controller = await getSnapController( getSnapControllerOptions({ messenger, state: { @@ -9886,7 +9910,7 @@ describe('SnapController', () => { describe('removeSnap', () => { it('will remove the "wallet_snap" permission from a subject that no longer has any permitted snaps', async () => { const messenger = getSnapControllerMessenger(); - const snapController = getSnapController( + const snapController = await getSnapController( getSnapControllerOptions({ messenger, state: { @@ -9934,7 +9958,7 @@ describe('SnapController', () => { it('will update the "wallet_snap" permission from a subject that has one or more permitted snaps', async () => { const messenger = getSnapControllerMessenger(); - const snapController = getSnapController( + const snapController = await getSnapController( getSnapControllerOptions({ messenger, state: { @@ -9989,7 +10013,7 @@ describe('SnapController', () => { it("will skip subjects that don't have the snap permission", async () => { const messenger = getSnapControllerMessenger(); - const snapController = getSnapController( + const snapController = await getSnapController( getSnapControllerOptions({ messenger, state: { @@ -10047,7 +10071,7 @@ describe('SnapController', () => { it('removes snap state', async () => { const messenger = getSnapControllerMessenger(); - const snapController = getSnapController( + const snapController = await getSnapController( getSnapControllerOptions({ messenger, state: { @@ -10071,9 +10095,9 @@ describe('SnapController', () => { }); describe('enableSnap', () => { - it('enables a disabled snap', () => { + it('enables a disabled snap', async () => { const messenger = getSnapControllerMessenger(); - const snapController = getSnapController( + const snapController = await getSnapController( getSnapControllerOptions({ state: { snaps: getPersistedSnapsState( @@ -10096,8 +10120,8 @@ describe('SnapController', () => { snapController.destroy(); }); - it('throws an error if the specified snap does not exist', () => { - const snapController = getSnapController(); + it('throws an error if the specified snap does not exist', async () => { + const snapController = await getSnapController(); expect(() => snapController.enableSnap(MOCK_SNAP_ID)).toThrow( `Snap "${MOCK_SNAP_ID}" not found.`, ); @@ -10105,8 +10129,8 @@ describe('SnapController', () => { snapController.destroy(); }); - it('throws an error if the specified snap is blocked', () => { - const snapController = getSnapController( + it('throws an error if the specified snap is blocked', async () => { + const snapController = await getSnapController( getSnapControllerOptions({ state: { snaps: getPersistedSnapsState( @@ -10127,7 +10151,7 @@ describe('SnapController', () => { describe('disableSnap', () => { it('disables a snap', async () => { const messenger = getSnapControllerMessenger(); - const snapController = getSnapController( + const snapController = await getSnapController( getSnapControllerOptions({ state: { snaps: getPersistedSnapsState(), @@ -10149,7 +10173,7 @@ describe('SnapController', () => { }); it('stops a running snap when disabling it', async () => { - const snapController = getSnapController( + const snapController = await getSnapController( getSnapControllerOptions({ state: { snaps: getPersistedSnapsState(), @@ -10170,7 +10194,7 @@ describe('SnapController', () => { }); it('throws an error if the specified snap does not exist', async () => { - const snapController = getSnapController(); + const snapController = await getSnapController(); await expect(snapController.disableSnap(MOCK_SNAP_ID)).rejects.toThrow( `Snap "${MOCK_SNAP_ID}" not found.`, ); @@ -10185,7 +10209,7 @@ describe('SnapController', () => { const rootMessenger = getControllerMessenger(registry); const messenger = getSnapControllerMessenger(rootMessenger); - const snapController = getSnapController( + const snapController = await getSnapController( getSnapControllerOptions({ messenger, state: { @@ -10216,7 +10240,7 @@ describe('SnapController', () => { origin: 'bar.io', }); - const snapController = getSnapController( + const snapController = await getSnapController( getSnapControllerOptions({ messenger, state: { @@ -10281,7 +10305,7 @@ describe('SnapController', () => { origin: MOCK_ORIGIN, }); - const snapController = getSnapController( + const snapController = await getSnapController( getSnapControllerOptions({ messenger, state: { @@ -10324,7 +10348,7 @@ describe('SnapController', () => { origin: 'bar.io', }); - const snapController = getSnapController( + const snapController = await getSnapController( getSnapControllerOptions({ messenger, state: { @@ -10379,7 +10403,7 @@ describe('SnapController', () => { origin: MOCK_ORIGIN, }); - const snapController = getSnapController( + const snapController = await getSnapController( getSnapControllerOptions({ messenger, state: { @@ -10423,7 +10447,7 @@ describe('SnapController', () => { origin: MOCK_ORIGIN, }); - const snapController = getSnapController( + const snapController = await getSnapController( getSnapControllerOptions({ messenger, state: { @@ -10497,7 +10521,7 @@ describe('SnapController', () => { ), }); - const snapController = getSnapController( + const snapController = await getSnapController( getSnapControllerOptions({ messenger, state: { @@ -10560,7 +10584,7 @@ describe('SnapController', () => { registry.resolveVersion.mockResolvedValue(updateVersion); - const snapController = getSnapController( + const snapController = await getSnapController( getSnapControllerOptions({ messenger, state: { @@ -10605,7 +10629,7 @@ describe('SnapController', () => { }, }, }); - const [snapController] = getSnapControllerWithEES(options); + const [snapController] = await getSnapControllerWithEES(options); const { messenger } = options; @@ -10707,7 +10731,7 @@ describe('SnapController', () => { ); const messenger = getSnapControllerMessenger(rootMessenger); - const snapController = getSnapController( + const snapController = await getSnapController( getSnapControllerOptions({ messenger, state: { @@ -10818,7 +10842,7 @@ describe('SnapController', () => { }, ); - const snapController = getSnapController( + const snapController = await getSnapController( getSnapControllerOptions({ messenger, state: { @@ -10893,7 +10917,7 @@ describe('SnapController', () => { }, ); - const snapController = getSnapController( + const snapController = await getSnapController( getSnapControllerOptions({ messenger, state: { @@ -10914,10 +10938,10 @@ describe('SnapController', () => { }); describe('SnapController:get', () => { - it('gets a snap', () => { + it('gets a snap', async () => { const messenger = getSnapControllerMessenger(); - const snapController = getSnapController( + const snapController = await getSnapController( getSnapControllerOptions({ messenger, state: { @@ -10940,7 +10964,7 @@ describe('SnapController', () => { it('handles a snap RPC request', async () => { const messenger = getSnapControllerMessenger(); - const snapController = getSnapController( + const snapController = await getSnapController( getSnapControllerOptions({ messenger, state: { @@ -10973,7 +10997,7 @@ describe('SnapController', () => { getNodeEESMessenger(rootMessenger), ) as unknown as NodeThreadExecutionService; - const [snapController] = getSnapControllerWithEES( + const [snapController] = await getSnapControllerWithEES( getSnapControllerWithEESOptions({ rootMessenger, trackEvent: mockTrackEvent, @@ -11039,7 +11063,7 @@ describe('SnapController', () => { getNodeEESMessenger(rootMessenger), ) as unknown as NodeThreadExecutionService; - const [snapController] = getSnapControllerWithEES( + const [snapController] = await getSnapControllerWithEES( getSnapControllerWithEESOptions({ environmentEndowmentPermissions: ['endowment:cronjob'], rootMessenger, @@ -11081,7 +11105,7 @@ describe('SnapController', () => { getNodeEESMessenger(rootMessenger), ) as unknown as NodeThreadExecutionService; - const [snapController] = getSnapControllerWithEES( + const [snapController] = await getSnapControllerWithEES( getSnapControllerWithEESOptions({ rootMessenger, trackEvent: mockTrackEvent, @@ -11123,7 +11147,7 @@ describe('SnapController', () => { getNodeEESMessenger(rootMessenger), ) as unknown as NodeThreadExecutionService; - const [snapController] = getSnapControllerWithEES( + const [snapController] = await getSnapControllerWithEES( getSnapControllerWithEESOptions({ rootMessenger, trackEvent: mockTrackEvent, @@ -11159,7 +11183,7 @@ describe('SnapController', () => { it('handles a transaction insight request', async () => { const messenger = getSnapControllerMessenger(); - const snapController = getSnapController( + const snapController = await getSnapController( getSnapControllerOptions({ messenger, state: { @@ -11188,7 +11212,7 @@ describe('SnapController', () => { it('handles a signature insight request', async () => { const messenger = getSnapControllerMessenger(); - const snapController = getSnapController( + const snapController = await getSnapController( getSnapControllerOptions({ messenger, state: { @@ -11217,7 +11241,7 @@ describe('SnapController', () => { it('handles a name lookup request', async () => { const messenger = getSnapControllerMessenger(); - const snapController = getSnapController( + const snapController = await getSnapController( getSnapControllerOptions({ messenger, state: { @@ -11266,7 +11290,7 @@ describe('SnapController', () => { DEFAULT_ENCRYPTION_KEY_DERIVATION_OPTIONS, ); - const snapController = getSnapController( + const snapController = await getSnapController( getSnapControllerOptions({ messenger, state: { @@ -11309,7 +11333,7 @@ describe('SnapController', () => { }, ); - const snapController = getSnapController( + const snapController = await getSnapController( getSnapControllerOptions({ messenger, state: { @@ -11363,7 +11387,7 @@ describe('SnapController', () => { const state = { foo: 'bar' }; - const snapController = getSnapController( + const snapController = await getSnapController( getSnapControllerOptions({ messenger, state: { @@ -11440,7 +11464,7 @@ describe('SnapController', () => { ), ); - const snapController = getSnapController( + const snapController = await getSnapController( getSnapControllerOptions({ messenger, state: { @@ -11464,7 +11488,7 @@ describe('SnapController', () => { it('throws an error if the state is corrupt', async () => { const messenger = getSnapControllerMessenger(); - const snapController = getSnapController( + const snapController = await getSnapController( getSnapControllerOptions({ messenger, state: { @@ -11494,7 +11518,7 @@ describe('SnapController', () => { const state = { foo: 'bar' }; - const snapController = getSnapController( + const snapController = await getSnapController( getSnapControllerOptions({ messenger, state: { @@ -11524,7 +11548,7 @@ describe('SnapController', () => { it(`returns null if the Snap has no state yet`, async () => { const messenger = getSnapControllerMessenger(); - const snapController = getSnapController( + const snapController = await getSnapController( getSnapControllerOptions({ messenger, state: { @@ -11552,10 +11576,10 @@ describe('SnapController', () => { }); describe('SnapController:has', () => { - it('checks if a snap exists in state', () => { + it('checks if a snap exists in state', async () => { const messenger = getSnapControllerMessenger(); const id = 'npm:fooSnap' as SnapId; - const snapController = getSnapController( + const snapController = await getSnapController( getSnapControllerOptions({ messenger, state: { @@ -11595,7 +11619,7 @@ describe('SnapController', () => { it(`updates the snap's state`, async () => { const messenger = getSnapControllerMessenger(); - const snapController = getSnapController( + const snapController = await getSnapController( getSnapControllerOptions({ messenger, state: { @@ -11636,7 +11660,7 @@ describe('SnapController', () => { it(`updates the snap's unencrypted state`, async () => { const messenger = getSnapControllerMessenger(); - const snapController = getSnapController( + const snapController = await getSnapController( getSnapControllerOptions({ messenger, state: { @@ -11677,7 +11701,7 @@ describe('SnapController', () => { hmac(sha512, key, data), ); - const snapController = getSnapController( + const snapController = await getSnapController( getSnapControllerOptions({ messenger, state: { @@ -11713,7 +11737,7 @@ describe('SnapController', () => { const encryptor = getSnapControllerEncryptor(); const encryptWithKey = jest.spyOn(encryptor, 'encryptWithKey'); - const snapController = getSnapController( + const snapController = await getSnapController( getSnapControllerOptions({ messenger, state: { @@ -11781,7 +11805,7 @@ describe('SnapController', () => { const messenger = getSnapControllerMessenger(); const errorValue = new Error('Failed to persist state.'); - const snapController = getSnapController( + const snapController = await getSnapController( getSnapControllerOptions({ messenger, state: { @@ -11826,7 +11850,7 @@ describe('SnapController', () => { it('clears the state of a snap', async () => { const messenger = getSnapControllerMessenger(); - const snapController = getSnapController( + const snapController = await getSnapController( getSnapControllerOptions({ messenger, state: { @@ -11854,7 +11878,7 @@ describe('SnapController', () => { it('clears the unencrypted state of a snap', async () => { const messenger = getSnapControllerMessenger(); - const snapController = getSnapController( + const snapController = await getSnapController( getSnapControllerOptions({ messenger, state: { @@ -11883,7 +11907,7 @@ describe('SnapController', () => { const messenger = getSnapControllerMessenger(); const errorValue = new Error('Failed to persist state.'); - const snapController = getSnapController( + const snapController = await getSnapController( getSnapControllerOptions({ messenger, state: { @@ -11923,7 +11947,7 @@ describe('SnapController', () => { describe('SnapController:updateRegistry', () => { it('calls SnapController.updateRegistry()', async () => { const messenger = getSnapControllerMessenger(); - const snapController = getSnapController( + const snapController = await getSnapController( getSnapControllerOptions({ messenger, }), @@ -11941,7 +11965,7 @@ describe('SnapController', () => { }); describe('SnapController:enable', () => { - it('calls SnapController.enableSnap()', () => { + it('calls SnapController.enableSnap()', async () => { const messenger = getSnapControllerMessenger(); const mockSnap = getMockSnapData({ id: 'npm:example' as SnapId, @@ -11949,7 +11973,7 @@ describe('SnapController', () => { enabled: false, }); - const snapController = getSnapController( + const snapController = await getSnapController( getSnapControllerOptions({ messenger, state: { @@ -11974,7 +11998,7 @@ describe('SnapController', () => { enabled: true, }); - const snapController = getSnapController( + const snapController = await getSnapController( getSnapControllerOptions({ messenger, state: { @@ -11999,7 +12023,7 @@ describe('SnapController', () => { enabled: true, }); - const snapController = getSnapController( + const snapController = await getSnapController( getSnapControllerOptions({ messenger, state: { @@ -12024,7 +12048,7 @@ describe('SnapController', () => { origin: MOCK_ORIGIN, }); - const snapController = getSnapController( + const snapController = await getSnapController( getSnapControllerOptions({ messenger, state: { @@ -12049,14 +12073,14 @@ describe('SnapController', () => { }); describe('SnapController:getAllSnaps', () => { - it('calls SnapController.getAllSnaps()', () => { + it('calls SnapController.getAllSnaps()', async () => { const messenger = getSnapControllerMessenger(); const mockSnap = getMockSnapData({ id: MOCK_SNAP_ID, origin: MOCK_ORIGIN, }); - const snapController = getSnapController( + const snapController = await getSnapController( getSnapControllerOptions({ messenger, state: { @@ -12073,7 +12097,7 @@ describe('SnapController', () => { }); describe('SnapController:getRunnableSnaps', () => { - it('calls SnapController.getRunnableSnaps()', () => { + it('calls SnapController.getRunnableSnaps()', async () => { const messenger = getSnapControllerMessenger(); const mockSnap = getMockSnapData({ id: MOCK_SNAP_ID, @@ -12085,7 +12109,7 @@ describe('SnapController', () => { enabled: false, }); - const snapController = getSnapController( + const snapController = await getSnapController( getSnapControllerOptions({ messenger, state: { @@ -12107,7 +12131,7 @@ describe('SnapController', () => { describe('SnapController:install', () => { it('calls SnapController.installSnaps()', async () => { const messenger = getSnapControllerMessenger(); - const snapController = getSnapController( + const snapController = await getSnapController( getSnapControllerOptions({ messenger, }), @@ -12127,7 +12151,7 @@ describe('SnapController', () => { }); describe('SnapController:disconnectOrigin', () => { - it('calls SnapController.removeSnapFromSubject()', () => { + it('calls SnapController.removeSnapFromSubject()', async () => { const messenger = getSnapControllerMessenger(); const permittedSnaps = [ MOCK_SNAP_ID, @@ -12141,7 +12165,7 @@ describe('SnapController', () => { getPersistedSnapObject({ id: snapId as SnapId }), ); const snaps = getPersistedSnapsState(...snapObjects); - const snapController = getSnapController( + const snapController = await getSnapController( getSnapControllerOptions({ messenger, state: { @@ -12186,9 +12210,9 @@ describe('SnapController', () => { }); describe('SnapController:revokeDynamicPermissions', () => { - it('calls PermissionController:revokePermissions', () => { + it('calls PermissionController:revokePermissions', async () => { const messenger = getSnapControllerMessenger(); - const snapController = getSnapController( + const snapController = await getSnapController( getSnapControllerOptions({ messenger, }), @@ -12208,9 +12232,9 @@ describe('SnapController', () => { snapController.destroy(); }); - it('throws if input permission is not a dynamic permission', () => { + it('throws if input permission is not a dynamic permission', async () => { const messenger = getSnapControllerMessenger(); - const snapController = getSnapController( + const snapController = await getSnapController( getSnapControllerOptions({ messenger, }), @@ -12242,7 +12266,7 @@ describe('SnapController', () => { const messenger = getSnapControllerMessenger(); - const snapController = getSnapController( + const snapController = await getSnapController( getSnapControllerOptions({ messenger, detectSnapLocation: loopbackDetect({ @@ -12295,7 +12319,7 @@ describe('SnapController', () => { const messenger = getSnapControllerMessenger(); - const snapController = getSnapController( + const snapController = await getSnapController( getSnapControllerOptions({ messenger, detectSnapLocation: loopbackDetect({ @@ -12327,7 +12351,7 @@ describe('SnapController', () => { it('returns null if file does not exist', async () => { const messenger = getSnapControllerMessenger(); - const snapController = getSnapController( + const snapController = await getSnapController( getSnapControllerOptions({ messenger, detectSnapLocation: loopbackDetect(), @@ -12363,7 +12387,7 @@ describe('SnapController', () => { const messenger = getSnapControllerMessenger(); - const snapController = getSnapController( + const snapController = await getSnapController( getSnapControllerOptions({ messenger, detectSnapLocation: loopbackDetect({ @@ -12408,7 +12432,7 @@ describe('SnapController', () => { it('calls the `onInstall` lifecycle hook', async () => { const rootMessenger = getControllerMessenger(); const messenger = getSnapControllerMessenger(rootMessenger); - const snapController = getSnapController( + const snapController = await getSnapController( getSnapControllerOptions({ messenger, state: { @@ -12468,7 +12492,7 @@ describe('SnapController', () => { it('does not call the `onInstall` lifecycle hook if the snap does not have the `endowment:lifecycle-hooks` permission', async () => { const rootMessenger = getControllerMessenger(); const messenger = getSnapControllerMessenger(rootMessenger); - const snapController = getSnapController( + const snapController = await getSnapController( getSnapControllerOptions({ messenger, state: { @@ -12513,7 +12537,7 @@ describe('SnapController', () => { const rootMessenger = getControllerMessenger(); const messenger = getSnapControllerMessenger(rootMessenger); - const snapController = getSnapController( + const snapController = await getSnapController( getSnapControllerOptions({ messenger, state: { @@ -12550,7 +12574,7 @@ describe('SnapController', () => { it('calls the `onUpdate` lifecycle hook', async () => { const rootMessenger = getControllerMessenger(); const messenger = getSnapControllerMessenger(rootMessenger); - const snapController = getSnapController( + const snapController = await getSnapController( getSnapControllerOptions({ messenger, state: { @@ -12611,7 +12635,7 @@ describe('SnapController', () => { it('does not call the `onUpdate` lifecycle hook if the snap does not have the `endowment:lifecycle-hooks` permission', async () => { const rootMessenger = getControllerMessenger(); const messenger = getSnapControllerMessenger(rootMessenger); - const snapController = getSnapController( + const snapController = await getSnapController( getSnapControllerOptions({ messenger, state: { @@ -12656,7 +12680,7 @@ describe('SnapController', () => { const rootMessenger = getControllerMessenger(); const messenger = getSnapControllerMessenger(rootMessenger); - const snapController = getSnapController( + const snapController = await getSnapController( getSnapControllerOptions({ messenger, state: { @@ -12701,7 +12725,7 @@ describe('SnapController', () => { origin: MOCK_ORIGIN, }); - const snapController = getSnapController( + const snapController = await getSnapController( getSnapControllerOptions({ messenger, state: { @@ -12732,7 +12756,7 @@ describe('SnapController', () => { platformVersion: '6.0.0' as SemVerVersion, }); - const snapController = getSnapController( + const snapController = await getSnapController( getSnapControllerOptions({ messenger, state: { @@ -12759,7 +12783,7 @@ describe('SnapController', () => { platformVersion: '6.0.0' as SemVerVersion, }); - const snapController = getSnapController( + const snapController = await getSnapController( getSnapControllerOptions({ messenger, state: { @@ -12786,7 +12810,7 @@ describe('SnapController', () => { platformVersion: '6.0.0' as SemVerVersion, }); - const snapController = getSnapController( + const snapController = await getSnapController( getSnapControllerOptions({ messenger, state: { @@ -12812,7 +12836,7 @@ describe('SnapController', () => { const manifest = getSnapManifest(); delete manifest.platformVersion; - const snapController = getSnapController( + const snapController = await getSnapController( getSnapControllerOptions({ messenger, state: { @@ -12864,7 +12888,7 @@ describe('SnapController', () => { }, }); - const snapController = getSnapController( + const snapController = await getSnapController( getSnapControllerOptions({ messenger, state: { @@ -12950,7 +12974,7 @@ describe('SnapController', () => { }, }); - const snapController = getSnapController( + const snapController = await getSnapController( getSnapControllerOptions({ messenger, state: { @@ -13008,8 +13032,8 @@ describe('SnapController', () => { }); describe('metadata', () => { - it('includes expected state in debug snapshots', () => { - const controller = getSnapController(); + it('includes expected state in debug snapshots', async () => { + const controller = await getSnapController(); expect( deriveStateFromMetadata( @@ -13025,8 +13049,8 @@ describe('SnapController', () => { }); describe('includeInStateLogs', () => { - it('includes expected state in state logs', () => { - const controller = getSnapController(); + it('includes expected state in state logs', async () => { + const controller = await getSnapController(); expect( deriveStateFromMetadata( @@ -13042,13 +13066,13 @@ describe('SnapController', () => { `); }); - it('strips out large state properties', () => { + it('strips out large state properties', async () => { const id = 'npm:foo' as SnapId; const auxiliaryFile = new VirtualFile({ path: 'src/foo.json', value: stringToBytes('{ "foo" : "bar" }'), }); - const controller = getSnapController( + const controller = await getSnapController( getSnapControllerOptions({ state: { snaps: getPersistedSnapsState( @@ -13110,8 +13134,8 @@ describe('SnapController', () => { }); describe('persist', () => { - it('persists expected state', () => { - const controller = getSnapController(); + it('persists expected state', async () => { + const controller = await getSnapController(); expect( deriveStateFromMetadata( @@ -13130,7 +13154,7 @@ describe('SnapController', () => { it('can rehydrate state', async () => { const id = 'npm:foo' as SnapId; - const firstSnapController = getSnapController( + const firstSnapController = await getSnapController( getSnapControllerOptions({ state: { snaps: getPersistedSnapsState( @@ -13153,7 +13177,7 @@ describe('SnapController', () => { ); // create a new controller - const secondSnapController = getSnapController( + const secondSnapController = await getSnapController( getSnapControllerOptions({ state: persistedState as PersistedSnapControllerState, }), @@ -13169,7 +13193,7 @@ describe('SnapController', () => { }); it('does not persist snaps in the installing state', async () => { - const firstSnapController = getSnapController( + const firstSnapController = await getSnapController( getSnapControllerOptions({ state: { snaps: getPersistedSnapsState( @@ -13193,7 +13217,7 @@ describe('SnapController', () => { ); // create a new controller - const secondSnapController = getSnapController( + const secondSnapController = await getSnapController( getSnapControllerOptions({ state: persistedState as PersistedSnapControllerState, }), @@ -13205,8 +13229,8 @@ describe('SnapController', () => { }); }); - it('exposes expected state to UI', () => { - const controller = getSnapController(); + it('exposes expected state to UI', async () => { + const controller = await getSnapController(); expect( deriveStateFromMetadata( diff --git a/packages/snaps-controllers/src/snaps/SnapController.ts b/packages/snaps-controllers/src/snaps/SnapController.ts index 05877f7d82..b0801fc21f 100644 --- a/packages/snaps-controllers/src/snaps/SnapController.ts +++ b/packages/snaps-controllers/src/snaps/SnapController.ts @@ -77,6 +77,8 @@ import type { StatusContext, StatusEvents, StatusStates, + StorageServiceSnapData, + StoredSnap, TruncatedSnap, TruncatedSnapFields, } from '@metamask/snaps-utils'; @@ -238,7 +240,7 @@ export type SnapRuntimeData = { /** * A promise that resolves when the Snap has finished installing */ - installPromise: null | Promise; + installPromise: null | Promise; /** * A promise that resolves when the Snap has finished booting @@ -339,6 +341,7 @@ type RollbackSnapshot = { granted?: RequestedPermissions; requestData?: Record; }; + previousSourceCode: string; previousInitialConnections?: Record | null; newInitialConnections?: Record; newVersion: string; @@ -1339,20 +1342,39 @@ export class SnapController extends BaseController< /** * Initialise the SnapController. * - * Currently this method sets up the preinstalled snaps and calls the `onStart` lifecycle hook for all + * Currently this method sets up the controller and calls the `onStart` lifecycle hook for all * runnable Snaps. */ - async init() { + init() { // Lazily populate the `isReady` state. this.#ensureCanUsePlatform().catch(logWarning); - if (this.#preinstalledSnaps) { - await this.#handlePreinstalledSnaps(this.#preinstalledSnaps); - } + this.#setup().catch((error) => { + logError('Error during SnapController initialization.', error); + }); this.#callLifecycleHooks(METAMASK_ORIGIN, HandlerType.OnStart); + } - this.#controllerSetup.resolve(); + /** + * Sets up the SnapController by handling preinstalled snaps. + * This method will resolve the controller setup promise when complete + * or reject the promise if there is an error. + * + * @throws If there is an error during setup. + */ + async #setup() { + try { + if (this.#preinstalledSnaps) { + await this.#handlePreinstalledSnaps(this.#preinstalledSnaps); + } + + this.#controllerSetup.resolve(); + } catch (error) { + this.#controllerSetup.reject(error); + + throw error; + } } async #handlePreinstalledSnaps(preinstalledSnaps: PreinstalledSnap[]) { @@ -3246,7 +3268,7 @@ export class SnapController extends BaseController< * version. * @returns The resulting snap object. */ - async #add(args: AddSnapArgs): Promise { + async #add(args: AddSnapArgs): Promise { const { id: snapId, location, versionRange } = args; this.#setupRuntime(snapId); @@ -3396,7 +3418,7 @@ export class SnapController extends BaseController< * @param args - The add snap args. * @returns The resulting snap object. */ - async #set(args: SetSnapArgs): Promise { + async #set(args: SetSnapArgs): Promise { const { id: snapId, origin, @@ -3478,8 +3500,6 @@ export class SnapController extends BaseController< // If the snap was blocked, it isn't any longer delete snap.blockInformation; - await this.#setSourceCode(snapId, sourceCode); - // store the snap back in state const { inversePatches } = this.update((state: any) => { state.snaps[snapId] = snap; @@ -3487,14 +3507,18 @@ export class SnapController extends BaseController< // checking for isUpdate here as this function is also used in // the install flow, we do not care to create snapshots for installs - // @TODO (guillaumerx): Find a way to add the sourceCode to the rollback snapshot if (isUpdate) { + const previousSourceCode = await this.#getSourceCode(snapId); const rollbackSnapshot = this.#getRollbackSnapshot(snapId); + if (rollbackSnapshot !== undefined) { rollbackSnapshot.statePatches = inversePatches; + rollbackSnapshot.previousSourceCode = previousSourceCode; } } + await this.#setSourceCode(snapId, sourceCode); + // In case the Snap uses a localized manifest, we need to get the // proposed name from the localized manifest. const { proposedName } = getLocalizedSnapManifest( @@ -4262,6 +4286,7 @@ export class SnapController extends BaseController< this.#rollbackSnapshots.set(snapId, { statePatches: [], + previousSourceCode: '', permissions: {}, newVersion: '', }); @@ -4303,12 +4328,15 @@ export class SnapController extends BaseController< permissions, previousInitialConnections, newInitialConnections, + previousSourceCode, } = rollbackSnapshot; if (statePatches?.length) { this.applyPatches(statePatches); } + await this.#setSourceCode(snapId, previousSourceCode); + // Reset snap status, as we may have been in another state when we stored state patches // But now we are 100% in a stopped state if (this.get(snapId)?.status !== SnapStatus.Stopped) { @@ -4696,46 +4724,55 @@ export class SnapController extends BaseController< * @param snapId - The snap ID. * @returns The source code for the snap. */ - async #getSourceCode(snapId: SnapId) { - const { result } = await this.messenger.call( + async #getSourceCode(snapId: SnapId): Promise { + const storage = await this.#getStorage(snapId); + + assert(storage?.sourceCode, `Source code for snap "${snapId}" not found.`); + + return storage?.sourceCode; + } + + async #getStorage(snapId: SnapId): Promise { + const { result, error } = await this.messenger.call( 'StorageService:getItem', this.name, snapId, ); - assert(result, `Source code for snap "${snapId}" not found.`); + assert(!error, `Error retrieving storage for snap "${snapId}": ${error}`); - return result as string; + return result as StorageServiceSnapData | null; } /** * Store the source code for a snap in storage. + * For now we call the StorageService with just the source code + * since that's all we store. * * @param snapId - The snap ID. * @param sourceCode - The source code for the snap. */ - async #setSourceCode(snapId: SnapId, sourceCode: string) { - await this.messenger.call( - 'StorageService:setItem', - this.name, - snapId, + async #setSourceCode(snapId: SnapId, sourceCode: string): Promise { + await this.messenger.call('StorageService:setItem', this.name, snapId, { sourceCode, - ); + }); } /** * Remove the source code for a snap from storage. + * Since we only store source code, this effectively + * removes all data for the snap for now. * * @param snapId - The snap ID. */ - async #removeSourceCode(snapId: SnapId) { + async #removeSourceCode(snapId: SnapId): Promise { await this.messenger.call('StorageService:removeItem', this.name, snapId); } /** - * Clear all snap source code from storage. + * Clear all snap data from the storage service. */ - async #clearStorageService() { + async #clearStorageService(): Promise { await this.messenger.call('StorageService:clear', this.name); } } diff --git a/packages/snaps-controllers/src/test-utils/controller.tsx b/packages/snaps-controllers/src/test-utils/controller.tsx index 51eda543aa..92785980c3 100644 --- a/packages/snaps-controllers/src/test-utils/controller.tsx +++ b/packages/snaps-controllers/src/test-utils/controller.tsx @@ -25,6 +25,7 @@ import { } from '@metamask/snaps-rpc-methods'; import type { SnapId } from '@metamask/snaps-sdk'; import { Text } from '@metamask/snaps-sdk/jsx'; +import type { PersistedSnap, Snap } from '@metamask/snaps-utils'; import { SnapCaveatType } from '@metamask/snaps-utils'; import { getPersistedSnapObject, @@ -35,6 +36,7 @@ import { MockControllerMessenger, TEST_SECRET_RECOVERY_PHRASE_SEED_BYTES, } from '@metamask/snaps-utils/test-utils'; +import { InMemoryStorageAdapter } from '@metamask/storage-service'; import type { Json } from '@metamask/utils'; import { MOCK_CRONJOB_PERMISSION } from './cronjob'; @@ -69,7 +71,7 @@ import type { SnapsRegistryActions, SnapsRegistryEvents, } from '../snaps'; -import { SnapController } from '../snaps'; +import { controllerName, SnapController } from '../snaps'; import type { KeyDerivationOptions } from '../types'; import type { WebSocketServiceActions, @@ -153,6 +155,8 @@ export class MockApprovalController { export const approvalControllerMock = new MockApprovalController(); +export const storageAdapter = new InMemoryStorageAdapter(); + export const snapDialogPermissionKey = 'snap_dialog'; export const MOCK_INTERFACE_ID = 'QovlAsV2Z3xLP5hsrVMsz'; @@ -452,6 +456,26 @@ export const getControllerMessenger = (registry = new MockSnapsRegistry()) => { }, ); + messenger.registerActionHandler( + 'StorageService:setItem', + storageAdapter.setItem.bind(storageAdapter), + ); + + messenger.registerActionHandler( + 'StorageService:getItem', + storageAdapter.getItem.bind(storageAdapter), + ); + + messenger.registerActionHandler( + 'StorageService:removeItem', + storageAdapter.removeItem.bind(storageAdapter), + ); + + messenger.registerActionHandler( + 'StorageService:clear', + storageAdapter.clear.bind(storageAdapter), + ); + jest.spyOn(messenger, 'call'); return messenger; @@ -497,6 +521,10 @@ export const getSnapControllerMessenger = ( 'SnapsRegistry:resolveVersion', 'SnapInterfaceController:createInterface', 'SnapInterfaceController:getInterface', + 'StorageService:setItem', + 'StorageService:getItem', + 'StorageService:removeItem', + 'StorageService:clear', ], events: [ 'ExecutionService:unhandledError', @@ -614,19 +642,94 @@ export const getSnapControllerWithEESOptions = ({ }; }; -export const getSnapController = (options = getSnapControllerOptions()) => { - return new SnapController(options); +export const hydrateStorageService = async ( + sourceCodes: Record, +) => { + await Promise.all( + Object.entries(sourceCodes).map(async ([snapId, sourceCode]) => { + await storageAdapter.setItem(controllerName, snapId, { sourceCode }); + }), + ); }; -export const getSnapControllerWithEES = ( +export const extractSourceCodeFromState = ( + state: PersistedSnapControllerState | undefined, +) => { + if (!state) { + return { state: undefined, sourceCodes: undefined }; + } + + const { snaps: snapControllerState, ...stateRest } = state; + + const { snaps, sourceCodes } = Object.entries(snapControllerState).reduce<{ + snaps: Record; + sourceCodes: Record; + }>( + (acc, [snapId, snap]) => { + const { sourceCode, ...rest } = snap; + + acc.snaps[snapId as SnapId] = rest; + acc.sourceCodes[snapId as SnapId] = sourceCode; + + return acc; + }, + { + snaps: {}, + sourceCodes: {}, + }, + ); + + const newState = { + snaps, + ...stateRest, + }; + + return { state: newState, sourceCodes }; +}; + +export const getSnapController = async ( + options = getSnapControllerOptions(), + init = true, +) => { + const { state, ...restOptions } = options; + const { state: snapControllerState, sourceCodes } = + extractSourceCodeFromState(state); + + const controller = new SnapController({ + state: snapControllerState, + ...restOptions, + }); + + if (sourceCodes) { + await hydrateStorageService(sourceCodes); + } + + if (init) { + controller.init(); + } + + return controller; +}; + +export const getSnapControllerWithEES = async ( options = getSnapControllerWithEESOptions(), service?: ReturnType, + init = true, ) => { const _service = // @ts-expect-error: TODO: Investigate type mismatch. service ?? getNodeEES(getNodeEESMessenger(options.rootMessenger)); + if (options.state?.snaps) { + await hydrateStorageService(options.state.snaps); + } + const controller = new SnapController(options); + + if (init) { + controller.init(); + } + return [controller, _service] as const; }; From c056319c8bedd13863f03af3a3b5be29cb2d1241 Mon Sep 17 00:00:00 2001 From: Guillaume Roux Date: Thu, 18 Dec 2025 11:15:09 +0100 Subject: [PATCH 3/6] WIP --- packages/snaps-utils/src/snaps.ts | 9 ++++++++- packages/snaps-utils/src/test-utils/snap.ts | 2 -- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/packages/snaps-utils/src/snaps.ts b/packages/snaps-utils/src/snaps.ts index e1af0bdf8c..57ed8e4f3a 100644 --- a/packages/snaps-utils/src/snaps.ts +++ b/packages/snaps-utils/src/snaps.ts @@ -91,10 +91,17 @@ export type SnapAuxiliaryFile = { */ export type SnapAuxilaryFile = SnapAuxiliaryFile; -export type PersistedSnap = Snap & { +/** + * A Snap's data as stored in the StorageService. + */ +export type StorageServiceSnapData = { sourceCode: string; }; +export type PersistedSnap = Snap; + +export type StoredSnap = PersistedSnap & StorageServiceSnapData; + /** * A Snap as it exists in {@link SnapController} state. */ diff --git a/packages/snaps-utils/src/test-utils/snap.ts b/packages/snaps-utils/src/test-utils/snap.ts index 2012112dc0..9976af03dc 100644 --- a/packages/snaps-utils/src/test-utils/snap.ts +++ b/packages/snaps-utils/src/test-utils/snap.ts @@ -59,7 +59,6 @@ export const getSnapObject = ({ enabled = true, id = MOCK_SNAP_ID, initialPermissions = getSnapManifest().initialPermissions, - sourceCode = DEFAULT_SNAP_BUNDLE, manifest = getSnapManifest(), status = SnapStatus.Stopped, version = getSnapManifest().version, @@ -74,7 +73,6 @@ export const getSnapObject = ({ return { blocked, initialPermissions, - sourceCode, id, version: version as SemVerVersion, manifest, From f820083e43c8e56533baa21aa3309038d7c7740a Mon Sep 17 00:00:00 2001 From: Guillaume Roux Date: Thu, 18 Dec 2025 11:37:21 +0100 Subject: [PATCH 4/6] rework is ready --- .../snaps-controllers/src/snaps/SnapController.ts | 13 ++++++++----- .../snaps-controllers/src/test-utils/controller.tsx | 2 +- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/packages/snaps-controllers/src/snaps/SnapController.ts b/packages/snaps-controllers/src/snaps/SnapController.ts index b0801fc21f..ed0c1f0deb 100644 --- a/packages/snaps-controllers/src/snaps/SnapController.ts +++ b/packages/snaps-controllers/src/snaps/SnapController.ts @@ -1346,9 +1346,6 @@ export class SnapController extends BaseController< * runnable Snaps. */ init() { - // Lazily populate the `isReady` state. - this.#ensureCanUsePlatform().catch(logWarning); - this.#setup().catch((error) => { logError('Error during SnapController initialization.', error); }); @@ -1369,6 +1366,8 @@ export class SnapController extends BaseController< await this.#handlePreinstalledSnaps(this.#preinstalledSnaps); } + await this.#platformIsReady(); + this.#controllerSetup.resolve(); } catch (error) { this.#controllerSetup.reject(error); @@ -1765,11 +1764,15 @@ export class SnapController extends BaseController< * Waits for onboarding and then asserts whether the Snaps platform is allowed to run. */ async #ensureCanUsePlatform() { - // Ensure the user has onboarded before allowing access to Snaps. - await this.#ensureOnboardingComplete(); + await this.#platformIsReady(); // Ensure the controller has finished setting up. await this.#controllerSetup.promise; + } + + async #platformIsReady() { + // Ensure the user has onboarded before allowing access to Snaps. + await this.#ensureOnboardingComplete(); const flags = this.#getFeatureFlags(); diff --git a/packages/snaps-controllers/src/test-utils/controller.tsx b/packages/snaps-controllers/src/test-utils/controller.tsx index 92785980c3..69192534a7 100644 --- a/packages/snaps-controllers/src/test-utils/controller.tsx +++ b/packages/snaps-controllers/src/test-utils/controller.tsx @@ -25,7 +25,7 @@ import { } from '@metamask/snaps-rpc-methods'; import type { SnapId } from '@metamask/snaps-sdk'; import { Text } from '@metamask/snaps-sdk/jsx'; -import type { PersistedSnap, Snap } from '@metamask/snaps-utils'; +import type { PersistedSnap } from '@metamask/snaps-utils'; import { SnapCaveatType } from '@metamask/snaps-utils'; import { getPersistedSnapObject, From 801cc12e08c43d466c4c9b08b84de85ec724eb07 Mon Sep 17 00:00:00 2001 From: Guillaume Roux Date: Tue, 6 Jan 2026 12:04:53 +0100 Subject: [PATCH 5/6] fix `SnapController` tests --- .../src/snaps/SnapController.test.tsx | 1451 +++++++++++------ .../src/snaps/SnapController.ts | 10 +- .../src/test-utils/controller.tsx | 118 +- packages/snaps-utils/src/test-utils/snap.ts | 10 +- 4 files changed, 1026 insertions(+), 563 deletions(-) diff --git a/packages/snaps-controllers/src/snaps/SnapController.test.tsx b/packages/snaps-controllers/src/snaps/SnapController.test.tsx index c732de9815..1c3f3cdbc0 100644 --- a/packages/snaps-controllers/src/snaps/SnapController.test.tsx +++ b/packages/snaps-controllers/src/snaps/SnapController.test.tsx @@ -91,6 +91,7 @@ import type { SnapControllerState, } from './SnapController'; import { + controllerName, SNAP_APPROVAL_INSTALL, SNAP_APPROVAL_RESULT, SNAP_APPROVAL_UPDATE, @@ -111,6 +112,7 @@ import { getSnapControllerOptions, getSnapControllerWithEES, getSnapControllerWithEESOptions, + hydrateStorageService, loopbackDetect, LoopbackLocation, MOCK_BLOCK_NUMBER, @@ -126,6 +128,7 @@ import { MOCK_WALLET_SNAP_PERMISSION, MockSnapsRegistry, sleep, + waitForControllerToBeReady, waitForStateChange, } from '../test-utils'; import { delay } from '../utils'; @@ -158,7 +161,7 @@ describe('SnapController', () => { }); it('creates a snap controller and execution service', async () => { - const [snapController, service] = await getSnapControllerWithEES(); + const [snapController, service] = getSnapControllerWithEES(); expect(service).toBeDefined(); expect(snapController).toBeDefined(); snapController.destroy(); @@ -166,7 +169,9 @@ describe('SnapController', () => { }); it('adds a snap and uses its JSON-RPC api with a NodeThreadExecutionService', async () => { - const [snapController, service] = await getSnapControllerWithEES( + await hydrateStorageService(); + + const [snapController, service] = getSnapControllerWithEES( getSnapControllerWithEESOptions({ state: { snaps: getPersistedSnapsState(), @@ -200,7 +205,9 @@ describe('SnapController', () => { getNodeEESMessenger(rootMessenger), ) as unknown as NodeThreadExecutionService; - const [snapController] = await getSnapControllerWithEES( + await hydrateStorageService(); + + const [snapController] = getSnapControllerWithEES( getSnapControllerWithEESOptions({ rootMessenger, state: { @@ -232,7 +239,10 @@ describe('SnapController', () => { it('passes endowments to a snap when executing it', async () => { const rootMessenger = getControllerMessenger(); const messenger = getSnapControllerMessenger(rootMessenger); - const snapController = await getSnapController( + + await hydrateStorageService(); + + const snapController = getSnapController( getSnapControllerOptions({ environmentEndowmentPermissions: ['endowment:foo'], messenger, @@ -253,23 +263,23 @@ describe('SnapController', () => { await snapController.startSnap(snap.id); - expect(messenger.call).toHaveBeenCalledTimes(3); + expect(messenger.call).toHaveBeenCalledTimes(5); expect(messenger.call).toHaveBeenNthCalledWith( - 1, + 3, 'PermissionController:hasPermission', MOCK_SNAP_ID, 'endowment:foo', ); expect(messenger.call).toHaveBeenNthCalledWith( - 2, + 4, 'PermissionController:getEndowments', MOCK_SNAP_ID, 'endowment:foo', ); expect(messenger.call).toHaveBeenNthCalledWith( - 3, + 5, 'ExecutionService:executeSnap', { snapId: MOCK_SNAP_ID, @@ -282,7 +292,10 @@ describe('SnapController', () => { it('errors if attempting to start a snap that was already started', async () => { const messenger = getSnapControllerMessenger(); - const snapController = await getSnapController( + + await hydrateStorageService(); + + const snapController = getSnapController( getSnapControllerOptions({ messenger, state: { @@ -296,9 +309,9 @@ describe('SnapController', () => { `Snap "${MOCK_SNAP_ID}" is already started.`, ); - expect(messenger.call).toHaveBeenCalledTimes(1); + expect(messenger.call).toHaveBeenCalledTimes(4); expect(messenger.call).toHaveBeenNthCalledWith( - 1, + 3, 'ExecutionService:executeSnap', { snapId: MOCK_SNAP_ID, @@ -316,8 +329,11 @@ describe('SnapController', () => { snaps: getPersistedSnapsState(), }, }); + + await hydrateStorageService(); + const { rootMessenger } = options; - const [snapController, service] = await getSnapControllerWithEES(options); + const [snapController, service] = getSnapControllerWithEES(options); const snap = snapController.getExpect(MOCK_SNAP_ID); await snapController.startSnap(snap.id); @@ -342,7 +358,9 @@ describe('SnapController', () => { }); it('adds a snap and uses its JSON-RPC API and then get stopped from idling too long', async () => { - const [snapController, service] = await getSnapControllerWithEES( + await hydrateStorageService(); + + const [snapController, service] = getSnapControllerWithEES( getSnapControllerWithEESOptions({ idleTimeCheckInterval: 10, maxIdleTime: 50, @@ -377,7 +395,10 @@ describe('SnapController', () => { it('terminates a snap even if connection to worker has failed', async () => { const rootMessenger = getControllerMessenger(); - const [snapController, service] = await getSnapControllerWithEES( + + await hydrateStorageService(); + + const [snapController, service] = getSnapControllerWithEES( getSnapControllerWithEESOptions({ rootMessenger, idleTimeCheckInterval: 10, @@ -427,7 +448,9 @@ describe('SnapController', () => { }); it(`reads a snap's status after adding it`, async () => { - const [snapController, service] = await getSnapControllerWithEES( + await hydrateStorageService(); + + const [snapController, service] = getSnapControllerWithEES( getSnapControllerWithEESOptions({ idleTimeCheckInterval: 1000, maxIdleTime: 2000, @@ -450,7 +473,9 @@ describe('SnapController', () => { }); it('adds a snap, stops it, and starts it again on-demand', async () => { - const [snapController, service] = await getSnapControllerWithEES( + await hydrateStorageService(); + + const [snapController, service] = getSnapControllerWithEES( getSnapControllerWithEESOptions({ idleTimeCheckInterval: 1000, maxIdleTime: 2000, @@ -505,7 +530,7 @@ describe('SnapController', () => { }), }); - const snapController = await getSnapController( + const snapController = getSnapController( getSnapControllerOptions({ messenger, detectSnapLocation: loopbackDetect({ manifest }), @@ -517,7 +542,7 @@ describe('SnapController', () => { }); expect(messenger.call).toHaveBeenNthCalledWith( - 4, + 5, 'ApprovalController:updateRequestState', { id: expect.any(String), @@ -541,6 +566,8 @@ describe('SnapController', () => { () => ({}), ); + await hydrateStorageService(); + const initialConnections = { 'npm:filsnap': {}, 'https://snaps.metamask.io': {}, @@ -558,7 +585,7 @@ describe('SnapController', () => { manifest: manifest.result, }); - const snapController = await getSnapController( + const snapController = getSnapController( getSnapControllerOptions({ messenger, state: { @@ -573,7 +600,7 @@ describe('SnapController', () => { }); expect(messenger.call).toHaveBeenNthCalledWith( - 4, + 5, 'ApprovalController:updateRequestState', { id: expect.any(String), @@ -623,6 +650,8 @@ describe('SnapController', () => { : {}, ); + await hydrateStorageService(); + const initialConnections = { 'npm:filsnap': {}, 'https://snaps.metamask.io': {}, @@ -640,7 +669,7 @@ describe('SnapController', () => { manifest: manifest.result, }); - const snapController = await getSnapController( + const snapController = getSnapController( getSnapControllerOptions({ messenger, state: { @@ -664,7 +693,7 @@ describe('SnapController', () => { }); expect(messenger.call).toHaveBeenNthCalledWith( - 6, + 7, 'ApprovalController:updateRequestState', { id: expect.any(String), @@ -702,6 +731,8 @@ describe('SnapController', () => { () => ({}), ); + await hydrateStorageService(); + const initialConnections = { 'npm:filsnap': {}, 'https://snaps.metamask.io': {}, @@ -719,7 +750,7 @@ describe('SnapController', () => { manifest: manifest.result, }); - const snapController = await getSnapController( + const snapController = getSnapController( getSnapControllerOptions({ messenger, state: { @@ -743,7 +774,7 @@ describe('SnapController', () => { }); expect(messenger.call).toHaveBeenNthCalledWith( - 6, + 7, 'ApprovalController:updateRequestState', { id: expect.any(String), @@ -770,7 +801,7 @@ describe('SnapController', () => { it('installs a snap via installSnaps', async () => { const messenger = getSnapControllerMessenger(); - const snapController = await getSnapController( + const snapController = getSnapController( getSnapControllerOptions({ messenger, detectSnapLocation: loopbackDetect(), @@ -795,7 +826,7 @@ describe('SnapController', () => { [MOCK_SNAP_ID]: expectedSnapObject, }); - expect(messenger.call).toHaveBeenCalledTimes(9); + expect(messenger.call).toHaveBeenCalledTimes(10); expect(messenger.call).toHaveBeenNthCalledWith( 1, @@ -826,6 +857,14 @@ describe('SnapController', () => { expect(messenger.call).toHaveBeenNthCalledWith( 3, + 'StorageService:setItem', + controllerName, + MOCK_SNAP_ID, + { sourceCode: DEFAULT_SNAP_BUNDLE }, + ); + + expect(messenger.call).toHaveBeenNthCalledWith( + 4, 'SubjectMetadataController:addSubjectMetadata', { subjectType: SubjectType.Snap, @@ -837,7 +876,7 @@ describe('SnapController', () => { ); expect(messenger.call).toHaveBeenNthCalledWith( - 4, + 5, 'ApprovalController:updateRequestState', { id: expect.any(String), @@ -850,13 +889,13 @@ describe('SnapController', () => { ); expect(messenger.call).toHaveBeenNthCalledWith( - 5, + 6, 'PermissionController:grantPermissions', expect.any(Object), ); expect(messenger.call).toHaveBeenNthCalledWith( - 6, + 7, 'ApprovalController:addRequest', expect.objectContaining({ requestData: { @@ -875,13 +914,13 @@ describe('SnapController', () => { ); expect(messenger.call).toHaveBeenNthCalledWith( - 7, + 8, 'ExecutionService:executeSnap', expect.any(Object), ); expect(messenger.call).toHaveBeenNthCalledWith( - 8, + 9, 'ApprovalController:updateRequestState', { id: expect.any(String), @@ -925,7 +964,7 @@ describe('SnapController', () => { }), }); - const snapController = await getSnapController( + const snapController = getSnapController( getSnapControllerOptions({ messenger, detectSnapLocation: loopbackDetect({ @@ -948,7 +987,7 @@ describe('SnapController', () => { }); it('throws an error if the installation is disabled during installSnaps', async () => { - const controller = await getSnapController( + const controller = getSnapController( getSnapControllerOptions({ featureFlags: { disableSnapInstallation: true, @@ -968,7 +1007,7 @@ describe('SnapController', () => { }); it('throws an error if the platform is disabled during installSnaps', async () => { - const controller = await getSnapController( + const controller = getSnapController( getSnapControllerOptions({ getFeatureFlags: () => ({ disableSnaps: true }), }), @@ -986,7 +1025,9 @@ describe('SnapController', () => { }); it('throws an error if the platform is disabled during handleRequest', async () => { - const controller = await getSnapController( + await hydrateStorageService(); + + const controller = getSnapController( getSnapControllerOptions({ getFeatureFlags: () => ({ disableSnaps: true }), state: getPersistedSnapsState(), @@ -1008,7 +1049,7 @@ describe('SnapController', () => { }); it('throws an error on invalid semver range during installSnaps', async () => { - const controller = await getSnapController(); + const controller = getSnapController(); await expect( controller.installSnaps(MOCK_ORIGIN, { @@ -1022,7 +1063,7 @@ describe('SnapController', () => { }); it("throws an error if semver version range doesn't match downloaded version", async () => { - const controller = await getSnapController( + const controller = getSnapController( getSnapControllerOptions({ detectSnapLocation: loopbackDetect() }), ); @@ -1041,7 +1082,7 @@ describe('SnapController', () => { const registry = new MockSnapsRegistry(); const rootMessenger = getControllerMessenger(registry); const messenger = getSnapControllerMessenger(rootMessenger); - const controller = await getSnapController( + const controller = getSnapController( getSnapControllerOptions({ featureFlags: { requireAllowlist: true }, messenger, @@ -1070,7 +1111,7 @@ describe('SnapController', () => { const registry = new MockSnapsRegistry(); const rootMessenger = getControllerMessenger(registry); const messenger = getSnapControllerMessenger(rootMessenger); - const controller = await getSnapController( + const controller = getSnapController( getSnapControllerOptions({ featureFlags: { requireAllowlist: true }, messenger, @@ -1109,7 +1150,7 @@ describe('SnapController', () => { }), }); - const controller = await getSnapController( + const controller = getSnapController( getSnapControllerOptions({ featureFlags: { requireAllowlist: true }, detectSnapLocation: loopbackDetect({ @@ -1148,7 +1189,7 @@ describe('SnapController', () => { registry.resolveVersion.mockReturnValue('1.1.0'); - const controller = await getSnapController( + const controller = getSnapController( getSnapControllerOptions({ messenger, featureFlags: { requireAllowlist: true }, @@ -1175,7 +1216,7 @@ describe('SnapController', () => { const registry = new MockSnapsRegistry(); const rootMessenger = getControllerMessenger(registry); const messenger = getSnapControllerMessenger(rootMessenger); - const controller = await getSnapController( + const controller = getSnapController( getSnapControllerOptions({ messenger, detectSnapLocation: (_location, options) => @@ -1194,7 +1235,10 @@ describe('SnapController', () => { it('reuses an already installed snap if it satisfies the requested SemVer range', async () => { const messenger = getSnapControllerMessenger(); - const controller = await getSnapController( + + await hydrateStorageService(); + + const controller = getSnapController( getSnapControllerOptions({ messenger, state: { @@ -1210,7 +1254,7 @@ describe('SnapController', () => { const newSnap = controller.get(MOCK_SNAP_ID); expect(newSnap).toStrictEqual(getSnapObject()); - expect(messenger.call).not.toHaveBeenCalled(); + expect(messenger.call).toHaveBeenCalledTimes(1); controller.destroy(); }); @@ -1218,7 +1262,7 @@ describe('SnapController', () => { it('fails to install snap if user rejects installation', async () => { const rootMessenger = getControllerMessenger(); const messenger = getSnapControllerMessenger(rootMessenger); - const controller = await getSnapController( + const controller = getSnapController( getSnapControllerOptions({ messenger, detectSnapLocation: loopbackDetect(), @@ -1262,7 +1306,7 @@ describe('SnapController', () => { ); expect(messenger.call).toHaveBeenNthCalledWith( - 5, + 6, 'ApprovalController:updateRequestState', expect.objectContaining({ id: expect.any(String), @@ -1287,7 +1331,7 @@ describe('SnapController', () => { it('removes a snap that errors during installation after being added', async () => { const rootMessenger = getControllerMessenger(); const messenger = getSnapControllerMessenger(rootMessenger); - const snapController = await getSnapController( + const snapController = getSnapController( getSnapControllerOptions({ messenger, detectSnapLocation: loopbackDetect(), @@ -1311,10 +1355,10 @@ describe('SnapController', () => { }), ).rejects.toThrow('User rejected the request.'); - expect(messengerCallMock).toHaveBeenCalledTimes(10); + expect(messengerCallMock).toHaveBeenCalledTimes(12); expect(messengerCallMock).toHaveBeenNthCalledWith( - 5, + 6, 'ApprovalController:updateRequestState', expect.objectContaining({ id: expect.any(String), @@ -1335,7 +1379,9 @@ describe('SnapController', () => { }); it('adds a snap, disable/enables it, and still gets a response from an RPC method', async () => { - const [snapController, service] = await getSnapControllerWithEES( + await hydrateStorageService(); + + const [snapController, service] = getSnapControllerWithEES( getSnapControllerWithEESOptions({ idleTimeCheckInterval: 1000, maxRequestTime: 2000, @@ -1431,7 +1477,9 @@ describe('SnapController', () => { }, }); - const snapController = await getSnapController(options); + await hydrateStorageService(); + + const snapController = getSnapController(options); const snap = snapController.getExpect(MOCK_SNAP_ID); rootMessenger.registerActionHandler( @@ -1480,7 +1528,9 @@ describe('SnapController', () => { }, }); - const snapController = await getSnapController(options); + await hydrateStorageService(); + + const snapController = getSnapController(options); const snap = snapController.getExpect(MOCK_SNAP_ID); await snapController.startSnap(snap.id); @@ -1505,7 +1555,9 @@ describe('SnapController', () => { }, }); - const snapController = await getSnapController(options); + await hydrateStorageService(); + + const snapController = getSnapController(options); const snap = snapController.getExpect(MOCK_SNAP_ID); rootMessenger.registerActionHandler( @@ -1561,6 +1613,12 @@ describe('SnapController', () => { module.exports.onRpcRequest = () => ethereum.request({ method: 'eth_blockNumber', params: [] }); `; + await hydrateStorageService({ + [MOCK_SNAP_ID]: { + sourceCode, + }, + }); + const options = getSnapControllerWithEESOptions({ environmentEndowmentPermissions: [SnapEndowments.EthereumProvider], idleTimeCheckInterval: 30000, @@ -1568,7 +1626,6 @@ describe('SnapController', () => { state: { snaps: getPersistedSnapsState( getPersistedSnapObject({ - sourceCode, manifest: getSnapManifest({ shasum: await getSnapChecksum(getMockSnapFiles({ sourceCode })), }), @@ -1600,7 +1657,7 @@ describe('SnapController', () => { getNodeEESMessenger(options.rootMessenger), setupSnapProvider, ); - const [snapController] = await getSnapControllerWithEES(options, service); + const [snapController] = getSnapControllerWithEES(options, service); const snap = snapController.getExpect(MOCK_SNAP_ID); await snapController.startSnap(snap.id); @@ -1634,6 +1691,12 @@ describe('SnapController', () => { module.exports.onRpcRequest = async () => (await fetch()) + (await fetch()); `; + await hydrateStorageService({ + [MOCK_SNAP_ID]: { + sourceCode, + }, + }); + const options = getSnapControllerWithEESOptions({ environmentEndowmentPermissions: [SnapEndowments.EthereumProvider], idleTimeCheckInterval: 30000, @@ -1641,7 +1704,6 @@ describe('SnapController', () => { state: { snaps: getPersistedSnapsState( getPersistedSnapObject({ - sourceCode, manifest: getSnapManifest({ shasum: await getSnapChecksum(getMockSnapFiles({ sourceCode })), }), @@ -1675,7 +1737,7 @@ describe('SnapController', () => { getNodeEESMessenger(rootMessenger), setupSnapProvider, ); - const [snapController] = await getSnapControllerWithEES(options, service); + const [snapController] = getSnapControllerWithEES(options, service); const snap = snapController.getExpect(MOCK_SNAP_ID); rootMessenger.registerActionHandler( @@ -1713,6 +1775,12 @@ describe('SnapController', () => { module.exports.onRpcRequest = async () => snap.request({ method: 'snap_dialog', params: null }); `; + await hydrateStorageService({ + [MOCK_SNAP_ID]: { + sourceCode, + }, + }); + const options = getSnapControllerWithEESOptions({ environmentEndowmentPermissions: [SnapEndowments.EthereumProvider], idleTimeCheckInterval: 30000, @@ -1720,7 +1788,6 @@ describe('SnapController', () => { state: { snaps: getPersistedSnapsState( getPersistedSnapObject({ - sourceCode, manifest: getSnapManifest({ shasum: await getSnapChecksum(getMockSnapFiles({ sourceCode })), }), @@ -1729,15 +1796,9 @@ describe('SnapController', () => { }, }); - const { rootMessenger } = options; - const [snapController] = await getSnapControllerWithEES(options); + const [snapController] = getSnapControllerWithEES(options); const snap = snapController.getExpect(MOCK_SNAP_ID); - rootMessenger.registerActionHandler( - 'PermissionController:hasPermission', - () => true, - ); - const results = (await Promise.allSettled([ snapController.handleRequest({ snapId: snap.id, @@ -1787,7 +1848,7 @@ describe('SnapController', () => { `, }); - const [snapController] = await getSnapControllerWithEES( + const [snapController] = getSnapControllerWithEES( getSnapControllerWithEESOptions({ detectSnapLocation: loopbackDetect({ manifest, @@ -1840,7 +1901,7 @@ describe('SnapController', () => { `, }); - const [snapController] = await getSnapControllerWithEES( + const [snapController] = getSnapControllerWithEES( getSnapControllerWithEESOptions({ detectSnapLocation: loopbackDetect({ manifest, @@ -1900,7 +1961,7 @@ describe('SnapController', () => { }); const rootMessenger = getControllerMessenger(); - const [snapController, service] = await getSnapControllerWithEES( + const [snapController, service] = getSnapControllerWithEES( getSnapControllerWithEESOptions({ maxRequestTime: 50, rootMessenger, @@ -1975,6 +2036,13 @@ describe('SnapController', () => { `; const rootMessenger = getControllerMessenger(); + + await hydrateStorageService({ + [MOCK_SNAP_ID]: { + sourceCode, + }, + }); + const options = getSnapControllerWithEESOptions({ rootMessenger, idleTimeCheckInterval: 10, @@ -1982,7 +2050,6 @@ describe('SnapController', () => { state: { snaps: getPersistedSnapsState( getPersistedSnapObject({ - sourceCode, manifest: getSnapManifest({ shasum: await getSnapChecksum(getMockSnapFiles({ sourceCode })), }), @@ -1990,15 +2057,10 @@ describe('SnapController', () => { ), }, }); - const [snapController, service] = await getSnapControllerWithEES(options); + const [snapController, service] = getSnapControllerWithEES(options); const snap = snapController.getExpect(MOCK_SNAP_ID); - rootMessenger.registerActionHandler( - 'PermissionController:hasPermission', - () => true, - ); - await snapController.startSnap(snap.id); expect(snapController.state.snaps[snap.id].status).toBe('running'); @@ -2043,7 +2105,10 @@ describe('SnapController', () => { it(`shouldn't time out a long running snap on start up`, async () => { const rootMessenger = getControllerMessenger(); const messenger = getSnapControllerMessenger(rootMessenger); - const snapController = await getSnapController( + + await hydrateStorageService(); + + const snapController = getSnapController( getSnapControllerOptions({ messenger, maxRequestTime: 50, @@ -2053,11 +2118,6 @@ describe('SnapController', () => { }), ); - rootMessenger.registerActionHandler( - 'PermissionController:hasPermission', - () => true, - ); - rootMessenger.registerActionHandler( 'ExecutionService:executeSnap', async () => await sleep(300), @@ -2078,6 +2138,9 @@ describe('SnapController', () => { it('removes a snap that is stopped without errors', async () => { const rootMessenger = getControllerMessenger(); + + await hydrateStorageService(); + const options = getSnapControllerWithEESOptions({ rootMessenger, idleTimeCheckInterval: 30000, @@ -2090,7 +2153,7 @@ describe('SnapController', () => { const { messenger } = options; - const [snapController, service] = await getSnapControllerWithEES( + const [snapController, service] = getSnapControllerWithEES( options, new ExecutionEnvironmentStub( getNodeEESMessenger(options.rootMessenger), @@ -2145,6 +2208,8 @@ describe('SnapController', () => { const rootMessenger = getControllerMessenger(); const messenger = getSnapControllerMessenger(rootMessenger); + await hydrateStorageService(); + const state = { myVariable: 1 }; const mockEncryptedState = await encrypt( @@ -2159,7 +2224,7 @@ describe('SnapController', () => { .fn() .mockReturnValue(TEST_SECRET_RECOVERY_PHRASE_SEED_BYTES); - const snapController = await getSnapController( + const snapController = getSnapController( getSnapControllerOptions({ messenger, state: { @@ -2196,7 +2261,7 @@ describe('SnapController', () => { describe('handleRequest', () => { it('throws if the Snap is not installed', async () => { - const snapController = await getSnapController(); + const snapController = getSnapController(); await expect( snapController.handleRequest({ @@ -2225,7 +2290,10 @@ describe('SnapController', () => { async (handler) => { const rootMessenger = getControllerMessenger(); const messenger = getSnapControllerMessenger(rootMessenger); - const snapController = await getSnapController( + + await hydrateStorageService(); + + const snapController = getSnapController( getSnapControllerOptions({ messenger, state: { @@ -2260,7 +2328,10 @@ describe('SnapController', () => { it('does not throw if the snap uses a permitted handler', async () => { const rootMessenger = getControllerMessenger(); const messenger = getSnapControllerMessenger(rootMessenger); - const snapController = await getSnapController( + + await hydrateStorageService(); + + const snapController = getSnapController( getSnapControllerOptions({ messenger, state: { @@ -2294,7 +2365,10 @@ describe('SnapController', () => { it('allows MetaMask to send a JSON-RPC request', async () => { const rootMessenger = getControllerMessenger(); const messenger = getSnapControllerMessenger(rootMessenger); - const snapController = await getSnapController( + + await hydrateStorageService(); + + const snapController = getSnapController( getSnapControllerOptions({ messenger, state: { @@ -2341,7 +2415,10 @@ describe('SnapController', () => { it('allows MetaMask to send a keyring request', async () => { const rootMessenger = getControllerMessenger(); const messenger = getSnapControllerMessenger(rootMessenger); - const snapController = await getSnapController( + + await hydrateStorageService(); + + const snapController = getSnapController( getSnapControllerOptions({ messenger, state: { @@ -2388,7 +2465,10 @@ describe('SnapController', () => { it('allows a website origin if it is in the `allowedOrigins` list', async () => { const rootMessenger = getControllerMessenger(); const messenger = getSnapControllerMessenger(rootMessenger); - const snapController = await getSnapController( + + await hydrateStorageService(); + + const snapController = getSnapController( getSnapControllerOptions({ messenger, state: { @@ -2435,7 +2515,10 @@ describe('SnapController', () => { it('allows a website origin if it is in the `allowedOrigins` list for keyring requests', async () => { const rootMessenger = getControllerMessenger(); const messenger = getSnapControllerMessenger(rootMessenger); - const snapController = await getSnapController( + + await hydrateStorageService(); + + const snapController = getSnapController( getSnapControllerOptions({ messenger, state: { @@ -2482,7 +2565,10 @@ describe('SnapController', () => { it('allows a website origin if `dapps` is `true`', async () => { const rootMessenger = getControllerMessenger(); const messenger = getSnapControllerMessenger(rootMessenger); - const snapController = await getSnapController( + + await hydrateStorageService(); + + const snapController = getSnapController( getSnapControllerOptions({ messenger, state: { @@ -2530,7 +2616,10 @@ describe('SnapController', () => { it('allows a Snap origin if it is in the `allowedOrigins` list', async () => { const rootMessenger = getControllerMessenger(); const messenger = getSnapControllerMessenger(rootMessenger); - const snapController = await getSnapController( + + await hydrateStorageService(); + + const snapController = getSnapController( getSnapControllerOptions({ messenger, state: { @@ -2577,7 +2666,10 @@ describe('SnapController', () => { it('allows a Snap origin if `snaps` is `true`', async () => { const rootMessenger = getControllerMessenger(); const messenger = getSnapControllerMessenger(rootMessenger); - const snapController = await getSnapController( + + await hydrateStorageService(); + + const snapController = getSnapController( getSnapControllerOptions({ messenger, state: { @@ -2638,7 +2730,10 @@ describe('SnapController', () => { async (value: RpcOrigins) => { const rootMessenger = getControllerMessenger(); const messenger = getSnapControllerMessenger(rootMessenger); - const snapController = await getSnapController( + + await hydrateStorageService(); + + const snapController = getSnapController( getSnapControllerOptions({ messenger, state: { @@ -2691,7 +2786,10 @@ describe('SnapController', () => { const { promise, resolve } = createDeferredPromise(); const ensureOnboardingComplete = jest.fn().mockReturnValue(promise); - const snapController = await getSnapController( + + await hydrateStorageService(); + + const snapController = getSnapController( getSnapControllerOptions({ messenger, state: { @@ -2735,7 +2833,10 @@ describe('SnapController', () => { it('throws if the snap does not have permission to handle JSON-RPC requests from dapps', async () => { const rootMessenger = getControllerMessenger(); const messenger = getSnapControllerMessenger(rootMessenger); - const snapController = await getSnapController( + + await hydrateStorageService(); + + const snapController = getSnapController( getSnapControllerOptions({ messenger, state: { @@ -2775,7 +2876,10 @@ describe('SnapController', () => { it('throws if the snap does not have permission to handle JSON-RPC requests from snaps', async () => { const rootMessenger = getControllerMessenger(); const messenger = getSnapControllerMessenger(rootMessenger); - const snapController = await getSnapController( + + await hydrateStorageService(); + + const snapController = getSnapController( getSnapControllerOptions({ messenger, state: { @@ -2815,7 +2919,10 @@ describe('SnapController', () => { it('throws if the website origin is not in the `allowedOrigins` list for keyring requests', async () => { const rootMessenger = getControllerMessenger(); const messenger = getSnapControllerMessenger(rootMessenger); - const snapController = await getSnapController( + + await hydrateStorageService(); + + const snapController = getSnapController( getSnapControllerOptions({ messenger, state: { @@ -2864,7 +2971,10 @@ describe('SnapController', () => { it('injects context into onUserInput', async () => { const rootMessenger = getControllerMessenger(); const messenger = getSnapControllerMessenger(rootMessenger); - const snapController = await getSnapController( + + await hydrateStorageService(); + + const snapController = getSnapController( getSnapControllerOptions({ messenger, state: { @@ -2907,7 +3017,7 @@ describe('SnapController', () => { }); expect(messenger.call).toHaveBeenNthCalledWith( - 4, + 6, 'ExecutionService:handleRpcRequest', MOCK_SNAP_ID, { @@ -2935,7 +3045,10 @@ describe('SnapController', () => { it('throws if onTransaction handler returns a phishing link', async () => { const rootMessenger = getControllerMessenger(); const messenger = getSnapControllerMessenger(rootMessenger); - const snapController = await getSnapController( + + await hydrateStorageService(); + + const snapController = getSnapController( getSnapControllerOptions({ messenger, state: { @@ -2997,7 +3110,10 @@ describe('SnapController', () => { it('throws if onTransaction returns an invalid value', async () => { const rootMessenger = getControllerMessenger(); const messenger = getSnapControllerMessenger(rootMessenger); - const snapController = await getSnapController( + + await hydrateStorageService(); + + const snapController = getSnapController( getSnapControllerOptions({ messenger, state: { @@ -3055,7 +3171,10 @@ describe('SnapController', () => { it("doesn't throw if onTransaction return value is valid", async () => { const rootMessenger = getControllerMessenger(); const messenger = getSnapControllerMessenger(rootMessenger); - const snapController = await getSnapController( + + await hydrateStorageService(); + + const snapController = getSnapController( getSnapControllerOptions({ messenger, state: { @@ -3109,7 +3228,10 @@ describe('SnapController', () => { it('throws if onTransaction return value is an invalid id', async () => { const rootMessenger = getControllerMessenger(); const messenger = getSnapControllerMessenger(rootMessenger); - const snapController = await getSnapController( + + await hydrateStorageService(); + + const snapController = getSnapController( getSnapControllerOptions({ messenger, state: { @@ -3163,7 +3285,10 @@ describe('SnapController', () => { it("doesn't throw if onTransaction return value is an id", async () => { const rootMessenger = getControllerMessenger(); const messenger = getSnapControllerMessenger(rootMessenger); - const snapController = await getSnapController( + + await hydrateStorageService(); + + const snapController = getSnapController( getSnapControllerOptions({ messenger, state: { @@ -3222,7 +3347,10 @@ describe('SnapController', () => { it('throws if onSignature handler returns a phishing link', async () => { const rootMessenger = getControllerMessenger(); const messenger = getSnapControllerMessenger(rootMessenger); - const snapController = await getSnapController( + + await hydrateStorageService(); + + const snapController = getSnapController( getSnapControllerOptions({ messenger, state: { @@ -3284,7 +3412,10 @@ describe('SnapController', () => { it('throws if onSignature returns an invalid value', async () => { const rootMessenger = getControllerMessenger(); const messenger = getSnapControllerMessenger(rootMessenger); - const snapController = await getSnapController( + + await hydrateStorageService(); + + const snapController = getSnapController( getSnapControllerOptions({ messenger, state: { @@ -3342,7 +3473,10 @@ describe('SnapController', () => { it('throws if onSignature return value is an invalid id', async () => { const rootMessenger = getControllerMessenger(); const messenger = getSnapControllerMessenger(rootMessenger); - const snapController = await getSnapController( + + await hydrateStorageService(); + + const snapController = getSnapController( getSnapControllerOptions({ messenger, state: { @@ -3396,7 +3530,10 @@ describe('SnapController', () => { it("doesn't throw if onSignature return value is valid", async () => { const rootMessenger = getControllerMessenger(); const messenger = getSnapControllerMessenger(rootMessenger); - const snapController = await getSnapController( + + await hydrateStorageService(); + + const snapController = getSnapController( getSnapControllerOptions({ messenger, state: { @@ -3451,7 +3588,10 @@ describe('SnapController', () => { it(`doesn't throw if onTransaction handler returns null`, async () => { const rootMessenger = getControllerMessenger(); const messenger = getSnapControllerMessenger(rootMessenger); - const snapController = await getSnapController( + + await hydrateStorageService(); + + const snapController = getSnapController( getSnapControllerOptions({ messenger, state: { @@ -3503,7 +3643,10 @@ describe('SnapController', () => { it(`doesn't throw if onSignature handler returns null`, async () => { const rootMessenger = getControllerMessenger(); const messenger = getSnapControllerMessenger(rootMessenger); - const snapController = await getSnapController( + + await hydrateStorageService(); + + const snapController = getSnapController( getSnapControllerOptions({ messenger, state: { @@ -3555,7 +3698,10 @@ describe('SnapController', () => { it('throws if onHomePage handler returns a phishing link', async () => { const rootMessenger = getControllerMessenger(); const messenger = getSnapControllerMessenger(rootMessenger); - const snapController = await getSnapController( + + await hydrateStorageService(); + + const snapController = getSnapController( getSnapControllerOptions({ messenger, state: { @@ -3617,7 +3763,10 @@ describe('SnapController', () => { it('throws if onHomePage return value is an invalid id', async () => { const rootMessenger = getControllerMessenger(); const messenger = getSnapControllerMessenger(rootMessenger); - const snapController = await getSnapController( + + await hydrateStorageService(); + + const snapController = getSnapController( getSnapControllerOptions({ messenger, state: { @@ -3671,7 +3820,10 @@ describe('SnapController', () => { it("doesn't throw if onHomePage return value is valid", async () => { const rootMessenger = getControllerMessenger(); const messenger = getSnapControllerMessenger(rootMessenger); - const snapController = await getSnapController( + + await hydrateStorageService(); + + const snapController = getSnapController( getSnapControllerOptions({ messenger, state: { @@ -3725,7 +3877,10 @@ describe('SnapController', () => { it('throws if onSettingsPage handler returns a phishing link', async () => { const rootMessenger = getControllerMessenger(); const messenger = getSnapControllerMessenger(rootMessenger); - const snapController = await getSnapController( + + await hydrateStorageService(); + + const snapController = getSnapController( getSnapControllerOptions({ messenger, state: { @@ -3787,7 +3942,10 @@ describe('SnapController', () => { it('throws if onSettingsPage return value is an invalid id', async () => { const rootMessenger = getControllerMessenger(); const messenger = getSnapControllerMessenger(rootMessenger); - const snapController = await getSnapController( + + await hydrateStorageService(); + + const snapController = getSnapController( getSnapControllerOptions({ messenger, state: { @@ -3841,7 +3999,10 @@ describe('SnapController', () => { it("doesn't throw if onSettingsPage return value is valid", async () => { const rootMessenger = getControllerMessenger(); const messenger = getSnapControllerMessenger(rootMessenger); - const snapController = await getSnapController( + + await hydrateStorageService(); + + const snapController = getSnapController( getSnapControllerOptions({ messenger, state: { @@ -3895,7 +4056,10 @@ describe('SnapController', () => { it('throws if onNameLookup returns an invalid value', async () => { const rootMessenger = getControllerMessenger(); const messenger = getSnapControllerMessenger(rootMessenger); - const snapController = await getSnapController( + + await hydrateStorageService(); + + const snapController = getSnapController( getSnapControllerOptions({ messenger, state: { @@ -3952,7 +4116,10 @@ describe('SnapController', () => { it("doesn't throw if onNameLookup return value is valid", async () => { const rootMessenger = getControllerMessenger(); const messenger = getSnapControllerMessenger(rootMessenger); - const snapController = await getSnapController( + + await hydrateStorageService(); + + const snapController = getSnapController( getSnapControllerOptions({ messenger, state: { @@ -4014,7 +4181,10 @@ describe('SnapController', () => { it(`doesn't throw if onNameLookup handler returns null`, async () => { const rootMessenger = getControllerMessenger(); const messenger = getSnapControllerMessenger(rootMessenger); - const snapController = await getSnapController( + + await hydrateStorageService(); + + const snapController = getSnapController( getSnapControllerOptions({ messenger, state: { @@ -4067,7 +4237,10 @@ describe('SnapController', () => { it('throws if `onAssetsLookup` handler returns an invalid response', async () => { const rootMessenger = getControllerMessenger(); const messenger = getSnapControllerMessenger(rootMessenger); - const snapController = await getSnapController( + + await hydrateStorageService(); + + const snapController = getSnapController( getSnapControllerOptions({ messenger, state: { @@ -4129,7 +4302,10 @@ describe('SnapController', () => { it('filters out assets that are out of scope for `onAssetsLookup`', async () => { const rootMessenger = getControllerMessenger(); const messenger = getSnapControllerMessenger(rootMessenger); - const snapController = await getSnapController( + + await hydrateStorageService(); + + const snapController = getSnapController( getSnapControllerOptions({ messenger, state: { @@ -4205,7 +4381,10 @@ describe('SnapController', () => { it('returns the value when `onAssetsLookup` returns a valid response for fungible assets', async () => { const rootMessenger = getControllerMessenger(); const messenger = getSnapControllerMessenger(rootMessenger); - const snapController = await getSnapController( + + await hydrateStorageService(); + + const snapController = getSnapController( getSnapControllerOptions({ messenger, state: { @@ -4297,7 +4476,10 @@ describe('SnapController', () => { it('returns the value when `onAssetsLookup` returns a valid response for non-fungible assets', async () => { const rootMessenger = getControllerMessenger(); const messenger = getSnapControllerMessenger(rootMessenger); - const snapController = await getSnapController( + + await hydrateStorageService(); + + const snapController = getSnapController( getSnapControllerOptions({ messenger, state: { @@ -4419,7 +4601,10 @@ describe('SnapController', () => { it('throws if `onAssetsConversion` handler returns an invalid response', async () => { const rootMessenger = getControllerMessenger(); const messenger = getSnapControllerMessenger(rootMessenger); - const snapController = await getSnapController( + + await hydrateStorageService(); + + const snapController = getSnapController( getSnapControllerOptions({ messenger, state: { @@ -4481,7 +4666,10 @@ describe('SnapController', () => { it('filters out assets that are out of scope for `onAssetsConversion`', async () => { const rootMessenger = getControllerMessenger(); const messenger = getSnapControllerMessenger(rootMessenger); - const snapController = await getSnapController( + + await hydrateStorageService(); + + const snapController = getSnapController( getSnapControllerOptions({ messenger, state: { @@ -4555,7 +4743,10 @@ describe('SnapController', () => { it('returns the value when `onAssetsConversion` returns a valid response', async () => { const rootMessenger = getControllerMessenger(); const messenger = getSnapControllerMessenger(rootMessenger); - const snapController = await getSnapController( + + await hydrateStorageService(); + + const snapController = getSnapController( getSnapControllerOptions({ messenger, state: { @@ -4640,7 +4831,10 @@ describe('SnapController', () => { it('throws if `onAssetsMarketData` handler returns an invalid response', async () => { const rootMessenger = getControllerMessenger(); const messenger = getSnapControllerMessenger(rootMessenger); - const snapController = await getSnapController( + + await hydrateStorageService(); + + const snapController = getSnapController( getSnapControllerOptions({ messenger, state: { @@ -4702,7 +4896,10 @@ describe('SnapController', () => { it('filters out assets that are out of scope for `onAssetsMarketData`', async () => { const rootMessenger = getControllerMessenger(); const messenger = getSnapControllerMessenger(rootMessenger); - const snapController = await getSnapController( + + await hydrateStorageService(); + + const snapController = getSnapController( getSnapControllerOptions({ messenger, state: { @@ -4775,7 +4972,10 @@ describe('SnapController', () => { it('returns the value when `onAssetsMarketData` returns a valid response for fungible assets', async () => { const rootMessenger = getControllerMessenger(); const messenger = getSnapControllerMessenger(rootMessenger); - const snapController = await getSnapController( + + await hydrateStorageService(); + + const snapController = getSnapController( getSnapControllerOptions({ messenger, state: { @@ -4860,7 +5060,10 @@ describe('SnapController', () => { it('returns the value when `onAssetsMarketData` returns a valid response for non-fungible assets', async () => { const rootMessenger = getControllerMessenger(); const messenger = getSnapControllerMessenger(rootMessenger); - const snapController = await getSnapController( + + await hydrateStorageService(); + + const snapController = getSnapController( getSnapControllerOptions({ messenger, state: { @@ -4984,7 +5187,10 @@ describe('SnapController', () => { it('throws if `onAssetHistoricalPrice` handler returns an invalid response', async () => { const rootMessenger = getControllerMessenger(); const messenger = getSnapControllerMessenger(rootMessenger); - const snapController = await getSnapController( + + await hydrateStorageService(); + + const snapController = getSnapController( getSnapControllerOptions({ messenger, state: { @@ -5046,7 +5252,10 @@ describe('SnapController', () => { it('returns the value when `onAssetHistoricalPrice` returns a valid response', async () => { const rootMessenger = getControllerMessenger(); const messenger = getSnapControllerMessenger(rootMessenger); - const snapController = await getSnapController( + + await hydrateStorageService(); + + const snapController = getSnapController( getSnapControllerOptions({ messenger, state: { @@ -5123,7 +5332,10 @@ describe('SnapController', () => { it('returns the value when `onClientRequest` returns a valid response', async () => { const rootMessenger = getControllerMessenger(); const messenger = getSnapControllerMessenger(rootMessenger); - const snapController = await getSnapController( + + await hydrateStorageService(); + + const snapController = getSnapController( getSnapControllerOptions({ messenger, state: { @@ -5166,7 +5378,10 @@ describe('SnapController', () => { it('throws if the origin is not "metamask"', async () => { const rootMessenger = getControllerMessenger(); const messenger = getSnapControllerMessenger(rootMessenger); - const snapController = await getSnapController( + + await hydrateStorageService(); + + const snapController = getSnapController( getSnapControllerOptions({ messenger, state: { @@ -5209,13 +5424,16 @@ describe('SnapController', () => { describe('getRpcRequestHandler', () => { it('handlers populate the "jsonrpc" property if missing', async () => { const rootMessenger = getControllerMessenger(); + + await hydrateStorageService(); + const options = getSnapControllerWithEESOptions({ rootMessenger, state: { snaps: getPersistedSnapsState(), }, }); - const [snapController, service] = await getSnapControllerWithEES(options); + const [snapController, service] = getSnapControllerWithEES(options); rootMessenger.registerActionHandler( 'PermissionController:hasPermission', @@ -5236,7 +5454,7 @@ describe('SnapController', () => { }, }); - expect(options.messenger.call).toHaveBeenCalledTimes(5); + expect(options.messenger.call).toHaveBeenCalledTimes(7); expect(options.messenger.call).toHaveBeenCalledWith( 'ExecutionService:handleRpcRequest', MOCK_SNAP_ID, @@ -5259,7 +5477,12 @@ describe('SnapController', () => { it('handlers throw if the request has an invalid "jsonrpc" property', async () => { const fakeSnap = getPersistedSnapObject({ status: SnapStatus.Running }); const snapId = fakeSnap.id; - const snapController = await getSnapController( + + await hydrateStorageService({ + [fakeSnap.id]: { sourceCode: DEFAULT_SNAP_BUNDLE }, + }); + + const snapController = getSnapController( getSnapControllerOptions({ state: { snaps: { @@ -5292,7 +5515,12 @@ describe('SnapController', () => { it('handlers throw if the request is not valid JSON', async () => { const fakeSnap = getPersistedSnapObject({ status: SnapStatus.Running }); const snapId = fakeSnap.id; - const snapController = await getSnapController( + + await hydrateStorageService({ + [fakeSnap.id]: { sourceCode: DEFAULT_SNAP_BUNDLE }, + }); + + const snapController = getSnapController( getSnapControllerOptions({ state: { snaps: { @@ -5331,7 +5559,7 @@ describe('SnapController', () => { `, }); - const [snapController, service] = await getSnapControllerWithEES( + const [snapController, service] = getSnapControllerWithEES( getSnapControllerWithEESOptions({ detectSnapLocation: loopbackDetect({ manifest, @@ -5388,7 +5616,7 @@ describe('SnapController', () => { `, }); - const [snapController, service] = await getSnapControllerWithEES( + const [snapController, service] = getSnapControllerWithEES( getSnapControllerWithEESOptions({ idleTimeCheckInterval: 10, maxIdleTime: 50, @@ -5434,7 +5662,9 @@ describe('SnapController', () => { const snapObject = getPersistedSnapObject(); const truncatedSnap = getTruncatedSnap(); - const snapController = await getSnapController( + await hydrateStorageService(); + + const snapController = getSnapController( getSnapControllerOptions({ messenger, state: { @@ -5450,7 +5680,7 @@ describe('SnapController', () => { }); expect(result).toStrictEqual({ [MOCK_SNAP_ID]: truncatedSnap }); - expect(messenger.call).not.toHaveBeenCalled(); + expect(messenger.call).toHaveBeenCalledTimes(1); snapController.destroy(); }); @@ -5470,7 +5700,11 @@ describe('SnapController', () => { shouldAlwaysReload: true, }); - const snapController = await getSnapController( + await hydrateStorageService({ + [MOCK_LOCAL_SNAP_ID]: { sourceCode: DEFAULT_SNAP_BUNDLE }, + }); + + const snapController = getSnapController( getSnapControllerOptions({ messenger, state: { @@ -5499,10 +5733,10 @@ describe('SnapController', () => { expect(result).toStrictEqual({ [MOCK_LOCAL_SNAP_ID]: truncatedSnap }); - expect(messenger.call).toHaveBeenCalledTimes(11); + expect(messenger.call).toHaveBeenCalledTimes(13); expect(messenger.call).toHaveBeenNthCalledWith( - 1, + 2, 'ApprovalController:addRequest', expect.objectContaining({ type: SNAP_APPROVAL_INSTALL, @@ -5522,7 +5756,7 @@ describe('SnapController', () => { ); expect(messenger.call).toHaveBeenNthCalledWith( - 6, + 8, 'ApprovalController:updateRequestState', expect.objectContaining({ id: expect.any(String), @@ -5535,7 +5769,7 @@ describe('SnapController', () => { ); expect(messenger.call).toHaveBeenNthCalledWith( - 7, + 9, 'PermissionController:grantPermissions', { approvedPermissions: permissions, @@ -5552,7 +5786,7 @@ describe('SnapController', () => { ); expect(messenger.call).toHaveBeenNthCalledWith( - 8, + 10, 'ApprovalController:addRequest', expect.objectContaining({ type: SNAP_APPROVAL_RESULT, @@ -5572,13 +5806,13 @@ describe('SnapController', () => { ); expect(messenger.call).toHaveBeenNthCalledWith( - 9, + 11, 'ExecutionService:executeSnap', expect.objectContaining({}), ); expect(messenger.call).toHaveBeenNthCalledWith( - 10, + 12, 'ApprovalController:updateRequestState', expect.objectContaining({ id: expect.any(String), @@ -5619,7 +5853,7 @@ describe('SnapController', () => { .mockImplementationOnce(async () => Promise.resolve(manifest)) .mockImplementationOnce(async () => Promise.resolve(newManifest)); - const snapController = await getSnapController( + const snapController = getSnapController( getSnapControllerOptions({ messenger, detectSnapLocation: loopbackDetect(location), @@ -5649,7 +5883,7 @@ describe('SnapController', () => { [MOCK_LOCAL_SNAP_ID]: truncatedSnap, }); - expect(messenger.call).toHaveBeenCalledTimes(22); + expect(messenger.call).toHaveBeenCalledTimes(23); expect(messenger.call).toHaveBeenNthCalledWith( 1, @@ -5672,7 +5906,7 @@ describe('SnapController', () => { ); expect(messenger.call).toHaveBeenNthCalledWith( - 4, + 5, 'ApprovalController:updateRequestState', expect.objectContaining({ id: expect.any(String), @@ -5685,7 +5919,7 @@ describe('SnapController', () => { ); expect(messenger.call).toHaveBeenNthCalledWith( - 5, + 6, 'PermissionController:grantPermissions', { approvedPermissions: permissions, @@ -5702,7 +5936,7 @@ describe('SnapController', () => { ); expect(messenger.call).toHaveBeenNthCalledWith( - 6, + 7, 'ApprovalController:addRequest', expect.objectContaining({ id: expect.any(String), @@ -5723,13 +5957,13 @@ describe('SnapController', () => { ); expect(messenger.call).toHaveBeenNthCalledWith( - 7, + 8, 'ExecutionService:executeSnap', expect.anything(), ); expect(messenger.call).toHaveBeenNthCalledWith( - 8, + 9, 'ApprovalController:updateRequestState', expect.objectContaining({ id: expect.any(String), @@ -5767,7 +6001,7 @@ describe('SnapController', () => { ); expect(messenger.call).toHaveBeenNthCalledWith( - 17, + 18, 'ApprovalController:updateRequestState', expect.objectContaining({ id: expect.any(String), @@ -5780,7 +6014,7 @@ describe('SnapController', () => { ); expect(messenger.call).toHaveBeenNthCalledWith( - 18, + 19, 'PermissionController:grantPermissions', { approvedPermissions: permissions, @@ -5797,7 +6031,7 @@ describe('SnapController', () => { ); expect(messenger.call).toHaveBeenNthCalledWith( - 19, + 20, 'ApprovalController:addRequest', expect.objectContaining({ id: expect.any(String), @@ -5818,13 +6052,13 @@ describe('SnapController', () => { ); expect(messenger.call).toHaveBeenNthCalledWith( - 20, + 21, 'ExecutionService:executeSnap', expect.objectContaining({ snapId: MOCK_LOCAL_SNAP_ID }), ); expect(messenger.call).toHaveBeenNthCalledWith( - 21, + 22, 'ApprovalController:updateRequestState', expect.objectContaining({ id: expect.any(String), @@ -5851,7 +6085,11 @@ describe('SnapController', () => { shouldAlwaysReload: true, }); - const snapController = await getSnapController( + await hydrateStorageService({ + [MOCK_LOCAL_SNAP_ID]: { sourceCode: DEFAULT_SNAP_BUNDLE }, + }); + + const snapController = getSnapController( getSnapControllerOptions({ messenger, state: { @@ -5882,7 +6120,7 @@ describe('SnapController', () => { ).rejects.toThrow('User rejected the request.'); expect(messenger.call).toHaveBeenNthCalledWith( - 1, + 2, 'ApprovalController:addRequest', expect.objectContaining({ type: SNAP_APPROVAL_INSTALL, @@ -5899,7 +6137,7 @@ describe('SnapController', () => { ); expect(messenger.call).toHaveBeenNthCalledWith( - 7, + 9, 'ApprovalController:updateRequestState', expect.objectContaining({ id: expect.any(String), @@ -5927,7 +6165,11 @@ describe('SnapController', () => { shouldAlwaysReload: true, }); - const snapController = await getSnapController( + await hydrateStorageService({ + [MOCK_LOCAL_SNAP_ID]: { sourceCode: DEFAULT_SNAP_BUNDLE }, + }); + + const snapController = getSnapController( getSnapControllerOptions({ messenger, state: { @@ -5973,7 +6215,7 @@ describe('SnapController', () => { }), }); - const snapController = await getSnapController( + const snapController = getSnapController( getSnapControllerOptions({ messenger, detectSnapLocation: loopbackDetect({ manifest }), @@ -6030,7 +6272,7 @@ describe('SnapController', () => { const snapId = `${MOCK_SNAP_ID}_foo`; - const snapController = await getSnapController( + const snapController = getSnapController( getSnapControllerOptions({ messenger, detectSnapLocation: loopbackDetect({ manifest }), @@ -6108,9 +6350,11 @@ describe('SnapController', () => { preinstalledSnaps, rootMessenger, }); - const [snapController] = await getSnapControllerWithEES( - snapControllerOptions, - ); + + const [snapController] = getSnapControllerWithEES(snapControllerOptions); + + // Wait for the end of the initialization of the snap controller. + await waitForControllerToBeReady(snapControllerOptions.messenger); expect(snapControllerOptions.messenger.call).toHaveBeenCalledWith( 'PermissionController:grantPermissions', @@ -6220,9 +6464,7 @@ describe('SnapController', () => { preinstalledSnaps, rootMessenger, }); - const [snapController] = await getSnapControllerWithEES( - snapControllerOptions, - ); + const [snapController] = getSnapControllerWithEES(snapControllerOptions); expect(snapControllerOptions.messenger.call).not.toHaveBeenCalledWith( 'PermissionController:revokePermissions', @@ -6288,9 +6530,10 @@ describe('SnapController', () => { preinstalledSnaps, rootMessenger, }); - const [snapController] = await getSnapControllerWithEES( - snapControllerOptions, - ); + const [snapController] = getSnapControllerWithEES(snapControllerOptions); + + // Wait for the end of the initialization of the snap controller. + await waitForControllerToBeReady(snapControllerOptions.messenger); const approvedPermissions = { [WALLET_SNAP_PERMISSION_KEY]: { @@ -6365,9 +6608,10 @@ describe('SnapController', () => { preinstalledSnaps, rootMessenger, }); - const [snapController] = await getSnapControllerWithEES( - snapControllerOptions, - ); + const [snapController] = getSnapControllerWithEES(snapControllerOptions); + + // Wait for the end of the initialization of the snap controller. + await waitForControllerToBeReady(snapControllerOptions.messenger); expect(snapControllerOptions.messenger.call).toHaveBeenCalledWith( 'PermissionController:grantPermissions', @@ -6455,6 +6699,8 @@ describe('SnapController', () => { }, ); + await hydrateStorageService(); + const snapControllerOptions = getSnapControllerWithEESOptions({ preinstalledSnaps, rootMessenger, @@ -6464,9 +6710,10 @@ describe('SnapController', () => { ), }, }); - const [snapController] = await getSnapControllerWithEES( - snapControllerOptions, - ); + const [snapController] = getSnapControllerWithEES(snapControllerOptions); + + // Wait for the end of the initialization of the snap controller. + await waitForControllerToBeReady(snapControllerOptions.messenger); expect(snapControllerOptions.messenger.call).toHaveBeenCalledWith( 'PermissionController:revokePermissions', @@ -6530,6 +6777,8 @@ describe('SnapController', () => { }, ]; + await hydrateStorageService(); + const snapControllerOptions = getSnapControllerWithEESOptions({ preinstalledSnaps, rootMessenger, @@ -6537,11 +6786,9 @@ describe('SnapController', () => { snaps: getPersistedSnapsState(), }, }); - const [snapController] = await getSnapControllerWithEES( - snapControllerOptions, - ); + const [snapController] = getSnapControllerWithEES(snapControllerOptions); - expect(snapControllerOptions.messenger.call).toHaveBeenCalledTimes(0); + expect(snapControllerOptions.messenger.call).toHaveBeenCalledTimes(1); snapController.destroy(); }); @@ -6602,9 +6849,10 @@ describe('SnapController', () => { preinstalledSnaps, rootMessenger, }); - const [snapController] = await getSnapControllerWithEES( - snapControllerOptions, - ); + const [snapController] = getSnapControllerWithEES(snapControllerOptions); + + // Wait for the end of the initialization of the snap controller. + await waitForControllerToBeReady(snapControllerOptions.messenger); expect(snapControllerOptions.messenger.call).toHaveBeenCalledWith( 'PermissionController:grantPermissions', @@ -6702,9 +6950,7 @@ describe('SnapController', () => { rootMessenger, detectSnapLocation, }); - const [snapController] = await getSnapControllerWithEES( - snapControllerOptions, - ); + const [snapController] = getSnapControllerWithEES(snapControllerOptions); await expect( snapController.installSnaps(MOCK_ORIGIN, { @@ -6760,9 +7006,7 @@ describe('SnapController', () => { preinstalledSnaps, rootMessenger, }); - const [snapController] = await getSnapControllerWithEES( - snapControllerOptions, - ); + const [snapController] = getSnapControllerWithEES(snapControllerOptions); expect(snapController.get(MOCK_SNAP_ID)?.hidden).toBe(true); @@ -6814,9 +7058,7 @@ describe('SnapController', () => { preinstalledSnaps, rootMessenger, }); - const [snapController] = await getSnapControllerWithEES( - snapControllerOptions, - ); + const [snapController] = getSnapControllerWithEES(snapControllerOptions); expect(snapController.get(MOCK_SNAP_ID)?.hideSnapBranding).toBe(true); @@ -6856,9 +7098,10 @@ describe('SnapController', () => { preinstalledSnaps, rootMessenger, }); - const [snapController] = await getSnapControllerWithEES( - snapControllerOptions, - ); + const [snapController] = getSnapControllerWithEES(snapControllerOptions); + + // Wait for the end of the initialization of the snap controller. + await waitForControllerToBeReady(snapControllerOptions.messenger); expect(log).toHaveBeenCalledWith( 'The permissions for "npm:@metamask/example-snap" were out of sync and have been automatically restored. If you see this message, please file a bug report.', @@ -6866,7 +7109,7 @@ describe('SnapController', () => { // We expect two calls as we mock the PermissionController to always return an empty set. expect(snapControllerOptions.messenger.call).toHaveBeenNthCalledWith( - 3, + 5, 'PermissionController:grantPermissions', { approvedPermissions: { @@ -6883,7 +7126,7 @@ describe('SnapController', () => { ); expect(snapControllerOptions.messenger.call).toHaveBeenNthCalledWith( - 6, + 8, 'SubjectMetadataController:addSubjectMetadata', { subjectType: SubjectType.Snap, @@ -6895,7 +7138,7 @@ describe('SnapController', () => { ); expect(snapControllerOptions.messenger.call).toHaveBeenNthCalledWith( - 7, + 9, 'PermissionController:grantPermissions', { approvedPermissions: { @@ -6946,9 +7189,10 @@ describe('SnapController', () => { preinstalledSnaps, rootMessenger, }); - const [snapController] = await getSnapControllerWithEES( - snapControllerOptions, - ); + const [snapController] = getSnapControllerWithEES(snapControllerOptions); + + // Wait for the end of the initialization of the snap controller. + await waitForControllerToBeReady(snapControllerOptions.messenger); expect(log).toHaveBeenCalledWith( 'The permissions for "npm:@metamask/example-snap" were out of sync and have been automatically restored. If you see this message, please file a bug report.', @@ -6956,7 +7200,7 @@ describe('SnapController', () => { // We expect two calls as we mock the PermissionController to always return an empty set. expect(snapControllerOptions.messenger.call).toHaveBeenNthCalledWith( - 3, + 5, 'PermissionController:grantPermissions', { approvedPermissions: { @@ -6973,7 +7217,7 @@ describe('SnapController', () => { ); expect(snapControllerOptions.messenger.call).toHaveBeenNthCalledWith( - 6, + 8, 'SubjectMetadataController:addSubjectMetadata', { subjectType: SubjectType.Snap, @@ -6985,7 +7229,7 @@ describe('SnapController', () => { ); expect(snapControllerOptions.messenger.call).toHaveBeenNthCalledWith( - 7, + 9, 'PermissionController:grantPermissions', { approvedPermissions: { @@ -7008,6 +7252,13 @@ describe('SnapController', () => { const rootMessenger = getControllerMessenger(); jest.spyOn(rootMessenger, 'call'); + rootMessenger.registerActionHandler( + 'PermissionController:hasPermission', + (_origin, permission) => + permission === SnapEndowments.LifecycleHooks || + permission === handlerEndowments[HandlerType.OnInstall], + ); + rootMessenger.registerActionHandler( 'PermissionController:getPermissions', () => ({ @@ -7037,14 +7288,14 @@ describe('SnapController', () => { ]; const messenger = getSnapControllerMessenger(rootMessenger); - const snapController = await getSnapController( + const snapController = getSnapController( getSnapControllerOptions({ messenger, preinstalledSnaps }), ); await new Promise((resolve) => setTimeout(resolve, 10)); expect(messenger.call).toHaveBeenNthCalledWith( - 7, + 13, 'ExecutionService:handleRpcRequest', MOCK_SNAP_ID, { @@ -7065,6 +7316,13 @@ describe('SnapController', () => { const rootMessenger = getControllerMessenger(); jest.spyOn(rootMessenger, 'call'); + rootMessenger.registerActionHandler( + 'PermissionController:hasPermission', + (_origin, permission) => + permission === SnapEndowments.LifecycleHooks || + permission === handlerEndowments[HandlerType.OnUpdate], + ); + rootMessenger.registerActionHandler( 'PermissionController:getPermissions', () => ({ @@ -7095,7 +7353,10 @@ describe('SnapController', () => { ]; const messenger = getSnapControllerMessenger(rootMessenger); - const snapController = await getSnapController( + + await hydrateStorageService(); + + const snapController = getSnapController( getSnapControllerOptions({ messenger, preinstalledSnaps, @@ -7110,7 +7371,7 @@ describe('SnapController', () => { await new Promise((resolve) => setTimeout(resolve, 10)); expect(messenger.call).toHaveBeenNthCalledWith( - 7, + 13, 'ExecutionService:handleRpcRequest', MOCK_SNAP_ID, { @@ -7131,7 +7392,7 @@ describe('SnapController', () => { const manifest = getSnapManifest(); const rootMessenger = getControllerMessenger(); const messenger = getSnapControllerMessenger(rootMessenger); - const snapController = await getSnapController( + const snapController = getSnapController( getSnapControllerOptions({ messenger, detectSnapLocation: loopbackDetect({ manifest }), @@ -7158,7 +7419,7 @@ describe('SnapController', () => { expect(result).toStrictEqual({ [MOCK_SNAP_ID]: truncatedSnap, }); - expect(messenger.call).toHaveBeenCalledTimes(9); + expect(messenger.call).toHaveBeenCalledTimes(10); expect(messenger.call).toHaveBeenNthCalledWith( 1, @@ -7178,7 +7439,7 @@ describe('SnapController', () => { ); expect(messenger.call).toHaveBeenNthCalledWith( - 4, + 5, 'ApprovalController:updateRequestState', expect.objectContaining({ id: expect.any(String), @@ -7191,7 +7452,7 @@ describe('SnapController', () => { ); expect(messenger.call).toHaveBeenNthCalledWith( - 5, + 6, 'PermissionController:grantPermissions', { approvedPermissions: permissions, @@ -7208,7 +7469,7 @@ describe('SnapController', () => { ); expect(messenger.call).toHaveBeenNthCalledWith( - 6, + 7, 'ApprovalController:addRequest', expect.objectContaining({ id: expect.any(String), @@ -7229,13 +7490,13 @@ describe('SnapController', () => { ); expect(messenger.call).toHaveBeenNthCalledWith( - 7, + 8, 'ExecutionService:executeSnap', expect.objectContaining({}), ); expect(messenger.call).toHaveBeenNthCalledWith( - 8, + 9, 'ApprovalController:updateRequestState', expect.objectContaining({ id: expect.any(String), @@ -7265,7 +7526,7 @@ describe('SnapController', () => { }); const messenger = getSnapControllerMessenger(); - const controller = await getSnapController( + const controller = getSnapController( getSnapControllerOptions({ messenger, detectSnapLocation: loopbackDetect({ @@ -7297,7 +7558,7 @@ describe('SnapController', () => { }); const messenger = getSnapControllerMessenger(); - const controller = await getSnapController( + const controller = getSnapController( getSnapControllerOptions({ messenger, detectSnapLocation: loopbackDetect({ @@ -7326,7 +7587,7 @@ describe('SnapController', () => { }); const messenger = getSnapControllerMessenger(); - const controller = await getSnapController( + const controller = getSnapController( getSnapControllerOptions({ messenger, detectSnapLocation: loopbackDetect({ @@ -7355,7 +7616,7 @@ describe('SnapController', () => { }); const messenger = getSnapControllerMessenger(); - const controller = await getSnapController( + const controller = getSnapController( getSnapControllerOptions({ messenger, detectSnapLocation: loopbackDetect({ @@ -7387,7 +7648,7 @@ describe('SnapController', () => { }); const messenger = getSnapControllerMessenger(); - const controller = await getSnapController( + const controller = getSnapController( getSnapControllerOptions({ messenger, featureFlags: { @@ -7429,7 +7690,7 @@ describe('SnapController', () => { }); const messenger = getSnapControllerMessenger(); - const snapController = await getSnapController( + const snapController = getSnapController( getSnapControllerOptions({ messenger, detectSnapLocation: loopbackDetect({ @@ -7460,7 +7721,7 @@ describe('SnapController', () => { ); expect(messenger.call).toHaveBeenNthCalledWith( - 4, + 5, 'ApprovalController:updateRequestState', expect.objectContaining({ id: expect.any(String), @@ -7494,7 +7755,7 @@ describe('SnapController', () => { ); expect(messenger.call).toHaveBeenNthCalledWith( - 5, + 6, 'PermissionController:grantPermissions', { approvedPermissions: { @@ -7550,7 +7811,7 @@ describe('SnapController', () => { }); const messenger = getSnapControllerMessenger(); - const snapController = await getSnapController( + const snapController = getSnapController( getSnapControllerOptions({ messenger, detectSnapLocation: loopbackDetect({ @@ -7590,7 +7851,7 @@ describe('SnapController', () => { ); expect(messenger.call).toHaveBeenNthCalledWith( - 4, + 5, 'ApprovalController:updateRequestState', expect.objectContaining({ id: expect.any(String), @@ -7607,7 +7868,7 @@ describe('SnapController', () => { ); expect(messenger.call).toHaveBeenNthCalledWith( - 5, + 6, 'PermissionController:grantPermissions', { approvedPermissions: { @@ -7654,7 +7915,10 @@ describe('SnapController', () => { const rootMessenger = getControllerMessenger(); const messenger = getSnapControllerMessenger(rootMessenger); - const snapController = await getSnapController( + + await hydrateStorageService(); + + const snapController = getSnapController( getSnapControllerOptions({ messenger, state: { @@ -7674,7 +7938,7 @@ describe('SnapController', () => { }); expect(messenger.call).toHaveBeenNthCalledWith( - 7, + 10, 'PermissionController:grantPermissions', { approvedPermissions: { @@ -7734,7 +7998,10 @@ describe('SnapController', () => { const rootMessenger = getControllerMessenger(); const messenger = getSnapControllerMessenger(rootMessenger); - const snapController = await getSnapController( + + await hydrateStorageService(); + + const snapController = getSnapController( getSnapControllerOptions({ messenger, state: { @@ -7749,7 +8016,7 @@ describe('SnapController', () => { }); expect(messenger.call).toHaveBeenNthCalledWith( - 7, + 10, 'PermissionController:revokePermissions', { [MOCK_SNAP_ID]: [SnapEndowments.Rpc, 'snap_dialog'], @@ -7757,7 +8024,7 @@ describe('SnapController', () => { ); expect(messenger.call).toHaveBeenNthCalledWith( - 8, + 11, 'PermissionController:grantPermissions', { approvedPermissions: { @@ -7791,7 +8058,7 @@ describe('SnapController', () => { it('returns an error on invalid snap id', async () => { const snapId = 'foo'; const messenger = getSnapControllerMessenger(); - const controller = await getSnapController( + const controller = getSnapController( getSnapControllerOptions({ messenger }), ); await expect( @@ -7833,7 +8100,7 @@ describe('SnapController', () => { }), ); - const controller = await getSnapController( + const controller = getSnapController( getSnapControllerOptions({ messenger, detectSnapLocation: detectLocationMock, @@ -7851,10 +8118,20 @@ describe('SnapController', () => { [MOCK_SNAP_ID]: { version: newVersionRange }, }); - expect(messenger.call).toHaveBeenCalledTimes(20); + expect(messenger.call).toHaveBeenCalledTimes(22); expect(messenger.call).toHaveBeenNthCalledWith( 3, + 'StorageService:setItem', + controllerName, + MOCK_SNAP_ID, + { + sourceCode: DEFAULT_SNAP_BUNDLE, + }, + ); + + expect(messenger.call).toHaveBeenNthCalledWith( + 4, 'SubjectMetadataController:addSubjectMetadata', { subjectType: SubjectType.Snap, @@ -7934,7 +8211,7 @@ describe('SnapController', () => { ); expect(messenger.call).toHaveBeenNthCalledWith( - 17, + 19, 'SubjectMetadataController:addSubjectMetadata', { subjectType: SubjectType.Snap, @@ -7946,13 +8223,13 @@ describe('SnapController', () => { ); expect(messenger.call).toHaveBeenNthCalledWith( - 18, + 20, 'ExecutionService:executeSnap', expect.objectContaining({}), ); expect(messenger.call).toHaveBeenNthCalledWith( - 19, + 21, 'ApprovalController:updateRequestState', expect.objectContaining({ id: expect.any(String), @@ -8003,7 +8280,10 @@ describe('SnapController', () => { manifest: manifest.result, }); const messenger = getSnapControllerMessenger(); - const controller = await getSnapController( + + await hydrateStorageService(); + + const controller = getSnapController( getSnapControllerOptions({ messenger, state: { @@ -8021,7 +8301,7 @@ describe('SnapController', () => { }), ).rejects.toThrow(errorMessage); - expect(messenger.call).toHaveBeenCalledTimes(2); + expect(messenger.call).toHaveBeenCalledTimes(3); expect(messenger.call).toHaveBeenCalledWith( 'ApprovalController:updateRequestState', @@ -8053,7 +8333,10 @@ describe('SnapController', () => { Promise.reject(new Error('foo')), ); const detect = loopbackDetect(location); - const controller = await getSnapController( + + await hydrateStorageService(); + + const controller = getSnapController( getSnapControllerOptions({ messenger, state: { @@ -8069,7 +8352,7 @@ describe('SnapController', () => { }), ).rejects.toThrow('foo'); - expect(messenger.call).toHaveBeenCalledTimes(2); + expect(messenger.call).toHaveBeenCalledTimes(4); expect(detect).toHaveBeenCalledTimes(1); expect(detect).toHaveBeenCalledWith( MOCK_SNAP_ID, @@ -8188,7 +8471,7 @@ describe('SnapController', () => { const { messenger } = options; - const [controller, service] = await getSnapControllerWithEES(options); + const [controller, service] = getSnapControllerWithEES(options); await controller.installSnaps(MOCK_ORIGIN, { [snapId1]: {} }); await controller.installSnaps(MOCK_ORIGIN, { [snapId2]: {} }); @@ -8229,7 +8512,7 @@ describe('SnapController', () => { ); expect(messenger.call).toHaveBeenNthCalledWith( - 48, + 53, 'PermissionController:revokePermissions', { [MOCK_ORIGIN]: [WALLET_SNAP_PERMISSION_KEY], @@ -8237,7 +8520,7 @@ describe('SnapController', () => { ); expect(messenger.call).toHaveBeenNthCalledWith( - 59, + 67, 'PermissionController:grantPermissions', { approvedPermissions: { @@ -8289,7 +8572,7 @@ describe('SnapController', () => { detectSnapLocation: detect, }); const { messenger } = options; - const [controller, service] = await getSnapControllerWithEES(options); + const [controller, service] = getSnapControllerWithEES(options); const listener = jest.fn(); messenger.subscribe('SnapController:snapRolledback' as any, listener); @@ -8340,7 +8623,7 @@ describe('SnapController', () => { }), }); - const controller = await getSnapController( + const controller = getSnapController( getSnapControllerOptions({ detectSnapLocation: loopbackDetect({ manifest, @@ -8368,7 +8651,7 @@ describe('SnapController', () => { localizationFiles: [getMockLocalizationFile()], }); - const snapController = await getSnapController( + const snapController = getSnapController( getSnapControllerOptions({ messenger, detectSnapLocation: loopbackDetect({ @@ -8416,7 +8699,7 @@ describe('SnapController', () => { localizationFiles: [getMockLocalizationFile({ messages: {} })], }); - const snapController = await getSnapController( + const snapController = getSnapController( getSnapControllerOptions({ messenger, detectSnapLocation: loopbackDetect({ @@ -8439,7 +8722,7 @@ describe('SnapController', () => { it('installs a local Snap as preinstalled Snap when `forcePreinstalledSnaps` is enabled', async () => { const messenger = getSnapControllerMessenger(); - const snapController = await getSnapController( + const snapController = getSnapController( getSnapControllerOptions({ messenger, detectSnapLocation: loopbackDetect(), @@ -8479,7 +8762,12 @@ describe('SnapController', () => { }); const messenger = getSnapControllerMessenger(); - const snapController = await getSnapController( + + await hydrateStorageService({ + [MOCK_LOCAL_SNAP_ID]: { sourceCode: DEFAULT_SNAP_BUNDLE }, + }); + + const snapController = getSnapController( getSnapControllerOptions({ messenger, detectSnapLocation: loopbackDetect(location), @@ -8521,7 +8809,7 @@ describe('SnapController', () => { sourceCode: 'a'.repeat(64_000_001), }); - const snapController = await getSnapController( + const snapController = getSnapController( getSnapControllerOptions({ messenger, detectSnapLocation: loopbackDetect({ @@ -8553,7 +8841,10 @@ describe('SnapController', () => { manifest: manifest.result, }); const messenger = getSnapControllerMessenger(); - const controller = await getSnapController( + + await hydrateStorageService(); + + const controller = getSnapController( getSnapControllerOptions({ messenger, state: { @@ -8595,7 +8886,10 @@ describe('SnapController', () => { const detectSnapLocation = loopbackDetect({ manifest: manifest.result, }); - const controller = await getSnapController( + + await hydrateStorageService(); + + const controller = getSnapController( getSnapControllerOptions({ messenger, state: { @@ -8628,7 +8922,10 @@ describe('SnapController', () => { manifest: manifest.result, }); const messenger = getSnapControllerMessenger(); - const controller = await getSnapController( + + await hydrateStorageService(); + + const controller = getSnapController( getSnapControllerOptions({ messenger, state: { @@ -8690,7 +8987,7 @@ describe('SnapController', () => { }), ); const messenger = getSnapControllerMessenger(); - const controller = await getSnapController( + const controller = getSnapController( getSnapControllerOptions({ messenger, detectSnapLocation, @@ -8727,7 +9024,7 @@ describe('SnapController', () => { date: expect.any(Number), }, ]); - expect(callActionSpy).toHaveBeenCalledTimes(20); + expect(callActionSpy).toHaveBeenCalledTimes(22); expect(callActionSpy).toHaveBeenNthCalledWith( 12, @@ -8798,13 +9095,13 @@ describe('SnapController', () => { ); expect(callActionSpy).toHaveBeenNthCalledWith( - 18, + 20, 'ExecutionService:executeSnap', expect.objectContaining({}), ); expect(messenger.call).toHaveBeenNthCalledWith( - 19, + 21, 'ApprovalController:updateRequestState', expect.objectContaining({ id: expect.any(String), @@ -8874,7 +9171,7 @@ describe('SnapController', () => { }), ); - const controller = await getSnapController( + const controller = getSnapController( getSnapControllerOptions({ messenger, detectSnapLocation, @@ -8892,7 +9189,7 @@ describe('SnapController', () => { [MOCK_SNAP_ID]: { version: '1.1.0' }, }); - expect(callActionSpy).toHaveBeenCalledTimes(20); + expect(callActionSpy).toHaveBeenCalledTimes(22); expect(callActionSpy).toHaveBeenNthCalledWith( 12, 'ApprovalController:addRequest', @@ -8990,7 +9287,7 @@ describe('SnapController', () => { }), ); - const controller = await getSnapController( + const controller = getSnapController( getSnapControllerOptions({ messenger, detectSnapLocation, @@ -9008,7 +9305,7 @@ describe('SnapController', () => { [MOCK_SNAP_ID]: { version: '1.1.0' }, }); - expect(callActionSpy).toHaveBeenCalledTimes(21); + expect(callActionSpy).toHaveBeenCalledTimes(23); expect(callActionSpy).toHaveBeenNthCalledWith( 12, 'ApprovalController:addRequest', @@ -9056,7 +9353,7 @@ describe('SnapController', () => { ); expect(messenger.call).toHaveBeenNthCalledWith( - 18, + 20, 'PermissionController:revokePermissions', { [MOCK_SNAP_ID]: ['endowment:ethereum-provider', 'endowment:caip25'], @@ -9079,7 +9376,10 @@ describe('SnapController', () => { }), ); const messenger = getSnapControllerMessenger(); - const controller = await getSnapController( + + await hydrateStorageService(); + + const controller = getSnapController( getSnapControllerOptions({ messenger, state: { @@ -9128,7 +9428,10 @@ describe('SnapController', () => { manifest: manifest.result, }); const messenger = getSnapControllerMessenger(); - const controller = await getSnapController( + + await hydrateStorageService(); + + const controller = getSnapController( getSnapControllerOptions({ messenger, state: { @@ -9149,16 +9452,16 @@ describe('SnapController', () => { const isRunning = controller.isRunning(MOCK_SNAP_ID); - expect(callActionSpy).toHaveBeenCalledTimes(11); + expect(callActionSpy).toHaveBeenCalledTimes(15); expect(callActionSpy).toHaveBeenNthCalledWith( - 1, + 3, 'ExecutionService:executeSnap', expect.objectContaining({ snapId: MOCK_SNAP_ID }), ); expect(callActionSpy).toHaveBeenNthCalledWith( - 2, + 4, 'ApprovalController:addRequest', { origin: MOCK_ORIGIN, @@ -9180,13 +9483,13 @@ describe('SnapController', () => { ); expect(callActionSpy).toHaveBeenNthCalledWith( - 4, + 6, 'PermissionController:getPermissions', MOCK_SNAP_ID, ); expect(callActionSpy).toHaveBeenNthCalledWith( - 5, + 7, 'ApprovalController:updateRequestState', expect.objectContaining({ id: expect.any(String), @@ -9205,7 +9508,7 @@ describe('SnapController', () => { ); expect(callActionSpy).toHaveBeenNthCalledWith( - 6, + 8, 'ApprovalController:addRequest', expect.objectContaining({ id: expect.any(String), @@ -9226,13 +9529,13 @@ describe('SnapController', () => { ); expect(callActionSpy).toHaveBeenNthCalledWith( - 7, + 9, 'ExecutionService:terminateSnap', MOCK_SNAP_ID, ); expect(callActionSpy).toHaveBeenNthCalledWith( - 10, + 14, 'ApprovalController:updateRequestState', expect.objectContaining({ id: expect.any(String), @@ -9259,7 +9562,10 @@ describe('SnapController', () => { const detectSnapLocation = loopbackDetect({ manifest: manifest.result, }); - const controller = await getSnapController( + + await hydrateStorageService(); + + const controller = getSnapController( getSnapControllerOptions({ messenger, state: { @@ -9278,13 +9584,6 @@ describe('SnapController', () => { }, }; - rootMessenger.registerActionHandler( - 'PermissionController:hasPermission', - () => { - return true; - }, - ); - rootMessenger.registerActionHandler( 'ApprovalController:addRequest', async (request) => { @@ -9319,10 +9618,10 @@ describe('SnapController', () => { const newSnap = controller.get(MOCK_SNAP_ID); expect(newSnap?.version).toBe('1.0.0'); - expect(callActionSpy).toHaveBeenCalledTimes(5); + expect(callActionSpy).toHaveBeenCalledTimes(7); expect(callActionSpy).toHaveBeenNthCalledWith( - 1, + 2, 'ApprovalController:addRequest', { origin: MOCK_ORIGIN, @@ -9344,13 +9643,13 @@ describe('SnapController', () => { ); expect(callActionSpy).toHaveBeenNthCalledWith( - 3, + 4, 'PermissionController:getPermissions', MOCK_SNAP_ID, ); expect(callActionSpy).toHaveBeenNthCalledWith( - 4, + 5, 'ApprovalController:updateRequestState', expect.objectContaining({ id: expect.any(String), @@ -9458,17 +9757,10 @@ describe('SnapController', () => { }), ); - const controller = await getSnapController( + const controller = getSnapController( getSnapControllerOptions({ messenger, detectSnapLocation: detect }), ); - rootMessenger.registerActionHandler( - 'PermissionController:hasPermission', - () => { - return true; - }, - ); - rootMessenger.registerActionHandler( 'ApprovalController:addRequest', async (request) => { @@ -9504,7 +9796,7 @@ describe('SnapController', () => { [MOCK_SNAP_ID]: { version: '1.1.0' }, }); - expect(callActionSpy).toHaveBeenCalledTimes(22); + expect(callActionSpy).toHaveBeenCalledTimes(24); expect(callActionSpy).toHaveBeenNthCalledWith( 12, @@ -9576,13 +9868,13 @@ describe('SnapController', () => { ); expect(callActionSpy).toHaveBeenNthCalledWith( - 18, + 20, 'PermissionController:revokePermissions', { [MOCK_SNAP_ID]: ['snap_manageState'] }, ); expect(callActionSpy).toHaveBeenNthCalledWith( - 19, + 21, 'PermissionController:grantPermissions', { approvedPermissions: { 'endowment:network-access': {} }, @@ -9599,13 +9891,13 @@ describe('SnapController', () => { ); expect(callActionSpy).toHaveBeenNthCalledWith( - 20, + 22, 'ExecutionService:executeSnap', expect.anything(), ); expect(callActionSpy).toHaveBeenNthCalledWith( - 21, + 23, 'ApprovalController:updateRequestState', expect.objectContaining({ id: expect.any(String), @@ -9674,7 +9966,9 @@ describe('SnapController', () => { }), }); - const snapController = await getSnapController( + await hydrateStorageService(); + + const snapController = getSnapController( getSnapControllerOptions({ messenger, state: { @@ -9821,20 +10115,13 @@ describe('SnapController', () => { ); /* eslint-enable @typescript-eslint/naming-convention */ - const snapController = await getSnapController( + const snapController = getSnapController( getSnapControllerOptions({ messenger, detectSnapLocation: detect, }), ); - rootMessenger.registerActionHandler( - 'PermissionController:hasPermission', - () => { - return true; - }, - ); - rootMessenger.registerActionHandler( 'ApprovalController:addRequest', async (request) => { @@ -9886,7 +10173,10 @@ describe('SnapController', () => { manifest: manifest.result, }); const messenger = getSnapControllerMessenger(); - const controller = await getSnapController( + + await hydrateStorageService(); + + const controller = getSnapController( getSnapControllerOptions({ messenger, state: { @@ -9910,7 +10200,10 @@ describe('SnapController', () => { describe('removeSnap', () => { it('will remove the "wallet_snap" permission from a subject that no longer has any permitted snaps', async () => { const messenger = getSnapControllerMessenger(); - const snapController = await getSnapController( + + await hydrateStorageService(); + + const snapController = getSnapController( getSnapControllerOptions({ messenger, state: { @@ -9944,9 +10237,9 @@ describe('SnapController', () => { }); await snapController.removeSnap(MOCK_SNAP_ID); - expect(callActionSpy).toHaveBeenCalledTimes(4); + expect(callActionSpy).toHaveBeenCalledTimes(6); expect(callActionSpy).toHaveBeenNthCalledWith( - 4, + 5, 'PermissionController:revokePermissions', { [MOCK_ORIGIN]: [WALLET_SNAP_PERMISSION_KEY], @@ -9958,7 +10251,13 @@ describe('SnapController', () => { it('will update the "wallet_snap" permission from a subject that has one or more permitted snaps', async () => { const messenger = getSnapControllerMessenger(); - const snapController = await getSnapController( + + await hydrateStorageService({ + [MOCK_SNAP_ID]: { sourceCode: DEFAULT_SNAP_BUNDLE }, + [`${MOCK_SNAP_ID}2`]: { sourceCode: DEFAULT_SNAP_BUNDLE }, + }); + + const snapController = getSnapController( getSnapControllerOptions({ messenger, state: { @@ -9998,9 +10297,9 @@ describe('SnapController', () => { }); await snapController.removeSnap(MOCK_SNAP_ID); - expect(callActionSpy).toHaveBeenCalledTimes(4); + expect(callActionSpy).toHaveBeenCalledTimes(7); expect(callActionSpy).toHaveBeenNthCalledWith( - 4, + 6, 'PermissionController:updateCaveat', MOCK_ORIGIN, WALLET_SNAP_PERMISSION_KEY, @@ -10013,7 +10312,10 @@ describe('SnapController', () => { it("will skip subjects that don't have the snap permission", async () => { const messenger = getSnapControllerMessenger(); - const snapController = await getSnapController( + + await hydrateStorageService(); + + const snapController = getSnapController( getSnapControllerOptions({ messenger, state: { @@ -10051,9 +10353,9 @@ describe('SnapController', () => { }); await snapController.removeSnap(MOCK_SNAP_ID); - expect(callActionSpy).toHaveBeenCalledTimes(5); + expect(callActionSpy).toHaveBeenCalledTimes(7); expect(callActionSpy).toHaveBeenNthCalledWith( - 4, + 5, 'PermissionController:revokePermissions', { [MOCK_ORIGIN]: [WALLET_SNAP_PERMISSION_KEY], @@ -10071,7 +10373,10 @@ describe('SnapController', () => { it('removes snap state', async () => { const messenger = getSnapControllerMessenger(); - const snapController = await getSnapController( + + await hydrateStorageService(); + + const snapController = getSnapController( getSnapControllerOptions({ messenger, state: { @@ -10097,7 +10402,10 @@ describe('SnapController', () => { describe('enableSnap', () => { it('enables a disabled snap', async () => { const messenger = getSnapControllerMessenger(); - const snapController = await getSnapController( + + await hydrateStorageService(); + + const snapController = getSnapController( getSnapControllerOptions({ state: { snaps: getPersistedSnapsState( @@ -10120,8 +10428,8 @@ describe('SnapController', () => { snapController.destroy(); }); - it('throws an error if the specified snap does not exist', async () => { - const snapController = await getSnapController(); + it('throws an error if the specified snap does not exist', () => { + const snapController = getSnapController(); expect(() => snapController.enableSnap(MOCK_SNAP_ID)).toThrow( `Snap "${MOCK_SNAP_ID}" not found.`, ); @@ -10130,7 +10438,9 @@ describe('SnapController', () => { }); it('throws an error if the specified snap is blocked', async () => { - const snapController = await getSnapController( + await hydrateStorageService(); + + const snapController = getSnapController( getSnapControllerOptions({ state: { snaps: getPersistedSnapsState( @@ -10151,7 +10461,10 @@ describe('SnapController', () => { describe('disableSnap', () => { it('disables a snap', async () => { const messenger = getSnapControllerMessenger(); - const snapController = await getSnapController( + + await hydrateStorageService(); + + const snapController = getSnapController( getSnapControllerOptions({ state: { snaps: getPersistedSnapsState(), @@ -10173,7 +10486,9 @@ describe('SnapController', () => { }); it('stops a running snap when disabling it', async () => { - const snapController = await getSnapController( + await hydrateStorageService(); + + const snapController = getSnapController( getSnapControllerOptions({ state: { snaps: getPersistedSnapsState(), @@ -10194,7 +10509,7 @@ describe('SnapController', () => { }); it('throws an error if the specified snap does not exist', async () => { - const snapController = await getSnapController(); + const snapController = getSnapController(); await expect(snapController.disableSnap(MOCK_SNAP_ID)).rejects.toThrow( `Snap "${MOCK_SNAP_ID}" not found.`, ); @@ -10209,7 +10524,9 @@ describe('SnapController', () => { const rootMessenger = getControllerMessenger(registry); const messenger = getSnapControllerMessenger(rootMessenger); - const snapController = await getSnapController( + await hydrateStorageService(); + + const snapController = getSnapController( getSnapControllerOptions({ messenger, state: { @@ -10240,7 +10557,12 @@ describe('SnapController', () => { origin: 'bar.io', }); - const snapController = await getSnapController( + await hydrateStorageService({ + [mockSnapA.id]: { sourceCode: DEFAULT_SNAP_BUNDLE }, + [mockSnapB.id]: { sourceCode: DEFAULT_SNAP_BUNDLE }, + }); + + const snapController = getSnapController( getSnapControllerOptions({ messenger, state: { @@ -10305,7 +10627,11 @@ describe('SnapController', () => { origin: MOCK_ORIGIN, }); - const snapController = await getSnapController( + await hydrateStorageService({ + [mockSnap.id]: { sourceCode: DEFAULT_SNAP_BUNDLE }, + }); + + const snapController = getSnapController( getSnapControllerOptions({ messenger, state: { @@ -10348,7 +10674,12 @@ describe('SnapController', () => { origin: 'bar.io', }); - const snapController = await getSnapController( + await hydrateStorageService({ + [mockSnapA.id]: { sourceCode: DEFAULT_SNAP_BUNDLE }, + [mockSnapB.id]: { sourceCode: DEFAULT_SNAP_BUNDLE }, + }); + + const snapController = getSnapController( getSnapControllerOptions({ messenger, state: { @@ -10403,7 +10734,11 @@ describe('SnapController', () => { origin: MOCK_ORIGIN, }); - const snapController = await getSnapController( + await hydrateStorageService({ + [mockSnap.id]: { sourceCode: DEFAULT_SNAP_BUNDLE }, + }); + + const snapController = getSnapController( getSnapControllerOptions({ messenger, state: { @@ -10447,7 +10782,11 @@ describe('SnapController', () => { origin: MOCK_ORIGIN, }); - const snapController = await getSnapController( + await hydrateStorageService({ + [mockSnap.id]: { sourceCode: DEFAULT_SNAP_BUNDLE }, + }); + + const snapController = getSnapController( getSnapControllerOptions({ messenger, state: { @@ -10504,6 +10843,10 @@ describe('SnapController', () => { preinstalled: true, }); + await hydrateStorageService({ + [snapId]: { sourceCode: DEFAULT_SNAP_BUNDLE }, + }); + const updateVersion = '1.2.1'; registry.resolveVersion.mockResolvedValue(updateVersion); @@ -10521,7 +10864,7 @@ describe('SnapController', () => { ), }); - const snapController = await getSnapController( + const snapController = getSnapController( getSnapControllerOptions({ messenger, state: { @@ -10543,13 +10886,13 @@ describe('SnapController', () => { expect(updatedSnap.preinstalled).toBe(true); expect(messenger.call).toHaveBeenNthCalledWith( - 7, + 10, 'PermissionController:revokePermissions', { [snapId]: [SnapEndowments.Rpc, SnapEndowments.LifecycleHooks] }, ); expect(messenger.call).toHaveBeenNthCalledWith( - 8, + 11, 'PermissionController:grantPermissions', { approvedPermissions: { @@ -10580,11 +10923,15 @@ describe('SnapController', () => { preinstalled: true, }); + await hydrateStorageService({ + [snapId]: { sourceCode: DEFAULT_SNAP_BUNDLE }, + }); + const updateVersion = '1.2.1'; registry.resolveVersion.mockResolvedValue(updateVersion); - const snapController = await getSnapController( + const snapController = getSnapController( getSnapControllerOptions({ messenger, state: { @@ -10610,17 +10957,19 @@ describe('SnapController', () => { describe('clearState', () => { it('clears the state, terminates running Snaps and cancels pending requests', async () => { - const options = getSnapControllerWithEESOptions({ - state: { - snaps: getPersistedSnapsState( - getPersistedSnapObject({ - sourceCode: ` + await hydrateStorageService({ + [MOCK_SNAP_ID]: { + sourceCode: ` module.exports.onRpcRequest = () => { while(true) {} }; `, - }), - ), + }, + }); + + const options = getSnapControllerWithEESOptions({ + state: { + snaps: getPersistedSnapsState(getPersistedSnapObject()), snapStates: { [MOCK_SNAP_ID]: JSON.stringify({ foo: 'bar' }), }, @@ -10629,10 +10978,12 @@ describe('SnapController', () => { }, }, }); - const [snapController] = await getSnapControllerWithEES(options); + const [snapController] = getSnapControllerWithEES(options); const { messenger } = options; + await waitForControllerToBeReady(messenger); + const callActionSpy = jest.spyOn(messenger, 'call'); expect(snapController.has(MOCK_SNAP_ID)).toBe(true); @@ -10648,8 +10999,6 @@ describe('SnapController', () => { await waitForStateChange(messenger); - await waitForStateChange(messenger); - expect(snapController.isRunning(MOCK_SNAP_ID)).toBe(true); await new Promise((resolve) => setTimeout(resolve, 100)); @@ -10731,7 +11080,10 @@ describe('SnapController', () => { ); const messenger = getSnapControllerMessenger(rootMessenger); - const snapController = await getSnapController( + + await hydrateStorageService(); + + const snapController = getSnapController( getSnapControllerOptions({ messenger, state: { @@ -10796,23 +11148,6 @@ describe('SnapController', () => { describe('SnapController actions', () => { describe('SnapController:init', () => { - it('populates `isReady`', async () => { - const rootMessenger = getControllerMessenger(); - const messenger = getSnapControllerMessenger(rootMessenger); - - const snapController = getSnapController( - getSnapControllerOptions({ messenger }), - ); - - expect(snapController.state.isReady).toBe(false); - messenger.call('SnapController:init'); - - await waitForStateChange(messenger); - expect(snapController.state.isReady).toBe(true); - - snapController.destroy(); - }); - it('calls `onStart` for all Snaps with the `endowment:lifecycle-hooks` permission', async () => { const rootMessenger = getControllerMessenger(); const messenger = getSnapControllerMessenger(rootMessenger); @@ -10842,7 +11177,12 @@ describe('SnapController', () => { }, ); - const snapController = await getSnapController( + await hydrateStorageService({ + [MOCK_SNAP_ID]: { sourceCode: DEFAULT_SNAP_BUNDLE }, + [MOCK_LOCAL_SNAP_ID]: { sourceCode: DEFAULT_SNAP_BUNDLE }, + }); + + const snapController = getSnapController( getSnapControllerOptions({ messenger, state: { @@ -10870,13 +11210,13 @@ describe('SnapController', () => { ); expect(call).toHaveBeenNthCalledWith( - 6, + 11, 'ExecutionService:executeSnap', expect.any(Object), ); expect(messenger.call).toHaveBeenNthCalledWith( - 7, + 12, 'ExecutionService:handleRpcRequest', MOCK_SNAP_ID, { @@ -10901,6 +11241,13 @@ describe('SnapController', () => { const rootMessenger = getControllerMessenger(); const messenger = getSnapControllerMessenger(rootMessenger); + rootMessenger.registerActionHandler( + 'PermissionController:hasPermission', + (_origin, permission) => + permission === SnapEndowments.LifecycleHooks || + permission === handlerEndowments.onStart, + ); + rootMessenger.registerActionHandler( 'PermissionController:getPermissions', () => { @@ -10917,16 +11264,21 @@ describe('SnapController', () => { }, ); - const snapController = await getSnapController( + await hydrateStorageService(); + + const snapController = getSnapController( getSnapControllerOptions({ messenger, state: { snaps: getPersistedSnapsState(), }, }), + false, ); messenger.call('SnapController:init'); + + await waitForControllerToBeReady(messenger); await sleep(10); expect(consoleErrorSpy).toHaveBeenCalledWith( @@ -10941,7 +11293,9 @@ describe('SnapController', () => { it('gets a snap', async () => { const messenger = getSnapControllerMessenger(); - const snapController = await getSnapController( + await hydrateStorageService(); + + const snapController = getSnapController( getSnapControllerOptions({ messenger, state: { @@ -10964,7 +11318,9 @@ describe('SnapController', () => { it('handles a snap RPC request', async () => { const messenger = getSnapControllerMessenger(); - const snapController = await getSnapController( + await hydrateStorageService(); + + const snapController = getSnapController( getSnapControllerOptions({ messenger, state: { @@ -10997,7 +11353,9 @@ describe('SnapController', () => { getNodeEESMessenger(rootMessenger), ) as unknown as NodeThreadExecutionService; - const [snapController] = await getSnapControllerWithEES( + await hydrateStorageService(); + + const [snapController] = getSnapControllerWithEES( getSnapControllerWithEESOptions({ rootMessenger, trackEvent: mockTrackEvent, @@ -11063,7 +11421,9 @@ describe('SnapController', () => { getNodeEESMessenger(rootMessenger), ) as unknown as NodeThreadExecutionService; - const [snapController] = await getSnapControllerWithEES( + await hydrateStorageService(); + + const [snapController] = getSnapControllerWithEES( getSnapControllerWithEESOptions({ environmentEndowmentPermissions: ['endowment:cronjob'], rootMessenger, @@ -11105,7 +11465,9 @@ describe('SnapController', () => { getNodeEESMessenger(rootMessenger), ) as unknown as NodeThreadExecutionService; - const [snapController] = await getSnapControllerWithEES( + await hydrateStorageService(); + + const [snapController] = getSnapControllerWithEES( getSnapControllerWithEESOptions({ rootMessenger, trackEvent: mockTrackEvent, @@ -11147,7 +11509,9 @@ describe('SnapController', () => { getNodeEESMessenger(rootMessenger), ) as unknown as NodeThreadExecutionService; - const [snapController] = await getSnapControllerWithEES( + await hydrateStorageService(); + + const [snapController] = getSnapControllerWithEES( getSnapControllerWithEESOptions({ rootMessenger, trackEvent: mockTrackEvent, @@ -11183,7 +11547,9 @@ describe('SnapController', () => { it('handles a transaction insight request', async () => { const messenger = getSnapControllerMessenger(); - const snapController = await getSnapController( + await hydrateStorageService(); + + const snapController = getSnapController( getSnapControllerOptions({ messenger, state: { @@ -11212,7 +11578,9 @@ describe('SnapController', () => { it('handles a signature insight request', async () => { const messenger = getSnapControllerMessenger(); - const snapController = await getSnapController( + await hydrateStorageService(); + + const snapController = getSnapController( getSnapControllerOptions({ messenger, state: { @@ -11241,7 +11609,9 @@ describe('SnapController', () => { it('handles a name lookup request', async () => { const messenger = getSnapControllerMessenger(); - const snapController = await getSnapController( + await hydrateStorageService(); + + const snapController = getSnapController( getSnapControllerOptions({ messenger, state: { @@ -11290,7 +11660,9 @@ describe('SnapController', () => { DEFAULT_ENCRYPTION_KEY_DERIVATION_OPTIONS, ); - const snapController = await getSnapController( + await hydrateStorageService(); + + const snapController = getSnapController( getSnapControllerOptions({ messenger, state: { @@ -11333,7 +11705,9 @@ describe('SnapController', () => { }, ); - const snapController = await getSnapController( + await hydrateStorageService(); + + const snapController = getSnapController( getSnapControllerOptions({ messenger, state: { @@ -11347,6 +11721,8 @@ describe('SnapController', () => { }), ); + await waitForControllerToBeReady(messenger); + const newState = { myVariable: 2 }; const promise = waitForStateChange(messenger); @@ -11387,7 +11763,12 @@ describe('SnapController', () => { const state = { foo: 'bar' }; - const snapController = await getSnapController( + await hydrateStorageService({ + [MOCK_SNAP_ID]: { sourceCode: DEFAULT_SNAP_BUNDLE }, + [MOCK_LOCAL_SNAP_ID]: { sourceCode: DEFAULT_SNAP_BUNDLE }, + }); + + const snapController = getSnapController( getSnapControllerOptions({ messenger, state: { @@ -11464,7 +11845,9 @@ describe('SnapController', () => { ), ); - const snapController = await getSnapController( + await hydrateStorageService(); + + const snapController = getSnapController( getSnapControllerOptions({ messenger, state: { @@ -11488,7 +11871,9 @@ describe('SnapController', () => { it('throws an error if the state is corrupt', async () => { const messenger = getSnapControllerMessenger(); - const snapController = await getSnapController( + await hydrateStorageService(); + + const snapController = getSnapController( getSnapControllerOptions({ messenger, state: { @@ -11518,7 +11903,9 @@ describe('SnapController', () => { const state = { foo: 'bar' }; - const snapController = await getSnapController( + await hydrateStorageService(); + + const snapController = getSnapController( getSnapControllerOptions({ messenger, state: { @@ -11548,7 +11935,9 @@ describe('SnapController', () => { it(`returns null if the Snap has no state yet`, async () => { const messenger = getSnapControllerMessenger(); - const snapController = await getSnapController( + await hydrateStorageService(); + + const snapController = getSnapController( getSnapControllerOptions({ messenger, state: { @@ -11579,14 +11968,18 @@ describe('SnapController', () => { it('checks if a snap exists in state', async () => { const messenger = getSnapControllerMessenger(); const id = 'npm:fooSnap' as SnapId; - const snapController = await getSnapController( + + await hydrateStorageService({ + [id]: { sourceCode: DEFAULT_SNAP_BUNDLE }, + }); + + const snapController = getSnapController( getSnapControllerOptions({ messenger, state: { snaps: getPersistedSnapsState( getPersistedSnapObject({ version: '0.0.1', - sourceCode: DEFAULT_SNAP_BUNDLE, id, manifest: getSnapManifest(), enabled: true, @@ -11619,7 +12012,9 @@ describe('SnapController', () => { it(`updates the snap's state`, async () => { const messenger = getSnapControllerMessenger(); - const snapController = await getSnapController( + await hydrateStorageService(); + + const snapController = getSnapController( getSnapControllerOptions({ messenger, state: { @@ -11660,7 +12055,9 @@ describe('SnapController', () => { it(`updates the snap's unencrypted state`, async () => { const messenger = getSnapControllerMessenger(); - const snapController = await getSnapController( + await hydrateStorageService(); + + const snapController = getSnapController( getSnapControllerOptions({ messenger, state: { @@ -11669,6 +12066,8 @@ describe('SnapController', () => { }), ); + await waitForControllerToBeReady(messenger); + const updateSnapStateSpy = jest.spyOn(snapController, 'updateSnapState'); const state = { foo: 'bar' }; @@ -11701,7 +12100,9 @@ describe('SnapController', () => { hmac(sha512, key, data), ); - const snapController = await getSnapController( + await hydrateStorageService(); + + const snapController = getSnapController( getSnapControllerOptions({ messenger, state: { @@ -11713,6 +12114,8 @@ describe('SnapController', () => { }), ); + await waitForControllerToBeReady(messenger); + const state = { foo: 'bar' }; const promise = waitForStateChange(messenger); @@ -11737,7 +12140,9 @@ describe('SnapController', () => { const encryptor = getSnapControllerEncryptor(); const encryptWithKey = jest.spyOn(encryptor, 'encryptWithKey'); - const snapController = await getSnapController( + await hydrateStorageService(); + + const snapController = getSnapController( getSnapControllerOptions({ messenger, state: { @@ -11751,6 +12156,8 @@ describe('SnapController', () => { }), ); + await waitForControllerToBeReady(messenger); + const promise = waitForStateChange(messenger); await messenger.call( 'SnapController:updateSnapState', @@ -11805,7 +12212,10 @@ describe('SnapController', () => { const messenger = getSnapControllerMessenger(); const errorValue = new Error('Failed to persist state.'); - const snapController = await getSnapController( + + await hydrateStorageService(); + + const snapController = getSnapController( getSnapControllerOptions({ messenger, state: { @@ -11850,7 +12260,9 @@ describe('SnapController', () => { it('clears the state of a snap', async () => { const messenger = getSnapControllerMessenger(); - const snapController = await getSnapController( + await hydrateStorageService(); + + const snapController = getSnapController( getSnapControllerOptions({ messenger, state: { @@ -11878,7 +12290,9 @@ describe('SnapController', () => { it('clears the unencrypted state of a snap', async () => { const messenger = getSnapControllerMessenger(); - const snapController = await getSnapController( + await hydrateStorageService(); + + const snapController = getSnapController( getSnapControllerOptions({ messenger, state: { @@ -11907,7 +12321,10 @@ describe('SnapController', () => { const messenger = getSnapControllerMessenger(); const errorValue = new Error('Failed to persist state.'); - const snapController = await getSnapController( + + await hydrateStorageService(); + + const snapController = getSnapController( getSnapControllerOptions({ messenger, state: { @@ -11947,7 +12364,7 @@ describe('SnapController', () => { describe('SnapController:updateRegistry', () => { it('calls SnapController.updateRegistry()', async () => { const messenger = getSnapControllerMessenger(); - const snapController = await getSnapController( + const snapController = getSnapController( getSnapControllerOptions({ messenger, }), @@ -11973,7 +12390,11 @@ describe('SnapController', () => { enabled: false, }); - const snapController = await getSnapController( + await hydrateStorageService({ + [mockSnap.id]: { sourceCode: DEFAULT_SNAP_BUNDLE }, + }); + + const snapController = getSnapController( getSnapControllerOptions({ messenger, state: { @@ -11998,7 +12419,11 @@ describe('SnapController', () => { enabled: true, }); - const snapController = await getSnapController( + await hydrateStorageService({ + [mockSnap.id]: { sourceCode: DEFAULT_SNAP_BUNDLE }, + }); + + const snapController = getSnapController( getSnapControllerOptions({ messenger, state: { @@ -12023,7 +12448,11 @@ describe('SnapController', () => { enabled: true, }); - const snapController = await getSnapController( + await hydrateStorageService({ + [mockSnap.id]: { sourceCode: DEFAULT_SNAP_BUNDLE }, + }); + + const snapController = getSnapController( getSnapControllerOptions({ messenger, state: { @@ -12048,7 +12477,9 @@ describe('SnapController', () => { origin: MOCK_ORIGIN, }); - const snapController = await getSnapController( + await hydrateStorageService(); + + const snapController = getSnapController( getSnapControllerOptions({ messenger, state: { @@ -12080,7 +12511,9 @@ describe('SnapController', () => { origin: MOCK_ORIGIN, }); - const snapController = await getSnapController( + await hydrateStorageService(); + + const snapController = getSnapController( getSnapControllerOptions({ messenger, state: { @@ -12109,7 +12542,12 @@ describe('SnapController', () => { enabled: false, }); - const snapController = await getSnapController( + await hydrateStorageService({ + [mockSnap.id]: { sourceCode: DEFAULT_SNAP_BUNDLE }, + [mockSnap2.id]: { sourceCode: DEFAULT_SNAP_BUNDLE }, + }); + + const snapController = getSnapController( getSnapControllerOptions({ messenger, state: { @@ -12131,7 +12569,7 @@ describe('SnapController', () => { describe('SnapController:install', () => { it('calls SnapController.installSnaps()', async () => { const messenger = getSnapControllerMessenger(); - const snapController = await getSnapController( + const snapController = getSnapController( getSnapControllerOptions({ messenger, }), @@ -12164,8 +12602,18 @@ describe('SnapController', () => { const snapObjects = permittedSnaps.map((snapId) => getPersistedSnapObject({ id: snapId as SnapId }), ); + + await hydrateStorageService( + permittedSnaps.reduce>( + (acc, snapId) => { + acc[snapId] = { sourceCode: DEFAULT_SNAP_BUNDLE }; + return acc; + }, + {}, + ), + ); const snaps = getPersistedSnapsState(...snapObjects); - const snapController = await getSnapController( + const snapController = getSnapController( getSnapControllerOptions({ messenger, state: { @@ -12186,9 +12634,9 @@ describe('SnapController', () => { MOCK_ORIGIN, MOCK_SNAP_ID, ); - expect(callActionSpy).toHaveBeenCalledTimes(3); + expect(callActionSpy).toHaveBeenCalledTimes(9); expect(callActionSpy).toHaveBeenNthCalledWith( - 3, + 9, 'PermissionController:updateCaveat', MOCK_ORIGIN, WALLET_SNAP_PERMISSION_KEY, @@ -12210,9 +12658,9 @@ describe('SnapController', () => { }); describe('SnapController:revokeDynamicPermissions', () => { - it('calls PermissionController:revokePermissions', async () => { + it('calls PermissionController:revokePermissions', () => { const messenger = getSnapControllerMessenger(); - const snapController = await getSnapController( + const snapController = getSnapController( getSnapControllerOptions({ messenger, }), @@ -12232,9 +12680,9 @@ describe('SnapController', () => { snapController.destroy(); }); - it('throws if input permission is not a dynamic permission', async () => { + it('throws if input permission is not a dynamic permission', () => { const messenger = getSnapControllerMessenger(); - const snapController = await getSnapController( + const snapController = getSnapController( getSnapControllerOptions({ messenger, }), @@ -12266,7 +12714,7 @@ describe('SnapController', () => { const messenger = getSnapControllerMessenger(); - const snapController = await getSnapController( + const snapController = getSnapController( getSnapControllerOptions({ messenger, detectSnapLocation: loopbackDetect({ @@ -12319,7 +12767,7 @@ describe('SnapController', () => { const messenger = getSnapControllerMessenger(); - const snapController = await getSnapController( + const snapController = getSnapController( getSnapControllerOptions({ messenger, detectSnapLocation: loopbackDetect({ @@ -12351,7 +12799,7 @@ describe('SnapController', () => { it('returns null if file does not exist', async () => { const messenger = getSnapControllerMessenger(); - const snapController = await getSnapController( + const snapController = getSnapController( getSnapControllerOptions({ messenger, detectSnapLocation: loopbackDetect(), @@ -12387,7 +12835,7 @@ describe('SnapController', () => { const messenger = getSnapControllerMessenger(); - const snapController = await getSnapController( + const snapController = getSnapController( getSnapControllerOptions({ messenger, detectSnapLocation: loopbackDetect({ @@ -12432,7 +12880,10 @@ describe('SnapController', () => { it('calls the `onInstall` lifecycle hook', async () => { const rootMessenger = getControllerMessenger(); const messenger = getSnapControllerMessenger(rootMessenger); - const snapController = await getSnapController( + + await hydrateStorageService(); + + const snapController = getSnapController( getSnapControllerOptions({ messenger, state: { @@ -12441,6 +12892,13 @@ describe('SnapController', () => { }), ); + rootMessenger.registerActionHandler( + 'PermissionController:hasPermission', + (_origin, permission) => + permission === SnapEndowments.LifecycleHooks || + permission === handlerEndowments.onInstall, + ); + rootMessenger.registerActionHandler( 'PermissionController:getPermissions', () => { @@ -12466,13 +12924,13 @@ describe('SnapController', () => { ); expect(messenger.call).toHaveBeenNthCalledWith( - 3, + 5, 'ExecutionService:executeSnap', expect.any(Object), ); expect(messenger.call).toHaveBeenNthCalledWith( - 4, + 6, 'ExecutionService:handleRpcRequest', MOCK_SNAP_ID, { @@ -12492,7 +12950,10 @@ describe('SnapController', () => { it('does not call the `onInstall` lifecycle hook if the snap does not have the `endowment:lifecycle-hooks` permission', async () => { const rootMessenger = getControllerMessenger(); const messenger = getSnapControllerMessenger(rootMessenger); - const snapController = await getSnapController( + + await hydrateStorageService(); + + const snapController = getSnapController( getSnapControllerOptions({ messenger, state: { @@ -12514,7 +12975,7 @@ describe('SnapController', () => { await new Promise((resolve) => setTimeout(resolve, 10)); - expect(messenger.call).toHaveBeenCalledTimes(1); + expect(messenger.call).toHaveBeenCalledTimes(2); expect(messenger.call).not.toHaveBeenCalledWith( 'ExecutionService:handleRpcRequest', MOCK_SNAP_ID, @@ -12537,7 +12998,10 @@ describe('SnapController', () => { const rootMessenger = getControllerMessenger(); const messenger = getSnapControllerMessenger(rootMessenger); - const snapController = await getSnapController( + + await hydrateStorageService(); + + const snapController = getSnapController( getSnapControllerOptions({ messenger, state: { @@ -12574,7 +13038,10 @@ describe('SnapController', () => { it('calls the `onUpdate` lifecycle hook', async () => { const rootMessenger = getControllerMessenger(); const messenger = getSnapControllerMessenger(rootMessenger); - const snapController = await getSnapController( + + await hydrateStorageService(); + + const snapController = getSnapController( getSnapControllerOptions({ messenger, state: { @@ -12583,6 +13050,13 @@ describe('SnapController', () => { }), ); + rootMessenger.registerActionHandler( + 'PermissionController:hasPermission', + (_origin, permission) => + permission === SnapEndowments.LifecycleHooks || + permission === handlerEndowments.onUpdate, + ); + rootMessenger.registerActionHandler( 'PermissionController:getPermissions', () => { @@ -12609,13 +13083,13 @@ describe('SnapController', () => { ); expect(messenger.call).toHaveBeenNthCalledWith( - 3, + 5, 'ExecutionService:executeSnap', expect.any(Object), ); expect(messenger.call).toHaveBeenNthCalledWith( - 4, + 6, 'ExecutionService:handleRpcRequest', MOCK_SNAP_ID, { @@ -12635,7 +13109,10 @@ describe('SnapController', () => { it('does not call the `onUpdate` lifecycle hook if the snap does not have the `endowment:lifecycle-hooks` permission', async () => { const rootMessenger = getControllerMessenger(); const messenger = getSnapControllerMessenger(rootMessenger); - const snapController = await getSnapController( + + await hydrateStorageService(); + + const snapController = getSnapController( getSnapControllerOptions({ messenger, state: { @@ -12657,7 +13134,7 @@ describe('SnapController', () => { await new Promise((resolve) => setTimeout(resolve, 10)); - expect(messenger.call).toHaveBeenCalledTimes(1); + expect(messenger.call).toHaveBeenCalledTimes(2); expect(messenger.call).not.toHaveBeenCalledWith( 'ExecutionService:handleRpcRequest', MOCK_SNAP_ID, @@ -12680,7 +13157,10 @@ describe('SnapController', () => { const rootMessenger = getControllerMessenger(); const messenger = getSnapControllerMessenger(rootMessenger); - const snapController = await getSnapController( + + await hydrateStorageService(); + + const snapController = getSnapController( getSnapControllerOptions({ messenger, state: { @@ -12725,7 +13205,12 @@ describe('SnapController', () => { origin: MOCK_ORIGIN, }); - const snapController = await getSnapController( + await hydrateStorageService({ + [mockSnap.id]: { sourceCode: DEFAULT_SNAP_BUNDLE }, + [mockSnap2.id]: { sourceCode: DEFAULT_SNAP_BUNDLE }, + }); + + const snapController = getSnapController( getSnapControllerOptions({ messenger, state: { @@ -12756,7 +13241,9 @@ describe('SnapController', () => { platformVersion: '6.0.0' as SemVerVersion, }); - const snapController = await getSnapController( + await hydrateStorageService(); + + const snapController = getSnapController( getSnapControllerOptions({ messenger, state: { @@ -12783,7 +13270,9 @@ describe('SnapController', () => { platformVersion: '6.0.0' as SemVerVersion, }); - const snapController = await getSnapController( + await hydrateStorageService(); + + const snapController = getSnapController( getSnapControllerOptions({ messenger, state: { @@ -12810,7 +13299,9 @@ describe('SnapController', () => { platformVersion: '6.0.0' as SemVerVersion, }); - const snapController = await getSnapController( + await hydrateStorageService(); + + const snapController = getSnapController( getSnapControllerOptions({ messenger, state: { @@ -12836,7 +13327,9 @@ describe('SnapController', () => { const manifest = getSnapManifest(); delete manifest.platformVersion; - const snapController = await getSnapController( + await hydrateStorageService(); + + const snapController = getSnapController( getSnapControllerOptions({ messenger, state: { @@ -12864,7 +13357,9 @@ describe('SnapController', () => { rootMessenger.registerActionHandler( 'PermissionController:hasPermission', - () => true, + (_origin, permission) => + permission === SnapEndowments.LifecycleHooks || + permission === handlerEndowments.onActive, ); rootMessenger.registerActionHandler( @@ -12888,7 +13383,9 @@ describe('SnapController', () => { }, }); - const snapController = await getSnapController( + await hydrateStorageService(); + + const snapController = getSnapController( getSnapControllerOptions({ messenger, state: { @@ -12897,6 +13394,8 @@ describe('SnapController', () => { }), ); + await waitForControllerToBeReady(messenger); + messenger.call('SnapController:setClientActive', true); await sleep(10); @@ -12908,26 +13407,26 @@ describe('SnapController', () => { ); expect(messenger.call).toHaveBeenNthCalledWith( - 3, + 5, 'PermissionController:hasPermission', MOCK_SNAP_ID, SnapEndowments.LifecycleHooks, ); expect(messenger.call).toHaveBeenNthCalledWith( - 4, + 6, 'PermissionController:getPermissions', MOCK_SNAP_ID, ); expect(messenger.call).toHaveBeenNthCalledWith( - 5, + 9, 'ExecutionService:executeSnap', expect.any(Object), ); expect(messenger.call).toHaveBeenNthCalledWith( - 6, + 11, 'ExecutionService:handleRpcRequest', MOCK_SNAP_ID, { @@ -12950,7 +13449,9 @@ describe('SnapController', () => { rootMessenger.registerActionHandler( 'PermissionController:hasPermission', - () => true, + (_origin, permission) => + permission === SnapEndowments.LifecycleHooks || + permission === handlerEndowments.onActive, ); rootMessenger.registerActionHandler( @@ -12974,7 +13475,9 @@ describe('SnapController', () => { }, }); - const snapController = await getSnapController( + await hydrateStorageService(); + + const snapController = getSnapController( getSnapControllerOptions({ messenger, state: { @@ -12983,6 +13486,8 @@ describe('SnapController', () => { }), ); + await waitForControllerToBeReady(messenger); + messenger.call('SnapController:setClientActive', false); await sleep(10); @@ -12994,26 +13499,26 @@ describe('SnapController', () => { ); expect(messenger.call).toHaveBeenNthCalledWith( - 3, + 5, 'PermissionController:hasPermission', MOCK_SNAP_ID, SnapEndowments.LifecycleHooks, ); expect(messenger.call).toHaveBeenNthCalledWith( - 4, + 6, 'PermissionController:getPermissions', MOCK_SNAP_ID, ); expect(messenger.call).toHaveBeenNthCalledWith( - 5, + 9, 'ExecutionService:executeSnap', expect.any(Object), ); expect(messenger.call).toHaveBeenNthCalledWith( - 6, + 11, 'ExecutionService:handleRpcRequest', MOCK_SNAP_ID, { @@ -13032,8 +13537,8 @@ describe('SnapController', () => { }); describe('metadata', () => { - it('includes expected state in debug snapshots', async () => { - const controller = await getSnapController(); + it('includes expected state in debug snapshots', () => { + const controller = getSnapController(getSnapControllerOptions()); expect( deriveStateFromMetadata( @@ -13049,8 +13554,8 @@ describe('SnapController', () => { }); describe('includeInStateLogs', () => { - it('includes expected state in state logs', async () => { - const controller = await getSnapController(); + it('includes expected state in state logs', () => { + const controller = getSnapController(); expect( deriveStateFromMetadata( @@ -13072,13 +13577,16 @@ describe('SnapController', () => { path: 'src/foo.json', value: stringToBytes('{ "foo" : "bar" }'), }); - const controller = await getSnapController( + + await hydrateStorageService(); + + const controller = getSnapController( getSnapControllerOptions({ state: { snaps: getPersistedSnapsState( getPersistedSnapObject({ version: '0.0.1', - sourceCode: DEFAULT_SNAP_BUNDLE, + id, status: SnapStatus.Stopped, auxiliaryFiles: [ @@ -13119,14 +13627,6 @@ describe('SnapController', () => { "value": "eyAiZm9vIiA6ICJiYXIiIH0=", }, ], - "sourceCode": " - module.exports.onRpcRequest = ({ request }) => { - console.log("Hello, world!"); - - const { method, id } = request; - return method + id; - }; - ", } `); expect(derivedSnapLargeProperties).toMatchInlineSnapshot(`{}`); @@ -13134,8 +13634,8 @@ describe('SnapController', () => { }); describe('persist', () => { - it('persists expected state', async () => { - const controller = await getSnapController(); + it('persists expected state', () => { + const controller = getSnapController(); expect( deriveStateFromMetadata( @@ -13154,13 +13654,17 @@ describe('SnapController', () => { it('can rehydrate state', async () => { const id = 'npm:foo' as SnapId; - const firstSnapController = await getSnapController( + + await hydrateStorageService({ + [id]: { sourceCode: DEFAULT_SNAP_BUNDLE }, + }); + + const firstSnapController = getSnapController( getSnapControllerOptions({ state: { snaps: getPersistedSnapsState( getPersistedSnapObject({ version: '0.0.1', - sourceCode: DEFAULT_SNAP_BUNDLE, id, status: SnapStatus.Stopped, }), @@ -13177,7 +13681,7 @@ describe('SnapController', () => { ); // create a new controller - const secondSnapController = await getSnapController( + const secondSnapController = getSnapController( getSnapControllerOptions({ state: persistedState as PersistedSnapControllerState, }), @@ -13193,13 +13697,14 @@ describe('SnapController', () => { }); it('does not persist snaps in the installing state', async () => { - const firstSnapController = await getSnapController( + await hydrateStorageService(); + + const firstSnapController = getSnapController( getSnapControllerOptions({ state: { snaps: getPersistedSnapsState( getPersistedSnapObject({ version: '0.0.1', - sourceCode: DEFAULT_SNAP_BUNDLE, status: SnapStatus.Installing, }), ), @@ -13217,7 +13722,7 @@ describe('SnapController', () => { ); // create a new controller - const secondSnapController = await getSnapController( + const secondSnapController = getSnapController( getSnapControllerOptions({ state: persistedState as PersistedSnapControllerState, }), @@ -13229,8 +13734,8 @@ describe('SnapController', () => { }); }); - it('exposes expected state to UI', async () => { - const controller = await getSnapController(); + it('exposes expected state to UI', () => { + const controller = getSnapController(); expect( deriveStateFromMetadata( diff --git a/packages/snaps-controllers/src/snaps/SnapController.ts b/packages/snaps-controllers/src/snaps/SnapController.ts index ed0c1f0deb..507e24873e 100644 --- a/packages/snaps-controllers/src/snaps/SnapController.ts +++ b/packages/snaps-controllers/src/snaps/SnapController.ts @@ -1347,7 +1347,7 @@ export class SnapController extends BaseController< */ init() { this.#setup().catch((error) => { - logError('Error during SnapController initialization.', error); + logWarning('Error during SnapController initialization.', error); }); this.#callLifecycleHooks(METAMASK_ORIGIN, HandlerType.OnStart); @@ -1366,9 +1366,9 @@ export class SnapController extends BaseController< await this.#handlePreinstalledSnaps(this.#preinstalledSnaps); } - await this.#platformIsReady(); - this.#controllerSetup.resolve(); + + await this.#platformIsReady(); } catch (error) { this.#controllerSetup.reject(error); @@ -1764,10 +1764,10 @@ export class SnapController extends BaseController< * Waits for onboarding and then asserts whether the Snaps platform is allowed to run. */ async #ensureCanUsePlatform() { - await this.#platformIsReady(); - // Ensure the controller has finished setting up. await this.#controllerSetup.promise; + + await this.#platformIsReady(); } async #platformIsReady() { diff --git a/packages/snaps-controllers/src/test-utils/controller.tsx b/packages/snaps-controllers/src/test-utils/controller.tsx index 69192534a7..226a47a595 100644 --- a/packages/snaps-controllers/src/test-utils/controller.tsx +++ b/packages/snaps-controllers/src/test-utils/controller.tsx @@ -25,9 +25,10 @@ import { } from '@metamask/snaps-rpc-methods'; import type { SnapId } from '@metamask/snaps-sdk'; import { Text } from '@metamask/snaps-sdk/jsx'; -import type { PersistedSnap } from '@metamask/snaps-utils'; +import type { StorageServiceSnapData } from '@metamask/snaps-utils'; import { SnapCaveatType } from '@metamask/snaps-utils'; import { + DEFAULT_SNAP_BUNDLE, getPersistedSnapObject, getSnapObject, MOCK_LOCAL_SNAP_ID, @@ -329,9 +330,12 @@ export const getControllerMessenger = (registry = new MockSnapsRegistry()) => { SnapControllerEvents | AllowedEvents >(); - messenger.registerActionHandler('PermissionController:hasPermission', () => { - return true; - }); + messenger.registerActionHandler( + 'PermissionController:hasPermission', + (_id, permission) => { + return permission !== SnapEndowments.LifecycleHooks; + }, + ); messenger.registerActionHandler('PermissionController:hasPermissions', () => { return true; @@ -642,76 +646,30 @@ export const getSnapControllerWithEESOptions = ({ }; }; -export const hydrateStorageService = async ( - sourceCodes: Record, -) => { - await Promise.all( - Object.entries(sourceCodes).map(async ([snapId, sourceCode]) => { - await storageAdapter.setItem(controllerName, snapId, { sourceCode }); - }), - ); -}; - -export const extractSourceCodeFromState = ( - state: PersistedSnapControllerState | undefined, -) => { - if (!state) { - return { state: undefined, sourceCodes: undefined }; - } - - const { snaps: snapControllerState, ...stateRest } = state; - - const { snaps, sourceCodes } = Object.entries(snapControllerState).reduce<{ - snaps: Record; - sourceCodes: Record; - }>( - (acc, [snapId, snap]) => { - const { sourceCode, ...rest } = snap; - - acc.snaps[snapId as SnapId] = rest; - acc.sourceCodes[snapId as SnapId] = sourceCode; - - return acc; - }, - { - snaps: {}, - sourceCodes: {}, - }, - ); - - const newState = { - snaps, - ...stateRest, - }; - - return { state: newState, sourceCodes }; -}; - -export const getSnapController = async ( +export const getSnapController = ( options = getSnapControllerOptions(), init = true, ) => { - const { state, ...restOptions } = options; - const { state: snapControllerState, sourceCodes } = - extractSourceCodeFromState(state); - - const controller = new SnapController({ - state: snapControllerState, - ...restOptions, - }); - - if (sourceCodes) { - await hydrateStorageService(sourceCodes); - } - + const controller = new SnapController(options); if (init) { controller.init(); } - return controller; }; -export const getSnapControllerWithEES = async ( +export const hydrateStorageService = async ( + snapsData: Record = { + [MOCK_SNAP_ID]: { sourceCode: DEFAULT_SNAP_BUNDLE }, + }, +) => { + await Promise.all( + Object.entries(snapsData).map(async ([snapId, snapData]) => { + await storageAdapter.setItem(controllerName, snapId, snapData); + }), + ); +}; + +export const getSnapControllerWithEES = ( options = getSnapControllerWithEESOptions(), service?: ReturnType, init = true, @@ -720,10 +678,6 @@ export const getSnapControllerWithEES = async ( // @ts-expect-error: TODO: Investigate type mismatch. service ?? getNodeEES(getNodeEESMessenger(options.rootMessenger)); - if (options.state?.snaps) { - await hydrateStorageService(options.state.snaps); - } - const controller = new SnapController(options); if (init) { @@ -871,7 +825,6 @@ export const getRestrictedSnapInterfaceControllerMessenger = ( 'SnapController:get', 'AccountsController:getSelectedMultichainAccount', 'AccountsController:listMultichainAccounts', - 'PermissionController:hasPermission', ], events: ['NotificationServicesController:notificationsListUpdated'], messenger: snapInterfaceControllerMessenger, @@ -934,13 +887,6 @@ export const getRestrictedSnapInterfaceControllerMessenger = ( messenger.registerActionHandler('SnapController:get', (snapId: string) => { return getSnapObject({ id: snapId as SnapId }); }); - - messenger.registerActionHandler( - 'PermissionController:hasPermission', - () => { - return true; - }, - ); } jest.spyOn(snapInterfaceControllerMessenger, 'call'); @@ -1011,6 +957,24 @@ export async function waitForStateChange( }); } +/** + * Wait for the controller to be ready by listening for the state change event. + * + * @param messenger - The messenger to listen to. + * @returns A promise that resolves when the controller is ready. + */ +export async function waitForControllerToBeReady( + messenger: Messenger<'SnapController', any, SnapControllerStateChangeEvent>, +) { + return new Promise((resolve) => { + messenger.subscribe('SnapController:stateChange', (snapControllerState) => { + if (snapControllerState.isReady) { + resolve(); + } + }); + }); +} + // Mock controller messenger for Multichain Router export const getRootMultichainRouterMessenger = () => { const messenger = new MockControllerMessenger< diff --git a/packages/snaps-utils/src/test-utils/snap.ts b/packages/snaps-utils/src/test-utils/snap.ts index 9976af03dc..9af374c65a 100644 --- a/packages/snaps-utils/src/test-utils/snap.ts +++ b/packages/snaps-utils/src/test-utils/snap.ts @@ -2,11 +2,7 @@ import type { SnapId } from '@metamask/snaps-sdk'; import type { SemVerVersion } from '@metamask/utils'; import type { MakeSemVer } from './common'; -import { - DEFAULT_SNAP_BUNDLE, - DEFAULT_SNAP_SHASUM, - getSnapManifest, -} from './manifest'; +import { DEFAULT_SNAP_SHASUM, getSnapManifest } from './manifest'; import type { PersistedSnap, Snap, TruncatedSnap } from '../snaps'; import { SnapStatus } from '../snaps'; @@ -25,7 +21,6 @@ export const getPersistedSnapObject = ({ initialPermissions = getSnapManifest().initialPermissions, initialConnections = getSnapManifest().initialConnections, manifest = getSnapManifest(), - sourceCode = DEFAULT_SNAP_BUNDLE, status = SnapStatus.Stopped, version = getSnapManifest().version, versionHistory = [ @@ -45,7 +40,7 @@ export const getPersistedSnapObject = ({ manifest, status, enabled, - sourceCode, + versionHistory, auxiliaryFiles, localizationFiles, @@ -150,7 +145,6 @@ export const getMockSnapData = ({ enabled, id, manifest, - sourceCode, }), }; }; From 326039791c47416ed5119b84b9cd1dcd874c407a Mon Sep 17 00:00:00 2001 From: Guillaume Roux Date: Tue, 6 Jan 2026 12:14:40 +0100 Subject: [PATCH 6/6] fix `yarn.lock` --- yarn.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/yarn.lock b/yarn.lock index 5cdb2fcde8..4d92196936 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4210,8 +4210,8 @@ __metadata: "@metamask/snaps-rpc-methods": "workspace:^" "@metamask/snaps-sdk": "workspace:^" "@metamask/snaps-utils": "workspace:^" - "@metamask/superstruct": "npm:^3.2.1" "@metamask/storage-service": "npm:^0.0.1" + "@metamask/superstruct": "npm:^3.2.1" "@metamask/utils": "npm:^11.9.0" "@noble/hashes": "npm:^1.7.1" "@swc/core": "npm:1.11.31"