Skip to content

Commit

Permalink
Merge pull request #739 from alephium/next
Browse files Browse the repository at this point in the history
Next -> master
  • Loading branch information
nop33 authored Jul 16, 2024
2 parents 8547a9a + c9c2c32 commit 2d5f135
Show file tree
Hide file tree
Showing 34 changed files with 2,266 additions and 376 deletions.
6 changes: 5 additions & 1 deletion apps/desktop-wallet/.eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,5 +17,9 @@ along with the library. If not, see <http://www.gnu.org/licenses/>.
*/

module.exports = {
extends: ['@alephium/eslint-config/base', '@alephium/eslint-config/react']
extends: [
'@alephium/eslint-config/base',
'@alephium/eslint-config/react',
'plugin:@tanstack/eslint-plugin-query/recommended'
]
}
5 changes: 4 additions & 1 deletion apps/desktop-wallet/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@
"not op_mini all"
],
"dependencies": {
"@tanstack/react-query": "5.45.0",
"@tanstack/react-query-devtools": "^5.50.1",
"electron-context-menu": "^3.1.2",
"electron-is-dev": "^2.0.0",
"electron-updater": "^5.3.0"
Expand All @@ -63,6 +65,7 @@
"@electron/notarize": "^1.2.3",
"@json-rpc-tools/utils": "^1.7.6",
"@reduxjs/toolkit": "^1.9.1",
"@tanstack/eslint-plugin-query": "^5.50.1",
"@testing-library/jest-dom": "^5.14.1",
"@testing-library/react": "^14.0.0",
"@types/events": "^3.0.0",
Expand Down Expand Up @@ -137,7 +140,7 @@
"stylis": "^4.0.0",
"ts-json-schema-generator": "^1.5.0",
"type-fest": "^3.5.1",
"typescript": "^5.2.2",
"typescript": "^5.5.3",
"vite": "^4.5.2",
"vite-plugin-svgr": "^3.2.0",
"vite-tsconfig-paths": "^4.2.0",
Expand Down
36 changes: 3 additions & 33 deletions apps/desktop-wallet/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,15 +19,12 @@ along with the library. If not, see <http://www.gnu.org/licenses/>.
import {
AddressHash,
localStorageNetworkSettingsMigrated,
PRICES_REFRESH_INTERVAL,
selectDoVerifiedFungibleTokensNeedInitialization,
syncTokenCurrentPrices,
syncTokenPriceHistories,
syncUnknownTokensInfo,
syncVerifiedFungibleTokens
} from '@alephium/shared'
import { useInitializeClient, useInterval } from '@alephium/shared-react'
import { ALPH } from '@alephium/token-list'
import { difference, union } from 'lodash'
import { usePostHog } from 'posthog-js/react'
import { useCallback, useEffect, useMemo, useState } from 'react'
Expand All @@ -40,6 +37,7 @@ import SplashScreen from '@/components/SplashScreen'
import { WalletConnectContextProvider } from '@/contexts/walletconnect'
import useAnalytics from '@/features/analytics/useAnalytics'
import AutoUpdateSnackbar from '@/features/autoUpdate/AutoUpdateSnackbar'
import { useAlphPrice } from '@/features/tokenPrices/tokenPricesHooks'
import { useAppDispatch, useAppSelector } from '@/hooks/redux'
import useAutoLock from '@/hooks/useAutoLock'
import Router from '@/routes'
Expand Down Expand Up @@ -88,6 +86,7 @@ const App = () => {
const showDevIndication = useDevModeShortcut()
const posthog = usePostHog()
const { sendAnalytics } = useAnalytics()
useAlphPrice() // TODO: Group with other prefetch queries in a usePrefetchData hook

const addressesStatus = useAppSelector((s) => s.addresses.status)
const isSyncingAddressData = useAppSelector((s) => s.addresses.syncingAddressData)
Expand Down Expand Up @@ -244,7 +243,7 @@ const App = () => {
sendAnalytics
])

// Fetch verified tokens from GitHub token-list and sync current and historical prices for each verified fungible
// Fetch verified tokens from GitHub token-list and sync historical prices for each verified fungible
// token found in each address
useEffect(() => {
if (networkStatus === 'online' && !isLoadingVerifiedFungibleTokens) {
Expand All @@ -253,7 +252,6 @@ const App = () => {
} else if (verifiedFungibleTokenSymbols.uninitialized.length > 0) {
const symbols = verifiedFungibleTokenSymbols.uninitialized

dispatch(syncTokenCurrentPrices({ verifiedFungibleTokenSymbols: symbols, currency: settings.fiatCurrency }))
dispatch(syncTokenPriceHistories({ verifiedFungibleTokenSymbols: symbols, currency: settings.fiatCurrency }))
}
}
Expand All @@ -266,34 +264,6 @@ const App = () => {
verifiedFungibleTokensNeedInitialization
])

useEffect(() => {
if (
networkStatus === 'online' &&
!isLoadingVerifiedFungibleTokens &&
verifiedFungibleTokenSymbols.uninitialized.length > 1
) {
console.log(
'TODO: Sync address verified tokens balance histories for',
verifiedFungibleTokenSymbols.uninitialized.filter((symbol) => symbol !== ALPH.symbol)
)
}
}, [isLoadingVerifiedFungibleTokens, networkStatus, verifiedFungibleTokenSymbols.uninitialized])

const refreshTokensLatestPrice = useCallback(() => {
dispatch(
syncTokenCurrentPrices({
verifiedFungibleTokenSymbols: verifiedFungibleTokenSymbols.withPriceHistory,
currency: settings.fiatCurrency
})
)
}, [dispatch, settings.fiatCurrency, verifiedFungibleTokenSymbols.withPriceHistory])

useInterval(
refreshTokensLatestPrice,
PRICES_REFRESH_INTERVAL,
networkStatus !== 'online' || verifiedFungibleTokenSymbols.withPriceHistory.length === 0
)

const refreshAddressesData = useCallback(() => {
try {
dispatch(syncAddressesData(addressesWithPendingTxs))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import AddressBadge from '@/components/AddressBadge'
import AssetBadge from '@/components/AssetBadge'
import Badge from '@/components/Badge'
import SelectOptionItemContent from '@/components/Inputs/SelectOptionItemContent'
import { useSortTokensByWorth } from '@/features/tokenPrices/tokenPricesHooks'
import { useAppSelector } from '@/hooks/redux'
import { makeSelectAddressesTokens } from '@/storage/addresses/addressesSelectors'
import { Address } from '@/types/addresses'
Expand All @@ -39,7 +40,7 @@ const SelectOptionAddress = ({ address, isSelected, className }: SelectOptionAdd
const selectAddressesTokens = useMemo(makeSelectAddressesTokens, [])
const assets = useAppSelector((s) => selectAddressesTokens(s, address.hash))

const knownAssetsWithBalance = assets.filter((a) => a.balance > 0 && a.name)
const knownAssetsWithBalance = useSortTokensByWorth(assets.filter((a) => a.balance > 0 && a.name))
const unknownAssetsNb = assets.filter((a) => a.balance > 0 && !a.name).length
const showAssetList = knownAssetsWithBalance.length > 0 || unknownAssetsNb > 0

Expand Down
97 changes: 97 additions & 0 deletions apps/desktop-wallet/src/features/tokenPrices/tokenPricesHooks.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
/*
Copyright 2018 - 2024 The Alephium Authors
This file is part of the alephium project.
The library is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
The library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with the library. If not, see <http://www.gnu.org/licenses/>.
*/

import { AddressHash, Asset, calculateAmountWorth, client, ONE_MINUTE_MS, TOKENS_QUERY_LIMIT } from '@alephium/shared'
import { ALPH } from '@alephium/token-list'
import { explorer } from '@alephium/web3'
import { useQueries } from '@tanstack/react-query'
import { chunk, orderBy } from 'lodash'
import { useMemo } from 'react'

import { useAppSelector } from '@/hooks/redux'
import {
makeSelectAddressesKnownFungibleTokens,
makeSelectAddressesListedFungibleTokenSymbols
} from '@/storage/addresses/addressesSelectors'

export const useAddressesTokensPrices = () => {
const currency = useAppSelector((s) => s.settings.fiatCurrency).toLowerCase()
const addressTokensSymbols = useAppSelector(useMemo(makeSelectAddressesListedFungibleTokenSymbols, [])) // TODO: To be replaced when tokens are fetched with Tanstack
const addressTokensSymbolsWithPrice = addressTokensSymbols.filter((symbol) => symbol in explorer.TokensWithPrice)

const { data, isPending } = useQueries({
queries: chunk(addressTokensSymbolsWithPrice, TOKENS_QUERY_LIMIT).map((symbols) => ({
queryKey: ['tokenPrices', symbols, { currency }],
queryFn: async () =>
(await client.explorer.market.postMarketPrices({ currency }, symbols)).map((price, i) => ({
price,
symbol: symbols[i]
})),
refetchInterval: ONE_MINUTE_MS
})),
combine: (results) => ({
data: results.flatMap(({ data }) => data).filter((price) => !!price),
isPending: results.some(({ isPending }) => isPending)
})
})

return { data, isPending }
}

export const useAlphPrice = () => {
const { data: tokenPrices } = useAddressesTokensPrices()

return tokenPrices.find((token) => token.symbol === ALPH.symbol)?.price
}

export const useSortTokensByWorth = (tokens: Asset[]) => {
const { data: tokenPrices } = useAddressesTokensPrices()

const tokensWithWorth = tokens.map((token) => {
const tokenPrice = tokenPrices.find((t) => t.symbol === token.symbol)

return {
...token,
worth: tokenPrice ? calculateAmountWorth(token.balance, tokenPrice.price, token.decimals) : undefined
}
})

return orderBy(
tokensWithWorth,
[
(a) => (a.verified ? 0 : 1),
(a) => a.worth ?? -1,
(a) => a.verified === undefined,
(a) => a.name?.toLowerCase(),
'id'
],
['asc', 'desc', 'asc', 'asc', 'asc']
)
}

export const useAddressesTokensWorth = (addressHashes?: AddressHash[] | AddressHash) => {
const { data: tokenPrices } = useAddressesTokensPrices()
const selectAddressesKnownFungibleTokens = useMemo(makeSelectAddressesKnownFungibleTokens, [])
const tokens = useAppSelector((s) => selectAddressesKnownFungibleTokens(s, addressHashes))

return tokenPrices.reduce((totalWorth, { symbol, price }) => {
const token = tokens.find((t) => t.symbol === symbol)

return token ? totalWorth + calculateAmountWorth(token.balance, price, token.decimals) : totalWorth
}, 0)
}
26 changes: 25 additions & 1 deletion apps/desktop-wallet/src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,10 @@ import '@/index.css' // Importing CSS through CSS file to avoid font flickering
import '@/i18n'
import '@yaireo/tagify/dist/tagify.css' // Tagify CSS: important to import after index.css file

import { MAX_API_RETRIES, ONE_MINUTE_MS } from '@alephium/shared'
import isPropValid from '@emotion/is-prop-valid'
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
import { ReactQueryDevtools } from '@tanstack/react-query-devtools'
import { StrictMode, Suspense } from 'react'
import ReactDOM from 'react-dom'
import { Provider } from 'react-redux'
Expand All @@ -38,14 +41,35 @@ import { store } from '@/storage/store'
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
// const root = createRoot(document.getElementById('root')!)

const queryClient = new QueryClient({
defaultOptions: {
queries: {
staleTime: ONE_MINUTE_MS,
retry: (failureCount, error) => {
if (error.message !== '[API Error] - status code: 429') {
return false
} else if (failureCount > MAX_API_RETRIES) {
console.error(`API failed after ${MAX_API_RETRIES} retries, won't retry anymore`, error)
return false
}

return true
}
}
}
})

ReactDOM.render(
<AnalyticsProvider>
<StrictMode>
<Provider store={store}>
<Router>
<Suspense fallback="loading">
<StyleSheetManager shouldForwardProp={shouldForwardProp}>
<App />
<QueryClientProvider client={queryClient}>
<App />
<ReactQueryDevtools initialIsOpen={false} />
</QueryClientProvider>
<Tooltips />
</StyleSheetManager>
</Suspense>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,12 +28,16 @@ import AddressColorIndicator from '@/components/AddressColorIndicator'
import Amount from '@/components/Amount'
import AssetBadge from '@/components/AssetBadge'
import SkeletonLoader from '@/components/SkeletonLoader'
import {
useAddressesTokensPrices,
useAddressesTokensWorth,
useSortTokensByWorth
} from '@/features/tokenPrices/tokenPricesHooks'
import { useAppSelector } from '@/hooks/redux'
import AddressDetailsModal from '@/modals/AddressDetailsModal'
import ModalPortal from '@/modals/ModalPortal'
import {
makeSelectAddressesTokens,
makeSelectAddressesTokensWorth,
selectAddressByHash,
selectIsStateUninitialized
} from '@/storage/addresses/addressesSelectors'
Expand All @@ -54,13 +58,12 @@ const AddressGridRow = ({ addressHash, className }: AddressGridRowProps) => {
const stateUninitialized = useAppSelector(selectIsStateUninitialized)
const verifiedFungibleTokensNeedInitialization = useAppSelector(selectDoVerifiedFungibleTokensNeedInitialization)
const fiatCurrency = useAppSelector((s) => s.settings.fiatCurrency)
const areTokenPricesInitialized = useAppSelector((s) => s.tokenPrices.status === 'initialized')
const selectAddessesTokensWorth = useMemo(makeSelectAddressesTokensWorth, [])
const balanceInFiat = useAppSelector((s) => selectAddessesTokensWorth(s, addressHash))
const { isPending: isPendingTokenPrices } = useAddressesTokensPrices()
const balanceInFiat = useAddressesTokensWorth(addressHash)

const [isAddressDetailsModalOpen, setIsAddressDetailsModalOpen] = useState(false)

const assetsWithBalance = assets.filter((asset) => asset.balance > 0)
const assetsWithBalance = useSortTokensByWorth(assets.filter((asset) => asset.balance > 0))
const [displayedAssets, ...hiddenAssetsChunks] = chunk(assetsWithBalance, maxDisplayedAssets)
const hiddenAssets = hiddenAssetsChunks.flat()

Expand Down Expand Up @@ -127,7 +130,7 @@ const AddressGridRow = ({ addressHash, className }: AddressGridRowProps) => {
{stateUninitialized ? <SkeletonLoader height="18.5px" /> : <Amount value={BigInt(address.balance)} />}
</AmountCell>
<FiatAmountCell>
{stateUninitialized || !areTokenPricesInitialized ? (
{stateUninitialized || isPendingTokenPrices ? (
<SkeletonLoader height="18.5px" />
) : (
<Amount value={balanceInFiat} isFiat suffix={CURRENCIES[fiatCurrency].symbol} />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ along with the library. If not, see <http://www.gnu.org/licenses/>.
import { AddressHash, CURRENCIES } from '@alephium/shared'
import { motion } from 'framer-motion'
import { ChevronRight } from 'lucide-react'
import { useMemo, useState } from 'react'
import { useState } from 'react'
import { useTranslation } from 'react-i18next'
import { useNavigate } from 'react-router-dom'
import styled from 'styled-components'
Expand All @@ -32,14 +32,11 @@ import FocusableContent from '@/components/FocusableContent'
import SkeletonLoader from '@/components/SkeletonLoader'
import { ExpandableTable, ExpandRow, TableHeader } from '@/components/Table'
import TableCellAmount from '@/components/TableCellAmount'
import { useAddressesTokensWorth } from '@/features/tokenPrices/tokenPricesHooks'
import { useAppSelector } from '@/hooks/redux'
import AddressDetailsModal from '@/modals/AddressDetailsModal'
import ModalPortal from '@/modals/ModalPortal'
import {
makeSelectAddressesTokensWorth,
selectAllAddresses,
selectIsStateUninitialized
} from '@/storage/addresses/addressesSelectors'
import { selectAllAddresses, selectIsStateUninitialized } from '@/storage/addresses/addressesSelectors'
import { Address } from '@/types/addresses'

interface AddressesContactsListProps {
Expand Down Expand Up @@ -116,8 +113,7 @@ const AddressesList = ({ className, isExpanded, onExpand, onAddressClick }: Addr
}

const AddressWorth = ({ addressHash }: { addressHash: AddressHash }) => {
const selectAddessesTokensWorth = useMemo(makeSelectAddressesTokensWorth, [])
const balanceInFiat = useAppSelector((s) => selectAddessesTokensWorth(s, addressHash))
const balanceInFiat = useAddressesTokensWorth(addressHash)
const fiatCurrency = useAppSelector((s) => s.settings.fiatCurrency)

return <AmountStyled value={balanceInFiat} isFiat suffix={CURRENCIES[fiatCurrency].symbol} tabIndex={0} />
Expand Down
Loading

0 comments on commit 2d5f135

Please sign in to comment.