From 1a87c5a92b4f17292b2c6c84016d8b3dbb0181b9 Mon Sep 17 00:00:00 2001 From: George Weiler Date: Tue, 13 Jan 2026 14:54:15 -0700 Subject: [PATCH 1/4] feat: adds preferred provider to ramp controller state --- .../src/RampsController.test.ts | 82 ++++++++++++++++++- .../ramps-controller/src/RampsController.ts | 26 +++++- packages/ramps-controller/src/RampsService.ts | 31 +++++++ packages/ramps-controller/src/index.ts | 3 + 4 files changed, 140 insertions(+), 2 deletions(-) diff --git a/packages/ramps-controller/src/RampsController.test.ts b/packages/ramps-controller/src/RampsController.test.ts index f432bf90f7d..6b0750696ce 100644 --- a/packages/ramps-controller/src/RampsController.test.ts +++ b/packages/ramps-controller/src/RampsController.test.ts @@ -8,7 +8,7 @@ import type { import type { RampsControllerMessenger } from './RampsController'; import { RampsController } from './RampsController'; -import type { Country, TokensResponse } from './RampsService'; +import type { Country, TokensResponse, Provider } from './RampsService'; import type { RampsServiceGetGeolocationAction, RampsServiceGetCountriesAction, @@ -24,6 +24,7 @@ describe('RampsController', () => { expect(controller.state).toMatchInlineSnapshot(` Object { "eligibility": null, + "preferredProvider": null, "requests": Object {}, "tokens": null, "userRegion": null, @@ -42,6 +43,7 @@ describe('RampsController', () => { ({ controller }) => { expect(controller.state).toStrictEqual({ eligibility: null, + preferredProvider: null, tokens: null, userRegion: 'US', requests: {}, @@ -55,6 +57,7 @@ describe('RampsController', () => { expect(controller.state).toMatchInlineSnapshot(` Object { "eligibility": null, + "preferredProvider": null, "requests": Object {}, "tokens": null, "userRegion": null, @@ -98,6 +101,7 @@ describe('RampsController', () => { ).toMatchInlineSnapshot(` Object { "eligibility": null, + "preferredProvider": null, "requests": Object {}, "tokens": null, "userRegion": null, @@ -117,6 +121,7 @@ describe('RampsController', () => { ).toMatchInlineSnapshot(` Object { "eligibility": null, + "preferredProvider": null, "tokens": null, "userRegion": null, } @@ -135,6 +140,7 @@ describe('RampsController', () => { ).toMatchInlineSnapshot(` Object { "eligibility": null, + "preferredProvider": null, "tokens": null, "userRegion": null, } @@ -153,6 +159,7 @@ describe('RampsController', () => { ).toMatchInlineSnapshot(` Object { "eligibility": null, + "preferredProvider": null, "requests": Object {}, "tokens": null, "userRegion": null, @@ -1090,6 +1097,79 @@ describe('RampsController', () => { }); }); + describe('setPreferredProvider', () => { + const mockProvider: Provider = { + id: '/providers/paypal-staging', + name: 'PayPal (Staging)', + environmentType: 'STAGING', + description: 'Test provider description', + hqAddress: '2211 N 1st St, San Jose, CA 95131', + links: [ + { + name: 'Homepage', + url: 'https://www.paypal.com/us/home', + }, + { + name: 'Terms of Service', + url: 'https://www.paypal.com/us/legalhub/cryptocurrencies-tnc', + }, + { + name: 'Support', + url: 'https://www.paypal.com/us/cshelp', + }, + ], + logos: { + light: '/assets/providers/paypal_light.png', + dark: '/assets/providers/paypal_dark.png', + height: 24, + width: 77, + }, + }; + + it('sets preferred provider', async () => { + await withController(({ controller }) => { + expect(controller.state.preferredProvider).toBeNull(); + + controller.setPreferredProvider(mockProvider); + + expect(controller.state.preferredProvider).toStrictEqual(mockProvider); + }); + }); + + it('clears preferred provider when set to null', async () => { + await withController( + { options: { state: { preferredProvider: mockProvider } } }, + ({ controller }) => { + expect(controller.state.preferredProvider).toStrictEqual(mockProvider); + + controller.setPreferredProvider(null); + + expect(controller.state.preferredProvider).toBeNull(); + }, + ); + }); + + it('updates preferred provider when a new provider is set', async () => { + await withController( + { options: { state: { preferredProvider: mockProvider } } }, + ({ controller }) => { + const newProvider: Provider = { + ...mockProvider, + id: '/providers/ramp-network-staging', + name: 'Ramp Network (Staging)', + }; + + controller.setPreferredProvider(newProvider); + + expect(controller.state.preferredProvider).toStrictEqual(newProvider); + expect(controller.state.preferredProvider?.id).toBe( + '/providers/ramp-network-staging', + ); + }, + ); + }); + }); + describe('updateUserRegion with automatic eligibility', () => { it('automatically fetches eligibility after getting user region', async () => { await withController(async ({ controller, rootMessenger }) => { diff --git a/packages/ramps-controller/src/RampsController.ts b/packages/ramps-controller/src/RampsController.ts index 35e3576f8da..88b37d1c763 100644 --- a/packages/ramps-controller/src/RampsController.ts +++ b/packages/ramps-controller/src/RampsController.ts @@ -7,7 +7,7 @@ import { BaseController } from '@metamask/base-controller'; import type { Messenger } from '@metamask/messenger'; import type { Json } from '@metamask/utils'; -import type { Country, Eligibility, TokensResponse } from './RampsService'; +import type { Country, Eligibility, TokensResponse, Provider } from './RampsService'; import type { RampsServiceGetGeolocationAction, RampsServiceGetCountriesAction, @@ -50,6 +50,11 @@ export type RampsControllerState = { * Initially set via geolocation fetch, but can be manually changed by the user. */ userRegion: string | null; + /** + * The user's preferred provider. + * Can be manually set by the user. + */ + preferredProvider: Provider | null; /** * Eligibility information for the user's current region. */ @@ -76,6 +81,12 @@ const rampsControllerMetadata = { includeInStateLogs: true, usedInUi: true, }, + preferredProvider: { + persist: true, + includeInDebugSnapshot: true, + includeInStateLogs: true, + usedInUi: true, + }, eligibility: { persist: true, includeInDebugSnapshot: true, @@ -107,6 +118,7 @@ const rampsControllerMetadata = { export function getDefaultRampsControllerState(): RampsControllerState { return { userRegion: null, + preferredProvider: null, eligibility: null, tokens: null, requests: {}, @@ -485,6 +497,18 @@ export class RampsController extends BaseController< } } + /** + * Sets the user's preferred provider. + * This allows users to set their preferred ramp provider. + * + * @param provider - The provider object to set. + */ + setPreferredProvider(provider: Provider | null): void { + this.update((state) => { + state.preferredProvider = provider; + }); + } + /** * Initializes the controller by fetching the user's region from geolocation. * This should be called once at app startup to set up the initial region. diff --git a/packages/ramps-controller/src/RampsService.ts b/packages/ramps-controller/src/RampsService.ts index 060406b8797..7a3557b74e1 100644 --- a/packages/ramps-controller/src/RampsService.ts +++ b/packages/ramps-controller/src/RampsService.ts @@ -62,6 +62,37 @@ export type Eligibility = { global?: boolean; }; +/** + * Represents a provider link. + */ +export type ProviderLink = { + name: string; + url: string; +}; + +/** + * Represents provider logos. + */ +export type ProviderLogos = { + light: string; + dark: string; + height: number; + width: number; +}; + +/** + * Represents a ramp provider. + */ +export type Provider = { + id: string; + name: string; + environmentType: string; + description: string; + hqAddress: string; + links: ProviderLink[]; + logos: ProviderLogos; +}; + /** * Represents a country returned from the regions/countries API. */ diff --git a/packages/ramps-controller/src/index.ts b/packages/ramps-controller/src/index.ts index 390e650887b..77fc6f6ab0d 100644 --- a/packages/ramps-controller/src/index.ts +++ b/packages/ramps-controller/src/index.ts @@ -19,6 +19,9 @@ export type { State, Eligibility, CountryPhone, + Provider, + ProviderLink, + ProviderLogos, } from './RampsService'; export { RampsService, From cac6d5fecc2591b239e61dad592cf68de386f978 Mon Sep 17 00:00:00 2001 From: George Weiler Date: Tue, 13 Jan 2026 15:15:20 -0700 Subject: [PATCH 2/4] chore: changelog --- packages/ramps-controller/CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/ramps-controller/CHANGELOG.md b/packages/ramps-controller/CHANGELOG.md index 9d6d29015df..88d75eefb40 100644 --- a/packages/ramps-controller/CHANGELOG.md +++ b/packages/ramps-controller/CHANGELOG.md @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Added + +- Add `preferredProvider` state and `setPreferredProvider()` method to RampsController + ## [3.0.0] ### Added From dc22af50a2bc852cef1c64e195b481b42bacf903 Mon Sep 17 00:00:00 2001 From: George Weiler Date: Tue, 13 Jan 2026 15:27:25 -0700 Subject: [PATCH 3/4] fix: tests --- .../ramps-controller/src/RampsController.test.ts | 4 +++- packages/ramps-controller/src/RampsController.ts | 7 ++++++- packages/ramps-controller/src/selectors.test.ts | 15 +++++++++++++++ 3 files changed, 24 insertions(+), 2 deletions(-) diff --git a/packages/ramps-controller/src/RampsController.test.ts b/packages/ramps-controller/src/RampsController.test.ts index 6b0750696ce..0e3f994b663 100644 --- a/packages/ramps-controller/src/RampsController.test.ts +++ b/packages/ramps-controller/src/RampsController.test.ts @@ -1140,7 +1140,9 @@ describe('RampsController', () => { await withController( { options: { state: { preferredProvider: mockProvider } } }, ({ controller }) => { - expect(controller.state.preferredProvider).toStrictEqual(mockProvider); + expect(controller.state.preferredProvider).toStrictEqual( + mockProvider, + ); controller.setPreferredProvider(null); diff --git a/packages/ramps-controller/src/RampsController.ts b/packages/ramps-controller/src/RampsController.ts index 88b37d1c763..6370e00d193 100644 --- a/packages/ramps-controller/src/RampsController.ts +++ b/packages/ramps-controller/src/RampsController.ts @@ -7,7 +7,12 @@ import { BaseController } from '@metamask/base-controller'; import type { Messenger } from '@metamask/messenger'; import type { Json } from '@metamask/utils'; -import type { Country, Eligibility, TokensResponse, Provider } from './RampsService'; +import type { + Country, + Eligibility, + TokensResponse, + Provider, +} from './RampsService'; import type { RampsServiceGetGeolocationAction, RampsServiceGetCountriesAction, diff --git a/packages/ramps-controller/src/selectors.test.ts b/packages/ramps-controller/src/selectors.test.ts index 07645b78069..c1e2a04491a 100644 --- a/packages/ramps-controller/src/selectors.test.ts +++ b/packages/ramps-controller/src/selectors.test.ts @@ -25,6 +25,7 @@ describe('createRequestSelector', () => { const state: TestRootState = { ramps: { userRegion: null, + preferredProvider: null, eligibility: null, tokens: null, requests: { @@ -55,6 +56,7 @@ describe('createRequestSelector', () => { const state: TestRootState = { ramps: { userRegion: null, + preferredProvider: null, eligibility: null, tokens: null, requests: { @@ -88,6 +90,7 @@ describe('createRequestSelector', () => { const state: TestRootState = { ramps: { userRegion: null, + preferredProvider: null, eligibility: null, tokens: null, requests: { @@ -117,6 +120,7 @@ describe('createRequestSelector', () => { const state: TestRootState = { ramps: { userRegion: null, + preferredProvider: null, eligibility: null, tokens: null, requests: {}, @@ -169,6 +173,7 @@ describe('createRequestSelector', () => { const state: TestRootState = { ramps: { userRegion: null, + preferredProvider: null, eligibility: null, tokens: null, requests: { @@ -194,6 +199,7 @@ describe('createRequestSelector', () => { const state1: TestRootState = { ramps: { userRegion: null, + preferredProvider: null, eligibility: null, tokens: null, requests: { @@ -208,6 +214,7 @@ describe('createRequestSelector', () => { const state2: TestRootState = { ramps: { userRegion: null, + preferredProvider: null, eligibility: null, tokens: null, requests: { @@ -234,6 +241,7 @@ describe('createRequestSelector', () => { const state: TestRootState = { ramps: { userRegion: null, + preferredProvider: null, eligibility: null, tokens: null, requests: { @@ -263,6 +271,7 @@ describe('createRequestSelector', () => { const state: TestRootState = { ramps: { userRegion: null, + preferredProvider: null, eligibility: null, tokens: null, requests: { @@ -291,6 +300,7 @@ describe('createRequestSelector', () => { const loadingState: TestRootState = { ramps: { userRegion: null, + preferredProvider: null, eligibility: null, tokens: null, requests: { @@ -307,6 +317,7 @@ describe('createRequestSelector', () => { const successState: TestRootState = { ramps: { userRegion: null, + preferredProvider: null, eligibility: null, tokens: null, requests: { @@ -331,6 +342,7 @@ describe('createRequestSelector', () => { const successState: TestRootState = { ramps: { userRegion: null, + preferredProvider: null, eligibility: null, tokens: null, requests: { @@ -346,6 +358,7 @@ describe('createRequestSelector', () => { const errorState: TestRootState = { ramps: { userRegion: null, + preferredProvider: null, eligibility: null, tokens: null, requests: { @@ -376,6 +389,7 @@ describe('createRequestSelector', () => { const state: TestRootState = { ramps: { userRegion: null, + preferredProvider: null, eligibility: null, tokens: null, requests: { @@ -410,6 +424,7 @@ describe('createRequestSelector', () => { const state: TestRootState = { ramps: { userRegion: null, + preferredProvider: null, eligibility: null, tokens: null, requests: { From 4e1e0f785b96e611f0b0220a258a1b49a672ce9b Mon Sep 17 00:00:00 2001 From: George Weiler Date: Tue, 13 Jan 2026 15:31:39 -0700 Subject: [PATCH 4/4] chore: changelog --- packages/ramps-controller/CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/ramps-controller/CHANGELOG.md b/packages/ramps-controller/CHANGELOG.md index 88d75eefb40..b2fb4ae64fa 100644 --- a/packages/ramps-controller/CHANGELOG.md +++ b/packages/ramps-controller/CHANGELOG.md @@ -9,7 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added -- Add `preferredProvider` state and `setPreferredProvider()` method to RampsController +- Add `preferredProvider` state and `setPreferredProvider()` method to RampsController ([#7617](https://github.com/MetaMask/core/pull/7617)) ## [3.0.0]