@@ -6,12 +6,15 @@ import type {
66 SnapKeyring ,
77 SnapKeyringState ,
88} from '@metamask/eth-snap-keyring/v2' ;
9+ import type { SnapMessage } from '@metamask/eth-snap-keyring' ;
10+ import { KeyringEvent } from '@metamask/keyring-api' ;
911import type { Keyring } from '@metamask/keyring-api/v2' ;
1012import { KeyringType } from '@metamask/keyring-api/v2' ;
1113import type {
1214 KeyringControllerGetStateAction ,
1315 KeyringControllerStateChangeEvent ,
1416 KeyringControllerWithControllerAction ,
17+ KeyringControllerWithKeyringV2UnsafeAction ,
1518} from '@metamask/keyring-controller' ;
1619import type { Messenger } from '@metamask/messenger' ;
1720import type {
@@ -24,13 +27,15 @@ import type {
2427 SnapControllerSnapUninstalledEvent ,
2528 SnapControllerStateChangeEvent ,
2629} from '@metamask/snaps-controllers' ;
30+ import type { Json } from '@metamask/snaps-sdk' ;
2731import { SnapId } from '@metamask/snaps-sdk' ;
2832import type { TruncatedSnap } from '@metamask/snaps-utils' ;
2933
3034import { projectLogger as log } from './logger' ;
3135import type {
3236 SnapAccountServiceEnsureReadyAction ,
3337 SnapAccountServiceGetSnapsAction ,
38+ SnapAccountServiceHandleKeyringSnapMessageAction ,
3439} from './SnapAccountService-method-action-types' ;
3540import { SnapPlatformWatcher } from './SnapPlatformWatcher' ;
3641import type { SnapPlatformWatcherConfig } from './SnapPlatformWatcher' ;
@@ -67,14 +72,19 @@ export const serviceName = 'SnapAccountService';
6772 * All of the methods within {@link SnapAccountService} that are exposed via
6873 * the messenger.
6974 */
70- const MESSENGER_EXPOSED_METHODS = [ 'ensureReady' , 'getSnaps' ] as const ;
75+ const MESSENGER_EXPOSED_METHODS = [
76+ 'ensureReady' ,
77+ 'getSnaps' ,
78+ 'handleKeyringSnapMessage' ,
79+ ] as const ;
7180
7281/**
7382 * Actions that {@link SnapAccountService} exposes to other consumers.
7483 */
7584export type SnapAccountServiceActions =
7685 | SnapAccountServiceEnsureReadyAction
77- | SnapAccountServiceGetSnapsAction ;
86+ | SnapAccountServiceGetSnapsAction
87+ | SnapAccountServiceHandleKeyringSnapMessageAction ;
7888
7989/**
8090 * Actions from other messengers that {@link SnapAccountService} calls.
@@ -83,7 +93,8 @@ type AllowedActions =
8393 | SnapControllerGetStateAction
8494 | SnapControllerGetRunnableSnapsAction
8595 | KeyringControllerGetStateAction
86- | KeyringControllerWithControllerAction ;
96+ | KeyringControllerWithControllerAction
97+ | KeyringControllerWithKeyringV2UnsafeAction ;
8798
8899/**
89100 * Events that {@link SnapAccountService} exposes to other consumers.
@@ -352,6 +363,89 @@ export class SnapAccountService {
352363 await this . #watcher. ensureCanUseSnapPlatform ( ) ;
353364 }
354365
366+ /**
367+ * Handle a message from a Snap.
368+ *
369+ * Only `AccountCreated` triggers lazy keyring creation, since that is the
370+ * single entry point for the v1 event-driven flow. All other events from
371+ * unknown Snaps throw an error.
372+ *
373+ * @param snapId - ID of the Snap.
374+ * @param message - Message sent by the Snap.
375+ * @returns The execution result.
376+ */
377+ async handleKeyringSnapMessage (
378+ snapId : SnapId ,
379+ message : SnapMessage ,
380+ ) : Promise < Json > {
381+ // We assume the Snap platform always sends a valid `KeyringEvent` here.
382+ const event = message . method as KeyringEvent ;
383+
384+ if ( message . method === 'getSelectedAccounts' ) {
385+ return [ ] ;
386+ }
387+
388+ const isSnapKeyringForThisSnap = (
389+ keyring : Keyring ,
390+ ) : keyring is SnapKeyring =>
391+ isSnapKeyring ( keyring ) && keyring . snapId === snapId ;
392+
393+ // We can create a new keyring if the message is an AccountCreated event.
394+ const isAccountCreatedMessage = event === KeyringEvent . AccountCreated ;
395+
396+ // Create the Snap keyring if it doesn't exist yet (in an atomic way). We
397+ // cannot assume the keyring exists (e.g for the MMI Snap).
398+ // NOTE: We only auto-create it for v1 account creation flows.
399+ if ( isAccountCreatedMessage ) {
400+ await this . #messenger. call (
401+ 'KeyringController:withController' ,
402+ async ( controller ) => {
403+ const hasSnapKeyring = controller . keyrings . some (
404+ ( { keyringV2 } ) => keyringV2 && isSnapKeyringForThisSnap ( keyringV2 ) ,
405+ ) ;
406+
407+ if ( ! hasSnapKeyring ) {
408+ log ( `Auto-creating Snap keyring for "${ snapId } "...` ) ;
409+
410+ await controller . addNewKeyring ( KeyringType . Snap , {
411+ snapId,
412+ accounts : { } ,
413+ } ) ;
414+
415+ log ( `Snap keyring for "${ snapId } " is ready!` ) ;
416+ }
417+ } ,
418+ ) ;
419+ } else {
420+ log (
421+ `No Snap keyring found for snap "${ snapId } ". Cannot handle message with method "${ event } ".` ,
422+ ) ;
423+
424+ throw new Error (
425+ 'Cannot delegate keyring Snap message, keyring does not exist yet.' ,
426+ ) ;
427+ }
428+
429+ // This part of the flow relies on v1 flows, so we have to go with
430+ // "unsafe" to avoid deadlocks. The keyring persistence will be done in a
431+ // later stage of `handleKeyringSnapMessage` after the message is handled,
432+ // so we don't have to worry about that here.
433+ const result = await this . #messenger. call (
434+ 'KeyringController:withKeyringV2Unsafe' ,
435+ { filter : ( keyring ) => isSnapKeyringForThisSnap ( keyring ) } ,
436+ async ( { keyring } ) => {
437+ const snapKeyring = keyring as SnapKeyring ;
438+ return snapKeyring . handleKeyringSnapMessage ( message ) ;
439+ } ,
440+ ) ;
441+
442+ log (
443+ `Snap keyring for "${ snapId } " handled keyring message "${ event } " successfully!` ,
444+ ) ;
445+
446+ return result as Json ;
447+ }
448+
355449 /**
356450 * Handles a Snap being added (installed or enabled). If the Snap is an
357451 * account-management Snap, adds it to the internal set of tracked Snaps.
0 commit comments