feat(ramps-controller): add setSelectedProviderForAsset controller method - #9759
Merged
saustrie-consensys merged 7 commits intoAug 4, 2026
Merged
Conversation
…thod Adds setSelectedProviderForAsset(assetId, options?) to RampsController. When the currently selected provider does not support the given CAIP-19 asset, the method switches providers.selected to the first provider in providers.data that does, using the existing providerServesAsset utility. Returns true if a switch was made, false otherwise (no-op when providers are not loaded, the selected provider already supports the asset, or no compatible alternative exists). This moves the "silent provider switch on asset mismatch" logic from duplicated UI-layer code (BuildQuote tier-1 effect, useEnsureCompatibleProvider) into the controller, where it can be tested in isolation and called from any surface without re-implementing providerServesAsset + find. Registers as RampsController:setSelectedProviderForAsset messenger action. Action-types file regenerated via messenger-action-types:generate.
Contributor
Author
|
@metamaskbot publish-preview |
Contributor
|
Preview builds have been published. Learn how to use preview builds in other projects. Expand for full list of packages and versions. |
Previously providerServesAsset checked only for key presence in
supportedCryptoCurrencies, so a provider with { "asset": false } would
be treated as serving that asset. This aligns the function with the
mobile UI's providerSupportsAsset utility, which has always required
the value to be strictly true.
Without this fix, setSelectedProviderForAsset could select a provider
with a false-valued entry, causing an infinite re-run loop in consumers
that combine a controller-side switch with a UI-side isTokenUnavailable
check (which uses the stricter providerSupportsAsset).
…ProviderForAsset The existing test passes for the wrong reason: the provider has empty supportedCryptoCurrencies so providerServesAsset is false regardless, meaning the p.id !== selectedProvider.id guard in find() never needs to fire. Add a test that actually exercises the guard: providers.selected is a stale copy with no assets while providers.data has the same provider ID with the asset — the guard must skip it to prevent a spurious self-switch.
…erForAsset Rename single-letter `p` parameter in providers.find callback (id-length rule requires at least 2 chars). Drop the `=== true` comparison against a boolean value in providerAvailability (no-unnecessary-boolean-literal- compare).
saustrie-consensys
added a commit
to MetaMask/metamask-mobile
that referenced
this pull request
Aug 3, 2026
Pins @metamask/ramps-controller to the preview build published from MetaMask/core#9759 (18.0.1-preview-f21e1e0) via a resolutions override. This makes setSelectedProviderForAsset available at runtime. With the method present, removes the typeof guard and type cast that were guarding against the missing symbol. The call is now a direct Engine.context.RampsController.setSelectedProviderForAsset(...) with no wrapper.
saustrie-consensys
marked this pull request as ready for review
August 3, 2026 14:56
saustrie-consensys
temporarily deployed
to
default-branch
August 3, 2026 14:57 — with
GitHub Actions
Inactive
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 2 potential issues.
❌ 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 88ad973. Configure here.
…data setSelectedProviderForAsset checked asset compatibility against the providers.selected snapshot, which can be stale once a newer providers list lands in providers.data. When the fresh entry for the current provider did serve the asset and another provider served it too, the method switched away and returned true. Look the current provider up in providers.data by id, falling back to providers.selected when it is absent, and keep the self-switch guard on the replacement lookup. Add a regression test for the case where a second compatible provider is available. Also export RampsControllerSetSelectedProviderForAssetAction from the package index alongside its sibling method action types, and apply oxfmt formatting to the test file.
…ller-set-provider-for-asset-fixes
saustrie-consensys
enabled auto-merge
August 3, 2026 19:24
amitabh94
approved these changes
Aug 4, 2026
saustrie-consensys
deleted the
saustrie/ramps-controller-set-provider-for-asset
branch
August 4, 2026 02:49
saustrie-consensys
added a commit
to MetaMask/metamask-mobile
that referenced
this pull request
Aug 4, 2026
Pins @metamask/ramps-controller to the preview build published from MetaMask/core#9759 (18.0.1-preview-f21e1e0) via a resolutions override. This makes setSelectedProviderForAsset available at runtime. With the method present, removes the typeof guard and type cast that were guarding against the missing symbol. The call is now a direct Engine.context.RampsController.setSelectedProviderForAsset(...) with no wrapper.
pull Bot
pushed a commit
to Reality2byte/core
that referenced
this pull request
Aug 4, 2026
## Explanation Automated release bump created with `yarn create-release-branch` for: - `@metamask/ramps-controller` `18.0.1` to `19.0.0` (major). Unreleased includes breaking changes from MetaMask#9752 (remove `getDefaultRedirectUrl` from `RampsControllerOptions`; require `RampsService:getDefaultRedirectCallbackUrl` delegation) plus the new `setSelectedProviderForAsset` API and `providerServesAsset` fix from MetaMask#9759. - `@metamask/transaction-pay-controller` `26.2.0` to `26.2.1` (patch). Included as a direct dependent of the major bump so its `@metamask/ramps-controller` range moves to `^19.0.0`. Also publishes the pending `@metamask/network-controller` bump. SemVer: major is required because of the MetaMask#9752 BREAKING entries in Unreleased; a minor would be wrong even though MetaMask#9759 alone would have been a minor. ## References - Related to MetaMask#9759 (`setSelectedProviderForAsset` and `providerServesAsset` fix) - Related to MetaMask#9752 (Headless Buy default redirect URL sourced from `RampsService`; breaking for hosts) - Consumer mobile adoption for MetaMask#9759: MetaMask/metamask-mobile#34195 - Breaking-change consumer note from MetaMask#9752: hosts must drop `getDefaultRedirectUrl` and delegate `RampsService:getDefaultRedirectCallbackUrl` (or spread `RAMPS_CONTROLLER_REQUIRED_SERVICE_ACTIONS`) ## 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 - [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** > Low direct code risk in this diff, but shipping ramps-controller 19.0.0 forces consumer migrations (messenger delegation, dropped `getDefaultRedirectUrl`) and can break MM Pay fiat quoting if hosts are not updated. > > **Overview** > This is an **automated release cut** (no application code in the diff): it finalizes versions and changelogs so npm consumers pick up ramps work that landed in earlier PRs. > > **`@metamask/ramps-controller` `19.0.0` (major)** moves Unreleased notes into `[19.0.0]`. Hosts upgrading get **breaking** Headless Buy redirect wiring: `getDefaultRedirectUrl` is removed from `RampsControllerOptions`, widened `getQuotes` paths must delegate **`RampsService:getDefaultRedirectCallbackUrl`**, and defaults come from **`RampsService` / `getDefaultRedirectCallbackUrl(environment)`**. The release also documents **`setSelectedProviderForAsset`** plus a **`providerServesAsset`** fix (map values must be `true`). > > **`@metamask/transaction-pay-controller` `26.2.1` (patch)** bumps **`@metamask/ramps-controller`** to `^19.0.0` as a direct dependent and records a pending **`@metamask/network-controller`** `^35.0.1` bump. The root monorepo version becomes **`1172.0.0`**; **`yarn.lock`** is updated accordingly. > > <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit 277ec22. 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 firas9941/metamask-mobile
that referenced
this pull request
Aug 6, 2026
MetaMask#34195) <!-- Please submit this PR as a draft initially. Do not mark it as "Ready for review" until this PR meets the canonical Definition of Ready For Review in `docs/readme/ready-for-review.md`. --> ## **Description** <!-- mms-check: type=text required=true --> Rewires the provider-compatibility switch so the logic lives in `RampsController` (via `setSelectedProviderForAsset`, from MetaMask/core#9759) rather than being duplicated in the UI layer. **Background.** When a user selects Coinbase (or any non-native aggregator) in UB2 and then opens MMPay, `RampsController.providers.selected` still points to the aggregator. MMPay's `useHasNativeFiatProvider` checks `selectedProvider?.type === 'native'` and returns false, causing `useAutomaticTransactionPayToken` to dead-end before selecting a payment method. The "Pay with..." section skeleton spins indefinitely. **This PR.** The switch logic moves to the controller. `RampsController.setSelectedProviderForAsset(assetId)`: - Finds the first provider in `state.providers.data` that serves `assetId`. - Calls `setSelectedProvider` internally with `autoSelected: true`. - Returns `true` if a switch happened, `false` otherwise. Changes: - `useRampsProviders.ts` / `useRampsController.ts` - expose `setSelectedProviderForAsset` from `Engine.context.RampsController`. - `useEnsureCompatibleProvider.ts` - single `useEffect` calling `setSelectedProviderForAsset`. - `BuildQuote.tsx` Effect 2 - uses controller method instead of inline find-and-switch. - `money-account-deposit-info.tsx` - calls `useEnsureCompatibleProvider` so MMPay switches away from an incompatible aggregator. - Bumps `@metamask/ramps-controller` to stable `^19.0.0` (no preview resolution). - Includes the TRAM-3757 redirect-URL consumer wiring so `19.0.0` works before/alongside MetaMask#34207 landing on main. Merge after MetaMask#34207. ## **Changelog** <!-- mms-check: type=changelog required=true blocking=true --> CHANGELOG entry: null ## **Related issues** <!-- mms-check: type=issue-link required=true --> Refs: https://consensyssoftware.atlassian.net/browse/TRAM-3635 Refs: MetaMask/core#9759 Refs: MetaMask#34207 ## **Manual testing steps** <!-- mms-check: type=manual-testing required=true --> ```gherkin Feature: MMPay provider compatibility after UB2 aggregator selection Scenario: user opens MMPay after selecting Coinbase in UB2 Given the user has selected Coinbase as the provider in UB2 When the user opens MMPay Add funds Then the Pay with section resolves to a payment method row And the skeleton does not spin indefinitely ``` Device testing tracked in TRAM-3635. ## **Screenshots/Recordings** <!-- mms-check: type=screenshot required=true --> N/A - logic-only change; visual behavior is the fix itself (no more infinite skeleton). ### **Before** MetaMask#33977 ### **After** https://consensys.slack.com/archives/C030G4739T3/p1785749933438429?thread_ts=1785357615.968319&cid=C030G4739T3 ## **Pre-merge author checklist** <!-- mms-check: type=checklist required=true --> - [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 fiat deposit provider selection and auto-pay timing in confirmations; wrong switching could hide payment methods or pick the wrong ramp provider. > > **Overview** > Wires **`RampsController.setSelectedProviderForAsset`** through `useRampsProviders` / `useRampsController` so UI stops duplicating “pick a provider for this asset” logic. > > **`useEnsureCompatibleProvider`** runs on MMPay add-funds (`MoneyAccountDepositInfo`): it calls the controller first, and when `moneyHeadlessAllProviders` is off it can still **auto-select a native** provider if an aggregator already lists the asset (fixes stuck “Pay with…” after UB2 left Coinbase selected). > > **`BuildQuote`** token-unavailable handling now tries `setSelectedProviderForAsset` before falling back to another supporting provider or the token-not-available modal; it waits until providers are loaded. > > **`useAutomaticTransactionPayToken`** no longer latches early on the fiat path when fiat isn’t ready yet, so a later provider switch can complete auto-selection. > > <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit efad05d. Bugbot is set up for automated code reviews on this repo. Configure [here](https://www.cursor.com/dashboard/bugbot).</sup> <!-- /CURSOR_SUMMARY -->
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.

