Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion packages/bridge-controller/src/bridge-controller.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2665,7 +2665,7 @@ describe('BridgeController', function () {
it('should track the PageViewed event', () => {
bridgeController.trackUnifiedSwapBridgeEvent(
UnifiedSwapBridgeEventName.PageViewed,
{ abc: 1 },
{},
);
expect(trackMetaMetricsFn).toHaveBeenCalledTimes(1);

Expand Down Expand Up @@ -2881,6 +2881,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,
Expand Down Expand Up @@ -2920,6 +2921,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,
Expand Down Expand Up @@ -2962,6 +2964,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',
Expand Down Expand Up @@ -3002,6 +3005,7 @@ describe('BridgeController', function () {
{
error_message: 'Failed to submit tx',
is_hardware_wallet: false,
account_hardware_type: null,
usd_quoted_gas: 1,
gas_included: false,
gas_included_7702: false,
Expand Down
30 changes: 13 additions & 17 deletions packages/bridge-controller/src/bridge-controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,10 +67,10 @@ import {
} from './utils/metrics/constants';
import {
formatProviderLabel,
getAccountHardwareType,
getRequestParams,
getSwapTypeFromQuote,
isCustomSlippage,
isHardwareWallet,
toInputChangedPropertyKey,
toInputChangedPropertyValue,
} from './utils/metrics/properties';
Expand Down Expand Up @@ -913,15 +913,18 @@ export class BridgeController extends StaticIntervalPollingController<BridgePoll

readonly #getRequestMetadata = (): Omit<
RequestMetadata,
| 'stx_enabled'
| 'usd_amount_source'
| 'security_warnings'
| 'is_hardware_wallet'
'stx_enabled' | 'usd_amount_source' | 'security_warnings'
> => {
const accountHardwareType = getAccountHardwareType(
this.#getMultichainSelectedAccount(),
);

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,
};
};

Expand Down Expand Up @@ -959,9 +962,14 @@ export class BridgeController extends StaticIntervalPollingController<BridgePoll
};
switch (eventName) {
case UnifiedSwapBridgeEventName.ButtonClicked:
return {
...getRequestParams(this.state.quoteRequest),
...baseProperties,
};
case UnifiedSwapBridgeEventName.PageViewed:
return {
...getRequestParams(this.state.quoteRequest),
...this.#getRequestMetadata(),
...baseProperties,
};
case UnifiedSwapBridgeEventName.QuotesValidationFailed:
Expand All @@ -975,29 +983,20 @@ export class BridgeController extends StaticIntervalPollingController<BridgePoll
...getRequestParams(this.state.quoteRequest),
...this.#getRequestMetadata(),
...this.#getQuoteFetchData(),
is_hardware_wallet: isHardwareWallet(
this.#getMultichainSelectedAccount(),
),
refresh_count: this.state.quotesRefreshCount,
...baseProperties,
};
case UnifiedSwapBridgeEventName.QuotesRequested:
return {
...getRequestParams(this.state.quoteRequest),
...this.#getRequestMetadata(),
is_hardware_wallet: isHardwareWallet(
this.#getMultichainSelectedAccount(),
),
has_sufficient_funds: !this.state.quoteRequest.insufficientBal,
...baseProperties,
};
case UnifiedSwapBridgeEventName.QuotesError:
return {
...getRequestParams(this.state.quoteRequest),
...this.#getRequestMetadata(),
is_hardware_wallet: isHardwareWallet(
this.#getMultichainSelectedAccount(),
),
error_message: this.state.quoteFetchError,
has_sufficient_funds: !this.state.quoteRequest.insufficientBal,
...baseProperties,
Expand All @@ -1009,9 +1008,6 @@ export class BridgeController extends StaticIntervalPollingController<BridgePoll
...getRequestParams(this.state.quoteRequest),
...this.#getRequestMetadata(),
...this.#getQuoteFetchData(),
is_hardware_wallet: isHardwareWallet(
this.#getMultichainSelectedAccount(),
),
...baseProperties,
};
case UnifiedSwapBridgeEventName.Failed: {
Expand Down
2 changes: 2 additions & 0 deletions packages/bridge-controller/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ export {
} from './utils/metrics/constants';

export type {
AccountHardwareType,
RequiredEventContextFromClient,
CrossChainSwapsEventProperties,
TradeData,
Expand All @@ -21,6 +22,7 @@ export type {

export {
formatProviderLabel,
getAccountHardwareType,
getRequestParams,
getSwapType,
isHardwareWallet,
Expand Down
35 changes: 35 additions & 0 deletions packages/bridge-controller/src/utils/metrics/properties.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import {
toInputChangedPropertyValue,
getSwapTypeFromQuote,
formatProviderLabel,
getAccountHardwareType,
isHardwareWallet,
getRequestParams,
getQuotesReceivedProperties,
} from './properties';
Expand Down Expand Up @@ -225,6 +227,39 @@ describe('properties', () => {
});
});

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();
Expand Down
21 changes: 20 additions & 1 deletion packages/bridge-controller/src/utils/metrics/properties.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import type { AccountsControllerState } from '@metamask/accounts-controller';

import { MetricsSwapType } from './constants';
import type {
AccountHardwareType,
InputKeys,
InputValues,
QuoteWarning,
Expand Down Expand Up @@ -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;
};

/**
Expand Down
19 changes: 17 additions & 2 deletions packages/bridge-controller/src/utils/metrics/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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[];
};
Expand Down Expand Up @@ -167,7 +175,10 @@ type RequiredEventContextFromClientBase = {
Pick<QuoteFetchData, 'price_impact'> &
Pick<
RequestMetadata,
'stx_enabled' | 'usd_amount_source' | 'is_hardware_wallet'
| 'stx_enabled'
| 'usd_amount_source'
| 'is_hardware_wallet'
| 'account_hardware_type'
> &
Pick<
RequestParams,
Expand Down Expand Up @@ -256,7 +267,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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2109,7 +2109,7 @@ describe('BridgeStatusController', () => {
id: 'test-snap',
},
keyring: {
type: 'Hardware',
type: 'QR Hardware Wallet Device',
},
},
options: { scope: 'solana-chain-id' },
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,10 @@ import {
StatusTypes,
UnifiedSwapBridgeEventName,
formatChainIdToCaip,
getAccountHardwareType,
isCrossChain,
isTronChainId,
isEvmTxData,
isHardwareWallet,
MetricsActionType,
MetaMetricsSwapsEventSource,
isBitcoinTrade,
Expand Down Expand Up @@ -260,7 +260,13 @@ export class BridgeStatusController extends StaticIntervalPollingController<Brid
this.#trackUnifiedSwapBridgeEvent(
UnifiedSwapBridgeEventName.Failed,
historyKey ?? txMetaId,
getEVMTxPropertiesFromTransactionMeta(transactionMeta),
getEVMTxPropertiesFromTransactionMeta(
transactionMeta,
this.messenger.call(
'AccountsController:getAccountByAddress',
transactionMeta.txParams.from,
),
),
);
}
}
Expand Down Expand Up @@ -1333,12 +1339,12 @@ export class BridgeStatusController extends StaticIntervalPollingController<Brid
'Failed to submit cross-chain swap transaction: undefined multichain account',
);
}
const isHardwareAccount = isHardwareWallet(selectedAccount);
const accountHardwareType = getAccountHardwareType(selectedAccount);

const preConfirmationProperties = getPreConfirmationPropertiesFromQuote(
quoteResponse,
isStxEnabledOnClient,
isHardwareAccount,
accountHardwareType,
location,
abTests,
);
Expand Down Expand Up @@ -1452,7 +1458,8 @@ export class BridgeStatusController extends StaticIntervalPollingController<Brid
// For hardware wallets on Mobile, this is fixes an issue where the Ledger does not get prompted for the 2nd approval
// Extension does not have this issue
const requireApproval =
this.#clientId === BridgeClientId.MOBILE && isHardwareAccount;
this.#clientId === BridgeClientId.MOBILE &&
accountHardwareType !== null;

// Handle smart transactions if enabled
txMeta = await this.#trace(
Expand Down Expand Up @@ -1618,11 +1625,11 @@ export class BridgeStatusController extends StaticIntervalPollingController<Brid

// Build pre-confirmation properties for error tracking parity with submitTx
const account = this.#getMultichainSelectedAccount(accountAddress);
const isHardwareAccount = Boolean(account) && isHardwareWallet(account);
const accountHardwareType = getAccountHardwareType(account);
const preConfirmationProperties = getPreConfirmationPropertiesFromQuote(
quoteResponse,
false,
isHardwareAccount,
accountHardwareType,
location,
abTests,
);
Expand Down
Loading
Loading