Skip to content

feat: add new hardware wallet flows analytics#27675

Merged
mathieuartu merged 7 commits into
mainfrom
feat/add-hw-analytics
Mar 27, 2026
Merged

feat: add new hardware wallet flows analytics#27675
mathieuartu merged 7 commits into
mainfrom
feat/add-hw-analytics

Conversation

@mathieuartu

@mathieuartu mathieuartu commented Mar 19, 2026

Copy link
Copy Markdown
Contributor

Description

Changelog

CHANGELOG entry: null

Related issues

Fixes: https://consensyssoftware.atlassian.net/browse/MUL-1546

Manual testing steps

no manual testing steps

Screenshots/Recordings

Before

After

Pre-merge author checklist

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.

Note

Medium Risk
Touches the hardware wallet connection flow and bottom-sheet callbacks to emit new analytics, so incorrect state resets or callback wiring could affect connection UX/cleanup. No auth or funds-handling logic changes, and coverage is added with extensive unit tests.

Overview
Adds a new hardware-wallet analytics module that classifies flow context (Connection/Send/Swaps/Transaction/Message) and normalizes error types/details, then emits three new MetaMetrics events for recovery: HARDWARE_WALLET_RECOVERY_MODAL_VIEWED, ..._CTA_CLICKED, and ..._SUCCESS_MODAL_VIEWED.

Wires this into the hardware wallet UI/flow by deriving the analytics flow from the first pending approval at the start of each ensureDeviceReady run, tracking CTA taps from error screens via a new onCTAClicked prop on HardwareWalletBottomSheet, and resetting analytics state when the sheet is closed. Includes comprehensive unit tests for helper mappings, Redux-derived flow detection, and event firing/counting behavior.

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

@mathieuartu mathieuartu self-assigned this Mar 19, 2026
@mathieuartu mathieuartu added skip-e2e skip E2E test jobs team-accounts labels Mar 19, 2026
@github-actions

Copy link
Copy Markdown
Contributor

CLA Signature Action: All authors have signed the CLA. You may need to manually re-run the blocking PR check if it doesn't pass in a few minutes.

@metamaskbot metamaskbot added the team-accounts-framework Accounts team label Mar 19, 2026
@github-actions github-actions Bot added risk-medium Moderate testing recommended · Possible bug introduction risk size-XL labels Mar 19, 2026
Comment thread app/core/HardwareWallet/analytics/useHardwareWalletAnalytics.ts Outdated
@mathieuartu mathieuartu force-pushed the feat/add-hw-analytics branch from bb69d9d to e905f31 Compare March 19, 2026 09:54
@github-actions github-actions Bot added risk-medium Moderate testing recommended · Possible bug introduction risk and removed risk-medium Moderate testing recommended · Possible bug introduction risk labels Mar 19, 2026
@github-actions github-actions Bot added risk-low Low testing needed · Low bug introduction risk and removed risk-medium Moderate testing recommended · Possible bug introduction risk labels Mar 19, 2026
@mathieuartu mathieuartu changed the title feat: add hw analytics feat: add new hardware wallet flows analytics Mar 20, 2026
Comment thread app/core/HardwareWallet/analytics/helpers.ts Outdated
Comment thread app/core/HardwareWallet/analytics/useHardwareWalletAnalytics.ts Outdated
Comment thread app/core/HardwareWallet/analytics/useHardwareWalletAnalytics.ts Outdated
@github-actions github-actions Bot added risk-low Low testing needed · Low bug introduction risk and removed risk-low Low testing needed · Low bug introduction risk labels Mar 24, 2026
Comment thread app/core/HardwareWallet/HardwareWalletProvider.test.tsx
@github-actions github-actions Bot added risk-low Low testing needed · Low bug introduction risk and removed risk-low Low testing needed · Low bug introduction risk labels Mar 24, 2026
@mathieuartu mathieuartu enabled auto-merge March 24, 2026 15:56
@mathieuartu mathieuartu disabled auto-merge March 24, 2026 15:58

@gantunesr gantunesr left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Looks good. Did not test

@owencraston

Copy link
Copy Markdown
Contributor

did an initial round of testing and here were the results:

Overall, the events made sense and the properties for the most part were correct. The only issue I can see is that the location was always set to "location": "Connection", despite this being in the send flow. I would expect this value to be Send in this case.

