Skip to content

feat(ramps-controller): consume moneyHeadlessAllProviders flag controller-side; share provider/quote/error helpers#9409

Merged
saustrie-consensys merged 3 commits into
mainfrom
saustrie/ramps-controller-ui-logic
Jul 13, 2026
Merged

feat(ramps-controller): consume moneyHeadlessAllProviders flag controller-side; share provider/quote/error helpers#9409
saustrie-consensys merged 3 commits into
mainfrom
saustrie/ramps-controller-ui-logic

Conversation

@saustrie-consensys

@saustrie-consensys saustrie-consensys commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

Explanation

Headless Buy provider expansion previously reached RampsController through an injected getProviderScope callback returning a multivariate scope (off | in-app | all), and metamask-mobile re-derived gating logic the controller already owns privately. This PR replaces the scope concept with a single boolean remote feature flag that the controller reads itself, and lifts the shared logic into pure, tested, exported helpers so consumers cannot drift from the controller's own selection.

Flag semantics (moneyHeadlessAllProviders, boolean; this is the client-side key, which the ClientConfig API camelizes from the kebab-case LaunchDarkly dashboard key money-headless-all-providers):

  • true: the auto-selection quote path (autoSelectProvider / restrictToKnownOrNativeProviders) widens to every supporting provider class: native, in-app WebView aggregator, and external-browser / custom-action. The best quote is returned at success[0], enforcing per-provider fiat limits.
  • false, missing, or any non-boolean value: today's native-only behavior. The intermediate in-app scope is removed, along with the exported ProviderScope type and the getProviderScope constructor option (breaking).

