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
2 changes: 2 additions & 0 deletions .github/CODEOWNERS
Original file line number Diff line number Diff line change
Expand Up @@ -154,9 +154,11 @@
/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/passkey-controller/ @MetaMask/web3auth
/packages/wallet/src/initialization/instances/permission-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/seedless-onboarding-controller/ @MetaMask/web3auth
/packages/wallet/src/initialization/instances/storage-service/ @MetaMask/extension-platform @MetaMask/mobile-platform @MetaMask/core-platform
/packages/wallet/src/initialization/instances/subject-metadata-controller/ @MetaMask/core-platform
/packages/wallet/src/initialization/instances/transaction-controller/ @MetaMask/confirmations

## Package Release related
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -662,6 +662,7 @@ linkStyle default opacity:0.5
wallet --> messenger;
wallet --> network_controller;
wallet --> passkey_controller;
wallet --> permission_controller;
wallet --> remote_feature_flag_controller;
wallet --> seedless_onboarding_controller;
wallet --> storage_service;
Expand Down
31 changes: 17 additions & 14 deletions codeowners.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,13 @@ type PackageInfo = {
teams: string[];

/**
* The package's directory name under
* `/packages/wallet/src/initialization/instances`, used to generate its rule
* in the "Initialization" section. Omit this if the package has not been
* added to the Wallet Library yet.
* The package's directory name(s) under
* `/packages/wallet/src/initialization/instances`, used to generate its rule(s)
* in the "Initialization" section. Pass an array for packages that export more
* than one wired controller. Omit this if the package has not been added to the
* Wallet Library yet.
*/
initializationPath?: string;
initializationPath?: string | string[];
};

