From ae62ef9137eb323685f226f5ff0aa9ba29375239 Mon Sep 17 00:00:00 2001 From: Matthew Wall Date: Tue, 11 Jun 2024 16:57:34 -0400 Subject: [PATCH] Add analytics (#5805) * add analytics * clean up analytics and add bridging and native values * add native values and cleanup * fix lint * fix cast * code review changes --- .../screens/Swap/components/FlipButton.tsx | 11 ++- .../screens/Swap/components/GasButton.tsx | 28 ++++--- .../screens/Swap/components/GasPanel.tsx | 5 +- .../screens/Swap/components/SearchInput.tsx | 22 ++---- .../components/TokenList/ChainSelection.tsx | 8 ++ .../TokenList/TokenToBuySection.tsx | 12 +++ .../components/TokenList/TokenToSellList.tsx | 12 +++ .../screens/Swap/hooks/useSelectedGas.ts | 16 ++-- .../Swap/hooks/useSwapInputsController.ts | 8 ++ .../screens/Swap/providers/swap-provider.tsx | 44 +++++++++-- src/__swaps__/utils/meteorology.ts | 5 +- src/analytics/event.ts | 79 ++++++++++++++++++- 12 files changed, 204 insertions(+), 46 deletions(-) diff --git a/src/__swaps__/screens/Swap/components/FlipButton.tsx b/src/__swaps__/screens/Swap/components/FlipButton.tsx index c43134dd4e9..53cdc34656a 100644 --- a/src/__swaps__/screens/Swap/components/FlipButton.tsx +++ b/src/__swaps__/screens/Swap/components/FlipButton.tsx @@ -12,20 +12,27 @@ import { AnimatedBlurView } from '@/__swaps__/screens/Swap/components/AnimatedBl import { useSwapContext } from '@/__swaps__/screens/Swap/providers/swap-provider'; import { TIMING_CONFIGS } from '@/components/animations/animationConfigs'; import { SwapAssetType } from '@/__swaps__/types/swap'; +import { analyticsV2 } from '@/analytics'; export const FlipButton = () => { const { isDarkMode } = useColorMode(); - const { AnimatedSwapStyles, internalSelectedInputAsset, internalSelectedOutputAsset, setAsset } = useSwapContext(); + const { AnimatedSwapStyles, internalSelectedInputAsset, internalSelectedOutputAsset, setAsset, SwapInputController } = useSwapContext(); const handleSwapAssets = useCallback(() => { if (internalSelectedInputAsset.value && internalSelectedOutputAsset.value) { const assetTypeToSet = SwapAssetType.outputAsset; const assetToSet = internalSelectedInputAsset.value; + analyticsV2.track(analyticsV2.event.swapsFlippedAssets, { + inputAmount: SwapInputController.inputValues.value.inputAmount, + previousInputAsset: internalSelectedInputAsset.value, + previousOutputAsset: internalSelectedOutputAsset.value, + }); + setAsset({ type: assetTypeToSet, asset: assetToSet }); } - }, [internalSelectedInputAsset, internalSelectedOutputAsset, /* lastTypedInput, */ setAsset]); + }, [SwapInputController.inputValues, internalSelectedInputAsset, internalSelectedOutputAsset, setAsset]); const flipButtonInnerStyles = useAnimatedStyle(() => { return { diff --git a/src/__swaps__/screens/Swap/components/GasButton.tsx b/src/__swaps__/screens/Swap/components/GasButton.tsx index 76d45315560..71a31c8ea88 100644 --- a/src/__swaps__/screens/Swap/components/GasButton.tsx +++ b/src/__swaps__/screens/Swap/components/GasButton.tsx @@ -1,5 +1,6 @@ import { ChainId } from '@/__swaps__/types/chains'; import { weiToGwei } from '@/__swaps__/utils/ethereum'; +import { OnPressMenuItemEventObject } from 'react-native-ios-context-menu'; import { getCachedCurrentBaseFee, useMeteorologySuggestions } from '@/__swaps__/utils/meteorology'; import { add } from '@/__swaps__/utils/numbers'; import { ContextMenu } from '@/components/context-menu'; @@ -8,7 +9,7 @@ import ContextMenuButton from '@/components/native-context-menu/contextMenu'; import { Box, Inline, Text, TextIcon, useColorMode, useForegroundColor } from '@/design-system'; import { IS_ANDROID } from '@/env'; import * as i18n from '@/languages'; -import { useSwapsStore } from '@/state/swaps/swapsStore'; +import { swapsStore } from '@/state/swaps/swapsStore'; import { gasUtils } from '@/utils'; import React, { ReactNode, useCallback, useMemo } from 'react'; import { StyleSheet } from 'react-native'; @@ -16,7 +17,8 @@ import { runOnJS, runOnUI } from 'react-native-reanimated'; import { ETH_COLOR, ETH_COLOR_DARK, THICK_BORDER_WIDTH } from '../constants'; import { formatNumber } from '../hooks/formatNumber'; import { GasSettings, useCustomGasSettings } from '../hooks/useCustomGas'; -import { GasSpeed, setSelectedGasSpeed, useSelectedGas, useSelectedGasSpeed } from '../hooks/useSelectedGas'; +import { GasSpeed } from '@/__swaps__/types/gas'; +import { setSelectedGasSpeed, useSelectedGas, useSelectedGasSpeed } from '../hooks/useSelectedGas'; import { useSwapContext } from '../providers/swap-provider'; import { EstimatedSwapGasFee } from './EstimatedSwapGasFee'; import { GestureHandlerV1Button } from './GestureHandlerV1Button'; @@ -26,7 +28,7 @@ const { GAS_ICONS } = gasUtils; const GAS_BUTTON_HIT_SLOP = 16; function EstimatedGasFee() { - const chainId = useSwapsStore(s => s.inputAsset?.chainId || ChainId.mainnet); + const chainId = swapsStore(s => s.inputAsset?.chainId || ChainId.mainnet); const gasSettings = useSelectedGas(chainId); return ( @@ -40,7 +42,7 @@ function EstimatedGasFee() { } function SelectedGas() { - const chainId = useSwapsStore(s => s.inputAsset?.chainId || ChainId.mainnet); + const chainId = swapsStore(s => s.inputAsset?.chainId || ChainId.mainnet); const selectedGasSpeed = useSelectedGasSpeed(chainId); return ( @@ -83,17 +85,16 @@ function keys(obj: Record | undefined) { const GasMenu = ({ backToReview = false, children }: { backToReview?: boolean; children: ReactNode }) => { const { SwapNavigation } = useSwapContext(); - const chainId = useSwapsStore(s => s.inputAsset?.chainId || ChainId.mainnet); + const chainId = swapsStore(s => s.inputAsset?.chainId || ChainId.mainnet); const metereologySuggestions = useMeteorologySuggestions({ chainId }); const customGasSettings = useCustomGasSettings(chainId); - const menuOptions = useMemo(() => [...keys(metereologySuggestions.data), 'custom'] as const, [metereologySuggestions.data]); + const menuOptions = useMemo(() => [...keys(metereologySuggestions.data), GasSpeed.CUSTOM] as GasSpeed[], [metereologySuggestions.data]); const handlePressSpeedOption = useCallback( (selectedGasSpeed: GasSpeed) => { setSelectedGasSpeed(chainId, selectedGasSpeed); - - if (selectedGasSpeed === 'custom') { + if (selectedGasSpeed === GasSpeed.CUSTOM) { runOnUI(SwapNavigation.handleShowGas)({ backToReview }); } }, @@ -101,12 +102,15 @@ const GasMenu = ({ backToReview = false, children }: { backToReview?: boolean; c ); const handlePressMenuItem = useCallback( - ({ nativeEvent: { actionKey } }: any) => handlePressSpeedOption(actionKey), + ({ nativeEvent: { actionKey } }: OnPressMenuItemEventObject) => handlePressSpeedOption(actionKey as GasSpeed), [handlePressSpeedOption] ); const handlePressActionSheet = useCallback( - (buttonIndex: number) => handlePressSpeedOption(menuOptions[buttonIndex]), + (buttonIndex: number) => { + if (buttonIndex < 0) return; + handlePressSpeedOption(menuOptions[buttonIndex]); + }, [handlePressSpeedOption, menuOptions] ); @@ -115,7 +119,7 @@ const GasMenu = ({ backToReview = false, children }: { backToReview?: boolean; c if (IS_ANDROID) return gasOption; const currentBaseFee = getCachedCurrentBaseFee(chainId); - const gasSettings = gasOption === 'custom' ? customGasSettings : metereologySuggestions.data?.[gasOption]; + const gasSettings = gasOption === GasSpeed.CUSTOM ? customGasSettings : metereologySuggestions.data?.[gasOption]; const subtitle = getEstimatedFeeRangeInGwei(gasSettings, currentBaseFee); return { @@ -176,7 +180,7 @@ export function ReviewGasButton() { const handleShowCustomGas = () => { 'worklet'; - runOnJS(setSelectedGasSpeed)(internalSelectedInputAsset.value?.chainId || ChainId.mainnet, 'custom'); + runOnJS(setSelectedGasSpeed)(internalSelectedInputAsset.value?.chainId || ChainId.mainnet, GasSpeed.CUSTOM); SwapNavigation.handleShowGas({ backToReview: true }); }; diff --git a/src/__swaps__/screens/Swap/components/GasPanel.tsx b/src/__swaps__/screens/Swap/components/GasPanel.tsx index 17224d285c7..eea79c5db84 100644 --- a/src/__swaps__/screens/Swap/components/GasPanel.tsx +++ b/src/__swaps__/screens/Swap/components/GasPanel.tsx @@ -29,6 +29,7 @@ import { formatNumber } from '../hooks/formatNumber'; import { GasSettings, getCustomGasSettings, setCustomGasSettings, useCustomGasStore } from '../hooks/useCustomGas'; import { setSelectedGasSpeed, useSelectedGasSpeed } from '../hooks/useSelectedGas'; import { EstimatedSwapGasFee } from './EstimatedSwapGasFee'; +import { GasSpeed } from '@/__swaps__/types/gas'; const { GAS_TRENDS } = gasUtils; @@ -334,12 +335,12 @@ function saveCustomGasSettings() { const { inputAsset } = useSwapsStore.getState(); const chainId = inputAsset?.chainId || ChainId.mainnet; if (!unsaved) { - if (getCustomGasSettings(chainId)) setSelectedGasSpeed(chainId, 'custom'); + if (getCustomGasSettings(chainId)) setSelectedGasSpeed(chainId, GasSpeed.CUSTOM); return; } setCustomGasSettings(chainId, unsaved); - setSelectedGasSpeed(chainId, 'custom'); + setSelectedGasSpeed(chainId, GasSpeed.CUSTOM); useGasPanelStore.setState(undefined); } diff --git a/src/__swaps__/screens/Swap/components/SearchInput.tsx b/src/__swaps__/screens/Swap/components/SearchInput.tsx index 4c9099bc7d7..1df585b4256 100644 --- a/src/__swaps__/screens/Swap/components/SearchInput.tsx +++ b/src/__swaps__/screens/Swap/components/SearchInput.tsx @@ -77,21 +77,15 @@ export const SearchInput = ({ }; }); - const onInputSearchQueryChange = useDebouncedCallback( - (text: string) => { - userAssetsStore.getState().setSearchQuery(text); - }, - 50, - { leading: true, trailing: true } - ); + const onInputSearchQueryChange = useDebouncedCallback((text: string) => userAssetsStore.getState().setSearchQuery(text), 50, { + leading: true, + trailing: true, + }); - const onOutputSearchQueryChange = useDebouncedCallback( - (text: string) => { - useSwapsStore.setState({ outputSearchQuery: text }); - }, - 100, - { leading: false, trailing: true } - ); + const onOutputSearchQueryChange = useDebouncedCallback((text: string) => useSwapsStore.setState({ outputSearchQuery: text }), 100, { + leading: false, + trailing: true, + }); const isSearchFocused = useDerivedValue( () => diff --git a/src/__swaps__/screens/Swap/components/TokenList/ChainSelection.tsx b/src/__swaps__/screens/Swap/components/TokenList/ChainSelection.tsx index e2663ab5593..f23e02f4b5b 100644 --- a/src/__swaps__/screens/Swap/components/TokenList/ChainSelection.tsx +++ b/src/__swaps__/screens/Swap/components/TokenList/ChainSelection.tsx @@ -17,6 +17,8 @@ import { OnPressMenuItemEventObject } from 'react-native-ios-context-menu'; import { userAssetsStore } from '@/state/assets/userAssets'; import { useSharedValueState } from '@/hooks/reanimated/useSharedValueState'; import { chainNameForChainIdWithMainnetSubstitution } from '@/__swaps__/utils/chains'; +import { analyticsV2 } from '@/analytics'; +import { swapsStore } from '@/state/swaps/swapsStore'; type ChainSelectionProps = { allText?: string; @@ -50,6 +52,12 @@ export const ChainSelection = memo(function ChainSelection({ allText, output }: const handleSelectChain = useCallback( ({ nativeEvent: { actionKey } }: Omit) => { + analyticsV2.track(analyticsV2.event.swapsChangedChainId, { + inputAsset: swapsStore.getState().inputAsset, + type: output ? 'output' : 'input', + chainId: Number(actionKey) as ChainId, + }); + if (output) { setSelectedOutputChainId(Number(actionKey) as ChainId); } else { diff --git a/src/__swaps__/screens/Swap/components/TokenList/TokenToBuySection.tsx b/src/__swaps__/screens/Swap/components/TokenList/TokenToBuySection.tsx index e6f0cf370c8..bca98d0bbd8 100644 --- a/src/__swaps__/screens/Swap/components/TokenList/TokenToBuySection.tsx +++ b/src/__swaps__/screens/Swap/components/TokenList/TokenToBuySection.tsx @@ -19,6 +19,8 @@ import { userAssetsStore } from '@/state/assets/userAssets'; import { EXPANDED_INPUT_HEIGHT } from '../../constants'; import { DEVICE_WIDTH } from '@/utils/deviceUtils'; import { getStandardizedUniqueIdWorklet } from '@/__swaps__/utils/swaps'; +import { swapsStore } from '@/state/swaps/swapsStore'; +import { analyticsV2 } from '@/analytics'; interface SectionProp { color: TextStyle['color']; @@ -95,6 +97,16 @@ export const TokenToBuySection = ({ section }: { section: AssetToBuySection }) = type: SwapAssetType.outputAsset, asset: parsedAsset, }); + + const { outputSearchQuery } = swapsStore.getState(); + + // track what search query the user had prior to selecting an asset + if (outputSearchQuery.trim().length) { + analyticsV2.track(analyticsV2.event.swapsSearchedForToken, { + query: outputSearchQuery, + type: 'output', + }); + } }, [internalSelectedInputAsset, internalSelectedOutputAsset, isFetching, isQuoteStale, setAsset] ); diff --git a/src/__swaps__/screens/Swap/components/TokenList/TokenToSellList.tsx b/src/__swaps__/screens/Swap/components/TokenList/TokenToSellList.tsx index 9405fd12344..fb8d07b685c 100644 --- a/src/__swaps__/screens/Swap/components/TokenList/TokenToSellList.tsx +++ b/src/__swaps__/screens/Swap/components/TokenList/TokenToSellList.tsx @@ -14,6 +14,8 @@ import { EXPANDED_INPUT_HEIGHT } from '../../constants'; import { DEVICE_WIDTH } from '@/utils/deviceUtils'; import { getStandardizedUniqueIdWorklet } from '@/__swaps__/utils/swaps'; import { useDelayedMount } from '@/hooks/useDelayedMount'; +import { swapsStore } from '@/state/swaps/swapsStore'; +import { analyticsV2 } from '@/analytics'; export const TokenToSellList = () => { const shouldMount = useDelayedMount(); @@ -43,6 +45,16 @@ const TokenToSellListComponent = () => { type: SwapAssetType.inputAsset, asset: token, }); + + const { inputSearchQuery } = userAssetsStore.getState(); + + // track what search query the user had prior to selecting an asset + if (inputSearchQuery.trim().length) { + analyticsV2.track(analyticsV2.event.swapsSearchedForToken, { + query: inputSearchQuery, + type: 'input', + }); + } }, [internalSelectedInputAsset, internalSelectedOutputAsset, isFetching, isQuoteStale, setAsset] ); diff --git a/src/__swaps__/screens/Swap/hooks/useSelectedGas.ts b/src/__swaps__/screens/Swap/hooks/useSelectedGas.ts index 1e8cfe3ec3d..b94d34de547 100644 --- a/src/__swaps__/screens/Swap/hooks/useSelectedGas.ts +++ b/src/__swaps__/screens/Swap/hooks/useSelectedGas.ts @@ -3,30 +3,30 @@ import { getCachedGasSuggestions, useMeteorologySuggestions } from '@/__swaps__/ import { createRainbowStore } from '@/state/internal/createRainbowStore'; import { useMemo } from 'react'; import { getCustomGasSettings, useCustomGasSettings } from './useCustomGas'; +import { GasSpeed } from '@/__swaps__/types/gas'; -export type GasSpeed = 'custom' | 'urgent' | 'fast' | 'normal'; const useSelectedGasSpeedStore = createRainbowStore<{ [c in ChainId]?: GasSpeed }>(() => ({}), { version: 0, storageKey: 'preferred gas speed', }); export const useSelectedGasSpeed = (chainId: ChainId) => useSelectedGasSpeedStore(s => { - const speed = s[chainId] || 'fast'; - if (speed === 'custom' && getCustomGasSettings(chainId) === undefined) return 'fast'; + const speed = s[chainId] || GasSpeed.FAST; + if (speed === GasSpeed.CUSTOM && getCustomGasSettings(chainId) === undefined) return GasSpeed.FAST; return speed; }); export const setSelectedGasSpeed = (chainId: ChainId, speed: GasSpeed) => useSelectedGasSpeedStore.setState({ [chainId]: speed }); -export const getSelectedGasSpeed = (chainId: ChainId) => useSelectedGasSpeedStore.getState()[chainId] || 'fast'; +export const getSelectedGasSpeed = (chainId: ChainId) => useSelectedGasSpeedStore.getState()[chainId] || GasSpeed.FAST; export function useGasSettings(chainId: ChainId, speed: GasSpeed) { const userCustomGasSettings = useCustomGasSettings(chainId); const { data: metereologySuggestions } = useMeteorologySuggestions({ chainId, - enabled: speed !== 'custom', + enabled: speed !== GasSpeed.CUSTOM, }); return useMemo(() => { - if (speed === 'custom') return userCustomGasSettings; + if (speed === GasSpeed.CUSTOM) return userCustomGasSettings; return metereologySuggestions?.[speed]; }, [speed, userCustomGasSettings, metereologySuggestions]); } @@ -45,11 +45,11 @@ export function getGasSettingsBySpeed(chainId: ChainId) { } export function getGasSettings(speed: GasSpeed, chainId: ChainId) { - if (speed === 'custom') return getCustomGasSettings(chainId); + if (speed === GasSpeed.CUSTOM) return getCustomGasSettings(chainId); return getCachedGasSuggestions(chainId)?.[speed]; } export function getSelectedGas(chainId: ChainId) { - const selectedGasSpeed = useSelectedGasSpeedStore.getState()[chainId] || 'fast'; + const selectedGasSpeed = useSelectedGasSpeedStore.getState()[chainId] || GasSpeed.FAST; return getGasSettings(selectedGasSpeed, chainId); } diff --git a/src/__swaps__/screens/Swap/hooks/useSwapInputsController.ts b/src/__swaps__/screens/Swap/hooks/useSwapInputsController.ts index ad6750d1fa9..123db634323 100644 --- a/src/__swaps__/screens/Swap/hooks/useSwapInputsController.ts +++ b/src/__swaps__/screens/Swap/hooks/useSwapInputsController.ts @@ -29,6 +29,8 @@ import { } from '@/resources/assets/externalAssetsQuery'; import { ethereumUtils } from '@/utils'; import { queryClient } from '@/react-query'; +import { userAssetsStore } from '@/state/assets/userAssets'; +import { analyticsV2 } from '@/analytics'; import { divWorklet, equalWorklet, greaterThanWorklet, mulWorklet, toFixedWorklet } from '@/__swaps__/safe-math/SafeMath'; function getInitialInputValues(initialSelectedInputAsset: ExtendedAnimatedAssetWithColors | null) { @@ -498,6 +500,12 @@ export function useSwapInputsController({ ) : undefined; + analyticsV2.track(analyticsV2.event.swapsReceivedQuote, { + inputAsset: internalSelectedInputAsset.value, + outputAsset: internalSelectedOutputAsset.value, + quote: quoteResponse, + }); + runOnUI(() => { setQuote({ data: quoteResponse, diff --git a/src/__swaps__/screens/Swap/providers/swap-provider.tsx b/src/__swaps__/screens/Swap/providers/swap-provider.tsx index 9ca5177abfc..a51ea8e91a6 100644 --- a/src/__swaps__/screens/Swap/providers/swap-provider.tsx +++ b/src/__swaps__/screens/Swap/providers/swap-provider.tsx @@ -38,13 +38,14 @@ import { walletExecuteRap } from '@/raps/execute'; import { queryClient } from '@/react-query'; import { userAssetsQueryKey } from '@/resources/assets/UserAssetsQuery'; import { useAccountSettings } from '@/hooks'; -import { getGasSettingsBySpeed, getSelectedGas } from '../hooks/useSelectedGas'; +import { getGasSettingsBySpeed, getSelectedGas, getSelectedGasSpeed } from '../hooks/useSelectedGas'; import { LegacyTransactionGasParamAmounts, TransactionGasParamAmounts } from '@/entities'; import { equalWorklet } from '@/__swaps__/safe-math/SafeMath'; import { useSwapSettings } from '../hooks/useSwapSettings'; import { useSwapOutputQuotesDisabled } from '../hooks/useSwapOutputQuotesDisabled'; import { getNetworkObj } from '@/networks'; import { userAssetsStore } from '@/state/assets/userAssets'; +import { analyticsV2 } from '@/analytics'; const swapping = i18n.t(i18n.l.swap.actions.swapping); const tapToSwap = i18n.t(i18n.l.swap.actions.tap_to_swap); @@ -176,6 +177,9 @@ export const SwapProvider = ({ children }: SwapProviderProps) => { const providerUrl = provider?.connection?.url; const connectedToHardhat = !!providerUrl && isHardHat(providerUrl); + const isBridge = swapsStore.getState().inputAsset?.mainnetAddress === swapsStore.getState().outputAsset?.mainnetAddress; + const slippage = swapsStore.getState().slippage; + const selectedGas = getSelectedGas(parameters.chainId); if (!selectedGas) { runOnUI(resetSwappingStatus)(); @@ -191,6 +195,7 @@ export const SwapProvider = ({ children }: SwapProviderProps) => { } const gasFeeParamsBySpeed = getGasSettingsBySpeed(parameters.chainId); + const selectedGasSpeed = getSelectedGasSpeed(parameters.chainId); let gasParams: TransactionGasParamAmounts | LegacyTransactionGasParamAmounts = {} as | TransactionGasParamAmounts @@ -210,14 +215,27 @@ export const SwapProvider = ({ children }: SwapProviderProps) => { const { errorMessage } = await walletExecuteRap(wallet, type, { ...parameters, gasParams, - // eslint-disable-next-line @typescript-eslint/no-explicit-any - gasFeeParamsBySpeed: gasFeeParamsBySpeed as any, + // @ts-expect-error - collision between old gas types and new + gasFeeParamsBySpeed: gasFeeParamsBySpeed, }); runOnUI(resetSwappingStatus)(); if (errorMessage) { SwapInputController.quoteFetchingInterval.start(); + analyticsV2.track(analyticsV2.event.swapsFailed, { + createdAt: Date.now(), + type, + parameters, + selectedGas, + selectedGasSpeed, + slippage, + bridge: isBridge, + errorMessage, + inputNativeValue: SwapInputController.inputValues.value.inputNativeValue, + outputNativeValue: SwapInputController.inputValues.value.outputNativeValue, + }); + if (errorMessage !== 'handled') { logger.error(new RainbowError(`[getNonceAndPerformSwap]: Error executing swap: ${errorMessage}`)); const extractedError = errorMessage.split('[')[0]; @@ -234,15 +252,25 @@ export const SwapProvider = ({ children }: SwapProviderProps) => { }), }); - // TODO: Analytics NotificationManager?.postNotification('rapCompleted'); Navigation.handleAction(Routes.PROFILE_SCREEN, {}); + + analyticsV2.track(analyticsV2.event.swapsSubmitted, { + createdAt: Date.now(), + type, + parameters, + selectedGas, + selectedGasSpeed, + slippage, + bridge: isBridge, + inputNativeValue: SwapInputController.inputValues.value.inputNativeValue, + outputNativeValue: SwapInputController.inputValues.value.outputNativeValue, + }); }; const executeSwap = () => { 'worklet'; - // TODO: Analytics if (configProgress.value !== NavigationSteps.SHOW_REVIEW) return; const inputAsset = internalSelectedInputAsset.value; @@ -472,6 +500,12 @@ export const SwapProvider = ({ children }: SwapProviderProps) => { } logger.debug(`[setAsset]: Setting ${type} asset to ${extendedAsset?.name} on ${extendedAsset?.chainId}`); + + analyticsV2.track(analyticsV2.event.swapsSelectedAsset, { + asset, + otherAsset: otherSelectedAsset, + type, + }); }, [ SwapInputController.quoteFetchingInterval, diff --git a/src/__swaps__/utils/meteorology.ts b/src/__swaps__/utils/meteorology.ts index cff7bc1b5f3..69d8c1fcec4 100644 --- a/src/__swaps__/utils/meteorology.ts +++ b/src/__swaps__/utils/meteorology.ts @@ -7,8 +7,9 @@ import { QueryConfig, QueryFunctionArgs, QueryFunctionResult, createQueryKey, qu import { getNetworkFromChainId } from '@/utils/ethereumUtils'; import { useCallback } from 'react'; import { GasSettings } from '../screens/Swap/hooks/useCustomGas'; -import { GasSpeed, getSelectedGasSpeed, useGasSettings } from '../screens/Swap/hooks/useSelectedGas'; +import { getSelectedGasSpeed, useGasSettings } from '../screens/Swap/hooks/useSelectedGas'; import { getMinimalTimeUnitStringForMs } from './time'; +import { GasSpeed } from '../types/gas'; // Query Types @@ -239,7 +240,7 @@ export function useMeteorologySuggestion select(speed === 'custom' ? undefined : selectGasSuggestions(d)[speed]), + (d: MeteorologyResult) => select(speed === GasSpeed.CUSTOM ? undefined : selectGasSuggestions(d)[speed]), [select, speed] ), enabled: enabled && speed !== 'custom', diff --git a/src/analytics/event.ts b/src/analytics/event.ts index 8a970c56fc5..3a9d2776d3e 100644 --- a/src/analytics/event.ts +++ b/src/analytics/event.ts @@ -1,9 +1,16 @@ +import { GasSettings } from '@/__swaps__/screens/Swap/hooks/useCustomGas'; +import { ExtendedAnimatedAssetWithColors, ParsedSearchAsset } from '@/__swaps__/types/assets'; +import { ChainId } from '@/__swaps__/types/chains'; +import { GasSpeed } from '@/__swaps__/types/gas'; +import { SwapAssetType } from '@/__swaps__/types/swap'; import { UnlockableAppIconKey } from '@/appIcons/appIcons'; import { CardType } from '@/components/cards/GenericCard'; import { LearnCategory } from '@/components/cards/utils/types'; import { FiatProviderName } from '@/entities/f2c'; import { Network } from '@/networks/types'; +import { RapSwapActionParameters } from '@/raps/references'; import { RequestSource } from '@/utils/requestNavigationHandlers'; +import { CrosschainQuote, Quote, QuoteError } from '@rainbow-me/swaps'; /** * All events, used by `analytics.track()` @@ -20,7 +27,7 @@ export const event = { swapSubmitted: 'Submitted Swap', // notification promo sheet was shown notificationsPromoShown: 'notifications_promo.shown', - // only for iOS — initial prompt is not allowed — Android is enabled by default + // only for iOS — initial prompt is not allowed — Android is enabled by default notificationsPromoPermissionsBlocked: 'notifications_promo.permissions_blocked', // only for iOS, Android is enabled by default notificationsPromoPermissionsGranted: 'notifications_promo.permissions_granted', @@ -126,10 +133,41 @@ export const event = { txRequestApprove: 'request.approved', addNewWalletGroupName: 'add_new_wallet_group.name', + // swaps related analytics + swapsSelectedAsset: 'swaps.selected_asset', + swapsSearchedForToken: 'swaps.searched_for_token', + swapsChangedChainId: 'swaps.changed_chain_id', + swapsFlippedAssets: 'swaps.flipped_assets', + swapsToggledFlashbots: 'swaps.toggled_flashbots', + swapsReceivedQuote: 'swaps.received_quote', + swapsSubmitted: 'swaps.submitted', + swapsFailed: 'swaps.failed', + swapsSucceeded: 'swaps.succeeded', + // app browser events browserTrendingDappClicked: 'browser.trending_dapp_pressed', } as const; +type SwapEventParameters = { + createdAt: number; + type: T; + bridge: boolean; + inputNativeValue: string | number; + outputNativeValue: string | number; + parameters: Omit, 'gasParams' | 'gasFeeParamsBySpeed' | 'selectedGasFee'>; + selectedGas: GasSettings; + selectedGasSpeed: GasSpeed; + slippage: string; +}; + +type SwapsEventFailedParameters = { + errorMessage: string | null; +} & SwapEventParameters; + +type SwapsEventSucceededParameters = { + nonce: number | undefined; +} & SwapEventParameters; + /** * Properties corresponding to each event */ @@ -475,6 +513,45 @@ export type EventProperties = { [event.addNewWalletGroupName]: { name: string; }; + + // swaps related events + [event.swapsSelectedAsset]: { + asset: ParsedSearchAsset | ExtendedAnimatedAssetWithColors | null; + otherAsset: ParsedSearchAsset | ExtendedAnimatedAssetWithColors | null; + type: SwapAssetType; + }; + + [event.swapsSearchedForToken]: { + query: string; + type: 'input' | 'output'; + }; + + [event.swapsChangedChainId]: { + inputAsset: ParsedSearchAsset | ExtendedAnimatedAssetWithColors | null; + type: 'input' | 'output'; + chainId: ChainId; + }; + + [event.swapsFlippedAssets]: { + inputAmount: string | number; + previousInputAsset: ParsedSearchAsset | ExtendedAnimatedAssetWithColors | null; + previousOutputAsset: ParsedSearchAsset | ExtendedAnimatedAssetWithColors | null; + }; + + [event.swapsToggledFlashbots]: { + enabled: boolean; + }; + + [event.swapsReceivedQuote]: { + inputAsset: ParsedSearchAsset | ExtendedAnimatedAssetWithColors | null; + outputAsset: ParsedSearchAsset | ExtendedAnimatedAssetWithColors | null; + quote: Quote | CrosschainQuote | QuoteError | null; + }; + + [event.swapsSubmitted]: SwapEventParameters<'swap' | 'crosschainSwap'>; + [event.swapsFailed]: SwapsEventFailedParameters<'swap' | 'crosschainSwap'>; + [event.swapsSucceeded]: SwapsEventSucceededParameters<'swap' | 'crosschainSwap'>; + [event.browserTrendingDappClicked]: { name: string; url: string;