{
"type": "track",
"event": "Hardware Wallet Recovery Modal Viewed",
"properties": {
"location": "Connection",
"device_type": "Ledger",
"error_type": "Device Locked",
"error_type_view_count": 1,
"error_code": "1100",
"error_message": "Unlock it and try again to continue"
}
}

{
"type": "track",
"event": "Hardware Wallet Recovery CTA Clicked",
"properties": {
"location": "Connection",
"device_type": "Ledger",
"error_type": "Device Locked",
"error_type_view_count": 1,
"error_code": "1100",
"error_message": "Unlock it and try again to continue"
}
}

{
"type": "track",
"event": "Hardware Wallet Recovery CTA Clicked",
"properties": {
"location": "Connection",
"device_type": "Ledger",
"error_type": "Device Locked",
"error_type_view_count": 2,
"error_code": "1100",
"error_message": "Unlock it and try again to continue"
}
}

{
"type": "track",
"event": "Hardware Wallet Recovery Modal Viewed",
"properties": {
"location": "Connection",
"device_type": "Ledger",
"error_type": "Device Locked",
"error_type_view_count": 3,
"error_code": "1100",
"error_message": "Unlock it and try again to continue"
}
}

{
"type": "track",
"event": "Hardware Wallet Recovery CTA Clicked",
"properties": {
"location": "Connection",
"device_type": "Ledger",
"error_type": "Device Locked",
"error_type_view_count": 3,
"error_code": "1100",
"error_message": "Unlock it and try again to continue"
}
}

{
"type": "track",
"event": "Hardware Wallet Recovery Modal Viewed",
"properties": {
"location": "Connection",
"device_type": "Ledger",
"error_type": "Ethereum App Not Opened",
"error_type_view_count": 1,
"error_code": "6003",
"error_message": "Open Ethereum app on device"
}
}

{
"type": "track",
"event": "Hardware Wallet Recovery Success Modal Viewed",
"properties": {
"location": "Connection",
"device_type": "Ledger",
"error_type": "Ethereum App Not Opened",
"error_type_view_count": 1,
"error_code": "6003",
"error_message": "Open Ethereum app on device"
}
}

@github-actions github-actions Bot added risk-medium Moderate testing recommended · Possible bug introduction risk and removed risk-low Low testing needed · Low bug introduction risk labels Mar 26, 2026
@github-actions

Copy link
Copy Markdown
Contributor

🔍 Smart E2E Test Selection

  • Selected E2E tags: SmokeAccounts, SmokeConfirmations
  • Selected Performance tags: None (no tests recommended)
  • Risk Level: medium
  • AI Confidence: 72%
click to see 🤖 AI reasoning details

E2E Test Selection:
The changes are primarily analytics additions to the hardware wallet flow:

  1. New analytics events in MetaMetrics.events.ts for hardware wallet recovery (modal viewed, CTA clicked, success modal viewed) - additive only.

  2. HardwareWalletProvider.tsx now integrates useHardwareWalletAnalytics and useAnalyticsFlowFromApproval hooks. The provider wraps the entire app (via Root/index.tsx), so any regression here could affect all hardware wallet users. The changes add new state (analyticsFlow, operationTypeRef), new callbacks (handleFlowStart, handleCloseFlow), and wire up onCTAClicked to the bottom sheet.

  3. HardwareWalletBottomSheet.tsx adds onCTAClicked prop that fires on Continue/Retry/Dismiss - this modifies the callback chain for error recovery flows.

  4. useDeviceConnectionFlow.ts adds onFlowStart callback that fires at the start of each ensureDeviceReady call.

  5. useAnalyticsFlowFromApproval.ts reads from Redux store (pending approvals + transaction metadata) to derive the analytics flow context.

Why SmokeAccounts: The SmokeAccounts tag covers QR-based hardware wallet account addition flows, which directly exercise the HardwareWalletProvider and its connection flow. The changes to handleCloseFlow (resetting analytics state on close) and onFlowStart could affect the account connection UX.

Why SmokeConfirmations: Hardware wallet signing flows (personal_sign, typed data, transactions) go through the HardwareWalletProvider's showAwaitingConfirmation/hideAwaitingConfirmation path. The new operationTypeRef tracking and analytics flow derivation from pending approvals could affect confirmation flows for hardware wallet users.

