diff --git a/app/components/UI/Bridge/_mocks_/initialState.ts b/app/components/UI/Bridge/_mocks_/initialState.ts
index 75f1770530f..69972a0c442 100644
--- a/app/components/UI/Bridge/_mocks_/initialState.ts
+++ b/app/components/UI/Bridge/_mocks_/initialState.ts
@@ -7,6 +7,8 @@ import {
SolAccountType,
BtcScope,
BtcAccountType,
+ TrxScope,
+ TrxAccountType,
} from '@metamask/keyring-api';
import { AccountWalletType, AccountGroupType } from '@metamask/account-api';
import { ethers } from 'ethers';
@@ -53,6 +55,11 @@ export const solanaToken2Address =
export const btcNativeTokenAddress =
'bip122:000000000019d6689c085ae165831e93/slip44:0' as CaipAssetId;
+// Tron account and tokens
+export const trxAccountId = 'trxAccountId';
+export const trxAccountAddress = 'TN3W4Bb1JVHPiWJVm7d9q9qHGXSdoMrMrE';
+export const trxNativeTokenAddress = 'tron:728126428/slip44:195' as CaipAssetId;
+
export const initialState = {
engine: {
backgroundState: {
@@ -100,6 +107,11 @@ export const initialState = {
isActiveDest: true,
isGaslessSwapEnabled: false,
},
+ [TrxScope.Mainnet]: {
+ isActiveSrc: true,
+ isActiveDest: true,
+ isGaslessSwapEnabled: false,
+ },
},
bip44DefaultPairs: {
bip122: {
@@ -259,6 +271,12 @@ export const initialState = {
'bip122:000000000019d6689c085ae165831e93/slip44:0' as const,
isEvm: false as const,
},
+ [TrxScope.Mainnet]: {
+ chainId: TrxScope.Mainnet,
+ name: 'Tron',
+ nativeCurrency: 'tron:728126428/slip44:195' as const,
+ isEvm: false as const,
+ },
},
},
MultichainBalancesController: {
@@ -279,12 +297,19 @@ export const initialState = {
unit: 'BTC',
},
},
+ [trxAccountId]: {
+ [trxNativeTokenAddress]: {
+ amount: '500',
+ unit: 'TRX',
+ },
+ },
},
},
MultichainAssetsController: {
accountsAssets: {
[solanaAccountId]: [solanaNativeTokenAddress, solanaToken2Address],
[btcAccountId]: [btcNativeTokenAddress],
+ [trxAccountId]: [trxNativeTokenAddress],
},
assetsMetadata: {
[btcNativeTokenAddress]: {
@@ -348,6 +373,19 @@ export const initialState = {
},
],
},
+ [trxNativeTokenAddress]: {
+ name: 'Tron',
+ symbol: 'TRX',
+ iconUrl: 'https://tron.network/static/images/logo.png',
+ fungible: true as const,
+ units: [
+ {
+ name: 'Tron',
+ symbol: 'TRX',
+ decimals: 6,
+ },
+ ],
+ },
},
},
MultichainAssetsRatesController: {
@@ -364,6 +402,10 @@ export const initialState = {
rate: '100000', // 1 BTC = 100000 USD
conversionTime: 0,
},
+ [trxNativeTokenAddress]: {
+ rate: '0.10', // 1 TRX = 0.10 USD
+ conversionTime: 0,
+ },
},
},
AccountsController: {
@@ -400,6 +442,16 @@ export const initialState = {
lastSelected: 0,
},
},
+ [trxAccountId]: {
+ id: trxAccountId,
+ address: trxAccountAddress,
+ name: 'Account 4',
+ type: TrxAccountType.Eoa,
+ scopes: [TrxScope.Mainnet],
+ metadata: {
+ lastSelected: 0,
+ },
+ },
},
},
},
@@ -428,7 +480,12 @@ export const initialState = {
groupIndex: 0,
},
},
- accounts: [evmAccountId, solanaAccountId, btcAccountId],
+ accounts: [
+ evmAccountId,
+ solanaAccountId,
+ btcAccountId,
+ trxAccountId,
+ ],
},
},
},
diff --git a/app/components/UI/Bridge/components/BridgeDestNetworkSelector/__snapshots__/BridgeDestNetworkSelector.test.tsx.snap b/app/components/UI/Bridge/components/BridgeDestNetworkSelector/__snapshots__/BridgeDestNetworkSelector.test.tsx.snap
index 5b6873b749f..f8c8134cee0 100644
--- a/app/components/UI/Bridge/components/BridgeDestNetworkSelector/__snapshots__/BridgeDestNetworkSelector.test.tsx.snap
+++ b/app/components/UI/Bridge/components/BridgeDestNetworkSelector/__snapshots__/BridgeDestNetworkSelector.test.tsx.snap
@@ -758,6 +758,112 @@ exports[`BridgeDestNetworkSelector renders with initial state and displays netwo
+
+
+
+
+
+
+
+
+
+
+ Tron
+
+
+
+
+
+
+
diff --git a/app/components/UI/Bridge/components/BridgeDestNetworksBar.tsx b/app/components/UI/Bridge/components/BridgeDestNetworksBar.tsx
index 00ee2dcce22..47d9eb33df7 100644
--- a/app/components/UI/Bridge/components/BridgeDestNetworksBar.tsx
+++ b/app/components/UI/Bridge/components/BridgeDestNetworksBar.tsx
@@ -30,7 +30,7 @@ import { selectChainId } from '../../../../selectors/networkController';
// Using ScrollView from react-native-gesture-handler to fix scroll issues with the bottom sheet
import { ScrollView } from 'react-native-gesture-handler';
///: BEGIN:ONLY_INCLUDE_IF(keyring-snaps)
-import { BtcScope, SolScope } from '@metamask/keyring-api';
+import { BtcScope, SolScope, TrxScope } from '@metamask/keyring-api';
import { BridgeViewMode } from '../types';
///: END:ONLY_INCLUDE_IF
const createStyles = (params: { theme: Theme }) => {
@@ -69,16 +69,17 @@ export const ChainPopularity: Record = {
///: BEGIN:ONLY_INCLUDE_IF(keyring-snaps)
[BtcScope.Mainnet]: 3,
[SolScope.Mainnet]: 4,
+ [TrxScope.Mainnet]: 5,
///: END:ONLY_INCLUDE_IF
- [CHAIN_IDS.BASE]: 5,
- [CHAIN_IDS.ARBITRUM]: 6,
- [CHAIN_IDS.LINEA_MAINNET]: 7,
- [CHAIN_IDS.POLYGON]: 8,
- [CHAIN_IDS.AVALANCHE]: 9,
- [CHAIN_IDS.OPTIMISM]: 10,
- [CHAIN_IDS.ZKSYNC_ERA]: 11,
- [NETWORKS_CHAIN_ID.SEI]: 12,
- [NETWORKS_CHAIN_ID.MONAD]: 13,
+ [CHAIN_IDS.BASE]: 6,
+ [CHAIN_IDS.ARBITRUM]: 7,
+ [CHAIN_IDS.LINEA_MAINNET]: 8,
+ [CHAIN_IDS.POLYGON]: 9,
+ [CHAIN_IDS.AVALANCHE]: 10,
+ [CHAIN_IDS.OPTIMISM]: 11,
+ [CHAIN_IDS.ZKSYNC_ERA]: 12,
+ [NETWORKS_CHAIN_ID.SEI]: 13,
+ [NETWORKS_CHAIN_ID.MONAD]: 14,
};
export const BridgeDestNetworksBar = () => {
diff --git a/app/components/UI/Bridge/components/BridgeDestTokenSelector/__snapshots__/BridgeDestTokenSelector.test.tsx.snap b/app/components/UI/Bridge/components/BridgeDestTokenSelector/__snapshots__/BridgeDestTokenSelector.test.tsx.snap
index 642a448224a..bd5d4722251 100644
--- a/app/components/UI/Bridge/components/BridgeDestTokenSelector/__snapshots__/BridgeDestTokenSelector.test.tsx.snap
+++ b/app/components/UI/Bridge/components/BridgeDestTokenSelector/__snapshots__/BridgeDestTokenSelector.test.tsx.snap
@@ -722,6 +722,60 @@ exports[`BridgeDestTokenSelector renders with initial state and displays tokens
+
+
+
+ Tron
+
+
+
{
optimismChainId,
SolScope.Mainnet,
BtcScope.Mainnet,
+ TrxScope.Mainnet,
]);
// Should navigate back
diff --git a/app/components/UI/Bridge/components/BridgeSourceNetworkSelector/__snapshots__/BridgeSourceNetworkSelector.test.tsx.snap b/app/components/UI/Bridge/components/BridgeSourceNetworkSelector/__snapshots__/BridgeSourceNetworkSelector.test.tsx.snap
index 2b10f897f80..050d3b8463e 100644
--- a/app/components/UI/Bridge/components/BridgeSourceNetworkSelector/__snapshots__/BridgeSourceNetworkSelector.test.tsx.snap
+++ b/app/components/UI/Bridge/components/BridgeSourceNetworkSelector/__snapshots__/BridgeSourceNetworkSelector.test.tsx.snap
@@ -1364,6 +1364,199 @@ exports[`BridgeSourceNetworkSelector renders with initial state and displays net
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Tron
+
+
+
+
+
+ $50
+
+
+
+
+
+
diff --git a/app/components/UI/Bridge/components/BridgeSourceTokenSelector/__snapshots__/BridgeSourceTokenSelector.test.tsx.snap b/app/components/UI/Bridge/components/BridgeSourceTokenSelector/__snapshots__/BridgeSourceTokenSelector.test.tsx.snap
index ed92091f927..f1e1649c075 100644
--- a/app/components/UI/Bridge/components/BridgeSourceTokenSelector/__snapshots__/BridgeSourceTokenSelector.test.tsx.snap
+++ b/app/components/UI/Bridge/components/BridgeSourceTokenSelector/__snapshots__/BridgeSourceTokenSelector.test.tsx.snap
@@ -751,6 +751,28 @@ exports[`BridgeSourceTokenSelector renders with initial state and displays token
/>
+
+ +1
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ TRX
+
+
+
+ Tron
+
+
+
+
+ 50 USD
+
+
+ 500 TRX
+
+
+
+
+
+
diff --git a/app/components/UI/Bridge/hooks/useSortedSourceNetworks/__snapshots__/useSortedSourceNetworks.test.ts.snap b/app/components/UI/Bridge/hooks/useSortedSourceNetworks/__snapshots__/useSortedSourceNetworks.test.ts.snap
index 61f87b8e080..57ded41b071 100644
--- a/app/components/UI/Bridge/hooks/useSortedSourceNetworks/__snapshots__/useSortedSourceNetworks.test.ts.snap
+++ b/app/components/UI/Bridge/hooks/useSortedSourceNetworks/__snapshots__/useSortedSourceNetworks.test.ts.snap
@@ -48,5 +48,16 @@ exports[`useSortedSourceNetworks should sort networks by total fiat value in des
"ticker": "BTC",
"totalFiatValue": 1500,
},
+ {
+ "chainId": "tron:728126428",
+ "decimals": 6,
+ "imageSource": 1,
+ "isEvm": false,
+ "isTestnet": false,
+ "name": "Tron",
+ "nativeCurrency": "tron:728126428/slip44:195",
+ "ticker": "TRX",
+ "totalFiatValue": 50,
+ },
]
`;
diff --git a/app/components/UI/Bridge/hooks/useSortedSourceNetworks/useSortedSourceNetworks.ts b/app/components/UI/Bridge/hooks/useSortedSourceNetworks/useSortedSourceNetworks.ts
index 32edaf73e61..807c3efee06 100644
--- a/app/components/UI/Bridge/hooks/useSortedSourceNetworks/useSortedSourceNetworks.ts
+++ b/app/components/UI/Bridge/hooks/useSortedSourceNetworks/useSortedSourceNetworks.ts
@@ -7,7 +7,7 @@ import { selectLastSelectedEvmAccount } from '../../../../../selectors/accountsC
import { InternalAccount } from '@metamask/keyring-internal-api';
import { isNonEvmChainId } from '@metamask/bridge-controller';
import { useTokensWithBalance } from '../useTokensWithBalance';
-import { BtcScope, SolScope } from '@metamask/keyring-api';
+import { BtcScope, SolScope, TrxScope } from '@metamask/keyring-api';
export const useSortedSourceNetworks = () => {
const enabledSourceChains = useSelector(selectEnabledSourceChains);
@@ -68,6 +68,15 @@ export const useSortedSourceNetworks = () => {
0,
);
+ // Calculate total fiat value for Tron (native + tokens)
+ const trxTokensWithBalance = useTokensWithBalance({
+ chainIds: [TrxScope.Mainnet],
+ });
+ const trxFiatTotal = trxTokensWithBalance.reduce(
+ (sum, token) => sum + (token.tokenFiatAmount ?? 0),
+ 0,
+ );
+
// Sort networks by total fiat value in descending order
const sortedSourceNetworks = useMemo(
() =>
@@ -79,6 +88,8 @@ export const useSortedSourceNetworks = () => {
totalFiatValue = solFiatTotal;
} else if (chain.chainId === BtcScope.Mainnet) {
totalFiatValue = btcFiatTotal;
+ } else if (chain.chainId === TrxScope.Mainnet) {
+ totalFiatValue = trxFiatTotal;
} else {
totalFiatValue = getEvmChainTotalFiatValue(chain.chainId);
}
@@ -94,6 +105,7 @@ export const useSortedSourceNetworks = () => {
getEvmChainTotalFiatValue,
solFiatTotal,
btcFiatTotal,
+ trxFiatTotal,
],
);
diff --git a/app/components/Views/AccountPermissions/NetworkPermissionsConnected/NetworkPermissionsConnected.tsx b/app/components/Views/AccountPermissions/NetworkPermissionsConnected/NetworkPermissionsConnected.tsx
index a9eae2f0a90..f3b69587b07 100644
--- a/app/components/Views/AccountPermissions/NetworkPermissionsConnected/NetworkPermissionsConnected.tsx
+++ b/app/components/Views/AccountPermissions/NetworkPermissionsConnected/NetworkPermissionsConnected.tsx
@@ -11,8 +11,6 @@ import { AccountPermissionsScreens } from '../AccountPermissions.types';
import { MetaMetricsEvents } from '../../../../core/Analytics';
import Routes from '../../../../constants/navigation/Routes';
import {
- selectProviderConfig,
- ProviderConfig,
selectEvmChainId,
selectEvmNetworkConfigurationsByChainId,
} from '../../../../selectors/networkController';
@@ -48,6 +46,7 @@ import { getCaip25Caveat } from '../../../../core/Permissions';
import { getPermittedEthChainIds } from '@metamask/chain-agnostic-permission';
import { toHex } from '@metamask/controller-utils';
import { parseCaipChainId } from '@metamask/utils';
+import { POPULAR_NETWORK_CHAIN_IDS } from '../../../../constants/popular-networks';
// Needs to be updated to handle non-evm
const NetworkPermissionsConnected = ({
@@ -59,7 +58,6 @@ const NetworkPermissionsConnected = ({
const { navigate } = useNavigation();
const { trackEvent, createEventBuilder } = useMetrics();
- const providerConfig: ProviderConfig = useSelector(selectProviderConfig);
const evmChainId = useSelector(selectEvmChainId);
const evmCaipChainId = `eip155:${parseInt(evmChainId, 16)}`;
@@ -141,12 +139,15 @@ const NetworkPermissionsConnected = ({
const theNetworkName = handleNetworkSwitch(reference);
if (theNetworkName) {
+ const targetChainId = toHex(reference);
trackEvent(
createEventBuilder(MetaMetricsEvents.NETWORK_SWITCHED)
.addProperties({
chain_id: reference,
- from_network: providerConfig?.nickname || theNetworkName,
- to_network: theNetworkName,
+ from_network: evmChainId,
+ to_network: targetChainId,
+ custom_network:
+ !POPULAR_NETWORK_CHAIN_IDS.has(targetChainId),
})
.build(),
);
diff --git a/app/components/Views/EditAccountName/EditAccountName.tsx b/app/components/Views/EditAccountName/EditAccountName.tsx
index 2db10a9589c..28dd266673a 100644
--- a/app/components/Views/EditAccountName/EditAccountName.tsx
+++ b/app/components/Views/EditAccountName/EditAccountName.tsx
@@ -114,7 +114,7 @@ const EditAccountName = () => {
const analyticsProperties = async () => {
const accountType = getAddressAccountType(selectedAccount?.address);
const account_type =
- accountType === 'QR' ? 'hardware' : accountType;
+ accountType === 'QR Hardware' ? 'hardware' : accountType;
return { account_type, chain_id: getDecimalChainId(chainId) };
};
const analyticsProps = await analyticsProperties();
diff --git a/app/components/Views/NetworkSelector/useSwitchNetworks.ts b/app/components/Views/NetworkSelector/useSwitchNetworks.ts
index c651e65185c..a7816bdf43a 100644
--- a/app/components/Views/NetworkSelector/useSwitchNetworks.ts
+++ b/app/components/Views/NetworkSelector/useSwitchNetworks.ts
@@ -66,7 +66,6 @@ export function useSwitchNetworks({
domainIsConnectedDapp = false,
origin = '',
selectedChainId,
- selectedNetworkName,
dismissModal,
closeRpcModal,
parentSpan,
@@ -109,12 +108,8 @@ export function useSwitchNetworks({
const { MultichainNetworkController, SelectedNetworkController } =
Engine.context;
- const {
- name: nickname,
- chainId,
- rpcEndpoints,
- defaultRpcEndpointIndex,
- } = networkConfiguration;
+ const { chainId, rpcEndpoints, defaultRpcEndpointIndex } =
+ networkConfiguration;
const networkConfigurationId =
rpcEndpoints[defaultRpcEndpointIndex].networkClientId;
@@ -154,8 +149,9 @@ export function useSwitchNetworks({
createEventBuilder(MetaMetricsEvents.NETWORK_SWITCHED)
.addProperties({
chain_id: getDecimalChainId(chainId),
- from_network: selectedNetworkName,
- to_network: nickname,
+ from_network: selectedChainId,
+ to_network: chainId,
+ custom_network: !POPULAR_NETWORK_CHAIN_IDS.has(chainId),
})
.build(),
);
@@ -163,7 +159,7 @@ export function useSwitchNetworks({
[
domainIsConnectedDapp,
origin,
- selectedNetworkName,
+ selectedChainId,
trackEvent,
createEventBuilder,
parentSpan,
@@ -225,12 +221,14 @@ export function useSwitchNetworks({
endTrace({ name: TraceName.SwitchBuiltInNetwork });
endTrace({ name: TraceName.NetworkSwitch });
+ const toChainId = BUILT_IN_NETWORKS[type].chainId;
trackEvent(
createEventBuilder(MetaMetricsEvents.NETWORK_SWITCHED)
.addProperties({
- chain_id: getDecimalChainId(selectedChainId),
- from_network: selectedNetworkName,
- to_network: type,
+ chain_id: getDecimalChainId(toChainId),
+ from_network: selectedChainId,
+ to_network: toChainId,
+ custom_network: !POPULAR_NETWORK_CHAIN_IDS.has(toChainId),
})
.build(),
);
@@ -241,7 +239,6 @@ export function useSwitchNetworks({
networkConfigurations,
setTokenNetworkFilter,
selectedChainId,
- selectedNetworkName,
trackEvent,
createEventBuilder,
parentSpan,
diff --git a/app/core/Engine/controllers/transaction-controller/utils.test.ts b/app/core/Engine/controllers/transaction-controller/utils.test.ts
index 8065a760ea1..54c4ab27659 100644
--- a/app/core/Engine/controllers/transaction-controller/utils.test.ts
+++ b/app/core/Engine/controllers/transaction-controller/utils.test.ts
@@ -46,9 +46,16 @@ jest.mock('../../../Analytics/MetricsEventBuilder', () => ({
},
}));
+const mockGetAddressAccountType = jest.fn().mockReturnValue('MetaMask');
+const mockIsValidHexAddress = jest.fn().mockReturnValue(true);
+const mockIsHardwareAccount = jest.fn().mockReturnValue(false);
+
jest.mock('../../../../util/address', () => ({
- getAddressAccountType: jest.fn().mockReturnValue('MetaMask'),
- isValidHexAddress: jest.fn().mockReturnValue(true),
+ ...jest.requireActual('../../../../util/address'),
+ getAddressAccountType: (...args: unknown[]) =>
+ mockGetAddressAccountType(...args),
+ isValidHexAddress: (...args: unknown[]) => mockIsValidHexAddress(...args),
+ isHardwareAccount: (...args: unknown[]) => mockIsHardwareAccount(...args),
}));
jest.mock('../../../../util/rpc-domain-utils', () => ({
@@ -295,6 +302,10 @@ describe('generateDefaultTransactionMetrics', () => {
mockEventBuilder,
);
+ mockGetAddressAccountType.mockReturnValue('MetaMask');
+ mockIsValidHexAddress.mockReturnValue(true);
+ mockIsHardwareAccount.mockReturnValue(false);
+
mockNativeBalance('0x1', FROM_ADDRESS_MOCK, '0x0000000000000000000');
});
@@ -309,6 +320,7 @@ describe('generateDefaultTransactionMetrics', () => {
metametricsEvent: mockMetametricsEvent,
properties: expect.objectContaining({
account_eip7702_upgraded: undefined,
+ account_hardware_type: null,
account_type: 'MetaMask',
additional_property: 'test_value',
chain_id: '0x1',
@@ -347,6 +359,7 @@ describe('generateDefaultTransactionMetrics', () => {
metametricsEvent: mockMetametricsEvent,
properties: expect.objectContaining({
account_eip7702_upgraded: undefined,
+ account_hardware_type: null,
account_type: 'MetaMask',
chain_id: '0x1',
dapp_host_name: 'N/A',
@@ -770,6 +783,7 @@ describe('generateDefaultTransactionMetrics', () => {
expect(metrics.properties).toEqual(
expect.objectContaining({
account_eip7702_upgraded: undefined,
+ account_hardware_type: null,
account_type: 'MetaMask',
api_method: 'wallet_sendCalls',
batch_transaction_count: 2,
@@ -808,6 +822,7 @@ describe('generateDefaultTransactionMetrics', () => {
expect(metrics.properties).toEqual(
expect.objectContaining({
account_eip7702_upgraded: undefined,
+ account_hardware_type: null,
account_type: 'MetaMask',
chain_id: '0xaa36a7',
dapp_host_name: 'metamask',
@@ -848,6 +863,7 @@ describe('generateDefaultTransactionMetrics', () => {
expect(metrics.properties).toEqual(
expect.objectContaining({
account_eip7702_upgraded: undefined,
+ account_hardware_type: null,
account_type: 'MetaMask',
chain_id: '0xaa36a7',
dapp_host_name: 'metamask',
@@ -884,6 +900,7 @@ describe('generateDefaultTransactionMetrics', () => {
expect.objectContaining({
account_eip7702_upgraded:
'0x63c0c19a282a1b52b07dd5a65b58948a07dae32b',
+ account_hardware_type: null,
account_type: 'MetaMask',
api_method: 'wallet_sendCalls',
batch_transaction_count: 2,
@@ -908,4 +925,77 @@ describe('generateDefaultTransactionMetrics', () => {
);
});
});
+
+ describe('hardware wallet metrics', () => {
+ it('sets account_hardware_type to Ledger for Ledger hardware wallet', async () => {
+ mockIsHardwareAccount.mockReturnValue(true);
+ mockGetAddressAccountType.mockReturnValue('Ledger');
+
+ const result = await generateDefaultTransactionMetrics(
+ mockMetametricsEvent,
+ mockTransactionMeta as TransactionMeta,
+ mockEventHandlerRequest as TransactionEventHandlerRequest,
+ );
+
+ expect(result.properties.account_type).toBe('Ledger');
+ expect(result.properties.account_hardware_type).toBe('Ledger');
+ });
+
+ it('sets account_hardware_type to QR Hardware for QR hardware wallet', async () => {
+ mockIsHardwareAccount.mockReturnValue(true);
+ mockGetAddressAccountType.mockReturnValue('QR Hardware');
+
+ const result = await generateDefaultTransactionMetrics(
+ mockMetametricsEvent,
+ mockTransactionMeta as TransactionMeta,
+ mockEventHandlerRequest as TransactionEventHandlerRequest,
+ );
+
+ expect(result.properties.account_type).toBe('QR Hardware');
+ expect(result.properties.account_hardware_type).toBe('QR Hardware');
+ });
+
+ it('sets account_hardware_type to null for non-hardware wallet', async () => {
+ mockIsHardwareAccount.mockReturnValue(false);
+ mockGetAddressAccountType.mockReturnValue('MetaMask');
+
+ const result = await generateDefaultTransactionMetrics(
+ mockMetametricsEvent,
+ mockTransactionMeta as TransactionMeta,
+ mockEventHandlerRequest as TransactionEventHandlerRequest,
+ );
+
+ expect(result.properties.account_type).toBe('MetaMask');
+ expect(result.properties.account_hardware_type).toBeNull();
+ });
+
+ it('sets account_type to unknown when from address is invalid', async () => {
+ mockIsValidHexAddress.mockReturnValue(false);
+
+ const result = await generateDefaultTransactionMetrics(
+ mockMetametricsEvent,
+ mockTransactionMeta as TransactionMeta,
+ mockEventHandlerRequest as TransactionEventHandlerRequest,
+ );
+
+ expect(result.properties.account_type).toBe('unknown');
+ expect(result.properties.account_hardware_type).toBeNull();
+ });
+
+ it('defaults to unknown account_type when getAddressAccountType throws', async () => {
+ mockIsValidHexAddress.mockReturnValue(true);
+ mockGetAddressAccountType.mockImplementation(() => {
+ throw new Error('Wallet locked');
+ });
+
+ const result = await generateDefaultTransactionMetrics(
+ mockMetametricsEvent,
+ mockTransactionMeta as TransactionMeta,
+ mockEventHandlerRequest as TransactionEventHandlerRequest,
+ );
+
+ expect(result.properties.account_type).toBe('unknown');
+ expect(result.properties.account_hardware_type).toBeNull();
+ });
+ });
});
diff --git a/app/core/Engine/controllers/transaction-controller/utils.ts b/app/core/Engine/controllers/transaction-controller/utils.ts
index 50538959ca0..13b544718ca 100644
--- a/app/core/Engine/controllers/transaction-controller/utils.ts
+++ b/app/core/Engine/controllers/transaction-controller/utils.ts
@@ -27,6 +27,7 @@ import type {
} from './types';
import {
getAddressAccountType,
+ isHardwareAccount,
isValidHexAddress,
} from '../../../../util/address';
import { hasTransactionType } from '../../../../components/Views/confirmations/utils/transaction';
@@ -192,12 +193,20 @@ export async function generateDefaultTransactionMetrics(
const { from } = txParams || {};
let accountType = 'unknown';
+ let accountHardwareType: string | null = null;
// Fails if wallet locked
try {
- accountType = isValidHexAddress(from)
- ? getAddressAccountType(from)
- : accountType;
+ if (isValidHexAddress(from)) {
+ // Get account type based on the keyring associated with
+ // this address.
+ accountType = getAddressAccountType(from);
+
+ // Also populate this one for HW accounts.
+ if (isHardwareAccount(from)) {
+ accountHardwareType = accountType;
+ }
+ }
} catch {
// Intentionally empty
}
@@ -212,6 +221,7 @@ export async function generateDefaultTransactionMetrics(
...batchProperties,
...gasFeeProperties,
account_type: accountType,
+ account_hardware_type: accountHardwareType,
chain_id: chainId,
dapp_host_name: origin ?? 'N/A',
error: error?.message,
diff --git a/app/core/RPCMethods/lib/ethereum-chain-utils.js b/app/core/RPCMethods/lib/ethereum-chain-utils.js
index 9a6349b2df0..00ac9d3a129 100644
--- a/app/core/RPCMethods/lib/ethereum-chain-utils.js
+++ b/app/core/RPCMethods/lib/ethereum-chain-utils.js
@@ -15,6 +15,7 @@ import { MetaMetrics, MetaMetricsEvents } from '../../../core/Analytics';
import { MetricsEventBuilder } from '../../../core/Analytics/MetricsEventBuilder';
import Engine from '../../Engine';
import { isSnapId } from '@metamask/snaps-utils';
+import { POPULAR_NETWORK_CHAIN_IDS } from '../../../constants/popular-networks';
const EVM_NATIVE_TOKEN_DECIMALS = 18;
@@ -278,10 +279,14 @@ export async function switchToNetwork({
networkClientId,
);
+ const fromChainId = hooks.fromNetworkConfiguration?.chainId;
const analyticsParams = {
chain_id: getDecimalChainId(chainId),
source: 'Custom Network API',
symbol: nativeCurrency || 'ETH',
+ from_network: fromChainId,
+ to_network: chainId,
+ custom_network: !POPULAR_NETWORK_CHAIN_IDS.has(chainId),
...analytics,
};
diff --git a/app/core/RPCMethods/lib/ethereum-chain-utils.test.ts b/app/core/RPCMethods/lib/ethereum-chain-utils.test.ts
index b879fe1532e..6504ced6ad4 100644
--- a/app/core/RPCMethods/lib/ethereum-chain-utils.test.ts
+++ b/app/core/RPCMethods/lib/ethereum-chain-utils.test.ts
@@ -39,12 +39,14 @@ describe('switchToNetwork', () => {
build: jest.fn().mockReturnValue(mockMetricsBuilderBuild),
});
+ const fromChainId = '0x89';
const mockHooks = {
getCaveat: jest
.fn()
.mockReturnValue({ value: getDefaultCaip25CaveatValue() }),
requestPermittedChainsPermissionIncrementalForOrigin: jest.fn(),
hasApprovalRequestsForOrigin: jest.fn(),
+ fromNetworkConfiguration: { chainId: fromChainId },
};
const chainId = '0x1';
@@ -82,6 +84,9 @@ describe('switchToNetwork', () => {
chain_id: '1',
source: 'Custom Network API',
symbol: 'ETH',
+ from_network: fromChainId,
+ to_network: chainId,
+ custom_network: false,
test: 'test',
});
expect(mockTrackEvent).toHaveBeenCalledWith(mockMetricsBuilderBuild);
diff --git a/app/util/address/index.test.ts b/app/util/address/index.test.ts
index 66af057d267..1d26437ac64 100644
--- a/app/util/address/index.test.ts
+++ b/app/util/address/index.test.ts
@@ -517,8 +517,8 @@ describe('getAddressAccountType', () => {
'Invalid address: undefined',
);
});
- it('should return QR if address is from a keyring type qr', () => {
- expect(getAddressAccountType(mockQrKeyringAddress)).toBe('QR');
+ it('should return QR Hardware if address is from a keyring type qr', () => {
+ expect(getAddressAccountType(mockQrKeyringAddress)).toBe('QR Hardware');
});
it('should return imported if address is from a keyring type simple', () => {
expect(getAddressAccountType(mockSimpleKeyringAddress)).toBe('Imported');
diff --git a/app/util/address/index.ts b/app/util/address/index.ts
index 7b1e2351462..39eed9903a5 100644
--- a/app/util/address/index.ts
+++ b/app/util/address/index.ts
@@ -462,7 +462,7 @@ export function getAddressAccountType(address: string) {
if (targetKeyring) {
switch (targetKeyring.type) {
case ExtendedKeyringTypes.qr:
- return 'QR';
+ return 'QR Hardware';
case ExtendedKeyringTypes.simple:
return 'Imported';
case ExtendedKeyringTypes.ledger: