Skip to content

feat: Implementation of gelocation controller#8037

Open
MarioAslau wants to merge 8 commits intomainfrom
feat/geolocation-cotroller
Open

feat: Implementation of gelocation controller#8037
MarioAslau wants to merge 8 commits intomainfrom
feat/geolocation-cotroller

Conversation

@MarioAslau
Copy link
Contributor

@MarioAslau MarioAslau commented Feb 25, 2026

Explanation

Problem

On mobile, four separate features (Ramp, Perps, Rewards, Card) independently call the same geolocation API endpoint (https://on-ramp.api.cx.metamask.io/geolocation), each with its own implementation, URL constants, caching strategy, and trigger mechanism. This results in 2–4 redundant HTTP calls on every app load (~1.8–3.1 seconds each), inconsistent environment URL handling across features, and no shared cache.

Feature Fetch Method Caching Trigger
Ramp fetch() None useEffect on mount
Perps successfulFetch() 5-min TTL + promise dedup checkEligibility() call
Rewards successfulFetch() Controller-level in-memory getGeoRewardsMetadata() call
Card fetch() None getCardholder() call

Solution

This PR introduces a new @metamask/geolocation-controller package that centralises geolocation fetching behind a single GeolocationController. The controller extends BaseController from @metamask/base-controller and follows established controller conventions (matching the patterns in connectivity-controller).

Key design decisions:

  • TTL-based caching (configurable, default 5 minutes) — prevents redundant network calls when the cached value is still fresh.
  • Promise coalescing — concurrent calls to getGeolocation() share a single in-flight promise, so multiple consumers calling simultaneously produce only one HTTP request.
  • Injectable fetch function — the controller accepts a fetch parameter (defaults to globalThis.fetch), consistent with the RampsService pattern. This makes the controller fully testable without global mocks.
  • Environment-aware URL resolution — a getGeolocationUrl callback is provided at construction time, allowing the host application to resolve the correct API URL (prod vs. dev/UAT) without the controller having to know about environment configuration.
  • No persistence — state is in-memory only (persist: false on all metadata fields). Geolocation is re-fetched on every app restart, which is appropriate since the data is IP-based and lightweight.
  • Platform-agnostic — no dependency on mobile or extension-specific code. The package is reusable across all MetaMask clients.

What's included

New package: packages/geolocation-controller/

  • src/GeolocationController.ts — Controller implementation with state management, TTL cache, and promise deduplication
  • src/types.tsGeolocationStatus type ('idle' | 'loading' | 'complete' | 'error')
  • src/GeolocationController-method-action-types.ts — Messenger action types for getGeolocation and refreshGeolocation
  • src/index.ts — Public exports
  • src/GeolocationController.test.ts — 25 test cases covering cache behaviour, promise deduplication, fetch success/failure, refresh, messenger integration, metadata, and edge cases
  • Package scaffolding: package.json, tsconfig.json, tsconfig.build.json, jest.config.js, typedoc.json, README.md, CHANGELOG.md, LICENSE

Modified: tsconfig.json — added project reference for the new package (alphabetical order, after gator-permissions-controller)

Public API

State:

type GeolocationControllerState = {
  location: string;            // ISO country code, e.g. "US", "GB", or "UNKNOWN"
  status: GeolocationStatus;   // 'idle' | 'loading' | 'complete' | 'error'
  lastFetchedAt: number | null; // Epoch ms of last successful fetch
  error: string | null;         // Last error message
};

Messenger actions:

  • GeolocationController:getGeolocation — Returns the cached location if fresh, otherwise fetches and returns it. Concurrent calls are deduplicated.
  • GeolocationController:refreshGeolocation — Forces a fresh fetch, bypassing the cache.
  • GeolocationController:getState — Returns the current controller state (provided by BaseController).

Messenger events:

  • GeolocationController:stateChange — Fires on every state transition (provided by BaseController).

References

  • Jira Ticket: https://consensyssoftware.atlassian.net/browse/MCWP-350
  • Related ADR: decisions/decisions/core/0019-geolocation-controller.md
  • Messenger pattern ADR: decisions/decisions/core/0001-messaging-non-controllers.md
  • Reference controller used as template: packages/connectivity-controller/
  • Follow-up ticket: Mobile integration and consumer migration (register in Engine, migrate Ramp/Perps/Rewards/Card, remove duplicate implementations)

Checklist

  • I've updated the test suite for new or updated code as appropriate
  • I've updated documentation (JSDoc, Markdown, etc.) for new or updated code as appropriate
  • I've communicated my changes to consumers by updating changelogs for packages I've changed
  • I've introduced breaking changes in this PR and have prepared draft pull requests for clients and consumer packages to resolve them

Note: No breaking changes — this is an entirely new package with no existing consumers.


Note

Low Risk
Mostly additive: introduces a new package and wires it into monorepo tooling (tsconfig refs, yarn workspace, CODEOWNERS/teams, README). Risk is mainly around build/packaging integration rather than behavior changes in existing runtime code.

Overview
Introduces a new @metamask/geolocation-controller package implementing GeolocationController (a BaseController) that fetches an ISO country code via an injected fetch/getGeolocationUrl, with TTL in-memory caching, in-flight request deduplication, and a refreshGeolocation path that bypasses cache while preventing stale updates.

Adds full package scaffolding and tests (Jest + typedoc + TS configs, changelog/license/readme), and wires the new workspace into repo metadata and tooling (CODEOWNERS, teams.json, root tsconfig references, yarn.lock, and README package list/dependency graph).

Written by Cursor Bugbot for commit 62051c4. This will update automatically on new commits. Configure here.

Copy link
Contributor

@NicolasMassart NicolasMassart left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A few suggestions to fix and questions, after that, will look good.

Copy link
Contributor

@cryptodev-2s cryptodev-2s left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you also run update-readme-content to update the root readme

@MarioAslau MarioAslau requested a review from a team as a code owner February 26, 2026 16:25
Copy link

@cursor cursor bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, have a team admin enable autofix in the Cursor dashboard.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants