feat: implement wallet-side analytics for SDKConnectV2 / MWP#27864
Conversation
| // class supports the `mobile/sdk-connect-v2` namespace. Currently | ||
| // `Analytics.track()` is hard-typed to `MMConnectPayload` (namespace | ||
| // `metamask/connect`). The upstream work is tracked in: | ||
| // https://consensyssoftware.atlassian.net/browse/WAPI-1350 | ||
|
|
||
| const V2_ANALYTICS_ENDPOINT = | ||
| 'https://mm-sdk-analytics.api.cx.metamask.io/v2/events'; |
There was a problem hiding this comment.
I think we can't/shouldn't be piping these through this proxy endpoint since it fully bypasses the analytics consent gate we apply to all other analytics in the wallet. I suppose we could read that state and still pass it through this same proxy to keep our events colocated 🤔 If you have the time would you mind pinging the @MetaMask/mobile-platform devs to see what they think on this? Maybe @NicolasMassart and/or @Cal-L ?
There was a problem hiding this comment.
Why SDK doesn't initialize using the same system as controllers in mobile app? It would benefit from common analytics id and the option consents data
There was a problem hiding this comment.
@NicolasMassart I wouldn't be able to tell you, when our team inherited code ownership for this, this structure was already in place as such, where ConnectionRegistry is not a part of the Engine controller system and is instead a standalone singleton created directly in app/core/SDKConnectV2/index.ts.
Would you be okay if for this PR, we follow an approach where we check consent in v2-analytics.ts before sending the payload ?
We can add something to our backlog for making ConnectionRegistry a proper controller where we :
- Register
SDKConnectV2inCONTROLLER_MESSENGERS - Create a messenger definition that delegates
AnalyticsController:trackEvent - Change the singleton initialization to go through
initModularizedControllers
cc.: @adonesky1
There was a problem hiding this comment.
5af23b6 fixes so we don't bypass analytics consent gate anymore. Followed the "lightweight" approach stated in the prior comment.
There was a problem hiding this comment.
Yes please add a followup issue for this, we have to pipe all analytics through the analytics-controller.
There was a problem hiding this comment.
Or you can simply call analytics.trackEvent in trackWalletEvent for now, and refactor later. But getting rid of this separate endpoint here would be great, it's just a one line change
There was a problem hiding this comment.
@NicolasMassart @adonesky1 tracking follow up here
…merge The main merge brought in PR #28322's existing REMOTE_CONNECTION_REQUEST_RECEIVED entries, which collided with the ones this branch had added. Consolidate by keeping main's originals in their groups and moving REMOTE_CONNECTION_REQUEST_FAILED alongside them, fixing TS2300/TS1117 errors in lint:tsc.
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
There are 3 total unresolved issues (including 2 from previous reviews).
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, have a team admin enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 0e612b5. Configure here.
🔍 Smart E2E Test Selection
click to see 🤖 AI reasoning detailsE2E Test Selection:
Risk Assessment: Low risk because:
Tag Selection Rationale:
Tags NOT selected: SmokeConfirmations, SmokeTrade, SmokeAccounts, SmokeRamps, SmokeCard, SmokePerps, SmokePredictions, SmokeIdentity, SmokeSeedlessOnboarding, FlaskBuildTests - none of these flows are touched by the changes. Performance Test Selection: |
|
|
✅ E2E Fixture Validation — Schema is up to date |




