From 193096f037bece264438bd820ea561a96cc3407a Mon Sep 17 00:00:00 2001 From: GeorgeGkas Date: Fri, 17 Apr 2026 13:11:33 +0300 Subject: [PATCH 1/3] feat: normalize bridge hardware type --- .../bridge-controller.sse.test.ts.snap | 7 ++ .../bridge-controller.test.ts.snap | 21 +++- .../src/bridge-controller.test.ts | 6 +- .../src/bridge-controller.ts | 33 +++--- packages/bridge-controller/src/index.ts | 2 + .../src/utils/metrics/properties.test.ts | 35 ++++++ .../src/utils/metrics/properties.ts | 21 +++- .../src/utils/metrics/types.ts | 14 ++- .../bridge-status-controller.test.ts.snap | 48 ++++++++ .../src/bridge-status-controller.test.ts | 17 +-- .../src/bridge-status-controller.ts | 106 ++++++++++-------- .../src/utils/metrics.test.ts | 30 ++++- .../src/utils/metrics.ts | 21 +++- 13 files changed, 274 insertions(+), 87 deletions(-) diff --git a/packages/bridge-controller/src/__snapshots__/bridge-controller.sse.test.ts.snap b/packages/bridge-controller/src/__snapshots__/bridge-controller.sse.test.ts.snap index 5257395de2d..fd91b7ce23f 100644 --- a/packages/bridge-controller/src/__snapshots__/bridge-controller.sse.test.ts.snap +++ b/packages/bridge-controller/src/__snapshots__/bridge-controller.sse.test.ts.snap @@ -63,6 +63,7 @@ exports[`BridgeController SSE should replace all stale quotes after a refresh an [ "Unified SwapBridge Quotes Requested", { + "account_hardware_type": null, "action_type": "swapbridge-v1", "chain_id_destination": "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp", "chain_id_source": "eip155:1", @@ -89,6 +90,7 @@ exports[`BridgeController SSE should reset and refetch quotes after quote reques [ "Unified SwapBridge Quotes Requested", { + "account_hardware_type": null, "action_type": "swapbridge-v1", "chain_id_destination": "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp", "chain_id_source": "eip155:1", @@ -115,6 +117,7 @@ exports[`BridgeController SSE should reset quotes list if quote refresh fails 2` [ "Unified SwapBridge Quotes Requested", { + "account_hardware_type": null, "action_type": "swapbridge-v1", "chain_id_destination": "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp", "chain_id_source": "eip155:1", @@ -137,6 +140,7 @@ exports[`BridgeController SSE should reset quotes list if quote refresh fails 2` [ "Unified SwapBridge Quotes Error", { + "account_hardware_type": null, "action_type": "swapbridge-v1", "chain_id_destination": "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp", "chain_id_source": "eip155:1", @@ -232,6 +236,7 @@ exports[`BridgeController SSE should rethrow error from server 3`] = ` [ "Unified SwapBridge Quotes Requested", { + "account_hardware_type": null, "action_type": "swapbridge-v1", "chain_id_destination": "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp", "chain_id_source": "eip155:1", @@ -254,6 +259,7 @@ exports[`BridgeController SSE should rethrow error from server 3`] = ` [ "Unified SwapBridge Quotes Error", { + "account_hardware_type": null, "action_type": "swapbridge-v1", "chain_id_destination": "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp", "chain_id_source": "eip155:1", @@ -349,6 +355,7 @@ exports[`BridgeController SSE should trigger quote polling if request is valid 2 [ "Unified SwapBridge Quotes Requested", { + "account_hardware_type": null, "action_type": "swapbridge-v1", "chain_id_destination": "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp", "chain_id_source": "eip155:1", diff --git a/packages/bridge-controller/src/__snapshots__/bridge-controller.test.ts.snap b/packages/bridge-controller/src/__snapshots__/bridge-controller.test.ts.snap index 5fb18da1a0c..21dfa063595 100644 --- a/packages/bridge-controller/src/__snapshots__/bridge-controller.test.ts.snap +++ b/packages/bridge-controller/src/__snapshots__/bridge-controller.test.ts.snap @@ -61,6 +61,7 @@ exports[`BridgeController trackUnifiedSwapBridgeEvent bridge-status-controller c [ "Unified SwapBridge Completed", { + "account_hardware_type": null, "action_type": "swapbridge-v1", "actual_time_minutes": 10, "approval_transaction": "PENDING", @@ -98,6 +99,7 @@ exports[`BridgeController trackUnifiedSwapBridgeEvent bridge-status-controller c [ "Unified SwapBridge Failed", { + "account_hardware_type": null, "action_type": "swapbridge-v1", "actual_time_minutes": 10, "allowance_reset_transaction": "PENDING", @@ -140,6 +142,7 @@ exports[`BridgeController trackUnifiedSwapBridgeEvent bridge-status-controller c [ "Unified SwapBridge Failed", { + "account_hardware_type": null, "action_type": "swapbridge-v1", "chain_id_destination": "eip155:1", "chain_id_source": "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp", @@ -195,6 +198,7 @@ exports[`BridgeController trackUnifiedSwapBridgeEvent bridge-status-controller c [ "Unified SwapBridge Submitted", { + "account_hardware_type": null, "action_type": "swapbridge-v1", "chain_id_destination": "eip155:1", "chain_id_source": "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp", @@ -223,6 +227,7 @@ exports[`BridgeController trackUnifiedSwapBridgeEvent client-side calls should t [ "Unified SwapBridge All Quotes Opened", { + "account_hardware_type": null, "action_type": "swapbridge-v1", "can_submit": true, "chain_id_destination": null, @@ -253,6 +258,7 @@ exports[`BridgeController trackUnifiedSwapBridgeEvent client-side calls should t [ "Unified SwapBridge All Quotes Sorted", { + "account_hardware_type": null, "action_type": "swapbridge-v1", "best_quote_provider": "provider_bridge2", "can_submit": true, @@ -341,11 +347,15 @@ exports[`BridgeController trackUnifiedSwapBridgeEvent client-side calls should t [ "Unified SwapBridge Page Viewed", { - "abc": 1, + "account_hardware_type": null, "action_type": "swapbridge-v1", "chain_id_destination": null, "chain_id_source": "eip155:1", + "custom_slippage": false, + "is_hardware_wallet": false, "location": "Main View", + "slippage_limit": undefined, + "swap_type": "crosschain", "token_address_destination": null, "token_address_source": "eip155:1/slip44:60", }, @@ -358,6 +368,7 @@ exports[`BridgeController trackUnifiedSwapBridgeEvent client-side calls should t [ "Unified SwapBridge Quote Selected", { + "account_hardware_type": null, "action_type": "swapbridge-v1", "best_quote_provider": "provider_bridge2", "can_submit": false, @@ -401,6 +412,7 @@ exports[`BridgeController trackUnifiedSwapBridgeEvent client-side calls should t [ "Unified SwapBridge Quotes Received", { + "account_hardware_type": null, "action_type": "swapbridge-v1", "best_quote_provider": "provider_bridge2", "can_submit": true, @@ -475,6 +487,7 @@ exports[`BridgeController updateBridgeQuoteRequestParams should only poll once i [ "Unified SwapBridge Quotes Requested", { + "account_hardware_type": null, "action_type": "swapbridge-v1", "chain_id_destination": "eip155:10", "chain_id_source": "eip155:1", @@ -497,6 +510,7 @@ exports[`BridgeController updateBridgeQuoteRequestParams should only poll once i [ "Unified SwapBridge Quotes Received", { + "account_hardware_type": null, "action_type": "swapbridge-v1", "best_quote_provider": "provider_bridge2", "can_submit": true, @@ -892,6 +906,7 @@ exports[`BridgeController updateBridgeQuoteRequestParams should trigger quote po [ "Unified SwapBridge Quotes Requested", { + "account_hardware_type": null, "action_type": "swapbridge-v1", "chain_id_destination": "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp", "chain_id_source": "eip155:1", @@ -914,6 +929,7 @@ exports[`BridgeController updateBridgeQuoteRequestParams should trigger quote po [ "Unified SwapBridge Quotes Requested", { + "account_hardware_type": null, "action_type": "swapbridge-v1", "chain_id_destination": "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp", "chain_id_source": "eip155:1", @@ -936,6 +952,7 @@ exports[`BridgeController updateBridgeQuoteRequestParams should trigger quote po [ "Unified SwapBridge Quotes Requested", { + "account_hardware_type": null, "action_type": "swapbridge-v1", "chain_id_destination": "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp", "chain_id_source": "eip155:1", @@ -958,6 +975,7 @@ exports[`BridgeController updateBridgeQuoteRequestParams should trigger quote po [ "Unified SwapBridge Quotes Error", { + "account_hardware_type": null, "action_type": "swapbridge-v1", "chain_id_destination": "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp", "chain_id_source": "eip155:1", @@ -981,6 +999,7 @@ exports[`BridgeController updateBridgeQuoteRequestParams should trigger quote po [ "Unified SwapBridge Quotes Requested", { + "account_hardware_type": null, "action_type": "swapbridge-v1", "chain_id_destination": "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp", "chain_id_source": "eip155:1", diff --git a/packages/bridge-controller/src/bridge-controller.test.ts b/packages/bridge-controller/src/bridge-controller.test.ts index 1443f94d49c..17206df11d2 100644 --- a/packages/bridge-controller/src/bridge-controller.test.ts +++ b/packages/bridge-controller/src/bridge-controller.test.ts @@ -2895,8 +2895,7 @@ describe('BridgeController', function () { rootMessenger.call( 'BridgeController:trackUnifiedSwapBridgeEvent', UnifiedSwapBridgeEventName.PageViewed, - // @ts-expect-error Partial mock. - { abc: 1 }, + {}, ); expect(trackMetaMetricsFn).toHaveBeenCalledTimes(1); @@ -3267,6 +3266,7 @@ describe('BridgeController', function () { chain_id_destination: formatChainIdToCaip(1), custom_slippage: false, is_hardware_wallet: false, + account_hardware_type: null, slippage_limit: 0.5, usd_quoted_gas: 1, gas_included: false, @@ -3310,6 +3310,7 @@ describe('BridgeController', function () { usd_amount_source: 100, stx_enabled: false, is_hardware_wallet: false, + account_hardware_type: null, swap_type: MetricsSwapType.CROSSCHAIN, provider: 'provider_bridge', price_impact: 6, @@ -3355,6 +3356,7 @@ describe('BridgeController', function () { usd_amount_source: 100, stx_enabled: false, is_hardware_wallet: false, + account_hardware_type: null, swap_type: MetricsSwapType.CROSSCHAIN, chain_id_destination: formatChainIdToCaip(ChainId.SOLANA), token_symbol_destination: 'USDC', diff --git a/packages/bridge-controller/src/bridge-controller.ts b/packages/bridge-controller/src/bridge-controller.ts index 858f6046583..6445f048814 100644 --- a/packages/bridge-controller/src/bridge-controller.ts +++ b/packages/bridge-controller/src/bridge-controller.ts @@ -67,10 +67,10 @@ import { } from './utils/metrics/constants'; import { formatProviderLabel, + getAccountHardwareType, getRequestParams, getSwapTypeFromQuote, isCustomSlippage, - isHardwareWallet, toInputChangedPropertyKey, toInputChangedPropertyValue, } from './utils/metrics/properties'; @@ -933,15 +933,21 @@ export class BridgeController extends StaticIntervalPollingController => { + const { walletAddress } = this.state.quoteRequest; + const accountHardwareType = getAccountHardwareType( + walletAddress + ? this.#getMultichainSelectedAccount(walletAddress) + : undefined, + ); + return { slippage_limit: this.state.quoteRequest.slippage, swap_type: getSwapTypeFromQuote(this.state.quoteRequest), custom_slippage: isCustomSlippage(this.state.quoteRequest.slippage), + account_hardware_type: accountHardwareType, + is_hardware_wallet: accountHardwareType !== null, }; }; @@ -979,9 +985,14 @@ export class BridgeController extends StaticIntervalPollingController { }); }); + describe('getAccountHardwareType', () => { + it('returns null for non-hardware accounts', () => { + expect( + getAccountHardwareType({ + metadata: { + keyring: { + type: 'HD Key Tree', + }, + }, + } as never), + ).toBeNull(); + expect(isHardwareWallet(undefined)).toBe(false); + }); + + it.each([ + ['Ledger Hardware', 'Ledger'], + ['Trezor Hardware', 'Trezor'], + ['QR Hardware Wallet Device', 'QR Hardware'], + ['Lattice Hardware', 'Lattice'], + ] as const)('maps %s to %s', (keyringType, expected) => { + const account = { + metadata: { + keyring: { + type: keyringType, + }, + }, + } as never; + + expect(getAccountHardwareType(account)).toBe(expected); + expect(isHardwareWallet(account)).toBe(true); + }); + }); + describe('getRequestParams', () => { beforeEach(() => { jest.clearAllMocks(); diff --git a/packages/bridge-controller/src/utils/metrics/properties.ts b/packages/bridge-controller/src/utils/metrics/properties.ts index bb2501cb7c2..bfc8289d716 100644 --- a/packages/bridge-controller/src/utils/metrics/properties.ts +++ b/packages/bridge-controller/src/utils/metrics/properties.ts @@ -16,6 +16,7 @@ import { } from '../caip-formatters'; import { MetricsSwapType } from './constants'; import type { + AccountHardwareType, InputKeys, InputValues, QuoteWarning, @@ -106,10 +107,28 @@ export const getRequestParams = ({ }; }; +export const getAccountHardwareType = ( + selectedAccount?: AccountsControllerState['internalAccounts']['accounts'][string], +): AccountHardwareType => { + // Unified bridge analytics only support the schema enum values for hardware accounts. + switch (selectedAccount?.metadata?.keyring.type) { + case 'Ledger Hardware': + return 'Ledger'; + case 'Trezor Hardware': + return 'Trezor'; + case 'QR Hardware Wallet Device': + return 'QR Hardware'; + case 'Lattice Hardware': + return 'Lattice'; + default: + return null; + } +}; + export const isHardwareWallet = ( selectedAccount?: AccountsControllerState['internalAccounts']['accounts'][string], ) => { - return selectedAccount?.metadata?.keyring.type?.includes('Hardware') ?? false; + return getAccountHardwareType(selectedAccount) !== null; }; /** diff --git a/packages/bridge-controller/src/utils/metrics/types.ts b/packages/bridge-controller/src/utils/metrics/types.ts index 46d44ebd786..253ea403685 100644 --- a/packages/bridge-controller/src/utils/metrics/types.ts +++ b/packages/bridge-controller/src/utils/metrics/types.ts @@ -22,12 +22,20 @@ export type RequestParams = { token_address_destination: CaipAssetType | null; }; +export type AccountHardwareType = + | 'Ledger' + | 'Trezor' + | 'QR Hardware' + | 'Lattice' + | null; + export type RequestMetadata = { slippage_limit?: number; // undefined === auto custom_slippage: boolean; usd_amount_source: number; // Use quoteResponse when available stx_enabled: boolean; is_hardware_wallet: boolean; + account_hardware_type: AccountHardwareType; swap_type: MetricsSwapType; security_warnings: string[]; }; @@ -263,7 +271,11 @@ export type RequiredEventContextFromClient = { */ export type EventPropertiesFromControllerState = { [UnifiedSwapBridgeEventName.ButtonClicked]: RequestParams; - [UnifiedSwapBridgeEventName.PageViewed]: RequestParams; + [UnifiedSwapBridgeEventName.PageViewed]: RequestParams & + Omit< + RequestMetadata, + 'stx_enabled' | 'usd_amount_source' | 'security_warnings' + >; [UnifiedSwapBridgeEventName.InputChanged]: { input: InputKeys; input_value: string; diff --git a/packages/bridge-status-controller/src/__snapshots__/bridge-status-controller.test.ts.snap b/packages/bridge-status-controller/src/__snapshots__/bridge-status-controller.test.ts.snap index d7ab89a99d2..29afe29f9dd 100644 --- a/packages/bridge-status-controller/src/__snapshots__/bridge-status-controller.test.ts.snap +++ b/packages/bridge-status-controller/src/__snapshots__/bridge-status-controller.test.ts.snap @@ -161,6 +161,7 @@ exports[`BridgeStatusController startPollingForBridgeTxStatus emits bridgeTransa "BridgeController:trackUnifiedSwapBridgeEvent", "Unified SwapBridge Failed", { + "account_hardware_type": null, "action_type": "swapbridge-v1", "actual_time_minutes": 105213.34261666666, "allowance_reset_transaction": undefined, @@ -340,6 +341,7 @@ exports[`BridgeStatusController startPollingForBridgeTxStatus stops polling when "BridgeController:trackUnifiedSwapBridgeEvent", "Unified SwapBridge Completed", { + "account_hardware_type": null, "action_type": "swapbridge-v1", "actual_time_minutes": 105213.34261666666, "allowance_reset_transaction": undefined, @@ -546,6 +548,7 @@ exports[`BridgeStatusController submitTx: EVM bridge should delay after submitti "BridgeController:trackUnifiedSwapBridgeEvent", "Unified SwapBridge Submitted", { + "account_hardware_type": null, "action_type": "swapbridge-v1", "chain_id_destination": "eip155:10", "chain_id_source": "eip155:8453", @@ -865,6 +868,7 @@ exports[`BridgeStatusController submitTx: EVM bridge should delay after submitti "BridgeController:trackUnifiedSwapBridgeEvent", "Unified SwapBridge Submitted", { + "account_hardware_type": null, "action_type": "swapbridge-v1", "chain_id_destination": "eip155:10", "chain_id_source": "eip155:59144", @@ -1233,6 +1237,7 @@ exports[`BridgeStatusController submitTx: EVM bridge should handle smart transac "BridgeController:trackUnifiedSwapBridgeEvent", "Unified SwapBridge Submitted", { + "account_hardware_type": null, "action_type": "swapbridge-v1", "chain_id_destination": "eip155:10", "chain_id_source": "eip155:42161", @@ -1481,6 +1486,7 @@ exports[`BridgeStatusController submitTx: EVM bridge should not call handleMobil "BridgeController:trackUnifiedSwapBridgeEvent", "Unified SwapBridge Submitted", { + "account_hardware_type": null, "action_type": "swapbridge-v1", "chain_id_destination": "eip155:10", "chain_id_source": "eip155:42161", @@ -1803,6 +1809,7 @@ exports[`BridgeStatusController submitTx: EVM bridge should not call handleMobil "BridgeController:trackUnifiedSwapBridgeEvent", "Unified SwapBridge Submitted", { + "account_hardware_type": null, "action_type": "swapbridge-v1", "chain_id_destination": "eip155:10", "chain_id_source": "eip155:42161", @@ -2125,6 +2132,7 @@ exports[`BridgeStatusController submitTx: EVM bridge should reset USDT allowance "BridgeController:trackUnifiedSwapBridgeEvent", "Unified SwapBridge Submitted", { + "account_hardware_type": null, "action_type": "swapbridge-v1", "chain_id_destination": "eip155:10", "chain_id_source": "eip155:42161", @@ -2473,6 +2481,7 @@ exports[`BridgeStatusController submitTx: EVM bridge should successfully submit "BridgeController:trackUnifiedSwapBridgeEvent", "Unified SwapBridge Submitted", { + "account_hardware_type": null, "action_type": "swapbridge-v1", "chain_id_destination": "eip155:10", "chain_id_source": "eip155:42161", @@ -2770,6 +2779,7 @@ exports[`BridgeStatusController submitTx: EVM bridge should successfully submit "BridgeController:trackUnifiedSwapBridgeEvent", "Unified SwapBridge Submitted", { + "account_hardware_type": null, "action_type": "swapbridge-v1", "chain_id_destination": "eip155:10", "chain_id_source": "eip155:42161", @@ -2868,6 +2878,7 @@ exports[`BridgeStatusController submitTx: EVM bridge should throw an error if ap "BridgeController:trackUnifiedSwapBridgeEvent", "Unified SwapBridge Submitted", { + "account_hardware_type": null, "action_type": "swapbridge-v1", "chain_id_destination": "eip155:10", "chain_id_source": "eip155:42161", @@ -2949,6 +2960,7 @@ exports[`BridgeStatusController submitTx: EVM bridge should throw an error if ap "BridgeController:trackUnifiedSwapBridgeEvent", "Unified SwapBridge Failed", { + "account_hardware_type": null, "action_type": "swapbridge-v1", "chain_id_destination": "eip155:10", "chain_id_source": "eip155:42161", @@ -2988,6 +3000,7 @@ exports[`BridgeStatusController submitTx: EVM bridge should throw an error if ap "BridgeController:trackUnifiedSwapBridgeEvent", "Unified SwapBridge Submitted", { + "account_hardware_type": null, "action_type": "swapbridge-v1", "chain_id_destination": "eip155:10", "chain_id_source": "eip155:42161", @@ -3072,6 +3085,7 @@ exports[`BridgeStatusController submitTx: EVM bridge should throw an error if ap "BridgeController:trackUnifiedSwapBridgeEvent", "Unified SwapBridge Failed", { + "account_hardware_type": null, "action_type": "swapbridge-v1", "chain_id_destination": "eip155:10", "chain_id_source": "eip155:42161", @@ -3259,6 +3273,7 @@ exports[`BridgeStatusController submitTx: EVM bridge waits for approval tx confi "BridgeController:trackUnifiedSwapBridgeEvent", "Unified SwapBridge Submitted", { + "account_hardware_type": "Ledger", "action_type": "swapbridge-v1", "chain_id_destination": "eip155:10", "chain_id_source": "eip155:42161", @@ -3452,6 +3467,7 @@ exports[`BridgeStatusController submitTx: EVM swap should gracefully handle isAt "BridgeController:trackUnifiedSwapBridgeEvent", "Unified SwapBridge Submitted", { + "account_hardware_type": null, "action_type": "swapbridge-v1", "chain_id_destination": "eip155:42161", "chain_id_source": "eip155:42161", @@ -3907,6 +3923,7 @@ exports[`BridgeStatusController submitTx: EVM swap should handle smart transacti "BridgeController:trackUnifiedSwapBridgeEvent", "Unified SwapBridge Submitted", { + "account_hardware_type": null, "action_type": "swapbridge-v1", "chain_id_destination": "eip155:42161", "chain_id_source": "eip155:42161", @@ -4327,6 +4344,7 @@ exports[`BridgeStatusController submitTx: EVM swap should successfully submit an "BridgeController:trackUnifiedSwapBridgeEvent", "Unified SwapBridge Submitted", { + "account_hardware_type": null, "action_type": "swapbridge-v1", "chain_id_destination": "eip155:42161", "chain_id_source": "eip155:42161", @@ -4765,6 +4783,7 @@ exports[`BridgeStatusController submitTx: Solana bridge should handle snap contr "BridgeController:trackUnifiedSwapBridgeEvent", "Unified SwapBridge Submitted", { + "account_hardware_type": null, "action_type": "swapbridge-v1", "chain_id_destination": "eip155:1", "chain_id_source": "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp", @@ -4807,6 +4826,7 @@ exports[`BridgeStatusController submitTx: Solana bridge should handle snap contr "BridgeController:trackUnifiedSwapBridgeEvent", "Unified SwapBridge Failed", { + "account_hardware_type": null, "action_type": "swapbridge-v1", "chain_id_destination": "eip155:1", "chain_id_source": "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp", @@ -4846,6 +4866,7 @@ exports[`BridgeStatusController submitTx: Solana bridge should successfully subm "BridgeController:trackUnifiedSwapBridgeEvent", "Unified SwapBridge Submitted", { + "account_hardware_type": null, "action_type": "swapbridge-v1", "chain_id_destination": "eip155:1", "chain_id_source": "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp", @@ -5043,6 +5064,7 @@ exports[`BridgeStatusController submitTx: Solana bridge should throw error when "BridgeController:trackUnifiedSwapBridgeEvent", "Unified SwapBridge Submitted", { + "account_hardware_type": null, "action_type": "swapbridge-v1", "chain_id_destination": "eip155:1", "chain_id_source": "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp", @@ -5067,6 +5089,7 @@ exports[`BridgeStatusController submitTx: Solana bridge should throw error when "BridgeController:trackUnifiedSwapBridgeEvent", "Unified SwapBridge Failed", { + "account_hardware_type": null, "action_type": "swapbridge-v1", "chain_id_destination": "eip155:1", "chain_id_source": "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp", @@ -5106,6 +5129,7 @@ exports[`BridgeStatusController submitTx: Solana swap should handle snap control "BridgeController:trackUnifiedSwapBridgeEvent", "Unified SwapBridge Submitted", { + "account_hardware_type": "QR Hardware", "action_type": "swapbridge-v1", "chain_id_destination": "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp", "chain_id_source": "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp", @@ -5148,6 +5172,7 @@ exports[`BridgeStatusController submitTx: Solana swap should handle snap control "BridgeController:trackUnifiedSwapBridgeEvent", "Unified SwapBridge Failed", { + "account_hardware_type": "QR Hardware", "action_type": "swapbridge-v1", "chain_id_destination": "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp", "chain_id_source": "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp", @@ -5187,6 +5212,7 @@ exports[`BridgeStatusController submitTx: Solana swap should successfully submit "BridgeController:trackUnifiedSwapBridgeEvent", "Unified SwapBridge Submitted", { + "account_hardware_type": "QR Hardware", "action_type": "swapbridge-v1", "chain_id_destination": "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp", "chain_id_source": "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp", @@ -5236,6 +5262,7 @@ exports[`BridgeStatusController submitTx: Solana swap should successfully submit "BridgeController:trackUnifiedSwapBridgeEvent", "Unified SwapBridge Completed", { + "account_hardware_type": "QR Hardware", "action_type": "swapbridge-v1", "actual_time_minutes": 0, "allowance_reset_transaction": undefined, @@ -5434,6 +5461,7 @@ exports[`BridgeStatusController submitTx: Solana swap should throw error when sn "BridgeController:trackUnifiedSwapBridgeEvent", "Unified SwapBridge Submitted", { + "account_hardware_type": null, "action_type": "swapbridge-v1", "chain_id_destination": "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp", "chain_id_source": "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp", @@ -5458,6 +5486,7 @@ exports[`BridgeStatusController submitTx: Solana swap should throw error when sn "BridgeController:trackUnifiedSwapBridgeEvent", "Unified SwapBridge Failed", { + "account_hardware_type": null, "action_type": "swapbridge-v1", "chain_id_destination": "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp", "chain_id_source": "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp", @@ -5497,6 +5526,7 @@ exports[`BridgeStatusController submitTx: Tron swap with approval should handle "BridgeController:trackUnifiedSwapBridgeEvent", "Unified SwapBridge Submitted", { + "account_hardware_type": null, "action_type": "swapbridge-v1", "chain_id_destination": "tron:728126428", "chain_id_source": "tron:728126428", @@ -5543,6 +5573,7 @@ exports[`BridgeStatusController submitTx: Tron swap with approval should handle "BridgeController:trackUnifiedSwapBridgeEvent", "Unified SwapBridge Failed", { + "account_hardware_type": null, "action_type": "swapbridge-v1", "chain_id_destination": "tron:728126428", "chain_id_source": "tron:728126428", @@ -5582,6 +5613,7 @@ exports[`BridgeStatusController submitTx: Tron swap with approval should success "BridgeController:trackUnifiedSwapBridgeEvent", "Unified SwapBridge Submitted", { + "account_hardware_type": null, "action_type": "swapbridge-v1", "chain_id_destination": "eip155:1", "chain_id_source": "tron:728126428", @@ -5803,6 +5835,7 @@ exports[`BridgeStatusController submitTx: Tron swap with approval should success "BridgeController:trackUnifiedSwapBridgeEvent", "Unified SwapBridge Submitted", { + "account_hardware_type": null, "action_type": "swapbridge-v1", "chain_id_destination": "tron:728126428", "chain_id_source": "tron:728126428", @@ -6120,6 +6153,7 @@ exports[`BridgeStatusController subscription handlers TransactionController:tran "BridgeController:trackUnifiedSwapBridgeEvent", "Unified SwapBridge Completed", { + "account_hardware_type": null, "action_type": "swapbridge-v1", "actual_time_minutes": 0, "allowance_reset_transaction": undefined, @@ -6161,6 +6195,7 @@ exports[`BridgeStatusController subscription handlers TransactionController:tran "BridgeController:trackUnifiedSwapBridgeEvent", "Unified SwapBridge Failed", { + "account_hardware_type": null, "action_type": "swapbridge-v1", "actual_time_minutes": 0, "allowance_reset_transaction": undefined, @@ -6208,6 +6243,7 @@ exports[`BridgeStatusController subscription handlers TransactionController:tran "BridgeController:trackUnifiedSwapBridgeEvent", "Unified SwapBridge Failed", { + "account_hardware_type": null, "action_type": "swapbridge-v1", "actual_time_minutes": 0, "allowance_reset_transaction": undefined, @@ -6249,6 +6285,7 @@ exports[`BridgeStatusController subscription handlers TransactionController:tran "BridgeController:trackUnifiedSwapBridgeEvent", "Unified SwapBridge Failed", { + "account_hardware_type": null, "action_type": "swapbridge-v1", "actual_time_minutes": 0, "chain_id_destination": "eip155:42161", @@ -6283,10 +6320,15 @@ exports[`BridgeStatusController subscription handlers TransactionController:tran exports[`BridgeStatusController subscription handlers TransactionController:transactionFailed should track failed event for bridge transaction if not in txHistory 1`] = ` [ + [ + "AccountsController:getAccountByAddress", + undefined, + ], [ "BridgeController:trackUnifiedSwapBridgeEvent", "Unified SwapBridge Failed", { + "account_hardware_type": null, "action_type": "swapbridge-v1", "actual_time_minutes": 0, "chain_id_destination": "eip155:42161", @@ -6322,6 +6364,10 @@ exports[`BridgeStatusController subscription handlers TransactionController:tran exports[`BridgeStatusController subscription handlers TransactionController:transactionFailed should track failed event for swap transaction 1`] = ` [ + [ + "AccountsController:getAccountByAddress", + undefined, + ], [ "AccountsController:getAccountByAddress", "0xaccount1", @@ -6333,6 +6379,7 @@ exports[`BridgeStatusController subscription handlers TransactionController:tran "BridgeController:trackUnifiedSwapBridgeEvent", "Unified SwapBridge Failed", { + "account_hardware_type": null, "action_type": "swapbridge-v1", "actual_time_minutes": 0, "allowance_reset_transaction": undefined, @@ -6375,6 +6422,7 @@ exports[`BridgeStatusController subscription handlers TransactionController:tran "BridgeController:trackUnifiedSwapBridgeEvent", "Unified SwapBridge Failed", { + "account_hardware_type": null, "action_type": "swapbridge-v1", "actual_time_minutes": 0, "chain_id_destination": "eip155:42161", diff --git a/packages/bridge-status-controller/src/bridge-status-controller.test.ts b/packages/bridge-status-controller/src/bridge-status-controller.test.ts index a7cdba0c0de..7fb97f71daf 100644 --- a/packages/bridge-status-controller/src/bridge-status-controller.test.ts +++ b/packages/bridge-status-controller/src/bridge-status-controller.test.ts @@ -602,7 +602,6 @@ function registerDefaultActionHandlers( 'AccountsController:getAccountByAddress', () => ({ address: account, - // @ts-expect-error: Partial mock. metadata: { keyring: { type: 'any' } }, }), ); @@ -617,7 +616,6 @@ function registerDefaultActionHandlers( () => 'networkClientId', ); - // @ts-expect-error: Partial mock. rootMessenger.registerActionHandler('NetworkController:getState', () => ({ selectedNetworkClientId: 'networkClientId', })); @@ -625,7 +623,6 @@ function registerDefaultActionHandlers( rootMessenger.registerActionHandler( 'NetworkController:getNetworkClientById', () => ({ - // @ts-expect-error: Partial mock. configuration: { chainId: numberToHex(srcChainId), }, @@ -633,7 +630,6 @@ function registerDefaultActionHandlers( ); rootMessenger.registerActionHandler('TransactionController:getState', () => ({ - // @ts-expect-error: Partial mock. transactions: [{ id: txMetaId, hash: txHash }], })); } @@ -1096,7 +1092,6 @@ describe('BridgeStatusController', () => { rootMessenger.registerActionHandler( 'TransactionController:getState', () => ({ - // @ts-expect-error: Partial mock. transactions: [{ id: 'bridgeTxMetaId1', hash: undefined }], }), ); @@ -1227,7 +1222,6 @@ describe('BridgeStatusController', () => { ); rootMessenger.registerActionHandler( 'TransactionController:getState', - // @ts-expect-error: Partial mock. () => { getStateCallCount += 1; return { @@ -1984,7 +1978,7 @@ describe('BridgeStatusController', () => { id: 'test-snap', }, keyring: { - type: 'Hardware', + type: 'QR Hardware Wallet Device', }, }, options: { scope: 'solana-chain-id' }, @@ -2976,7 +2970,6 @@ describe('BridgeStatusController', () => { const result = await rootMessenger.call( 'BridgeStatusController:submitTx', 'otherAccount', - // @ts-expect-error: Partial mock. lineaQuoteResponse, false, ); @@ -3024,7 +3017,6 @@ describe('BridgeStatusController', () => { const result = await rootMessenger.call( 'BridgeStatusController:submitTx', 'otherAccount', - // @ts-expect-error: Partial mock. baseQuoteResponse, false, ); @@ -3606,7 +3598,6 @@ describe('BridgeStatusController', () => { ...mockEvmQuoteResponse.quote, gasIncluded: true, feeData: { - // @ts-expect-error: Partial mock. txFee: { maxFeePerGas: '123', maxPriorityFeePerGas: '123', @@ -3721,7 +3712,6 @@ describe('BridgeStatusController', () => { gasIncluded7702: false, feeData: { ...quoteWithoutApproval.quote.feeData, - // @ts-expect-error: Partial mock. txFee: { maxFeePerGas: '1395348', // Decimal string from quote maxPriorityFeePerGas: '1000001', @@ -3793,7 +3783,6 @@ describe('BridgeStatusController', () => { gasIncluded7702: false, feeData: { ...quoteWithoutApproval.quote.feeData, - // @ts-expect-error: Partial mock. txFee: { maxFeePerGas: '1395348', // Decimal string from quote maxPriorityFeePerGas: '1000001', @@ -3928,7 +3917,6 @@ describe('BridgeStatusController', () => { gasIncluded7702: true, // 7702 takes precedence → batch path feeData: { ...quoteWithoutApproval.quote.feeData, - // @ts-expect-error: Partial mock. txFee: { maxFeePerGas: '1395348', maxPriorityFeePerGas: '1000001', @@ -3982,7 +3970,6 @@ describe('BridgeStatusController', () => { gasIncluded7702: true, // 7702 takes precedence → batch path feeData: { ...mockEvmQuoteResponse.quote.feeData, - // @ts-expect-error: Partial mock. txFee: { maxFeePerGas: '1395348', maxPriorityFeePerGas: '1000001', @@ -4095,6 +4082,7 @@ describe('BridgeStatusController', () => { "BridgeController:trackUnifiedSwapBridgeEvent", "Unified SwapBridge Failed", { + "account_hardware_type": null, "action_type": "swapbridge-v1", "chain_id_destination": "eip155:42161", "chain_id_source": "eip155:42161", @@ -4184,6 +4172,7 @@ describe('BridgeStatusController', () => { "BridgeController:trackUnifiedSwapBridgeEvent", "Unified SwapBridge Failed", { + "account_hardware_type": null, "action_type": "swapbridge-v1", "chain_id_destination": "eip155:42161", "chain_id_source": "eip155:42161", diff --git a/packages/bridge-status-controller/src/bridge-status-controller.ts b/packages/bridge-status-controller/src/bridge-status-controller.ts index 208cfe7f98e..9fd22e9f874 100644 --- a/packages/bridge-status-controller/src/bridge-status-controller.ts +++ b/packages/bridge-status-controller/src/bridge-status-controller.ts @@ -11,11 +11,11 @@ import { formatChainIdToHex, isNonEvmChainId, StatusTypes, + getAccountHardwareType, UnifiedSwapBridgeEventName, isCrossChain, isTronChainId, isEvmTxData, - isHardwareWallet, MetricsActionType, MetaMetricsSwapsEventSource, isBitcoinTrade, @@ -205,43 +205,7 @@ export class BridgeStatusController extends StaticIntervalPollingController { - const { type, status, id: txMetaId, actionId } = transactionMeta; - - if ( - type && - [ - TransactionType.bridge, - TransactionType.swap, - TransactionType.bridgeApproval, - TransactionType.swapApproval, - ].includes(type) && - [ - TransactionStatus.failed, - TransactionStatus.dropped, - TransactionStatus.rejected, - ].includes(status) - ) { - // Mark tx as failed in txHistory - this.#markTxAsFailed(transactionMeta); - // Track failed event - if (status !== TransactionStatus.rejected) { - // Look up history by txMetaId first, then by actionId (for pre-submission failures) - let historyKey: string | undefined; - if (this.state.txHistory[txMetaId]) { - historyKey = txMetaId; - } else if (actionId && this.state.txHistory[actionId]) { - historyKey = actionId; - } - - this.#trackUnifiedSwapBridgeEvent( - UnifiedSwapBridgeEventName.Failed, - historyKey ?? txMetaId, - getEVMTxPropertiesFromTransactionMeta(transactionMeta), - ); - } - } - }, + this.#onTransactionFailed, ); this.messenger.subscribe( @@ -266,6 +230,58 @@ export class BridgeStatusController extends StaticIntervalPollingController { + const { type, status, id: txMetaId, actionId } = transactionMeta; + + if ( + type && + [ + TransactionType.bridge, + TransactionType.swap, + TransactionType.bridgeApproval, + TransactionType.swapApproval, + ].includes(type) && + [ + TransactionStatus.failed, + TransactionStatus.dropped, + TransactionStatus.rejected, + ].includes(status) + ) { + this.#markTxAsFailed(transactionMeta); + if (status !== TransactionStatus.rejected) { + let historyKey: string | undefined; + if (this.state.txHistory[txMetaId]) { + historyKey = txMetaId; + } else if (actionId && this.state.txHistory[actionId]) { + historyKey = actionId; + } + + const activeHistoryKey = historyKey ?? txMetaId; + + // Skip account lookup and tracking when featureId is set (e.g. PERPS) + if (this.state.txHistory[activeHistoryKey]?.featureId) { + return; + } + + this.#trackUnifiedSwapBridgeEvent( + UnifiedSwapBridgeEventName.Failed, + activeHistoryKey, + getEVMTxPropertiesFromTransactionMeta( + transactionMeta, + this.messenger.call( + 'AccountsController:getAccountByAddress', + transactionMeta.txParams?.from, + ), + ), + ); + } + } + }; + // Mark tx as failed in txHistory if either the approval or trade fails readonly #markTxAsFailed = ({ id: txMetaId, @@ -956,12 +972,12 @@ export class BridgeStatusController extends StaticIntervalPollingController { usd_amount_source: 2000, swap_type: 'crosschain', is_hardware_wallet: false, + account_hardware_type: null, stx_enabled: false, }); }); @@ -900,6 +901,32 @@ describe('metrics utils', () => { hardwareWalletAccount, ); expect(result.is_hardware_wallet).toBe(true); + expect(result.account_hardware_type).toBe('Ledger'); + }); + + it('should keep Lattice accounts as Lattice', () => { + const latticeAccount = { + id: 'test-account', + type: 'eip155:eoa' as const, + address: '0xaccount1', + options: {}, + metadata: { + name: 'Test Account', + importTime: 1234567890, + keyring: { + type: 'Lattice Hardware', + }, + }, + scopes: [], + methods: [], + }; + + const result = getRequestMetadataFromHistory( + mockHistoryItem, + latticeAccount, + ); + expect(result.is_hardware_wallet).toBe(true); + expect(result.account_hardware_type).toBe('Lattice'); }); it('should handle missing pricing data', () => { @@ -985,7 +1012,7 @@ describe('metrics utils', () => { gasFee: { effective: { usd: '2.54739' } }, } as never, false, - false, + null, MetaMetricsSwapsEventSource.MainView, abTests, activeAbTests, @@ -1035,6 +1062,7 @@ describe('metrics utils', () => { 'eip155:1/erc20:0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48', custom_slippage: false, is_hardware_wallet: false, + account_hardware_type: null, swap_type: MetricsSwapType.SINGLE, security_warnings: [], price_impact: 0, diff --git a/packages/bridge-status-controller/src/utils/metrics.ts b/packages/bridge-status-controller/src/utils/metrics.ts index 740d2554a91..75611842b87 100644 --- a/packages/bridge-status-controller/src/utils/metrics.ts +++ b/packages/bridge-status-controller/src/utils/metrics.ts @@ -4,19 +4,20 @@ import type { AccountsControllerState } from '@metamask/accounts-controller'; import { StatusTypes, + getAccountHardwareType, formatChainIdToHex, isEthUsdt, formatChainIdToCaip, formatProviderLabel, isCustomSlippage, getSwapType, - isHardwareWallet, formatAddressToAssetId, MetricsActionType, MetricsSwapType, MetaMetricsSwapsEventSource, } from '@metamask/bridge-controller'; import type { + AccountHardwareType, QuoteFetchData, QuoteMetadata, QuoteResponse, @@ -175,7 +176,7 @@ export const getPriceImpactFromQuote = ( * * @param quoteResponse - The quote response * @param isStxEnabledOnClient - Whether smart transactions are enabled on the client, for example the getSmartTransactionsEnabled selector value from the extension - * @param isHardwareAccount - whether the tx is submitted using a hardware wallet + * @param accountHardwareType - The hardware wallet type used to submit the tx, or null if not a hardware wallet * @param location - The entry point from which the user initiated the swap or bridge (e.g. Main View, Token View, Trending Explore) * @param abTests - Legacy A/B test context for `ab_tests` (backward compatibility) * @param activeAbTests - New A/B test context for `active_ab_tests` (migration target) @@ -184,7 +185,7 @@ export const getPriceImpactFromQuote = ( export const getPreConfirmationPropertiesFromQuote = ( quoteResponse: QuoteResponse & Partial, isStxEnabledOnClient: boolean, - isHardwareAccount: boolean, + accountHardwareType: AccountHardwareType, location: MetaMetricsSwapsEventSource = MetaMetricsSwapsEventSource.MainView, abTests?: Record, activeAbTests?: { key: string; value: string }[], @@ -197,7 +198,8 @@ export const getPreConfirmationPropertiesFromQuote = ( token_symbol_source: quote.srcAsset.symbol, chain_id_destination: formatChainIdToCaip(quote.destChainId), token_symbol_destination: quote.destAsset.symbol, - is_hardware_wallet: isHardwareAccount, + account_hardware_type: accountHardwareType, + is_hardware_wallet: accountHardwareType !== null, swap_type: getSwapType( quoteResponse.quote.srcChainId, quoteResponse.quote.destChainId, @@ -238,13 +240,15 @@ export const getRequestMetadataFromHistory = ( account?: AccountsControllerState['internalAccounts']['accounts'][string], ): RequestMetadata => { const { quote, slippagePercentage, isStxEnabled } = historyItem; + const accountHardwareType = getAccountHardwareType(account); return { slippage_limit: slippagePercentage, custom_slippage: isCustomSlippage(slippagePercentage), usd_amount_source: Number(historyItem.pricingData?.amountSentInUsd ?? 0), swap_type: getSwapType(quote.srcChainId, quote.destChainId), - is_hardware_wallet: isHardwareWallet(account), + account_hardware_type: accountHardwareType, + is_hardware_wallet: accountHardwareType !== null, stx_enabled: isStxEnabled ?? false, security_warnings: [], }; @@ -254,11 +258,15 @@ export const getRequestMetadataFromHistory = ( * Get the properties for a swap transaction that is not in the txHistory * * @param transactionMeta - The transaction meta + * @param account - The account that submitted the transaction * @returns The properties for the swap transaction */ export const getEVMTxPropertiesFromTransactionMeta = ( transactionMeta: TransactionMeta, + account?: AccountsControllerState['internalAccounts']['accounts'][string], ) => { + const accountHardwareType = getAccountHardwareType(account); + return { source_transaction: [ TransactionStatus.failed, @@ -285,7 +293,8 @@ export const getEVMTxPropertiesFromTransactionMeta = ( transactionMeta.chainId, ) ?? ('' as CaipAssetType), custom_slippage: false, - is_hardware_wallet: false, + account_hardware_type: accountHardwareType, + is_hardware_wallet: accountHardwareType !== null, swap_type: transactionMeta.type && [TransactionType.swap, TransactionType.swapApproval].includes( From 45e8b3d09fcad39f514469e51b5542a25bc03129 Mon Sep 17 00:00:00 2001 From: GeorgeGkas Date: Fri, 17 Apr 2026 13:26:41 +0300 Subject: [PATCH 2/3] fix: add missing type --- packages/bridge-controller/src/utils/metrics/types.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/bridge-controller/src/utils/metrics/types.ts b/packages/bridge-controller/src/utils/metrics/types.ts index 253ea403685..69876b1ab78 100644 --- a/packages/bridge-controller/src/utils/metrics/types.ts +++ b/packages/bridge-controller/src/utils/metrics/types.ts @@ -175,7 +175,10 @@ type RequiredEventContextFromClientBase = { Pick & Pick< RequestMetadata, - 'stx_enabled' | 'usd_amount_source' | 'is_hardware_wallet' + | 'stx_enabled' + | 'usd_amount_source' + | 'is_hardware_wallet' + | 'account_hardware_type' > & Pick< RequestParams, From d931d10e7a510b507a681e2620f1732dd620aa42 Mon Sep 17 00:00:00 2001 From: GeorgeGkas Date: Fri, 17 Apr 2026 13:26:57 +0300 Subject: [PATCH 3/3] chore: update changelog --- packages/bridge-controller/CHANGELOG.md | 11 +++++++++++ packages/bridge-status-controller/CHANGELOG.md | 4 ++++ 2 files changed, 15 insertions(+) diff --git a/packages/bridge-controller/CHANGELOG.md b/packages/bridge-controller/CHANGELOG.md index f3b55d98c53..8487dd16989 100644 --- a/packages/bridge-controller/CHANGELOG.md +++ b/packages/bridge-controller/CHANGELOG.md @@ -7,8 +7,19 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Added + +- Add `AccountHardwareType` type and `getAccountHardwareType` function to the package exports ([#8503](https://github.com/MetaMask/core/pull/8503)) + - `AccountHardwareType` is a union of `'Ledger' | 'Trezor' | 'QR Hardware' | 'Lattice' | null` + - `getAccountHardwareType` maps a keyring type string to the corresponding `AccountHardwareType` value + ### Changed +- Add `account_hardware_type` field to `RequestMetadata` and all cross-chain swap analytics events ([#8503](https://github.com/MetaMask/core/pull/8503)) + - `account_hardware_type` carries the specific hardware wallet brand (e.g. `'Ledger'`) or `null` for software wallets + - `is_hardware_wallet` is now derived from `account_hardware_type !== null`, keeping both fields in sync + - `EventPropertiesFromControllerState[PageViewed]` now includes `account_hardware_type`, `is_hardware_wallet`, `custom_slippage`, `slippage_limit`, and `swap_type` (previously only `RequestParams` fields were included) + - Bump `@metamask/transaction-controller` from `^64.2.0` to `^64.3.0` ([#8482](https://github.com/MetaMask/core/pull/8482)) - Bump `@metamask/keyring-api` from `^21.6.0` to `^23.0.1` ([#8464](https://github.com/MetaMask/core/pull/8464)) diff --git a/packages/bridge-status-controller/CHANGELOG.md b/packages/bridge-status-controller/CHANGELOG.md index 0f3d4c2456a..4ad04f92c73 100644 --- a/packages/bridge-status-controller/CHANGELOG.md +++ b/packages/bridge-status-controller/CHANGELOG.md @@ -17,6 +17,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed +- Add `account_hardware_type` field to all cross-chain swap analytics events ([#8503](https://github.com/MetaMask/core/pull/8503)) + - `account_hardware_type` carries the specific hardware wallet brand (e.g. `'Ledger'`, `'QR Hardware'`) or `null` for software wallets + - `is_hardware_wallet` is now derived from `account_hardware_type !== null`, keeping both fields in sync +- `getEVMTxPropertiesFromTransactionMeta` now accepts an optional `account` parameter to populate `account_hardware_type` for `TransactionController:transactionFailed` events ([#8503](https://github.com/MetaMask/core/pull/8503)) - Bump `@metamask/accounts-controller` from `^37.1.1` to `^37.2.0` ([#8363](https://github.com/MetaMask/core/pull/8363)) - Bump `@metamask/keyring-controller` from `^25.1.1` to `^25.2.0` ([#8363](https://github.com/MetaMask/core/pull/8363)) - Bump `@metamask/messenger` from `^1.0.0` to `^1.1.1` ([#8364](https://github.com/MetaMask/core/pull/8364), [#8373](https://github.com/MetaMask/core/pull/8373))