Skip to content

feat(wallet): Add SeedlessOnboardingController and PasskeyController#9533

Open
lwin-kyaw wants to merge 11 commits into
mainfrom
feat/passkey-seedless-init-instance
Open

feat(wallet): Add SeedlessOnboardingController and PasskeyController#9533
lwin-kyaw wants to merge 11 commits into
mainfrom
feat/passkey-seedless-init-instance

Conversation

@lwin-kyaw

@lwin-kyaw lwin-kyaw commented Jul 16, 2026

Copy link
Copy Markdown
Contributor

Explanation

Adds PasskeyController and SeedlessOnboardingController as default initialized controllers in the wallet package, following the InitializationConfiguration pattern established by KeyringController, 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/wallet is the shared controller-integration layer for extension, mobile, and other clients, so values that differ between clients are injected via instanceOptions rather than hardcoded.

PasskeyController — WebAuthn relying-party configuration is platform-specific, so passkeyController.expectedRPID, passkeyController.expectedOrigin, and passkeyController.rpName must be supplied by each client (see the options table below). The wallet init forwards instanceOptions to the controller as-is and does not inject any MetaMask-specific defaults; rpName is required, and userName / userDisplayName fall back to rpName inside PasskeyController when 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 to OAuthService on extension or AuthTokenHandler on mobile). A default PBKDF2 encryptor (600,000 iterations, shared with KeyringController) is applied when no custom encryptor is passed. All other SeedlessOnboardingController constructor options (e.g. network, passwordOutdatedCacheTTL, keyDerivationInterface) are forwarded from instanceOptions as-is.

Neither controller requires messenger delegation beyond the standard getState / stateChanged wiring — both are self-contained at the messenger level.

References

Per-environment options

Option Extension Mobile Default (Node / tests)
passkeyController.expectedRPID Extension origin from extension.runtime.getURL('') (stripped trailing slash) NA Test placeholder (e.g. 'extension-id')
passkeyController.expectedOrigin Same as expectedRPID NA Test placeholder (e.g. 'https://extension.origin')
passkeyController.rpName Supplied by client (e.g. 'MetaMask') NA Supplied by caller (e.g. 'MetaMask'); required
passkeyController.userName / userDisplayName Supplied by client when a value other than rpName is desired NA Fall back to rpName inside the controller when omitted
seedlessOnboardingController.refreshJWTToken Delegates to OAuthService:getNewRefreshToken via init messenger Delegates to AuthTokenHandler jest.fn() in unit tests
seedlessOnboardingController.revokeRefreshToken Delegates to OAuthService:revokeRefreshToken Delegates to AuthTokenHandler jest.fn() in unit tests
seedlessOnboardingController.renewRefreshToken Delegates to OAuthService:renewRefreshToken Delegates to AuthTokenHandler jest.fn() in unit tests
seedlessOnboardingController.encryptor encryptorFactory(600_000) (extension-local factory) Mobile Encryptor adapter (cipherdata normalization) Wallet default: encryptorFactory(600_000)
seedlessOnboardingController.network Web3AuthNetwork.Devnet (dev/test) or Mainnet (prod) web3AuthNetwork constant Must be supplied by caller when needed

PasskeyController has no messenger-level dependencies on other controllers. SeedlessOnboardingController currently receives JWT callbacks out-of-band (not via messenger); a future refactor may route these through OAuthService actions directly.

Client construction sites

These are the current main construction sites the compatibility audit was based on (the adoption PRs below will replace them):

PasskeyController

SeedlessOnboardingController

Client adoption PRs

Checklist

  • I've updated the test suite for new or updated code as appropriate
  • I've updated documentation (JSDoc, Markdown, etc.) for new or updated code as appropriate
  • I've communicated my changes to consumers by updating changelogs for packages I've changed
  • I've introduced breaking changes in this PR and have prepared draft pull requests for clients and consumer packages to resolve them

Note

High Risk
Breaking wallet bootstrap adds auth/onboarding controllers touching passkeys, JWT lifecycle, and vault encryption; clients must supply options and avoid duplicate controller wiring.

Overview
Breaking: @metamask/wallet now initializes PasskeyController and SeedlessOnboardingController by default, alongside existing wallet instances.

Each controller gets an InitializationConfiguration module (init, messenger wiring, instanceOptions types). Clients inject platform-specific settings via optional passkeyController and seedlessOnboardingController on WalletOptions.instanceOptions—e.g. WebAuthn expectedRPID / expectedOrigin for passkeys, JWT refresh/revoke/renew callbacks for seedless onboarding. Seedless onboarding defaults to the same PBKDF2 encryptor as keyring (encryptorFactory(600_000)) when no custom encryptor is passed.

