Skip to content

Commit 03597ba

Browse files
feat: Integrate Geolocation Controller (MetaMask#26730)
## **Description** Register the new `@metamask/geolocation-controller` in the mobile Engine and migrate all 4 existing geolocation consumers (Ramp, Perps, Rewards, Card) to use the centralised controller instead of their individual implementations. ### Problem Four separate features independently fetch geolocation from the same API, each with their own URL constants, caching logic, and error handling: - **Ramp** — `useDetectGeolocation` hook with `GEOLOCATION_URLS` constant - **Perps** — `EligibilityService.fetchGeoLocation()` with `ON_RAMP_GEO_BLOCKING_URLS`, TTL caching, and promise deduplication - **Rewards** — `RewardsDataService.fetchGeoLocation()` with `GEOLOCATION_URLS` constant - **Card** — `CardSDK.getGeoLocation()` with a hardcoded URL This results in 2–4 redundant API calls on app load and duplicated code across the codebase. ### Solution The core `@metamask/geolocation-controller` package provides two components: - **`GeolocationApiService`** — handles HTTP fetch, TTL caching (5 min), and promise deduplication - **`GeolocationController`** — manages UI-facing state (`location`, `status`, `lastFetchedAt`, `error`) and delegates to the ApiService via messenger This PR: 1. **Registers both components** in the Engine following the established `RewardsController`/`RewardsDataService` pattern 2. **Eagerly fetches** geolocation on Engine start so the value is available before any consumer needs it 3. **Migrates all 4 consumers** to read from the centralised controller: - **Ramp**: Deleted `useDetectGeolocation` hook; redefined `getDetectedGeolocation` selector to read from `GeolocationController` state (preserves the 10+ downstream consumer files unchanged) - **Perps**: `refreshEligibility()` calls `GeolocationController:getGeolocation` via messenger and passes the result to `checkEligibility()` as a parameter; removed all geolocation-fetching code from `EligibilityService` - **Rewards**: Replaced `RewardsDataService:fetchGeoLocation` messenger call with `GeolocationController:getGeolocation` in `RewardsController.getGeoRewardsMetadata()`; removed `fetchGeoLocation()` method from `RewardsDataService` - **Card**: Replaced `cardSDK.getGeoLocation()` with `Engine.context.GeolocationController?.state?.location` in `getCardholder.ts`; removed `getGeoLocation()` from `CardSDK` 4. **Removes all duplicate** `GEOLOCATION_URLS`, `ON_RAMP_GEO_BLOCKING_URLS`, and hardcoded geolocation URL constants ### Files changed summary **Created (6 files):** | File | Purpose | |------|---------| | `app/core/Engine/controllers/geolocation-api-service-init.ts` | Init function with environment-aware `SDK.Env` mapping | | `app/core/Engine/messengers/geolocation-api-service-messenger.ts` | Simple messenger factory (no delegated actions) | | `app/core/Engine/controllers/geolocation-controller/index.ts` | Init function with eager geolocation fetch | | `app/core/Engine/messengers/geolocation-controller-messenger/index.ts` | Messenger delegating `GeolocationApiService:fetchGeolocation` | | `app/selectors/geolocationController/index.ts` | Redux selectors for controller state | | `app/core/Engine/controllers/geolocation-api-service-init.test.ts` | Unit tests for env mapping | **Created (test, 1 file):** | File | Purpose | |------|---------| | `app/core/Engine/controllers/geolocation-controller/index.test.ts` | Unit tests for init + eager fetch | **Deleted (2 files):** | File | Reason | |------|--------| | `app/components/UI/Ramp/hooks/useDetectGeolocation.ts` | Replaced by centralised controller | | `app/components/UI/Ramp/hooks/useDetectGeolocation.test.ts` | Tests for deleted hook | **Modified — Engine registration (5 files):** - `package.json` — added `@metamask/geolocation-controller` dependency - `app/core/Engine/Engine.ts` — registered both init functions - `app/core/Engine/types.ts` — added to `Controllers`, `EngineState`, `ControllersToInitialize`, `GlobalActions`, `GlobalEvents` - `app/core/Engine/constants.ts` — added `GeolocationController:stateChange` - `app/core/Engine/messengers/index.ts` — registered both messenger factories **Modified — Consumer migration (9 files):** - `app/components/UI/Ramp/index.tsx` — removed `useDetectGeolocation()` call - `app/reducers/fiatOrders/index.ts` — redefined `getDetectedGeolocation` to read from controller - `app/controllers/perps/PerpsController.ts` — calls `GeolocationController:getGeolocation` via messenger - `app/controllers/perps/services/EligibilityService.ts` — removed all geolocation-fetching code - `app/controllers/perps/types/index.ts` — added `geoLocation` to `CheckEligibilityParams` - `app/core/Engine/messengers/perps-controller-messenger/index.ts` — delegated `GeolocationController:getGeolocation` - `app/core/Engine/controllers/rewards-controller/services/rewards-data-service.ts` — removed `fetchGeoLocation()`, `GEOLOCATION_URLS`, action type - `app/core/Engine/controllers/rewards-controller/services/index.ts` — removed re-export - `app/core/Engine/controllers/rewards-controller/RewardsController.ts` — calls `GeolocationController:getGeolocation` - `app/core/Engine/messengers/rewards-controller-messenger/index.ts` — added/removed delegated actions - `app/components/UI/Card/sdk/CardSDK.ts` — removed `getGeoLocation()` method - `app/components/UI/Card/util/getCardholder.ts` — reads from `Engine.context` **Modified — Tests (5 files):** - `app/controllers/perps/services/EligibilityService.test.ts` — tests `checkEligibility` with `geoLocation` param - `app/controllers/perps/PerpsController.test.ts` — updated mock - `app/core/Engine/controllers/rewards-controller/services/rewards-data-service.test.ts` — removed `fetchGeoLocation` tests - `app/core/Engine/controllers/rewards-controller/RewardsController.test.ts` — updated messenger call references - `app/components/UI/Card/sdk/CardSDK.test.ts` — removed `getGeoLocation` tests - `app/components/UI/Card/util/getCardholder.test.ts` — mocks `Engine.context` instead of `cardSDK.getGeoLocation()` - `app/reducers/fiatOrders/index.test.ts` — updated `getDetectedGeolocation` tests ## **Changelog** CHANGELOG entry: null ## **Related issues** Refs: https://consensyssoftware.atlassian.net/browse/MCWP-350 ## **Manual testing steps** ```gherkin Feature: Centralised geolocation via GeolocationController Scenario: Only one geolocation API call is made on app load Given the app is freshly launched When the Engine initialises Then exactly 1 request to on-ramp.api.cx.metamask.io/geolocation is made And no duplicate geolocation requests are made by Ramp, Perps, Rewards, or Card Scenario: Ramp region detection uses controller-sourced geolocation Given the user opens the Buy/Sell flow When the Ramp UI loads Then the detected region matches the user's actual location And no separate geolocation fetch is triggered by the Ramp hook Scenario: Perps eligibility check uses controller-sourced geolocation Given the user navigates to the Perps feature When eligibility is checked Then the eligibility result is correct for the user's region And EligibilityService does not make its own geolocation API call Scenario: Rewards geo-blocking uses controller-sourced geolocation Given the user accesses the Rewards feature When getGeoRewardsMetadata is called Then geo-blocking works correctly (UK/GB/GI users are blocked from opt-in) And RewardsDataService does not make its own geolocation API call Scenario: Card country detection uses controller-sourced geolocation Given the user accesses the Card feature When cardholder accounts are loaded Then the correct country code is returned And CardSDK does not make its own geolocation API call Scenario: Geolocation re-fetches after TTL expires Given geolocation was fetched more than 5 minutes ago When a consumer requests geolocation Then a fresh API call is made And the cached value is updated ``` ## **Screenshots/Recordings** ### **Before** N/A — no UI changes, backend/controller wiring only. ### **After** https://github.com/user-attachments/assets/793c67b5-fbbd-48b9-a8e3-4e4924257c0b N/A — no UI changes, backend/controller wiring only. ## **Pre-merge author checklist** - [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. ## **Pre-merge reviewer checklist** - [x] I've manually tested the PR (e.g. pull and build branch, run the app, test code being changed). - [x] 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 Engine initialization and multiple feature flows (Ramp/Perps/Rewards/Card) by changing the geolocation source and state shape, so regressions could impact region-gating and routing decisions. Logic is mostly wiring/migration, but it changes when/where geolocation is fetched (eager on Engine start). > > **Overview** > Integrates `@metamask/geolocation-controller` into the mobile Engine by registering `GeolocationApiService` and `GeolocationController`, adding messengers/types/background-state plumbing, and **eagerly fetching** geolocation on Engine startup. > > Migrates geolocation consumers to the centralized controller: Ramp drops `useDetectGeolocation` and `fiatOrders.detectedGeolocation` (selector now reads `engine.backgroundState.GeolocationController.location`), Perps passes controller-provided `geoLocation` into `EligibilityService.checkEligibility`, Rewards switches `RewardsController` to call `GeolocationController:getGeolocation` and removes `RewardsDataService.fetchGeoLocation`, and Card removes `CardSDK.getGeoLocation` in favor of `Engine.controllerMessenger.call('GeolocationController:getGeolocation')` in `getCardholder`. > > Updates unit/E2E tests, fixtures, and API mocks to the new state location and ISO 3166-2 location code expectations. > > <sup>Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit f9d0630. This will update automatically on new commits. Configure [here](https://cursor.com/dashboard?tab=bugbot).</sup> <!-- /CURSOR_SUMMARY --> --------- Co-authored-by: metamaskbot <metamaskbot@users.noreply.github.com>
1 parent 44f7fd2 commit 03597ba

53 files changed

Lines changed: 624 additions & 1173 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

app/components/UI/Card/sdk/CardSDK.test.ts

Lines changed: 0 additions & 84 deletions
Original file line numberDiff line numberDiff line change
@@ -624,90 +624,6 @@ describe('CardSDK', () => {
624624
});
625625
});
626626

