Skip to content

refactor: Add MultichainRouter to new controller instantiation pattern #31951

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 9 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions app/scripts/controller-init/controller-list.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import {
CronjobController,
ExecutionService,
JsonSnapsRegistry,
MultichainRouter,
SnapController,
SnapInsightsController,
SnapInterfaceController,
Expand Down Expand Up @@ -58,6 +59,7 @@ export type Controller =
| MultichainBalancesController
| MultichainTransactionsController
| MultichainNetworkController
| MultichainRouter
| NetworkController
| NotificationServicesController
| NotificationServicesPushController
Expand Down
6 changes: 6 additions & 0 deletions app/scripts/controller-init/messengers/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import {
getMultichainAssetsControllerMessenger,
getMultichainNetworkControllerMessenger,
getMultichainAssetsRatesControllerMessenger,
getMultichainRouterMessenger,
} from './multichain';
import { getInstitutionalSnapControllerMessenger } from './accounts/institutional-snap-controller-messenger';
import {
Expand All @@ -40,6 +41,7 @@ import {
getNotificationServicesControllerMessenger,
getNotificationServicesPushControllerMessenger,
} from './notifications';
import { getMultichainRouterInitMessenger } from './multichain/multichain-router-messenger';

export const CONTROLLER_MESSENGERS = {
AuthenticationController: {
Expand Down Expand Up @@ -78,6 +80,10 @@ export const CONTROLLER_MESSENGERS = {
getMessenger: getMultichainNetworkControllerMessenger,
getInitMessenger: noop,
},
MultichainRouter: {
getMessenger: getMultichainRouterMessenger,
getInitMessenger: getMultichainRouterInitMessenger,
},
NotificationServicesController: {
getMessenger: getNotificationServicesControllerMessenger,
getInitMessenger: noop,
Expand Down
2 changes: 2 additions & 0 deletions app/scripts/controller-init/messengers/multichain/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,11 @@ export { getMultichainAssetsRatesControllerMessenger } from './multichain-assets
export { getMultichainBalancesControllerMessenger } from './multichain-balances-controller-messenger';
export { getMultichainTransactionsControllerMessenger } from './multichain-transactions-controller-messenger';
export { getMultichainNetworkControllerMessenger } from './multichain-network-controller-messenger';
export { getMultichainRouterMessenger } from './multichain-router-messenger';

export type { MultichainAssetsControllerMessenger } from './multichain-assets-controller-messenger';
export type { MultichainAssetsRatesControllerMessenger } from './multichain-assets-rates-controller-messenger';
export type { MultichainBalancesControllerMessenger } from './multichain-balances-controller-messenger';
export type { MultichainTransactionsControllerMessenger } from './multichain-transactions-controller-messenger';
export type { MultichainNetworkControllerMessenger } from './multichain-network-controller-messenger';
export type { MultichainRouterMessenger } from './multichain-router-messenger';
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { Messenger, RestrictedMessenger } from '@metamask/base-controller';
import { getMultichainRouterMessenger } from './multichain-router-messenger';

describe('getMultichainRouterMessenger', () => {
it('returns a restricted messenger', () => {
const messenger = new Messenger<never, never>();
const multichainRouterMessenger = getMultichainRouterMessenger(messenger);

expect(multichainRouterMessenger).toBeInstanceOf(RestrictedMessenger);
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import { Messenger } from '@metamask/base-controller';
import {
AccountsControllerListMultichainAccountsAction,
GetAllSnaps,
HandleSnapRequest,
} from '@metamask/snaps-controllers';
import { GetPermissions } from '@metamask/permission-controller';
import { KeyringControllerWithKeyringAction } from '@metamask/keyring-controller';

type Actions =
| GetAllSnaps
| HandleSnapRequest
| GetPermissions
| AccountsControllerListMultichainAccountsAction;

type Events = never;

export type MultichainRouterMessenger = ReturnType<
typeof getMultichainRouterMessenger
>;

/**
* Get a restricted messenger for the Multichain Router. This is scoped to the
* actions and events that the Multichain Router is allowed to handle.
*
* @param messenger - The controller messenger to restrict.
* @returns The restricted controller messenger.
*/
export function getMultichainRouterMessenger(
messenger: Messenger<Actions, Events>,
) {
return messenger.getRestricted({
name: 'MultichainRouter',
allowedActions: [
`SnapController:getAll`,
`SnapController:handleRequest`,
`PermissionController:getPermissions`,
`AccountsController:listMultichainAccounts`,
],
allowedEvents: [],
});
}

type InitActions = KeyringControllerWithKeyringAction;

export type MultichainRouterInitMessenger = ReturnType<
typeof getMultichainRouterInitMessenger
>;

/**
* Get a restricted controller messenger for Multichain Router initialization. This is
* scoped to the actions and events that the Multichain Router needs for instantiation.
*
* @param messenger - The messenger to restrict.
* @returns The restricted controller messenger.
*/
export function getMultichainRouterInitMessenger(
messenger: Messenger<InitActions, never>,
) {
return messenger.getRestricted({
name: 'MultichainRouter',
allowedActions: ['KeyringController:withKeyring'],
allowedEvents: [],
});
}
1 change: 1 addition & 0 deletions app/scripts/controller-init/multichain/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@ export { MultichainBalancesControllerInit } from './multichain-balances-controll
export { MultichainTransactionsControllerInit } from './multichain-transactions-controller-init';
export { MultichainNetworkControllerInit } from './multichain-network-controller-init';
export { MultichainAssetsRatesControllerInit } from './multichain-rates-assets-controller-init';
export { MultichainRouterInit } from './multichain-router-init';
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import { Messenger } from '@metamask/base-controller';
import { MultichainRouter } from '@metamask/snaps-controllers';
import { buildControllerInitRequestMock } from '../test/utils';
import { ControllerInitRequest } from '../types';
import {
getMultichainRouterMessenger,
MultichainRouterMessenger,
} from '../messengers/multichain';
import {
getMultichainRouterInitMessenger,
MultichainRouterInitMessenger,
} from '../messengers/multichain/multichain-router-messenger';
import { MultichainRouterInit } from './multichain-router-init';

jest.mock('@metamask/snaps-controllers');

function buildInitRequestMock(): jest.Mocked<
ControllerInitRequest<
MultichainRouterMessenger,
MultichainRouterInitMessenger
>
> {
const baseControllerMessenger = new Messenger();

return {
...buildControllerInitRequestMock(),
controllerMessenger: getMultichainRouterMessenger(baseControllerMessenger),
initMessenger: getMultichainRouterInitMessenger(baseControllerMessenger),
};
}

describe('MultichainRouterInit', () => {
const multichainRouterClassMock = jest.mocked(MultichainRouter);

beforeEach(() => {
jest.resetAllMocks();
});

it('returns controller instance', () => {
const requestMock = buildInitRequestMock();
expect(MultichainRouterInit(requestMock).controller).toBeInstanceOf(
MultichainRouter,
);
});

it('initializes with correct messenger and state', () => {
const requestMock = buildInitRequestMock();
MultichainRouterInit(requestMock);

expect(multichainRouterClassMock).toHaveBeenCalledWith({
messenger: requestMock.controllerMessenger,
withSnapKeyring: expect.any(Function),
});
});
});
41 changes: 41 additions & 0 deletions app/scripts/controller-init/multichain/multichain-router-init.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { MultichainRouter } from '@metamask/snaps-controllers';
import { ControllerInitFunction } from '../types';
import { MultichainRouterMessenger } from '../messengers/multichain';
import { MultichainRouterInitMessenger } from '../messengers/multichain/multichain-router-messenger';

/**
* Initialize the Multichain Network controller.
*
* @param request - The request object.
* @param request.controllerMessenger - The messenger to use for the controller.
* @param request.initMessenger - The init messenger. This has access to
* different functions than the controller messenger, and should be used for
* initialization purposes only.
* @returns The initialized controller.
*/
export const MultichainRouterInit: ControllerInitFunction<
MultichainRouter,
MultichainRouterMessenger,
MultichainRouterInitMessenger
> = ({ controllerMessenger, initMessenger }) => {
const controller = new MultichainRouter({
messenger: controllerMessenger,
// Binding the call to provide the selector only giving the controller the option to pass the operation
withSnapKeyring: (...args) =>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We will need to rethink this as we had to make a change to this function to unblock the release.

// @ts-expect-error mistmatch with the withSnapKeyring signature and withKeyring.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there an issue tracking this mismatch we can link?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i don't think we need an issue tracking this specifically. Frederik is aware that this hook needs to be called from the messenger inside the MultichainRouter implementation which will allow us to remove it here and resolves the type issue

initMessenger.call(
'KeyringController:withKeyring',
{
type: 'Snap Keyring',
},
// @ts-expect-error mistmatch with the withSnapKeyring signature and withKeyring.
...args,
),
});

return {
controller,
persistedStateKey: null,
memStateKey: null,
};
};
26 changes: 3 additions & 23 deletions app/scripts/metamask-controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,6 @@ import {
import { LoggingController, LogType } from '@metamask/logging-controller';
import { PermissionLogController } from '@metamask/permission-log-controller';

import { MultichainRouter } from '@metamask/snaps-controllers';
import {
createSnapsMethodMiddleware,
buildSnapEndowmentSpecifications,
Expand Down Expand Up @@ -380,6 +379,7 @@ import {
MultichainAssetsRatesControllerInit,
///: END:ONLY_INCLUDE_IF
MultichainNetworkControllerInit,
MultichainRouterInit,
} from './controller-init/multichain';
import {
AssetsContractControllerInit,
Expand Down Expand Up @@ -1175,28 +1175,6 @@ export default class MetamaskController extends EventEmitter {
subjectCacheLimit: 100,
});

const multichainRouterMessenger = this.controllerMessenger.getRestricted({
name: 'MultichainRouter',
allowedActions: [
`SnapController:getAll`,
`SnapController:handleRequest`,
`${this.permissionController.name}:getPermissions`,
`AccountsController:listMultichainAccounts`,
],
allowedEvents: [],
});

this.multichainRouter = new MultichainRouter({
messenger: multichainRouterMessenger,
// Binding the call to provide the selector only giving the controller the option to pass the operation
withSnapKeyring: this.keyringController.withKeyring.bind(
this.keyringController,
{
type: 'Snap Keyring',
},
),
});

// account tracker watches balances, nonces, and any code at their address
this.accountTrackerController = new AccountTrackerController({
state: { accounts: {} },
Expand Down Expand Up @@ -1807,6 +1785,7 @@ export default class MetamaskController extends EventEmitter {
MultichainTransactionsController: MultichainTransactionsControllerInit,
///: END:ONLY_INCLUDE_IF
MultichainNetworkController: MultichainNetworkControllerInit,
MultichainRouter: MultichainRouterInit,
AuthenticationController: AuthenticationControllerInit,
UserStorageController: UserStorageControllerInit,
NotificationServicesController: NotificationServicesControllerInit,
Expand Down Expand Up @@ -1855,6 +1834,7 @@ export default class MetamaskController extends EventEmitter {
this.tokenRatesController = controllersByName.TokenRatesController;
this.multichainNetworkController =
controllersByName.MultichainNetworkController;
this.multichainRouter = controllersByName.multichainRouter;
this.authenticationController = controllersByName.AuthenticationController;
this.userStorageController = controllersByName.UserStorageController;
this.notificationServicesController =
Expand Down
Loading