From a82e7d6ec9681c5ce1507c292b8c1bf508ef4a2b Mon Sep 17 00:00:00 2001 From: IF <139582705+infiniteflower@users.noreply.github.com> Date: Tue, 3 Feb 2026 11:13:18 -0500 Subject: [PATCH 1/5] feat(bridge-status-controller): add Max Polling Reached metrics event Track when polling has reached maximum attempts and stops, emitting the same properties as the Submitted event plus polling_attempts count. --- .../src/utils/metrics/constants.ts | 2 ++ .../src/utils/metrics/types.ts | 15 ++++++++ .../src/bridge-status-controller.ts | 35 ++++++++++++++++++- 3 files changed, 51 insertions(+), 1 deletion(-) diff --git a/packages/bridge-controller/src/utils/metrics/constants.ts b/packages/bridge-controller/src/utils/metrics/constants.ts index bf5da4454f3..49948778efe 100644 --- a/packages/bridge-controller/src/utils/metrics/constants.ts +++ b/packages/bridge-controller/src/utils/metrics/constants.ts @@ -1,3 +1,4 @@ +/* eslint-disable @typescript-eslint/naming-convention */ export const UNIFIED_SWAP_BRIDGE_EVENT_CATEGORY = 'Unified SwapBridge'; /** @@ -20,6 +21,7 @@ export enum UnifiedSwapBridgeEventName { AssetDetailTooltipClicked = `${UNIFIED_SWAP_BRIDGE_EVENT_CATEGORY} Asset Detail Tooltip Clicked`, QuotesValidationFailed = `${UNIFIED_SWAP_BRIDGE_EVENT_CATEGORY} Quotes Failed Validation`, StatusValidationFailed = `${UNIFIED_SWAP_BRIDGE_EVENT_CATEGORY} Status Failed Validation`, + MaxPollingReached = `${UNIFIED_SWAP_BRIDGE_EVENT_CATEGORY} Max Polling Reached`, } export enum AbortReason { diff --git a/packages/bridge-controller/src/utils/metrics/types.ts b/packages/bridge-controller/src/utils/metrics/types.ts index fe6c7a0ef40..f73d3d9d56f 100644 --- a/packages/bridge-controller/src/utils/metrics/types.ts +++ b/packages/bridge-controller/src/utils/metrics/types.ts @@ -1,3 +1,4 @@ +/* eslint-disable @typescript-eslint/naming-convention */ import type { CaipAssetType, CaipChainId } from '@metamask/utils'; import type { @@ -212,6 +213,19 @@ export type RequiredEventContextFromClient = { [UnifiedSwapBridgeEventName.StatusValidationFailed]: { failures: string[]; }; + [UnifiedSwapBridgeEventName.MaxPollingReached]: TradeData & + Pick & + Omit & + Pick< + RequestParams, + | 'token_symbol_source' + | 'token_symbol_destination' + | 'chain_id_source' + | 'chain_id_destination' + > & { + action_type: MetricsActionType; + polling_attempts: number; + }; }; /** @@ -268,6 +282,7 @@ export type EventPropertiesFromControllerState = { [UnifiedSwapBridgeEventName.StatusValidationFailed]: RequestParams & { refresh_count: number; }; + [UnifiedSwapBridgeEventName.MaxPollingReached]: null; }; /** diff --git a/packages/bridge-status-controller/src/bridge-status-controller.ts b/packages/bridge-status-controller/src/bridge-status-controller.ts index 7383d6d963e..8955c0458f1 100644 --- a/packages/bridge-status-controller/src/bridge-status-controller.ts +++ b/packages/bridge-status-controller/src/bridge-status-controller.ts @@ -665,6 +665,38 @@ export class BridgeStatusController extends StaticIntervalPollingController= MAX_ATTEMPTS && pollingToken) { this.stopPollingByPollingToken(pollingToken); delete this.#pollingTokensByTxMetaId[bridgeTxMetaId]; + + // Track max polling reached event + const historyItem = this.state.txHistory[bridgeTxMetaId]; + if (historyItem && !historyItem.featureId) { + const selectedAccount = this.messenger.call( + 'AccountsController:getAccountByAddress', + historyItem.account, + ); + const requestParams = getRequestParamFromHistory(historyItem); + const requestMetadata = getRequestMetadataFromHistory( + historyItem, + selectedAccount, + ); + const { security_warnings: _, ...metadataWithoutWarnings } = + requestMetadata; + + this.#trackUnifiedSwapBridgeEvent( + UnifiedSwapBridgeEventName.MaxPollingReached, + bridgeTxMetaId, + { + ...getTradeDataFromHistory(historyItem), + ...getPriceImpactFromQuote(historyItem.quote), + ...metadataWithoutWarnings, + chain_id_source: requestParams.chain_id_source, + chain_id_destination: requestParams.chain_id_destination, + token_symbol_source: requestParams.token_symbol_source, + token_symbol_destination: requestParams.token_symbol_destination, + action_type: MetricsActionType.SWAPBRIDGE_V1, + polling_attempts: newAttempts.counter, + }, + ); + } } // Update the attempts counter @@ -1915,7 +1947,8 @@ export class BridgeStatusController extends StaticIntervalPollingController( eventName: EventName, txMetaId?: string, From efee5cf4d58db5ae6865806937ad0b0be0be2706 Mon Sep 17 00:00:00 2001 From: IF <139582705+infiniteflower@users.noreply.github.com> Date: Tue, 3 Feb 2026 11:25:18 -0500 Subject: [PATCH 2/5] feat(bridge-status-controller): add Polling Manually Restarted metrics event Track when polling is manually restarted via restartPollingForFailedAttempts, emitting the same properties as the Submitted event plus polling_attempts count. --- .../src/utils/metrics/constants.ts | 1 + .../src/utils/metrics/types.ts | 14 +++++++ .../src/bridge-status-controller.ts | 37 ++++++++++++++++++- 3 files changed, 51 insertions(+), 1 deletion(-) diff --git a/packages/bridge-controller/src/utils/metrics/constants.ts b/packages/bridge-controller/src/utils/metrics/constants.ts index 49948778efe..289c7a15c8f 100644 --- a/packages/bridge-controller/src/utils/metrics/constants.ts +++ b/packages/bridge-controller/src/utils/metrics/constants.ts @@ -22,6 +22,7 @@ export enum UnifiedSwapBridgeEventName { QuotesValidationFailed = `${UNIFIED_SWAP_BRIDGE_EVENT_CATEGORY} Quotes Failed Validation`, StatusValidationFailed = `${UNIFIED_SWAP_BRIDGE_EVENT_CATEGORY} Status Failed Validation`, MaxPollingReached = `${UNIFIED_SWAP_BRIDGE_EVENT_CATEGORY} Max Polling Reached`, + PollingManuallyRestarted = `${UNIFIED_SWAP_BRIDGE_EVENT_CATEGORY} Polling Manually Restarted`, } export enum AbortReason { diff --git a/packages/bridge-controller/src/utils/metrics/types.ts b/packages/bridge-controller/src/utils/metrics/types.ts index f73d3d9d56f..5bd4682c0a5 100644 --- a/packages/bridge-controller/src/utils/metrics/types.ts +++ b/packages/bridge-controller/src/utils/metrics/types.ts @@ -226,6 +226,19 @@ export type RequiredEventContextFromClient = { action_type: MetricsActionType; polling_attempts: number; }; + [UnifiedSwapBridgeEventName.PollingManuallyRestarted]: TradeData & + Pick & + Omit & + Pick< + RequestParams, + | 'token_symbol_source' + | 'token_symbol_destination' + | 'chain_id_source' + | 'chain_id_destination' + > & { + action_type: MetricsActionType; + polling_attempts: number; + }; }; /** @@ -283,6 +296,7 @@ export type EventPropertiesFromControllerState = { refresh_count: number; }; [UnifiedSwapBridgeEventName.MaxPollingReached]: null; + [UnifiedSwapBridgeEventName.PollingManuallyRestarted]: null; }; /** diff --git a/packages/bridge-status-controller/src/bridge-status-controller.ts b/packages/bridge-status-controller/src/bridge-status-controller.ts index 8955c0458f1..1206fcb86c2 100644 --- a/packages/bridge-status-controller/src/bridge-status-controller.ts +++ b/packages/bridge-status-controller/src/bridge-status-controller.ts @@ -379,6 +379,9 @@ export class BridgeStatusController extends StaticIntervalPollingController { if (targetTxMetaId) { @@ -400,6 +403,37 @@ export class BridgeStatusController extends StaticIntervalPollingController( eventName: EventName, txMetaId?: string, From a551b8a0518bb955c15a64ac7529f5e3aeb983db Mon Sep 17 00:00:00 2001 From: IF <139582705+infiniteflower@users.noreply.github.com> Date: Tue, 3 Feb 2026 11:44:30 -0500 Subject: [PATCH 3/5] docs: update changelogs for polling metrics events --- packages/bridge-controller/CHANGELOG.md | 1 + packages/bridge-status-controller/CHANGELOG.md | 5 +++++ 2 files changed, 6 insertions(+) diff --git a/packages/bridge-controller/CHANGELOG.md b/packages/bridge-controller/CHANGELOG.md index 75bded7a9ec..7b6e52adb4a 100644 --- a/packages/bridge-controller/CHANGELOG.md +++ b/packages/bridge-controller/CHANGELOG.md @@ -11,6 +11,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Add `HYPEREVM` network support ([#7787](https://github.com/MetaMask/core/pull/7787)) - Add `HYPEREVM` into constants `ALLOWED_BRIDGE_CHAIN_IDS`, `SWAPS_TOKEN_OBJECT` and `NETWORK_TO_NAME_MAP` +- Add `MaxPollingReached` and `PollingManuallyRestarted` to `UnifiedSwapBridgeEventName` enum ([#7825](https://github.com/MetaMask/core/pull/7825)) ### Changed diff --git a/packages/bridge-status-controller/CHANGELOG.md b/packages/bridge-status-controller/CHANGELOG.md index 3a139096178..9d91ccaefc2 100644 --- a/packages/bridge-status-controller/CHANGELOG.md +++ b/packages/bridge-status-controller/CHANGELOG.md @@ -7,6 +7,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Added + +- Add `Unified SwapBridge Max Polling Reached` metrics event, emitted when polling stops due to max attempts ([#7825](https://github.com/MetaMask/core/pull/7825)) +- Add `Unified SwapBridge Polling Manually Restarted` metrics event, emitted when polling is restarted via `restartPollingForFailedAttempts` ([#7825](https://github.com/MetaMask/core/pull/7825)) + ### Changed - Bump `@metamask/transaction-controller` from `^62.11.0` to `^62.12.0` ([#7775](https://github.com/MetaMask/core/pull/7775)) From 58d02e4fbabbdda75765826aa2500e10a91167f7 Mon Sep 17 00:00:00 2001 From: IF <139582705+infiniteflower@users.noreply.github.com> Date: Tue, 3 Feb 2026 16:01:53 -0500 Subject: [PATCH 4/5] chore: remove naming-convention lint suppressions for bridge metrics files Enable linting for @typescript-eslint/naming-convention in constants.ts and types.ts under bridge-controller metrics. --- eslint-suppressions.json | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/eslint-suppressions.json b/eslint-suppressions.json index 0dae1f432de..277816c07da 100644 --- a/eslint-suppressions.json +++ b/eslint-suppressions.json @@ -650,21 +650,11 @@ "count": 1 } }, - "packages/bridge-controller/src/utils/metrics/constants.ts": { - "@typescript-eslint/naming-convention": { - "count": 2 - } - }, "packages/bridge-controller/src/utils/metrics/properties.ts": { "@typescript-eslint/explicit-function-return-type": { "count": 5 } }, - "packages/bridge-controller/src/utils/metrics/types.ts": { - "@typescript-eslint/naming-convention": { - "count": 83 - } - }, "packages/bridge-controller/src/utils/quote-fees.ts": { "@typescript-eslint/explicit-function-return-type": { "count": 1 From 71b86237cc3f8d8051d28d50db5d8112acb81da0 Mon Sep 17 00:00:00 2001 From: IF <139582705+infiniteflower@users.noreply.github.com> Date: Wed, 4 Feb 2026 14:19:18 -0500 Subject: [PATCH 5/5] refactor(bridge): combine polling metrics events into single event Combine MaxPollingReached and PollingManuallyRestarted events into a single PollingStatusUpdated event with a polling_status property to distinguish between the two cases. --- packages/bridge-controller/CHANGELOG.md | 2 +- packages/bridge-controller/src/index.ts | 1 + .../src/utils/metrics/constants.ts | 8 +++++-- .../src/utils/metrics/types.ts | 22 +++++-------------- .../bridge-status-controller/CHANGELOG.md | 3 +-- .../src/bridge-status-controller.ts | 14 +++++++----- 6 files changed, 22 insertions(+), 28 deletions(-) diff --git a/packages/bridge-controller/CHANGELOG.md b/packages/bridge-controller/CHANGELOG.md index 7b6e52adb4a..cf3997a8c95 100644 --- a/packages/bridge-controller/CHANGELOG.md +++ b/packages/bridge-controller/CHANGELOG.md @@ -11,7 +11,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Add `HYPEREVM` network support ([#7787](https://github.com/MetaMask/core/pull/7787)) - Add `HYPEREVM` into constants `ALLOWED_BRIDGE_CHAIN_IDS`, `SWAPS_TOKEN_OBJECT` and `NETWORK_TO_NAME_MAP` -- Add `MaxPollingReached` and `PollingManuallyRestarted` to `UnifiedSwapBridgeEventName` enum ([#7825](https://github.com/MetaMask/core/pull/7825)) +- Add `PollingStatusUpdated` to `UnifiedSwapBridgeEventName` enum and `PollingStatus` enum with `MaxPollingReached` and `ManuallyRestarted` values ([#7825](https://github.com/MetaMask/core/pull/7825)) ### Changed diff --git a/packages/bridge-controller/src/index.ts b/packages/bridge-controller/src/index.ts index 714397be2a2..666ab67908d 100644 --- a/packages/bridge-controller/src/index.ts +++ b/packages/bridge-controller/src/index.ts @@ -3,6 +3,7 @@ export { BridgeController } from './bridge-controller'; export { UnifiedSwapBridgeEventName, UNIFIED_SWAP_BRIDGE_EVENT_CATEGORY, + PollingStatus, } from './utils/metrics/constants'; export type { diff --git a/packages/bridge-controller/src/utils/metrics/constants.ts b/packages/bridge-controller/src/utils/metrics/constants.ts index 289c7a15c8f..4b4d94e9461 100644 --- a/packages/bridge-controller/src/utils/metrics/constants.ts +++ b/packages/bridge-controller/src/utils/metrics/constants.ts @@ -21,8 +21,12 @@ export enum UnifiedSwapBridgeEventName { AssetDetailTooltipClicked = `${UNIFIED_SWAP_BRIDGE_EVENT_CATEGORY} Asset Detail Tooltip Clicked`, QuotesValidationFailed = `${UNIFIED_SWAP_BRIDGE_EVENT_CATEGORY} Quotes Failed Validation`, StatusValidationFailed = `${UNIFIED_SWAP_BRIDGE_EVENT_CATEGORY} Status Failed Validation`, - MaxPollingReached = `${UNIFIED_SWAP_BRIDGE_EVENT_CATEGORY} Max Polling Reached`, - PollingManuallyRestarted = `${UNIFIED_SWAP_BRIDGE_EVENT_CATEGORY} Polling Manually Restarted`, + PollingStatusUpdated = `${UNIFIED_SWAP_BRIDGE_EVENT_CATEGORY} Polling Status Updated`, +} + +export enum PollingStatus { + MaxPollingReached = 'max_polling_reached', + ManuallyRestarted = 'manually_restarted', } export enum AbortReason { diff --git a/packages/bridge-controller/src/utils/metrics/types.ts b/packages/bridge-controller/src/utils/metrics/types.ts index 5bd4682c0a5..b78dd35c520 100644 --- a/packages/bridge-controller/src/utils/metrics/types.ts +++ b/packages/bridge-controller/src/utils/metrics/types.ts @@ -6,6 +6,7 @@ import type { MetaMetricsSwapsEventSource, MetricsActionType, MetricsSwapType, + PollingStatus, } from './constants'; import type { SortOrder, StatusTypes } from '../../types'; @@ -213,7 +214,7 @@ export type RequiredEventContextFromClient = { [UnifiedSwapBridgeEventName.StatusValidationFailed]: { failures: string[]; }; - [UnifiedSwapBridgeEventName.MaxPollingReached]: TradeData & + [UnifiedSwapBridgeEventName.PollingStatusUpdated]: TradeData & Pick & Omit & Pick< @@ -224,20 +225,8 @@ export type RequiredEventContextFromClient = { | 'chain_id_destination' > & { action_type: MetricsActionType; - polling_attempts: number; - }; - [UnifiedSwapBridgeEventName.PollingManuallyRestarted]: TradeData & - Pick & - Omit & - Pick< - RequestParams, - | 'token_symbol_source' - | 'token_symbol_destination' - | 'chain_id_source' - | 'chain_id_destination' - > & { - action_type: MetricsActionType; - polling_attempts: number; + polling_status: PollingStatus; + retry_attempts: number; }; }; @@ -295,8 +284,7 @@ export type EventPropertiesFromControllerState = { [UnifiedSwapBridgeEventName.StatusValidationFailed]: RequestParams & { refresh_count: number; }; - [UnifiedSwapBridgeEventName.MaxPollingReached]: null; - [UnifiedSwapBridgeEventName.PollingManuallyRestarted]: null; + [UnifiedSwapBridgeEventName.PollingStatusUpdated]: null; }; /** diff --git a/packages/bridge-status-controller/CHANGELOG.md b/packages/bridge-status-controller/CHANGELOG.md index 9d91ccaefc2..b3c16179183 100644 --- a/packages/bridge-status-controller/CHANGELOG.md +++ b/packages/bridge-status-controller/CHANGELOG.md @@ -9,8 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added -- Add `Unified SwapBridge Max Polling Reached` metrics event, emitted when polling stops due to max attempts ([#7825](https://github.com/MetaMask/core/pull/7825)) -- Add `Unified SwapBridge Polling Manually Restarted` metrics event, emitted when polling is restarted via `restartPollingForFailedAttempts` ([#7825](https://github.com/MetaMask/core/pull/7825)) +- Add `Unified SwapBridge Polling Status Updated` metrics event with `status` property (`max_polling_reached` or `manually_restarted`), emitted when polling stops due to max attempts or is manually restarted ([#7825](https://github.com/MetaMask/core/pull/7825)) ### Changed diff --git a/packages/bridge-status-controller/src/bridge-status-controller.ts b/packages/bridge-status-controller/src/bridge-status-controller.ts index 1206fcb86c2..bfe50c56232 100644 --- a/packages/bridge-status-controller/src/bridge-status-controller.ts +++ b/packages/bridge-status-controller/src/bridge-status-controller.ts @@ -21,6 +21,7 @@ import { isBitcoinTrade, isTronTrade, AbortReason, + PollingStatus, } from '@metamask/bridge-controller'; import type { TraceCallback } from '@metamask/controller-utils'; import { toHex } from '@metamask/controller-utils'; @@ -419,7 +420,7 @@ export class BridgeStatusController extends StaticIntervalPollingController( eventName: EventName, txMetaId?: string,