Description
DEEP_LINK_USED(MetaMetrics) for every MWP deeplink received, withroute: 'mmc-mwp'and Branch.io app-installation detectionRemote Connection Request Received,Remote Connection Request Failed) through the standard MetaMetrics / AnalyticsController pipelinehandleSimpleDeeplink) withfound_in_storeto surface silent store-miss failuresMotivation
Dapp-side events (
mmconnect_connection_initiatedwithtransport_type: 'mwp') tell us how many dapps tried to connect via MWP, but not how many wallets actually received them. The drop-off between dapp-initiated and wallet-received (silent failures, iOS routing issues) is completely invisible without wallet-side instrumentation. MWP adoption rate — the key signal for deprecating SDKv1 — can't be tracked from the wallet's perspective.Changes
Part A —
DEEP_LINK_USEDfor MWP deeplinksapp/core/DeeplinkManager/types/deepLinkAnalytics.types.tsMMC_MWP = 'mmc_mwp'toDeepLinkRouteenumapp/core/DeeplinkManager/util/deeplinks/deepLinkAnalytics.tsMMC_MWPin the route-extractor mapapp/core/DeeplinkManager/handlers/legacy/handleDeeplink.tsDEEP_LINK_USEDasynchronously beforeSDKConnectV2.handleMwpDeeplink()— never blocks the WebSocket handshakePart B — Wallet-side connection events via MetaMetrics
All wallet-side MWP events are routed through the standard
analytics.trackEvent()/AnalyticsControllerpipeline. This means:analyticsIdmetamask-mobile-globalsdefault propertiesThe per-session
remote_session_id(previouslyanon_id) is preserved as an event property so dapp↔wallet join analysis continues to work.app/core/Analytics/MetaMetrics.events.tsREMOTE_CONNECTION_REQUEST_RECEIVEDandREMOTE_CONNECTION_REQUEST_FAILEDto theEVENT_NAMEenum andeventsobjectapp/core/SDKConnectV2/services/connection-registry.tshandleConnectDeeplinkandhandleSimpleDeeplinkwithanalytics.trackEvent()calls usingAnalyticsEventBuilderandMetaMetricsEventsapp/core/SDKConnectV2/services/connection-registry.test.tsfound_in_store, andfailure_reasonDeleted (superseded by inline
analytics.trackEvent()calls):app/core/SDKConnectV2/services/v2-analytics.ts— no longer needed; removed the custom HTTP POST tomm-sdk-analytics.api.cx.metamask.io/v2/eventsand all associated typesapp/core/SDKConnectV2/services/v2-analytics.test.ts— tests moved toconnection-registry.test.tsEvent map
handleConnectDeeplink()entryRemote Connection Request Receivedsdk_version,sdk_platformhandleConnectDeeplink()catchRemote Connection Request Failedfailure_reasonhandleSimpleDeeplink()foundRemote Connection Request Receivedfound_in_store: true,sdk_version,sdk_platformhandleSimpleDeeplink()not foundRemote Connection Request Receivedfound_in_store: falseAll events include
remote_session_id(per-session UUID shared with the dapp) andplatform: 'mobile'.Test plan
metamask://connect/mwp?p=...&c=1) — verifyDEEP_LINK_USEDappears in MetaMetrics withroute: sdk_mwpRemote Connection Request Receivedarrives in MetaMetrics withsdk_version,sdk_platform, andremote_session_idRemote Connection Request Failedfires withfailure_reasonmetamask://connect/mwp?id=...) for an existing session — verifyRemote Connection Request Receivedwithfound_in_store: trueRemote Connection Request Receivedwithfound_in_store: falsemm-sdk-analytics.api.cx.metamask.io(v2 relay fully removed)Changelog
CHANGELOG entry: null
Related issues
Fixes:
Schema PRs:
Remote Connection Request Received/FailedRemote Connection RPC Request Received/Approved/RejectedManual testing steps
Screenshots/Recordings
Before
After
Pre-merge author checklist
Pre-merge reviewer checklist
Note
Medium Risk
Touches early deeplink handling and connection establishment error paths to emit analytics, which could impact connection reliability if event construction is wrong (though calls are guarded/fire-and-forget).
Overview
Adds wallet-side analytics for MWP/
SDKConnectV2flows:handleDeeplinknow asynchronously emitsMetaMetricsEvents.DEEP_LINK_USEDfor MWP deeplinks (including Branch-basedwas_app_installed) while still fast-pathing toSDKConnectV2.In
ConnectionRegistry, MWP connect and reconnect deeplinks now track MetaMetrics events for request received (includingremote_session_id,transport_type, and optional SDK metadata +found_in_store) and request failed (withfailure_reason), with analytics wrapped to never throw. The PR also extends the deep link route taxonomy withDeepLinkRoute.MMC_MWPand adds/updates unit tests to assert the new tracking behavior.Reviewed by Cursor Bugbot for commit efb7b89. Bugbot is set up for automated code reviews on this repo. Configure here.