Commit 6cb6faa9 authored by lynn's avatar lynn Committed by GitHub

feat: add time / duration based event properties to swap events (#4193)

* init commit

* remove absolute value in date calc

* all the events are now logged properly plus changed native token address to NATIVE

* add documentation line

* remove unnecessary prop

* respond to vm comments

* merge and rename util method

* respond to vm comments again
parent 3c5cc21e
...@@ -40,6 +40,8 @@ export enum WALLET_CONNECTION_RESULT { ...@@ -40,6 +40,8 @@ export enum WALLET_CONNECTION_RESULT {
FAILED = 'Failed', FAILED = 'Failed',
} }
export const NATIVE_CHAIN_ID = 'NATIVE'
export enum SWAP_PRICE_UPDATE_USER_RESPONSE { export enum SWAP_PRICE_UPDATE_USER_RESPONSE {
ACCEPTED = 'Accepted', ACCEPTED = 'Accepted',
REJECTED = 'Rejected', REJECTED = 'Rejected',
...@@ -89,7 +91,6 @@ export const enum ElementName { ...@@ -89,7 +91,6 @@ export const enum ElementName {
SWAP_BUTTON = 'swap-button', SWAP_BUTTON = 'swap-button',
SWAP_DETAILS_DROPDOWN = 'swap-details-dropdown', SWAP_DETAILS_DROPDOWN = 'swap-details-dropdown',
SWAP_TOKENS_REVERSE_ARROW_BUTTON = 'swap-tokens-reverse-arrow-button', SWAP_TOKENS_REVERSE_ARROW_BUTTON = 'swap-tokens-reverse-arrow-button',
SWAP_TRADE_PRICE_ROW = 'swap-trade-price-row',
TOKEN_SELECTOR_ROW = 'token-selector-row', TOKEN_SELECTOR_ROW = 'token-selector-row',
WALLET_TYPE_OPTION = 'wallet-type-option', WALLET_TYPE_OPTION = 'wallet-type-option',
// alphabetize additional element names. // alphabetize additional element names.
......
import { Currency, CurrencyAmount, Percent, Token } from '@uniswap/sdk-core' import { Currency, CurrencyAmount, Percent, Token } from '@uniswap/sdk-core'
export const getDurationTillTimestampSinceEpoch = (futureTimestampSinceEpoch?: number): number | undefined => { import { NATIVE_CHAIN_ID } from './constants'
if (!futureTimestampSinceEpoch) return undefined
return futureTimestampSinceEpoch - new Date().getTime() / 1000 export const getDurationUntilTimestampSeconds = (futureTimestampInSecondsSinceEpoch?: number): number | undefined => {
if (!futureTimestampInSecondsSinceEpoch) return undefined
return futureTimestampInSecondsSinceEpoch - new Date().getTime() / 1000
}
export const getDurationFromDateMilliseconds = (start: Date): number => {
return new Date().getTime() - start.getTime()
} }
export const getNumberFormattedToDecimalPlace = ( export const formatToDecimal = (
intialNumberObject: Percent | CurrencyAmount<Token | Currency>, intialNumberObject: Percent | CurrencyAmount<Token | Currency>,
decimalPlace: number decimalPlace: number
): number => parseFloat(intialNumberObject.toFixed(decimalPlace)) ): number => parseFloat(intialNumberObject.toFixed(decimalPlace))
export const formatPercentInBasisPointsNumber = (percent: Percent): number => parseFloat(percent.toFixed(6)) * 100 export const getTokenAddress = (currency: Currency) => (currency.isNative ? NATIVE_CHAIN_ID : currency.address)
export const formatPercentInBasisPointsNumber = (percent: Percent): number => parseFloat(percent.toFixed(2)) * 100
import { Currency, Token } from '@uniswap/sdk-core' import { Currency } from '@uniswap/sdk-core'
import { ElementName, Event, EventName } from 'components/AmplitudeAnalytics/constants' import { ElementName, Event, EventName } from 'components/AmplitudeAnalytics/constants'
import { TraceEvent } from 'components/AmplitudeAnalytics/TraceEvent' import { TraceEvent } from 'components/AmplitudeAnalytics/TraceEvent'
import { getTokenAddress } from 'components/AmplitudeAnalytics/utils'
import { AutoColumn } from 'components/Column' import { AutoColumn } from 'components/Column'
import CurrencyLogo from 'components/CurrencyLogo' import CurrencyLogo from 'components/CurrencyLogo'
import { AutoRow } from 'components/Row' import { AutoRow } from 'components/Row'
...@@ -33,15 +34,10 @@ const BaseWrapper = styled.div<{ disable?: boolean }>` ...@@ -33,15 +34,10 @@ const BaseWrapper = styled.div<{ disable?: boolean }>`
filter: ${({ disable }) => disable && 'grayscale(1)'}; filter: ${({ disable }) => disable && 'grayscale(1)'};
` `
const formatAnalyticsEventProperties = ( const formatAnalyticsEventProperties = (currency: Currency, searchQuery: string, isAddressSearch: string | false) => ({
currency: Currency,
tokenAddress: string | undefined,
searchQuery: string,
isAddressSearch: string | false
) => ({
token_symbol: currency?.symbol, token_symbol: currency?.symbol,
token_chain_id: currency?.chainId, token_chain_id: currency?.chainId,
...(tokenAddress ? { token_address: tokenAddress } : {}), token_address: getTokenAddress(currency),
is_suggested_token: true, is_suggested_token: true,
is_selected_from_list: false, is_selected_from_list: false,
is_imported_by_user: false, is_imported_by_user: false,
...@@ -70,13 +66,12 @@ export default function CommonBases({ ...@@ -70,13 +66,12 @@ export default function CommonBases({
<AutoRow gap="4px"> <AutoRow gap="4px">
{bases.map((currency: Currency) => { {bases.map((currency: Currency) => {
const isSelected = selectedCurrency?.equals(currency) const isSelected = selectedCurrency?.equals(currency)
const tokenAddress = currency instanceof Token ? currency?.address : undefined
return ( return (
<TraceEvent <TraceEvent
events={[Event.onClick, Event.onKeyPress]} events={[Event.onClick, Event.onKeyPress]}
name={EventName.TOKEN_SELECTED} name={EventName.TOKEN_SELECTED}
properties={formatAnalyticsEventProperties(currency, tokenAddress, searchQuery, isAddressSearch)} properties={formatAnalyticsEventProperties(currency, searchQuery, isAddressSearch)}
element={ElementName.COMMON_BASES_CURRENCY_BUTTON} element={ElementName.COMMON_BASES_CURRENCY_BUTTON}
key={currencyId(currency)} key={currencyId(currency)}
> >
......
...@@ -191,8 +191,8 @@ export function CurrencySearch({ ...@@ -191,8 +191,8 @@ export function CurrencySearch({
}, []) }, [])
return ( return (
<Trace name={EventName.TOKEN_SELECTOR_OPENED} modal={ModalName.TOKEN_SELECTOR} shouldLogImpression={true}> <ContentWrapper>
<ContentWrapper> <Trace name={EventName.TOKEN_SELECTOR_OPENED} modal={ModalName.TOKEN_SELECTOR} shouldLogImpression>
<PaddedColumn gap="16px"> <PaddedColumn gap="16px">
<RowBetween> <RowBetween>
<Text fontWeight={500} fontSize={16}> <Text fontWeight={500} fontSize={16}>
...@@ -270,7 +270,7 @@ export function CurrencySearch({ ...@@ -270,7 +270,7 @@ export function CurrencySearch({
</ButtonText> </ButtonText>
</Row> </Row>
</Footer> </Footer>
</ContentWrapper> </Trace>
</Trace> </ContentWrapper>
) )
} }
...@@ -26,6 +26,7 @@ export default function ConfirmSwapModal({ ...@@ -26,6 +26,7 @@ export default function ConfirmSwapModal({
isOpen, isOpen,
attemptingTxn, attemptingTxn,
txHash, txHash,
swapQuoteReceivedDate,
}: { }: {
isOpen: boolean isOpen: boolean
trade: InterfaceTrade<Currency, Currency, TradeType> | undefined trade: InterfaceTrade<Currency, Currency, TradeType> | undefined
...@@ -38,6 +39,7 @@ export default function ConfirmSwapModal({ ...@@ -38,6 +39,7 @@ export default function ConfirmSwapModal({
onConfirm: () => void onConfirm: () => void
swapErrorMessage: ReactNode | undefined swapErrorMessage: ReactNode | undefined
onDismiss: () => void onDismiss: () => void
swapQuoteReceivedDate: Date | undefined
}) { }) {
// shouldLogModalCloseEvent lets the child SwapModalHeader component know when modal has been closed // shouldLogModalCloseEvent lets the child SwapModalHeader component know when modal has been closed
// and an event triggered by modal closing should be logged. // and an event triggered by modal closing should be logged.
...@@ -75,9 +77,10 @@ export default function ConfirmSwapModal({ ...@@ -75,9 +77,10 @@ export default function ConfirmSwapModal({
allowedSlippage={allowedSlippage} allowedSlippage={allowedSlippage}
disabledConfirm={showAcceptChanges} disabledConfirm={showAcceptChanges}
swapErrorMessage={swapErrorMessage} swapErrorMessage={swapErrorMessage}
swapQuoteReceivedDate={swapQuoteReceivedDate}
/> />
) : null ) : null
}, [onConfirm, showAcceptChanges, swapErrorMessage, trade, allowedSlippage, txHash]) }, [onConfirm, showAcceptChanges, swapErrorMessage, trade, allowedSlippage, txHash, swapQuoteReceivedDate])
// text to show while loading // text to show while loading
const pendingText = ( const pendingText = (
......
...@@ -2,9 +2,7 @@ import { Trans } from '@lingui/macro' ...@@ -2,9 +2,7 @@ import { Trans } from '@lingui/macro'
import { Currency, Percent, TradeType } from '@uniswap/sdk-core' import { Currency, Percent, TradeType } from '@uniswap/sdk-core'
import { useWeb3React } from '@web3-react/core' import { useWeb3React } from '@web3-react/core'
import { ElementName, Event, EventName } from 'components/AmplitudeAnalytics/constants' import { ElementName, Event, EventName } from 'components/AmplitudeAnalytics/constants'
import { Trace } from 'components/AmplitudeAnalytics/Trace'
import { TraceEvent } from 'components/AmplitudeAnalytics/TraceEvent' import { TraceEvent } from 'components/AmplitudeAnalytics/TraceEvent'
import { formatPercentInBasisPointsNumber, getNumberFormattedToDecimalPlace } from 'components/AmplitudeAnalytics/utils'
import AnimatedDropdown from 'components/AnimatedDropdown' import AnimatedDropdown from 'components/AnimatedDropdown'
import Card, { OutlineCard } from 'components/Card' import Card, { OutlineCard } from 'components/Card'
import { AutoColumn } from 'components/Column' import { AutoColumn } from 'components/Column'
...@@ -13,15 +11,13 @@ import Row, { RowBetween, RowFixed } from 'components/Row' ...@@ -13,15 +11,13 @@ import Row, { RowBetween, RowFixed } from 'components/Row'
import { MouseoverTooltipContent } from 'components/Tooltip' import { MouseoverTooltipContent } from 'components/Tooltip'
import { SUPPORTED_GAS_ESTIMATE_CHAIN_IDS } from 'constants/chains' import { SUPPORTED_GAS_ESTIMATE_CHAIN_IDS } from 'constants/chains'
import { darken } from 'polished' import { darken } from 'polished'
import { useEffect, useState } from 'react' import { useState } from 'react'
import { ChevronDown, Info } from 'react-feather' import { ChevronDown, Info } from 'react-feather'
import { InterfaceTrade } from 'state/routing/types' import { InterfaceTrade } from 'state/routing/types'
import styled, { keyframes, useTheme } from 'styled-components/macro' import styled, { keyframes, useTheme } from 'styled-components/macro'
import { HideSmall, ThemedText } from 'theme' import { HideSmall, ThemedText } from 'theme'
import { computeRealizedLPFeePercent } from 'utils/prices'
import { AdvancedSwapDetails } from './AdvancedSwapDetails' import { AdvancedSwapDetails } from './AdvancedSwapDetails'
import { getPriceImpactPercent } from './AdvancedSwapDetails'
import GasEstimateBadge from './GasEstimateBadge' import GasEstimateBadge from './GasEstimateBadge'
import { ResponsiveTooltipContainer } from './styleds' import { ResponsiveTooltipContainer } from './styleds'
import SwapRoute from './SwapRoute' import SwapRoute from './SwapRoute'
...@@ -124,29 +120,6 @@ interface SwapDetailsInlineProps { ...@@ -124,29 +120,6 @@ interface SwapDetailsInlineProps {
allowedSlippage: Percent allowedSlippage: Percent
} }
const formatAnalyticsEventProperties = (trade: InterfaceTrade<Currency, Currency, TradeType>) => {
const lpFeePercent = trade ? computeRealizedLPFeePercent(trade) : undefined
return {
token_in_symbol: trade.inputAmount.currency.symbol,
token_out_symbol: trade.outputAmount.currency.symbol,
token_in_address: trade.inputAmount.currency.isToken ? trade.inputAmount.currency.address : undefined,
token_out_address: trade.outputAmount.currency.isToken ? trade.outputAmount.currency.address : undefined,
price_impact_basis_points: lpFeePercent
? formatPercentInBasisPointsNumber(getPriceImpactPercent(lpFeePercent, trade))
: undefined,
estimated_network_fee_usd: trade.gasUseEstimateUSD
? getNumberFormattedToDecimalPlace(trade.gasUseEstimateUSD, 2)
: undefined,
chain_id:
trade.inputAmount.currency.chainId === trade.outputAmount.currency.chainId
? trade.inputAmount.currency.chainId
: undefined,
token_in_amount: getNumberFormattedToDecimalPlace(trade.inputAmount, trade.inputAmount.currency.decimals),
token_out_amount: getNumberFormattedToDecimalPlace(trade.outputAmount, trade.outputAmount.currency.decimals),
// TODO(lynnshaoyu): Implement quote_latency_milliseconds.
}
}
export default function SwapDetailsDropdown({ export default function SwapDetailsDropdown({
trade, trade,
syncing, syncing,
...@@ -158,11 +131,6 @@ export default function SwapDetailsDropdown({ ...@@ -158,11 +131,6 @@ export default function SwapDetailsDropdown({
const theme = useTheme() const theme = useTheme()
const { chainId } = useWeb3React() const { chainId } = useWeb3React()
const [showDetails, setShowDetails] = useState(false) const [showDetails, setShowDetails] = useState(false)
const [isFirstPriceFetch, setIsFirstPriceFetch] = useState(true)
useEffect(() => {
if (isFirstPriceFetch && syncing) setIsFirstPriceFetch(false)
}, [isFirstPriceFetch, syncing])
return ( return (
<Wrapper> <Wrapper>
...@@ -206,18 +174,11 @@ export default function SwapDetailsDropdown({ ...@@ -206,18 +174,11 @@ export default function SwapDetailsDropdown({
)} )}
{trade ? ( {trade ? (
<LoadingOpacityContainer $loading={syncing}> <LoadingOpacityContainer $loading={syncing}>
<Trace <TradePrice
name={EventName.SWAP_QUOTE_RECEIVED} price={trade.executionPrice}
element={ElementName.SWAP_TRADE_PRICE_ROW} showInverted={showInverted}
properties={formatAnalyticsEventProperties(trade)} setShowInverted={setShowInverted}
shouldLogImpression={!loading && !syncing && isFirstPriceFetch} />
>
<TradePrice
price={trade.executionPrice}
showInverted={showInverted}
setShowInverted={setShowInverted}
/>
</Trace>
</LoadingOpacityContainer> </LoadingOpacityContainer>
) : loading || syncing ? ( ) : loading || syncing ? (
<ThemedText.DeprecatedMain fontSize={14}> <ThemedText.DeprecatedMain fontSize={14}>
......
...@@ -5,8 +5,10 @@ import { Event } from 'components/AmplitudeAnalytics/constants' ...@@ -5,8 +5,10 @@ import { Event } from 'components/AmplitudeAnalytics/constants'
import { TraceEvent } from 'components/AmplitudeAnalytics/TraceEvent' import { TraceEvent } from 'components/AmplitudeAnalytics/TraceEvent'
import { import {
formatPercentInBasisPointsNumber, formatPercentInBasisPointsNumber,
getDurationTillTimestampSinceEpoch, formatToDecimal,
getNumberFormattedToDecimalPlace, getDurationFromDateMilliseconds,
getDurationUntilTimestampSeconds,
getTokenAddress,
} from 'components/AmplitudeAnalytics/utils' } from 'components/AmplitudeAnalytics/utils'
import { useStablecoinValue } from 'hooks/useStablecoinPrice' import { useStablecoinValue } from 'hooks/useStablecoinPrice'
import useTransactionDeadline from 'hooks/useTransactionDeadline' import useTransactionDeadline from 'hooks/useTransactionDeadline'
...@@ -31,6 +33,7 @@ interface AnalyticsEventProps { ...@@ -31,6 +33,7 @@ interface AnalyticsEventProps {
tokenInAmountUsd: string | undefined tokenInAmountUsd: string | undefined
tokenOutAmountUsd: string | undefined tokenOutAmountUsd: string | undefined
lpFeePercent: Percent lpFeePercent: Percent
swapQuoteReceivedDate: Date | undefined
} }
const formatAnalyticsEventProperties = ({ const formatAnalyticsEventProperties = ({
...@@ -43,20 +46,19 @@ const formatAnalyticsEventProperties = ({ ...@@ -43,20 +46,19 @@ const formatAnalyticsEventProperties = ({
tokenInAmountUsd, tokenInAmountUsd,
tokenOutAmountUsd, tokenOutAmountUsd,
lpFeePercent, lpFeePercent,
swapQuoteReceivedDate,
}: AnalyticsEventProps) => ({ }: AnalyticsEventProps) => ({
estimated_network_fee_usd: trade.gasUseEstimateUSD estimated_network_fee_usd: trade.gasUseEstimateUSD ? formatToDecimal(trade.gasUseEstimateUSD, 2) : undefined,
? getNumberFormattedToDecimalPlace(trade.gasUseEstimateUSD, 2)
: undefined,
transaction_hash: txHash, transaction_hash: txHash,
transaction_deadline_seconds: getDurationTillTimestampSinceEpoch(transactionDeadlineSecondsSinceEpoch), transaction_deadline_seconds: getDurationUntilTimestampSeconds(transactionDeadlineSecondsSinceEpoch),
token_in_amount_usd: tokenInAmountUsd ? parseFloat(tokenInAmountUsd) : undefined, token_in_amount_usd: tokenInAmountUsd ? parseFloat(tokenInAmountUsd) : undefined,
token_out_amount_usd: tokenOutAmountUsd ? parseFloat(tokenOutAmountUsd) : undefined, token_out_amount_usd: tokenOutAmountUsd ? parseFloat(tokenOutAmountUsd) : undefined,
token_in_address: trade.inputAmount.currency.isToken ? trade.inputAmount.currency.address : undefined, token_in_address: getTokenAddress(trade.inputAmount.currency),
token_out_address: trade.outputAmount.currency.isToken ? trade.outputAmount.currency.address : undefined, token_out_address: getTokenAddress(trade.outputAmount.currency),
token_in_symbol: trade.inputAmount.currency.symbol, token_in_symbol: trade.inputAmount.currency.symbol,
token_out_symbol: trade.outputAmount.currency.symbol, token_out_symbol: trade.outputAmount.currency.symbol,
token_in_amount: getNumberFormattedToDecimalPlace(trade.inputAmount, trade.inputAmount.currency.decimals), token_in_amount: formatToDecimal(trade.inputAmount, trade.inputAmount.currency.decimals),
token_out_amount: getNumberFormattedToDecimalPlace(trade.outputAmount, trade.outputAmount.currency.decimals), token_out_amount: formatToDecimal(trade.outputAmount, trade.outputAmount.currency.decimals),
price_impact_basis_points: formatPercentInBasisPointsNumber(getPriceImpactPercent(lpFeePercent, trade)), price_impact_basis_points: formatPercentInBasisPointsNumber(getPriceImpactPercent(lpFeePercent, trade)),
allowed_slippage_basis_points: formatPercentInBasisPointsNumber(allowedSlippage), allowed_slippage_basis_points: formatPercentInBasisPointsNumber(allowedSlippage),
is_auto_router_api: isAutoRouterApi, is_auto_router_api: isAutoRouterApi,
...@@ -65,7 +67,9 @@ const formatAnalyticsEventProperties = ({ ...@@ -65,7 +67,9 @@ const formatAnalyticsEventProperties = ({
trade.inputAmount.currency.chainId === trade.outputAmount.currency.chainId trade.inputAmount.currency.chainId === trade.outputAmount.currency.chainId
? trade.inputAmount.currency.chainId ? trade.inputAmount.currency.chainId
: undefined, : undefined,
// TODO(lynnshaoyu): implement duration_from_first_quote_to_swap_submission_seconds duration_from_first_quote_to_swap_submission_milliseconds: swapQuoteReceivedDate
? getDurationFromDateMilliseconds(swapQuoteReceivedDate)
: undefined,
}) })
export default function SwapModalFooter({ export default function SwapModalFooter({
...@@ -75,6 +79,7 @@ export default function SwapModalFooter({ ...@@ -75,6 +79,7 @@ export default function SwapModalFooter({
onConfirm, onConfirm,
swapErrorMessage, swapErrorMessage,
disabledConfirm, disabledConfirm,
swapQuoteReceivedDate,
}: { }: {
trade: InterfaceTrade<Currency, Currency, TradeType> trade: InterfaceTrade<Currency, Currency, TradeType>
txHash: string | undefined txHash: string | undefined
...@@ -82,6 +87,7 @@ export default function SwapModalFooter({ ...@@ -82,6 +87,7 @@ export default function SwapModalFooter({
onConfirm: () => void onConfirm: () => void
swapErrorMessage: ReactNode | undefined swapErrorMessage: ReactNode | undefined
disabledConfirm: boolean disabledConfirm: boolean
swapQuoteReceivedDate: Date | undefined
}) { }) {
const transactionDeadlineSecondsSinceEpoch = useTransactionDeadline()?.toNumber() // in seconds since epoch const transactionDeadlineSecondsSinceEpoch = useTransactionDeadline()?.toNumber() // in seconds since epoch
const isAutoSlippage = useUserSlippageTolerance() === 'auto' const isAutoSlippage = useUserSlippageTolerance() === 'auto'
...@@ -107,6 +113,7 @@ export default function SwapModalFooter({ ...@@ -107,6 +113,7 @@ export default function SwapModalFooter({
tokenInAmountUsd, tokenInAmountUsd,
tokenOutAmountUsd, tokenOutAmountUsd,
lpFeePercent, lpFeePercent,
swapQuoteReceivedDate,
})} })}
> >
<ButtonError <ButtonError
......
...@@ -4,12 +4,19 @@ import { Currency, CurrencyAmount, Token, TradeType } from '@uniswap/sdk-core' ...@@ -4,12 +4,19 @@ import { Currency, CurrencyAmount, Token, TradeType } from '@uniswap/sdk-core'
import { Trade as V2Trade } from '@uniswap/v2-sdk' import { Trade as V2Trade } from '@uniswap/v2-sdk'
import { Trade as V3Trade } from '@uniswap/v3-sdk' import { Trade as V3Trade } from '@uniswap/v3-sdk'
import { useWeb3React } from '@web3-react/core' import { useWeb3React } from '@web3-react/core'
import { ElementName, Event, EventName } from 'components/AmplitudeAnalytics/constants' import { sendAnalyticsEvent } from 'components/AmplitudeAnalytics'
import { PageName, SectionName } from 'components/AmplitudeAnalytics/constants' import { ElementName, Event, EventName, PageName, SectionName } from 'components/AmplitudeAnalytics/constants'
import { Trace } from 'components/AmplitudeAnalytics/Trace' import { Trace } from 'components/AmplitudeAnalytics/Trace'
import { TraceEvent } from 'components/AmplitudeAnalytics/TraceEvent' import { TraceEvent } from 'components/AmplitudeAnalytics/TraceEvent'
import {
formatPercentInBasisPointsNumber,
formatToDecimal,
getDurationFromDateMilliseconds,
getTokenAddress,
} from 'components/AmplitudeAnalytics/utils'
import { sendEvent } from 'components/analytics' import { sendEvent } from 'components/analytics'
import { NetworkAlert } from 'components/NetworkAlert/NetworkAlert' import { NetworkAlert } from 'components/NetworkAlert/NetworkAlert'
import { getPriceImpactPercent } from 'components/swap/AdvancedSwapDetails'
import SwapDetailsDropdown from 'components/swap/SwapDetailsDropdown' import SwapDetailsDropdown from 'components/swap/SwapDetailsDropdown'
import UnsupportedCurrencyFooter from 'components/swap/UnsupportedCurrencyFooter' import UnsupportedCurrencyFooter from 'components/swap/UnsupportedCurrencyFooter'
import { MouseoverTooltip } from 'components/Tooltip' import { MouseoverTooltip } from 'components/Tooltip'
...@@ -61,6 +68,7 @@ import { useExpertModeManager } from '../../state/user/hooks' ...@@ -61,6 +68,7 @@ import { useExpertModeManager } from '../../state/user/hooks'
import { LinkStyledButton, ThemedText } from '../../theme' import { LinkStyledButton, ThemedText } from '../../theme'
import { computeFiatValuePriceImpact } from '../../utils/computeFiatValuePriceImpact' import { computeFiatValuePriceImpact } from '../../utils/computeFiatValuePriceImpact'
import { maxAmountSpend } from '../../utils/maxAmountSpend' import { maxAmountSpend } from '../../utils/maxAmountSpend'
import { computeRealizedLPFeePercent } from '../../utils/prices'
import { warningSeverity } from '../../utils/prices' import { warningSeverity } from '../../utils/prices'
import { supportedChainId } from '../../utils/supportedChainId' import { supportedChainId } from '../../utils/supportedChainId'
import AppBody from '../AppBody' import AppBody from '../AppBody'
...@@ -78,10 +86,38 @@ export function getIsValidSwapQuote( ...@@ -78,10 +86,38 @@ export function getIsValidSwapQuote(
return !!swapInputError && !!trade && (tradeState === TradeState.VALID || tradeState === TradeState.SYNCING) return !!swapInputError && !!trade && (tradeState === TradeState.VALID || tradeState === TradeState.SYNCING)
} }
const formatAnalyticsEventProperties = (
trade: InterfaceTrade<Currency, Currency, TradeType>,
fetchingSwapQuoteStartTime: Date | undefined
) => {
const lpFeePercent = trade ? computeRealizedLPFeePercent(trade) : undefined
return {
token_in_symbol: trade.inputAmount.currency.symbol,
token_out_symbol: trade.outputAmount.currency.symbol,
token_in_address: getTokenAddress(trade.inputAmount.currency),
token_out_address: getTokenAddress(trade.outputAmount.currency),
price_impact_basis_points: lpFeePercent
? formatPercentInBasisPointsNumber(getPriceImpactPercent(lpFeePercent, trade))
: undefined,
estimated_network_fee_usd: trade.gasUseEstimateUSD ? formatToDecimal(trade.gasUseEstimateUSD, 2) : undefined,
chain_id:
trade.inputAmount.currency.chainId === trade.outputAmount.currency.chainId
? trade.inputAmount.currency.chainId
: undefined,
token_in_amount: formatToDecimal(trade.inputAmount, trade.inputAmount.currency.decimals),
token_out_amount: formatToDecimal(trade.outputAmount, trade.outputAmount.currency.decimals),
quote_latency_milliseconds: fetchingSwapQuoteStartTime
? getDurationFromDateMilliseconds(fetchingSwapQuoteStartTime)
: undefined,
}
}
export default function Swap() { export default function Swap() {
const navigate = useNavigate() const navigate = useNavigate()
const { account, chainId } = useWeb3React() const { account, chainId } = useWeb3React()
const loadedUrlParams = useDefaultsFromURLSearch() const loadedUrlParams = useDefaultsFromURLSearch()
const [newSwapQuoteNeedsLogging, setNewSwapQuoteNeedsLogging] = useState(true)
const [fetchingSwapQuoteStartTime, setFetchingSwapQuoteStartTime] = useState<Date | undefined>()
// token warning stuff // token warning stuff
const [loadedInputCurrency, loadedOutputCurrency] = [ const [loadedInputCurrency, loadedOutputCurrency] = [
...@@ -350,6 +386,7 @@ export default function Swap() { ...@@ -350,6 +386,7 @@ export default function Swap() {
// errors // errors
const [showInverted, setShowInverted] = useState<boolean>(false) const [showInverted, setShowInverted] = useState<boolean>(false)
const [swapQuoteReceivedDate, setSwapQuoteReceivedDate] = useState<Date | undefined>()
// warnings on the greater of fiat value price impact and execution price impact // warnings on the greater of fiat value price impact and execution price impact
const priceImpactSeverity = useMemo(() => { const priceImpactSeverity = useMemo(() => {
...@@ -412,6 +449,38 @@ export default function Swap() { ...@@ -412,6 +449,38 @@ export default function Swap() {
const priceImpactTooHigh = priceImpactSeverity > 3 && !isExpertMode const priceImpactTooHigh = priceImpactSeverity > 3 && !isExpertMode
// Handle time based logging events and event properties.
useEffect(() => {
const now = new Date()
// If a trade exists, and we need to log the receipt of this new swap quote:
if (newSwapQuoteNeedsLogging && !!trade) {
// Set the current datetime as the time of receipt of latest swap quote.
setSwapQuoteReceivedDate(now)
// Log swap quote.
sendAnalyticsEvent(
EventName.SWAP_QUOTE_RECEIVED,
formatAnalyticsEventProperties(trade, fetchingSwapQuoteStartTime)
)
// Latest swap quote has just been logged, so we don't need to log the current trade anymore
// unless user inputs change again and a new trade is in the process of being generated.
setNewSwapQuoteNeedsLogging(false)
// New quote is not being fetched, so set start time of quote fetch to undefined.
setFetchingSwapQuoteStartTime(undefined)
}
// If another swap quote is being loaded based on changed user inputs:
if (routeIsLoading) {
setNewSwapQuoteNeedsLogging(true)
if (!fetchingSwapQuoteStartTime) setFetchingSwapQuoteStartTime(now)
}
}, [
newSwapQuoteNeedsLogging,
routeIsSyncing,
routeIsLoading,
fetchingSwapQuoteStartTime,
trade,
setSwapQuoteReceivedDate,
])
return ( return (
<Trace page={PageName.SWAP_PAGE} shouldLogImpression> <Trace page={PageName.SWAP_PAGE} shouldLogImpression>
<> <>
...@@ -436,6 +505,7 @@ export default function Swap() { ...@@ -436,6 +505,7 @@ export default function Swap() {
onConfirm={handleSwap} onConfirm={handleSwap}
swapErrorMessage={swapErrorMessage} swapErrorMessage={swapErrorMessage}
onDismiss={handleConfirmDismiss} onDismiss={handleConfirmDismiss}
swapQuoteReceivedDate={swapQuoteReceivedDate}
/> />
<AutoColumn gap={'sm'}> <AutoColumn gap={'sm'}>
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment