From e9230279986efbb95e1d427fd4d5f0dec38d3ece Mon Sep 17 00:00:00 2001 From: Bryan Fullam Date: Mon, 9 Mar 2026 20:19:52 +0100 Subject: [PATCH 1/8] feat: add active_ab_tests to swap events --- packages/bridge-controller/CHANGELOG.md | 1 + .../src/utils/metrics/types.ts | 8 ++++ .../bridge-status-controller/CHANGELOG.md | 1 + .../src/bridge-status-controller.test.ts | 4 +- .../src/bridge-status-controller.ts | 38 ++++++++++++++++--- .../bridge-status-controller/src/types.ts | 10 ++++- .../src/utils/metrics.test.ts | 36 +++++++++++++++++- .../src/utils/metrics.ts | 13 ++++++- 8 files changed, 100 insertions(+), 11 deletions(-) diff --git a/packages/bridge-controller/CHANGELOG.md b/packages/bridge-controller/CHANGELOG.md index a284dbe2cef..cdd1d58ad08 100644 --- a/packages/bridge-controller/CHANGELOG.md +++ b/packages/bridge-controller/CHANGELOG.md @@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed - Widen `RequiredEventContextFromClient` `InputChanged.input_amount_preset` to accept arbitrary string labels (for example `85%`, `95%`, `MAX`) while preserving compatibility with `InputAmountPreset` enum values. ([#8069](https://github.com/MetaMask/core/pull/8069)) +- Include optional `active_ab_tests` property in Unified SwapBridge metrics event context and payload types, alongside existing `ab_tests`. ## [67.3.0] diff --git a/packages/bridge-controller/src/utils/metrics/types.ts b/packages/bridge-controller/src/utils/metrics/types.ts index 9d8504c106a..7949b573cb2 100644 --- a/packages/bridge-controller/src/utils/metrics/types.ts +++ b/packages/bridge-controller/src/utils/metrics/types.ts @@ -243,11 +243,15 @@ type RequiredEventContextFromClientBase = { * This combines the event-specific properties from RequiredEventContextFromClientBase * with an optional `location` property. When `location` is omitted, the controller * falls back to the value stored via `setLocation()` (defaults to MainView). + * + * `ab_tests` is the legacy field and `active_ab_tests` is the newer field. + * Both are kept for a migration window and are treated as separate payloads. */ export type RequiredEventContextFromClient = { [K in keyof RequiredEventContextFromClientBase]: RequiredEventContextFromClientBase[K] & { location?: MetaMetricsSwapsEventSource; ab_tests?: Record; + active_ab_tests?: Record; }; }; @@ -309,6 +313,9 @@ export type EventPropertiesFromControllerState = { /** * trackUnifiedSwapBridgeEvent payload properties consist of required properties from the client * and properties from the bridge controller + * + * `ab_tests` will be deprecated in favor of `active_ab_tests` in the future. + * `ab_tests` and `active_ab_tests` intentionally coexist during migration. */ export type CrossChainSwapsEventProperties< T extends UnifiedSwapBridgeEventName, @@ -317,6 +324,7 @@ export type CrossChainSwapsEventProperties< action_type: MetricsActionType; location: MetaMetricsSwapsEventSource; ab_tests?: Record; + active_ab_tests?: Record; } | Pick[T] | Pick[T]; diff --git a/packages/bridge-status-controller/CHANGELOG.md b/packages/bridge-status-controller/CHANGELOG.md index 125966be132..946265b6e23 100644 --- a/packages/bridge-status-controller/CHANGELOG.md +++ b/packages/bridge-status-controller/CHANGELOG.md @@ -13,6 +13,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Added optional `abTests` parameter to `StartPollingForBridgeTxStatusArgs` ([#8007](https://github.com/MetaMask/core/pull/8007)) - Added optional `abTests` parameter to `submitTx` and `submitIntent` methods for A/B test experiment attribution ([#8007](https://github.com/MetaMask/core/pull/8007)) - `trackUnifiedSwapBridgeEvent` now resolves `ab_tests` from event properties or transaction history and includes it in emitted events ([#8007](https://github.com/MetaMask/core/pull/8007)) +- Added optional `activeAbTests` context support so Unified SwapBridge events can include `active_ab_tests` independently of `ab_tests`. ### Changed 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 5d4469f177f..10e2743fcfd 100644 --- a/packages/bridge-status-controller/src/bridge-status-controller.test.ts +++ b/packages/bridge-status-controller/src/bridge-status-controller.test.ts @@ -4255,7 +4255,7 @@ describe('BridgeStatusController', () => { expect(messengerCallSpy.mock.lastCall).toMatchSnapshot(); }); - it('should include ab_tests from history in tracked event properties', () => { + it('should include ab_tests and active_ab_tests from history in tracked event properties', () => { const abTestsTxMetaId = 'bridgeTxMetaIdAbTests'; bridgeStatusController.startPollingForBridgeTxStatus({ ...getMockStartPollingForBridgeTxStatusArgs({ @@ -4263,6 +4263,7 @@ describe('BridgeStatusController', () => { srcTxHash: '0xsrcTxHashAbTests', }), abTests: { token_details_layout: 'treatment' }, + activeAbTests: { bridge_quote_sorting: 'variant_b' }, }); const messengerCallSpy = jest.spyOn(mockBridgeStatusMessenger, 'call'); @@ -4284,6 +4285,7 @@ describe('BridgeStatusController', () => { expect.anything(), expect.objectContaining({ ab_tests: { token_details_layout: 'treatment' }, + active_ab_tests: { bridge_quote_sorting: 'variant_b' }, }), ); }); diff --git a/packages/bridge-status-controller/src/bridge-status-controller.ts b/packages/bridge-status-controller/src/bridge-status-controller.ts index 0b0efe710d0..c14f6402312 100644 --- a/packages/bridge-status-controller/src/bridge-status-controller.ts +++ b/packages/bridge-status-controller/src/bridge-status-controller.ts @@ -522,6 +522,7 @@ export class BridgeStatusController extends StaticIntervalPollingController { // Use actionId as key for pre-submission, or txMeta.id for post-submission @@ -1318,7 +1320,8 @@ export class BridgeStatusController extends StaticIntervalPollingController, + activeAbTests?: Record, ): Promise> => { this.messenger.call( 'BridgeController:stopPollingForQuotes', @@ -1351,6 +1355,7 @@ export class BridgeStatusController extends StaticIntervalPollingController; + activeAbTests?: Record; }): Promise> => { - const { quoteResponse, signature, accountAddress, location, abTests } = - params; + const { + quoteResponse, + signature, + accountAddress, + location, + abTests, + activeAbTests, + } = params; this.messenger.call( 'BridgeController:stopPollingForQuotes', @@ -1638,6 +1653,7 @@ export class BridgeStatusController extends StaticIntervalPollingController[EventName], ): void => { + // Legacy/new metrics fields are intentionally kept independent during migration. const historyAbTests = txMetaId ? this.state.txHistory?.[txMetaId]?.abTests : undefined; - const resolvedAbTests = - eventProperties?.ab_tests ?? historyAbTests ?? undefined; + const historyActiveAbTests = txMetaId + ? this.state.txHistory?.[txMetaId]?.activeAbTests + : undefined; + const resolvedAbTests = eventProperties?.ab_tests ?? historyAbTests; + const resolvedActiveAbTests = + eventProperties?.active_ab_tests ?? historyActiveAbTests; const baseProperties = { action_type: MetricsActionType.SWAPBRIDGE_V1, @@ -1848,6 +1870,10 @@ export class BridgeStatusController extends StaticIntervalPollingController 0 && { ab_tests: resolvedAbTests, }), + ...(resolvedActiveAbTests && + Object.keys(resolvedActiveAbTests).length > 0 && { + active_ab_tests: resolvedActiveAbTests, + }), }; // This will publish events for PERPS dropped tx failures as well diff --git a/packages/bridge-status-controller/src/types.ts b/packages/bridge-status-controller/src/types.ts index ed410151560..b47b84d4116 100644 --- a/packages/bridge-status-controller/src/types.ts +++ b/packages/bridge-status-controller/src/types.ts @@ -138,10 +138,15 @@ export type BridgeHistoryItem = { */ location?: MetaMetricsSwapsEventSource; /** - * A/B test context to attribute swap/bridge events to specific experiments. + * Legacy A/B test metrics context (`ab_tests`) kept for backward compatibility. * Keys are test names, values are variant names (e.g. { token_details_layout: 'treatment' }). */ abTests?: Record; + /** + * New A/B test metrics context (`active_ab_tests`) that replaces `ab_tests`. + * Kept separate so migration can run both payloads in parallel. + */ + activeAbTests?: Record; /** * Attempts tracking for exponential backoff on failed fetches. * We track the number of attempts and the last attempt time for each txMetaId that has failed at least once @@ -211,7 +216,10 @@ export type StartPollingForBridgeTxStatusArgs = { approvalTxId?: BridgeHistoryItem['approvalTxId']; isStxEnabled?: BridgeHistoryItem['isStxEnabled']; location?: BridgeHistoryItem['location']; + // Legacy field for `ab_tests` metrics payload. abTests?: BridgeHistoryItem['abTests']; + // New field for `active_ab_tests` metrics payload. + activeAbTests?: BridgeHistoryItem['activeAbTests']; accountAddress: string; }; diff --git a/packages/bridge-status-controller/src/utils/metrics.test.ts b/packages/bridge-status-controller/src/utils/metrics.test.ts index 2d17838369e..af239fbbb2c 100644 --- a/packages/bridge-status-controller/src/utils/metrics.test.ts +++ b/packages/bridge-status-controller/src/utils/metrics.test.ts @@ -1,4 +1,9 @@ -import { StatusTypes, FeeType, ActionTypes } from '@metamask/bridge-controller'; +import { + StatusTypes, + FeeType, + ActionTypes, + MetaMetricsSwapsEventSource, +} from '@metamask/bridge-controller'; import { MetricsSwapType, MetricsActionType, @@ -17,6 +22,7 @@ import { getTradeDataFromHistory, getRequestMetadataFromHistory, getEVMTxPropertiesFromTransactionMeta, + getPreConfirmationPropertiesFromQuote, } from './metrics'; import type { BridgeHistoryItem } from '../types'; @@ -964,6 +970,34 @@ describe('metrics utils', () => { }); }); + describe('getPreConfirmationPropertiesFromQuote', () => { + it('should include both ab_tests and active_ab_tests when both sets are provided', () => { + const abTests = { token_details_layout: 'treatment' }; + const activeAbTests = { bridge_quote_sorting: 'variant_b' }; + const result = getPreConfirmationPropertiesFromQuote( + { + quote: mockHistoryItem.quote, + estimatedProcessingTimeInSeconds: 900, + adjustedReturn: { usd: '1980' }, + sentAmount: { usd: '2000' }, + gasFee: { effective: { usd: '2.54739' } }, + } as never, + false, + false, + MetaMetricsSwapsEventSource.MainView, + abTests, + activeAbTests, + ); + + expect(result).toStrictEqual( + expect.objectContaining({ + ab_tests: abTests, + active_ab_tests: activeAbTests, + }), + ); + }); + }); + describe('getEVMSwapTxPropertiesFromTransactionMeta', () => { const mockTransactionMeta: TransactionMeta = { id: 'test-tx-id', diff --git a/packages/bridge-status-controller/src/utils/metrics.ts b/packages/bridge-status-controller/src/utils/metrics.ts index 462a92025aa..7274f943b0a 100644 --- a/packages/bridge-status-controller/src/utils/metrics.ts +++ b/packages/bridge-status-controller/src/utils/metrics.ts @@ -177,7 +177,8 @@ export const getPriceImpactFromQuote = ( * @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 location - The entry point from which the user initiated the swap or bridge (e.g. Main View, Token View, Trending Explore) - * @param abTests - A/B test context to attribute events to specific experiments + * @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) * @returns The properties for the pre-confirmation event */ export const getPreConfirmationPropertiesFromQuote = ( @@ -186,6 +187,7 @@ export const getPreConfirmationPropertiesFromQuote = ( isHardwareAccount: boolean, location: MetaMetricsSwapsEventSource = MetaMetricsSwapsEventSource.MainView, abTests?: Record, + activeAbTests?: Record, ) => { const { quote } = quoteResponse; return { @@ -205,7 +207,14 @@ export const getPreConfirmationPropertiesFromQuote = ( action_type: MetricsActionType.SWAPBRIDGE_V1, custom_slippage: false, // TODO detect whether the user changed the default slippage location, - ...(abTests && Object.keys(abTests).length > 0 && { ab_tests: abTests }), + ...(abTests && + Object.keys(abTests).length > 0 && { + ab_tests: abTests, + }), + ...(activeAbTests && + Object.keys(activeAbTests).length > 0 && { + active_ab_tests: activeAbTests, + }), }; }; From fccc363c6205ef2f8455ed3b23deae7f34512073 Mon Sep 17 00:00:00 2001 From: Bryan Fullam Date: Mon, 9 Mar 2026 20:22:42 +0100 Subject: [PATCH 2/8] chore: changelog --- packages/bridge-controller/CHANGELOG.md | 2 +- packages/bridge-status-controller/CHANGELOG.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/bridge-controller/CHANGELOG.md b/packages/bridge-controller/CHANGELOG.md index cdd1d58ad08..7f8f39eb4f0 100644 --- a/packages/bridge-controller/CHANGELOG.md +++ b/packages/bridge-controller/CHANGELOG.md @@ -10,7 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed - Widen `RequiredEventContextFromClient` `InputChanged.input_amount_preset` to accept arbitrary string labels (for example `85%`, `95%`, `MAX`) while preserving compatibility with `InputAmountPreset` enum values. ([#8069](https://github.com/MetaMask/core/pull/8069)) -- Include optional `active_ab_tests` property in Unified SwapBridge metrics event context and payload types, alongside existing `ab_tests`. +- Include optional `active_ab_tests` property in Unified SwapBridge metrics event context and payload types, alongside existing `ab_tests`. ([#8152](https://github.com/MetaMask/core/pull/8152)) ## [67.3.0] diff --git a/packages/bridge-status-controller/CHANGELOG.md b/packages/bridge-status-controller/CHANGELOG.md index 946265b6e23..a6c06c5a72d 100644 --- a/packages/bridge-status-controller/CHANGELOG.md +++ b/packages/bridge-status-controller/CHANGELOG.md @@ -13,7 +13,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Added optional `abTests` parameter to `StartPollingForBridgeTxStatusArgs` ([#8007](https://github.com/MetaMask/core/pull/8007)) - Added optional `abTests` parameter to `submitTx` and `submitIntent` methods for A/B test experiment attribution ([#8007](https://github.com/MetaMask/core/pull/8007)) - `trackUnifiedSwapBridgeEvent` now resolves `ab_tests` from event properties or transaction history and includes it in emitted events ([#8007](https://github.com/MetaMask/core/pull/8007)) -- Added optional `activeAbTests` context support so Unified SwapBridge events can include `active_ab_tests` independently of `ab_tests`. +- Added optional `activeAbTests` context support so Unified SwapBridge events can include `active_ab_tests` independently of `ab_tests`. ([#8152](https://github.com/MetaMask/core/pull/8152)) ### Changed From fd0c1946885d030164d229f10436dd111a8fed68 Mon Sep 17 00:00:00 2001 From: Bryan Fullam Date: Tue, 10 Mar 2026 13:03:52 +0100 Subject: [PATCH 3/8] fix: format submitIntent destructuring after merge --- .../src/bridge-status-controller.ts | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/packages/bridge-status-controller/src/bridge-status-controller.ts b/packages/bridge-status-controller/src/bridge-status-controller.ts index b298ae06198..fa17d5dce36 100644 --- a/packages/bridge-status-controller/src/bridge-status-controller.ts +++ b/packages/bridge-status-controller/src/bridge-status-controller.ts @@ -1618,13 +1618,8 @@ export class BridgeStatusController extends StaticIntervalPollingController; activeAbTests?: Record; }): Promise> => { - const { - quoteResponse, - accountAddress, - location, - abTests, - activeAbTests, - } = params; + const { quoteResponse, accountAddress, location, abTests, activeAbTests } = + params; this.messenger.call( 'BridgeController:stopPollingForQuotes', From 384cfdfef81d6ff4ba5fa9bc93863dd0b02d974f Mon Sep 17 00:00:00 2001 From: Bryan Fullam Date: Tue, 10 Mar 2026 13:09:04 +0100 Subject: [PATCH 4/8] chore: changloe --- packages/bridge-controller/CHANGELOG.md | 2 +- packages/bridge-status-controller/CHANGELOG.md | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/bridge-controller/CHANGELOG.md b/packages/bridge-controller/CHANGELOG.md index d30e21ce036..e503e13c984 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 - **BREAKING:** Optional constructor option `getUseAssetsControllerForRates`: when it returns true, exchange rates are read from the new `@metamask/assets-controller` (`AssetsController:getExchangeRatesForBridge`) instead of `MultichainAssetsRatesController`, `TokenRatesController`, and `CurrencyRateController`. ([#8090](https://github.com/MetaMask/core/pull/8090)) - Add `AssetPickerOpened` unified swap bridge metrics event with an `asset_location` property to indicate `'source'` or `'destination'`. ([#7985](https://github.com/MetaMask/core/pull/7985)) +- Add optional `active_ab_tests` property in Unified SwapBridge metrics event context and payload types, alongside existing `ab_tests`. ([#8152](https://github.com/MetaMask/core/pull/8152)) ### Changed @@ -34,7 +35,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed - Widen `RequiredEventContextFromClient` `InputChanged.input_amount_preset` to accept arbitrary string labels (for example `85%`, `95%`, `MAX`) while preserving compatibility with `InputAmountPreset` enum values. ([#8069](https://github.com/MetaMask/core/pull/8069)) -- Include optional `active_ab_tests` property in Unified SwapBridge metrics event context and payload types, alongside existing `ab_tests`. ([#8152](https://github.com/MetaMask/core/pull/8152)) ## [67.3.0] diff --git a/packages/bridge-status-controller/CHANGELOG.md b/packages/bridge-status-controller/CHANGELOG.md index 3954b107546..f18df50fd4d 100644 --- a/packages/bridge-status-controller/CHANGELOG.md +++ b/packages/bridge-status-controller/CHANGELOG.md @@ -7,6 +7,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Added +- Added optional `activeAbTests` context support so Unified SwapBridge events can include `active_ab_tests` independently of `ab_tests`. ([#8152](https://github.com/MetaMask/core/pull/8152)) + ### Changed - Bump `@metamask/transaction-controller` from `^62.19.0` to `^62.20.0` ([#8104](https://github.com/MetaMask/core/pull/8104)) @@ -19,7 +22,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Added optional `abTests` parameter to `StartPollingForBridgeTxStatusArgs` ([#8007](https://github.com/MetaMask/core/pull/8007)) - Added optional `abTests` parameter to `submitTx` and `submitIntent` methods for A/B test experiment attribution ([#8007](https://github.com/MetaMask/core/pull/8007)) - `trackUnifiedSwapBridgeEvent` now resolves `ab_tests` from event properties or transaction history and includes it in emitted events ([#8007](https://github.com/MetaMask/core/pull/8007)) -- Added optional `activeAbTests` context support so Unified SwapBridge events can include `active_ab_tests` independently of `ab_tests`. ([#8152](https://github.com/MetaMask/core/pull/8152)) ### Changed From ad4b7f24ec6ba66a64718e8d71b9d76cc3c96f76 Mon Sep 17 00:00:00 2001 From: Bryan Fullam Date: Tue, 10 Mar 2026 15:28:55 +0100 Subject: [PATCH 5/8] fix: activeAbTests as array of objects --- packages/bridge-controller/src/utils/metrics/types.ts | 4 ++-- .../src/bridge-status-controller.test.ts | 4 ++-- .../src/bridge-status-controller.ts | 6 +++--- packages/bridge-status-controller/src/types.ts | 3 ++- packages/bridge-status-controller/src/utils/metrics.test.ts | 2 +- packages/bridge-status-controller/src/utils/metrics.ts | 4 ++-- 6 files changed, 12 insertions(+), 11 deletions(-) diff --git a/packages/bridge-controller/src/utils/metrics/types.ts b/packages/bridge-controller/src/utils/metrics/types.ts index 4173120cd05..080a0b149d0 100644 --- a/packages/bridge-controller/src/utils/metrics/types.ts +++ b/packages/bridge-controller/src/utils/metrics/types.ts @@ -254,7 +254,7 @@ export type RequiredEventContextFromClient = { [K in keyof RequiredEventContextFromClientBase]: RequiredEventContextFromClientBase[K] & { location?: MetaMetricsSwapsEventSource; ab_tests?: Record; - active_ab_tests?: Record; + active_ab_tests?: Record[]; }; }; @@ -328,7 +328,7 @@ export type CrossChainSwapsEventProperties< action_type: MetricsActionType; location: MetaMetricsSwapsEventSource; ab_tests?: Record; - active_ab_tests?: Record; + active_ab_tests?: Record[]; } | Pick[T] | Pick[T]; 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 10e2743fcfd..0ee94d4cf19 100644 --- a/packages/bridge-status-controller/src/bridge-status-controller.test.ts +++ b/packages/bridge-status-controller/src/bridge-status-controller.test.ts @@ -4263,7 +4263,7 @@ describe('BridgeStatusController', () => { srcTxHash: '0xsrcTxHashAbTests', }), abTests: { token_details_layout: 'treatment' }, - activeAbTests: { bridge_quote_sorting: 'variant_b' }, + activeAbTests: [{ bridge_quote_sorting: 'variant_b' }], }); const messengerCallSpy = jest.spyOn(mockBridgeStatusMessenger, 'call'); @@ -4285,7 +4285,7 @@ describe('BridgeStatusController', () => { expect.anything(), expect.objectContaining({ ab_tests: { token_details_layout: 'treatment' }, - active_ab_tests: { bridge_quote_sorting: 'variant_b' }, + active_ab_tests: [{ bridge_quote_sorting: 'variant_b' }], }), ); }); diff --git a/packages/bridge-status-controller/src/bridge-status-controller.ts b/packages/bridge-status-controller/src/bridge-status-controller.ts index fa17d5dce36..9e389b38d5c 100644 --- a/packages/bridge-status-controller/src/bridge-status-controller.ts +++ b/packages/bridge-status-controller/src/bridge-status-controller.ts @@ -1321,7 +1321,7 @@ export class BridgeStatusController extends StaticIntervalPollingController, - activeAbTests?: Record, + activeAbTests?: Record[], ): Promise> => { this.messenger.call( 'BridgeController:stopPollingForQuotes', @@ -1616,7 +1616,7 @@ export class BridgeStatusController extends StaticIntervalPollingController; - activeAbTests?: Record; + activeAbTests?: Record[]; }): Promise> => { const { quoteResponse, accountAddress, location, abTests, activeAbTests } = params; @@ -1856,7 +1856,7 @@ export class BridgeStatusController extends StaticIntervalPollingController 0 && { + resolvedActiveAbTests.length > 0 && { active_ab_tests: resolvedActiveAbTests, }), }; diff --git a/packages/bridge-status-controller/src/types.ts b/packages/bridge-status-controller/src/types.ts index 2d5dcccbe29..9cc148f5958 100644 --- a/packages/bridge-status-controller/src/types.ts +++ b/packages/bridge-status-controller/src/types.ts @@ -146,8 +146,9 @@ export type BridgeHistoryItem = { /** * New A/B test metrics context (`active_ab_tests`) that replaces `ab_tests`. * Kept separate so migration can run both payloads in parallel. + * This field is an array of test objects. */ - activeAbTests?: Record; + activeAbTests?: Record[]; /** * Attempts tracking for exponential backoff on failed fetches. * We track the number of attempts and the last attempt time for each txMetaId that has failed at least once diff --git a/packages/bridge-status-controller/src/utils/metrics.test.ts b/packages/bridge-status-controller/src/utils/metrics.test.ts index af239fbbb2c..a068a52d585 100644 --- a/packages/bridge-status-controller/src/utils/metrics.test.ts +++ b/packages/bridge-status-controller/src/utils/metrics.test.ts @@ -973,7 +973,7 @@ describe('metrics utils', () => { describe('getPreConfirmationPropertiesFromQuote', () => { it('should include both ab_tests and active_ab_tests when both sets are provided', () => { const abTests = { token_details_layout: 'treatment' }; - const activeAbTests = { bridge_quote_sorting: 'variant_b' }; + const activeAbTests = [{ bridge_quote_sorting: 'variant_b' }]; const result = getPreConfirmationPropertiesFromQuote( { quote: mockHistoryItem.quote, diff --git a/packages/bridge-status-controller/src/utils/metrics.ts b/packages/bridge-status-controller/src/utils/metrics.ts index 7274f943b0a..cbf0d3e7c22 100644 --- a/packages/bridge-status-controller/src/utils/metrics.ts +++ b/packages/bridge-status-controller/src/utils/metrics.ts @@ -187,7 +187,7 @@ export const getPreConfirmationPropertiesFromQuote = ( isHardwareAccount: boolean, location: MetaMetricsSwapsEventSource = MetaMetricsSwapsEventSource.MainView, abTests?: Record, - activeAbTests?: Record, + activeAbTests?: Record[], ) => { const { quote } = quoteResponse; return { @@ -212,7 +212,7 @@ export const getPreConfirmationPropertiesFromQuote = ( ab_tests: abTests, }), ...(activeAbTests && - Object.keys(activeAbTests).length > 0 && { + activeAbTests.length > 0 && { active_ab_tests: activeAbTests, }), }; From cb4e503119a401e90654588f7eb11c2a16df8ecf Mon Sep 17 00:00:00 2001 From: Bryan Fullam Date: Tue, 10 Mar 2026 16:41:03 +0100 Subject: [PATCH 6/8] Fix active_ab_tests typing --- packages/bridge-controller/src/utils/metrics/types.ts | 4 ++-- .../src/bridge-status-controller.test.ts | 6 ++++-- .../src/bridge-status-controller.ts | 4 ++-- packages/bridge-status-controller/src/types.ts | 2 +- packages/bridge-status-controller/src/utils/metrics.test.ts | 4 +++- packages/bridge-status-controller/src/utils/metrics.ts | 2 +- 6 files changed, 13 insertions(+), 9 deletions(-) diff --git a/packages/bridge-controller/src/utils/metrics/types.ts b/packages/bridge-controller/src/utils/metrics/types.ts index 080a0b149d0..df0396cfe5e 100644 --- a/packages/bridge-controller/src/utils/metrics/types.ts +++ b/packages/bridge-controller/src/utils/metrics/types.ts @@ -254,7 +254,7 @@ export type RequiredEventContextFromClient = { [K in keyof RequiredEventContextFromClientBase]: RequiredEventContextFromClientBase[K] & { location?: MetaMetricsSwapsEventSource; ab_tests?: Record; - active_ab_tests?: Record[]; + active_ab_tests?: { key: string; value: string }[]; }; }; @@ -328,7 +328,7 @@ export type CrossChainSwapsEventProperties< action_type: MetricsActionType; location: MetaMetricsSwapsEventSource; ab_tests?: Record; - active_ab_tests?: Record[]; + active_ab_tests?: { key: string; value: string }[]; } | Pick[T] | Pick[T]; 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 0ee94d4cf19..c336673ce2a 100644 --- a/packages/bridge-status-controller/src/bridge-status-controller.test.ts +++ b/packages/bridge-status-controller/src/bridge-status-controller.test.ts @@ -4263,7 +4263,7 @@ describe('BridgeStatusController', () => { srcTxHash: '0xsrcTxHashAbTests', }), abTests: { token_details_layout: 'treatment' }, - activeAbTests: [{ bridge_quote_sorting: 'variant_b' }], + activeAbTests: [{ key: 'bridge_quote_sorting', value: 'variant_b' }], }); const messengerCallSpy = jest.spyOn(mockBridgeStatusMessenger, 'call'); @@ -4285,7 +4285,9 @@ describe('BridgeStatusController', () => { expect.anything(), expect.objectContaining({ ab_tests: { token_details_layout: 'treatment' }, - active_ab_tests: [{ bridge_quote_sorting: 'variant_b' }], + active_ab_tests: [ + { key: 'bridge_quote_sorting', value: 'variant_b' }, + ], }), ); }); diff --git a/packages/bridge-status-controller/src/bridge-status-controller.ts b/packages/bridge-status-controller/src/bridge-status-controller.ts index 9e389b38d5c..144a68f470a 100644 --- a/packages/bridge-status-controller/src/bridge-status-controller.ts +++ b/packages/bridge-status-controller/src/bridge-status-controller.ts @@ -1321,7 +1321,7 @@ export class BridgeStatusController extends StaticIntervalPollingController, - activeAbTests?: Record[], + activeAbTests?: { key: string; value: string }[], ): Promise> => { this.messenger.call( 'BridgeController:stopPollingForQuotes', @@ -1616,7 +1616,7 @@ export class BridgeStatusController extends StaticIntervalPollingController; - activeAbTests?: Record[]; + activeAbTests?: { key: string; value: string }[]; }): Promise> => { const { quoteResponse, accountAddress, location, abTests, activeAbTests } = params; diff --git a/packages/bridge-status-controller/src/types.ts b/packages/bridge-status-controller/src/types.ts index 9cc148f5958..7473c502d49 100644 --- a/packages/bridge-status-controller/src/types.ts +++ b/packages/bridge-status-controller/src/types.ts @@ -148,7 +148,7 @@ export type BridgeHistoryItem = { * Kept separate so migration can run both payloads in parallel. * This field is an array of test objects. */ - activeAbTests?: Record[]; + activeAbTests?: { key: string; value: string }[]; /** * Attempts tracking for exponential backoff on failed fetches. * We track the number of attempts and the last attempt time for each txMetaId that has failed at least once diff --git a/packages/bridge-status-controller/src/utils/metrics.test.ts b/packages/bridge-status-controller/src/utils/metrics.test.ts index a068a52d585..b7475214048 100644 --- a/packages/bridge-status-controller/src/utils/metrics.test.ts +++ b/packages/bridge-status-controller/src/utils/metrics.test.ts @@ -973,7 +973,9 @@ describe('metrics utils', () => { describe('getPreConfirmationPropertiesFromQuote', () => { it('should include both ab_tests and active_ab_tests when both sets are provided', () => { const abTests = { token_details_layout: 'treatment' }; - const activeAbTests = [{ bridge_quote_sorting: 'variant_b' }]; + const activeAbTests = [ + { key: 'bridge_quote_sorting', value: 'variant_b' }, + ]; const result = getPreConfirmationPropertiesFromQuote( { quote: mockHistoryItem.quote, diff --git a/packages/bridge-status-controller/src/utils/metrics.ts b/packages/bridge-status-controller/src/utils/metrics.ts index cbf0d3e7c22..e24188fb114 100644 --- a/packages/bridge-status-controller/src/utils/metrics.ts +++ b/packages/bridge-status-controller/src/utils/metrics.ts @@ -187,7 +187,7 @@ export const getPreConfirmationPropertiesFromQuote = ( isHardwareAccount: boolean, location: MetaMetricsSwapsEventSource = MetaMetricsSwapsEventSource.MainView, abTests?: Record, - activeAbTests?: Record[], + activeAbTests?: { key: string; value: string }[], ) => { const { quote } = quoteResponse; return { From b9f09873b62a4cc10ecb0ac2158f9020341f1e7a Mon Sep 17 00:00:00 2001 From: Bryan Fullam Date: Tue, 10 Mar 2026 17:11:50 +0100 Subject: [PATCH 7/8] chore: changelog --- packages/bridge-controller/CHANGELOG.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/bridge-controller/CHANGELOG.md b/packages/bridge-controller/CHANGELOG.md index d503f97f4b1..5df1e17952c 100644 --- a/packages/bridge-controller/CHANGELOG.md +++ b/packages/bridge-controller/CHANGELOG.md @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Added + +- Add optional `active_ab_tests` property in Unified SwapBridge metrics event context and payload types, alongside existing `ab_tests`. ([#8152](https://github.com/MetaMask/core/pull/8152)) + ## [69.0.1] ### Changed @@ -20,7 +24,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - **BREAKING:** Optional constructor option `getUseAssetsControllerForRates`: when it returns true, exchange rates are read from the new `@metamask/assets-controller` (`AssetsController:getExchangeRatesForBridge`) instead of `MultichainAssetsRatesController`, `TokenRatesController`, and `CurrencyRateController`. ([#8090](https://github.com/MetaMask/core/pull/8090)) - Add `AssetPickerOpened` unified swap bridge metrics event with an `asset_location` property to indicate `'source'` or `'destination'`. ([#7985](https://github.com/MetaMask/core/pull/7985)) -- Add optional `active_ab_tests` property in Unified SwapBridge metrics event context and payload types, alongside existing `ab_tests`. ([#8152](https://github.com/MetaMask/core/pull/8152)) ### Changed From 11de6f26ef3ab13e589fa44b05e3cb546a631523 Mon Sep 17 00:00:00 2001 From: Bryan Fullam Date: Tue, 10 Mar 2026 17:20:11 +0100 Subject: [PATCH 8/8] chore: changelog formatting --- packages/bridge-status-controller/CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/bridge-status-controller/CHANGELOG.md b/packages/bridge-status-controller/CHANGELOG.md index 8f755f7e161..bd397d6b0ae 100644 --- a/packages/bridge-status-controller/CHANGELOG.md +++ b/packages/bridge-status-controller/CHANGELOG.md @@ -8,6 +8,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] ### Added + - Added optional `activeAbTests` context support so Unified SwapBridge events can include `active_ab_tests` independently of `ab_tests`. ([#8152](https://github.com/MetaMask/core/pull/8152)) ## [68.0.2]