Commit 3ed3ed49 authored by eddie's avatar eddie Committed by GitHub

feat: sort tokens in selector by USD value (#6744)

* feat: sort tokens in selector by USD value

* fix: sync visible balances in list with the sorting values

* fix: tryParseCurrencyAmount

* fix: remove todo

* fix: make shared hook for cached query

* fix: replace true with modalOpen

* fix: default to zero balance

* fix: add test and comment

* feat: fallback to unfilterd tokens

* fix: unconnected balances

* fix: update tests

* fix: test selector
parent 3a0c4ad4
...@@ -58,7 +58,7 @@ describe('Swap', () => { ...@@ -58,7 +58,7 @@ describe('Swap', () => {
// Select USDC // Select USDC
cy.get('#swap-currency-output .open-currency-select-button').click() cy.get('#swap-currency-output .open-currency-select-button').click()
cy.get(getTestSelector('token-search-input')).type(USDC_MAINNET.address) cy.get(getTestSelector('token-search-input')).type(USDC_MAINNET.address)
cy.contains('USDC').click() cy.get(getTestSelector('common-base-USDC')).click()
// Enter amount to swap // Enter amount to swap
cy.get('#swap-currency-output .token-amount-input').type('1').should('have.value', '1') cy.get('#swap-currency-output .token-amount-input').type('1').should('have.value', '1')
......
...@@ -11,8 +11,6 @@ import { LoadingBubble } from 'components/Tokens/loading' ...@@ -11,8 +11,6 @@ import { LoadingBubble } from 'components/Tokens/loading'
import { formatDelta } from 'components/Tokens/TokenDetails/PriceChart' import { formatDelta } from 'components/Tokens/TokenDetails/PriceChart'
import Tooltip from 'components/Tooltip' import Tooltip from 'components/Tooltip'
import { getConnection } from 'connection' import { getConnection } from 'connection'
import { usePortfolioBalancesQuery } from 'graphql/data/__generated__/types-and-hooks'
import { GQL_MAINNET_CHAINS } from 'graphql/data/util'
import { useDisableNFTRoutes } from 'hooks/useDisableNFTRoutes' import { useDisableNFTRoutes } from 'hooks/useDisableNFTRoutes'
import { useProfilePageState, useSellAsset, useWalletCollections } from 'nft/hooks' import { useProfilePageState, useSellAsset, useWalletCollections } from 'nft/hooks'
import { useIsNftClaimAvailable } from 'nft/hooks/useIsNftClaimAvailable' import { useIsNftClaimAvailable } from 'nft/hooks/useIsNftClaimAvailable'
...@@ -34,6 +32,7 @@ import { useToggleAccountDrawer } from '.' ...@@ -34,6 +32,7 @@ import { useToggleAccountDrawer } from '.'
import IconButton, { IconHoverText, IconWithConfirmTextButton } from './IconButton' import IconButton, { IconHoverText, IconWithConfirmTextButton } from './IconButton'
import MiniPortfolio from './MiniPortfolio' import MiniPortfolio from './MiniPortfolio'
import { portfolioFadeInAnimation } from './MiniPortfolio/PortfolioRow' import { portfolioFadeInAnimation } from './MiniPortfolio/PortfolioRow'
import { useCachedPortfolioBalancesQuery } from './PrefetchBalancesWrapper'
const AuthenticatedHeaderWrapper = styled.div` const AuthenticatedHeaderWrapper = styled.div`
padding: 20px 16px; padding: 20px 16px;
...@@ -226,11 +225,7 @@ export default function AuthenticatedHeader({ account, openSettings }: { account ...@@ -226,11 +225,7 @@ export default function AuthenticatedHeader({ account, openSettings }: { account
const openFiatOnrampUnavailableTooltip = useCallback(() => setShow(true), [setShow]) const openFiatOnrampUnavailableTooltip = useCallback(() => setShow(true), [setShow])
const closeFiatOnrampUnavailableTooltip = useCallback(() => setShow(false), [setShow]) const closeFiatOnrampUnavailableTooltip = useCallback(() => setShow(false), [setShow])
const { data: portfolioBalances } = usePortfolioBalancesQuery({ const { data: portfolioBalances } = useCachedPortfolioBalancesQuery({ account })
variables: { ownerAddress: account ?? '', chains: GQL_MAINNET_CHAINS },
fetchPolicy: 'cache-only', // PrefetchBalancesWrapper handles balance fetching/staleness; this component only reads from cache
})
const portfolio = portfolioBalances?.portfolios?.[0] const portfolio = portfolioBalances?.portfolios?.[0]
const totalBalance = portfolio?.tokensTotalDenominatedValue?.value const totalBalance = portfolio?.tokensTotalDenominatedValue?.value
const absoluteChange = portfolio?.tokensTotalDenominatedValueChange?.absolute?.value const absoluteChange = portfolio?.tokensTotalDenominatedValueChange?.absolute?.value
......
import { TraceEvent } from '@uniswap/analytics' import { TraceEvent } from '@uniswap/analytics'
import { BrowserEvent, InterfaceElementName, SharedEventName } from '@uniswap/analytics-events' import { BrowserEvent, InterfaceElementName, SharedEventName } from '@uniswap/analytics-events'
import { formatNumber, NumberType } from '@uniswap/conedison/format' import { formatNumber, NumberType } from '@uniswap/conedison/format'
import { useCachedPortfolioBalancesQuery } from 'components/AccountDrawer/PrefetchBalancesWrapper'
import Row from 'components/Row' import Row from 'components/Row'
import { formatDelta } from 'components/Tokens/TokenDetails/PriceChart' import { formatDelta } from 'components/Tokens/TokenDetails/PriceChart'
import { PortfolioBalancesQuery, usePortfolioBalancesQuery } from 'graphql/data/__generated__/types-and-hooks' import { PortfolioBalancesQuery } from 'graphql/data/__generated__/types-and-hooks'
import { import { getTokenDetailsURL, gqlToCurrency, logSentryErrorForUnsupportedChain } from 'graphql/data/util'
getTokenDetailsURL,
GQL_MAINNET_CHAINS,
gqlToCurrency,
logSentryErrorForUnsupportedChain,
} from 'graphql/data/util'
import { useAtomValue } from 'jotai/utils' import { useAtomValue } from 'jotai/utils'
import { EmptyWalletModule } from 'nft/components/profile/view/EmptyWalletContent' import { EmptyWalletModule } from 'nft/components/profile/view/EmptyWalletContent'
import { useCallback, useMemo, useState } from 'react' import { useCallback, useMemo, useState } from 'react'
...@@ -35,11 +31,7 @@ export default function Tokens({ account }: { account: string }) { ...@@ -35,11 +31,7 @@ export default function Tokens({ account }: { account: string }) {
const hideSmallBalances = useAtomValue(hideSmallBalancesAtom) const hideSmallBalances = useAtomValue(hideSmallBalancesAtom)
const [showHiddenTokens, setShowHiddenTokens] = useState(false) const [showHiddenTokens, setShowHiddenTokens] = useState(false)
const { data } = usePortfolioBalancesQuery({ const { data } = useCachedPortfolioBalancesQuery({ account })
variables: { ownerAddress: account, chains: GQL_MAINNET_CHAINS },
fetchPolicy: 'cache-only', // PrefetchBalancesWrapper handles balance fetching/staleness; this component only reads from cache
errorPolicy: 'all',
})
const visibleTokens = useMemo(() => { const visibleTokens = useMemo(() => {
return !hideSmallBalances return !hideSmallBalances
......
import { useWeb3React } from '@web3-react/core' import { useWeb3React } from '@web3-react/core'
import { usePortfolioBalancesLazyQuery } from 'graphql/data/__generated__/types-and-hooks' import { usePortfolioBalancesLazyQuery, usePortfolioBalancesQuery } from 'graphql/data/__generated__/types-and-hooks'
import { GQL_MAINNET_CHAINS } from 'graphql/data/util' import { GQL_MAINNET_CHAINS } from 'graphql/data/util'
import usePrevious from 'hooks/usePrevious' import usePrevious from 'hooks/usePrevious'
import { PropsWithChildren, useCallback, useEffect, useMemo, useState } from 'react' import { atom, useAtom } from 'jotai'
import { PropsWithChildren, useCallback, useEffect, useMemo } from 'react'
import { useAllTransactions } from 'state/transactions/hooks' import { useAllTransactions } from 'state/transactions/hooks'
import { TransactionDetails } from 'state/transactions/types' import { TransactionDetails } from 'state/transactions/types'
import { useAccountDrawer } from '.'
const isTxPending = (tx: TransactionDetails) => !tx.receipt const isTxPending = (tx: TransactionDetails) => !tx.receipt
function wasPending(previousTxs: { [hash: string]: TransactionDetails | undefined }, current: TransactionDetails) { function wasPending(previousTxs: { [hash: string]: TransactionDetails | undefined }, current: TransactionDetails) {
const previousTx = previousTxs[current.hash] const previousTx = previousTxs[current.hash]
...@@ -36,36 +35,50 @@ function useHasUpdatedTx(account: string | undefined) { ...@@ -36,36 +35,50 @@ function useHasUpdatedTx(account: string | undefined) {
}, [account, currentChainTxs, previousPendingTxs]) }, [account, currentChainTxs, previousPendingTxs])
} }
export function useCachedPortfolioBalancesQuery({ account }: { account?: string }) {
return usePortfolioBalancesQuery({
skip: !account,
variables: { ownerAddress: account ?? '', chains: GQL_MAINNET_CHAINS },
fetchPolicy: 'cache-only', // PrefetchBalancesWrapper handles balance fetching/staleness; this component only reads from cache
errorPolicy: 'all',
})
}
const hasUnfetchedBalancesAtom = atom<boolean>(true)
/* Prefetches & caches portfolio balances when the wrapped component is hovered or the user completes a transaction */ /* Prefetches & caches portfolio balances when the wrapped component is hovered or the user completes a transaction */
export default function PrefetchBalancesWrapper({ children }: PropsWithChildren) { export default function PrefetchBalancesWrapper({
children,
shouldFetchOnAccountUpdate,
}: PropsWithChildren<{ shouldFetchOnAccountUpdate: boolean }>) {
const { account } = useWeb3React() const { account } = useWeb3React()
const [prefetchPortfolioBalances] = usePortfolioBalancesLazyQuery() const [prefetchPortfolioBalances] = usePortfolioBalancesLazyQuery()
const [drawerOpen] = useAccountDrawer()
const [hasUnfetchedBalances, setHasUnfetchedBalances] = useState(true) // Use an atom to track unfetched state to avoid duplicating fetches if this component appears multiple times on the page.
const [hasUnfetchedBalances, setHasUnfetchedBalances] = useAtom(hasUnfetchedBalancesAtom)
const fetchBalances = useCallback(() => { const fetchBalances = useCallback(() => {
if (account) { if (account) {
prefetchPortfolioBalances({ variables: { ownerAddress: account, chains: GQL_MAINNET_CHAINS } }) prefetchPortfolioBalances({ variables: { ownerAddress: account, chains: GQL_MAINNET_CHAINS } })
setHasUnfetchedBalances(false) setHasUnfetchedBalances(false)
} }
}, [account, prefetchPortfolioBalances]) }, [account, prefetchPortfolioBalances, setHasUnfetchedBalances])
const prevAccount = usePrevious(account) const prevAccount = usePrevious(account)
// TODO(cartcrom): add delay for refetching on optimism, as there is high latency in new balances being available
const hasUpdatedTx = useHasUpdatedTx(account) const hasUpdatedTx = useHasUpdatedTx(account)
// Listens for account changes & recently updated transactions to keep portfolio balances fresh in apollo cache // Listens for account changes & recently updated transactions to keep portfolio balances fresh in apollo cache
useEffect(() => { useEffect(() => {
const accountChanged = prevAccount !== undefined && prevAccount !== account const accountChanged = prevAccount !== undefined && prevAccount !== account
if (hasUpdatedTx || accountChanged) { if (hasUpdatedTx || accountChanged) {
// If the drawer is open, fetch balances immediately, else set a flag to fetch on next hover // The parent configures whether these conditions should trigger an immediate fetch,
if (drawerOpen) { // if not, we set a flag to fetch on next hover.
if (shouldFetchOnAccountUpdate) {
fetchBalances() fetchBalances()
} else { } else {
setHasUnfetchedBalances(true) setHasUnfetchedBalances(true)
} }
} }
}, [account, prevAccount, drawerOpen, fetchBalances, hasUpdatedTx]) }, [account, prevAccount, shouldFetchOnAccountUpdate, fetchBalances, hasUpdatedTx, setHasUnfetchedBalances])
const onHover = useCallback(() => { const onHover = useCallback(() => {
if (hasUnfetchedBalances) fetchBalances() if (hasUnfetchedBalances) fetchBalances()
......
...@@ -5,6 +5,7 @@ import { formatCurrencyAmount, NumberType } from '@uniswap/conedison/format' ...@@ -5,6 +5,7 @@ import { formatCurrencyAmount, NumberType } from '@uniswap/conedison/format'
import { Currency, CurrencyAmount, Percent } from '@uniswap/sdk-core' import { Currency, CurrencyAmount, Percent } from '@uniswap/sdk-core'
import { Pair } from '@uniswap/v2-sdk' import { Pair } from '@uniswap/v2-sdk'
import { useWeb3React } from '@web3-react/core' import { useWeb3React } from '@web3-react/core'
import PrefetchBalancesWrapper from 'components/AccountDrawer/PrefetchBalancesWrapper'
import { AutoColumn } from 'components/Column' import { AutoColumn } from 'components/Column'
import { LoadingOpacityContainer, loadingOpacityMixin } from 'components/Loader/styled' import { LoadingOpacityContainer, loadingOpacityMixin } from 'components/Loader/styled'
import CurrencyLogo from 'components/Logo/CurrencyLogo' import CurrencyLogo from 'components/Logo/CurrencyLogo'
...@@ -264,7 +265,7 @@ export default function SwapCurrencyInputPanel({ ...@@ -264,7 +265,7 @@ export default function SwapCurrencyInputPanel({
$loading={loading} $loading={loading}
/> />
)} )}
<PrefetchBalancesWrapper shouldFetchOnAccountUpdate={modalOpen}>
<CurrencySelect <CurrencySelect
disabled={!chainAllowed || disabled} disabled={!chainAllowed || disabled}
visible={currency !== undefined} visible={currency !== undefined}
...@@ -303,6 +304,7 @@ export default function SwapCurrencyInputPanel({ ...@@ -303,6 +304,7 @@ export default function SwapCurrencyInputPanel({
{onCurrencySelect && <StyledDropDown selected={!!currency} />} {onCurrencySelect && <StyledDropDown selected={!!currency} />}
</Aligner> </Aligner>
</CurrencySelect> </CurrencySelect>
</PrefetchBalancesWrapper>
</InputRow> </InputRow>
{Boolean(!hideInput && !hideBalance) && ( {Boolean(!hideInput && !hideBalance) && (
<FiatRow> <FiatRow>
......
...@@ -81,6 +81,7 @@ export default function CommonBases({ ...@@ -81,6 +81,7 @@ export default function CommonBases({
onClick={() => !isSelected && onSelect(currency)} onClick={() => !isSelected && onSelect(currency)}
disable={isSelected} disable={isSelected}
key={currencyId(currency)} key={currencyId(currency)}
data-testid={`common-base-${currency.symbol}`}
> >
<CurrencyLogoFromList currency={currency} /> <CurrencyLogoFromList currency={currency} />
<Text fontWeight={500} fontSize={16}> <Text fontWeight={500} fontSize={16}>
......
import { screen } from '@testing-library/react' import { screen } from '@testing-library/react'
import { Currency, CurrencyAmount as mockCurrencyAmount, Token as mockToken } from '@uniswap/sdk-core' import { Currency, CurrencyAmount as mockCurrencyAmount, Token as mockToken } from '@uniswap/sdk-core'
import { useWeb3React } from '@web3-react/core'
import { DAI, USDC_MAINNET, WBTC } from 'constants/tokens' import { DAI, USDC_MAINNET, WBTC } from 'constants/tokens'
import * as mockJSBI from 'jsbi' import * as mockJSBI from 'jsbi'
import { mocked } from 'test-utils/mocked'
import { render } from 'test-utils/render' import { render } from 'test-utils/render'
import CurrencyList from '.' import CurrencyList from '.'
...@@ -42,6 +44,7 @@ it('renders loading rows when isLoading is true', () => { ...@@ -42,6 +44,7 @@ it('renders loading rows when isLoading is true', () => {
isLoading={true} isLoading={true}
searchQuery="" searchQuery=""
isAddressSearch="" isAddressSearch=""
balances={{}}
/> />
) )
expect(component.findByTestId('loading-rows')).toBeTruthy() expect(component.findByTestId('loading-rows')).toBeTruthy()
...@@ -61,9 +64,37 @@ it('renders currency rows correctly when currencies list is non-empty', () => { ...@@ -61,9 +64,37 @@ it('renders currency rows correctly when currencies list is non-empty', () => {
isLoading={false} isLoading={false}
searchQuery="" searchQuery=""
isAddressSearch="" isAddressSearch=""
balances={{}}
/> />
) )
expect(screen.getByText('Wrapped BTC')).toBeInTheDocument() expect(screen.getByText('Wrapped BTC')).toBeInTheDocument()
expect(screen.getByText('DAI')).toBeInTheDocument() expect(screen.getByText('DAI')).toBeInTheDocument()
expect(screen.getByText('USDC')).toBeInTheDocument() expect(screen.getByText('USDC')).toBeInTheDocument()
}) })
it('renders currency rows correctly with balances', () => {
mocked(useWeb3React).mockReturnValue({
account: '0x52270d8234b864dcAC9947f510CE9275A8a116Db',
isActive: true,
} as ReturnType<typeof useWeb3React>)
render(
<CurrencyList
height={10}
currencies={[DAI, USDC_MAINNET, WBTC]}
otherListTokens={[]}
selectedCurrency={null}
onCurrencySelect={noOp}
isLoading={false}
searchQuery=""
isAddressSearch=""
showCurrencyAmount
balances={{
[DAI.address.toLowerCase()]: { usdValue: 2, balance: 2 },
}}
/>
)
expect(screen.getByText('Wrapped BTC')).toBeInTheDocument()
expect(screen.getByText('DAI')).toBeInTheDocument()
expect(screen.getByText('USDC')).toBeInTheDocument()
expect(screen.getByText('2')).toBeInTheDocument()
})
...@@ -5,6 +5,8 @@ import { useWeb3React } from '@web3-react/core' ...@@ -5,6 +5,8 @@ import { useWeb3React } from '@web3-react/core'
import Loader from 'components/Icons/LoadingSpinner' import Loader from 'components/Icons/LoadingSpinner'
import TokenSafetyIcon from 'components/TokenSafety/TokenSafetyIcon' import TokenSafetyIcon from 'components/TokenSafety/TokenSafetyIcon'
import { checkWarning } from 'constants/tokenSafety' import { checkWarning } from 'constants/tokenSafety'
import { TokenBalances } from 'lib/hooks/useTokenList/sorting'
import tryParseCurrencyAmount from 'lib/utils/tryParseCurrencyAmount'
import { CSSProperties, MutableRefObject, useCallback, useMemo } from 'react' import { CSSProperties, MutableRefObject, useCallback, useMemo } from 'react'
import { Check } from 'react-feather' import { Check } from 'react-feather'
import { FixedSizeList } from 'react-window' import { FixedSizeList } from 'react-window'
...@@ -12,7 +14,6 @@ import { Text } from 'rebass' ...@@ -12,7 +14,6 @@ import { Text } from 'rebass'
import styled from 'styled-components/macro' import styled from 'styled-components/macro'
import { useIsUserAddedToken } from '../../../hooks/Tokens' import { useIsUserAddedToken } from '../../../hooks/Tokens'
import { useCurrencyBalance } from '../../../state/connection/hooks'
import { WrappedTokenInfo } from '../../../state/lists/wrappedTokenInfo' import { WrappedTokenInfo } from '../../../state/lists/wrappedTokenInfo'
import { ThemedText } from '../../../theme' import { ThemedText } from '../../../theme'
import Column, { AutoColumn } from '../../Column' import Column, { AutoColumn } from '../../Column'
...@@ -115,6 +116,7 @@ export function CurrencyRow({ ...@@ -115,6 +116,7 @@ export function CurrencyRow({
style, style,
showCurrencyAmount, showCurrencyAmount,
eventProperties, eventProperties,
balance,
}: { }: {
currency: Currency currency: Currency
onSelect: (hasWarning: boolean) => void onSelect: (hasWarning: boolean) => void
...@@ -123,11 +125,11 @@ export function CurrencyRow({ ...@@ -123,11 +125,11 @@ export function CurrencyRow({
style?: CSSProperties style?: CSSProperties
showCurrencyAmount?: boolean showCurrencyAmount?: boolean
eventProperties: Record<string, unknown> eventProperties: Record<string, unknown>
balance?: CurrencyAmount<Currency>
}) { }) {
const { account } = useWeb3React() const { account } = useWeb3React()
const key = currencyKey(currency) const key = currencyKey(currency)
const customAdded = useIsUserAddedToken(currency) const customAdded = useIsUserAddedToken(currency)
const balance = useCurrencyBalance(account ?? undefined, currency)
const warning = currency.isNative ? null : checkWarning(currency.address) const warning = currency.isNative ? null : checkWarning(currency.address)
const isBlockedToken = !!warning && !warning.canProceed const isBlockedToken = !!warning && !warning.canProceed
const blockedTokenOpacity = '0.6' const blockedTokenOpacity = '0.6'
...@@ -175,7 +177,7 @@ export function CurrencyRow({ ...@@ -175,7 +177,7 @@ export function CurrencyRow({
</Column> </Column>
{showCurrencyAmount ? ( {showCurrencyAmount ? (
<RowFixed style={{ justifySelf: 'flex-end' }}> <RowFixed style={{ justifySelf: 'flex-end' }}>
{balance ? <Balance balance={balance} /> : account ? <Loader /> : null} {account ? balance ? <Balance balance={balance} /> : <Loader /> : null}
{isSelected && <CheckIcon />} {isSelected && <CheckIcon />}
</RowFixed> </RowFixed>
) : ( ) : (
...@@ -235,6 +237,7 @@ export default function CurrencyList({ ...@@ -235,6 +237,7 @@ export default function CurrencyList({
isLoading, isLoading,
searchQuery, searchQuery,
isAddressSearch, isAddressSearch,
balances,
}: { }: {
height: number height: number
currencies: Currency[] currencies: Currency[]
...@@ -247,6 +250,7 @@ export default function CurrencyList({ ...@@ -247,6 +250,7 @@ export default function CurrencyList({
isLoading: boolean isLoading: boolean
searchQuery: string searchQuery: string
isAddressSearch: string | false isAddressSearch: string | false
balances: TokenBalances
}) { }) {
const itemData: Currency[] = useMemo(() => { const itemData: Currency[] = useMemo(() => {
if (otherListTokens && otherListTokens?.length > 0) { if (otherListTokens && otherListTokens?.length > 0) {
...@@ -261,6 +265,12 @@ export default function CurrencyList({ ...@@ -261,6 +265,12 @@ export default function CurrencyList({
const currency = row const currency = row
const balance =
tryParseCurrencyAmount(
String(balances[currency.isNative ? 'ETH' : currency.address?.toLowerCase()]?.balance ?? 0),
currency
) ?? CurrencyAmount.fromRawAmount(currency, 0)
const isSelected = Boolean(currency && selectedCurrency && selectedCurrency.equals(currency)) const isSelected = Boolean(currency && selectedCurrency && selectedCurrency.equals(currency))
const otherSelected = Boolean(currency && otherCurrency && otherCurrency.equals(currency)) const otherSelected = Boolean(currency && otherCurrency && otherCurrency.equals(currency))
const handleSelect = (hasWarning: boolean) => currency && onCurrencySelect(currency, hasWarning) const handleSelect = (hasWarning: boolean) => currency && onCurrencySelect(currency, hasWarning)
...@@ -279,13 +289,23 @@ export default function CurrencyList({ ...@@ -279,13 +289,23 @@ export default function CurrencyList({
otherSelected={otherSelected} otherSelected={otherSelected}
showCurrencyAmount={showCurrencyAmount} showCurrencyAmount={showCurrencyAmount}
eventProperties={formatAnalyticsEventProperties(token, index, data, searchQuery, isAddressSearch)} eventProperties={formatAnalyticsEventProperties(token, index, data, searchQuery, isAddressSearch)}
balance={balance}
/> />
) )
} else { } else {
return null return null
} }
}, },
[onCurrencySelect, otherCurrency, selectedCurrency, showCurrencyAmount, isLoading, isAddressSearch, searchQuery] [
selectedCurrency,
otherCurrency,
isLoading,
onCurrencySelect,
showCurrencyAmount,
searchQuery,
isAddressSearch,
balances,
]
) )
const itemKey = useCallback((index: number, data: typeof itemData) => { const itemKey = useCallback((index: number, data: typeof itemData) => {
......
...@@ -4,18 +4,19 @@ import { Trace } from '@uniswap/analytics' ...@@ -4,18 +4,19 @@ import { Trace } from '@uniswap/analytics'
import { InterfaceEventName, InterfaceModalName } from '@uniswap/analytics-events' import { InterfaceEventName, InterfaceModalName } from '@uniswap/analytics-events'
import { Currency, Token } from '@uniswap/sdk-core' import { Currency, Token } from '@uniswap/sdk-core'
import { useWeb3React } from '@web3-react/core' import { useWeb3React } from '@web3-react/core'
import { useCachedPortfolioBalancesQuery } from 'components/AccountDrawer/PrefetchBalancesWrapper'
import { sendEvent } from 'components/analytics' import { sendEvent } from 'components/analytics'
import { supportedChainIdFromGQLChain } from 'graphql/data/util'
import useDebounce from 'hooks/useDebounce' import useDebounce from 'hooks/useDebounce'
import { useOnClickOutside } from 'hooks/useOnClickOutside' import { useOnClickOutside } from 'hooks/useOnClickOutside'
import useToggle from 'hooks/useToggle' import useToggle from 'hooks/useToggle'
import useNativeCurrency from 'lib/hooks/useNativeCurrency' import useNativeCurrency from 'lib/hooks/useNativeCurrency'
import { getTokenFilter } from 'lib/hooks/useTokenList/filtering' import { getTokenFilter } from 'lib/hooks/useTokenList/filtering'
import { tokenComparator, useSortTokensByQuery } from 'lib/hooks/useTokenList/sorting' import { TokenBalances, tokenComparator, useSortTokensByQuery } from 'lib/hooks/useTokenList/sorting'
import { ChangeEvent, KeyboardEvent, RefObject, useCallback, useEffect, useMemo, useRef, useState } from 'react' import { ChangeEvent, KeyboardEvent, RefObject, useCallback, useEffect, useMemo, useRef, useState } from 'react'
import AutoSizer from 'react-virtualized-auto-sizer' import AutoSizer from 'react-virtualized-auto-sizer'
import { FixedSizeList } from 'react-window' import { FixedSizeList } from 'react-window'
import { Text } from 'rebass' import { Text } from 'rebass'
import { useAllTokenBalances } from 'state/connection/hooks'
import styled, { useTheme } from 'styled-components/macro' import styled, { useTheme } from 'styled-components/macro'
import { UserAddedToken } from 'types/tokens' import { UserAddedToken } from 'types/tokens'
...@@ -61,7 +62,7 @@ export function CurrencySearch({ ...@@ -61,7 +62,7 @@ export function CurrencySearch({
isOpen, isOpen,
onlyShowCurrenciesWithBalance, onlyShowCurrenciesWithBalance,
}: CurrencySearchProps) { }: CurrencySearchProps) {
const { chainId } = useWeb3React() const { chainId, account } = useWeb3React()
const theme = useTheme() const theme = useTheme()
const [tokenLoaderTimerElapsed, setTokenLoaderTimerElapsed] = useState(false) const [tokenLoaderTimerElapsed, setTokenLoaderTimerElapsed] = useState(false)
...@@ -90,33 +91,52 @@ export function CurrencySearch({ ...@@ -90,33 +91,52 @@ export function CurrencySearch({
return Object.values(defaultTokens).filter(getTokenFilter(debouncedQuery)) return Object.values(defaultTokens).filter(getTokenFilter(debouncedQuery))
}, [defaultTokens, debouncedQuery]) }, [defaultTokens, debouncedQuery])
const [balances, balancesAreLoading] = useAllTokenBalances() const { data, loading: balancesAreLoading } = useCachedPortfolioBalancesQuery({ account })
const balances: TokenBalances = useMemo(() => {
return (
data?.portfolios?.[0].tokenBalances?.reduce((balanceMap, tokenBalance) => {
if (
tokenBalance.token?.chain &&
supportedChainIdFromGQLChain(tokenBalance.token?.chain) === chainId &&
tokenBalance.token?.address !== undefined &&
tokenBalance.denominatedValue?.value !== undefined
) {
const address = tokenBalance.token?.standard === 'ERC20' ? tokenBalance.token?.address?.toLowerCase() : 'ETH'
const usdValue = tokenBalance.denominatedValue?.value
const balance = tokenBalance.quantity
balanceMap[address] = { usdValue, balance: balance ?? 0 }
}
return balanceMap
}, {} as TokenBalances) ?? {}
)
}, [chainId, data?.portfolios])
const sortedTokens: Token[] = useMemo( const sortedTokens: Token[] = useMemo(
() => () =>
!balancesAreLoading !balancesAreLoading
? filteredTokens ? filteredTokens
.filter((token) => { .filter((token) => {
if (onlyShowCurrenciesWithBalance) { if (onlyShowCurrenciesWithBalance) {
return balances[token.address]?.greaterThan(0) return balances[token.address?.toLowerCase()]?.usdValue > 0
} }
// If there is no query, filter out unselected user-added tokens with no balance. // If there is no query, filter out unselected user-added tokens with no balance.
if (!debouncedQuery && token instanceof UserAddedToken) { if (!debouncedQuery && token instanceof UserAddedToken) {
if (selectedCurrency?.equals(token) || otherSelectedCurrency?.equals(token)) return true if (selectedCurrency?.equals(token) || otherSelectedCurrency?.equals(token)) return true
return balances[token.address]?.greaterThan(0) return balances[token.address.toLowerCase()]?.usdValue > 0
} }
return true return true
}) })
.sort(tokenComparator.bind(null, balances)) .sort(tokenComparator.bind(null, balances))
: [], : filteredTokens,
[ [
balances,
balancesAreLoading, balancesAreLoading,
debouncedQuery,
filteredTokens, filteredTokens,
otherSelectedCurrency, balances,
selectedCurrency,
onlyShowCurrenciesWithBalance, onlyShowCurrenciesWithBalance,
debouncedQuery,
selectedCurrency,
otherSelectedCurrency,
] ]
) )
const isLoading = Boolean(balancesAreLoading && !tokenLoaderTimerElapsed) const isLoading = Boolean(balancesAreLoading && !tokenLoaderTimerElapsed)
...@@ -131,7 +151,7 @@ export function CurrencySearch({ ...@@ -131,7 +151,7 @@ export function CurrencySearch({
const tokens = filteredSortedTokens.filter((t) => !(t.equals(wrapped) || (disableNonToken && t.isNative))) const tokens = filteredSortedTokens.filter((t) => !(t.equals(wrapped) || (disableNonToken && t.isNative)))
const shouldShowWrapped = const shouldShowWrapped =
!onlyShowCurrenciesWithBalance || (!balancesAreLoading && balances[wrapped.address]?.greaterThan(0)) !onlyShowCurrenciesWithBalance || (!balancesAreLoading && balances[wrapped.address]?.usdValue > 0)
const natives = ( const natives = (
disableNonToken || native.equals(wrapped) ? [wrapped] : shouldShowWrapped ? [native, wrapped] : [native] disableNonToken || native.equals(wrapped) ? [wrapped] : shouldShowWrapped ? [native, wrapped] : [native]
).filter((n) => n.symbol?.toLowerCase()?.indexOf(s) !== -1 || n.name?.toLowerCase()?.indexOf(s) !== -1) ).filter((n) => n.symbol?.toLowerCase()?.indexOf(s) !== -1 || n.name?.toLowerCase()?.indexOf(s) !== -1)
...@@ -280,6 +300,7 @@ export function CurrencySearch({ ...@@ -280,6 +300,7 @@ export function CurrencySearch({
isLoading={isLoading} isLoading={isLoading}
searchQuery={searchQuery} searchQuery={searchQuery}
isAddressSearch={isAddressSearch} isAddressSearch={isAddressSearch}
balances={balances}
/> />
)} )}
</AutoSizer> </AutoSizer>
......
...@@ -212,8 +212,9 @@ function Web3StatusInner() { ...@@ -212,8 +212,9 @@ function Web3StatusInner() {
} }
export default function Web3Status() { export default function Web3Status() {
const [isDrawerOpen] = useAccountDrawer()
return ( return (
<PrefetchBalancesWrapper> <PrefetchBalancesWrapper shouldFetchOnAccountUpdate={isDrawerOpen}>
<Web3StatusInner /> <Web3StatusInner />
<Portal> <Portal>
<PortfolioDrawer /> <PortfolioDrawer />
......
import { Currency, CurrencyAmount, Token } from '@uniswap/sdk-core' import { Token } from '@uniswap/sdk-core'
import { TokenInfo } from '@uniswap/token-lists' import { TokenInfo } from '@uniswap/token-lists'
import { useMemo } from 'react' import { useMemo } from 'react'
/** Sorts currency amounts (descending). */ /** Sorts currency amounts (descending). */
function balanceComparator(a?: CurrencyAmount<Currency>, b?: CurrencyAmount<Currency>) { function balanceComparator(a?: number, b?: number) {
if (a && b) { if (a && b) {
return a.greaterThan(b) ? -1 : a.equalTo(b) ? 0 : 1 return a > b ? -1 : a === b ? 0 : 1
} else if (a?.greaterThan('0')) { } else if ((a ?? 0) > 0) {
return -1 return -1
} else if (b?.greaterThan('0')) { } else if ((b ?? 0) > 0) {
return 1 return 1
} }
return 0 return 0
} }
type TokenBalances = { [tokenAddress: string]: CurrencyAmount<Token> | undefined } export type TokenBalances = { [tokenAddress: string]: { usdValue: number; balance: number } }
/** Sorts tokens by currency amount (descending), then safety, then symbol (ascending). */ /** Sorts tokens by currency amount (descending), then safety, then symbol (ascending). */
export function tokenComparator(balances: TokenBalances, a: Token, b: Token) { export function tokenComparator(balances: TokenBalances, a: Token, b: Token) {
// Sorts by balances // Sorts by balances
const balanceComparison = balanceComparator(balances[a.address], balances[b.address]) const balanceComparison = balanceComparator(
balances[a.address.toLowerCase()]?.usdValue,
balances[b.address.toLowerCase()]?.usdValue
)
if (balanceComparison !== 0) return balanceComparison if (balanceComparison !== 0) return balanceComparison
// Sorts by symbol // Sorts by symbol
......
...@@ -1761,6 +1761,7 @@ exports[`disable nft on landing page does not render nft information and card 1` ...@@ -1761,6 +1761,7 @@ exports[`disable nft on landing page does not render nft information and card 1`
type="text" type="text"
value="" value=""
/> />
<div>
<button <button
class="c23 c24 c25 c26 open-currency-select-button" class="c23 c24 c25 c26 open-currency-select-button"
> >
...@@ -1794,6 +1795,7 @@ exports[`disable nft on landing page does not render nft information and card 1` ...@@ -1794,6 +1795,7 @@ exports[`disable nft on landing page does not render nft information and card 1`
</span> </span>
</button> </button>
</div> </div>
</div>
<div <div
class="c32 c33" class="c32 c33"
> >
...@@ -1872,6 +1874,7 @@ exports[`disable nft on landing page does not render nft information and card 1` ...@@ -1872,6 +1874,7 @@ exports[`disable nft on landing page does not render nft information and card 1`
type="text" type="text"
value="" value=""
/> />
<div>
<button <button
class="c23 c24 c25 c39 open-currency-select-button" class="c23 c24 c25 c39 open-currency-select-button"
> >
...@@ -1895,6 +1898,7 @@ exports[`disable nft on landing page does not render nft information and card 1` ...@@ -1895,6 +1898,7 @@ exports[`disable nft on landing page does not render nft information and card 1`
</span> </span>
</button> </button>
</div> </div>
</div>
<div <div
class="c32 c33" class="c32 c33"
> >
...@@ -4263,6 +4267,7 @@ exports[`disable nft on landing page renders nft information and card 1`] = ` ...@@ -4263,6 +4267,7 @@ exports[`disable nft on landing page renders nft information and card 1`] = `
type="text" type="text"
value="" value=""
/> />
<div>
<button <button
class="c23 c24 c25 c26 open-currency-select-button" class="c23 c24 c25 c26 open-currency-select-button"
> >
...@@ -4296,6 +4301,7 @@ exports[`disable nft on landing page renders nft information and card 1`] = ` ...@@ -4296,6 +4301,7 @@ exports[`disable nft on landing page renders nft information and card 1`] = `
</span> </span>
</button> </button>
</div> </div>
</div>
<div <div
class="c32 c33" class="c32 c33"
> >
...@@ -4374,6 +4380,7 @@ exports[`disable nft on landing page renders nft information and card 1`] = ` ...@@ -4374,6 +4380,7 @@ exports[`disable nft on landing page renders nft information and card 1`] = `
type="text" type="text"
value="" value=""
/> />
<div>
<button <button
class="c23 c24 c25 c39 open-currency-select-button" class="c23 c24 c25 c39 open-currency-select-button"
> >
...@@ -4397,6 +4404,7 @@ exports[`disable nft on landing page renders nft information and card 1`] = ` ...@@ -4397,6 +4404,7 @@ exports[`disable nft on landing page renders nft information and card 1`] = `
</span> </span>
</button> </button>
</div> </div>
</div>
<div <div
class="c32 c33" class="c32 c33"
> >
......
import { CurrencyAmount, Token } from '@uniswap/sdk-core'
import { useWeb3React } from '@web3-react/core'
import { useTokenBalancesWithLoadingIndicator } from 'lib/hooks/useCurrencyBalance'
import { useMemo } from 'react'
import { useDefaultActiveTokens } from '../../hooks/Tokens'
export { export {
default as useCurrencyBalance, default as useCurrencyBalance,
useCurrencyBalances, useCurrencyBalances,
...@@ -13,12 +6,3 @@ export { ...@@ -13,12 +6,3 @@ export {
useTokenBalances, useTokenBalances,
useTokenBalancesWithLoadingIndicator, useTokenBalancesWithLoadingIndicator,
} from 'lib/hooks/useCurrencyBalance' } from 'lib/hooks/useCurrencyBalance'
// mimics useAllBalances
export function useAllTokenBalances(): [{ [tokenAddress: string]: CurrencyAmount<Token> | undefined }, boolean] {
const { account, chainId } = useWeb3React()
const allTokens = useDefaultActiveTokens(chainId)
const allTokensArray = useMemo(() => Object.values(allTokens ?? {}), [allTokens])
const [balances, balancesIsLoading] = useTokenBalancesWithLoadingIndicator(account ?? undefined, allTokensArray)
return [balances ?? {}, balancesIsLoading]
}
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