/**
Expand Down Expand Up @@ -256,6 +257,10 @@ const PACKAGES: Record<string, PackageInfo> = {
},
'permission-controller': {
teams: ['@MetaMask/core-platform'],
initializationPath: [
'permission-controller',
'subject-metadata-controller',
],
},
'permission-log-controller': {
teams: ['@MetaMask/core-platform'],
Expand Down Expand Up @@ -607,16 +612,14 @@ function buildJointTeamOwnershipSection(): CodeownersSection {
function buildInitializationSection(): CodeownersSection {
return {
title: 'Initialization',
rules: Object.keys(PACKAGES)
.filter((name) => PACKAGES[name].initializationPath !== undefined)
.sort()
.map((name) => {
const { teams, initializationPath } = PACKAGES[name];
return {
pattern: `/packages/wallet/src/initialization/instances/${initializationPath}/`,
rules: Object.values(PACKAGES)
.flatMap(({ teams, initializationPath = [] }) =>
[initializationPath].flat().map((instancePath) => ({
pattern: `/packages/wallet/src/initialization/instances/${instancePath}/`,
owners: teams,
};
}),
})),
)
.sort((ruleA, ruleB) => ruleA.pattern.localeCompare(ruleB.pattern)),
};
}

Expand Down
12 changes: 12 additions & 0 deletions packages/wallet/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,20 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

### Added

- **BREAKING:** Wire `PermissionController` and `SubjectMetadataController` into the default wallet initialization ([#9300](https://github.com/MetaMask/core/pull/9300))
- Consumers that pass their own `messenger` and already wire either controller must remove their own before upgrading, or the duplicate action registration will collide.
- Adds optional `instanceOptions.permissionController` (`caveatSpecifications`, `permissionSpecifications`, `unrestrictedMethods`) and `instanceOptions.subjectMetadataController.subjectCacheLimit`. Specifications default to empty, so no permission can be granted until a consumer injects them; `subjectCacheLimit` defaults to `100`.
- Consumers restoring persisted state must restore the `PermissionController` key alongside the `SubjectMetadataController` key, since subject metadata is retained only for origins holding permissions.
- Consumers whose `permissionSpecifications` invoke actions that `PermissionControllerMessenger` does not declare (for example the Snaps `wallet_snap` specifications) must replace the `PermissionController` configuration via `initializationConfigurations`.
- Export the `InitializationConfiguration` type, which a consumer needs in order to supply `initializationConfigurations` ([#9300](https://github.com/MetaMask/core/pull/9300))

### Changed

- **BREAKING:** A configuration passed to `initializationConfigurations` that overrides a default is now initialized in that default's position rather than ahead of all defaults, preserving construction-order dependencies between default controllers (e.g. `PermissionController` before `SubjectMetadataController`) ([#9300](https://github.com/MetaMask/core/pull/9300))
- An override that relied on being constructed before every other default must now account for its default's position; configurations that do not override a default are still initialized first.
- `initialize` now throws when two entries in `initializationConfigurations` share a `name` ([#9300](https://github.com/MetaMask/core/pull/9300))
- Bump `@metamask/network-controller` from `^35.0.0` to `^35.0.1` ([#9758](https://github.com/MetaMask/core/pull/9758))

## [9.0.0]
Expand Down
1 change: 1 addition & 0 deletions packages/wallet/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@
"@metamask/messenger": "^2.0.0",
"@metamask/network-controller": "^35.0.1",
"@metamask/passkey-controller": "^3.0.0",
"@metamask/permission-controller": "^13.1.1",
"@metamask/remote-feature-flag-controller": "^5.0.0",
"@metamask/scure-bip39": "^2.1.1",
"@metamask/seedless-onboarding-controller": "^10.1.0",
Expand Down
89 changes: 87 additions & 2 deletions packages/wallet/src/Wallet.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@ import { webcrypto } from 'crypto';
import MockEncryptor from '../../keyring-controller/tests/mocks/mockEncryptor.js';
import * as initializationModule from './initialization/initialization.js';
import { AlwaysOnlineAdapter } from './initialization/instances/connectivity-controller/always-online-adapter.js';
import { subjectMetadataController } from './initialization/instances/subject-metadata-controller/subject-metadata-controller.js';
import type { InitializationConfiguration } from './initialization/types.js';
import type { WalletOptions } from './types.js';
import { importSecretRecoveryPhrase } from './utilities.js';
import { Wallet } from './Wallet.js';

Expand All @@ -23,8 +26,35 @@ const REMOTE_FEATURE_FLAG_OPTIONS = {
},
};

async function setupWallet(): Promise<Wallet> {
const wallet = new Wallet({
const PERSISTED_SUBJECT_ORIGIN = 'https://metamask.io';

const PERSISTED_PERMISSIONED_SUBJECT_STATE = {
PermissionController: {
subjects: {
[PERSISTED_SUBJECT_ORIGIN]: {
origin: PERSISTED_SUBJECT_ORIGIN,
permissions: { somePermission: {} },
},
},
},
SubjectMetadataController: {
subjectMetadata: {
[PERSISTED_SUBJECT_ORIGIN]: {
origin: PERSISTED_SUBJECT_ORIGIN,
name: 'MetaMask',
subjectType: null,
extensionId: null,
iconUrl: null,
},
},
},
};

function createWallet(
options: Omit<WalletOptions, 'instanceOptions'> = {},
): Wallet {
return new Wallet({
...options,
instanceOptions: {
connectivityController: {
connectivityAdapter: new AlwaysOnlineAdapter(),
Expand All @@ -41,6 +71,10 @@ async function setupWallet(): Promise<Wallet> {
remoteFeatureFlagController: REMOTE_FEATURE_FLAG_OPTIONS,
},
});
}

async function setupWallet(): Promise<Wallet> {
const wallet = createWallet();

await importSecretRecoveryPhrase(wallet, TEST_PASSWORD, TEST_SRP);

Expand Down Expand Up @@ -508,4 +542,55 @@ describe('Wallet', () => {
).toStrictEqual({ testFlag: true });
});
});

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

expect(
wallet.messenger.call('PermissionController:getState'),
).toStrictEqual({ subjects: {} });
});
});

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

expect(
wallet.messenger.call('SubjectMetadataController:getState'),
).toStrictEqual({ subjectMetadata: {} });
});

// Hydrating persisted metadata calls `PermissionController:hasPermissions`,
// so construction throws unless `PermissionController` came first.
it.each([
{
description: 'from the defaults',
initializationConfigurations: undefined,
},
{
description: 'when overridden',
initializationConfigurations: [
subjectMetadataController as InitializationConfiguration<
unknown,
unknown
>,
],
},
])(
'hydrates persisted subject metadata $description, consulting the wired PermissionController for retention',
({ initializationConfigurations }) => {
const wallet = createWallet({
initializationConfigurations,
state: PERSISTED_PERMISSIONED_SUBJECT_STATE,
});

// The subject holds permissions, so its metadata survives hydration.
expect(
Object.keys(wallet.state.SubjectMetadataController.subjectMetadata),
).toContain(PERSISTED_SUBJECT_ORIGIN);
},
);
});
});
1 change: 1 addition & 0 deletions packages/wallet/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ export { Wallet } from './Wallet.js';
export { AlwaysOnlineAdapter } from './initialization/instances/connectivity-controller/always-online-adapter.js';
export { importSecretRecoveryPhrase } from './utilities.js';
export type { WalletOptions } from './types.js';
export type { InitializationConfiguration } from './initialization/types.js';
export type {
DefaultActions,
DefaultEvents,
Expand Down
Loading