diff --git a/app/components/UI/Navbar/index.js b/app/components/UI/Navbar/index.js index 8c8d941b7bb..435f30d5fe3 100644 --- a/app/components/UI/Navbar/index.js +++ b/app/components/UI/Navbar/index.js @@ -63,6 +63,7 @@ import { toChecksumHexAddress } from '@metamask/controller-utils'; ///: BEGIN:ONLY_INCLUDE_IF(keyring-snaps) import { isBtcAccount } from '../../../core/Multichain/utils'; ///: END:ONLY_INCLUDE_IF +import { withMetaMetrics } from '../Stake/utils/metaMetrics/withMetaMetrics'; const trackEvent = (event, params = {}) => { MetaMetrics.getInstance().trackEvent(event); @@ -1949,16 +1950,23 @@ export const getSettingsNavigationOptions = (title, themeColors) => { * @param {String} title - Navbar Title. * @param {NavigationProp} navigation Navigation object returned from useNavigation hook. * @param {ThemeColors} themeColors theme.colors returned from useStyles hook. - * @param {{ backgroundColor?: string, hasCancelButton?: boolean, hasBackButton?: boolean }} [options] - Optional options for navbar. + * @param {{ backgroundColor?: string, hasCancelButton?: boolean, hasBackButton?: boolean }} [navBarOptions] - Optional navbar options. + * @param {{ cancelButtonEvent?: { event: IMetaMetricsEvent, properties: Record }, backButtonEvent?: { event: IMetaMetricsEvent, properties: Record} }} [metricsOptions] - Optional metrics options. * @returns Staking Navbar Component. */ -export function getStakingNavbar(title, navigation, themeColors, options) { - const { hasBackButton = true, hasCancelButton = true } = options ?? {}; +export function getStakingNavbar( + title, + navigation, + themeColors, + navBarOptions, + metricsOptions, +) { + const { hasBackButton = true, hasCancelButton = true } = navBarOptions ?? {}; const innerStyles = StyleSheet.create({ headerStyle: { backgroundColor: - options?.backgroundColor ?? themeColors.background.default, + navBarOptions?.backgroundColor ?? themeColors.background.default, shadowOffset: null, }, headerLeft: { @@ -1978,6 +1986,28 @@ export function getStakingNavbar(title, navigation, themeColors, options) { navigation.goBack(); } + function handleBackPress() { + if (metricsOptions?.backButtonEvent) { + withMetaMetrics(navigationPop, { + event: metricsOptions.backButtonEvent.event, + properties: metricsOptions.backButtonEvent.properties, + }); + } else { + navigationPop(); + } + } + + function handleCancelPress() { + if (metricsOptions?.cancelButtonEvent) { + withMetaMetrics(navigationPop, { + event: metricsOptions.cancelButtonEvent.event, + properties: metricsOptions.cancelButtonEvent.properties, + }); + } else { + navigationPop(); + } + } + return { headerTitle: () => ( @@ -1990,7 +2020,7 @@ export function getStakingNavbar(title, navigation, themeColors, options) { ) : ( @@ -1999,7 +2029,7 @@ export function getStakingNavbar(title, navigation, themeColors, options) { headerRight: () => hasCancelButton ? ( navigation.dangerouslyGetParent()?.pop()} + onPress={handleCancelPress} style={styles.closeButton} > diff --git a/app/components/UI/Stake/Views/StakeConfirmationView/StakeConfirmationView.tsx b/app/components/UI/Stake/Views/StakeConfirmationView/StakeConfirmationView.tsx index 0eb0e2ba8de..8a86705d0e8 100644 --- a/app/components/UI/Stake/Views/StakeConfirmationView/StakeConfirmationView.tsx +++ b/app/components/UI/Stake/Views/StakeConfirmationView/StakeConfirmationView.tsx @@ -13,6 +13,8 @@ import { strings } from '../../../../../../locales/i18n'; import { FooterButtonGroupActions } from '../../components/StakingConfirmation/ConfirmationFooter/FooterButtonGroup/FooterButtonGroup.types'; import UnstakingTimeCard from '../../components/StakingConfirmation/UnstakeTimeCard/UnstakeTimeCard'; import { ScrollView } from 'react-native-gesture-handler'; +import { MetaMetricsEvents } from '../../../../hooks/useMetrics'; +import { EVENT_LOCATIONS, EVENT_PROVIDERS } from '../../constants/events'; const MOCK_STAKING_CONTRACT_NAME = 'MM Pooled Staking'; @@ -23,10 +25,24 @@ const StakeConfirmationView = ({ route }: StakeConfirmationViewProps) => { useEffect(() => { navigation.setOptions( - getStakingNavbar(strings('stake.stake'), navigation, theme.colors, { - backgroundColor: theme.colors.background.alternative, - hasCancelButton: false, - }), + getStakingNavbar( + strings('stake.stake'), + navigation, + theme.colors, + { + backgroundColor: theme.colors.background.alternative, + hasCancelButton: false, + }, + { + backButtonEvent: { + event: MetaMetricsEvents.STAKE_CONFIRMATION_BACK_CLICKED, + properties: { + selected_provider: EVENT_PROVIDERS.CONSENSYS, + location: EVENT_LOCATIONS.STAKE_CONFIRMATION_VIEW, + }, + }, + }, + ), ); }, [navigation, theme.colors]); diff --git a/app/components/UI/Stake/Views/StakeInputView/StakeInputView.test.tsx b/app/components/UI/Stake/Views/StakeInputView/StakeInputView.test.tsx index 650343c3958..559c068e427 100644 --- a/app/components/UI/Stake/Views/StakeInputView/StakeInputView.test.tsx +++ b/app/components/UI/Stake/Views/StakeInputView/StakeInputView.test.tsx @@ -246,6 +246,8 @@ describe('StakeInputView', () => { annualRewardRate: '2.5%', annualRewardsETH: '0.00938 ETH', annualRewardsFiat: '18.75 USD', + estimatedGasFee: '0.25', + estimatedGasFeePercentage: '66%', }, }); }); diff --git a/app/components/UI/Stake/Views/StakeInputView/StakeInputView.tsx b/app/components/UI/Stake/Views/StakeInputView/StakeInputView.tsx index ead9d7016c8..3ae652a718b 100644 --- a/app/components/UI/Stake/Views/StakeInputView/StakeInputView.tsx +++ b/app/components/UI/Stake/Views/StakeInputView/StakeInputView.tsx @@ -20,6 +20,8 @@ import useStakingInputHandlers from '../../hooks/useStakingInput'; import InputDisplay from '../../components/InputDisplay'; import { MetaMetricsEvents, useMetrics } from '../../../../hooks/useMetrics'; import { withMetaMetrics } from '../../utils/metaMetrics/withMetaMetrics'; +import { formatEther } from 'ethers/lib/utils'; +import { EVENT_PROVIDERS, EVENT_LOCATIONS } from '../../constants/events'; const StakeInputView = () => { const title = strings('stake.stake_eth'); @@ -49,6 +51,8 @@ const StakeInputView = () => { handleMax, balanceValue, isHighGasCostImpact, + getDepositTxGasPercentage, + estimatedGasFeeWei, isLoadingStakingGasFee, } = useStakingInputHandlers(); @@ -60,6 +64,21 @@ const StakeInputView = () => { const handleStakePress = useCallback(() => { if (isHighGasCostImpact()) { + trackEvent( + createEventBuilder( + MetaMetricsEvents.STAKE_GAS_COST_IMPACT_WARNING_TRIGGERED, + ) + .addProperties({ + selected_provider: EVENT_PROVIDERS.CONSENSYS, + location: EVENT_LOCATIONS.STAKE_INPUT_VIEW, + tokens_to_stake_native_value: amountEth, + tokens_to_stake_usd_value: fiatAmount, + estimated_gas_fee: formatEther(estimatedGasFeeWei.toString()), + estimated_gas_percentage_of_deposit: `${getDepositTxGasPercentage()}%`, + }) + .build(), + ); + navigation.navigate('StakeModals', { screen: Routes.STAKING.MODALS.GAS_IMPACT, params: { @@ -68,6 +87,8 @@ const StakeInputView = () => { annualRewardsETH, annualRewardsFiat, annualRewardRate, + estimatedGasFee: formatEther(estimatedGasFeeWei.toString()), + estimatedGasFeePercentage: `${getDepositTxGasPercentage()}%`, }, }); return; @@ -86,7 +107,7 @@ const StakeInputView = () => { trackEvent( createEventBuilder(MetaMetricsEvents.REVIEW_STAKE_BUTTON_CLICKED) .addProperties({ - selected_provider: 'consensys', + selected_provider: EVENT_PROVIDERS.CONSENSYS, tokens_to_stake_native_value: amountEth, tokens_to_stake_usd_value: fiatAmount, }) @@ -103,6 +124,8 @@ const StakeInputView = () => { trackEvent, createEventBuilder, amountEth, + estimatedGasFeeWei, + getDepositTxGasPercentage, ]); const handleMaxButtonPress = () => { @@ -124,9 +147,23 @@ const StakeInputView = () => { useEffect(() => { navigation.setOptions( - getStakingNavbar(title, navigation, theme.colors, { - hasBackButton: false, - }), + getStakingNavbar( + title, + navigation, + theme.colors, + { + hasBackButton: false, + }, + { + cancelButtonEvent: { + event: MetaMetricsEvents.STAKE_CANCEL_CLICKED, + properties: { + selected_provider: EVENT_PROVIDERS.CONSENSYS, + location: EVENT_LOCATIONS.STAKE_INPUT_VIEW, + }, + }, + }, + ), ); }, [navigation, theme.colors, title]); @@ -148,9 +185,9 @@ const StakeInputView = () => { handleCurrencySwitch={withMetaMetrics(handleCurrencySwitch, { event: MetaMetricsEvents.STAKE_INPUT_CURRENCY_SWITCH_CLICKED, properties: { - selected_provider: 'consensys', + selected_provider: EVENT_PROVIDERS.CONSENSYS, text: 'Currency Switch Trigger', - location: 'Stake Input View', + location: EVENT_LOCATIONS.STAKE_INPUT_VIEW, // We want to track the currency switching to. Not the current currency. currency_type: isEth ? 'fiat' : 'native', }, @@ -163,9 +200,9 @@ const StakeInputView = () => { onIconPress={withMetaMetrics(navigateToLearnMoreModal, { event: MetaMetricsEvents.TOOLTIP_OPENED, properties: { - selected_provider: 'consensys', + selected_provider: EVENT_PROVIDERS.CONSENSYS, text: 'Tooltip Opened', - location: 'Stake Input View', + location: EVENT_LOCATIONS.STAKE_INPUT_VIEW, tooltip_name: 'MetaMask Pool Estimated Rewards', }, })} @@ -178,7 +215,7 @@ const StakeInputView = () => { withMetaMetrics(handleQuickAmountPress, { event: MetaMetricsEvents.STAKE_INPUT_QUICK_AMOUNT_CLICKED, properties: { - location: 'StakeInputView', + location: EVENT_LOCATIONS.STAKE_INPUT_VIEW, amount: value, // onMaxPress is called instead when it's defined and the max is clicked. is_max: false, @@ -189,7 +226,7 @@ const StakeInputView = () => { onMaxPress={withMetaMetrics(handleMaxButtonPress, { event: MetaMetricsEvents.STAKE_INPUT_QUICK_AMOUNT_CLICKED, properties: { - location: 'StakeInputView', + location: EVENT_LOCATIONS.STAKE_INPUT_VIEW, is_max: true, mode: isEth ? 'native' : 'fiat', }, diff --git a/app/components/UI/Stake/Views/UnstakeConfirmationView/UnstakeConfirmationView.tsx b/app/components/UI/Stake/Views/UnstakeConfirmationView/UnstakeConfirmationView.tsx index 1a78eab35e5..4c3a94084a3 100644 --- a/app/components/UI/Stake/Views/UnstakeConfirmationView/UnstakeConfirmationView.tsx +++ b/app/components/UI/Stake/Views/UnstakeConfirmationView/UnstakeConfirmationView.tsx @@ -11,6 +11,8 @@ import TokenValueStack from '../../components/StakingConfirmation/TokenValueStac import AccountCard from '../../components/StakingConfirmation/AccountCard/AccountCard'; import ConfirmationFooter from '../../components/StakingConfirmation/ConfirmationFooter/ConfirmationFooter'; import { FooterButtonGroupActions } from '../../components/StakingConfirmation/ConfirmationFooter/FooterButtonGroup/FooterButtonGroup.types'; +import { MetaMetricsEvents } from '../../../../hooks/useMetrics'; +import { EVENT_LOCATIONS, EVENT_PROVIDERS } from '../../constants/events'; const MOCK_STAKING_CONTRACT_NAME = 'MM Pooled Staking'; @@ -21,10 +23,24 @@ const UnstakeConfirmationView = ({ route }: UnstakeConfirmationViewProps) => { useEffect(() => { navigation.setOptions( - getStakingNavbar(strings('stake.unstake'), navigation, theme.colors, { - backgroundColor: theme.colors.background.alternative, - hasCancelButton: false, - }), + getStakingNavbar( + strings('stake.unstake'), + navigation, + theme.colors, + { + backgroundColor: theme.colors.background.alternative, + hasCancelButton: false, + }, + { + backButtonEvent: { + event: MetaMetricsEvents.UNSTAKE_CONFIRMATION_BACK_CLICKED, + properties: { + selected_provider: EVENT_PROVIDERS.CONSENSYS, + location: EVENT_LOCATIONS.UNSTAKE_CONFIRMATION_VIEW, + }, + }, + }, + ), ); }, [navigation, theme.colors]); diff --git a/app/components/UI/Stake/Views/UnstakeInputView/UnstakeInputView.tsx b/app/components/UI/Stake/Views/UnstakeInputView/UnstakeInputView.tsx index b27bbbe07e0..1786301fb0e 100644 --- a/app/components/UI/Stake/Views/UnstakeInputView/UnstakeInputView.tsx +++ b/app/components/UI/Stake/Views/UnstakeInputView/UnstakeInputView.tsx @@ -20,6 +20,7 @@ import Routes from '../../../../../constants/navigation/Routes'; import { MetaMetricsEvents, useMetrics } from '../../../../hooks/useMetrics'; import useUnstakingInputHandlers from '../../hooks/useUnstakingInput'; import { withMetaMetrics } from '../../utils/metaMetrics/withMetaMetrics'; +import { EVENT_LOCATIONS, EVENT_PROVIDERS } from '../../constants/events'; const UnstakeInputView = () => { const title = strings('stake.unstake_eth'); @@ -54,9 +55,23 @@ const UnstakeInputView = () => { useEffect(() => { navigation.setOptions( - getStakingNavbar(title, navigation, theme.colors, { - hasBackButton: false, - }), + getStakingNavbar( + title, + navigation, + theme.colors, + { + hasBackButton: false, + }, + { + cancelButtonEvent: { + event: MetaMetricsEvents.UNSTAKE_CANCEL_CLICKED, + properties: { + selected_provider: EVENT_PROVIDERS.CONSENSYS, + location: EVENT_LOCATIONS.UNSTAKE_INPUT_VIEW, + }, + }, + }, + ), ); }, [navigation, theme.colors, title]); @@ -71,7 +86,7 @@ const UnstakeInputView = () => { trackEvent( createEventBuilder(MetaMetricsEvents.REVIEW_UNSTAKE_BUTTON_CLICKED) .addProperties({ - selected_provider: 'consensys', + selected_provider: EVENT_PROVIDERS.CONSENSYS, tokens_to_stake_native_value: amountEth, tokens_to_stake_usd_value: fiatAmount, }) @@ -100,9 +115,9 @@ const UnstakeInputView = () => { handleCurrencySwitch={withMetaMetrics(handleCurrencySwitch, { event: MetaMetricsEvents.UNSTAKE_INPUT_CURRENCY_SWITCH_CLICKED, properties: { - selected_provider: 'consensys', + selected_provider: EVENT_PROVIDERS.CONSENSYS, text: 'Currency Switch Trigger', - location: 'Unstake Input View', + location: EVENT_LOCATIONS.UNSTAKE_INPUT_VIEW, // We want to track the currency switching to. Not the current currency. currency_type: isEth ? 'fiat' : 'native', }, @@ -116,7 +131,7 @@ const UnstakeInputView = () => { withMetaMetrics(handleQuickAmountPress, { event: MetaMetricsEvents.UNSTAKE_INPUT_QUICK_AMOUNT_CLICKED, properties: { - location: 'UnstakeInputView', + location: EVENT_LOCATIONS.UNSTAKE_INPUT_VIEW, amount: value, is_max: value === 1, mode: isEth ? 'native' : 'fiat', diff --git a/app/components/UI/Stake/components/GasImpactModal/GasImpactModal.test.tsx b/app/components/UI/Stake/components/GasImpactModal/GasImpactModal.test.tsx index 7018981373c..f6eca2e2f22 100644 --- a/app/components/UI/Stake/components/GasImpactModal/GasImpactModal.test.tsx +++ b/app/components/UI/Stake/components/GasImpactModal/GasImpactModal.test.tsx @@ -29,6 +29,8 @@ const props: GasImpactModalProps = { annualRewardRate: '2.5%', annualRewardsETH: '2.5 ETH', annualRewardsFiat: '$5000', + estimatedGasFee: '0.009171428571428572', + estimatedGasFeePercentage: '35%', }, name: 'params', }, diff --git a/app/components/UI/Stake/components/GasImpactModal/GasImpactModal.types.ts b/app/components/UI/Stake/components/GasImpactModal/GasImpactModal.types.ts index a00204cfbee..7a1d9441615 100644 --- a/app/components/UI/Stake/components/GasImpactModal/GasImpactModal.types.ts +++ b/app/components/UI/Stake/components/GasImpactModal/GasImpactModal.types.ts @@ -6,6 +6,8 @@ interface GasImpactModalRouteParams { annualRewardsETH: string; annualRewardsFiat: string; annualRewardRate: string; + estimatedGasFee: string; + estimatedGasFeePercentage: string; } export interface GasImpactModalProps { diff --git a/app/components/UI/Stake/components/GasImpactModal/index.tsx b/app/components/UI/Stake/components/GasImpactModal/index.tsx index 4e348f75426..dabbaca7c5d 100644 --- a/app/components/UI/Stake/components/GasImpactModal/index.tsx +++ b/app/components/UI/Stake/components/GasImpactModal/index.tsx @@ -1,4 +1,4 @@ -import React, { useRef } from 'react'; +import React, { useCallback, useRef } from 'react'; import BottomSheet, { BottomSheetRef, } from '../../../../../component-library/components/BottomSheets/BottomSheet'; @@ -22,27 +22,65 @@ import { useNavigation } from '@react-navigation/native'; import Routes from '../../../../../constants/navigation/Routes'; import { GasImpactModalProps } from './GasImpactModal.types'; import { strings } from '../../../../../../locales/i18n'; +import { MetaMetricsEvents, useMetrics } from '../../../../hooks/useMetrics'; +import { formatEther } from 'ethers/lib/utils'; +import { EVENT_LOCATIONS, EVENT_PROVIDERS } from '../../constants/events'; const GasImpactModal = ({ route }: GasImpactModalProps) => { const { styles } = useStyles(styleSheet, {}); const { navigate } = useNavigation(); + const { trackEvent, createEventBuilder } = useMetrics(); + const sheetRef = useRef(null); + const { + amountWei, + annualRewardRate, + annualRewardsFiat, + annualRewardsETH, + amountFiat, + estimatedGasFee, + estimatedGasFeePercentage, + } = route.params; + + const metricsEvent = useCallback( + ( + eventName: + | typeof MetaMetricsEvents.STAKE_GAS_COST_IMPACT_CANCEL_CLICKED + | typeof MetaMetricsEvents.STAKE_GAS_COST_IMPACT_PROCEEDED_CLICKED, + ) => { + trackEvent( + createEventBuilder(eventName) + .addProperties({ + selected_provider: EVENT_PROVIDERS.CONSENSYS, + location: EVENT_LOCATIONS.GAS_IMPACT_MODAL, + tokens_to_stake_native_value: formatEther(amountWei), + tokens_to_stake_usd_value: amountFiat, + estimated_gas_fee: estimatedGasFee, + estimated_gas_percentage_of_deposit: estimatedGasFeePercentage, + }) + .build(), + ); + }, + [ + amountFiat, + amountWei, + createEventBuilder, + estimatedGasFee, + estimatedGasFeePercentage, + trackEvent, + ], + ); + const handleClose = () => { + metricsEvent(MetaMetricsEvents.STAKE_GAS_COST_IMPACT_CANCEL_CLICKED); sheetRef.current?.onCloseBottomSheet(); }; const handleNavigateToStakeReviewScreen = () => { - const { - amountWei, - annualRewardRate, - annualRewardsFiat, - annualRewardsETH, - amountFiat, - } = route.params; - + metricsEvent(MetaMetricsEvents.STAKE_GAS_COST_IMPACT_PROCEEDED_CLICKED); navigate('StakeScreens', { screen: Routes.STAKING.STAKE_CONFIRMATION, params: { diff --git a/app/components/UI/Stake/components/LearnMoreModal/index.tsx b/app/components/UI/Stake/components/LearnMoreModal/index.tsx index eb321f8d238..5d6d4b6677d 100644 --- a/app/components/UI/Stake/components/LearnMoreModal/index.tsx +++ b/app/components/UI/Stake/components/LearnMoreModal/index.tsx @@ -19,6 +19,7 @@ import { POOLED_STAKING_FAQ_URL } from '../../constants'; import createLearnMoreModalStyles from './LearnMoreModal.styles'; import { MetaMetricsEvents } from '../../../../hooks/useMetrics'; import { withMetaMetrics } from '../../utils/metaMetrics/withMetaMetrics'; +import { EVENT_LOCATIONS, EVENT_PROVIDERS } from '../../constants/events'; const styles = createLearnMoreModalStyles(); @@ -99,9 +100,9 @@ const LearnMoreModal = () => { onPress={withMetaMetrics(handleLearnMoreBrowserRedirect, { event: MetaMetricsEvents.STAKE_LEARN_MORE_CLICKED, properties: { - selected_provider: 'consensys', + selected_provider: EVENT_PROVIDERS.CONSENSYS, text: 'Learn More', - location: 'Learn More Modal', + location: EVENT_LOCATIONS.LEARN_MORE_MODAL, }, })} label={strings('stake.learn_more')} diff --git a/app/components/UI/Stake/components/StakeButton/index.tsx b/app/components/UI/Stake/components/StakeButton/index.tsx index ca578bef3bd..f197ed15dfc 100644 --- a/app/components/UI/Stake/components/StakeButton/index.tsx +++ b/app/components/UI/Stake/components/StakeButton/index.tsx @@ -25,6 +25,7 @@ import { strings } from '../../../../../../locales/i18n'; import { RootState } from '../../../../../reducers'; import useStakingEligibility from '../../hooks/useStakingEligibility'; import { StakeSDKProvider } from '../../sdk/stakeSdkProvider'; +import { EVENT_LOCATIONS } from '../../constants/events'; interface StakeButtonProps { asset: TokenI; @@ -69,7 +70,7 @@ const StakeButtonContent = ({ asset }: StakeButtonProps) => { createEventBuilder(MetaMetricsEvents.STAKE_BUTTON_CLICKED) .addProperties({ chain_id: getDecimalChainId(chainId), - location: 'Home Screen', + location: EVENT_LOCATIONS.HOME_SCREEN, text: 'Stake', token_symbol: asset.symbol, url: AppConstants.STAKE.URL, diff --git a/app/components/UI/Stake/components/StakingBalance/StakingBalance.tsx b/app/components/UI/Stake/components/StakingBalance/StakingBalance.tsx index 1d41646bc3c..ce12aec48e7 100644 --- a/app/components/UI/Stake/components/StakingBalance/StakingBalance.tsx +++ b/app/components/UI/Stake/components/StakingBalance/StakingBalance.tsx @@ -1,4 +1,4 @@ -import React, { useMemo } from 'react'; +import React, { useEffect, useMemo, useState } from 'react'; import Badge, { BadgeVariant, } from '../../../../../component-library/components/Badges/Badge'; @@ -44,6 +44,8 @@ import useBalance from '../../hooks/useBalance'; import { NetworkBadgeSource } from '../../../AssetOverview/Balance/Balance'; import { selectChainId } from '../../../../../selectors/networkController'; import SkeletonPlaceholder from 'react-native-skeleton-placeholder'; +import { MetaMetricsEvents, useMetrics } from '../../../../hooks/useMetrics'; +import { EVENT_LOCATIONS, EVENT_PROVIDERS } from '../../constants/events'; export interface StakingBalanceProps { asset: TokenI; @@ -51,6 +53,12 @@ export interface StakingBalanceProps { const StakingBalanceContent = ({ asset }: StakingBalanceProps) => { const { styles } = useStyles(styleSheet, {}); + + const [ + hasSentViewingStakingRewardsMetric, + setHasSentViewingStakingRewardsMetric, + ] = useState(false); + const chainId = useSelector(selectChainId); const networkName = useSelector(selectNetworkName); @@ -58,6 +66,8 @@ const StakingBalanceContent = ({ asset }: StakingBalanceProps) => { const { isStakingSupportedChain } = useStakingChain(); + const { trackEvent, createEventBuilder } = useMetrics(); + const { pooledStakesData, exchangeRate, @@ -92,6 +102,28 @@ const StakingBalanceContent = ({ asset }: StakingBalanceProps) => { const hasClaimableEth = !!Number(claimableEth); + useEffect(() => { + if (hasStakedPositions && !hasSentViewingStakingRewardsMetric) { + trackEvent( + createEventBuilder( + MetaMetricsEvents.VISITED_ETH_OVERVIEW_WITH_STAKED_POSITIONS, + ) + .addProperties({ + selected_provider: EVENT_PROVIDERS.CONSENSYS, + location: EVENT_LOCATIONS.STAKING_BALANCE, + }) + .build(), + ); + + setHasSentViewingStakingRewardsMetric(true); + } + }, [ + createEventBuilder, + hasSentViewingStakingRewardsMetric, + hasStakedPositions, + trackEvent, + ]); + if (!isStakingSupportedChain) { return <>; } diff --git a/app/components/UI/Stake/components/StakingBalance/StakingBanners/ClaimBanner/ClaimBanner.tsx b/app/components/UI/Stake/components/StakingBalance/StakingBanners/ClaimBanner/ClaimBanner.tsx index 3a13ee588ca..e99f93880c0 100644 --- a/app/components/UI/Stake/components/StakingBalance/StakingBanners/ClaimBanner/ClaimBanner.tsx +++ b/app/components/UI/Stake/components/StakingBalance/StakingBanners/ClaimBanner/ClaimBanner.tsx @@ -23,6 +23,7 @@ import { MetaMetricsEvents, useMetrics, } from '../../../../../../hooks/useMetrics'; +import { EVENT_LOCATIONS } from '../../../../constants/events'; type StakeBannerProps = Pick & { claimableAmount: string; @@ -48,7 +49,7 @@ const ClaimBanner = ({ claimableAmount, style }: StakeBannerProps) => { trackEvent( createEventBuilder(MetaMetricsEvents.STAKE_CLAIM_BUTTON_CLICKED) .addProperties({ - location: 'Token Details', + location: EVENT_LOCATIONS.TOKEN_DETAILS, }) .build(), ); diff --git a/app/components/UI/Stake/components/StakingBalance/StakingButtons/StakingButtons.tsx b/app/components/UI/Stake/components/StakingBalance/StakingButtons/StakingButtons.tsx index e6245f6506a..a87ea82fc06 100644 --- a/app/components/UI/Stake/components/StakingBalance/StakingButtons/StakingButtons.tsx +++ b/app/components/UI/Stake/components/StakingBalance/StakingButtons/StakingButtons.tsx @@ -11,6 +11,7 @@ import Routes from '../../../../../../constants/navigation/Routes'; import { useMetrics, MetaMetricsEvents } from '../../../../../hooks/useMetrics'; import { useSelector } from 'react-redux'; import { selectChainId } from '../../../../../../selectors/networkController'; +import { EVENT_LOCATIONS } from '../../../constants/events'; interface StakingButtonsProps extends Pick { hasStakedPositions: boolean; @@ -34,7 +35,7 @@ const StakingButtons = ({ trackEvent( createEventBuilder(MetaMetricsEvents.STAKE_WITHDRAW_BUTTON_CLICKED) .addProperties({ - location: 'Token Details', + location: EVENT_LOCATIONS.TOKEN_DETAILS, text: 'Unstake', token_symbol: 'ETH', chain_id: chainId, @@ -48,7 +49,7 @@ const StakingButtons = ({ trackEvent( createEventBuilder(MetaMetricsEvents.STAKE_BUTTON_CLICKED) .addProperties({ - location: 'Token Details', + location: EVENT_LOCATIONS.TOKEN_DETAILS, text: 'Stake', token_symbol: 'ETH', chain_id: chainId, diff --git a/app/components/UI/Stake/components/StakingBalance/StakingCta/StakingCta.tsx b/app/components/UI/Stake/components/StakingBalance/StakingCta/StakingCta.tsx index a6e2f4efca0..d292873ef5e 100644 --- a/app/components/UI/Stake/components/StakingBalance/StakingCta/StakingCta.tsx +++ b/app/components/UI/Stake/components/StakingBalance/StakingCta/StakingCta.tsx @@ -13,6 +13,7 @@ import { strings } from '../../../../../../../locales/i18n'; import { useNavigation } from '@react-navigation/native'; import Routes from '../../../../../../constants/navigation/Routes'; import { MetaMetricsEvents, useMetrics } from '../../../../../hooks/useMetrics'; +import { EVENT_LOCATIONS, EVENT_PROVIDERS } from '../../../constants/events'; interface StakingCtaProps extends Pick { estimatedRewardRate: string; @@ -30,9 +31,9 @@ const StakingCta = ({ estimatedRewardRate, style }: StakingCtaProps) => { trackEvent( createEventBuilder(MetaMetricsEvents.STAKE_LEARN_MORE_CLICKED) .addProperties({ - selected_provider: 'consensys', + selected_provider: EVENT_PROVIDERS.CONSENSYS, text: 'Learn More', - location: 'Token Details', + location: EVENT_LOCATIONS.TOKEN_DETAILS, }) .build(), ); diff --git a/app/components/UI/Stake/components/StakingConfirmation/ConfirmationFooter/FooterButtonGroup/FooterButtonGroup.tsx b/app/components/UI/Stake/components/StakingConfirmation/ConfirmationFooter/FooterButtonGroup/FooterButtonGroup.tsx index c60c393fb18..98c3e8fc987 100644 --- a/app/components/UI/Stake/components/StakingConfirmation/ConfirmationFooter/FooterButtonGroup/FooterButtonGroup.tsx +++ b/app/components/UI/Stake/components/StakingConfirmation/ConfirmationFooter/FooterButtonGroup/FooterButtonGroup.tsx @@ -1,4 +1,4 @@ -import React, { useCallback, useState } from 'react'; +import React, { useCallback, useMemo, useState } from 'react'; import { useNavigation } from '@react-navigation/native'; import { View } from 'react-native'; import { strings } from '../../../../../../../../locales/i18n'; @@ -24,6 +24,36 @@ import { import Routes from '../../../../../../../constants/navigation/Routes'; import usePoolStakedUnstake from '../../../../hooks/usePoolStakedUnstake'; import usePooledStakes from '../../../../hooks/usePooledStakes'; +import { + MetaMetricsEvents, + useMetrics, +} from '../../../../../../hooks/useMetrics'; +import { IMetaMetricsEvent } from '../../../../../../../core/Analytics'; +import { formatEther } from 'ethers/lib/utils'; +import { EVENT_LOCATIONS, EVENT_PROVIDERS } from '../../../../constants/events'; + +const STAKING_TX_METRIC_EVENTS: Record< + FooterButtonGroupActions, + Record< + 'APPROVED' | 'REJECTED' | 'CONFIRMED' | 'FAILED' | 'SUBMITTED', + IMetaMetricsEvent + > +> = { + STAKE: { + APPROVED: MetaMetricsEvents.STAKE_TRANSACTION_APPROVED, + REJECTED: MetaMetricsEvents.STAKE_TRANSACTION_REJECTED, + CONFIRMED: MetaMetricsEvents.STAKE_TRANSACTION_CONFIRMED, + FAILED: MetaMetricsEvents.STAKE_TRANSACTION_FAILED, + SUBMITTED: MetaMetricsEvents.STAKE_TRANSACTION_SUBMITTED, + }, + UNSTAKE: { + APPROVED: MetaMetricsEvents.UNSTAKE_TRANSACTION_APPROVED, + REJECTED: MetaMetricsEvents.UNSTAKE_TRANSACTION_REJECTED, + CONFIRMED: MetaMetricsEvents.UNSTAKE_TRANSACTION_CONFIRMED, + FAILED: MetaMetricsEvents.UNSTAKE_TRANSACTION_FAILED, + SUBMITTED: MetaMetricsEvents.UNSTAKE_TRANSACTION_SUBMITTED, + }, +}; const FooterButtonGroup = ({ valueWei, action }: FooterButtonGroupProps) => { const { styles } = useStyles(styleSheet, {}); @@ -31,6 +61,8 @@ const FooterButtonGroup = ({ valueWei, action }: FooterButtonGroupProps) => { const navigation = useNavigation(); const { navigate } = navigation; + const { trackEvent, createEventBuilder } = useMetrics(); + const activeAccount = useSelector(selectSelectedInternalAccount); const { attemptDepositTransaction } = usePoolStakedDeposit(); @@ -40,13 +72,49 @@ const FooterButtonGroup = ({ valueWei, action }: FooterButtonGroupProps) => { const [didSubmitTransaction, setDidSubmitTransaction] = useState(false); + const isStaking = useMemo( + () => action === FooterButtonGroupActions.STAKE, + [action], + ); + + const submitTxMetaMetric = useCallback( + (txEventName: IMetaMetricsEvent) => { + const { STAKE_CONFIRMATION_VIEW, UNSTAKE_CONFIRMATION_VIEW } = + EVENT_LOCATIONS; + + const location = isStaking + ? STAKE_CONFIRMATION_VIEW + : UNSTAKE_CONFIRMATION_VIEW; + + return trackEvent( + createEventBuilder(txEventName) + .addProperties({ + selected_provider: EVENT_PROVIDERS.CONSENSYS, + location, + transaction_amount_eth: formatEther(valueWei), + }) + .build(), + ); + }, + [createEventBuilder, isStaking, trackEvent, valueWei], + ); + const listenForTransactionEvents = useCallback( (transactionId?: string) => { if (!transactionId) return; + Engine.controllerMessenger.subscribeOnceIf( + 'TransactionController:transactionApproved', + () => { + submitTxMetaMetric(STAKING_TX_METRIC_EVENTS[action].APPROVED); + }, + ({ transactionMeta }) => transactionMeta.id === transactionId, + ); + Engine.controllerMessenger.subscribeOnceIf( 'TransactionController:transactionSubmitted', () => { + submitTxMetaMetric(STAKING_TX_METRIC_EVENTS[action].SUBMITTED); setDidSubmitTransaction(false); navigate(Routes.TRANSACTIONS_VIEW); }, @@ -56,6 +124,7 @@ const FooterButtonGroup = ({ valueWei, action }: FooterButtonGroupProps) => { Engine.controllerMessenger.subscribeOnceIf( 'TransactionController:transactionFailed', () => { + submitTxMetaMetric(STAKING_TX_METRIC_EVENTS[action].FAILED); setDidSubmitTransaction(false); }, ({ transactionMeta }) => transactionMeta.id === transactionId, @@ -64,6 +133,7 @@ const FooterButtonGroup = ({ valueWei, action }: FooterButtonGroupProps) => { Engine.controllerMessenger.subscribeOnceIf( 'TransactionController:transactionRejected', () => { + submitTxMetaMetric(STAKING_TX_METRIC_EVENTS[action].REJECTED); setDidSubmitTransaction(false); }, ({ transactionMeta }) => transactionMeta.id === transactionId, @@ -72,12 +142,13 @@ const FooterButtonGroup = ({ valueWei, action }: FooterButtonGroupProps) => { Engine.controllerMessenger.subscribeOnceIf( 'TransactionController:transactionConfirmed', () => { + submitTxMetaMetric(STAKING_TX_METRIC_EVENTS[action].CONFIRMED); refreshPooledStakes(); }, (transactionMeta) => transactionMeta.id === transactionId, ); }, - [navigate, refreshPooledStakes], + [action, navigate, refreshPooledStakes, submitTxMetaMetric], ); const handleConfirmation = async () => { @@ -86,17 +157,36 @@ const FooterButtonGroup = ({ valueWei, action }: FooterButtonGroupProps) => { setDidSubmitTransaction(true); + const metricsEvent = { + name: isStaking + ? MetaMetricsEvents.STAKE_TRANSACTION_INITIATED + : MetaMetricsEvents.UNSTAKE_TRANSACTION_INITIATED, + location: isStaking + ? 'StakeConfirmationView' + : 'UnstakeConfirmationView', + }; + + trackEvent( + createEventBuilder(metricsEvent.name) + .addProperties({ + selected_provider: EVENT_PROVIDERS.CONSENSYS, + location: metricsEvent.location, + transaction_amount_eth: formatEther(valueWei), + }) + .build(), + ); + let transactionId: string | undefined; - if (action === FooterButtonGroupActions.STAKE) { + if (isStaking) { const txRes = await attemptDepositTransaction( valueWei, activeAccount.address, ); transactionId = txRes?.transactionMeta?.id; } - - if (action === FooterButtonGroupActions.UNSTAKE) { + // Unstaking + else { const txRes = await attemptUnstakeTransaction( valueWei, activeAccount.address, @@ -110,6 +200,26 @@ const FooterButtonGroup = ({ valueWei, action }: FooterButtonGroupProps) => { } }; + const handleCancelPress = () => { + const metricsEvent = { + name: isStaking + ? MetaMetricsEvents.STAKE_CANCEL_CLICKED + : MetaMetricsEvents.UNSTAKE_CANCEL_CLICKED, + location: isStaking ? 'StakeConfirmationView' : 'UnstakeConfirmationView', + }; + + trackEvent( + createEventBuilder(metricsEvent.name) + .addProperties({ + selected_provider: EVENT_PROVIDERS.CONSENSYS, + location: metricsEvent.location, + }) + .build(), + ); + + navigation.goBack(); + }; + return (