No E2E tests found specifically for hardware wallet flows in the test suite (searched tests/smoke//* and e2e//*), so these tags cover the closest related flows.

The changes are analytics-only additions with no functional logic changes to connection/signing, making the risk medium rather than high. The useHardwareWalletStateManager.ts change is a pure refactor with no functional difference.

Performance Test Selection:
The changes are analytics additions to the hardware wallet flow - new event tracking hooks and callbacks. These do not affect UI rendering performance, data loading, account/network list components, or critical user flow timing. The new hooks (useHardwareWalletAnalytics, useAnalyticsFlowFromApproval) are lightweight React hooks that read from Redux store and fire analytics events on state transitions. No performance tests are warranted.

View GitHub Actions results

@cursor cursor Bot left a comment

Copy link
Copy Markdown
Contributor

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.

Fix All in Cursor

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

});

const awaitingConfirmationRejectRef = useRef<(() => void) | null>(null);
const operationTypeRef = useRef<'transaction' | 'message' | null>(null);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Unused operationTypeRef is written but never read

Low Severity

operationTypeRef is declared, assigned in showAwaitingConfirmation, and cleared in hideAwaitingConfirmation, but its .current value is never read anywhere — not in the provider, not in the context value, and not passed to any child component or hook. This is dead code introduced in this PR.

Additional Locations (2)
Fix in Cursor Fix in Web

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Will fix in a small follow up since this was tested manually and approved already

@sonarqubecloud

Copy link
Copy Markdown

@owencraston owencraston left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Tested my with my Ledger Flex using all three flows (swaps, send and dapps) and everything worked as expected. here were the results...

{
"type": "track",
"event": "Hardware Wallet Recovery Modal Viewed",
"properties": {
"location": "Send",
"device_type": "Ledger",
"error_type": "Device Locked",
"error_type_view_count": 1,
"error_code": "1100",
"error_message": "Unlock it and try again to continue"
}
}

{
"type": "track",
"event": "Hardware Wallet Recovery CTA Clicked",
"properties": {
"location": "Send",
"device_type": "Ledger",
"error_type": "Device Locked",
"error_type_view_count": 1,
"error_code": "1100",
"error_message": "Unlock it and try again to continue"
}
}

{
"type": "track",
"event": "Hardware Wallet Recovery Modal Viewed",
"properties": {
"location": "Send",
"device_type": "Ledger",
"error_type": "Ethereum App Not Opened",
"error_type_view_count": 1,
"error_code": "6003",
"error_message": "Open Ethereum app on device"
}
}

{
"type": "track",
"event": "Hardware Wallet Recovery CTA Clicked",
"properties": {
"location": "Send",
"device_type": "Ledger",
"error_type": "Ethereum App Not Opened",
"error_type_view_count": 1,
"error_code": "6003",
"error_message": "Open Ethereum app on device"
}
}

{
"type": "track",
"event": "Hardware Wallet Recovery Modal Viewed",
"properties": {
"location": "Send",
"device_type": "Ledger",
"error_type": "Ethereum App Not Opened",
"error_type_view_count": 2,
"error_code": "6003",
"error_message": "Open Ethereum app on device"
}
}

{
"type": "track",
"event": "Hardware Wallet Recovery Success Modal Viewed",
"properties": {
"location": "Send",
"device_type": "Ledger",
"error_type": "Ethereum App Not Opened",
"error_type_view_count": 2,
"error_code": "6003",
"error_message": "Open Ethereum app on device"
}
}

// swap

{
"type": "track",
"event": "Hardware Wallet Recovery Modal Viewed",
"properties": {
"location": "Swaps",
"device_type": "Ledger",
"error_type": "Device Locked",
"error_type_view_count": 1,
"error_code": "1100",
"error_message": "Unlock it and try again to continue"
}
}

{
"type": "track",
"event": "Hardware Wallet Recovery CTA Clicked",
"properties": {
"location": "Swaps",
"device_type": "Ledger",
"error_type": "Device Locked",
"error_type_view_count": 1,
"error_code": "1100",
"error_message": "Unlock it and try again to continue"
}
}

