Skip to content

Commit d0c242b

Browse files
chore(runway): cherry-pick feat: return actual host for known public domains in analytics cp-7.64.0 (#25448)
- feat: return actual host for known public domains in analytics cp-7.64.0 (#25385) ## **Description** Improves analytics data quality by returning the actual domain host for known public RPC providers instead of masking them as 'custom'. - Add `isPublicRpcDomain` helper in `rpc-domain-utils.ts` that checks if an RPC URL has a known public domain - Simplify `isPublicEndpointUrl` by using the new helper - `sanitizeRpcUrl` now returns the actual host (e.g., `mainnet.infura.io`, `eth-mainnet.alchemyapi.io` or any RPC from chainid.network) for known public domains, improving the accuracy of `rpc_domain` in analytics events ## **Changelog** CHANGELOG entry: null ## **Related issues** Fixes: https://consensyssoftware.atlassian.net/browse/WPC-342 ## **Manual testing steps** ```gherkin Feature: RPC domain analytics Scenario: Verify rpc_domain shows actual host when switching to public RPC via banner # Setup - Add Ink network with local RPC Given user navigates to Settings → Networks → Add Network And user adds Ink network (Chain ID: 57073) with local RPC endpoint: http://127.0.0.1:8545 And user also adds public RPC endpoint: https://rpc-qnd.inkonchain.com And user sets the local RPC as the default endpoint And user switches to Ink network # Trigger degraded state When user disconnects local RPC (or it becomes unavailable) And user waits for banner showing "Still connecting to Ink..." # Trigger RPC update from banner Then the "Update RPC" button appears on the banner When user clicks "Update RPC" on the banner And user is navigated to Edit Network screen And user switches default RPC to https://rpc-qnd.inkonchain.com # Verify analytics in Segment When user checks Segment dashboard for "Network Connection Banner RPC Updated" event Then the event property from_rpc_domain should be "custom" (local RPC is private) And the event property to_rpc_domain should be "rpc-qnd.inkonchain.com" (known public domain) Scenario: Verify rpc_domain for Infura networks using Switch to MetaMask default # Setup - Configure Arbitrum with local RPC Given user starts a local Ganache server: npx ganache --chain.chainId 42161 And user navigates to Settings → Networks → Arbitrum One And user adds a new RPC endpoint: http://127.0.0.1:8545 And user sets the local RPC as the default endpoint # Trigger degraded state When user stops the Ganache server (Ctrl+C) And user waits for banner showing "Still connecting to Arbitrum One..." # Switch to Infura via banner button Then the "Switch to MetaMask default RPC" button appears on the banner When user clicks "Switch to MetaMask default RPC" Then the toast "Updated to MetaMask default" appears # Verify analytics When user checks Segment for "Network Connection Banner Switch To MetaMask Default RPC Clicked" Then rpc_domain should be "custom" (the local RPC being switched from) ``` ## **Screenshots/Recordings** N/A - Internal analytics improvement, no UI changes. ### **Before** ### **After** ## **Pre-merge author checklist** - [ ] 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). - [ ] I've completed the PR template to the best of my ability - [ ] I've included tests if applicable - [ ] I've documented my code using [JSDoc](https://jsdoc.app/) format if applicable - [ ] 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] > **Medium Risk** > Mainly affects analytics sanitization/allowlisting for RPC endpoint URLs, which has privacy implications if misclassified. Also adds a new async init step during `Engine` startup (non-blocking) that could surface new runtime errors (captured to Sentry). > > **Overview** > **Improves RPC-domain analytics classification.** Adds `isPublicRpcDomain` in `rpc-domain-utils.ts` to treat endpoints with known public provider domains (from cached Safe Chains data and allowlisted providers like Infura/Alchemy) as public, while still excluding localhost/invalid/unknown domains. > > `isPublicEndpointUrl` (network-controller utils) now delegates to this helper, and `Engine` asynchronously warms the RPC provider domain cache on startup (errors reported via Sentry). Tests are expanded to cover localhost/invalid URLs and known public providers (e.g., Alchemy). > > <sup>Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit c976dd0. This will update automatically on new commits. Configure [here](https://cursor.com/dashboard?tab=bugbot).</sup> <!-- /CURSOR_SUMMARY --> [910d769](910d769) Co-authored-by: cryptodev-2s <109512101+cryptodev-2s@users.noreply.github.com>
1 parent 5f7e4d6 commit d0c242b

5 files changed

Lines changed: 78 additions & 11 deletions

File tree

app/core/Engine/Engine.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ import {
4949
import NotificationManager from '../NotificationManager';
5050
import Logger from '../../util/Logger';
5151
import { isZero } from '../../util/lodash';
52+
import { initializeRpcProviderDomains } from '../../util/rpc-domain-utils';
5253

5354
///: BEGIN:ONLY_INCLUDE_IF(preinstalled-snaps,external-snaps)
5455
import { notificationServicesControllerInit } from './controllers/notifications/notification-services-controller-init';
@@ -68,6 +69,7 @@ import {
6869
parseCaipAssetType,
6970
} from '@metamask/utils';
7071
import { providerErrors } from '@metamask/rpc-errors';
72+
import { captureException } from '@sentry/react-native';
7173

7274
import {
7375
networkIdUpdated,
@@ -473,6 +475,12 @@ export class Engine {
473475
controllersByName.NetworkEnablementController;
474476
networkEnablementController.init();
475477

478+
// Initialize RPC domain validation cache for analytics
479+
// This runs asynchronously and doesn't block Engine initialization
480+
initializeRpcProviderDomains().catch((error) => {
481+
captureException(error);
482+
});
483+
476484
///: BEGIN:ONLY_INCLUDE_IF(preinstalled-snaps,external-snaps)
477485
snapController.init();
478486
cronjobController.init();

app/core/Engine/controllers/network-controller/utils.test.ts

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -371,6 +371,36 @@ describe('isPublicEndpointUrl', () => {
371371
),
372372
).toBe(false);
373373
});
374+
375+
it('returns false for localhost URLs', () => {
376+
expect(
377+
isPublicEndpointUrl(
378+
'http://localhost:8545',
379+
MOCK_METAMASK_INFURA_PROJECT_ID,
380+
),
381+
).toBe(false);
382+
expect(
383+
isPublicEndpointUrl(
384+
'http://127.0.0.1:8545',
385+
MOCK_METAMASK_INFURA_PROJECT_ID,
386+
),
387+
).toBe(false);
388+
});
389+
390+
it('returns false for invalid URLs', () => {
391+
expect(
392+
isPublicEndpointUrl(':::invalid-url', MOCK_METAMASK_INFURA_PROJECT_ID),
393+
).toBe(false);
394+
});
395+
396+
it('returns true for known public provider domains like Alchemy', () => {
397+
expect(
398+
isPublicEndpointUrl(
399+
'https://eth-mainnet.alchemyapi.io/v2/some-key',
400+
MOCK_METAMASK_INFURA_PROJECT_ID,
401+
),
402+
).toBe(true);
403+
});
374404
});
375405

376406
/**

app/core/Engine/controllers/network-controller/utils.ts

Lines changed: 5 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import {
66
PopularList,
77
} from '../../../../util/networks/customNetworks';
88
import { BUILT_IN_CUSTOM_NETWORKS_RPC } from '@metamask/controller-utils';
9+
import { isPublicRpcDomain } from '../../../../util/rpc-domain-utils';
910

1011
/**
1112
* We capture Segment events for degraded or unavailable RPC endpoints for 1%
@@ -150,17 +151,10 @@ export function isPublicEndpointUrl(
150151
endpointUrl: string,
151152
infuraProjectId: string,
152153
) {
153-
const isMetaMaskInfuraEndpointUrl = getIsMetaMaskInfuraEndpointUrl(
154-
endpointUrl,
155-
infuraProjectId,
156-
);
157-
const isQuicknodeEndpointUrl = getIsQuicknodeEndpointUrl(endpointUrl);
158-
const isKnownCustomEndpointUrl =
159-
KNOWN_CUSTOM_ENDPOINT_URLS.includes(endpointUrl);
160-
161154
return (
162-
isMetaMaskInfuraEndpointUrl ||
163-
isQuicknodeEndpointUrl ||
164-
isKnownCustomEndpointUrl
155+
getIsMetaMaskInfuraEndpointUrl(endpointUrl, infuraProjectId) ||
156+
getIsQuicknodeEndpointUrl(endpointUrl) ||
157+
KNOWN_CUSTOM_ENDPOINT_URLS.includes(endpointUrl) ||
158+
isPublicRpcDomain(endpointUrl)
165159
);
166160
}

app/util/rpc-domain-utils.test.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import {
77
getKnownDomains,
88
isKnownDomain,
99
extractRpcDomain,
10+
isPublicRpcDomain,
1011
getNetworkRpcUrl,
1112
getModuleState,
1213
} from './rpc-domain-utils';
@@ -419,6 +420,28 @@ describe('rpc-domain-utils', () => {
419420
});
420421
});
421422

423+
describe('isPublicRpcDomain', () => {
424+
it('returns false for invalid URLs', () => {
425+
expect(isPublicRpcDomain(':::invalid-url')).toBe(false);
426+
});
427+
428+
it('returns false for private/localhost URLs', () => {
429+
expect(isPublicRpcDomain('http://localhost:8545')).toBe(false);
430+
expect(isPublicRpcDomain('http://127.0.0.1:8545')).toBe(false);
431+
});
432+
433+
it('returns false for unknown private domains', () => {
434+
expect(isPublicRpcDomain('https://unknown-domain.com')).toBe(false);
435+
});
436+
437+
it('returns true for known public provider URLs', () => {
438+
expect(isPublicRpcDomain('https://mainnet.infura.io/v3/key')).toBe(true);
439+
expect(
440+
isPublicRpcDomain('https://eth-mainnet.alchemyapi.io/v2/key'),
441+
).toBe(true);
442+
});
443+
});
444+
422445
describe('getNetworkRpcUrl', () => {
423446
describe('when retrieving RPC URLs', () => {
424447
it('returns RPC URL from legacy format', () => {

app/util/rpc-domain-utils.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,18 @@ export const RpcDomainStatus = {
107107
export type RpcDomainStatus =
108108
(typeof RpcDomainStatus)[keyof typeof RpcDomainStatus];
109109

110+
/**
111+
* Checks if an RPC endpoint URL has a valid public domain.
112+
* Extracts the domain from the URL and verifies it's not private, invalid, or unknown.
113+
*
114+
* @param endpointUrl - The RPC endpoint URL to check
115+
* @returns True if the URL has a valid public domain, false otherwise
116+
*/
117+
export function isPublicRpcDomain(endpointUrl: string): boolean {
118+
const rpcDomain = extractRpcDomain(endpointUrl);
119+
return !Object.values(RpcDomainStatus).includes(rpcDomain as RpcDomainStatus);
120+
}
121+
110122
function parseDomain(url: string): string | undefined {
111123
try {
112124
const normalizedUrl = url.includes('://') ? url : `https://${url}`;

0 commit comments

Comments
 (0)