The package adds dependencies on @metamask/passkey-controller and @metamask/seedless-onboarding-controller, updates build/tsconfig references, CODEOWNERS for the new init paths, and the README dependency graph. Unit tests cover registration in defaultConfigurations, option/state forwarding, and messenger getState exposure.

Reviewed by Cursor Bugbot for commit c60b979. Bugbot is set up for automated code reviews on this repo. Configure here.

@lwin-kyaw
lwin-kyaw requested a review from a team as a code owner July 16, 2026 07:10
@lwin-kyaw

Copy link
Copy Markdown
Contributor Author

@metamaskbot publish-preview

@lwin-kyaw lwin-kyaw changed the title Feat/passkey seedless init instance feat(wallet): Add SeedlessOnboardingController and PasskeyController Jul 16, 2026
@github-actions

Copy link
Copy Markdown
Contributor

Preview builds have been published. Learn how to use preview builds in other projects.

Expand for full list of packages and versions.
@metamask-previews/account-tree-controller@7.5.5-preview-b867f452d
@metamask-previews/accounts-controller@39.0.5-preview-b867f452d
@metamask-previews/address-book-controller@7.1.2-preview-b867f452d
@metamask-previews/ai-controllers@0.8.0-preview-b867f452d
@metamask-previews/analytics-controller@1.2.1-preview-b867f452d
@metamask-previews/analytics-data-regulation-controller@0.0.0-preview-b867f452d
@metamask-previews/announcement-controller@8.1.0-preview-b867f452d
@metamask-previews/app-metadata-controller@2.0.1-preview-b867f452d
@metamask-previews/approval-controller@9.0.2-preview-b867f452d
@metamask-previews/assets-controller@11.0.0-preview-b867f452d
@metamask-previews/assets-controllers@109.4.1-preview-b867f452d
@metamask-previews/authenticated-user-storage@3.0.1-preview-b867f452d
@metamask-previews/base-controller@9.1.0-preview-b867f452d
@metamask-previews/base-data-service@0.1.3-preview-b867f452d
@metamask-previews/bitcoin-regtest-up@1.0.0-preview-b867f452d
@metamask-previews/bridge-controller@77.5.0-preview-b867f452d
@metamask-previews/bridge-status-controller@74.3.0-preview-b867f452d
@metamask-previews/build-utils@3.0.4-preview-b867f452d
@metamask-previews/chain-agnostic-permission@1.7.0-preview-b867f452d
@metamask-previews/chomp-api-service@3.1.0-preview-b867f452d
@metamask-previews/claims-controller@0.5.3-preview-b867f452d
@metamask-previews/client-controller@1.0.1-preview-b867f452d
@metamask-previews/client-utils@1.1.0-preview-b867f452d
@metamask-previews/compliance-controller@2.1.0-preview-b867f452d
@metamask-previews/composable-controller@12.0.1-preview-b867f452d
@metamask-previews/config-registry-controller@0.4.1-preview-b867f452d
@metamask-previews/connectivity-controller@0.3.0-preview-b867f452d
@metamask-previews/controller-utils@12.3.0-preview-b867f452d
@metamask-previews/core-backend@6.5.0-preview-b867f452d
@metamask-previews/delegation-controller@3.0.2-preview-b867f452d
@metamask-previews/earn-controller@12.2.2-preview-b867f452d
@metamask-previews/eip-5792-middleware@3.0.5-preview-b867f452d
@metamask-previews/eip-7702-internal-rpc-middleware@0.1.1-preview-b867f452d
@metamask-previews/eip1193-permission-middleware@2.0.1-preview-b867f452d
@metamask-previews/ens-controller@19.1.5-preview-b867f452d
@metamask-previews/eth-block-tracker@15.0.1-preview-b867f452d
@metamask-previews/eth-json-rpc-middleware@23.1.3-preview-b867f452d
@metamask-previews/eth-json-rpc-provider@6.0.1-preview-b867f452d
@metamask-previews/foundryup@1.0.1-preview-b867f452d
@metamask-previews/gas-fee-controller@26.2.4-preview-b867f452d
@metamask-previews/gator-permissions-controller@4.2.3-preview-b867f452d
@metamask-previews/geolocation-controller@0.1.3-preview-b867f452d
@metamask-previews/java-tron-up@1.0.0-preview-b867f452d
@metamask-previews/json-rpc-engine@10.5.0-preview-b867f452d
@metamask-previews/json-rpc-middleware-stream@8.0.8-preview-b867f452d
@metamask-previews/keyring-controller@27.1.0-preview-b867f452d
@metamask-previews/local-node-utils@1.0.0-preview-b867f452d
@metamask-previews/logging-controller@8.0.2-preview-b867f452d
@metamask-previews/message-manager@14.1.2-preview-b867f452d
@metamask-previews/messenger@2.0.0-preview-b867f452d
@metamask-previews/messenger-cli@0.2.0-preview-b867f452d
@metamask-previews/money-account-api-data-service@0.1.0-preview-b867f452d
@metamask-previews/money-account-balance-service@2.2.0-preview-b867f452d
@metamask-previews/money-account-controller@0.3.3-preview-b867f452d
@metamask-previews/money-account-upgrade-controller@2.2.1-preview-b867f452d
@metamask-previews/multichain-account-service@13.0.0-preview-b867f452d
@metamask-previews/multichain-api-middleware@4.0.1-preview-b867f452d
@metamask-previews/multichain-network-controller@3.2.1-preview-b867f452d
@metamask-previews/multichain-transactions-controller@7.1.1-preview-b867f452d
@metamask-previews/name-controller@9.1.2-preview-b867f452d
@metamask-previews/network-connection-banner-controller@0.1.0-preview-b867f452d
@metamask-previews/network-controller@34.0.0-preview-b867f452d
@metamask-previews/network-enablement-controller@5.6.0-preview-b867f452d
@metamask-previews/notification-services-controller@25.0.0-preview-b867f452d
@metamask-previews/passkey-controller@2.1.0-preview-b867f452d
@metamask-previews/permission-controller@13.1.1-preview-b867f452d
@metamask-previews/permission-log-controller@5.1.0-preview-b867f452d
@metamask-previews/perps-controller@9.2.1-preview-b867f452d
@metamask-previews/phishing-controller@17.2.1-preview-b867f452d
@metamask-previews/platform-api-docs@0.0.0-preview-b867f452d
@metamask-previews/polling-controller@16.0.8-preview-b867f452d
@metamask-previews/preferences-controller@23.1.0-preview-b867f452d
@metamask-previews/profile-metrics-controller@4.0.2-preview-b867f452d
@metamask-previews/profile-sync-controller@28.3.0-preview-b867f452d
@metamask-previews/ramps-controller@17.0.0-preview-b867f452d
@metamask-previews/rate-limit-controller@7.0.1-preview-b867f452d
@metamask-previews/react-data-query@0.2.1-preview-b867f452d
@metamask-previews/remote-feature-flag-controller@4.2.2-preview-b867f452d
@metamask-previews/sample-controllers@5.0.3-preview-b867f452d
@metamask-previews/seedless-onboarding-controller@10.0.3-preview-b867f452d
@metamask-previews/selected-network-controller@26.1.5-preview-b867f452d
@metamask-previews/sentinel-api-service@1.0.0-preview-b867f452d
@metamask-previews/shield-controller@5.1.3-preview-b867f452d
@metamask-previews/signature-controller@39.2.7-preview-b867f452d
@metamask-previews/smart-transactions-controller@25.0.1-preview-b867f452d
@metamask-previews/snap-account-service@2.0.0-preview-b867f452d
@metamask-previews/social-controllers@2.5.0-preview-b867f452d
@metamask-previews/solana-test-validator-up@1.0.0-preview-b867f452d
@metamask-previews/stellar-quickstart-up@0.0.0-preview-b867f452d
@metamask-previews/storage-service@1.0.2-preview-b867f452d
@metamask-previews/subscription-controller@6.2.1-preview-b867f452d
@metamask-previews/transaction-controller@69.0.0-preview-b867f452d
@metamask-previews/transaction-pay-controller@25.0.0-preview-b867f452d
@metamask-previews/user-operation-controller@41.2.7-preview-b867f452d
@metamask-previews/wallet@7.0.1-preview-b867f452d
@metamask-previews/wallet-cli@0.0.0-preview-b867f452d

chaitanyapotti
chaitanyapotti previously approved these changes Jul 16, 2026
...options,
state,
messenger,
encryptor: (options.encryptor ??

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we need a typecast here? Is there a way to avoid this?

I think we also do this for KeyringController and I am not sure why. @mikesposito do you know? I think I've asked you this before.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm assuming that the generic types the controller is instantiated with do not match the ones of the encryptor created by the factory. We likely just need to carry additional generic types for encryption key and key derivation options (like KeyringController does IIRC)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, okay. Seems like a larger change. I guess we don't have to solve this right now.

* Encryptor used to protect the seedless onboarding vault. Defaults to a PBKDF2 encryptor
* configured with 600,000 iterations.
*/
encryptor?: GenericEncryptor;

@mcmire mcmire Jul 16, 2026

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we need to override this option just for initialization? (edit: oh, perhaps this is related to #9533 (comment))

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread packages/wallet/src/initialization/instances/passkey-controller/types.ts Outdated

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

❌ 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 ded1aa1. Configure here.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants