Skip to content
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
1 change: 1 addition & 0 deletions .github/CODEOWNERS
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,7 @@
/packages/wallet/src/initialization/instances/approval-controller/ @MetaMask/confirmations
/packages/wallet/src/initialization/instances/connectivity-controller/ @MetaMask/core-platform
/packages/wallet/src/initialization/instances/keyring-controller/ @MetaMask/accounts-engineers @MetaMask/core-platform
/packages/wallet/src/initialization/instances/preferences-controller/ @MetaMask/core-platform
/packages/wallet/src/initialization/instances/remote-feature-flag-controller/ @MetaMask/extension-platform @MetaMask/mobile-platform @MetaMask/core-platform
/packages/wallet/src/initialization/instances/storage-service/ @MetaMask/extension-platform @MetaMask/mobile-platform @MetaMask/core-platform

Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -614,6 +614,7 @@ linkStyle default opacity:0.5
wallet --> controller_utils;
wallet --> keyring_controller;
wallet --> messenger;
wallet --> preferences_controller;
wallet --> remote_feature_flag_controller;
wallet --> storage_service;
wallet_cli --> base_controller;
Expand Down
6 changes: 6 additions & 0 deletions packages/wallet/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

### Added

- **BREAKING:** Wire `PreferencesController` into the default wallet initialization ([#9232](https://github.com/MetaMask/core/pull/9232))
- The default `Wallet` now constructs a `PreferencesController` and registers its `PreferencesController:*` messenger actions. Consumers that pass their own `messenger` and already wire a `PreferencesController` must remove their own before upgrading, or the duplicate registration will collide.
- Export the `InitializationConfiguration`, `InitFunctionArguments`, and `InstanceState` types so consumers can author initialization configurations that override a default controller ([#9232](https://github.com/MetaMask/core/pull/9232))

### Changed

- Bump `@metamask/accounts-controller` from `^39.0.2` to `^39.0.3` ([#9231](https://github.com/MetaMask/core/pull/9231))
Expand Down
1 change: 1 addition & 0 deletions packages/wallet/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@
"@metamask/controller-utils": "^12.3.0",
"@metamask/keyring-controller": "^27.1.0",
"@metamask/messenger": "^1.2.0",
"@metamask/preferences-controller": "^23.1.0",
"@metamask/remote-feature-flag-controller": "^4.2.2",
"@metamask/scure-bip39": "^2.1.1",
"@metamask/storage-service": "^1.0.2",
Expand Down
93 changes: 93 additions & 0 deletions packages/wallet/src/Wallet.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { CONNECTIVITY_STATUSES } from '@metamask/connectivity-controller';
import { Messenger } from '@metamask/messenger';
import { getDefaultPreferencesState } from '@metamask/preferences-controller';
import { InMemoryStorageAdapter } from '@metamask/storage-service';
import { Json } from '@metamask/utils';
import { webcrypto } from 'crypto';
Expand Down Expand Up @@ -321,6 +322,98 @@ describe('Wallet', () => {
});
});

describe('PreferencesController', () => {
it('is wired and exposes its state on the wallet messenger', async () => {
const wallet = await setupWallet();
const { messenger } = wallet;

expect(messenger.call('PreferencesController:getState')).toStrictEqual(
getDefaultPreferencesState(),
);
});

it('applies initial state passed through the Wallet constructor', () => {
const wallet = new Wallet({
state: {
PreferencesController: { privacyMode: true },
},
instanceOptions: {
connectivityController: {
connectivityAdapter: new AlwaysOnlineAdapter(),
},
storageService: {
storage: new InMemoryStorageAdapter(),
},
remoteFeatureFlagController: REMOTE_FEATURE_FLAG_OPTIONS,
},
});

expect(wallet.state.PreferencesController.privacyMode).toBe(true);
});

it('routes its method actions through the wallet messenger', async () => {
const wallet = await setupWallet();
const { messenger } = wallet;

messenger.call(
'PreferencesController:setIpfsGateway',
'https://example.com/ipfs/',
);

expect(wallet.state.PreferencesController.ipfsGateway).toBe(
'https://example.com/ipfs/',
);
});

it('lets a consumer override the default with a diverging superset controller', () => {
// A client (e.g. the extension) whose local PreferencesController is a
// superset of the package one can keep it by supplying its own
// `PreferencesController` initialization configuration. The same `name`
// overrides the package default, so the wallet constructs the superset
// instead of the package controller — no convergence required.
class SupersetPreferencesController {
state = {
ipfsGateway: 'https://superset.example/ipfs/',
currentLocale: 'en',
preferences: { showTestNetworks: true },
};
}

const wallet = new Wallet({
initializationConfigurations: [
{
name: 'PreferencesController',
getMessenger: (): Messenger<string> =>
new Messenger({ namespace: 'PreferencesController' }),
init: (): SupersetPreferencesController =>
new SupersetPreferencesController(),
},
],
instanceOptions: {
connectivityController: {
connectivityAdapter: new AlwaysOnlineAdapter(),
},
storageService: {
storage: new InMemoryStorageAdapter(),
},
remoteFeatureFlagController: REMOTE_FEATURE_FLAG_OPTIONS,
},
});

expect(wallet.getInstance('PreferencesController')).toBeInstanceOf(
SupersetPreferencesController,
);
// The superset's shape — including fields absent from the package
// controller (`currentLocale`, nested `preferences`) — is what the wallet
// exposes, not the package defaults.
expect(wallet.state.PreferencesController).toStrictEqual({
ipfsGateway: 'https://superset.example/ipfs/',
currentLocale: 'en',
preferences: { showTestNetworks: true },
});
});
});

describe('StorageService', () => {
it('can set and get items', async () => {
const wallet = await setupWallet();
Expand Down
5 changes: 5 additions & 0 deletions packages/wallet/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,8 @@ export type {
DefaultState,
RootMessenger,
} from './initialization/defaults';
export type {
InitFunctionArguments,
InitializationConfiguration,
InstanceState,
} from './initialization/types';
1 change: 1 addition & 0 deletions packages/wallet/src/initialization/instances/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,6 @@ export { accountsController } from './accounts-controller/accounts-controller';
export { approvalController } from './approval-controller/approval-controller';
export { connectivityController } from './connectivity-controller/connectivity-controller';
export { keyringController } from './keyring-controller/keyring-controller';
export { preferencesController } from './preferences-controller/preferences-controller';
export { remoteFeatureFlagController } from './remote-feature-flag-controller/remote-feature-flag-controller';
export { storageService } from './storage-service/storage-service';
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
import { Messenger } from '@metamask/messenger';
import {
PreferencesController,
getDefaultPreferencesState,
} from '@metamask/preferences-controller';

import { defaultConfigurations } from '../../defaults';
import type {
DefaultActions,
DefaultEvents,
RootMessenger,
} from '../../defaults';
import { preferencesController } from './preferences-controller';

/**
* Creates a root messenger for use in tests.
*
* @returns A root messenger.
*/
function getRootMessenger(): RootMessenger<DefaultActions, DefaultEvents> {
return new Messenger({ namespace: 'Root' });
}

describe('preferencesController', () => {
it('is registered as a default initialization configuration', () => {
expect(Object.values(defaultConfigurations)).toContain(
preferencesController,
);
});

it('initializes a PreferencesController with default state', () => {
const messenger = preferencesController.getMessenger(getRootMessenger());

const instance = preferencesController.init({
state: undefined,
messenger,
options: {},
});

expect(instance).toBeInstanceOf(PreferencesController);
expect(instance.state).toStrictEqual(getDefaultPreferencesState());
});

it('merges provided state over the defaults', () => {
const messenger = preferencesController.getMessenger(getRootMessenger());

const instance = preferencesController.init({
state: { ipfsGateway: 'https://example.com/ipfs/', privacyMode: true },
messenger,
options: {},
});

expect(instance.state.ipfsGateway).toBe('https://example.com/ipfs/');
expect(instance.state.privacyMode).toBe(true);
expect(instance.state.useTokenDetection).toBe(
getDefaultPreferencesState().useTokenDetection,
);
});

it('exposes its state through the root messenger', () => {
const rootMessenger = getRootMessenger();
const messenger = preferencesController.getMessenger(rootMessenger);

preferencesController.init({ state: undefined, messenger, options: {} });

expect(rootMessenger.call('PreferencesController:getState')).toStrictEqual(
getDefaultPreferencesState(),
);
});

it('registers its method actions on the root messenger', () => {
const rootMessenger = getRootMessenger();
const messenger = preferencesController.getMessenger(rootMessenger);

const instance = preferencesController.init({
state: undefined,
messenger,
options: {},
});

rootMessenger.call(
'PreferencesController:setIpfsGateway',
'https://x/ipfs/',
);

expect(instance.state.ipfsGateway).toBe('https://x/ipfs/');
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { Messenger } from '@metamask/messenger';
import {
PreferencesController,
PreferencesControllerMessenger,
} from '@metamask/preferences-controller';

import type { InitializationConfiguration } from '../../types';

export const preferencesController: InitializationConfiguration<
PreferencesController,
PreferencesControllerMessenger
> = {
name: 'PreferencesController',
init: ({ state, messenger }) =>
new PreferencesController({
state,
messenger,
}),
getMessenger: (parent) =>
new Messenger({
namespace: 'PreferencesController',
parent,
}),
};
1 change: 1 addition & 0 deletions packages/wallet/tsconfig.build.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
{ "path": "../controller-utils/tsconfig.build.json" },
{ "path": "../keyring-controller/tsconfig.build.json" },
{ "path": "../messenger/tsconfig.build.json" },
{ "path": "../preferences-controller/tsconfig.build.json" },
{ "path": "../remote-feature-flag-controller/tsconfig.build.json" },
{ "path": "../storage-service/tsconfig.build.json" }
],
Expand Down
1 change: 1 addition & 0 deletions packages/wallet/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
{ "path": "../controller-utils/tsconfig.json" },
{ "path": "../keyring-controller/tsconfig.json" },
{ "path": "../messenger/tsconfig.json" },
{ "path": "../preferences-controller/tsconfig.json" },
{ "path": "../remote-feature-flag-controller/tsconfig.json" },
{ "path": "../storage-service/tsconfig.json" }
],
Expand Down
1 change: 1 addition & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -8938,6 +8938,7 @@ __metadata:
"@metamask/controller-utils": "npm:^12.3.0"
"@metamask/keyring-controller": "npm:^27.1.0"
"@metamask/messenger": "npm:^1.2.0"
"@metamask/preferences-controller": "npm:^23.1.0"
"@metamask/remote-feature-flag-controller": "npm:^4.2.2"
"@metamask/scure-bip39": "npm:^2.1.1"
"@metamask/storage-service": "npm:^1.0.2"
Expand Down
Loading