feat(ramps-controller): scope-aware in-app provider widening in getQuotes#9353
Conversation
…otes Add an optional `getProviderScope` callback to `RampsControllerOptions` (exported `ProviderScope` = 'off' | 'in-app' | 'all', read per getQuotes call). When the scope is non-'off', the native-only auto-selection path (autoSelectProvider / restrictToKnownOrNativeProviders) widens to every supporting provider and returns the best in-app quote at success[0]. `#pickInAppQuote` drops external-browser (buyWidget.browser 'IN_APP_OS_BROWSER') and custom-action quotes, enforces per-provider fiat limits up front, and orders by reliability then price off `sorted`. It also reuses the existing getProviders hydration fallback so an empty catalog does not silently quote nothing. The default stays native-only, explicit-`providers` callers are untouched, and providers.selected is never mutated. A host can now bump MM Pay's getRampsQuote (which reads success[0]) onto in-app providers with no transaction-pay-controller change.
|
@metamaskbot publish-preview |
|
Preview builds have been published. Learn how to use preview builds in other projects. Expand for full list of packages and versions. |
… link - Add tests for the remaining getQuotes/#pickInAppQuote branches to restore the package's 100% coverage: inline isCustomAction exclusion, sort-order fall-through to the first candidate, scope `all` keeping external/custom quotes, restrict-only widening with a fitting limit, and amount-above-max limit skip. - Flatten the CHANGELOG entry so the PR link sits on the top-level bullet (the check-changelog action requires each entry to link to the PR).
|
@metamaskbot publish-preview |
…testing REVERT before release. Forces #pickInAppQuote to skip native providers so a non-native in-app WebView provider is the one suggested during physical-device testing of the Phase 1 in-app flow. istanbul-ignored to keep coverage at 100%.
|
@metamaskbot publish-preview |
|
Preview builds have been published. Learn how to use preview builds in other projects. Expand for full list of packages and versions. |
|
Preview builds have been published. Learn how to use preview builds in other projects. Expand for full list of packages and versions. |
Fixes the lint:misc:check failure. Formatting only, no behaviour change.
…-in-app-provider-scope # Conflicts: # packages/ramps-controller/CHANGELOG.md
Remove the temporary device-testing exclusion of native providers from #pickInAppQuote. It dead-ended native-only assets in prefer-native regions (the on-ramp API dedups the aggregator away, leaving only Transak Native, which the block then excluded -> zero quotes). Add a regression test asserting native is selected when it ranks first among in-app candidates.
|
@metamaskbot publish-preview |
|
Preview builds have been published. Learn how to use preview builds in other projects. Expand for full list of packages and versions. |
…in-app path Add an optional getDefaultRedirectUrl callback. When getQuotes widens to in-app providers and the caller omits redirectUrl, forward this default so aggregator quotes carry the buyURL/buyWidget the client needs to open the in-app WebView. An explicit caller redirectUrl always wins; scope 'off' never injects a default.
|
@metamaskbot publish-preview |
|
Preview builds have been published. Learn how to use preview builds in other projects. Expand for full list of packages and versions. |
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, have a team admin enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 9e8ad9c. Configure here.
…supports the asset The empty-provider guard in getQuotes only short-circuited on restrictToKnownOrNativeProviders. A caller that triggers in-app widening via autoSelectProvider alone, in a region with no asset-supporting provider, got an empty providersToUse that was then passed to the service, which omits the provider filter and quotes every provider. Broaden the guard to also cover the widened in-app path (widenToInAppProviders) so it returns an empty response instead. Reported by Cursor Bugbot.
## Explanation This release branch publishes `@metamask/ramps-controller` `15.1.0`. The release includes the scope-aware in-app provider widening work from MetaMask#9353, plus the existing unreleased `init()` / countries catalog refresh entries from MetaMask#9261. The release tool also bumped the root monorepo version to `1095.0.0` and updated `@metamask/transaction-pay-controller`'s workspace dependency on `@metamask/ramps-controller` to `^15.1.0`. This branch was generated by `yarn create-release-branch`. ## References - Related to MetaMask#9353 - Related to MetaMask#9261 - Related to MetaMask/metamask-mobile#32682 ## 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 Validation: `yarn changelog:validate` Made with [Cursor](https://cursor.com) --------- Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>

Explanation
Part of Headless Buy "all providers" support (Phase 1). This adds an opt-in way for a host to widen
RampsController.getQuotesfrom native-only auto-selection to in-app WebView aggregator providers, with no change to callers that pass explicit providers.getProviderScopecallback onRampsControllerOptions, plus an exportedProviderScopetype ('off' | 'in-app' | 'all'). The callback is read on everygetQuotescall, so a host toggle takes effect at runtime. It defaults to'off'(today's behaviour) when omitted.'off', the native-only auto-selection path (autoSelectProvider/restrictToKnownOrNativeProviders) widens to every supporting provider and returns the single best in-app quote atsuccess[0]. A private#pickInAppQuotedrops external-browser (buyWidget.browser === 'IN_APP_OS_BROWSER') and custom-action quotes, enforces per-provider fiat limits up front, and orders by reliability then price using the server-providedsortedorder. It reuses the existinggetProvidershydration fallback so an empty catalog cannot silently quote nothing.getDefaultRedirectUrlcallback onRampsControllerOptions. On the widened in-app path, when the caller does not pass aredirectUrl,getQuotesuses this default so aggregator quotes carry thebuyURL/buyWidgetthe client needs to open the WebView. An explicit callerredirectUrlalways wins, and scope'off'never injects one. (This closes a gap where MetaMask Pay's quote fetch omitsredirectUrl, leaving aggregator quotes without a buy URL.)providersare unaffected, andproviders.selectedis never mutated.This lets a client move the caller that reads
getQuotes(...).success[0]onto in-app providers behind its own gate, with no other core changes.VIDEO IN MOBILE PR that uses this new core version: MetaMask/metamask-mobile#32682
Here's a Google Drive link since Github is uploading this video very slowly: https://drive.google.com/file/d/14Ialq7Tg9Ep0tpCljAle5ttAX92zTPIh/view?usp=sharing
Uploading ScreenRecording_07-06-2026 00-12-51_1.MP4…
References
Checklist
Note
Medium Risk
Changes which ramp provider and quote
getQuotesreturns for gated auto-select callers (fiat purchase flow), though defaults preserve prior native-only behavior and coverage is extensive.Overview
Adds opt-in Headless Buy Phase 1 behavior so hosts can widen fiat quote auto-selection from native-only to in-app aggregator providers without changing explicit-
providerscallers.getProviderScope('off' | 'in-app' | 'all', default'off') is read on eachgetQuotescall. When scope is not'off'and the caller usesautoSelectProviderorrestrictToKnownOrNativeProviderswithout an explicitproviderslist, the controller quotes every region/asset-supporting provider, then#pickInAppQuotereduces the response to a single winner atsuccess[0](forsuccess?.[0]consumers). Under'in-app', selection skips external-browser (IN_APP_OS_BROWSER), custom-action providers, and quotes outside per-provider fiat limits; ordering follows serversorted(reliability, then price). Scope'all'keeps external/custom quotes eligible. Empty supporting sets return an empty success list instead of unfiltered service quotes.getDefaultRedirectUrlfillsredirectUrlon that widened path when the caller omits it so aggregator quotes get a usablebuyWidgetURL; explicitredirectUrlwins and scope'off'never injects.ProviderScopeis exported from the package index.Reviewed by Cursor Bugbot for commit 0b318e0. Bugbot is set up for automated code reviews on this repo. Configure here.