From 931fefc638eb2af77bfd8147266af8903fe10cd4 Mon Sep 17 00:00:00 2001 From: Amitabh Aggarwal Date: Wed, 24 Jun 2026 23:21:54 -0500 Subject: [PATCH 1/5] fix(ramps-controller): refresh countries catalog on app startup Stop persisting the countries catalog and force-refetch via init() so preset amounts stay in sync after app restarts. TRAM-3682 Co-authored-by: Cursor --- packages/ramps-controller/CHANGELOG.md | 7 ++ .../RampsController-method-action-types.ts | 2 +- .../src/RampsController.test.ts | 64 ++++++++++++++++--- .../ramps-controller/src/RampsController.ts | 38 ++++++++--- 4 files changed, 94 insertions(+), 17 deletions(-) diff --git a/packages/ramps-controller/CHANGELOG.md b/packages/ramps-controller/CHANGELOG.md index 3218f1a4ed..cdb6c4a173 100644 --- a/packages/ramps-controller/CHANGELOG.md +++ b/packages/ramps-controller/CHANGELOG.md @@ -11,6 +11,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed +- Stop persisting the countries catalog in `RampsController` state and refetch it on every app startup via `init()` ([#TBD](https://github.com/MetaMask/core/pull/TBD), [TRAM-3682](https://consensyssoftware.atlassian.net/browse/TRAM-3682)) +- Re-sync `userRegion` preset amounts from the countries catalog after each `getCountries()` call ([#TBD](https://github.com/MetaMask/core/pull/TBD), [TRAM-3682](https://consensyssoftware.atlassian.net/browse/TRAM-3682)) + +## [15.0.0] + +### Changed + - **BREAKING:** `RampsController.getProviders`, `RampsService.getProviders`, `RampsController.getPaymentMethods`, and `RampsService.getPaymentMethods` no longer accept a `fiat` query filter; region local fiat filtering is applied server-side when omitted ([#9245](https://github.com/MetaMask/core/pull/9245)) - Bump `@metamask/controller-utils` from `^12.2.0` to `^12.3.0` ([#9218](https://github.com/MetaMask/core/pull/9218)) diff --git a/packages/ramps-controller/src/RampsController-method-action-types.ts b/packages/ramps-controller/src/RampsController-method-action-types.ts index b22872242c..e0e89a82d9 100644 --- a/packages/ramps-controller/src/RampsController-method-action-types.ts +++ b/packages/ramps-controller/src/RampsController-method-action-types.ts @@ -92,7 +92,7 @@ export type RampsControllerSetSelectedProviderAction = { * This should be called once at app startup to set up the initial region. * * Idempotent: subsequent calls return the same promise unless forceRefresh is set. - * Skips getCountries when countries are already loaded; skips geolocation when + * Always refetches the countries catalog on startup. Skips geolocation when * userRegion already exists. * * @param options - Options for cache behavior. forceRefresh bypasses idempotency and re-runs the full flow. diff --git a/packages/ramps-controller/src/RampsController.test.ts b/packages/ramps-controller/src/RampsController.test.ts index 46736e7c10..8b5c869d6e 100644 --- a/packages/ramps-controller/src/RampsController.test.ts +++ b/packages/ramps-controller/src/RampsController.test.ts @@ -921,12 +921,6 @@ describe('RampsController', () => { ), ).toMatchInlineSnapshot(` { - "countries": { - "data": [], - "error": null, - "isLoading": false, - "selected": null, - }, "orders": [], "providerAutoSelected": false, "userRegion": null, @@ -1880,6 +1874,60 @@ describe('RampsController', () => { }); }); + it('re-syncs userRegion preset amounts after getCountries', async () => { + const staleCountries: Country[] = [ + { + isoCode: 'CR', + id: '/regions/cr', + flag: 'πŸ‡¨πŸ‡·', + name: 'Costa Rica', + phone: { + prefix: '+506', + placeholder: '8312 3456', + template: 'XXXX XXXX', + }, + currency: 'CRC', + supported: { buy: true, sell: true }, + defaultAmount: 100, + quickAmounts: [20, 50, 100], + }, + ]; + const freshCountries: Country[] = [ + { + ...staleCountries[0], + defaultAmount: 25000, + quickAmounts: [10000, 25000, 50000], + }, + ]; + + await withController( + { + options: { + state: { + userRegion: { + country: staleCountries[0], + state: null, + regionCode: 'cr', + }, + }, + }, + }, + async ({ controller, rootMessenger }) => { + rootMessenger.registerActionHandler( + 'RampsService:getCountries', + async () => freshCountries, + ); + + await rootMessenger.call('RampsController:getCountries'); + + expect(controller.state.userRegion?.country.defaultAmount).toBe(25000); + expect(controller.state.userRegion?.country.quickAmounts).toStrictEqual( + [10000, 25000, 50000], + ); + }, + ); + }); + it('throws when updating resource field and resource is null', async () => { const stateWithNullCountries = { ...getDefaultRampsControllerState(), @@ -2110,7 +2158,7 @@ describe('RampsController', () => { }); }); - it('skips getCountries and geolocation when userRegion and countries exist', async () => { + it('refetches countries on init but skips geolocation when userRegion exists', async () => { let getCountriesCalled = false; let getGeolocationCalled = false; await withController( @@ -2148,7 +2196,7 @@ describe('RampsController', () => { await rootMessenger.call('RampsController:init'); - expect(getCountriesCalled).toBe(false); + expect(getCountriesCalled).toBe(true); expect(getGeolocationCalled).toBe(false); expect(controller.state.userRegion?.regionCode).toBe('us-ca'); }, diff --git a/packages/ramps-controller/src/RampsController.ts b/packages/ramps-controller/src/RampsController.ts index 7ab3bcdac9..0ae4d81398 100644 --- a/packages/ramps-controller/src/RampsController.ts +++ b/packages/ramps-controller/src/RampsController.ts @@ -388,7 +388,7 @@ const rampsControllerMetadata = { usedInUi: true, }, countries: { - persist: true, + persist: false, includeInDebugSnapshot: true, includeInStateLogs: true, usedInUi: true, @@ -1383,7 +1383,7 @@ export class RampsController extends BaseController< * This should be called once at app startup to set up the initial region. * * Idempotent: subsequent calls return the same promise unless forceRefresh is set. - * Skips getCountries when countries are already loaded; skips geolocation when + * Always refetches the countries catalog on startup. Skips geolocation when * userRegion already exists. * * @param options - Options for cache behavior. forceRefresh bypasses idempotency and re-runs the full flow. @@ -1412,12 +1412,7 @@ export class RampsController extends BaseController< } async #runInit(options?: ExecuteRequestOptions): Promise { - const forceRefresh = options?.forceRefresh === true; - const hasCountries = this.state.countries.data.length > 0; - - if (forceRefresh || !hasCountries) { - await this.getCountries(options); - } + await this.getCountries({ ...options, forceRefresh: true }); // Always prefer the user's persisted region. Geolocation is only used to // seed the initial value; once the user (or a prior init) has set a region @@ -1434,6 +1429,31 @@ export class RampsController extends BaseController< await this.setUserRegion(regionCode, options); } + /** + * Re-applies `userRegion` from the current countries catalog so preset + * amounts and support flags stay in sync after a catalog refresh. + */ + #syncUserRegionFromCountriesCatalog(): void { + const regionCode = this.state.userRegion?.regionCode; + if (!regionCode) { + return; + } + + const countriesData = this.state.countries.data; + if (!countriesData.length) { + return; + } + + const userRegion = findRegionFromCode(regionCode, countriesData); + if (!userRegion) { + return; + } + + this.update((state) => { + state.userRegion = userRegion; + }); + } + /** * Fetches the list of supported countries. * The API returns countries with support information for both buy and sell actions. @@ -1457,6 +1477,8 @@ export class RampsController extends BaseController< state.countries.data = Array.isArray(countries) ? [...countries] : []; }); + this.#syncUserRegionFromCountriesCatalog(); + return countries; } From fa3622c190a0cb9e4c8510eacd574358c6d54e56 Mon Sep 17 00:00:00 2001 From: Amitabh Aggarwal Date: Wed, 24 Jun 2026 23:24:57 -0500 Subject: [PATCH 2/5] chore(ramps-controller): fix Prettier in RampsController tests TRAM-3682 Co-authored-by: Cursor --- packages/ramps-controller/src/RampsController.test.ts | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/packages/ramps-controller/src/RampsController.test.ts b/packages/ramps-controller/src/RampsController.test.ts index 8b5c869d6e..a804f93e11 100644 --- a/packages/ramps-controller/src/RampsController.test.ts +++ b/packages/ramps-controller/src/RampsController.test.ts @@ -1920,10 +1920,12 @@ describe('RampsController', () => { await rootMessenger.call('RampsController:getCountries'); - expect(controller.state.userRegion?.country.defaultAmount).toBe(25000); - expect(controller.state.userRegion?.country.quickAmounts).toStrictEqual( - [10000, 25000, 50000], + expect(controller.state.userRegion?.country.defaultAmount).toBe( + 25000, ); + expect( + controller.state.userRegion?.country.quickAmounts, + ).toStrictEqual([10000, 25000, 50000]); }, ); }); From 27b4f2d48ce7b5cdf03d0913434e212f36f45bc1 Mon Sep 17 00:00:00 2001 From: Alex Mendonca Date: Thu, 25 Jun 2026 11:12:12 +0100 Subject: [PATCH 3/5] chore: fix changelog --- packages/ramps-controller/CHANGELOG.md | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/packages/ramps-controller/CHANGELOG.md b/packages/ramps-controller/CHANGELOG.md index cdb6c4a173..daff1ee1a3 100644 --- a/packages/ramps-controller/CHANGELOG.md +++ b/packages/ramps-controller/CHANGELOG.md @@ -11,14 +11,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed -- Stop persisting the countries catalog in `RampsController` state and refetch it on every app startup via `init()` ([#TBD](https://github.com/MetaMask/core/pull/TBD), [TRAM-3682](https://consensyssoftware.atlassian.net/browse/TRAM-3682)) -- Re-sync `userRegion` preset amounts from the countries catalog after each `getCountries()` call ([#TBD](https://github.com/MetaMask/core/pull/TBD), [TRAM-3682](https://consensyssoftware.atlassian.net/browse/TRAM-3682)) - -## [15.0.0] - -### Changed - - **BREAKING:** `RampsController.getProviders`, `RampsService.getProviders`, `RampsController.getPaymentMethods`, and `RampsService.getPaymentMethods` no longer accept a `fiat` query filter; region local fiat filtering is applied server-side when omitted ([#9245](https://github.com/MetaMask/core/pull/9245)) +- Stop persisting the countries catalog in `RampsController` state and refetch it on every app startup via `init()` ([#9261](https://github.com/MetaMask/core/pull/9261), [TRAM-3682](https://consensyssoftware.atlassian.net/browse/TRAM-3682)) +- Re-sync `userRegion` preset amounts from the countries catalog after each `getCountries()` call ([#9261](https://github.com/MetaMask/core/pull/9261), [TRAM-3682](https://consensyssoftware.atlassian.net/browse/TRAM-3682)) - Bump `@metamask/controller-utils` from `^12.2.0` to `^12.3.0` ([#9218](https://github.com/MetaMask/core/pull/9218)) ## [14.3.0] From ff0aa421c87e1b4c55073e16c4168bd89aa873d2 Mon Sep 17 00:00:00 2001 From: Alex Mendonca Date: Thu, 25 Jun 2026 11:21:31 +0100 Subject: [PATCH 4/5] chore: link changelog entries to PR for changelog check --- packages/ramps-controller/CHANGELOG.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/ramps-controller/CHANGELOG.md b/packages/ramps-controller/CHANGELOG.md index daff1ee1a3..f733e819d4 100644 --- a/packages/ramps-controller/CHANGELOG.md +++ b/packages/ramps-controller/CHANGELOG.md @@ -12,8 +12,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed - **BREAKING:** `RampsController.getProviders`, `RampsService.getProviders`, `RampsController.getPaymentMethods`, and `RampsService.getPaymentMethods` no longer accept a `fiat` query filter; region local fiat filtering is applied server-side when omitted ([#9245](https://github.com/MetaMask/core/pull/9245)) -- Stop persisting the countries catalog in `RampsController` state and refetch it on every app startup via `init()` ([#9261](https://github.com/MetaMask/core/pull/9261), [TRAM-3682](https://consensyssoftware.atlassian.net/browse/TRAM-3682)) -- Re-sync `userRegion` preset amounts from the countries catalog after each `getCountries()` call ([#9261](https://github.com/MetaMask/core/pull/9261), [TRAM-3682](https://consensyssoftware.atlassian.net/browse/TRAM-3682)) +- Stop persisting the countries catalog in `RampsController` state and refetch it on every app startup via `init()` ([#9261](https://github.com/MetaMask/core/pull/9261)) +- Re-sync `userRegion` preset amounts from the countries catalog after each `getCountries()` call ([#9261](https://github.com/MetaMask/core/pull/9261)) - Bump `@metamask/controller-utils` from `^12.2.0` to `^12.3.0` ([#9218](https://github.com/MetaMask/core/pull/9218)) ## [14.3.0] From 82c35e8f9f46b8761f97de7330956ff6149da70e Mon Sep 17 00:00:00 2001 From: Alex Mendonca Date: Thu, 25 Jun 2026 12:44:45 +0100 Subject: [PATCH 5/5] test(ramps-controller): cover catalog-sync guards; fix changelog after rebase --- packages/ramps-controller/CHANGELOG.md | 7 +- .../src/RampsController.test.ts | 104 ++++++++++++++++++ 2 files changed, 109 insertions(+), 2 deletions(-) diff --git a/packages/ramps-controller/CHANGELOG.md b/packages/ramps-controller/CHANGELOG.md index f733e819d4..2e0d9c1cb9 100644 --- a/packages/ramps-controller/CHANGELOG.md +++ b/packages/ramps-controller/CHANGELOG.md @@ -7,13 +7,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Changed + +- Stop persisting the countries catalog in `RampsController` state and refetch it on every app startup via `init()` ([#9261](https://github.com/MetaMask/core/pull/9261)) +- Re-sync `userRegion` preset amounts from the countries catalog after each `getCountries()` call ([#9261](https://github.com/MetaMask/core/pull/9261)) + ## [15.0.0] ### Changed - **BREAKING:** `RampsController.getProviders`, `RampsService.getProviders`, `RampsController.getPaymentMethods`, and `RampsService.getPaymentMethods` no longer accept a `fiat` query filter; region local fiat filtering is applied server-side when omitted ([#9245](https://github.com/MetaMask/core/pull/9245)) -- Stop persisting the countries catalog in `RampsController` state and refetch it on every app startup via `init()` ([#9261](https://github.com/MetaMask/core/pull/9261)) -- Re-sync `userRegion` preset amounts from the countries catalog after each `getCountries()` call ([#9261](https://github.com/MetaMask/core/pull/9261)) - Bump `@metamask/controller-utils` from `^12.2.0` to `^12.3.0` ([#9218](https://github.com/MetaMask/core/pull/9218)) ## [14.3.0] diff --git a/packages/ramps-controller/src/RampsController.test.ts b/packages/ramps-controller/src/RampsController.test.ts index a804f93e11..7ea65e59e2 100644 --- a/packages/ramps-controller/src/RampsController.test.ts +++ b/packages/ramps-controller/src/RampsController.test.ts @@ -1930,6 +1930,110 @@ describe('RampsController', () => { ); }); + it('leaves userRegion unchanged when the refreshed catalog is empty', async () => { + const userRegionCountry: Country = { + isoCode: 'CR', + id: '/regions/cr', + flag: 'πŸ‡¨πŸ‡·', + name: 'Costa Rica', + phone: { + prefix: '+506', + placeholder: '8312 3456', + template: 'XXXX XXXX', + }, + currency: 'CRC', + supported: { buy: true, sell: true }, + defaultAmount: 100, + quickAmounts: [20, 50, 100], + }; + + await withController( + { + options: { + state: { + userRegion: { + country: userRegionCountry, + state: null, + regionCode: 'cr', + }, + }, + }, + }, + async ({ controller, rootMessenger }) => { + rootMessenger.registerActionHandler( + 'RampsService:getCountries', + async () => [], + ); + + await rootMessenger.call('RampsController:getCountries'); + + expect(controller.state.countries.data).toStrictEqual([]); + expect(controller.state.userRegion?.country.defaultAmount).toBe(100); + }, + ); + }); + + it('leaves userRegion unchanged when its region is absent from the refreshed catalog', async () => { + const userRegionCountry: Country = { + isoCode: 'CR', + id: '/regions/cr', + flag: 'πŸ‡¨πŸ‡·', + name: 'Costa Rica', + phone: { + prefix: '+506', + placeholder: '8312 3456', + template: 'XXXX XXXX', + }, + currency: 'CRC', + supported: { buy: true, sell: true }, + defaultAmount: 100, + quickAmounts: [20, 50, 100], + }; + const otherCountries: Country[] = [ + { + isoCode: 'US', + id: '/regions/us', + flag: 'πŸ‡ΊπŸ‡Έ', + name: 'United States', + phone: { + prefix: '+1', + placeholder: '201 555 0123', + template: 'XXX XXX XXXX', + }, + currency: 'USD', + supported: { buy: true, sell: true }, + defaultAmount: 100, + quickAmounts: [100, 300, 1000], + }, + ]; + + await withController( + { + options: { + state: { + userRegion: { + country: userRegionCountry, + state: null, + regionCode: 'cr', + }, + }, + }, + }, + async ({ controller, rootMessenger }) => { + rootMessenger.registerActionHandler( + 'RampsService:getCountries', + async () => otherCountries, + ); + + await rootMessenger.call('RampsController:getCountries'); + + expect(controller.state.countries.data).toStrictEqual(otherCountries); + expect(controller.state.userRegion?.country.isoCode).toBe('CR'); + expect(controller.state.userRegion?.country.defaultAmount).toBe(100); + }, + ); + }); + it('throws when updating resource field and resource is null', async () => { const stateWithNullCountries = { ...getDefaultRampsControllerState(),