Skip to content

feat(ramps-controller): accept a JSON moneyHeadlessAllProviders payload carrying provider allowlists - #9524

Merged
saustrie-consensys merged 8 commits into
mainfrom
saustrie/ramps-headless-provider-allowlist
Jul 24, 2026
Merged

feat(ramps-controller): accept a JSON moneyHeadlessAllProviders payload carrying provider allowlists#9524
saustrie-consensys merged 8 commits into
mainfrom
saustrie/ramps-headless-provider-allowlist

Conversation

@saustrie-consensys

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

Copy link
Copy Markdown
Contributor

Explanation

moneyHeadlessAllProviders is currently a boolean remote flag: the literal true widens headless fiat auto-selection quoting to every provider class (#9409). Phase 2 of the Headless Buy all-providers work needs QA to force a specific provider mix (for example external-browser-only) without a client change per test configuration.

This PR widens the flag's value contract so the same LaunchDarkly key can carry provider allowlists:

  • The flag now also accepts an object payload { "enabled": true, "featureVersion": "1", "providerIds": ["/providers/x"] }. isHeadlessAllProvidersEnabled returns true for a payload whose enabled is the literal true and featureVersion is "1"; every other value resolves to false, and the boolean forms behave exactly as before. In LaunchDarkly the variation is delivered inside a versions envelope (e.g. { "versions": { "0.0.0": { "enabled": false, "featureVersion": "1" }, "8.4.0": { "enabled": true, "featureVersion": "1" } } }); RemoteFeatureFlagController resolves that to the flat payload before writing to state, so ramps code always evaluates the flat form.
  • New pure getHeadlessProviderAllowlist(remoteFeatureFlagState) resolves the allowlist from the payload's top-level providerIds; empty or malformed lists resolve to undefined (no restriction). Unknown keys and non-string entries are ignored.
  • When the resolved allowlist is non-empty, the widened pick drops candidates whose provider is not listed, before the existing fiat-limit filter and reliability-then-price ranking. Ids match in prefixed (/providers/x) or bare form, case-insensitively, via a module-local normalizer used only for allowlist matching (provider ids still match as-is everywhere else; the v16 removal of id normalization is not reverted). If nothing survives, getQuotes returns an empty success[] with sorted / error / customActions preserved.
  • The controller resolves the enabled bit and the allowlist from a single RemoteFeatureFlagController:getState read per getQuotes call, so a flag edit during the awaited fetch cannot produce a mixed read.

Backwards compatibility, and why this is a minor (not breaking) change: clients on earlier versions coerce any non-boolean value to false (native-only), so serving the object payload can never turn widening on for a client that cannot parse it, and production serves false everywhere today. No consumer needs code changes to keep working; boolean-only configurations see identical behavior. The documented "any non-boolean resolves to false" contract is widened, not changed, for the values it previously rejected.

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

Note

Medium Risk
Changes which provider wins headless fiat auto-select when the flag carries allowlists; gated by remote flags and backward-compatible for boolean true, but misconfigured LD payloads could yield empty quotes in production.

Overview
Extends moneyHeadlessAllProviders so LaunchDarkly can send an object payload (enabled, featureVersion: '1', optional providerIds, optional minimumVersion) in addition to boolean true. isHeadlessAllProvidersEnabled treats only literal true or a version-matched enabled object as on; other shapes (including { enabled: true } without featureVersion) stay off, while boolean-only configs behave as before.

New helpers getHeadlessProviderAllowlist, getHeadlessAllProvidersMinimumVersion, and HEADLESS_ALL_PROVIDERS_FEATURE_VERSION centralize parsing with the same remote/local override merge. RampsController.getQuotes reads enabled + allowlist in one flag snapshot; on the widened auto-select path, #pickWidenedQuote drops quotes from unlisted providers (case-insensitive match after normalizing /providers/ vs bare ids) before fiat limits and reliability/price ranking. If every candidate is filtered out, success[] is empty while sorted, error, and customActions are unchanged.

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

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

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

@saustrie-consensys
saustrie-consensys force-pushed the saustrie/ramps-headless-provider-allowlist branch from 052c607 to 40d5fb1 Compare July 23, 2026 16:35
@saustrie-consensys

Copy link
Copy Markdown
Contributor Author

@metamaskbot publish-preview

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

@saustrie-consensys
saustrie-consensys force-pushed the saustrie/ramps-headless-provider-allowlist branch from 40d5fb1 to fee1eca Compare July 23, 2026 18:41
…yload

An enabled object payload must now carry featureVersion '1'
(HEADLESS_ALL_PROVIDERS_FEATURE_VERSION) or it resolves to disabled, so
a future contract change cannot be misread by older clients. The new
getHeadlessAllProvidersMinimumVersion helper surfaces the payload's
minimumVersion for clients to validate against their app version with
their own version-gating util; this package performs no version
comparison itself. The boolean true form is unchanged.
@saustrie-consensys
saustrie-consensys force-pushed the saustrie/ramps-headless-provider-allowlist branch from 90c0bdc to 4021b59 Compare July 24, 2026 14:34
Comment on lines +91 to +125
function isEnabledPayload(value: Json | undefined): value is {
[key: string]: Json;
} {
return (
typeof value === 'object' &&
value !== null &&
!Array.isArray(value) &&
value.enabled === true &&
value.featureVersion === HEADLESS_ALL_PROVIDERS_FEATURE_VERSION
);
}

/**
* The `minimumVersion` carried by an enabled payload, or `undefined` for the
* boolean form, a disabled payload, or a malformed field. This package cannot
* compare app versions itself; mobile validates the value through its shared
* `validatedVersionGatedFeatureFlag` util, and the LaunchDarkly `versions`
* wrapper provides the server-side gate.
*
* @param remoteFeatureFlagState - `RemoteFeatureFlagController` state (or the
* relevant subset of it).
* @returns The minimum app version the payload declares, or `undefined`.
*/
export function getHeadlessAllProvidersMinimumVersion(
remoteFeatureFlagState: HeadlessFeatureFlagsLookup | null | undefined,
): string | undefined {
const value = resolveFlagValue(remoteFeatureFlagState);
if (!isEnabledPayload(value)) {
return undefined;
}
const { minimumVersion } = value;
return typeof minimumVersion === 'string' && minimumVersion.trim() !== ''
? minimumVersion
: undefined;
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Do we not get these utils from feature flag controller itself? Looks like we are reinventing the wheel for flag mechanism.

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.

These aren't functions that are already exported in remote-feature-flag-controller's index.ts. I could export it (which would also meaning version bumping remote-feature-flag-controller too instead of just ramps-controller).

transaction-pay-controller faced a similar problem before, and they chose to do it in-line instead of modifying the remote-feature-flag-controller to export a shared utility: https://github.com/MetaMask/core/blob/505289fe98/packages/transaction-pay-controller/src/utils/feature-flags.ts#L791-L802

I'm pro keeping things DRY, but I didn't want to bump remote-feature-flag-controller. I can make the change if you agree with my initial thought process of bumping remote-feature-flag-controller. @amitabh94

@amitabh94 amitabh94 Jul 24, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

We can keep it inline but this should be solved by the @MetaMask/mobile-platform team since multiple teams are running into this.

Maybe we can raise a PR for the platform team to review but I won't block this PR for this then.

@amitabh94

Copy link
Copy Markdown
Contributor

@saustrie-consensys

Copy link
Copy Markdown
Contributor Author

@amitabh94 I just modified the PR body to match the new changes within LaunchDarkly

Comment thread packages/ramps-controller/src/featureFlags.ts Outdated
amitabh94
amitabh94 previously approved these changes Jul 24, 2026
@saustrie-consensys
saustrie-consensys added this pull request to the merge queue Jul 24, 2026
Merged via the queue into main with commit 1d8a963 Jul 24, 2026
49 checks passed
@saustrie-consensys
saustrie-consensys deleted the saustrie/ramps-headless-provider-allowlist branch July 24, 2026 17:27
@saustrie-consensys saustrie-consensys mentioned this pull request Jul 24, 2026
4 tasks
pull Bot pushed a commit to Reality2byte/core that referenced this pull request Jul 24, 2026
## Explanation

Releases `@metamask/ramps-controller@17.2.0` (minor bump).

**Changes in this release:**

- Adds `getHeadlessProviderAllowlist(remoteFeatureFlagState)` helper
resolving the provider-id allowlist from the `moneyHeadlessAllProviders`
flag's object payload.
- Adds `HEADLESS_ALL_PROVIDERS_FEATURE_VERSION` constant (`'1'`) and
`getHeadlessAllProvidersMinimumVersion(remoteFeatureFlagState)` helper
for version gating.
- Widens the `moneyHeadlessAllProviders` flag value contract to accept
`{ enabled: true, featureVersion: "1", providerIds?: string[] }`
alongside the existing boolean form. Old clients coerce the object
payload to `false` (native-only), so serving it cannot turn widening on
for a client that cannot parse it.

Also includes a housekeeping fix to the CHANGELOG and a JSDoc correction
(stale
`surface`/`HEADLESS_ALLOWLIST_SURFACES`/`HeadlessAllowlistSurface`
references that were in the original MetaMask#9524 merge before those were
removed).

## References

- Builds on MetaMask#9524 (`moneyHeadlessAllProviders` object payload support)
- Consumer bump PR: MetaMask/metamask-mobile#33362

## 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)
- [ ] 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]
> **Low Risk**
> Version bumps and documentation/changelog fixes only; no new logic in
the diff.
> 
> **Overview**
> **Release cut** for `@metamask/ramps-controller@17.2.0` and the
monorepo at `1149.0.0`, with `transaction-pay-controller` and the
lockfile bumped to depend on ramps-controller `^17.2.0`.
> 
> The **17.2.0 changelog** is finalized and **corrected** so it matches
the shipped API: object-form `moneyHeadlessAllProviders` is documented
as `{ enabled, featureVersion: "1", providerIds? }` with helpers
`getHeadlessProviderAllowlist` and
`getHeadlessAllProvidersMinimumVersion`, and **stale** references to
per-surface allowlists (`surfaces`, `HEADLESS_ALLOWLIST_SURFACES`,
`getQuotes` `surface` option) are removed. **`featureFlags.ts` JSDoc**
is updated the same way. No runtime code changes appear in this diff
beyond version numbers and docs.
> 
> <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit
c6d5c11. 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 27, 2026
…taMask#9668)

## Explanation

Removes `getHeadlessAllProvidersMinimumVersion` from
`@metamask/ramps-controller`. App-version gating for
`moneyHeadlessAllProviders` is owned by the LaunchDarkly `versions`
wrapper (processed by `RemoteFeatureFlagController`), not a payload
`minimumVersion` field. `featureVersion` fail-closed enablement and
`providerIds` allowlisting are unchanged.

Supersedes MetaMask#9658 (fork PR with CI issues). Changes are identical; this
PR is from the org repo so the merge-queue CI can resolve the branch
correctly.

## References

* Related to MetaMask#9524

## 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
- [ ] I've introduced breaking changes in this PR and have prepared
draft pull requests for clients and consumer packages to resolve them

<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> **Medium Risk**
> Breaking public API removal; consumers importing the helper must
update, though runtime headless enablement and allowlisting behavior in
this package is unchanged.
> 
> **Overview**
> **Breaking:** Drops the exported
`getHeadlessAllProvidersMinimumVersion` helper from
`@metamask/ramps-controller` (implementation, package export, and unit
tests). The unreleased changelog records that **app-version gating** for
`moneyHeadlessAllProviders` is expected to come from the LaunchDarkly
**`versions` wrapper** handled by `RemoteFeatureFlagController`, not
from a `minimumVersion` field on the flag object payload.
> 
> `isHeadlessAllProvidersEnabled`, `getHeadlessProviderAllowlist`, and
`featureVersion` fail-closed behavior are unchanged.
> 
> <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit
aa04b15. Bugbot is set up for automated
code reviews on this repo. Configure
[here](https://www.cursor.com/dashboard/bugbot).</sup>
<!-- /CURSOR_SUMMARY -->
roz0n pushed a commit to MetaMask/metamask-mobile that referenced this pull request Jul 29, 2026
…ampSurface into headless getQuotes (#33362)

## **Description**

Phase 2 of Headless Buy all-providers needs QA to force a specific
provider mix (for example external-browser-only) with only a
LaunchDarkly edit. MetaMask/core#9524 widens the
`moneyHeadlessAllProviders` flag's value contract so the same key also
accepts a JSON payload `{ "enabled": true, "providerIds": [...] }`; when
provider ids are listed, the controller's widened quote pick drops
candidates whose provider is not listed. Old clients coerce the payload
to `false` (native-only), and production serves `false` everywhere, so
serving it cannot enable widening for a client that cannot parse it.

This PR is the mobile half:

- Bump `@metamask/ramps-controller` to consume the new contract. No
mobile flag-interpretation logic is added:
`useHeadlessAllProvidersEnabled` still delegates to the core helper, so
the object form flows through the existing UI availability gates
automatically.

Plan doc:
`app/components/UI/Ramp/headless/PLAN_-_ALL_PROVIDERS_SUPPORT.md`,
milestone P2.M5 (revision in #32906 records the single-flag JSON
design).

## **Changelog**

CHANGELOG entry: null

## **Related issues**

Refs: MetaMask/core#9524

## **Manual testing steps**

1. In the LaunchDarkly dev environment (or via Settings > Feature flag
override once LD serves an object variation), set
`money-headless-all-providers` to `{ "enabled": true, "providerIds":
["/providers/<external-browser-provider>"] }` in a region where an
in-app aggregator normally wins on reliability.
2. Drive an MM Pay money deposit (Money account > Add funds) and confirm
the selected quote comes from the forced provider.
3. Set the flag back to `true` (or remove `providerIds`) and confirm
selection reverts to the reliability winner.
4. Set the flag to `false` (or `{ "enabled": false }`) and confirm the
flow is native-only again.

## **Screenshots/Recordings**

### **Before**

N/A (dev/QA-gated quoting behavior; no UI change)

### **After**

Had to allow temporarily fiat transactions in Perps in my local RC, but
here's an example of perps allowing Coinbase


https://github.com/user-attachments/assets/1b08a4e6-9972-40bb-9d8d-1cc627d8c3ec




## **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**
> Touches MM Pay fiat availability and region gating behind a feature
flag, with a short “optimistic available” window during provider fetch
that could briefly show deposit UI before resolving.
> 
> **Overview**
> Bumps **`@metamask/ramps-controller`** to **^18.0.0** so mobile picks
up the updated **`moneyHeadlessAllProviders`** contract (JSON payload
with **`featureVersion: '1'`** and **`enabled: true`**, plus optional
**`providerIds`** handled in core). **`useHeadlessAllProvidersEnabled`**
still delegates to core’s **`isHeadlessAllProvidersEnabled`**; tests now
cover enabling object payloads, rejecting stale/malformed values, and
keeping local override behavior.
> 
> Renames **`useHasNativeFiatProvider`** to **`useHasFiatProvider`** and
updates MM Pay gates (**`useIsFiatPaymentAvailable`**, mocks) so
naming/docs match flag-aware availability (native-only when the flag is
off, any provider class when on).
> 
> Fixes a **cold provider fetch** regression in
**`useRegionHasFiatProvider`**: when all-providers widening is on and
the catalog is still loading with an empty list after a region switch,
the hook temporarily treats the region as available so add-funds entry
does not disappear until providers load.
> 
> Docs/comments only elsewhere (**`useRampsBuyLimits`**,
**`useFiatConfirm`** notes that **`rampSurface`** is analytics-only).
> 
> <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit
00d1fd2. 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: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
pull Bot pushed a commit to Reality2byte/metamask-mobile that referenced this pull request Jul 29, 2026
…ampSurface into headless getQuotes (MetaMask#33362)

## **Description**

Phase 2 of Headless Buy all-providers needs QA to force a specific
provider mix (for example external-browser-only) with only a
LaunchDarkly edit. MetaMask/core#9524 widens the
`moneyHeadlessAllProviders` flag's value contract so the same key also
accepts a JSON payload `{ "enabled": true, "providerIds": [...] }`; when
provider ids are listed, the controller's widened quote pick drops
candidates whose provider is not listed. Old clients coerce the payload
to `false` (native-only), and production serves `false` everywhere, so
serving it cannot enable widening for a client that cannot parse it.

This PR is the mobile half:

- Bump `@metamask/ramps-controller` to consume the new contract. No
mobile flag-interpretation logic is added:
`useHeadlessAllProvidersEnabled` still delegates to the core helper, so
the object form flows through the existing UI availability gates
automatically.

Plan doc:
`app/components/UI/Ramp/headless/PLAN_-_ALL_PROVIDERS_SUPPORT.md`,
milestone P2.M5 (revision in MetaMask#32906 records the single-flag JSON
design).

## **Changelog**

CHANGELOG entry: null

## **Related issues**

Refs: MetaMask/core#9524

## **Manual testing steps**

1. In the LaunchDarkly dev environment (or via Settings > Feature flag
override once LD serves an object variation), set
`money-headless-all-providers` to `{ "enabled": true, "providerIds":
["/providers/<external-browser-provider>"] }` in a region where an
in-app aggregator normally wins on reliability.
2. Drive an MM Pay money deposit (Money account > Add funds) and confirm
the selected quote comes from the forced provider.
3. Set the flag back to `true` (or remove `providerIds`) and confirm
selection reverts to the reliability winner.
4. Set the flag to `false` (or `{ "enabled": false }`) and confirm the
flow is native-only again.

## **Screenshots/Recordings**

### **Before**

N/A (dev/QA-gated quoting behavior; no UI change)

### **After**

Had to allow temporarily fiat transactions in Perps in my local RC, but
here's an example of perps allowing Coinbase


https://github.com/user-attachments/assets/1b08a4e6-9972-40bb-9d8d-1cc627d8c3ec




## **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**
> Touches MM Pay fiat availability and region gating behind a feature
flag, with a short “optimistic available” window during provider fetch
that could briefly show deposit UI before resolving.
> 
> **Overview**
> Bumps **`@metamask/ramps-controller`** to **^18.0.0** so mobile picks
up the updated **`moneyHeadlessAllProviders`** contract (JSON payload
with **`featureVersion: '1'`** and **`enabled: true`**, plus optional
**`providerIds`** handled in core). **`useHeadlessAllProvidersEnabled`**
still delegates to core’s **`isHeadlessAllProvidersEnabled`**; tests now
cover enabling object payloads, rejecting stale/malformed values, and
keeping local override behavior.
> 
> Renames **`useHasNativeFiatProvider`** to **`useHasFiatProvider`** and
updates MM Pay gates (**`useIsFiatPaymentAvailable`**, mocks) so
naming/docs match flag-aware availability (native-only when the flag is
off, any provider class when on).
> 
> Fixes a **cold provider fetch** regression in
**`useRegionHasFiatProvider`**: when all-providers widening is on and
the catalog is still loading with an empty list after a region switch,
the hook temporarily treats the region as available so add-funds entry
does not disappear until providers load.
> 
> Docs/comments only elsewhere (**`useRampsBuyLimits`**,
**`useFiatConfirm`** notes that **`rampSurface`** is analytics-only).
> 
> <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit
00d1fd2. 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: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
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