Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions packages/ramps-controller/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

### Fixed

- `setSelectedProvider` no longer fetches payment methods when no token is selected ([#8342](https://github.com/MetaMask/core/pull/8342))
- Previously, the `tokenSupportedByProvider` guard evaluated to `true` when no token was selected (vacuous truth), causing an unfiltered payment methods fetch with an empty `crypto` parameter. The guard now requires a token to be selected before triggering the fetch.

## [12.1.0]

### Added
Expand Down
55 changes: 53 additions & 2 deletions packages/ramps-controller/src/RampsController.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2869,11 +2869,21 @@ describe('RampsController', () => {
);
});

it('fetches getPaymentMethods when provider has no supportedCryptoCurrencies field', async () => {
it('fetches getPaymentMethods when provider has no supportedCryptoCurrencies field and a token is selected', async () => {
const providerWithoutField: Provider = { ...mockProvider };
delete (providerWithoutField as Partial<Provider>)
.supportedCryptoCurrencies;

const selectedToken: RampsToken = {
assetId: 'eip155:1/slip44:60',
chainId: 'eip155:1',
name: 'Ether',
symbol: 'ETH',
decimals: 18,
iconUrl: '',
tokenSupported: true,
};

const getPaymentMethodsMock = jest.fn(async () => ({ payments: [] }));

await withController(
Expand All @@ -2882,6 +2892,7 @@ describe('RampsController', () => {
state: {
userRegion: createMockUserRegion('us-ca'),
providers: createResourceState([providerWithoutField], null),
tokens: createResourceState(null, selectedToken),
},
},
},
Expand All @@ -2903,6 +2914,36 @@ describe('RampsController', () => {
);
});

it('skips getPaymentMethods when no token is selected', async () => {
const getPaymentMethodsMock = jest.fn(async () => ({ payments: [] }));

await withController(
{
options: {
state: {
userRegion: createMockUserRegion('us-ca'),
providers: createResourceState([mockProvider], null),
},
},
},
async ({ rootMessenger }) => {
rootMessenger.registerActionHandler(
'RampsService:getPaymentMethods',
getPaymentMethodsMock,
);

rootMessenger.call(
'RampsController:setSelectedProvider',
mockProvider.id,
);

await new Promise((resolve) => setTimeout(resolve, 0));

expect(getPaymentMethodsMock).not.toHaveBeenCalled();
},
);
});

it('fetches getPaymentMethods when selected token is explicitly supported by the new provider', async () => {
const supportedToken: RampsToken = {
assetId: 'eip155:1/slip44:60',
Expand Down Expand Up @@ -4417,6 +4458,16 @@ describe('RampsController', () => {
});

it('does not update paymentMethods when selectedProvider changes during request', async () => {
const selectedToken: RampsToken = {
assetId: 'eip155:1/slip44:60',
chainId: 'eip155:1',
name: 'Ether',
symbol: 'ETH',
decimals: 18,
iconUrl: '',
tokenSupported: true,
};

const providerA: Provider = {
id: '/providers/provider-a',
name: 'Provider A',
Expand Down Expand Up @@ -4472,7 +4523,7 @@ describe('RampsController', () => {
options: {
state: {
userRegion: createMockUserRegion('us-ca'),
tokens: createResourceState(null, null),
tokens: createResourceState(null, selectedToken),
providers: createResourceState([providerA, providerB], providerA),
paymentMethods: createResourceState([], null),
},
Expand Down
15 changes: 7 additions & 8 deletions packages/ramps-controller/src/RampsController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1228,22 +1228,21 @@ export class RampsController extends BaseController<
const selectedToken = this.state.tokens.selected;
const supportedCryptos = provider.supportedCryptoCurrencies;

// Only fetch payment methods if the selected token is supported by the new
// provider. If it isn't, the payment methods request would fail or return
// empty for the wrong reason; the UI will show the Token Not Available modal
// so the user can change token or pick a different provider.
// Only fetch payment methods when a token is selected AND that token is
// supported by the new provider. Without a token the API receives an empty
// `crypto` param and returns an unfiltered list — payment methods must
// always be scoped to a specific token.
const assetId = selectedToken?.assetId;
const tokenSupportedByProvider = !(
assetId && supportedCryptos?.[assetId] === false
);
const shouldFetchPaymentMethods =
Boolean(assetId) && !(supportedCryptos?.[assetId as string] === false);

this.update((state) => {
state.providers.selected = provider;
state.providerAutoSelected = options?.autoSelected ?? false;
resetResource(state, 'paymentMethods');
});

if (tokenSupportedByProvider) {
if (shouldFetchPaymentMethods) {
this.#fireAndForget(
this.getPaymentMethods(regionCode, { provider: provider.id }),
);
Expand Down
Loading