{
"type": "track",
"event": "Hardware Wallet Recovery Modal Viewed",
"properties": {
"location": "Swaps",
"device_type": "Ledger",
"error_type": "Ethereum App Not Opened",
"error_type_view_count": 1,
"error_code": "6003",
"error_message": "Open Ethereum app on device"
}
}

{
"type": "track",
"event": "Hardware Wallet Recovery CTA Clicked",
"properties": {
"location": "Swaps",
"device_type": "Ledger",
"error_type": "Ethereum App Not Opened",
"error_type_view_count": 1,
"error_code": "6003",
"error_message": "Open Ethereum app on device"
}
}

{
"type": "track",
"event": "Hardware Wallet Recovery Success Modal Viewed",
"properties": {
"location": "Swaps",
"device_type": "Ledger",
"error_type": "Ethereum App Not Opened",
"error_type_view_count": 1,
"error_code": "6003",
"error_message": "Open Ethereum app on device"
}
}

{
"type": "track",
"event": "Hardware Wallet Recovery Modal Viewed",
"properties": {
"location": "Swaps",
"device_type": "Ledger",
"error_type": "Blind Signing Not Enabled",
"error_type_view_count": 1,
"error_code": "6001",
"error_message": "Blind signing is disabled. Please enable it in your device settings"
}
}

// dapps

{
"type": "track",
"event": "Hardware Wallet Recovery Modal Viewed",
"properties": {
"location": "Message",
"device_type": "Ledger",
"error_type": "Device Locked",
"error_type_view_count": 1,
"error_code": "1100",
"error_message": "Unlock it and try again to continue"
}
}

{
"type": "track",
"event": "Hardware Wallet Recovery CTA Clicked",
"properties": {
"location": "Message",
"device_type": "Ledger",
"error_type": "Device Locked",
"error_type_view_count": 1,
"error_code": "1100",
"error_message": "Unlock it and try again to continue"
}
}

{
"type": "track",
"event": "Hardware Wallet Recovery Modal Viewed",
"properties": {
"location": "Message",
"device_type": "Ledger",
"error_type": "Ethereum App Not Opened",
"error_type_view_count": 1,
"error_code": "6003",
"error_message": "Open Ethereum app on device"
}
}

{
"type": "track",
"event": "Hardware Wallet Recovery Success Modal Viewed",
"properties": {
"location": "Message",
"device_type": "Ledger",
"error_type": "Ethereum App Not Opened",
"error_type_view_count": 1,
"error_code": "6003",
"error_message": "Open Ethereum app on device"
}
}

{
"type": "track",
"event": "Signature Rejected",
"properties": {
"account_type": "Ledger",
"dapp_host_name": "[[metamask.github.io](http://metamask.github.io/)](http://metamask.github.io/)",
"signature_type": "eth_signTypedData",
"version": "V1",
"chain_id": "1",
"request_source": "In-App-Browser",
"security_alert_response": "Benign",
"security_alert_reason": "",
"security_alert_source": "api"
}
}

{
"type": "track",
"event": "Hardware Wallet Recovery Modal Viewed",
"properties": {
"location": "Message",
"device_type": "Ledger",
"error_type": "Generic Error",
"error_type_view_count": 1,
"error_code": "99999",
"error_message": "Make sure your Ledger is set up with the Secret Recovery Phrase or passphrase for this account"
}
}

@owencraston

owencraston commented Mar 27, 2026

Copy link
Copy Markdown
Contributor

Pr to remove unused values from schema: https://github.com/Consensys/segment-schema/pull/501

@mathieuartu mathieuartu added this pull request to the merge queue Mar 27, 2026
Merged via the queue into main with commit d50db18 Mar 27, 2026
61 of 62 checks passed
@mathieuartu mathieuartu deleted the feat/add-hw-analytics branch March 27, 2026 09:00
@github-actions github-actions Bot locked and limited conversation to collaborators Mar 27, 2026
@metamaskbot metamaskbot added the release-7.73.0 Issue or pull request that will be included in release 7.73.0 label Mar 27, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

release-7.73.0 Issue or pull request that will be included in release 7.73.0 risk-medium Moderate testing recommended · Possible bug introduction risk size-XL skip-e2e skip E2E test jobs team-accounts team-accounts-framework Accounts team

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants