Skip to content

feat(wallet): Add SeedlessOnboardingController and PasskeyController#9533

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

feat(wallet): Add SeedlessOnboardingController and PasskeyController#9533
lwin-kyaw wants to merge 5 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 and passkeyController.expectedOrigin must be supplied by each client (see the options table below). rpName, userName, and userDisplayName default to MetaMask values when omitted ('MetaMask', 'MetaMask Wallet', 'MetaMask Wallet'). 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 / userName / userDisplayName 'MetaMask' / 'MetaMask Wallet' / 'MetaMask Wallet' (wallet defaults match current extension values) Wallet defaults apply 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
Marked breaking default wallet initialization for passkey and seedless onboarding (WebAuthn, JWT, vault encryption), so extension/mobile consumers must adopt new instance options and dependency bumps.

Overview
Breaking: The @metamask/wallet package now includes PasskeyController and SeedlessOnboardingController in its default controller set, with new dependencies and TypeScript project references.

New initialization modules follow the existing instance pattern: messengers, init wiring, and optional instanceOptions on WalletOptions (passkeyController, seedlessOnboardingController). Passkey init requires expectedRPID and expectedOrigin, applies MetaMask defaults for RP/user display fields when omitted, and forwards persisted passkey state. Seedless onboarding forwards JWT lifecycle callbacks from options and supplies a default PBKDF2 encryptor (600k iterations, shared with keyring) unless a custom encryptor is passed.

CODEOWNERS assigns the new init paths to @MetaMask/web3auth; README’s dependency graph and wallet changelog document the change. Unit tests cover registration in defaultConfigurations, option forwarding, and messenger state access.

Reviewed by Cursor Bugbot for commit b867f45. 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

@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 d795cf6. Configure here.

@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

messenger,
state,
rpId: options.rpId ?? undefined,
rpName: options.rpName ?? DEFAULT_PASSKEY_RP_NAME,

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.

What are your thoughts on having the controller provide these defaults instead of placing them here? Typically we try to keep these initialization files as light as possible. In other words does it make sense for rpName, userName, etc. to be optional at the controller level or it is better for those to be required?

...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))

Comment on lines +1 to +37
export type PasskeyControllerInstanceOptions = {
expectedRPID: string | string[];

/**
* Allowed value(s) for the WebAuthn client origin.
*/
expectedOrigin: string | string[];

/**
* Relying party ID(s) for verification (SHA-256 hash match in authenticator data). Pass a string or array of strings; an empty array skips RP ID
* allowlist checks in {@link verifyRegistrationResponse} / {@link verifyAuthenticationResponse}.
*
* @default undefined
*/
rpId?: string;

/**
* Relying party name shown in the platform passkey UI.
*
* @default 'MetaMask'
*/
rpName?: string;

/**
* Optional passkey user name; defaults to `rpName`.
*
* @default 'MetaMask Wallet'
*/
userName?: string;

/**
* Optional display name; defaults to `rpName`.
*
* @default 'MetaMask Wallet'
*/
userDisplayName?: string;
};

@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.

This is related to #9533 (comment).

If we can make these options optional in the controller, then I'm curious if we can simplify this:

Suggested change
export type PasskeyControllerInstanceOptions = {
expectedRPID: string | string[];
/**
* Allowed value(s) for the WebAuthn client origin.
*/
expectedOrigin: string | string[];
/**
* Relying party ID(s) for verification (SHA-256 hash match in authenticator data). Pass a string or array of strings; an empty array skips RP ID
* allowlist checks in {@link verifyRegistrationResponse} / {@link verifyAuthenticationResponse}.
*
* @default undefined
*/
rpId?: string;
/**
* Relying party name shown in the platform passkey UI.
*
* @default 'MetaMask'
*/
rpName?: string;
/**
* Optional passkey user name; defaults to `rpName`.
*
* @default 'MetaMask Wallet'
*/
userName?: string;
/**
* Optional display name; defaults to `rpName`.
*
* @default 'MetaMask Wallet'
*/
userDisplayName?: string;
};
export type PasskeyControllerInstanceOptions = Omit<
ConstructorParameters<typeof PasskeyController>[0],
'messenger' | 'state'
>;

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