627-
describe('getGeoLocation', () => {
628-
it('returns UNKNOWN when API call fails', async () => {
629-
const error = new Error('Network error');
630-
(global.fetch as jest.Mock).mockRejectedValueOnce(error);
631-
632-
const result = await cardSDK.getGeoLocation();
633-
634-
expect(result).toBe('UNKNOWN');
635-
expect(Logger.error).toHaveBeenCalledWith(
636-
error,
637-
expect.objectContaining({
638-
tags: expect.objectContaining({
639-
feature: 'card',
640-
operation: 'getGeoLocation',
641-
}),
642-
}),
643-
);
644-
});
645-
646-
it('returns UNKNOWN when fetch throws an error', async () => {
647-
const fetchError = new Error('Fetch failed');
648-
(global.fetch as jest.Mock).mockRejectedValueOnce(fetchError);
649-
650-
const result = await cardSDK.getGeoLocation();
651-
652-
expect(result).toBe('UNKNOWN');
653-
expect(Logger.error).toHaveBeenCalledWith(
654-
fetchError,
655-
expect.objectContaining({
656-
tags: expect.objectContaining({
657-
feature: 'card',
658-
operation: 'getGeoLocation',
659-
}),
660-
}),
661-
);
662-
});
663-
664-
it('returns UNKNOWN when response.text() throws an error', async () => {
665-
const textError = new Error('Failed to read response text');
666-
(global.fetch as jest.Mock).mockResolvedValueOnce({
667-
ok: true,
668-
text: jest.fn().mockRejectedValue(textError),
669-
});
670-
671-
const result = await cardSDK.getGeoLocation();
672-
673-
expect(result).toBe('UNKNOWN');
674-
expect(Logger.error).toHaveBeenCalledWith(
675-
textError,
676-
expect.objectContaining({
677-
tags: expect.objectContaining({
678-
feature: 'card',
679-
operation: 'getGeoLocation',
680-
}),
681-
}),
682-
);
683-
});
684-
685-
it('handles different country codes correctly', async () => {
686-
const countryCodes = ['US', 'GB', 'CA', 'DE', 'FR', 'UNKNOWN'];
687-
688-
for (const code of countryCodes) {
689-
(global.fetch as jest.Mock).mockResolvedValueOnce({
690-
ok: true,
691-
text: jest.fn().mockResolvedValue(code),
692-
});
693-
694-
const result = await cardSDK.getGeoLocation();
695-
expect(result).toBe(code);
696-
}
697-
});
698-
699-
it('handles empty string response from API', async () => {
700-
(global.fetch as jest.Mock).mockResolvedValueOnce({
701-
ok: true,
702-
text: jest.fn().mockResolvedValue(''),
703-
});
704-
705-
const result = await cardSDK.getGeoLocation();
706-
707-
expect(result).toBe('');
708-
});
709-
});
710-
711627
describe('getSupportedTokensAllowances', () => {
712628
const testAddress = '0x1234567890123456789012345678901234567890';
713629

app/components/UI/Card/sdk/CardSDK.ts

Lines changed: 0 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -465,29 +465,6 @@ export class CardSDK {
465465
return batches;
466466
}
467467

468-
getGeoLocation = async (): Promise<string> => {
469-
try {
470-
const response = await fetch(
471-
'https://on-ramp.api.cx.metamask.io/geolocation',
472-
);
473-
474-
if (!response.ok) {
475-
throw new Error(`Failed to get geolocation: ${response.statusText}`);
476-
}
477-
478-
return await response.text();
479-
} catch (error) {
480-
Logger.error(error as Error, {
481-
tags: { feature: 'card', operation: 'getGeoLocation' },
482-
context: {
483-
name: 'card_geolocation',
484-
data: { endpoint: 'geolocation' },
485-
},
486-
});
487-
return 'UNKNOWN';
488-
}
489-
};
490-
491468
// Only runs on linea network
492469
getSupportedTokensAllowances = async (
493470
address: string,

app/components/UI/Card/sdk/index.test.tsx

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,6 @@ jest.mock('./CardSDK', () => ({
4040
isCardEnabled: true,
4141
getSupportedTokensByChainId: jest.fn(() => []),
4242
isCardHolder: jest.fn(),
43-
getGeoLocation: jest.fn(),
4443
getSupportedTokensAllowances: jest.fn(),
4544
getPriorityToken: jest.fn(),
4645
refreshLocalToken: jest.fn(),
@@ -171,7 +170,6 @@ describe('CardSDK Context', () => {
171170
isCardEnabled: true,
172171
getSupportedTokensByChainId: jest.fn(() => []),
173172
isCardHolder: jest.fn(),
174-
getGeoLocation: jest.fn(),
175173
getSupportedTokensAllowances: jest.fn(),
176174
getPriorityToken: jest.fn(),
177175
getRegistrationStatus: jest.fn(),

app/components/UI/Card/util/getCardholder.test.ts

Lines changed: 33 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,17 @@ import Logger from '../../../../util/Logger';
44
import { CardFeatureFlag } from '../../../../selectors/featureFlagController/card';
55
import { isValidHexAddress } from '../../../../util/address';
66

7-
// Mock dependencies
87
jest.mock('../sdk/CardSDK');
98
jest.mock('../../../../util/Logger');
109
jest.mock('../../../../util/address');
1110

11+
const mockControllerMessengerCall = jest.fn();
12+
jest.mock('../../../../core/Engine', () => ({
13+
controllerMessenger: {
14+
call: (...args: unknown[]) => mockControllerMessengerCall(...args),
15+
},
16+
}));
17+
1218
const MockedCardSDK = CardSDK as jest.MockedClass<typeof CardSDK>;
1319
const mockedLogger = Logger as jest.Mocked<typeof Logger>;
1420
const mockedIsValidHexAddress = isValidHexAddress as jest.MockedFunction<
@@ -54,16 +60,13 @@ describe('getCardholder', () => {
5460

5561
mockCardSDKInstance = {
5662
isCardHolder: jest.fn(),
57-
getGeoLocation: jest.fn(),
5863
} as unknown as jest.Mocked<CardSDK>;
5964

6065
MockedCardSDK.mockImplementation(() => mockCardSDKInstance);
6166

62-
// Mock address utilities
6367
mockedIsValidHexAddress.mockReturnValue(true);
6468

65-
// Default mock for geolocation
66-
mockCardSDKInstance.getGeoLocation.mockResolvedValue('US');
69+
mockControllerMessengerCall.mockResolvedValue('US');
6770
});
6871

6972
describe('successful scenarios', () => {
@@ -74,7 +77,6 @@ describe('getCardholder', () => {
7477
] as `${string}:${string}:${string}`[];
7578

7679
mockCardSDKInstance.isCardHolder.mockResolvedValue(mockResult);
77-
mockCardSDKInstance.getGeoLocation.mockResolvedValue('US');
7880

7981
const result = await getCardholder({
8082
caipAccountIds: mockFormattedAccounts,
@@ -94,16 +96,15 @@ describe('getCardholder', () => {
9496
expect(mockCardSDKInstance.isCardHolder).toHaveBeenCalledWith(
9597
mockFormattedAccounts,
9698
);
97-
expect(mockCardSDKInstance.getGeoLocation).toHaveBeenCalled();
9899
});
99100

100101
it('should return only cardholder addresses from mixed results', async () => {
101102
const mockResult = [
102103
'eip155:59144:0x1234567890abcdef1234567890abcdef12345678',
103104
] as `${string}:${string}:${string}`[];
104105

106+
mockControllerMessengerCall.mockResolvedValue('GB');
105107
mockCardSDKInstance.isCardHolder.mockResolvedValue(mockResult);
106-
mockCardSDKInstance.getGeoLocation.mockResolvedValue('GB');
107108

108109
const result = await getCardholder({
109110
caipAccountIds: mockFormattedAccounts,
@@ -117,8 +118,8 @@ describe('getCardholder', () => {
117118
});
118119

119120
it('should return empty array and geolocation when no accounts are cardholders', async () => {
121+
mockControllerMessengerCall.mockResolvedValue('CA');
120122
mockCardSDKInstance.isCardHolder.mockResolvedValue([]);
121-
mockCardSDKInstance.getGeoLocation.mockResolvedValue('CA');
122123

123124
const result = await getCardholder({
124125
caipAccountIds: mockFormattedAccounts,
@@ -310,8 +311,8 @@ describe('getCardholder', () => {
310311
'eip155:59144:0x3333333333333333333333333333333333333333',
311312
] as `${string}:${string}:${string}`[];
312313

314+
mockControllerMessengerCall.mockResolvedValue('DE');
313315
mockCardSDKInstance.isCardHolder.mockResolvedValue(mockResult);
314-
mockCardSDKInstance.getGeoLocation.mockResolvedValue('DE');
315316

316317
const result = await getCardholder({
317318
caipAccountIds: mockFormattedAccounts,
@@ -335,8 +336,8 @@ describe('getCardholder', () => {
335336
'also:invalid',
336337
] as `${string}:${string}:${string}`[];
337338

339+
mockControllerMessengerCall.mockResolvedValue('FR');
338340
mockCardSDKInstance.isCardHolder.mockResolvedValue(mockResult);
339-
mockCardSDKInstance.getGeoLocation.mockResolvedValue('FR');
340341

341342
const result = await getCardholder({
342343
caipAccountIds: mockFormattedAccounts,
@@ -356,8 +357,8 @@ describe('getCardholder', () => {
356357
'eip155:59144:0x2222222222222222222222222222222222222222',
357358
] as `${string}:${string}:${string}`[];
358359

360+
mockControllerMessengerCall.mockResolvedValue('ES');
359361
mockCardSDKInstance.isCardHolder.mockResolvedValue(mockResult);
360-
mockCardSDKInstance.getGeoLocation.mockResolvedValue('ES');
361362
mockedIsValidHexAddress
362363
.mockReturnValueOnce(true)
363364
.mockReturnValueOnce(false)
@@ -379,35 +380,36 @@ describe('getCardholder', () => {
379380
});
380381
});
381382

382-
describe('geolocation handling', () => {
383-
it('should handle different geolocation values', async () => {
384-
const geoLocations = ['US', 'GB', 'CA', 'DE', 'UNKNOWN'];
385-
386-
for (const geoLocation of geoLocations) {
387-
mockCardSDKInstance.isCardHolder.mockResolvedValue([
388-
'eip155:59144:0x1234567890abcdef1234567890abcdef12345678',
389-
] as `${string}:${string}:${string}`[]);
390-
mockCardSDKInstance.getGeoLocation.mockResolvedValue(geoLocation);
383+
describe('geolocation from controller messenger', () => {
384+
it('should await geolocation from GeolocationController:getGeolocation', async () => {
385+
mockControllerMessengerCall.mockResolvedValue('JP');
386+
mockCardSDKInstance.isCardHolder.mockResolvedValue([
387+
'eip155:59144:0x1234567890abcdef1234567890abcdef12345678',
388+
] as `${string}:${string}:${string}`[]);
391389

392-
const result = await getCardholder({
393-
caipAccountIds: mockFormattedAccounts,
394-
cardFeatureFlag: mockCardFeatureFlag,
395-
});
390+
const result = await getCardholder({
391+
caipAccountIds: mockFormattedAccounts,
392+
cardFeatureFlag: mockCardFeatureFlag,
393+
});
396394

397-
expect(result.geoLocation).toBe(geoLocation);
398-
}
395+
expect(result.geoLocation).toBe('JP');
396+
expect(mockControllerMessengerCall).toHaveBeenCalledWith(
397+
'GeolocationController:getGeolocation',
398+
);
399399
});
400400

401-
it('should call getGeoLocation for each request', async () => {
401+
it('should return UNKNOWN when getGeolocation rejects', async () => {
402+
mockControllerMessengerCall.mockRejectedValue(
403+
new Error('Controller unavailable'),
404+
);
402405
mockCardSDKInstance.isCardHolder.mockResolvedValue([]);
403-
mockCardSDKInstance.getGeoLocation.mockResolvedValue('US');
404406

405-
await getCardholder({
407+
const result = await getCardholder({
406408
caipAccountIds: mockFormattedAccounts,
407409
cardFeatureFlag: mockCardFeatureFlag,
408410
});
409411

410-
expect(mockCardSDKInstance.getGeoLocation).toHaveBeenCalledTimes(1);
412+
expect(result.geoLocation).toBe('UNKNOWN');
411413
});
412414
});
413415
});

app/components/UI/Card/util/getCardholder.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { CardSDK } from '../sdk/CardSDK';
33
import Logger from '../../../../util/Logger';
44
import { isValidHexAddress } from '../../../../util/address';
55
import { isCaipAccountId, parseCaipAccountId } from '@metamask/utils';
6+
import Engine from '../../../../core/Engine';
67

78
export const getCardholder = async ({
89
caipAccountIds,
@@ -26,8 +27,12 @@ export const getCardholder = async ({
2627
cardFeatureFlag,
2728
});
2829

29-
const cardCaipAccountIds = await cardSDK.isCardHolder(caipAccountIds);
30-
const geoLocation = await cardSDK.getGeoLocation();
30+
const [cardCaipAccountIds, geoLocation] = await Promise.all([
31+
cardSDK.isCardHolder(caipAccountIds),
32+
Engine.controllerMessenger
33+
.call('GeolocationController:getGeolocation')
34+
.catch(() => 'UNKNOWN'),
35+
]);
3136

3237
const cardholderAddresses = cardCaipAccountIds.map((cardCaipAccountId) => {
3338
if (!isCaipAccountId(cardCaipAccountId)) return null;

app/components/UI/Earn/components/Musd/MusdConversionAssetListCta/MusdConversionAssetListCta.view.test.tsx

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@ import { initialState as initialFiatOrdersState } from '../../../../../../reduce
1010

1111
const fiatOrdersNoFetch = {
1212
...initialFiatOrdersState,
13-
detectedGeolocation: undefined,
1413
rampRoutingDecision: null,
1514
};
1615

app/components/UI/Ramp/RampsBootstrap.test.tsx

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,9 @@ import React from 'react';
22
import { render } from '@testing-library/react-native';
33
import RampsBootstrap from './RampsBootstrap';
44

5-
const mockUseDetectGeolocation = jest.fn();
65
const mockUseRampsSmartRouting = jest.fn();
76
const mockUseHydrateRampsController = jest.fn();
87

9-
jest.mock('./hooks/useDetectGeolocation', () => ({
10-
__esModule: true,
11-
default: (...args: unknown[]) => mockUseDetectGeolocation(...args),
12-
}));
13-
148
jest.mock('./hooks/useRampsSmartRouting', () => ({
159
__esModule: true,
1610
default: (...args: unknown[]) => mockUseRampsSmartRouting(...args),
@@ -26,12 +20,6 @@ describe('RampsBootstrap', () => {
2620
jest.clearAllMocks();
2721
});
2822

29-
it('calls useDetectGeolocation on mount', () => {
30-
render(<RampsBootstrap />);
31-
32-
expect(mockUseDetectGeolocation).toHaveBeenCalledTimes(1);
33-
});
34-
3523
it('calls useRampsSmartRouting on mount', () => {
3624
render(<RampsBootstrap />);
3725

app/components/UI/Ramp/RampsBootstrap.tsx

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,18 @@
1-
import useDetectGeolocation from './hooks/useDetectGeolocation';
21
import useHydrateRampsController from './hooks/useHydrateRampsController';
32
import useRampsSmartRouting from './hooks/useRampsSmartRouting';
43

54
/**
6-
* Ramps app bootstrap: runs geolocation detection, smart routing, and controller
7-
* hydration as soon as the app mounts so that by the time the user reaches Buy →
8-
* Token Selection (non-V2), detectedGeolocation and rampRoutingDecision are
9-
* often already set.
5+
* Ramps app bootstrap: runs smart routing and controller hydration as soon as
6+
* the app mounts so that by the time the user reaches Buy → Token Selection
7+
* (non-V2), rampRoutingDecision is often already set.
8+
*
9+
* Geolocation is now handled by GeolocationController (eager fetch on Engine
10+
* startup), so useDetectGeolocation is no longer needed here.
1011
*
1112
* V2: RampsController is initialized by Engine (rampsControllerInit); no UI
1213
* init. Mount at app root.
1314
*/
1415
function RampsBootstrap(): null {
15-
useDetectGeolocation();
1616
useRampsSmartRouting();
1717
useHydrateRampsController();
1818
return null;

app/components/UI/Ramp/Views/TokenSelection/TokenSelection.test.tsx

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,6 @@ function renderWithProvider(
4848
backgroundState,
4949
},
5050
fiatOrders: {
51-
detectedGeolocation: 'US',
5251
rampRoutingDecision: UnifiedRampRoutingType.DEPOSIT,
5352
...customState?.fiatOrders,
5453
},

0 commit comments

Comments
 (0)