Skip to content
Draft
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 @@ -146,6 +146,7 @@
/packages/wallet/src/initialization/instances/address-book-controller/ @MetaMask/confirmations
/packages/wallet/src/initialization/instances/approval-controller/ @MetaMask/confirmations
/packages/wallet/src/initialization/instances/connectivity-controller/ @MetaMask/core-platform
/packages/wallet/src/initialization/instances/gas-fee-controller/ @MetaMask/confirmations
/packages/wallet/src/initialization/instances/keyring-controller/ @MetaMask/accounts-engineers @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 @@ -645,6 +645,7 @@ linkStyle default opacity:0.5
wallet --> base_controller;
wallet --> connectivity_controller;
wallet --> controller_utils;
wallet --> gas_fee_controller;
wallet --> keyring_controller;
wallet --> messenger;
wallet --> network_controller;
Expand Down
4 changes: 4 additions & 0 deletions packages/wallet/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

### Added

- **BREAKING:** Wire `GasFeeController` into the default wallet initialization ([#9527](https://github.com/MetaMask/core/pull/9527))

## [7.0.1]

### Changed
Expand Down
1 change: 1 addition & 0 deletions packages/wallet/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@
"@metamask/browser-passworder": "^6.0.0",
"@metamask/connectivity-controller": "^0.3.0",
"@metamask/controller-utils": "^12.3.0",
"@metamask/gas-fee-controller": "^26.2.4",
"@metamask/keyring-controller": "^27.1.0",
"@metamask/messenger": "^2.0.0",
"@metamask/network-controller": "^34.0.0",
Expand Down
74 changes: 74 additions & 0 deletions packages/wallet/src/initialization/initialization.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import { orderByDependencies } from './initialization';
import type { InitializationConfiguration } from './types';

/**
* Builds a minimal initialization configuration for ordering tests. Only `name`
* and `dependencies` are relevant here.
*
* @param name - The instance name.
* @param dependencies - Names of instances that must be constructed first.
* @returns A stub initialization configuration.
*/
function config(
name: string,
dependencies?: string[],
): InitializationConfiguration<unknown, unknown> {
return {
name,
dependencies,
init: jest.fn(),
getMessenger: jest.fn(),
} as unknown as InitializationConfiguration<unknown, unknown>;
}

/**
* Extracts the names of ordered configurations.
*
* @param configurations - The configurations to name.
* @returns The names in order.
*/
function names(
configurations: InitializationConfiguration<unknown, unknown>[],
): string[] {
return configurations.map((configuration) => configuration.name);
}

describe('orderByDependencies', () => {
it('preserves the order of configurations without dependencies', () => {
const configurations = [config('A'), config('B'), config('C')];

expect(names(orderByDependencies(configurations))).toStrictEqual([
'A',
'B',
'C',
]);
});

it('constructs a dependency before the configuration that depends on it', () => {
const configurations = [config('A', ['B']), config('B'), config('C')];

expect(names(orderByDependencies(configurations))).toStrictEqual([
'B',
'A',
'C',
]);
});

it('treats dependencies outside the set as already satisfied', () => {
const configurations = [config('A', ['External']), config('B')];

expect(names(orderByDependencies(configurations))).toStrictEqual([
'A',
'B',
]);
});

it('falls back to the remaining order when a dependency cycle exists', () => {
const configurations = [config('A', ['B']), config('B', ['A'])];

expect(names(orderByDependencies(configurations))).toStrictEqual([
'A',
'B',
]);
});
});
51 changes: 47 additions & 4 deletions packages/wallet/src/initialization/initialization.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,12 @@ export function initialize(options: InitializeOptions): DefaultInstances {
(config) => config.name,
);

const configurationEntries = initializationConfigurations.concat(
Object.values(defaultConfigurations).filter(
(config) => !overriddenConfiguration.includes(config.name),
) as InitializationConfiguration<unknown, unknown>[],
const configurationEntries = orderByDependencies(
initializationConfigurations.concat(
Object.values(defaultConfigurations).filter(
(config) => !overriddenConfiguration.includes(config.name),
) as InitializationConfiguration<unknown, unknown>[],
),
);

const instances: Record<string, unknown> = {};
Expand All @@ -59,3 +61,44 @@ export function initialize(options: InitializeOptions): DefaultInstances {

return instances as DefaultInstances;
}

/**
* Orders initialization configurations so each one is constructed after its
* declared dependencies. The relative order of configurations without an unmet
* dependency is preserved, and dependencies outside the set are treated as
* already satisfied.
*
* @param configurations - The configurations to order.
* @returns The configurations ordered so that dependencies come first.
*/
export function orderByDependencies(
configurations: InitializationConfiguration<unknown, unknown>[],
): InitializationConfiguration<unknown, unknown>[] {
const availableNames = new Set(configurations.map((config) => config.name));
const remaining = [...configurations];
const initialized = new Set<string>();
const ordered: InitializationConfiguration<unknown, unknown>[] = [];

while (remaining.length > 0) {
const index = remaining.findIndex((config) =>
(config.dependencies ?? []).every(
(dependency) =>
!availableNames.has(dependency) || initialized.has(dependency),
),
);

if (index === -1) {
// A dependency cycle remains. Emit the rest in their current order rather
// than looping forever; the messenger surfaces a clear "handler has not
// been registered" error at construction time.
ordered.push(...remaining);
break;
}

const [config] = remaining.splice(index, 1);
ordered.push(config);
initialized.add(config.name);
}

return ordered;
}
Loading