Skip to content

feat(wallet): wire RemoteFeatureFlagController into default initialization#8969

Open
sirtimid wants to merge 6 commits into
mainfrom
sirtimid/wire-remote-feature-flag-controller
Open

feat(wallet): wire RemoteFeatureFlagController into default initialization#8969
sirtimid wants to merge 6 commits into
mainfrom
sirtimid/wire-remote-feature-flag-controller

Conversation

@sirtimid
Copy link
Copy Markdown
Contributor

@sirtimid sirtimid commented Jun 2, 2026

Explanation

This wires RemoteFeatureFlagController into @metamask/wallet's default controller initialization, so the wallet manages remote feature flags as part of its ensemble. It exposes the controller's standard messenger surface — RemoteFeatureFlagController:getState/:stateChange plus its method-actions (updateRemoteFeatureFlags, enable, disable, setFlagOverride, removeFlagOverride, clearAllFlagOverrides).

@metamask/wallet is the shared controller-integration layer for metamask-extension, metamask-mobile, and wallet-cli. The controller's constructor values that differ between those clients are therefore injectable via a new instanceOptions.remoteFeatureFlagController slot rather than hardcoded, each with a platform-agnostic default so a headless consumer can construct the wallet with no options at all. Values identical everywhere are not exposed.

The controller's messenger is a plain namespaced child with no delegation — the controller's own messenger type only allows its own actions/events, and both clients already construct it that way. The per-client orchestration that reads PreferencesController/OnboardingController (extension) or a basic-functionality selector (mobile) to enable/disable the controller is not part of the controller and is not delegated here: those sources aren't wallet controllers (mobile's isn't even a controller). Clients keep that orchestration in their own glue and drive it over the shared messenger via the exposed RemoteFeatureFlagController:enable/:disable actions; the wallet only takes the initial disabled value and a getMetaMetricsId callback as injectable options.

Note for the owning teams: when no clientConfigApiService is injected, the controller is wired with a network-free default that returns an empty flag set (so wallet-cli works headlessly). A client that intends to fetch flags but forgets to inject its service would therefore silently get zero flags rather than an error. Extension and mobile always inject a real ClientConfigApiService, so this only affects deliberately headless consumers — flagging it so the choice of "optional with inert default" vs. "required" is a conscious one. This mirrors the ApprovalController slot's no-op default.

Per-environment options

Option Extension Mobile Default (e.g. wallet-cli)
clientConfigApiService new ClientConfigApiService({ fetch, config: { client: Extension, distribution, environment } }) new ClientConfigApiService({ fetch, config: { client: Mobile, environment, distribution } }) network-free service that returns no flags
getMetaMetricsId () => MetaMetricsController:getMetaMetricsId () => analyticsId () => ''
clientVersion getBaseSemVerVersion() getBaseSemVerVersion() '0.0.0'
prevClientVersion persistedState.AppMetadataController.currentAppVersion persistedState.AppMetadataController.currentAppVersion undefined
fetchInterval 15 * 60 * 1000 __DEV__ ? 1000 : DEFAULT_FETCH_INTERVAL controller default (1 day)
disabled !completedOnboarding || !useExternalServices !selectBasicFunctionalityEnabled(state) controller default (false)

The dynamic enable/disable toggling (subscribing to the sources above and calling enable/disable) stays client-side; only the initial disabled value is an option here.

References

Current client construction sites (live main):

Closes #8794.

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, highlighting breaking changes as necessary
  • I've prepared draft pull requests for clients and consumer packages to resolve any breaking changes (N/A — additive)

🤖 Generated with Claude Code


Note

Medium Risk
Changes how shared wallets expose remote feature flags; misconfigured clients that omit clientConfigApiService get empty flags silently, and duplicate controller wiring in consumers could collide like ApprovalController.

Overview
@metamask/wallet now builds RemoteFeatureFlagController as part of default initialization and exposes its messenger actions (getState, updateRemoteFeatureFlags, overrides, etc.) on the shared wallet messenger.

A new instanceOptions.remoteFeatureFlagController slot forwards platform-specific settings (clientConfigApiService, getMetaMetricsId, clientVersion, prevClientVersion, fetchInterval, disabled) with headless-safe defaults: a no-network config service that returns no flags, empty metrics id, and 0.0.0 SemVer. prevClientVersion vs clientVersion drives cache invalidation on app upgrades. Runtime enable/disable from extension/mobile preferences stays client-side via messenger actions, not wallet wiring.

Adds @metamask/remote-feature-flag-controller dependency, initialization module + types, unit and Wallet integration tests, changelog entry, README dependency edge, and CODEOWNERS for the new init path.

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

sirtimid and others added 2 commits June 2, 2026 13:17
…ization

Adds `RemoteFeatureFlagController` to the wallet's default controller
ensemble, exposing per-platform constructor values through a new
`instanceOptions.remoteFeatureFlagController` slot: `clientConfigApiService`,
`getMetaMetricsId`, `clientVersion`, `prevClientVersion`, `fetchInterval`,
and `disabled`. Each is injectable with an inert/neutral default so the
controller is usable headlessly; extension and mobile inject their own
values. The controller's messenger is a plain namespaced child with no
delegation.

`prevClientVersion` lets consumers trigger feature-flag cache invalidation
when the client version changes between sessions.

Closes #8794

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@sirtimid sirtimid marked this pull request as ready for review June 2, 2026 12:01
@sirtimid sirtimid requested a review from a team as a code owner June 2, 2026 12:01
@sirtimid sirtimid temporarily deployed to default-branch June 2, 2026 12:01 — with GitHub Actions Inactive
@sirtimid sirtimid requested review from a team and FrederikBolding June 2, 2026 15:14
sirtimid and others added 3 commits June 2, 2026 18:11
…feature-flag-controller

# Conflicts:
#	packages/wallet/CHANGELOG.md
…tureFlagController

Migrates the RemoteFeatureFlagController instance to the per-controller
directory convention (introduced by #8953, extended by #8977):
`instances/remote-feature-flag-controller/` now holds the config, the
colocated test, and a `RemoteFeatureFlagControllerInstanceOptions` type in
its own `types.ts`. `InstanceSpecificOptions` references that type instead of
an inline shape, and `instances/index.ts` + the CODEOWNERS `## Initialization`
entry use the directory form. No public exports or option shapes change.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…g-controller' into sirtimid/wire-remote-feature-flag-controller
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.

wallet: Pass prevClientVersion to RemoteFeatureFlagController

1 participant