Skip to content

Commit fbdd300

Browse files
authored
feat(ramps): uses api endpoint to determine ramp eligibility (#22279)
## **Description** This PR updates the Ramps Smart Routing hook to use the actual API endpoints now that they are available in production and staging environments. **What is the reason for the change?** The smart routing feature was previously using a placeholder endpoint (`/endpoint-coming-soon`) while waiting for the backend API to be deployed. The API endpoints are now available and ready to be integrated. **What is the improvement/solution?** - Integrated production and staging API endpoints for ramp eligibility checks - Production URL: `https://on-ramp-content.api.cx.metamask.io/regions/countries/{region-code}` - Staging URL: `https://on-ramp-content.uat-api.cx.metamask.io/regions/countries/{region-code}` - Environment detection uses `process.env.METAMASK_ENVIRONMENT` directly to determine which endpoint to use - Production environments: `production`, `beta`, `rc` - Staging environments: `dev`, `exp`, `test`, `e2e`, and all others default to staging ## **Changelog** CHANGELOG entry: null ## **Related issues** Fixes: https://consensyssoftware.atlassian.net/browse/TRAM-2807 ## **Manual testing steps** ```gherkin Feature: Ramp Smart Routing API Integration Scenario: User in supported region sees appropriate ramp option Given the app is running in a production environment And the user's detected geolocation is "us-ca" When the ramp smart routing hook initializes Then the app fetches eligibility from "https://on-ramp-content.api.cx.metamask.io/regions/countries/us-ca" And the routing decision is set based on the API response Scenario: User in supported region in staging environment Given the app is running in a dev/staging environment And the user's detected geolocation is "us-ca" When the ramp smart routing hook initializes Then the app fetches eligibility from "https://on-ramp-content.uat-api.cx.metamask.io/regions/countries/us-ca" And the routing decision is set based on the API response Scenario: User in unsupported region Given the app is running in any environment And the user's detected geolocation returns global: false from API When the ramp smart routing hook initializes Then the routing decision is set to UNSUPPORTED And the user cannot access ramp features Scenario: API fetch fails Given the app is running in any environment And the API request fails or times out When the ramp smart routing hook initializes Then the routing decision is set to ERROR And appropriate error logging occurs ``` ## **Screenshots/Recordings** <!-- If applicable, add screenshots and/or recordings to visualize the before and after of your change. --> ### **Before** <!-- [screenshots/recordings] --> ### **After** <!-- [screenshots/recordings] --> ## **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 - [ ] 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** - [ ] I've manually tested the PR (e.g. pull and build branch, run the app, test code being changed). - [ ] 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] > Hook now fetches ramp eligibility from environment-specific endpoints with error handling; tests expanded and Babel config updated to preserve env vars. > > - **Ramps Smart Routing (`app/components/UI/Ramp/hooks/useRampsSmartRouting.ts`)** > - Use real eligibility API with env-based base URLs via `process.env.METAMASK_ENVIRONMENT` (`PRODUCTION` vs `STAGING`). > - Build fetch URL `'/regions/countries/{region}'`; validate `response.ok` and throw on errors; log and route to `ERROR` on failure. > - **Tests (`useRampsSmartRouting.test.ts`)** > - Add cases verifying production/staging URLs across envs (`production`, `beta`, `rc`, `dev`, `exp`, `test`, `e2e`). > - Add error-path tests for failed fetch and non-OK responses (404, 500); persist/restore env; update geolocation to `us-ca`. > - **Build/Config (`babel.config.tests.js`)** > - Exclude `useRampsSmartRouting.ts` and its tests from inline env var transform to allow runtime env-based behavior. > > <sup>Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit 77d4425. This will update automatically on new commits. Configure [here](https://cursor.com/dashboard?tab=bugbot).</sup> <!-- /CURSOR_SUMMARY -->
1 parent 7e3f4ce commit fbdd300

File tree

3 files changed

+178
-6
lines changed

3 files changed

+178
-6
lines changed

app/components/UI/Ramp/hooks/useRampsSmartRouting.test.ts

Lines changed: 145 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ const mockUseRampsUnifiedV1Enabled = jest.fn();
1919

2020
global.fetch = mockFetch as jest.Mock;
2121

22+
const originalMetamaskEnvironment = process.env.METAMASK_ENVIRONMENT;
23+
2224
jest.mock('react-redux', () => ({
2325
...jest.requireActual('react-redux'),
2426
useDispatch: () => mockDispatch,
@@ -60,6 +62,7 @@ const mockApiResponse = ({
6062
global,
6163
}: RampEligibilityAPIResponse) => {
6264
mockFetch.mockImplementation(async () => ({
65+
ok: true,
6366
json: async () => ({ deposit, aggregator, global }),
6467
}));
6568
};
@@ -68,7 +71,8 @@ describe('useRampsSmartRouting', () => {
6871
beforeEach(() => {
6972
jest.clearAllMocks();
7073
mockOrders = [];
71-
mockDetectedGeolocation = 'US';
74+
mockDetectedGeolocation = 'us-ca';
75+
process.env.METAMASK_ENVIRONMENT = 'dev';
7276
mockApiResponse({
7377
deposit: true,
7478
aggregator: false,
@@ -87,6 +91,10 @@ describe('useRampsSmartRouting', () => {
8791
});
8892
});
8993

94+
afterAll(() => {
95+
process.env.METAMASK_ENVIRONMENT = originalMetamaskEnvironment;
96+
});
97+
9098
describe('Feature flag check', () => {
9199
it('does nothing when unifiedV1Enabled is false', async () => {
92100
mockUseRampsUnifiedV1Enabled.mockReturnValue(false);
@@ -126,6 +134,106 @@ describe('useRampsSmartRouting', () => {
126134
jest.clearAllMocks();
127135
});
128136

137+
describe('API endpoint selection', () => {
138+
it('calls production URL for production environment', async () => {
139+
process.env.METAMASK_ENVIRONMENT = 'production';
140+
mockDetectedGeolocation = 'us-ca';
141+
mockOrders = [];
142+
143+
renderHook(() => useRampsSmartRouting());
144+
145+
await waitFor(() => {
146+
expect(mockFetch).toHaveBeenCalledWith(
147+
'https://on-ramp-content.api.cx.metamask.io/regions/countries/us-ca',
148+
);
149+
});
150+
});
151+
152+
it('calls production URL for beta environment', async () => {
153+
process.env.METAMASK_ENVIRONMENT = 'beta';
154+
mockDetectedGeolocation = 'us-ca';
155+
mockOrders = [];
156+
157+
renderHook(() => useRampsSmartRouting());
158+
159+
await waitFor(() => {
160+
expect(mockFetch).toHaveBeenCalledWith(
161+
'https://on-ramp-content.api.cx.metamask.io/regions/countries/us-ca',
162+
);
163+
});
164+
});
165+
166+
it('calls production URL for rc environment', async () => {
167+
process.env.METAMASK_ENVIRONMENT = 'rc';
168+
mockDetectedGeolocation = 'us-ca';
169+
mockOrders = [];
170+
171+
renderHook(() => useRampsSmartRouting());
172+
173+
await waitFor(() => {
174+
expect(mockFetch).toHaveBeenCalledWith(
175+
'https://on-ramp-content.api.cx.metamask.io/regions/countries/us-ca',
176+
);
177+
});
178+
});
179+
180+
it('calls staging URL for dev environment', async () => {
181+
process.env.METAMASK_ENVIRONMENT = 'dev';
182+
mockDetectedGeolocation = 'us-ca';
183+
mockOrders = [];
184+
185+
renderHook(() => useRampsSmartRouting());
186+
187+
await waitFor(() => {
188+
expect(mockFetch).toHaveBeenCalledWith(
189+
'https://on-ramp-content.uat-api.cx.metamask.io/regions/countries/us-ca',
190+
);
191+
});
192+
});
193+
194+
it('calls staging URL for exp environment', async () => {
195+
process.env.METAMASK_ENVIRONMENT = 'exp';
196+
mockDetectedGeolocation = 'us-ca';
197+
mockOrders = [];
198+
199+
renderHook(() => useRampsSmartRouting());
200+
201+
await waitFor(() => {
202+
expect(mockFetch).toHaveBeenCalledWith(
203+
'https://on-ramp-content.uat-api.cx.metamask.io/regions/countries/us-ca',
204+
);
205+
});
206+
});
207+
208+
it('calls staging URL for test environment', async () => {
209+
process.env.METAMASK_ENVIRONMENT = 'test';
210+
mockDetectedGeolocation = 'us-ca';
211+
mockOrders = [];
212+
213+
renderHook(() => useRampsSmartRouting());
214+
215+
await waitFor(() => {
216+
expect(mockFetch).toHaveBeenCalledWith(
217+
'https://on-ramp-content.uat-api.cx.metamask.io/regions/countries/us-ca',
218+
);
219+
});
220+
});
221+
222+
it('calls staging URL for e2e environment', async () => {
223+
process.env.METAMASK_ENVIRONMENT = 'e2e';
224+
mockDetectedGeolocation = 'us-ca';
225+
mockOrders = [];
226+
227+
renderHook(() => useRampsSmartRouting());
228+
229+
await waitFor(() => {
230+
expect(mockFetch).toHaveBeenCalledWith(
231+
'https://on-ramp-content.uat-api.cx.metamask.io/regions/countries/us-ca',
232+
);
233+
});
234+
});
235+
});
236+
129237
describe('Region support check', () => {
130238
it('routes to UNSUPPORTED when API returns global: false', async () => {
131239
mockApiResponse({
@@ -258,6 +366,42 @@ describe('useRampsSmartRouting', () => {
258366
}),
259367
);
260368
});
369+
370+
it('routes to ERROR when API returns 404', async () => {
371+
mockFetch.mockImplementation(async () => ({
372+
ok: false,
373+
status: 404,
374+
statusText: 'Not Found',
375+
}));
376+
mockOrders = [];
377+
378+
renderHook(() => useRampsSmartRouting());
379+
380+
await waitFor(() =>
381+
expect(mockDispatch).toHaveBeenCalledWith({
382+
type: 'FIAT_SET_RAMP_ROUTING_DECISION',
383+
payload: UnifiedRampRoutingType.ERROR,
384+
}),
385+
);
386+
});
387+
388+
it('routes to ERROR when API returns 500', async () => {
389+
mockFetch.mockImplementation(async () => ({
390+
ok: false,
391+
status: 500,
392+
statusText: 'Internal Server Error',
393+
}));
394+
mockOrders = [];
395+
396+
renderHook(() => useRampsSmartRouting());
397+
398+
await waitFor(() =>
399+
expect(mockDispatch).toHaveBeenCalledWith({
400+
type: 'FIAT_SET_RAMP_ROUTING_DECISION',
401+
payload: UnifiedRampRoutingType.ERROR,
402+
}),
403+
);
404+
});
261405
});
262406

263407
describe('Order history check', () => {

app/components/UI/Ramp/hooks/useRampsSmartRouting.ts

Lines changed: 31 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,24 @@ import {
1313
import useRampsUnifiedV1Enabled from './useRampsUnifiedV1Enabled';
1414
import Logger from '../../../../util/Logger';
1515

16+
const RAMP_ELIGIBILITY_URLS = {
17+
STAGING: 'https://on-ramp-content.uat-api.cx.metamask.io',
18+
PRODUCTION: 'https://on-ramp-content.api.cx.metamask.io',
19+
};
20+
21+
const getBaseUrl = () => {
22+
const metamaskEnvironment = process.env.METAMASK_ENVIRONMENT;
23+
24+
const isProductionEnvironment =
25+
metamaskEnvironment === 'production' ||
26+
metamaskEnvironment === 'beta' ||
27+
metamaskEnvironment === 'rc';
28+
29+
return isProductionEnvironment
30+
? RAMP_ELIGIBILITY_URLS.PRODUCTION
31+
: RAMP_ELIGIBILITY_URLS.STAGING;
32+
};
33+
1634
export enum RampRegionSupport {
1735
DEPOSIT = 'DEPOSIT',
1836
AGGREGATOR = 'AGGREGATOR',
@@ -44,11 +62,19 @@ export default function useRampsSmartRouting() {
4462
}
4563

4664
try {
47-
// TODO: Replace with actual API endpoint when it's available
48-
// https://consensyssoftware.atlassian.net/browse/TRAM-2807
49-
const response = await fetch(
50-
`/endpoint-coming-soon?region=${rampGeodetectedRegion}`,
51-
);
65+
const baseUrl = getBaseUrl();
66+
const url = new URL(
67+
`/regions/countries/${rampGeodetectedRegion}`,
68+
baseUrl,
69+
).toString();
70+
const response = await fetch(url);
71+
72+
if (!response.ok) {
73+
throw new Error(
74+
`Failed to fetch region eligibility: ${response.status} ${response.statusText}`,
75+
);
76+
}
77+
5278
const eligibility: RampEligibilityAPIResponse = await response.json();
5379

5480
if (!eligibility.global) {

babel.config.tests.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@ const newOverrides = [
2525
'app/components/UI/Ramp/Aggregator/sdk/getSdkEnvironment.test.ts',
2626
'app/components/UI/Ramp/hooks/useRampsUnifiedV1Enabled.ts',
2727
'app/components/UI/Ramp/hooks/useRampsUnifiedV1Enabled.test.ts',
28+
'app/components/UI/Ramp/hooks/useRampsSmartRouting.ts',
29+
'app/components/UI/Ramp/hooks/useRampsSmartRouting.test.ts',
2830
'app/store/migrations/**',
2931
'app/util/networks/customNetworks.tsx',
3032
],

0 commit comments

Comments
 (0)