Explanation
Currently, both UB2's BuildQuote (tier-1 silent-switch effect) and MMPay's
useEnsureCompatibleProviderhook duplicate the same logic: callproviderServesAsseton the selected provider, runproviders.findto get a compatible alternative, and callsetSelectedProvider. That logic belongs in the controller — it's a state operation, not a UI concern.This PR adds
setSelectedProviderForAsset(assetId, options?)toRampsController. When the currently selected provider does not serve the given CAIP-19 asset, it switchesproviders.selectedto the first provider inproviders.datathat does, using the already-presentproviderServesAssetutility inproviderAvailability.ts. Returnstrueif a switch was made,falseotherwise. No-op when:providers.datais empty (providers not yet loaded)Registers as
RampsController:setSelectedProviderForAssetvia the existingMESSENGER_EXPOSED_METHODSmechanism. Action-types file regenerated viamessenger-action-types:generate.The mobile consumer PR (stacked on the preview build) will simplify
useEnsureCompatibleProviderto delegate to this method and remove the duplicated find-and-switch from the UI layer.References
providerAvailability.ts— theproviderServesAssetutility this delegates to already existed and is tested independently.Checklist
Note
Medium Risk
Changes provider-selection behavior used in buy flows and alters providerServesAsset semantics, which may affect any code that depended on key-only matching.
Overview
Adds
setSelectedProviderForAsset(assetId, options?)onRampsController(andRampsController:setSelectedProviderForAssetvia messenger) so hosts can silently switchproviders.selectedto the first entry inproviders.datathat supports a CAIP-19 asset when the current selection does not. Compatibility uses the fresh provider row inproviders.datawhen it differs from a staleproviders.selectedcopy; it returns whether a switch happened and defaultsautoSelectedto true when forwarding tosetSelectedProvider.providerServesAssetnow treats an asset as supported only when thesupportedCryptoCurrenciesmap value istrue, not merely when the key exists (fixes false positives for{ assetId: false }).Reviewed by Cursor Bugbot for commit aadc2e6. Bugbot is set up for automated code reviews on this repo. Configure here.