From 3f87ea5d535adb3fe7ba49ea96fca0ddb0246d98 Mon Sep 17 00:00:00 2001 From: grypez <143971198+grypez@users.noreply.github.com> Date: Mon, 18 May 2026 12:36:28 -0400 Subject: [PATCH 1/2] feat(wallet): expose initializationConfigurations on the Wallet constructor Allows callers to pass an array of InitializationConfiguration objects to override or extend the default controller set. Overridden controllers replace their default counterpart by name; additional controllers are appended to the initialization sequence. Closes #8795 Co-Authored-By: Claude Sonnet 4.6 --- packages/wallet/CHANGELOG.md | 2 ++ packages/wallet/src/Wallet.ts | 12 +++++++++++- .../wallet/src/initialization/initialization.ts | 17 ++++++++++++++++- 3 files changed, 29 insertions(+), 2 deletions(-) diff --git a/packages/wallet/CHANGELOG.md b/packages/wallet/CHANGELOG.md index c26c00fe90..71a27f1284 100644 --- a/packages/wallet/CHANGELOG.md +++ b/packages/wallet/CHANGELOG.md @@ -17,5 +17,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Overrides the `fetch` implementation used by `NetworkController`'s RPC service. Defaults to `globalThis.fetch`. Allows platform-specific fetch implementations (e.g. React Native) to be injected. - Add optional `logger` field to `WalletOptions` ([#XXXX](https://github.com/MetaMask/core/pull/XXXX)) - When provided, emits an `info`-level log after each controller finishes initializing: `[wallet] ${ControllerName}: initialized`. Defaults to no-op (no output). Pass `console` during development to observe initialization order. +- Accept optional `initializationConfigurations` in the `Wallet` constructor ([#XXXX](https://github.com/MetaMask/core/pull/XXXX)) + - Allows callers to override or extend the default set of controller configurations. [Unreleased]: https://github.com/MetaMask/core/ diff --git a/packages/wallet/src/Wallet.ts b/packages/wallet/src/Wallet.ts index da3b2d7d3d..b5e6f39786 100644 --- a/packages/wallet/src/Wallet.ts +++ b/packages/wallet/src/Wallet.ts @@ -10,9 +10,12 @@ import type { RootMessenger, } from './initialization'; import { initialize } from './initialization'; +import type { InitializationConfiguration } from './initialization/types'; import type { WalletOptions } from './types'; export class Wallet { + // messenger is typed against the default controller set. Action/event types for + // any additional configurations passed to the constructor are not reflected here. public readonly messenger: RootMessenger; readonly #instances: DefaultInstances; @@ -23,7 +26,13 @@ export class Wallet { #destroyed = false; - constructor({ state, ...options }: WalletOptions) { + constructor({ + state, + initializationConfigurations, + ...options + }: WalletOptions & { + initializationConfigurations?: InitializationConfiguration[]; + }) { this.messenger = new Messenger({ namespace: 'Wallet', }); @@ -31,6 +40,7 @@ export class Wallet { this.#instances = initialize({ state: state ?? {}, messenger: this.messenger, + initializationConfigurations, options, }); diff --git a/packages/wallet/src/initialization/initialization.ts b/packages/wallet/src/initialization/initialization.ts index 49b4e7b37c..073bd88b46 100644 --- a/packages/wallet/src/initialization/initialization.ts +++ b/packages/wallet/src/initialization/initialization.ts @@ -8,17 +8,32 @@ import type { InitializationConfiguration } from './types'; export type InitializeArgs = { state: Record; messenger: RootMessenger; + initializationConfigurations?: InitializationConfiguration< + unknown, + unknown + >[]; options: WalletOptions; }; export function initialize({ state, messenger, + initializationConfigurations = [], options, }: InitializeArgs): DefaultInstances { + const overriddenConfiguration = initializationConfigurations.map( + (config) => config.name, + ); + + const configurationEntries = initializationConfigurations.concat( + Object.values(defaultConfigurations).filter( + (config) => !overriddenConfiguration.includes(config.name), + ), + ); + const instances: Record = {}; - for (const config of Object.values(defaultConfigurations) as InitializationConfiguration[]) { + for (const config of configurationEntries) { const { name } = config; const instanceState = state[name]; From 8918c10341d77ac122b72357431ab6d0cb87e71b Mon Sep 17 00:00:00 2001 From: grypez <143971198+grypez@users.noreply.github.com> Date: Thu, 21 May 2026 09:45:47 -0400 Subject: [PATCH 2/2] test(wallet): cover initializationConfigurations override path Exercises the map callback in initialize() that extracts overridden names, which was previously uncovered because no test passed a non-empty initializationConfigurations array. Co-Authored-By: Claude Sonnet 4.6 --- packages/wallet/src/Wallet.test.ts | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/packages/wallet/src/Wallet.test.ts b/packages/wallet/src/Wallet.test.ts index d5a8b2201a..b5af3b1602 100644 --- a/packages/wallet/src/Wallet.test.ts +++ b/packages/wallet/src/Wallet.test.ts @@ -210,6 +210,27 @@ describe('Wallet', () => { expect(Object.keys(wallet.state)).toStrictEqual(['WithMeta', 'NoMeta']); }); + it('initializes additional controllers passed via initializationConfigurations', () => { + const customInit = jest + .fn() + .mockReturnValue({ instance: {} as never }); + const customMessenger = jest.fn().mockReturnValue({} as never); + + wallet = new Wallet({ + ...options, + initializationConfigurations: [ + { + name: 'CustomController', + init: customInit, + messenger: customMessenger, + }, + ], + }); + + expect(customInit).toHaveBeenCalledTimes(1); + expect(customMessenger).toHaveBeenCalledTimes(1); + }); + it('publishes Wallet:destroyed exactly once on destroy', async () => { wallet = new Wallet(options);