From 26b22b0a8914db45b06737e9a1c994fb4f7505ed Mon Sep 17 00:00:00 2001 From: Amitabh Aggarwal Date: Fri, 5 Jun 2026 21:41:37 -0500 Subject: [PATCH 1/5] refactor(ramps-controller): extract resolveBestSupportingProvider from quote resolution --- .../ramps-controller/src/RampsController.ts | 119 +++++++++++++----- 1 file changed, 86 insertions(+), 33 deletions(-) diff --git a/packages/ramps-controller/src/RampsController.ts b/packages/ramps-controller/src/RampsController.ts index 5993c6b4c9..dc0ce28900 100644 --- a/packages/ramps-controller/src/RampsController.ts +++ b/packages/ramps-controller/src/RampsController.ts @@ -1965,6 +1965,68 @@ export class RampsController extends BaseController< return providerIds.filter((id) => supportingIds.has(id)); } + /** + * Picks the single best provider from the supporting set using this + * precedence (without any list-level fallbacks): + * 1. The currently selected provider, if it is in the supporting set. + * 2. The first preferred provider that supports the asset, where the + * preference is taken from `preferredProviderIds` when supplied, otherwise + * derived from the user's completed-order history (most recent first). + * This takes priority over Transak Native to preserve an existing KYC + * relationship. + * 3. A native provider (e.g. Transak Native). + * 4. The first supporting provider. + * + * Returns `null` when `supporting` is empty. + * + * @param options - The options. + * @param options.supporting - Providers that support the asset in this region. + * @param options.preferredProviderIds - Provider IDs to prefer, in order. + * @returns The best provider, or null if supporting is empty. + */ + #resolveBestSupportingProvider({ + supporting, + preferredProviderIds, + }: { + supporting: Provider[]; + preferredProviderIds?: string[]; + }): Provider | null { + if (supporting.length === 0) { + return null; + } + + // 1. The currently selected provider, if it supports the asset. + const { selected } = this.state.providers; + if (selected && supporting.some((provider) => provider.id === selected.id)) { + return selected; + } + + // 2. A provider the user has transacted with before — from + // `preferredProviderIds` when supplied, otherwise completed-order + // history. Takes priority over Transak Native to preserve an existing + // KYC relationship and avoid churn. + const preferred = + preferredProviderIds ?? this.#getPreferredProviderIdsFromOrders(); + + for (const preferredId of preferred) { + const match = supporting.find((provider) => provider.id === preferredId); + if (match) { + return match; + } + } + + // 3. A native provider (e.g. Transak Native). + const nativeProvider = supporting.find( + (provider) => provider.type === 'native', + ); + if (nativeProvider) { + return nativeProvider; + } + + // 4. Fallback: first supporting provider. + return supporting[0]; + } + /** * Resolves the provider IDs to use for a single quote request, scoped to the * given asset and region. Does not mutate `providers.selected` or any other @@ -2015,44 +2077,35 @@ export class RampsController extends BaseController< return all.map((provider) => provider.id); } - // 1. The currently selected provider, if it supports the asset. - const { selected } = this.state.providers; - if ( - selected && - supporting.some((provider) => provider.id === selected.id) - ) { - return [selected.id]; - } - - // 2. A provider the user has transacted with before — from - // `preferredProviderIds` when supplied, otherwise completed-order - // history. Takes priority over Transak Native to preserve an existing - // KYC relationship and avoid churn. - const preferred = - preferredProviderIds ?? this.#getPreferredProviderIdsFromOrders(); + const best = this.#resolveBestSupportingProvider({ + supporting, + preferredProviderIds, + }); - for (const preferredId of preferred) { - const match = supporting.find((provider) => provider.id === preferredId); - if (match) { - return [match.id]; - } + // Under headless gating with no best provider (empty supporting set), + // return nothing so the caller surfaces an "unavailable" state. + if (!best) { + return []; } - // 3. A native provider (e.g. Transak Native). - const nativeProvider = supporting.find( - (provider) => provider.type === 'native', - ); - if (nativeProvider) { - return [nativeProvider.id]; + // Under headless gating, only a native provider is accepted — any non-native + // fallback (step 4) is rejected. + if (restrictToKnownOrNative && best.type !== 'native') { + // Check if the best was chosen because of selected/preferred/native logic. + // The only case where we must block is when #resolveBestSupportingProvider + // fell through to step 4 (first-supporting). That happens iff best is not + // selected, not preferred, and not native. + const { selected } = this.state.providers; + const isSelected = selected?.id === best.id; + const preferred = + preferredProviderIds ?? this.#getPreferredProviderIdsFromOrders(); + const isPreferred = preferred.includes(best.id); + if (!isSelected && !isPreferred) { + return []; + } } - // 4. Fallback. Under headless gating, introduce no other provider — return - // nothing so the caller surfaces an "unavailable" state. Otherwise the - // aggregator and all other providers are treated equally: first wins. - if (restrictToKnownOrNative) { - return []; - } - return [supporting[0].id]; + return [best.id]; } /** From 73f972a4c2cb29a2dcab41a802f470558846fa50 Mon Sep 17 00:00:00 2001 From: Amitabh Aggarwal Date: Fri, 5 Jun 2026 21:47:15 -0500 Subject: [PATCH 2/5] feat(ramps-controller): add getBestProviderForAsset messenger accessor --- packages/ramps-controller/CHANGELOG.md | 1 + .../RampsController-method-action-types.ts | 26 ++++ .../src/RampsController.test.ts | 131 ++++++++++++++++++ .../ramps-controller/src/RampsController.ts | 41 ++++++ packages/ramps-controller/src/index.ts | 1 + 5 files changed, 200 insertions(+) diff --git a/packages/ramps-controller/CHANGELOG.md b/packages/ramps-controller/CHANGELOG.md index 65e3550c69..fe7c1f0e84 100644 --- a/packages/ramps-controller/CHANGELOG.md +++ b/packages/ramps-controller/CHANGELOG.md @@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added +- Add `getBestProviderForAsset` public method (and `RampsController:getBestProviderForAsset` messenger action) that returns the best `Provider` supporting a given CAIP-19 asset in the current (or supplied) region, using the same cascade as quote auto-selection (selected → preferred from order history → native → first supporting), without mutating any controller state ([#XXXX](https://github.com/MetaMask/core/pull/XXXX)) - Authenticate `RampsService.getPaymentMethods` and `RampsService.getQuotes` by sourcing a bearer token from `AuthenticationController:getBearerToken` and sending it as an `Authorization: Bearer ` header ([#8888](https://github.com/MetaMask/core/pull/8888)) ## [14.1.1] diff --git a/packages/ramps-controller/src/RampsController-method-action-types.ts b/packages/ramps-controller/src/RampsController-method-action-types.ts index 41d5a884af..ef6805109a 100644 --- a/packages/ramps-controller/src/RampsController-method-action-types.ts +++ b/packages/ramps-controller/src/RampsController-method-action-types.ts @@ -227,6 +227,31 @@ export type RampsControllerGetQuotesAction = { handler: RampsController['getQuotes']; }; +/** + * Returns the best provider that supports the given asset in the specified + * region (defaulting to the current user region), using the same selection + * cascade as quote auto-selection: + * 1. The currently selected provider, if it supports the asset. + * 2. The first provider from the user's completed-order history that + * supports the asset. + * 3. A native provider (e.g. Transak Native). + * 4. The first supporting provider. + * + * Read-only: does not mutate `providers.selected`, `providerAutoSelected`, + * or any other controller state. + * + * @param options - The options. + * @param options.assetId - CAIP-19 asset type identifier to resolve for. + * @param options.region - Region code to resolve against. Defaults to the + * current user region's region code. Returns null if no region available. + * @returns The best supporting Provider, or null if none supports the asset + * or no region is available. + */ +export type RampsControllerGetBestProviderForAssetAction = { + type: `RampsController:getBestProviderForAsset`; + handler: RampsController['getBestProviderForAsset']; +}; + /** * Adds or updates a V2 order in controller state. * If an order with the same providerOrderId already exists, the incoming @@ -641,6 +666,7 @@ export type RampsControllerMethodActions = | RampsControllerGetPaymentMethodsAction | RampsControllerSetSelectedPaymentMethodAction | RampsControllerGetQuotesAction + | RampsControllerGetBestProviderForAssetAction | RampsControllerAddOrderAction | RampsControllerRemoveOrderAction | RampsControllerStartOrderPollingAction diff --git a/packages/ramps-controller/src/RampsController.test.ts b/packages/ramps-controller/src/RampsController.test.ts index 7e1fd0d1b9..7d21be0f22 100644 --- a/packages/ramps-controller/src/RampsController.test.ts +++ b/packages/ramps-controller/src/RampsController.test.ts @@ -7131,6 +7131,137 @@ describe('RampsController', () => { }); }); + describe('getBestProviderForAsset', () => { + const ASSET_ETH = 'eip155:1/slip44:60'; + const ASSET_USDC = + 'eip155:42161/erc20:0xaf88d065e77c8cc2239327c5edb3a432268e5831'; + + const moonpay = createMockProvider({ + id: '/providers/moonpay', + name: 'MoonPay', + type: 'aggregator', + supportedCryptoCurrencies: { [ASSET_USDC]: true }, + }); + const transakNative = createMockProvider({ + id: '/providers/transak-native', + name: 'Transak Native', + type: 'native', + supportedCryptoCurrencies: { [ASSET_USDC]: true }, + }); + + it('returns the best supporting provider for the given asset', async () => { + await withController( + { + options: { + state: { + userRegion: createMockUserRegion('us'), + providers: createResourceState([moonpay, transakNative], null), + }, + }, + }, + async ({ controller }) => { + const result = await controller.getBestProviderForAsset({ + assetId: ASSET_USDC, + }); + + // moonpay is first-supporting (step 4), transakNative is native (step 3) + // step 3 (native) takes priority over step 4 (first-supporting) + expect(result).toStrictEqual(transakNative); + }, + ); + }); + + it('returns null when no provider supports the asset', async () => { + const ethOnly = createMockProvider({ + id: '/providers/eth-only', + name: 'ETH Only', + type: 'aggregator', + supportedCryptoCurrencies: { [ASSET_ETH]: true }, + }); + + await withController( + { + options: { + state: { + userRegion: createMockUserRegion('us'), + providers: createResourceState([ethOnly], null), + }, + }, + }, + async ({ controller }) => { + const result = await controller.getBestProviderForAsset({ + assetId: ASSET_USDC, + }); + + expect(result).toBeNull(); + }, + ); + }); + + it('returns null when no user region is set', async () => { + await withController( + { + options: { + state: { + userRegion: null, + providers: createResourceState([moonpay], null), + }, + }, + }, + async ({ controller }) => { + const result = await controller.getBestProviderForAsset({ + assetId: ASSET_USDC, + }); + + expect(result).toBeNull(); + }, + ); + }); + + it('is callable via messenger.call', async () => { + await withController( + { + options: { + state: { + userRegion: createMockUserRegion('us'), + providers: createResourceState([moonpay, transakNative], null), + }, + }, + }, + async ({ rootMessenger }) => { + const result = await rootMessenger.call( + 'RampsController:getBestProviderForAsset', + { assetId: ASSET_USDC }, + ); + + expect(result).toStrictEqual(transakNative); + }, + ); + }); + + it('does not mutate providers.selected after the call', async () => { + const preSetSelected = moonpay; + + await withController( + { + options: { + state: { + userRegion: createMockUserRegion('us'), + providers: createResourceState([moonpay, transakNative], preSetSelected), + }, + }, + }, + async ({ controller }) => { + await controller.getBestProviderForAsset({ assetId: ASSET_USDC }); + + expect(controller.state.providers.selected).toStrictEqual( + preSetSelected, + ); + }, + ); + }); + }); + describe('getOrder', () => { const mockOrder = { id: '/providers/transak-staging/orders/abc-123', diff --git a/packages/ramps-controller/src/RampsController.ts b/packages/ramps-controller/src/RampsController.ts index dc0ce28900..dafa9d573c 100644 --- a/packages/ramps-controller/src/RampsController.ts +++ b/packages/ramps-controller/src/RampsController.ts @@ -787,6 +787,7 @@ const MESSENGER_EXPOSED_METHODS = [ 'addPrecreatedOrder', 'getOrder', 'getOrderFromCallback', + 'getBestProviderForAsset', 'transakSetApiKey', 'transakSetAccessToken', 'transakClearAccessToken', @@ -2108,6 +2109,46 @@ export class RampsController extends BaseController< return [best.id]; } + /** + * Returns the best provider that supports the given asset in the specified + * region (defaulting to the current user region), using the same selection + * cascade as quote auto-selection: + * 1. The currently selected provider, if it supports the asset. + * 2. The first provider from the user's completed-order history that + * supports the asset. + * 3. A native provider (e.g. Transak Native). + * 4. The first supporting provider. + * + * Read-only: does not mutate `providers.selected`, `providerAutoSelected`, + * or any other controller state. + * + * @param options - The options. + * @param options.assetId - CAIP-19 asset type identifier to resolve for. + * @param options.region - Region code to resolve against. Defaults to the + * current user region's region code. Returns null if no region available. + * @returns The best supporting Provider, or null if none supports the asset + * or no region is available. + */ + async getBestProviderForAsset({ + assetId, + region, + }: { + assetId: string; + region?: string; + }): Promise { + const regionCode = region ?? this.state.userRegion?.regionCode; + if (!regionCode) { + return null; + } + + const { supporting } = await this.#getSupportingProvidersForRegion({ + assetId, + region: regionCode, + }); + + return this.#resolveBestSupportingProvider({ supporting }); + } + /** * Derives an ordered list of provider IDs from the user's completed-order * history, most recently completed first, with duplicates removed. diff --git a/packages/ramps-controller/src/index.ts b/packages/ramps-controller/src/index.ts index c1ec4d850c..19224e48b2 100644 --- a/packages/ramps-controller/src/index.ts +++ b/packages/ramps-controller/src/index.ts @@ -34,6 +34,7 @@ export type { RampsControllerAddPrecreatedOrderAction, RampsControllerGetOrderAction, RampsControllerGetOrderFromCallbackAction, + RampsControllerGetBestProviderForAssetAction, RampsControllerTransakSetApiKeyAction, RampsControllerTransakSetAccessTokenAction, RampsControllerTransakClearAccessTokenAction, From d04a0a17bf4b7118b9bd717cd2f01f3e3da3a09a Mon Sep 17 00:00:00 2001 From: Amitabh Aggarwal Date: Fri, 5 Jun 2026 22:00:45 -0500 Subject: [PATCH 3/5] chore(ramps-controller): address review feedback on getBestProviderForAsset Co-Authored-By: Claude Opus 4.8 (1M context) --- .../RampsController-method-action-types.ts | 9 +-- .../src/RampsController.test.ts | 56 +++++++++++++++++++ .../ramps-controller/src/RampsController.ts | 19 +++---- 3 files changed, 68 insertions(+), 16 deletions(-) diff --git a/packages/ramps-controller/src/RampsController-method-action-types.ts b/packages/ramps-controller/src/RampsController-method-action-types.ts index ef6805109a..6b4c3e07dd 100644 --- a/packages/ramps-controller/src/RampsController-method-action-types.ts +++ b/packages/ramps-controller/src/RampsController-method-action-types.ts @@ -232,8 +232,7 @@ export type RampsControllerGetQuotesAction = { * region (defaulting to the current user region), using the same selection * cascade as quote auto-selection: * 1. The currently selected provider, if it supports the asset. - * 2. The first provider from the user's completed-order history that - * supports the asset. + * 2. The first order-history provider that supports the asset. * 3. A native provider (e.g. Transak Native). * 4. The first supporting provider. * @@ -242,10 +241,8 @@ export type RampsControllerGetQuotesAction = { * * @param options - The options. * @param options.assetId - CAIP-19 asset type identifier to resolve for. - * @param options.region - Region code to resolve against. Defaults to the - * current user region's region code. Returns null if no region available. - * @returns The best supporting Provider, or null if none supports the asset - * or no region is available. + * @param options.region - Region code to resolve against; defaults to the current user region's region code. Returns null if no region available. + * @returns The best supporting Provider, or null if none supports the asset or no region is available. */ export type RampsControllerGetBestProviderForAssetAction = { type: `RampsController:getBestProviderForAsset`; diff --git a/packages/ramps-controller/src/RampsController.test.ts b/packages/ramps-controller/src/RampsController.test.ts index 7d21be0f22..79bd70d62f 100644 --- a/packages/ramps-controller/src/RampsController.test.ts +++ b/packages/ramps-controller/src/RampsController.test.ts @@ -7260,6 +7260,62 @@ describe('RampsController', () => { }, ); }); + + it('uses the explicit region override instead of the user region', async () => { + await withController( + { + options: { + state: { + // User region differs from the requested region, and its + // providers are not cached, so the explicit region must drive a + // fresh fetch. + userRegion: createMockUserRegion('fr'), + }, + }, + }, + async ({ controller, rootMessenger }) => { + let requestedRegion: string | undefined; + rootMessenger.registerActionHandler( + 'RampsService:getProviders', + async (region) => { + requestedRegion = region; + return { providers: [moonpay, transakNative] }; + }, + ); + + const result = await controller.getBestProviderForAsset({ + assetId: ASSET_USDC, + region: 'us', + }); + + expect(requestedRegion).toBe('us'); + expect(result).toStrictEqual(transakNative); + }, + ); + }); + + it('returns the selected provider when it supports the asset', async () => { + await withController( + { + options: { + state: { + userRegion: createMockUserRegion('us'), + // moonpay is selected; transakNative would otherwise win via the + // native step, so this proves the selected provider takes + // precedence. + providers: createResourceState([moonpay, transakNative], moonpay), + }, + }, + }, + async ({ controller }) => { + const result = await controller.getBestProviderForAsset({ + assetId: ASSET_USDC, + }); + + expect(result).toStrictEqual(moonpay); + }, + ); + }); }); describe('getOrder', () => { diff --git a/packages/ramps-controller/src/RampsController.ts b/packages/ramps-controller/src/RampsController.ts index dafa9d573c..90425b78b2 100644 --- a/packages/ramps-controller/src/RampsController.ts +++ b/packages/ramps-controller/src/RampsController.ts @@ -2078,9 +2078,14 @@ export class RampsController extends BaseController< return all.map((provider) => provider.id); } + // Resolve the preference list once and reuse it both for the pick and the + // restriction guard below, avoiding a duplicate order-history derivation. + const preferred = + preferredProviderIds ?? this.#getPreferredProviderIdsFromOrders(); + const best = this.#resolveBestSupportingProvider({ supporting, - preferredProviderIds, + preferredProviderIds: preferred, }); // Under headless gating with no best provider (empty supporting set), @@ -2092,14 +2097,11 @@ export class RampsController extends BaseController< // Under headless gating, only a native provider is accepted — any non-native // fallback (step 4) is rejected. if (restrictToKnownOrNative && best.type !== 'native') { - // Check if the best was chosen because of selected/preferred/native logic. // The only case where we must block is when #resolveBestSupportingProvider // fell through to step 4 (first-supporting). That happens iff best is not // selected, not preferred, and not native. const { selected } = this.state.providers; const isSelected = selected?.id === best.id; - const preferred = - preferredProviderIds ?? this.#getPreferredProviderIdsFromOrders(); const isPreferred = preferred.includes(best.id); if (!isSelected && !isPreferred) { return []; @@ -2114,8 +2116,7 @@ export class RampsController extends BaseController< * region (defaulting to the current user region), using the same selection * cascade as quote auto-selection: * 1. The currently selected provider, if it supports the asset. - * 2. The first provider from the user's completed-order history that - * supports the asset. + * 2. The first order-history provider that supports the asset. * 3. A native provider (e.g. Transak Native). * 4. The first supporting provider. * @@ -2124,10 +2125,8 @@ export class RampsController extends BaseController< * * @param options - The options. * @param options.assetId - CAIP-19 asset type identifier to resolve for. - * @param options.region - Region code to resolve against. Defaults to the - * current user region's region code. Returns null if no region available. - * @returns The best supporting Provider, or null if none supports the asset - * or no region is available. + * @param options.region - Region code to resolve against; defaults to the current user region's region code. Returns null if no region available. + * @returns The best supporting Provider, or null if none supports the asset or no region is available. */ async getBestProviderForAsset({ assetId, From a607973c70fd2efc87e8a89e8429c55e0e312e81 Mon Sep 17 00:00:00 2001 From: Amitabh Aggarwal Date: Fri, 5 Jun 2026 22:28:19 -0500 Subject: [PATCH 4/5] fix(ramps-controller): return region-resolved provider in getBestProviderForAsset selected-step The selected-provider branch returned state.providers.selected, whose region-specific fields (e.g. limits) may describe a different region than the one resolved here (notably under a region override). Return the matching entry from the region-resolved supporting list instead. Quote path unaffected (uses only .id). Adds a region-override regression test. Co-Authored-By: Claude Opus 4.8 (1M context) --- .../src/RampsController.test.ts | 77 +++++++++++++++++++ .../ramps-controller/src/RampsController.ts | 12 ++- 2 files changed, 86 insertions(+), 3 deletions(-) diff --git a/packages/ramps-controller/src/RampsController.test.ts b/packages/ramps-controller/src/RampsController.test.ts index 79bd70d62f..d01a71d1d4 100644 --- a/packages/ramps-controller/src/RampsController.test.ts +++ b/packages/ramps-controller/src/RampsController.test.ts @@ -7316,6 +7316,83 @@ describe('RampsController', () => { }, ); }); + + it('returns the region-resolved selected provider, not the stale state object, under a region override', async () => { + // The selected provider in state carries limits scoped to the user's + // region (fr). The override region (us) returns the same provider id but + // with distinct, region-specific limits. The selected-step must return the + // region-resolved entry so callers get the correct region's data. + const selectedFrLimits = { + fiat: { + eur: { + '/payments/debit-credit-card': { + minAmount: 10, + maxAmount: 100, + feeFixedRate: 1, + feeDynamicRate: 0.01, + }, + }, + }, + }; + const moonpayFr = createMockProvider({ + id: '/providers/moonpay', + name: 'MoonPay', + type: 'aggregator', + supportedCryptoCurrencies: { [ASSET_USDC]: true }, + limits: selectedFrLimits, + }); + const usLimits = { + fiat: { + usd: { + '/payments/debit-credit-card': { + minAmount: 20, + maxAmount: 500, + feeFixedRate: 2, + feeDynamicRate: 0.02, + }, + }, + }, + }; + const moonpayUs = createMockProvider({ + id: '/providers/moonpay', + name: 'MoonPay', + type: 'aggregator', + supportedCryptoCurrencies: { [ASSET_USDC]: true }, + limits: usLimits, + }); + + await withController( + { + options: { + state: { + userRegion: createMockUserRegion('fr'), + // Selected object reflects the fr region's limits. + providers: createResourceState([moonpayFr], moonpayFr), + }, + }, + }, + async ({ controller, rootMessenger }) => { + rootMessenger.registerActionHandler( + 'RampsService:getProviders', + async () => { + return { providers: [moonpayUs] }; + }, + ); + + const result = await controller.getBestProviderForAsset({ + assetId: ASSET_USDC, + region: 'us', + }); + + // Same id as state.providers.selected, but must be the region-resolved + // entry with the us limits — never the stale fr selected object. + expect(result?.id).toBe('/providers/moonpay'); + expect(result?.limits).toStrictEqual(usLimits); + expect(result).toBe(moonpayUs); + expect(result).not.toBe(moonpayFr); + }, + ); + }); }); describe('getOrder', () => { diff --git a/packages/ramps-controller/src/RampsController.ts b/packages/ramps-controller/src/RampsController.ts index 90425b78b2..698675d85d 100644 --- a/packages/ramps-controller/src/RampsController.ts +++ b/packages/ramps-controller/src/RampsController.ts @@ -1996,10 +1996,16 @@ export class RampsController extends BaseController< return null; } - // 1. The currently selected provider, if it supports the asset. + // 1. The currently selected provider, if it supports the asset. Return the + // region-resolved entry from `supporting` (not `state.providers.selected`), + // since the selected object may carry region-specific data (e.g. limits) + // for a different region than the one resolved here. const { selected } = this.state.providers; - if (selected && supporting.some((provider) => provider.id === selected.id)) { - return selected; + if (selected) { + const match = supporting.find((provider) => provider.id === selected.id); + if (match) { + return match; + } } // 2. A provider the user has transacted with before — from From 3e9ef3c5afb71c22e91d2ee69d22f7e34e8b8724 Mon Sep 17 00:00:00 2001 From: Amitabh Aggarwal Date: Fri, 5 Jun 2026 22:44:15 -0500 Subject: [PATCH 5/5] docs(ramps-controller): note reliability-order dependency on backend sort Co-Authored-By: Claude Opus 4.8 (1M context) --- packages/ramps-controller/src/RampsController.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/ramps-controller/src/RampsController.ts b/packages/ramps-controller/src/RampsController.ts index 698675d85d..bdb55b64b3 100644 --- a/packages/ramps-controller/src/RampsController.ts +++ b/packages/ramps-controller/src/RampsController.ts @@ -2030,7 +2030,11 @@ export class RampsController extends BaseController< return nativeProvider; } - // 4. Fallback: first supporting provider. + // 4. Fallback: first supporting provider. This is the most reliable + // supporting provider ONLY because the backend returns providers in + // reliability order (regions-v2 default sort) and the controller + // preserves that order (no re-sort). If the backend stops sorting, + // this becomes an arbitrary first rather than "most reliable". return supporting[0]; }