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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -213,4 +213,4 @@
"workbox-precaching": "^6.1.0",
"workbox-routing": "^6.1.0"
}
}
}
47 changes: 36 additions & 11 deletions src/components/AmplitudeAnalytics/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
* and logged.
*/
export enum EventName {
APP_LOADED = 'Application Loaded',
APPROVE_TOKEN_TXN_SUBMITTED = 'Approve Token Transaction Submitted',
CONNECT_WALLET_BUTTON_CLICKED = 'Connect Wallet Button Clicked',
EXPLORE_TOKEN_ROW_SELECTED = 'Explore Token Row Clicked',
Expand All @@ -27,15 +28,39 @@ export enum EventName {
}

export enum CUSTOM_USER_PROPERTIES {
WALLET_ADDRESS = 'wallet_address',
WALLET_TYPE = 'wallet_type',
USER_LAST_SEEN_DATE = 'user_last_seen_date',
USER_FIRST_SEEN_DATE = 'user_first_seen_date',
WALLET_CHAIN_IDS = 'all_wallet_chain_ids',
ALL_WALLET_ADDRESSES_CONNECTED = 'all_wallet_addresses_connected',
SCREEN_RESOLUTION = 'screen_resolution',
ALL_WALLET_CHAIN_IDS = 'all_wallet_chain_ids',
BROWSER = 'browser',
LIGHT_MODE = 'light_mode',
DARK_MODE = 'is_dark_mode',
SCREEN_RESOLUTION_HEIGHT = 'screen_resolution_height',
SCREEN_RESOLUTION_WIDTH = 'screen_resolution_width',
WALLET_ADDRESS = 'wallet_address',
WALLET_NATIVE_CURRENCY_BALANCE_USD = 'wallet_native_currency_balance_usd',
WALLET_TOKENS_ADDRESSES = 'wallet_tokens_addresses',
WALLET_TOKENS_SYMBOLS = 'wallet_tokens_symbols',
WALLET_TYPE = 'wallet_type',
}

export enum CUSTOM_USER_PROPERTY_SUFFIXES {
WALLET_TOKEN_AMOUNT_SUFFIX = '_token_amount',
}

export enum CUSTOM_USER_PROPERTY_PREFIXES {
WALLET_CHAIN_IDS_PREFIX = 'wallet_chain_ids_',
WALLET_FIRST_SEEN_DATE_PREFIX = 'first_seen_date_',
WALLET_LAST_SEEN_DATE_PREFIX = 'last_seen_date_',
}

export enum BROWSER {
FIREFOX = 'Mozilla Firefox',
SAMSUNG = 'Samsung Internet',
OPERA = 'Opera',
INTERNET_EXPLORER = 'Microsoft Internet Explorer',
EDGE = 'Microsoft Edge (Legacy)',
EDGE_CHROMIUM = 'Microsoft Edge (Chromium)',
CHROME = 'Google Chrome or Chromium',
SAFARI = 'Apple Safari',
UNKNOWN = 'unknown',
}

export enum WALLET_CONNECTION_RESULT {
Expand All @@ -53,7 +78,7 @@ export enum SWAP_PRICE_UPDATE_USER_RESPONSE {
/**
* Known pages in the app. Highest order context.
*/
export const enum PageName {
export enum PageName {
EXPLORE_PAGE = 'explore-page',
POOL_PAGE = 'pool-page',
SWAP_PAGE = 'swap-page',
Expand All @@ -66,14 +91,14 @@ export const enum PageName {
* eg a `back` button in a modal will have the same `element`,
* but a different `section`.
*/
export const enum SectionName {
export enum SectionName {
CURRENCY_INPUT_PANEL = 'swap-currency-input',
CURRENCY_OUTPUT_PANEL = 'swap-currency-output',
// alphabetize additional section names.
}

/** Known modals for analytics purposes. */
export const enum ModalName {
export enum ModalName {
CONFIRM_SWAP = 'confirm-swap-modal',
TOKEN_SELECTOR = 'token-selector-modal',
// alphabetize additional modal names.
Expand All @@ -83,7 +108,7 @@ export const enum ModalName {
* Known element names for analytics purposes.
* Use to identify low-level components given a TraceContext
*/
export const enum ElementName {
export enum ElementName {
APPROVE_TOKEN_BUTTON = 'approve-token-button',
AUTOROUTER_VISUALIZATION_ROW = 'expandable-autorouter-visualization-row',
COMMON_BASES_CURRENCY_BUTTON = 'common-bases-currency-button',
Expand Down
10 changes: 6 additions & 4 deletions src/components/AmplitudeAnalytics/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,8 @@ export function sendAnalyticsEvent(eventName: string, eventProperties?: Record<s
track(eventName, eventProperties)
}

type Value = string | number | boolean | string[] | number[]

/**
* Class that exposes methods to mutate the User Model's properties in
* Amplitude that represents the current session's user.
Expand All @@ -67,16 +69,16 @@ class UserModel {
identify(mutate(new Identify()))
}

set(key: string, value: string | number) {
set(key: string, value: Value) {
this.call((event) => event.set(key, value))
}

setOnce(key: string, value: string | number) {
setOnce(key: string, value: Value) {
this.call((event) => event.setOnce(key, value))
}

add(key: string, value: string | number) {
this.call((event) => event.add(key, typeof value === 'number' ? value : 0))
add(key: string, value: number) {
this.call((event) => event.add(key, value))
}

postInsert(key: string, value: string | number) {
Expand Down
10 changes: 5 additions & 5 deletions src/components/SearchModal/CurrencySearch.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -108,10 +108,10 @@ export function CurrencySearch({
}, [allTokens, debouncedQuery])

const [balances, balancesIsLoading] = useAllTokenBalances()
const sortedTokens: Token[] = useMemo(() => {
void balancesIsLoading // creates a new array once balances load to update hooks
return [...filteredTokens].sort(tokenComparator.bind(null, balances))
}, [balances, filteredTokens, balancesIsLoading])
const sortedTokens: Token[] = useMemo(
() => (!balancesIsLoading ? [...filteredTokens].sort(tokenComparator.bind(null, balances)) : []),
[balances, filteredTokens, balancesIsLoading]
)

const filteredSortedTokens = useSortTokensByQuery(debouncedQuery, sortedTokens)

Expand All @@ -126,7 +126,7 @@ export function CurrencySearch({
const s = debouncedQuery.toLowerCase().trim()
if (native.symbol?.toLowerCase()?.indexOf(s) !== -1) {
// Always bump the native token to the top of the list.
return native ? [native, ...filteredSortedTokens.filter((t) => !t.equals(native))] : filteredSortedTokens
return [native, ...filteredSortedTokens.filter((t) => !t.equals(native))]
}
return filteredSortedTokens
}, [debouncedQuery, native, filteredSortedTokens])
Expand Down
40 changes: 40 additions & 0 deletions src/components/WalletModal/index.test.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
import { Currency, CurrencyAmount } from '@uniswap/sdk-core'
import * as connectionUtils from 'connection/utils'
import JSBI from 'jsbi'
import { ApplicationModal } from 'state/application/reducer'

import { nativeOnChain } from '../../constants/tokens'
import { render, screen } from '../../test-utils'
import WalletModal from './index'

Expand All @@ -9,6 +12,11 @@ afterEach(() => {
jest.resetModules()
})

const currencyAmount = (token: Currency, amount: number) => CurrencyAmount.fromRawAmount(token, JSBI.BigInt(amount))

const mockEth = () => nativeOnChain(1)
const mockCurrencyAmount = currencyAmount(mockEth(), 1)

const UserAgentMock = jest.requireMock('utils/userAgent')
jest.mock('utils/userAgent', () => ({
isMobile: false,
Expand All @@ -23,6 +31,38 @@ jest.mock('.../../state/application/hooks', () => {
}
})

jest.mock('hooks/useStablecoinPrice', () => {
return {
useStablecoinValue: (_currencyAmount: CurrencyAmount<Currency> | undefined | null) => {
return
},
}
})

jest.mock('state/connection/hooks', () => {
return {
useAllTokenBalances: () => {
return [{}, false]
},
}
})

jest.mock('../../hooks/Tokens', () => {
return {
useAllTokens: () => ({}),
}
})

jest.mock('lib/hooks/useCurrencyBalance', () => {
return {
useCurrencyBalances: (account?: string, currencies?: (Currency | undefined)[]) => {
return [mockCurrencyAmount]
},
}
})

jest.mock('lib/hooks/useNativeCurrency', () => () => mockEth)

jest.mock('@web3-react/core', () => {
const web3React = jest.requireActual('@web3-react/core')
return {
Expand Down
105 changes: 89 additions & 16 deletions src/components/WalletModal/index.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,28 @@
import { Trans } from '@lingui/macro'
import { Currency, CurrencyAmount, Token } from '@uniswap/sdk-core'
import { useWeb3React } from '@web3-react/core'
import { Connector } from '@web3-react/types'
import { sendAnalyticsEvent, user } from 'components/AmplitudeAnalytics'
import { CUSTOM_USER_PROPERTIES, EventName, WALLET_CONNECTION_RESULT } from 'components/AmplitudeAnalytics/constants'
import {
CUSTOM_USER_PROPERTIES,
CUSTOM_USER_PROPERTY_PREFIXES,
CUSTOM_USER_PROPERTY_SUFFIXES,
EventName,
WALLET_CONNECTION_RESULT,
} from 'components/AmplitudeAnalytics/constants'
import { formatToDecimal, getTokenAddress } from 'components/AmplitudeAnalytics/utils'
import { sendEvent } from 'components/analytics'
import { AutoColumn } from 'components/Column'
import { AutoRow } from 'components/Row'
import { ConnectionType } from 'connection'
import { getConnection, getConnectionName, getIsCoinbaseWallet, getIsInjected, getIsMetaMask } from 'connection/utils'
import { useCallback, useEffect, useState } from 'react'
import { useStablecoinValue } from 'hooks/useStablecoinPrice'
import { useCurrencyBalances } from 'lib/hooks/useCurrencyBalance'
import useNativeCurrency from 'lib/hooks/useNativeCurrency'
import { tokenComparator } from 'lib/hooks/useTokenList/sorting'
import { useCallback, useEffect, useMemo, useState } from 'react'
import { ArrowLeft } from 'react-feather'
import { useAllTokenBalances } from 'state/connection/hooks'
import { updateConnectionError } from 'state/connection/reducer'
import { useAppDispatch, useAppSelector } from 'state/hooks'
import { updateSelectedWallet } from 'state/user/reducer'
Expand All @@ -18,6 +31,7 @@ import styled from 'styled-components/macro'
import { isMobile } from 'utils/userAgent'

import { ReactComponent as Close } from '../../assets/images/x.svg'
import { useAllTokens } from '../../hooks/Tokens'
import { useModalIsOpen, useToggleWalletModal } from '../../state/application/hooks'
import { ApplicationModal } from '../../state/application/reducer'
import { ExternalLink, ThemedText } from '../../theme'
Expand Down Expand Up @@ -114,25 +128,51 @@ const WALLET_VIEWS = {
PENDING: 'pending',
}

const sendAnalyticsWalletBalanceUserInfo = (
balances: (CurrencyAmount<Currency> | undefined)[],
nativeCurrencyBalanceUsd: number
) => {
const walletTokensSymbols: string[] = []
const walletTokensAddresses: string[] = []
balances.forEach((currencyAmount) => {
if (currencyAmount !== undefined) {
const tokenBalanceAmount = formatToDecimal(currencyAmount, currencyAmount.currency.decimals)
if (tokenBalanceAmount > 0) {
const tokenAddress = getTokenAddress(currencyAmount.currency)
walletTokensAddresses.push(getTokenAddress(currencyAmount.currency))
walletTokensSymbols.push(currencyAmount.currency.symbol ?? '')
const tokenPrefix = currencyAmount.currency.symbol ?? tokenAddress
user.set(`${tokenPrefix}${CUSTOM_USER_PROPERTY_SUFFIXES.WALLET_TOKEN_AMOUNT_SUFFIX}`, tokenBalanceAmount)
}
}
})
user.set(CUSTOM_USER_PROPERTIES.WALLET_NATIVE_CURRENCY_BALANCE_USD, nativeCurrencyBalanceUsd)
user.set(CUSTOM_USER_PROPERTIES.WALLET_TOKENS_ADDRESSES, walletTokensAddresses)
user.set(CUSTOM_USER_PROPERTIES.WALLET_TOKENS_SYMBOLS, walletTokensSymbols)
}

const sendAnalyticsEventAndUserInfo = (
account: string,
walletType: string,
chainId: number | undefined,
isReconnect: boolean
) => {
const currentDate = new Date().toISOString()
sendAnalyticsEvent(EventName.WALLET_CONNECT_TXN_COMPLETED, {
result: WALLET_CONNECTION_RESULT.SUCCEEDED,
wallet_address: account,
wallet_type: walletType,
is_reconnect: isReconnect,
})
const currentDate = new Date().toISOString()
user.set(CUSTOM_USER_PROPERTIES.WALLET_ADDRESS, account)
user.set(CUSTOM_USER_PROPERTIES.WALLET_TYPE, walletType)
if (chainId) user.postInsert(CUSTOM_USER_PROPERTIES.WALLET_CHAIN_IDS, chainId)
if (chainId) {
user.postInsert(CUSTOM_USER_PROPERTIES.ALL_WALLET_CHAIN_IDS, chainId)
user.postInsert(`${CUSTOM_USER_PROPERTY_PREFIXES.WALLET_CHAIN_IDS_PREFIX}${account}`, chainId)
}
user.postInsert(CUSTOM_USER_PROPERTIES.ALL_WALLET_ADDRESSES_CONNECTED, account)
user.setOnce(CUSTOM_USER_PROPERTIES.USER_FIRST_SEEN_DATE, currentDate)
user.set(CUSTOM_USER_PROPERTIES.USER_LAST_SEEN_DATE, currentDate)
user.setOnce(`${CUSTOM_USER_PROPERTY_PREFIXES.WALLET_FIRST_SEEN_DATE_PREFIX}${account}`, currentDate)
user.set(`${CUSTOM_USER_PROPERTY_PREFIXES.WALLET_LAST_SEEN_DATE_PREFIX}${account}`, currentDate)
}

export default function WalletModal({
Expand All @@ -146,10 +186,11 @@ export default function WalletModal({
}) {
const dispatch = useAppDispatch()
const { connector, account, chainId } = useWeb3React()
const [connectedWallets, updateConnectedWallets] = useConnectedWallets()
const [connectedWallets, addWalletToConnectedWallets] = useConnectedWallets()

const [walletView, setWalletView] = useState(WALLET_VIEWS.ACCOUNT)
const [lastActiveWalletAddress, setLastActiveWalletAddress] = useState<string | undefined>(account)
const [shouldLogWalletBalances, setShouldLogWalletBalances] = useState(false)

const [pendingConnector, setPendingConnector] = useState<Connector | undefined>()
const pendingError = useAppSelector((state) =>
Expand All @@ -159,6 +200,25 @@ export default function WalletModal({
const walletModalOpen = useModalIsOpen(ApplicationModal.WALLET)
const toggleWalletModal = useToggleWalletModal()

const allTokens = useAllTokens()
const [tokenBalances, tokenBalancesIsLoading] = useAllTokenBalances()
const sortedTokens: Token[] = useMemo(
() => (!tokenBalancesIsLoading ? Object.values(allTokens).sort(tokenComparator.bind(null, tokenBalances)) : []),
[tokenBalances, allTokens, tokenBalancesIsLoading]
)
const native = useNativeCurrency()

const sortedTokensWithETH: Currency[] = useMemo(
() =>
// Always bump the native token to the top of the list.
native ? [native, ...sortedTokens.filter((t) => !t.equals(native))] : sortedTokens,
[native, sortedTokens]
)

const balances = useCurrencyBalances(account, sortedTokensWithETH)
const nativeBalance = balances.length > 0 ? balances[0] : null
const nativeCurrencyBalanceUsdValue = useStablecoinValue(nativeBalance)?.toFixed(2)

const openOptions = useCallback(() => {
setWalletView(WALLET_VIEWS.OPTIONS)
}, [setWalletView])
Expand All @@ -180,18 +240,31 @@ export default function WalletModal({
useEffect(() => {
if (account && account !== lastActiveWalletAddress) {
const walletType = getConnectionName(getConnection(connector).type, getIsMetaMask())

if (
const isReconnect =
connectedWallets.filter((wallet) => wallet.account === account && wallet.walletType === walletType).length > 0
) {
sendAnalyticsEventAndUserInfo(account, walletType, chainId, true)
} else {
sendAnalyticsEventAndUserInfo(account, walletType, chainId, false)
updateConnectedWallets({ account, walletType })
}
sendAnalyticsEventAndUserInfo(account, walletType, chainId, isReconnect)
setShouldLogWalletBalances(true)
if (!isReconnect) addWalletToConnectedWallets({ account, walletType })
}
setLastActiveWalletAddress(account)
}, [connectedWallets, updateConnectedWallets, lastActiveWalletAddress, account, connector, chainId])
}, [connectedWallets, addWalletToConnectedWallets, lastActiveWalletAddress, account, connector, chainId])

// Send wallet balance info once it becomes available.
useEffect(() => {
if (!tokenBalancesIsLoading && shouldLogWalletBalances && balances && nativeCurrencyBalanceUsdValue) {
const nativeCurrencyBalanceUsd =
native && nativeCurrencyBalanceUsdValue ? parseFloat(nativeCurrencyBalanceUsdValue) : 0
sendAnalyticsWalletBalanceUserInfo(balances, nativeCurrencyBalanceUsd)
setShouldLogWalletBalances(false)
}
}, [
balances,
nativeCurrencyBalanceUsdValue,
shouldLogWalletBalances,
setShouldLogWalletBalances,
tokenBalancesIsLoading,
native,
])

const tryActivation = useCallback(
async (connector: Connector) => {
Expand Down
Loading