Controller-side consumption:

  • RemoteFeatureFlagController:getState is added to the controller's allowed messenger actions (same pattern as ConfigRegistryController / bridge-controller), and a private #isAllProvidersEnabled() resolves the flag per getQuotes call, so a remote fetch or a local dev override takes effect at runtime. The read fails closed: if the action is not delegated, quoting stays native-only.
  • New featureFlags module exports MONEY_HEADLESS_ALL_PROVIDERS_FLAG_KEY and the pure isHeadlessAllProvidersEnabled(remoteFeatureFlagState) helper (plus a HeadlessFeatureFlagsLookup type). The helper owns the key lookup, the localOverrides-over-remoteFeatureFlags merge (mobile's Settings > Feature flag override screen writes localOverrides, and published RemoteFeatureFlagController versions such as 4.2.2 do not fold overrides into remoteFeatureFlags state), and defensive coercion: only the literal boolean true enables. Mobile UI hooks call the same helper, so the UI gate and the controller resolve the flag identically.

Shared pure helpers (consumed by the controller internally so they stay behaviourally identical to its own selection):

  • providerAvailability: providerServesAsset, getProvidersServingAsset, regionHasProviderForAsset, isFiatDepositAvailable. The latter two take an allProvidersEnabled: boolean instead of a scope.
  • quoteClassification: isExternalBrowserQuote, isCustomActionQuote, isInAppOnlyQuote.
  • errorNormalization: getErrorMessage, extractExplicitTypedError, normalizeToTypedError (+ TypedError<Code>). The deliberately host-specific bits (deeplink schemes, redirect URLs, session lifecycle, error taxonomy values) stay in the consumer.

All 601 package tests pass with 100% coverage, including: widening on/off via the flag, localOverrides honored in both directions, garbage flag values coerced to false, an unwired RemoteFeatureFlagController:getState failing closed, and per-call flag reads picking up runtime changes.

References

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

Made with Cursor

…mers

Expose the provider-availability, quote-classification, and error-normalization
logic the controller uses internally as pure, tested helpers so headless-buy
consumers (e.g. metamask-mobile) can share it instead of re-deriving it:

- providerServesAsset, getProvidersServingAsset, regionHasProviderForAsset,
  isFiatDepositAvailable
- isExternalBrowserQuote, isCustomActionQuote, isInAppOnlyQuote
- getErrorMessage, extractExplicitTypedError, normalizeToTypedError (+ TypedError)

RampsController now derives its own region asset-matching and quote filtering
from these helpers so the shared surface stays behaviourally identical to the
controller's selection.
@saustrie-consensys saustrie-consensys force-pushed the saustrie/ramps-controller-ui-logic branch from 75ef2c5 to 685219b Compare July 7, 2026 14:59
@saustrie-consensys

Copy link
Copy Markdown
Contributor Author

@metamaskbot publish-preview

@github-actions

github-actions Bot commented Jul 7, 2026

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.3-preview-685219bbf
@metamask-previews/accounts-controller@39.0.4-preview-685219bbf
@metamask-previews/address-book-controller@7.1.2-preview-685219bbf
@metamask-previews/ai-controllers@0.7.0-preview-685219bbf
@metamask-previews/analytics-controller@1.2.1-preview-685219bbf
@metamask-previews/analytics-data-regulation-controller@0.0.0-preview-685219bbf
@metamask-previews/announcement-controller@8.1.0-preview-685219bbf
@metamask-previews/app-metadata-controller@2.0.1-preview-685219bbf
@metamask-previews/approval-controller@9.0.2-preview-685219bbf
@metamask-previews/assets-controller@10.0.1-preview-685219bbf
@metamask-previews/assets-controllers@109.3.0-preview-685219bbf
@metamask-previews/authenticated-user-storage@3.0.0-preview-685219bbf
@metamask-previews/base-controller@9.1.0-preview-685219bbf
@metamask-previews/base-data-service@0.1.3-preview-685219bbf
@metamask-previews/bitcoin-regtest-up@1.0.0-preview-685219bbf
@metamask-previews/bridge-controller@77.3.2-preview-685219bbf
@metamask-previews/bridge-status-controller@74.0.2-preview-685219bbf
@metamask-previews/build-utils@3.0.4-preview-685219bbf
@metamask-previews/chain-agnostic-permission@1.7.0-preview-685219bbf
@metamask-previews/chomp-api-service@3.1.0-preview-685219bbf
@metamask-previews/claims-controller@0.5.3-preview-685219bbf
@metamask-previews/client-controller@1.0.1-preview-685219bbf
@metamask-previews/client-utils@0.0.0-preview-685219bbf
@metamask-previews/compliance-controller@2.1.0-preview-685219bbf
@metamask-previews/composable-controller@12.0.1-preview-685219bbf
@metamask-previews/config-registry-controller@0.4.1-preview-685219bbf
@metamask-previews/connectivity-controller@0.2.0-preview-685219bbf
@metamask-previews/controller-utils@12.3.0-preview-685219bbf
@metamask-previews/core-backend@6.5.0-preview-685219bbf
@metamask-previews/delegation-controller@3.0.2-preview-685219bbf
@metamask-previews/earn-controller@12.2.2-preview-685219bbf
@metamask-previews/eip-5792-middleware@3.0.4-preview-685219bbf
@metamask-previews/eip-7702-internal-rpc-middleware@0.1.1-preview-685219bbf
@metamask-previews/eip1193-permission-middleware@2.0.1-preview-685219bbf
@metamask-previews/ens-controller@19.1.5-preview-685219bbf
@metamask-previews/eth-block-tracker@15.0.1-preview-685219bbf
@metamask-previews/eth-json-rpc-middleware@23.1.3-preview-685219bbf
@metamask-previews/eth-json-rpc-provider@6.0.1-preview-685219bbf
@metamask-previews/foundryup@1.0.1-preview-685219bbf
@metamask-previews/gas-fee-controller@26.2.4-preview-685219bbf
@metamask-previews/gator-permissions-controller@4.2.2-preview-685219bbf
@metamask-previews/geolocation-controller@0.1.3-preview-685219bbf
@metamask-previews/java-tron-up@1.0.0-preview-685219bbf
@metamask-previews/json-rpc-engine@10.5.0-preview-685219bbf
@metamask-previews/json-rpc-middleware-stream@8.0.8-preview-685219bbf
@metamask-previews/keyring-controller@27.1.0-preview-685219bbf
@metamask-previews/local-node-utils@1.0.0-preview-685219bbf
@metamask-previews/logging-controller@8.0.2-preview-685219bbf
@metamask-previews/message-manager@14.1.2-preview-685219bbf
@metamask-previews/messenger@2.0.0-preview-685219bbf
@metamask-previews/messenger-cli@0.2.0-preview-685219bbf
@metamask-previews/money-account-api-data-service@0.0.0-preview-685219bbf
@metamask-previews/money-account-balance-service@2.1.2-preview-685219bbf
@metamask-previews/money-account-controller@0.3.3-preview-685219bbf
@metamask-previews/money-account-upgrade-controller@2.2.1-preview-685219bbf
@metamask-previews/multichain-account-service@11.1.0-preview-685219bbf
@metamask-previews/multichain-api-middleware@4.0.1-preview-685219bbf
@metamask-previews/multichain-network-controller@3.2.1-preview-685219bbf
@metamask-previews/multichain-transactions-controller@7.1.1-preview-685219bbf
@metamask-previews/name-controller@9.1.2-preview-685219bbf
@metamask-previews/network-controller@34.0.0-preview-685219bbf
@metamask-previews/network-enablement-controller@5.4.1-preview-685219bbf
@metamask-previews/notification-services-controller@25.0.0-preview-685219bbf
@metamask-previews/passkey-controller@2.0.1-preview-685219bbf
@metamask-previews/permission-controller@13.1.1-preview-685219bbf
@metamask-previews/permission-log-controller@5.1.0-preview-685219bbf
@metamask-previews/perps-controller@9.2.1-preview-685219bbf
@metamask-previews/phishing-controller@17.2.0-preview-685219bbf
@metamask-previews/platform-api-docs@0.0.0-preview-685219bbf
@metamask-previews/polling-controller@16.0.8-preview-685219bbf
@metamask-previews/preferences-controller@23.1.0-preview-685219bbf
@metamask-previews/profile-metrics-controller@4.0.1-preview-685219bbf
@metamask-previews/profile-sync-controller@28.2.0-preview-685219bbf
@metamask-previews/ramps-controller@15.1.0-preview-685219bbf
@metamask-previews/rate-limit-controller@7.0.1-preview-685219bbf
@metamask-previews/react-data-query@0.2.1-preview-685219bbf
@metamask-previews/remote-feature-flag-controller@4.2.2-preview-685219bbf
@metamask-previews/sample-controllers@5.0.3-preview-685219bbf
@metamask-previews/seedless-onboarding-controller@10.0.3-preview-685219bbf
@metamask-previews/selected-network-controller@26.1.5-preview-685219bbf
@metamask-previews/shield-controller@5.1.2-preview-685219bbf
@metamask-previews/signature-controller@39.2.7-preview-685219bbf
@metamask-previews/smart-transactions-controller@24.2.4-preview-685219bbf
@metamask-previews/snap-account-service@1.0.0-preview-685219bbf
@metamask-previews/social-controllers@2.3.1-preview-685219bbf
@metamask-previews/solana-test-validator-up@1.0.0-preview-685219bbf
@metamask-previews/stellar-quickstart-up@0.0.0-preview-685219bbf
@metamask-previews/storage-service@1.0.2-preview-685219bbf
@metamask-previews/subscription-controller@6.2.0-preview-685219bbf
@metamask-previews/transaction-controller@68.2.2-preview-685219bbf
@metamask-previews/transaction-pay-controller@23.17.4-preview-685219bbf
@metamask-previews/user-operation-controller@41.2.6-preview-685219bbf
@metamask-previews/wallet@7.0.0-preview-685219bbf
@metamask-previews/wallet-cli@0.0.0-preview-685219bbf

@saustrie-consensys

Copy link
Copy Markdown
Contributor Author

@metamaskbot publish-preview

@saustrie-consensys saustrie-consensys changed the title feat(ramps-controller): share provider/quote/error helpers with consumers feat(ramps-controller): consume moneyHeadlessAllProviders flag controller-side; share provider/quote/error helpers Jul 11, 2026
@saustrie-consensys saustrie-consensys marked this pull request as ready for review July 13, 2026 12:55
@saustrie-consensys saustrie-consensys requested review from a team as code owners July 13, 2026 12:55
@saustrie-consensys saustrie-consensys self-assigned this Jul 13, 2026
…ller-ui-logic

# Conflicts:
#	packages/ramps-controller/CHANGELOG.md
#	packages/ramps-controller/package.json
#	packages/ramps-controller/src/RampsController.test.ts
#	packages/ramps-controller/src/RampsController.ts
#	yarn.lock
@saustrie-consensys

Copy link
Copy Markdown
Contributor Author

@metamaskbot publish-preview

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

@saustrie-consensys saustrie-consensys added this pull request to the merge queue Jul 13, 2026
Merged via the queue into main with commit c7d8bc0 Jul 13, 2026
421 checks passed
@saustrie-consensys saustrie-consensys deleted the saustrie/ramps-controller-ui-logic branch July 13, 2026 13:48
@saustrie-consensys saustrie-consensys mentioned this pull request Jul 13, 2026
4 tasks
saustrie-consensys added a commit to MetaMask/metamask-mobile that referenced this pull request Jul 13, 2026
… LD granular control (#33170)

## **Description**

Docs-only update to the Headless Buy all-providers plan
(`app/components/UI/Ramp/headless/PLAN_-_ALL_PROVIDERS_SUPPORT.md`).

Stacked on #32906 (base branch `docs/headless-buy-phase2-update`)
because both PRs edit the same file; this one layers a Phase 1 accuracy
pass and a new Phase 2 LaunchDarkly design on top of that PR's Phase 2
rewrites.

### Phase 1 accuracy pass

Phase 1 now describes the implementation as it exists across core #9353
(merged), mobile #32810 (merged), mobile #32682 (in review), and the
core #9409 / mobile #32890 rework (in review):

- The multivariate scope design (`moneyHeadlessProviderScope`, `off |
in-app | all`, the `ProviderScope` type, the `getProviderScope`
constructor injection, the persisted `fiatOrders.providerScope` Redux
setting, `getEffectiveProviderScope` / `useFiatProviderScope`, and the
HeadlessPlayground provider-scope toggle) is gone. The gate is one
boolean LaunchDarkly flag, `moneyHeadlessAllProviders`, consumed by
`RampsController` itself: a per-call
`RemoteFeatureFlagController:getState` read that fails closed, where
only the literal `true` widens to all provider classes and the dev
override is Settings > Feature flag override via `localOverrides`. P1.M2
is retitled and rewritten for this.
- P1.M0 (`failSession` terminal) is marked done (mobile #32810), and
P1.M1 is rewritten as the widened quoting capability that shipped
(`#pickWidenedQuote`, fiat-limit enforcement, default redirect URL,
relaxed availability gates).
- Stale scope references were fixed everywhere they appeared (intro,
checklist, Background, Design principles, Quote-layer, Architecture
diagram, Phase 2 preconditions, P2.M4, P2.M6, P2.M8, Deferred, Test
plan, Open risks), including the `#pickInAppQuote` to
`#pickWidenedQuote` rename and the removal of the in-app pre-filter.
- Phase 1 gets a single acceptance criterion: a user can complete a
Transak (Aggregator) buy in Brazil end to end through the headless flow.

### Phase 2: granular LaunchDarkly control (P2.M5 rework)

The old P2.M5 ("widen via the flag's `all` value") was obsolete: there
is no `all` value and the boolean already covers widening. It is
reworked into a granular LaunchDarkly control design: an optional
`moneyHeadlessProviderAllowlists` companion JSON flag carrying
provider-id allowlists, with optional per-surface lists (money account,
perps, predictions). After one-time client support lands, QA can force
specific provider mixes (for example external-browser-only) purely by
editing the flag in the LD dashboard, with no code change per test
configuration.

## **Changelog**

CHANGELOG entry: null

## **Related issues**

Refs: MetaMask/core#9409
Refs: #32890
Refs: #32906

## **Manual testing steps**

N/A. Docs-only change to a planning markdown file; no code paths are
affected.

## **Screenshots/Recordings**

N/A

### **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)

- [ ] I've tested on Android
  - Ideally on a mid-range device; emulator is acceptable
- [ ] 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
- [ ] 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.

<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> **Low Risk**
> Markdown planning doc only; no application code, flags, or tests are
modified.
> 
> **Overview**
> Updates **`PLAN_-_ALL_PROVIDERS_SUPPORT.md`** so Phase 1 matches
shipped/in-review work and Phase 2 documents the next flag design—no
runtime behavior changes.
> 
> **Phase 1** is reframed from multivariate scope (`off | in-app | all`)
and in-app-only quoting to the boolean **`moneyHeadlessAllProviders`**
read in **`RampsController`** (literal `true` widens; everything else
fails closed). The doc drops **`ProviderScope`**, Redux
**`providerScope`**, and playground toggles; marks **P1.M0** done;
rewrites **P1.M1** around **`#pickWidenedQuote`**, fiat limits, default
redirect URL, and relaxed gates; and adds a Brazil Transak end-to-end
acceptance criterion.
> 
> **Phase 2 P2.M5** is reworked from “flip flag to `all`” into optional
companion **`moneyHeadlessProviderAllowlists`** JSON (top-level and
per-surface provider lists) so QA can constrain mixes via LaunchDarkly
without per-config releases. Related sections (**P2.M6**, **P2.M8**,
test plan, risks) are updated for flag-only gating and
**`#pickWidenedQuote`** naming.
> 
> <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit
d163c06. Bugbot is set up for automated
code reviews on this repo. Configure
[here](https://www.cursor.com/dashboard/bugbot).</sup>
<!-- /CURSOR_SUMMARY -->
pull Bot pushed a commit to dmrazzy/core that referenced this pull request Jul 13, 2026
## Explanation

Releases `@metamask/ramps-controller` 17.0.0.

- **BREAKING:** `RampsController` now reads the
`moneyHeadlessAllProviders` boolean remote feature flag itself, through
the `RemoteFeatureFlagController:getState` messenger action resolved on
each auto-selection `getQuotes` call. This replaces the
`getProviderScope` constructor option and the exported `ProviderScope`
type. Consumers should delegate that action to the controller's
messenger; when it is missing, the read fails closed and quoting stays
native-only.
- When the flag is `true`, the auto-selection quote path
(`autoSelectProvider` / `restrictToKnownOrNativeProviders`) widens to
every supporting provider class (native, in-app WebView aggregator, and
external-browser / custom-action) and returns the best quote at
`success[0]`, enforcing per-provider fiat limits.
- Adds exported pure helpers so consumers resolve gating and
classification exactly like the controller:
`MONEY_HEADLESS_ALL_PROVIDERS_FLAG_KEY` /
`isHeadlessAllProvidersEnabled`, provider-availability helpers
(`providerServesAsset`, `getProvidersServingAsset`,
`regionHasProviderForAsset`, `isFiatDepositAvailable`),
quote-classification helpers (`isExternalBrowserQuote`,
`isCustomActionQuote`, `isInAppOnlyQuote`), and error-normalization
helpers (`getErrorMessage`, `extractExplicitTypedError`,
`normalizeToTypedError`).
- Adds `@metamask/remote-feature-flag-controller` `^4.2.2` as a runtime
dependency.

`@metamask/transaction-pay-controller` is intentionally not released
here. Everything it consumes from this package (`Quote`, `RampsOrder`,
`RampsOrderStatus`) is unchanged since 16.0.0, and its runtime
interaction goes through the messenger to the client-instantiated
controller. Its workspace dependency range moves to `^17.0.0` in this PR
via yarn constraints, with no version bump; the published range update
ships with its next release.

## References

- Sole change in this release: MetaMask#9409
- Consumer PR adopting the breaking change:
MetaMask/metamask-mobile#32890

## Checklist

- [x] I've updated the test suite for new or updated code as appropriate
- [x] I've updated documentation (JSDoc, Markdown, etc.) for new or
updated code as appropriate
- [x] I've communicated my changes to consumers by [updating changelogs
for packages I've
changed](https://github.com/MetaMask/core/tree/main/docs/processes/updating-changelogs.md)
- [x] I've introduced [breaking
changes](https://github.com/MetaMask/core/tree/main/docs/processes/breaking-changes.md)
in this PR and have prepared draft pull requests for clients and
consumer packages to resolve them

<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> **Medium Risk**
> The published ramps-controller major is breaking for hosts that still
pass `getProviderScope` or rely on `in-app` scope; this PR only versions
and documents that release, with limited direct runtime change in-repo
beyond dependency ranges.
> 
> **Overview**
> **Monorepo release 1119.0.0** cuts **`@metamask/ramps-controller`
17.0.0** (from 16.0.0) and records that release in its changelog. The
diff itself is versioning and lockfile alignment, not new controller
logic.
> 
> **`@metamask/transaction-pay-controller`** is updated only as a
workspace consumer: dependency range moves to **`^17.0.0`** with an
Unreleased changelog note; its package version stays **24.0.3** (no TPC
publish in this PR).
> 
> What ships in **ramps-controller 17.0.0** (documented in the new
changelog section): **breaking** removal of **`getProviderScope`** /
**`ProviderScope`** in favor of the **`moneyHeadlessAllProviders`**
remote feature flag via **`RemoteFeatureFlagController:getState`**;
auto-selection widens to all provider classes when the flag is true;
plus exported pure helpers for flag resolution, provider availability,
quote classification, and error normalization, and a
**`remote-feature-flag-controller`** runtime dependency.
> 
> <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit
26ff4f0. 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: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
saustrie-consensys added a commit to MetaMask/metamask-mobile that referenced this pull request Jul 13, 2026
…AllProviders remote flag (#32890)

## **Description**

Wires the Headless Buy provider expansion to a remote LaunchDarkly flag
so it can be activated without shipping a code change, completing the
"flag-gated activation" step from the all-providers plan
(`app/components/UI/Ramp/headless/PLAN_-_ALL_PROVIDERS_SUPPORT.md`).

Stacked on #32682 (Phase 1). Phase 1 gates the widening purely
client-side: a `fiatOrders.providerScope` Redux setting plus a hardcoded
production hard-off inside `getEffectiveProviderScope`. This PR replaces
all of that with a single boolean flag consumed by the controller
itself:

- The flag is `moneyHeadlessAllProviders` (boolean). `true` widens the
headless fiat quote path to every provider class (native, in-app WebView
aggregator, external-browser / custom-action); `false` or missing keeps
the native-only default, in every environment including production. The
intermediate `in-app` scope from the earlier multivariate design is
removed.
- `RampsController` (core PR MetaMask/core#9409, released as
`@metamask/ramps-controller@17.0.0` and consumed here as `^17.0.0`; the
earlier preview-build pin is removed) now reads the flag itself through
the `RemoteFeatureFlagController:getState` messenger action per quote
call. The `getProviderScope` injection in `ramps-controller-init.ts` is
gone; the ramps controller messenger delegates
`RemoteFeatureFlagController:getState`.
- UI hooks (`useHasNativeFiatProvider`, `useRegionHasFiatProvider`)
derive the boolean via a thin `useHeadlessAllProvidersEnabled` hook that
calls the core-exported `isHeadlessAllProvidersEnabled` helper against
`RemoteFeatureFlagController` state, then feed it into the core-exported
availability helpers. No flag interpretation logic lives in mobile.
- Deleted: the `ProviderScope` / `FiatProviderScope` types and every
scope-shaped API; the persisted `fiatOrders.providerScope` setting
(action, reducer case, selector, state key); `getEffectiveProviderScope`
/ `useFiatProviderScope`; the `selectMoneyHeadlessProviderScope`
selector directory; and the HeadlessPlayground provider-scope toggle
(including its locale strings).
- Dev override: the core helper merges `localOverrides` over
`remoteFeatureFlags`, so flipping `moneyHeadlessAllProviders` in
Settings > Feature flag override (non-production builds) overrides the
remote value for both the UI gates and the controller's quote widening.
The playground toggle is no longer needed.
- No state migration: `fiatOrders.providerScope` was introduced on the
unmerged Phase 1 branch (#32682) and never shipped in a release, so no
persisted production state references it; a stale key from dev/RC
installs is carried harmlessly and read by nothing.

### LaunchDarkly flag (dashboard changes handled separately)

- Create dashboard key `money-headless-all-providers`: boolean, default
variation `false`. Serve `false` in Production for now; the client also
resolves to native-only if the flag is missing or non-boolean.
- Key casing: LaunchDarkly dashboard keys are kebab-case and the
ClientConfig API serves them to clients camelized, so the code reads
`moneyHeadlessAllProviders` (same convention as existing flags; see the
naming table in `docs/perps/perps-feature-flags.md`).
- Retire the previously planned multivariate
`moneyHeadlessProviderScope` (string `off` | `in-app` | `all`); no
client reads it after this PR.

## **Changelog**

<!-- Dev/RC-gated, production hard-off by default; not end-user-facing
yet. -->

CHANGELOG entry: null

## **Related issues**

Refs: #32682

## **Manual testing steps**

```gherkin
Feature: Remote activation of Headless Buy all-providers widening

  Scenario: flag true widens to all providers
    Given a dev/RC build
    And the moneyHeadlessAllProviders flag resolves to true (remote or via override)
    When the user runs a Money "Add funds" deposit in an aggregator-only region
    Then aggregator providers are quoted and the aggregator WebView flow opens
    (the flow is no longer native-only)

  Scenario: flag false or missing stays native-only
    Given the moneyHeadlessAllProviders flag is false or not served
    When the user runs a Money "Add funds" deposit
    Then only native providers are offered, in every environment

  Scenario: dev override via Settings
    Given a non-production build with the remote flag false or unset
    When the user opens Settings > Feature flag override
    And sets moneyHeadlessAllProviders to true
    Then the widened all-providers flow is active (localOverrides wins for both
    the UI availability gates and the controller quote widening)
```

## **Screenshots/Recordings**

N/A. No user-facing UI change; behavior is identical to Phase 1 until
the flag is served `true`.

### **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)

- [ ] I've tested on Android
  - Ideally on a mid-range device; emulator is acceptable
- [ ] 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
- [ ] 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.


<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> **Medium Risk**
> Changes how Headless Buy widens provider quoting and deposit
eligibility gates across Money flows; mis-flagging could expose
aggregator paths in production, though default-off and shared core
helpers reduce UI/controller drift risk.
> 
> **Overview**
> Replaces the dev/RC **provider scope** Redux toggle (`off` / `in-app`
/ `all`) with a single remote boolean **`moneyHeadlessAllProviders`**,
aligned with `@metamask/ramps-controller@17.0.0`.
> 
> **Controller & wiring:** `RampsController` no longer receives
`getProviderScope` from mobile init; it reads the flag via
**`RemoteFeatureFlagController:getState`** (new messenger delegation).
**`getDefaultRedirectUrl`** for widened aggregator quotes is unchanged.
> 
> **UI gates:** New **`useHeadlessAllProvidersEnabled`** binds Redux
feature-flag state to core **`isHeadlessAllProvidersEnabled`** (supports
Settings **`localOverrides`**). **`useHasNativeFiatProvider`** and
**`useRegionHasFiatProvider`** delegate to core
**`isFiatDepositAvailable`** / **`regionHasProviderForAsset`** so UI and
controller stay in sync. **`getAggregatorRedirectConfig`** uses core
**`isExternalBrowserQuote`**; **`toHeadlessBuyError`** uses shared
**`extractExplicitTypedError`**.
> 
> **Removed:** `fiatOrders.providerScope`, `providerScope`
utils/hooks/tests, Headless Playground scope UI + locale strings, and
related reducer tests.
> 
> Default behavior stays **native-only** until the flag is literally
`true` (remote or override).
> 
> <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit
b91c61d. Bugbot is set up for automated
code reviews on this repo. Configure
[here](https://www.cursor.com/dashboard/bugbot).</sup>
<!-- /CURSOR_SUMMARY -->
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.

2 participants