feat(wallet): Add SeedlessOnboardingController and PasskeyController - #9533
Conversation
|
@metamaskbot publish-preview |
|
Preview builds have been published. Learn how to use preview builds in other projects. Expand for full list of packages and versions. |
| * Encryptor used to protect the seedless onboarding vault. Defaults to a PBKDF2 encryptor | ||
| * configured with 600,000 iterations. | ||
| */ | ||
| encryptor?: GenericEncryptor; |
There was a problem hiding this comment.
Why do we need to override this option just for initialization? (edit: oh, perhaps this is related to #9533 (comment))
There was a problem hiding this comment.
Hi, we wanna follow the KeyringControllerInstanceOptions.
We intentionally make encryptor field optional (default with GenericEncryptor) so that extension side controller init is align with KeyringController initialization.
For the mobile side, we have to provide the mobile compatible version.
There was a problem hiding this comment.
Hmm, we should think of a different way to accomplish this so we don't have to override anything in the init code. Ideally initialization code should be as simple as possible — it should only construct the controller, and the controller should do the rest. For instance in this case since the encryptor is supplied on the client side, the type of the encryptor in the controller should be generic enough to allow for that.
But if this is already a problem with KeyringController then it's a known problem. It would just be nice to resolve this at some point.
| describe('seedlessOnboardingController', () => { | ||
| beforeEach(() => { | ||
| jest.clearAllMocks(); | ||
| (SeedlessOnboardingController as jest.Mock).mockImplementation( |
There was a problem hiding this comment.
See note above about mocking PasskeyController. I feel like we shouldn't have to do this.
|
|
||
| describe('seedlessOnboardingController', () => { | ||
| beforeEach(() => { | ||
| jest.clearAllMocks(); |
There was a problem hiding this comment.
You shouldn't have to do this, mocks are cleared and restored automatically in this repo.
| jest.clearAllMocks(); |
| afterEach(() => { | ||
| jest.restoreAllMocks(); | ||
| }); | ||
|
|
There was a problem hiding this comment.
This shouldn't be necessary either.
| afterEach(() => { | |
| jest.restoreAllMocks(); | |
| }); |
| * Encryptor used to protect the seedless onboarding vault. Defaults to a PBKDF2 encryptor | ||
| * configured with 600,000 iterations. | ||
| */ | ||
| encryptor?: GenericEncryptor; |
There was a problem hiding this comment.
Hmm, we should think of a different way to accomplish this so we don't have to override anything in the init code. Ideally initialization code should be as simple as possible — it should only construct the controller, and the controller should do the rest. For instance in this case since the encryptor is supplied on the client side, the type of the encryptor in the controller should be generic enough to allow for that.
But if this is already a problem with KeyringController then it's a known problem. It would just be nice to resolve this at some point.
| beforeEach(() => { | ||
| jest.clearAllMocks(); | ||
| (PasskeyController as jest.Mock).mockImplementation( | ||
| (...args: unknown[]) => new ActualPasskeyController(...args), |
There was a problem hiding this comment.
Why are we mocking the controller only to effectively unmock it? I realize that you may have copied this from another test but I don't think we need to do this. See the tests for ApprovalController and RemoteFeatureFlagController.
| it('is registered as a default initialization configuration', () => { | ||
| expect(Object.values(defaultConfigurations)).toContain(passkeyController); | ||
| }); | ||
|
|
There was a problem hiding this comment.
Do we need this test? I realize we do this for other controllers, but we use import * as defaultConfigurations from './instances'; in defaults.ts, so we can probably make a safe assumption this is included. (Besides, the purpose of this file is to test passkey-controller.ts — not defaults.ts.)
| it('is registered as a default initialization configuration', () => { | ||
| expect(Object.values(defaultConfigurations)).toContain( | ||
| seedlessOnboardingController, | ||
| ); | ||
| }); | ||
|
|
There was a problem hiding this comment.
See note about this test in passkey-controller.test.ts.
|
@metamaskbot publish-preview |
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, have a team admin enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 30b4935. Configure here.
|
Preview builds have been published. Learn how to use preview builds in other projects. Expand for full list of packages and versions. |
mcmire
left a comment
There was a problem hiding this comment.
One nit, but looks good otherwise.
| messenger, | ||
| state, | ||
| }), | ||
| getMessenger: (parent: RootMessenger<DefaultActions, DefaultEvents>) => { |
There was a problem hiding this comment.
Nit: You don't need this type assertion, TypeScript can infer it:
| getMessenger: (parent: RootMessenger<DefaultActions, DefaultEvents>) => { | |
| getMessenger: (parent) => { |
…metamask/wallet (MetaMask#33430) ## **Description** Bumps `@metamask/wallet` to **v8.1.0** and adopts the two controllers it newly wires into the default `Wallet` initialization set, continuing the shared controller-integration migration ([`@metamask/wallet`](https://github.com/MetaMask/core/tree/main/packages/wallet) is the layer that extension, mobile, and wallet-cli all share). **GasFeeController** (core [MetaMask#9527](MetaMask/core#9527)): - Adds `wallet-init/instance-options/gas-fee-controller.ts` (+ test) supplying the mobile-specific options: `clientId`, EIP-1559/legacy gas API endpoints, and `getCurrentNetworkLegacyGasAPICompatibility` (mainnet ‖ BSC ‖ Polygon). The wallet builds `getProvider` and `getCurrentNetworkEIP1559Compatibility` from `NetworkController` itself. - Deletes the local `gas-fee-controller-init` and `gas-fee-controller-messenger` (+ tests + barrels). - Resolves the instance via `this.#wallet.getInstance('GasFeeController')` in `Engine.ts`. **SeedlessOnboardingController** (core [MetaMask#9533](MetaMask/core#9533)) — required in the same PR because wallet@8 wires both; leaving the local init would cause a double-registration boot crash: - Adds `wallet-init/instance-options/seedless-onboarding-controller.ts` (+ test) supplying the mobile-specific options: encryptor `cipher`↔`data` adapter, JWT handlers via `AuthTokenHandler`, `web3AuthNetwork`, and `passwordOutdatedCacheTTL`. - Deletes the local `seedless-onboarding-controller-messenger` factory entry from `MESSENGER_FACTORIES`. - Updates E2E Metro mock to intercept `@metamask/seedless-onboarding-controller` at the **package** level (routing to `tests/module-mocking/seedless/package.ts`) instead of the former local controller path. - Resolves the instance via `this.#wallet.getInstance('SeedlessOnboardingController')` in `Engine.ts`. Both controllers are dropped from `MessengerClientsToInitialize` only; they remain in `MessengerClients` / `EngineState` / actions / events / `BACKGROUND_STATE_CHANGE_EVENT_NAMES`, matching the already-migrated controllers. ## **Changelog** CHANGELOG entry: null ## **Related issues** Refs: MetaMask/core#9527 Refs: MetaMask/core#9533 ## **Manual testing steps** 1. Build and launch the app on iOS or Android and confirm it boots with no `A handler for GasFeeController:* has already been registered` or `SeedlessOnboardingController:*` error. 2. Open the send/confirmation flow on an EIP-1559 network (e.g. Ethereum mainnet) and confirm gas fee estimates (low / market / aggressive) load and update. 3. Repeat on a legacy-gas network (e.g. BSC or Polygon) and confirm gas prices load. 4. Submit a transaction and confirm gas estimation drives the fee correctly. 5. On a build with seedless onboarding enabled, complete a social login flow and confirm vault encryption/decryption works end-to-end. ## **Screenshots/Recordings** N/A — internal controller-wiring refactor; no UI changes. ### **Before** <!-- N/A --> ### **After** <!-- N/A --> ## **Pre-merge author checklist** - [x] I've followed [MetaMask Contributor Docs](https://github.com/MetaMask/contributor-docs) and [MetaMask Mobile Coding Standards](https://github.com/MetaMask/metamask-mobile/blob/main/.github/guidelines/CODING_GUIDELINES.md). - [x] I've completed the PR template to the best of my ability - [x] I've included tests if applicable - [x] I've documented my code using [JSDoc](https://jsdoc.app/) format if applicable - [x] I've applied the right labels on the PR (see [labeling guidelines](https://github.com/MetaMask/metamask-mobile/blob/main/.github/guidelines/LABELING_GUIDELINES.md)). Not required for external contributors. #### Performance checks (if applicable) - [x] I've tested on Android - Ideally on a mid-range device; emulator is acceptable - [x] I've tested with a power user scenario - Use these [power-user SRPs](https://consensyssoftware.atlassian.net/wiki/spaces/TL1/pages/edit-v2/401401446401?draftShareId=9d77e1e1-4bdc-4be1-9ebb-ccd916988d93) to import wallets with many accounts and tokens - [x] I've instrumented key operations with Sentry traces for production performance metrics - See [`trace()`](/app/util/trace.ts) for usage and [`addToken`](/app/components/Views/AddAsset/components/AddCustomToken/AddCustomToken.tsx#L274) for an example For performance guidelines and tooling, see the [Performance Guide](https://consensyssoftware.atlassian.net/wiki/spaces/TL1/pages/400085549067/Performance+Guide+for+Engineers). ## **Pre-merge reviewer checklist** - [ ] I've manually tested the PR (e.g. pull and build branch, run the app, test code being changed). - [ ] I confirm that this PR addresses all acceptance criteria described in the ticket it closes and includes the necessary testing evidence such as recordings and or screenshots. 🤖 Generated with [Claude Code](https://claude.com/claude-code) <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **High Risk** > Touches gas estimation for sends/confirmations and seedless vault encryption/onboarding—both security- and funds-sensitive—and changes controller lifecycle in a way that can cause boot failures if registration diverges. > > **Overview** > Bumps **`@metamask/wallet`** to **v8.1.0** (with **`@metamask/gas-fee-controller`** and **`@metamask/seedless-onboarding-controller`** bumps) and moves **GasFeeController** and **SeedlessOnboardingController** off mobile’s Engine messenger-client init path into the shared Wallet bootstrap. > > **GasFeeController** and **SeedlessOnboardingController** are no longer registered via local `*-init` modules or messenger factories. Mobile passes **`gasFeeController`** and **`seedlessOnboardingController`** **`instanceOptions`** from new **`wallet-init/instance-options/*`** builders (gas API endpoints, legacy-gas compatibility, seedless encryptor adapter, JWT handlers, Web3Auth network). **`Engine`** resolves both with **`this.#wallet.getInstance(...)`** instead of **`messengerClientsByName`**, avoiding double registration at boot. > > Local **`gas-fee-controller-init`**, **`gas-fee-controller-messenger`**, and the old seedless init/messenger wiring are removed; tests move to the instance-option modules. **CODEOWNERS** and E2E **Metro** mocks shift to **`@metamask/seedless-onboarding-controller`** at the package level because Wallet now constructs that controller. > > <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit 82f1fe5. Bugbot is set up for automated code reviews on this repo. Configure [here](https://www.cursor.com/dashboard/bugbot).</sup> <!-- /CURSOR_SUMMARY --> --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> Co-authored-by: Erik Marks <25517051+rekmarks@users.noreply.github.com>
…metamask/wallet (#33430) ## **Description** Bumps `@metamask/wallet` to **v8.1.0** and adopts the two controllers it newly wires into the default `Wallet` initialization set, continuing the shared controller-integration migration ([`@metamask/wallet`](https://github.com/MetaMask/core/tree/main/packages/wallet) is the layer that extension, mobile, and wallet-cli all share). **GasFeeController** (core [#9527](MetaMask/core#9527)): - Adds `wallet-init/instance-options/gas-fee-controller.ts` (+ test) supplying the mobile-specific options: `clientId`, EIP-1559/legacy gas API endpoints, and `getCurrentNetworkLegacyGasAPICompatibility` (mainnet ‖ BSC ‖ Polygon). The wallet builds `getProvider` and `getCurrentNetworkEIP1559Compatibility` from `NetworkController` itself. - Deletes the local `gas-fee-controller-init` and `gas-fee-controller-messenger` (+ tests + barrels). - Resolves the instance via `this.#wallet.getInstance('GasFeeController')` in `Engine.ts`. **SeedlessOnboardingController** (core [#9533](MetaMask/core#9533)) — required in the same PR because wallet@8 wires both; leaving the local init would cause a double-registration boot crash: - Adds `wallet-init/instance-options/seedless-onboarding-controller.ts` (+ test) supplying the mobile-specific options: encryptor `cipher`↔`data` adapter, JWT handlers via `AuthTokenHandler`, `web3AuthNetwork`, and `passwordOutdatedCacheTTL`. - Deletes the local `seedless-onboarding-controller-messenger` factory entry from `MESSENGER_FACTORIES`. - Updates E2E Metro mock to intercept `@metamask/seedless-onboarding-controller` at the **package** level (routing to `tests/module-mocking/seedless/package.ts`) instead of the former local controller path. - Resolves the instance via `this.#wallet.getInstance('SeedlessOnboardingController')` in `Engine.ts`. Both controllers are dropped from `MessengerClientsToInitialize` only; they remain in `MessengerClients` / `EngineState` / actions / events / `BACKGROUND_STATE_CHANGE_EVENT_NAMES`, matching the already-migrated controllers. ## **Changelog** CHANGELOG entry: null ## **Related issues** Refs: MetaMask/core#9527 Refs: MetaMask/core#9533 ## **Manual testing steps** 1. Build and launch the app on iOS or Android and confirm it boots with no `A handler for GasFeeController:* has already been registered` or `SeedlessOnboardingController:*` error. 2. Open the send/confirmation flow on an EIP-1559 network (e.g. Ethereum mainnet) and confirm gas fee estimates (low / market / aggressive) load and update. 3. Repeat on a legacy-gas network (e.g. BSC or Polygon) and confirm gas prices load. 4. Submit a transaction and confirm gas estimation drives the fee correctly. 5. On a build with seedless onboarding enabled, complete a social login flow and confirm vault encryption/decryption works end-to-end. ## **Screenshots/Recordings** N/A — internal controller-wiring refactor; no UI changes. ### **Before** <!-- N/A --> ### **After** <!-- N/A --> ## **Pre-merge author checklist** - [x] I've followed [MetaMask Contributor Docs](https://github.com/MetaMask/contributor-docs) and [MetaMask Mobile Coding Standards](https://github.com/MetaMask/metamask-mobile/blob/main/.github/guidelines/CODING_GUIDELINES.md). - [x] I've completed the PR template to the best of my ability - [x] I've included tests if applicable - [x] I've documented my code using [JSDoc](https://jsdoc.app/) format if applicable - [x] I've applied the right labels on the PR (see [labeling guidelines](https://github.com/MetaMask/metamask-mobile/blob/main/.github/guidelines/LABELING_GUIDELINES.md)). Not required for external contributors. #### Performance checks (if applicable) - [x] I've tested on Android - Ideally on a mid-range device; emulator is acceptable - [x] I've tested with a power user scenario - Use these [power-user SRPs](https://consensyssoftware.atlassian.net/wiki/spaces/TL1/pages/edit-v2/401401446401?draftShareId=9d77e1e1-4bdc-4be1-9ebb-ccd916988d93) to import wallets with many accounts and tokens - [x] I've instrumented key operations with Sentry traces for production performance metrics - See [`trace()`](/app/util/trace.ts) for usage and [`addToken`](/app/components/Views/AddAsset/components/AddCustomToken/AddCustomToken.tsx#L274) for an example For performance guidelines and tooling, see the [Performance Guide](https://consensyssoftware.atlassian.net/wiki/spaces/TL1/pages/400085549067/Performance+Guide+for+Engineers). ## **Pre-merge reviewer checklist** - [ ] I've manually tested the PR (e.g. pull and build branch, run the app, test code being changed). - [ ] I confirm that this PR addresses all acceptance criteria described in the ticket it closes and includes the necessary testing evidence such as recordings and or screenshots. 🤖 Generated with [Claude Code](https://claude.com/claude-code) <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **High Risk** > Touches gas estimation for sends/confirmations and seedless vault encryption/onboarding—both security- and funds-sensitive—and changes controller lifecycle in a way that can cause boot failures if registration diverges. > > **Overview** > Bumps **`@metamask/wallet`** to **v8.1.0** (with **`@metamask/gas-fee-controller`** and **`@metamask/seedless-onboarding-controller`** bumps) and moves **GasFeeController** and **SeedlessOnboardingController** off mobile’s Engine messenger-client init path into the shared Wallet bootstrap. > > **GasFeeController** and **SeedlessOnboardingController** are no longer registered via local `*-init` modules or messenger factories. Mobile passes **`gasFeeController`** and **`seedlessOnboardingController`** **`instanceOptions`** from new **`wallet-init/instance-options/*`** builders (gas API endpoints, legacy-gas compatibility, seedless encryptor adapter, JWT handlers, Web3Auth network). **`Engine`** resolves both with **`this.#wallet.getInstance(...)`** instead of **`messengerClientsByName`**, avoiding double registration at boot. > > Local **`gas-fee-controller-init`**, **`gas-fee-controller-messenger`**, and the old seedless init/messenger wiring are removed; tests move to the instance-option modules. **CODEOWNERS** and E2E **Metro** mocks shift to **`@metamask/seedless-onboarding-controller`** at the package level because Wallet now constructs that controller. > > <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit 82f1fe5. Bugbot is set up for automated code reviews on this repo. Configure [here](https://www.cursor.com/dashboard/bugbot).</sup> <!-- /CURSOR_SUMMARY --> --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> Co-authored-by: Erik Marks <25517051+rekmarks@users.noreply.github.com>
…eController from wallet (#45246) This PR continues the migration of controller construction into the shared `@metamask/wallet` integration layer. It upgrades `@metamask/wallet` from 7.0.1 to 8.1.0 and `@metamask/passkey-controller` from 2.0.1 to 3.0.0, then sources `PasskeyController` and `SeedlessOnboardingController` from the wallet instead of the extension's local messenger client initialization. The extension now supplies only its platform specific controller options: - Passkey relying party details, extension origin, browser platform, and onboarding state. - Seedless onboarding network, encryption, and OAuth token callbacks. - Gas fee API endpoints, polling interval, client ID, and legacy network compatibility. Passkey vault orchestration is delegated to `PasskeyController`, covering enrollment, unlock, removal, and vault key renewal on password change. The legacy background API exposes the required wallet owned controller actions while preserving existing UI call sites and error translation. This PR also incorporates the `GasFeeController` wallet migration. The extension's local GasFee initialization and messenger factories are removed, and consumers now resolve the wallet owned instance. Tests were added or updated for wallet option wiring, controller delegation, passkey errors, and legacy background API behavior. Upstream changes: - MetaMask/core#9533 - MetaMask/core#9527 CHANGELOG entry: null <!-- Fixes: --> 1. Configure a development build with `PASSKEY_ENABLED=true` and `SEEDLESS_ONBOARDING_ENABLED=true`, including the required OAuth development configuration. 2. Load the extension and complete the standard SRP onboarding flow. Lock and unlock the wallet with the password to verify the normal flow still works. 3. Open **Settings > Security & privacy**, set up a passkey, lock MetaMask, and verify that the wallet unlocks with the registered passkey. 4. With a passkey enrolled, change the wallet password and verify that the new password works and passkey unlock continues to work. 5. Remove the passkey using passkey verification, then verify that passkey unlock is no longer offered. 6. Reset the extension and create or import a wallet using Google or Apple social login. Lock and unlock the wallet and verify the social login wallet remains accessible. 7. Submit a transaction on a supported EVM network and verify that gas fee estimates load and the transaction can be confirmed. <!-- --> - [ ] I've followed [MetaMask Contributor Docs](https://github.com/MetaMask/contributor-docs) and [MetaMask Extension Coding Standards](https://github.com/MetaMask/metamask-extension/blob/main/.github/guidelines/CODING_GUIDELINES.md). - [ ] I've completed the PR template to the best of my ability - [ ] I’ve included tests if applicable - [ ] I’ve documented my code using [JSDoc](https://jsdoc.app/) format if applicable - [ ] I’ve applied the right labels on the PR (see [labeling guidelines](https://github.com/MetaMask/metamask-extension/blob/main/.github/guidelines/LABELING_GUIDELINES.md)). Not required for external contributors. - [x] I've manually tested the PR (e.g. pull and build branch, run the app, test code being changed). - [x] I confirm that this PR addresses all acceptance criteria described in the ticket it closes and includes the necessary testing evidence such as recordings and or screenshots. <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **High Risk** > Touches passkey enrollment, vault unlock, password change, and seedless OAuth flows with a large refactor of how controllers are constructed and delegated. > > **Overview** > Upgrades **`@metamask/wallet`** to **8.1.0** and **`@metamask/passkey-controller`** to **3.0.0**, then moves **`PasskeyController`**, **`SeedlessOnboardingController`**, and **`GasFeeController`** out of extension messenger-client init and into wallet initialization via new **`wallet-init`** instance options (extension origin/RP, OAuth callbacks, gas API endpoints, BSC legacy compatibility). > > **`MetaMaskController`** resolves those controllers with **`wallet.getInstance()`**, drops the large inline passkey/vault helpers, and routes the public API through messenger actions. Unlock and passkey-gated password change go through **`LegacyBackgroundApiService`** so post-unlock account init and **`seedlessOperationMutex`** serialization stay extension-specific. > > Local gas/passkey/seedless init modules, messenger factories, and related tests are removed; UI/redux call sites adopt object-shaped params and **`PasskeyControllerErrorCode`** (replacing **`ExtensionPasskeyErrorCode`**). LavaMoat policies are updated for the expanded wallet dependency graph. > > <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit 4215648. Bugbot is set up for automated code reviews on this repo. Configure [here](https://www.cursor.com/dashboard/bugbot).</sup> <!-- /CURSOR_SUMMARY --> --------- Co-authored-by: lwin <lwin.kyaw@consensys.net> Co-authored-by: MetaMask Bot <metamaskbot@users.noreply.github.com>
…eController from wallet (MetaMask#45246) ## **Description** This PR continues the migration of controller construction into the shared `@metamask/wallet` integration layer. It upgrades `@metamask/wallet` from 7.0.1 to 8.1.0 and `@metamask/passkey-controller` from 2.0.1 to 3.0.0, then sources `PasskeyController` and `SeedlessOnboardingController` from the wallet instead of the extension's local messenger client initialization. The extension now supplies only its platform specific controller options: - Passkey relying party details, extension origin, browser platform, and onboarding state. - Seedless onboarding network, encryption, and OAuth token callbacks. - Gas fee API endpoints, polling interval, client ID, and legacy network compatibility. Passkey vault orchestration is delegated to `PasskeyController`, covering enrollment, unlock, removal, and vault key renewal on password change. The legacy background API exposes the required wallet owned controller actions while preserving existing UI call sites and error translation. This PR also incorporates the `GasFeeController` wallet migration. The extension's local GasFee initialization and messenger factories are removed, and consumers now resolve the wallet owned instance. Tests were added or updated for wallet option wiring, controller delegation, passkey errors, and legacy background API behavior. Upstream changes: - MetaMask/core#9533 - MetaMask/core#9527 ## **Changelog** CHANGELOG entry: null <!-- ## **Related issues** Fixes: --> ## **Manual testing steps** 1. Configure a development build with `PASSKEY_ENABLED=true` and `SEEDLESS_ONBOARDING_ENABLED=true`, including the required OAuth development configuration. 2. Load the extension and complete the standard SRP onboarding flow. Lock and unlock the wallet with the password to verify the normal flow still works. 3. Open **Settings > Security & privacy**, set up a passkey, lock MetaMask, and verify that the wallet unlocks with the registered passkey. 4. With a passkey enrolled, change the wallet password and verify that the new password works and passkey unlock continues to work. 5. Remove the passkey using passkey verification, then verify that passkey unlock is no longer offered. 6. Reset the extension and create or import a wallet using Google or Apple social login. Lock and unlock the wallet and verify the social login wallet remains accessible. 7. Submit a transaction on a supported EVM network and verify that gas fee estimates load and the transaction can be confirmed. <!-- ## **Screenshots/Recordings** ### **Before** ### **After** --> ## **Pre-merge author checklist** - [ ] I've followed [MetaMask Contributor Docs](https://github.com/MetaMask/contributor-docs) and [MetaMask Extension Coding Standards](https://github.com/MetaMask/metamask-extension/blob/main/.github/guidelines/CODING_GUIDELINES.md). - [ ] I've completed the PR template to the best of my ability - [ ] I’ve included tests if applicable - [ ] I’ve documented my code using [JSDoc](https://jsdoc.app/) format if applicable - [ ] I’ve applied the right labels on the PR (see [labeling guidelines](https://github.com/MetaMask/metamask-extension/blob/main/.github/guidelines/LABELING_GUIDELINES.md)). Not required for external contributors. ## **Pre-merge reviewer checklist** - [x] I've manually tested the PR (e.g. pull and build branch, run the app, test code being changed). - [x] I confirm that this PR addresses all acceptance criteria described in the ticket it closes and includes the necessary testing evidence such as recordings and or screenshots. <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **High Risk** > Touches passkey enrollment, vault unlock, password change, and seedless OAuth flows with a large refactor of how controllers are constructed and delegated. > > **Overview** > Upgrades **`@metamask/wallet`** to **8.1.0** and **`@metamask/passkey-controller`** to **3.0.0**, then moves **`PasskeyController`**, **`SeedlessOnboardingController`**, and **`GasFeeController`** out of extension messenger-client init and into wallet initialization via new **`wallet-init`** instance options (extension origin/RP, OAuth callbacks, gas API endpoints, BSC legacy compatibility). > > **`MetaMaskController`** resolves those controllers with **`wallet.getInstance()`**, drops the large inline passkey/vault helpers, and routes the public API through messenger actions. Unlock and passkey-gated password change go through **`LegacyBackgroundApiService`** so post-unlock account init and **`seedlessOperationMutex`** serialization stay extension-specific. > > Local gas/passkey/seedless init modules, messenger factories, and related tests are removed; UI/redux call sites adopt object-shaped params and **`PasskeyControllerErrorCode`** (replacing **`ExtensionPasskeyErrorCode`**). LavaMoat policies are updated for the expanded wallet dependency graph. > > <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit 4215648. Bugbot is set up for automated code reviews on this repo. Configure [here](https://www.cursor.com/dashboard/bugbot).</sup> <!-- /CURSOR_SUMMARY --> --------- Co-authored-by: lwin <lwin.kyaw@consensys.net> Co-authored-by: MetaMask Bot <metamaskbot@users.noreply.github.com>

Explanation
Adds
PasskeyControllerandSeedlessOnboardingControlleras default initialized controllers in thewalletpackage, following theInitializationConfigurationpattern established byKeyringController,AccountsController, and other wallet instances. Each controller gets its own per-directory init module (passkey-controller/,seedless-onboarding-controller/) with colocated types and tests.@metamask/walletis the shared controller-integration layer for extension, mobile, and other clients, so values that differ between clients are injected viainstanceOptionsrather than hardcoded.PasskeyController — WebAuthn relying-party configuration is platform-specific, so
passkeyController.expectedRPID,passkeyController.expectedOrigin, andpasskeyController.rpNamemust be supplied by each client (see the options table below). The wallet init forwardsinstanceOptionsto the controller as-is and does not inject any MetaMask-specific defaults;rpNameis required, anduserName/userDisplayNamefall back torpNameinsidePasskeyControllerwhen omitted. Persisted passkey state is forwarded from wallet state when present.SeedlessOnboardingController — JWT lifecycle callbacks (
refreshJWTToken,revokeRefreshToken,renewRefreshToken) are platform-specific and must be injected by each client (typically delegating toOAuthServiceon extension orAuthTokenHandleron mobile). A default PBKDF2 encryptor (600,000 iterations, shared withKeyringController) is applied when no customencryptoris passed. All otherSeedlessOnboardingControllerconstructor options (e.g.network,passwordOutdatedCacheTTL,keyDerivationInterface) are forwarded frominstanceOptionsas-is.Neither controller requires messenger delegation beyond the standard
getState/stateChangedwiring — both are self-contained at the messenger level.References
Per-environment options
passkeyController.expectedRPIDextension.runtime.getURL('')(stripped trailing slash)'extension-id')passkeyController.expectedOriginexpectedRPID'https://extension.origin')passkeyController.rpName'MetaMask')'MetaMask'); requiredpasskeyController.userName/userDisplayNamerpNameis desiredrpNameinside the controller when omittedseedlessOnboardingController.refreshJWTTokenOAuthService:getNewRefreshTokenvia init messengerAuthTokenHandlerjest.fn()in unit testsseedlessOnboardingController.revokeRefreshTokenOAuthService:revokeRefreshTokenAuthTokenHandlerjest.fn()in unit testsseedlessOnboardingController.renewRefreshTokenOAuthService:renewRefreshTokenAuthTokenHandlerjest.fn()in unit testsseedlessOnboardingController.encryptorencryptorFactory(600_000)(extension-local factory)Encryptoradapter (cipher↔datanormalization)encryptorFactory(600_000)seedlessOnboardingController.networkWeb3AuthNetwork.Devnet(dev/test) orMainnet(prod)web3AuthNetworkconstantPasskeyControllerhas no messenger-level dependencies on other controllers.SeedlessOnboardingControllercurrently receives JWT callbacks out-of-band (not via messenger); a future refactor may route these throughOAuthServiceactions directly.Client construction sites
These are the current
mainconstruction sites the compatibility audit was based on (the adoption PRs below will replace them):PasskeyController
SeedlessOnboardingController
Client adoption PRs
Checklist
Note
Medium Risk
Breaking wallet initialization touches passkey vault protection and seedless auth/key material paths via Keyring delegation and encryptor defaults; misconfigured client options could break onboarding or auth at runtime.
Overview
Breaking:
@metamask/walletnow includes PasskeyController and SeedlessOnboardingController in the defaultinitialize()ensemble, so consumers must supply the new optionalinstanceOptions(passkeyController,seedlessOnboardingController) where those flows are used.New initialization modules follow the existing
InitializationConfigurationpattern: Passkey forwards client WebAuthn settings (expectedRPID,expectedOrigin,rpName, etc.) and delegates KeyringController password/export actions on the messenger; seedless onboarding forwards JWT lifecycle callbacks from the client and applies the shared 600k-iteration encryptor when none is provided.Dependencies, TypeScript project references, README dependency graph, CODEOWNERS /
codeowners.tsinit paths, and unit tests for init, state, and messenger exposure are updated accordingly.Reviewed by Cursor Bugbot for commit b5b7ac6. Bugbot is set up for automated code reviews on this repo. Configure here.