Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fetch token prices & history using new Explorer BE endpoints (rebased and fixed) #322

Merged
merged 34 commits into from
Feb 5, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
98916f1
Port work - fetch token prices (wip)
mvaivre Nov 17, 2023
2ecc943
New endpoints work, init new prices storage
mvaivre Nov 20, 2023
4e44bf2
WIP: Dedicated slices
mvaivre Jan 2, 2024
8eb05e2
selectAlphPrice selector + lint
mvaivre Jan 2, 2024
2c59a1d
Mention that total value is only about ALPH for now
mvaivre Jan 3, 2024
212403d
Remove unused code while we wait for explorer-be amount history endpo…
mvaivre Jan 3, 2024
a0f950b
Start migrating price history from coingecko to own service
mvaivre Jan 3, 2024
4652961
History properly fetched and shown
mvaivre Jan 3, 2024
d2636e9
Add changeset
mvaivre Jan 3, 2024
a460bc5
Better naming
mvaivre Jan 9, 2024
69010c2
price => value
mvaivre Jan 9, 2024
0d112f7
More efficient selectors
mvaivre Jan 9, 2024
a29e363
Fix rebase merge issues
nop33 Jan 23, 2024
d1e8d6a
Update web3 packages to 0.29.1
nop33 Jan 23, 2024
b227159
Update licence headers
nop33 Jan 29, 2024
a886c41
Rename for clarity
nop33 Jan 29, 2024
7cc0dce
Fix loading indication of worth and charts
nop33 Jan 29, 2024
6d67be6
Rename asset info to fungible token
nop33 Jan 29, 2024
1bf54bb
Append "balances" for clarity
nop33 Jan 29, 2024
d1242be
Get rid of useless loadingStarted
nop33 Jan 29, 2024
00e613f
Fetch ALPH historic balances earlier
nop33 Jan 29, 2024
f6af0fe
Flush price history when currency changes
nop33 Jan 29, 2024
00bbca3
Simplify ALPH price selectors
nop33 Jan 29, 2024
d150ad2
Fix price rendering in tokens table
nop33 Jan 29, 2024
7884e32
Rename and fix greeting message
nop33 Jan 29, 2024
8247702
Improve loading speed (by a lot)
nop33 Jan 30, 2024
22a7ee7
Reimplement polling mechanism
nop33 Jan 30, 2024
8d653e1
Fix greeting message animation
nop33 Jan 30, 2024
4c65e46
Rename for clarity
nop33 Jan 30, 2024
6bb9058
Use token prices status instead of loading
nop33 Jan 30, 2024
65f9b8b
Fix verified tokens initialization
nop33 Jan 30, 2024
bd02a50
Calculate total worth including tokens
nop33 Feb 1, 2024
af533a9
Create PRICES_REFRESH_INTERVAL const
nop33 Feb 5, 2024
c04f2cf
Merge branch 'master' into fetch-token-prices-rebased
nop33 Feb 5, 2024
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
5 changes: 5 additions & 0 deletions .changeset/young-zoos-fry.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'alephium-desktop-wallet': patch
---

Fetch token prices & ALPH history using new Explorer BE endpoints
92 changes: 76 additions & 16 deletions apps/desktop-wallet/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ along with the library. If not, see <http://www.gnu.org/licenses/>.
*/

import { AddressHash } from '@alephium/shared'
import { ALPH } from '@alephium/token-list'
import { AnimatePresence } from 'framer-motion'
import { difference } from 'lodash'
import { usePostHog } from 'posthog-js/react'
Expand All @@ -34,15 +35,20 @@ import { WalletConnectContextProvider } from '@/contexts/walletconnect'
import { useAppDispatch, useAppSelector } from '@/hooks/redux'
import UpdateWalletModal from '@/modals/UpdateWalletModal'
import Router from '@/routes'
import { syncAddressesData, syncAddressesHistoricBalances } from '@/storage/addresses/addressesActions'
import { makeSelectAddressesUnknownTokens, selectAddressIds } from '@/storage/addresses/addressesSelectors'
import { syncNetworkTokensInfo, syncUnknownTokensInfo } from '@/storage/assets/assetsActions'
import { selectIsTokensMetadataUninitialized } from '@/storage/assets/assetsSelectors'
import { syncAddressesAlphHistoricBalances, syncAddressesData } from '@/storage/addresses/addressesActions'
import {
makeSelectAddressesUnknownTokens,
selectAddressIds,
selectAllAddressVerifiedFungibleTokenSymbols
} from '@/storage/addresses/addressesSelectors'
import { syncUnknownTokensInfo, syncVerifiedFungibleTokens } from '@/storage/assets/assetsActions'
import { selectDoVerifiedFungibleTokensNeedInitialization } from '@/storage/assets/assetsSelectors'
import {
devModeShortcutDetected,
localStorageDataMigrated,
localStorageDataMigrationFailed
} from '@/storage/global/globalActions'
import { syncTokenCurrentPrices, syncTokenPriceHistories } from '@/storage/prices/pricesActions'
import { apiClientInitFailed, apiClientInitSucceeded } from '@/storage/settings/networkActions'
import { systemLanguageMatchFailed, systemLanguageMatchSucceeded } from '@/storage/settings/settingsActions'
import { makeSelectAddressesHashesWithPendingTransactions } from '@/storage/transactions/transactionsSelectors'
Expand All @@ -57,6 +63,8 @@ import { useInterval } from '@/utils/hooks'
import { migrateGeneralSettings, migrateNetworkSettings, migrateWalletData } from '@/utils/migration'
import { languageOptions } from '@/utils/settings'

const PRICES_REFRESH_INTERVAL = 60000

const App = () => {
const { newVersion, newVersionDownloadTriggered } = useGlobalContext()
const dispatch = useAppDispatch()
Expand All @@ -65,7 +73,6 @@ const App = () => {
const addressesWithPendingTxs = useAppSelector(selectAddressesHashesWithPendingTransactions)
const network = useAppSelector((s) => s.network)
const theme = useAppSelector((s) => s.global.theme)
const assetsInfo = useAppSelector((s) => s.assetsInfo)
const loading = useAppSelector((s) => s.global.loading)
const settings = useAppSelector((s) => s.settings)
const wallets = useAppSelector((s) => s.global.wallets)
Expand All @@ -74,12 +81,14 @@ const App = () => {

const addressesStatus = useAppSelector((s) => s.addresses.status)
const isSyncingAddressData = useAppSelector((s) => s.addresses.syncingAddressData)
const isTokensMetadataUninitialized = useAppSelector(selectIsTokensMetadataUninitialized)
const isLoadingTokensMetadata = useAppSelector((s) => s.assetsInfo.loading)
const verifiedFungibleTokensNeedInitialization = useAppSelector(selectDoVerifiedFungibleTokensNeedInitialization)
const isLoadingVerifiedFungibleTokens = useAppSelector((s) => s.fungibleTokens.loadingVerified)
const isLoadingUnverifiedFungibleTokens = useAppSelector((s) => s.fungibleTokens.loadingUnverified)
const verifiedFungibleTokenSymbols = useAppSelector(selectAllAddressVerifiedFungibleTokenSymbols)

const selectAddressesUnknownTokens = useMemo(makeSelectAddressesUnknownTokens, [])
const unknownTokens = useAppSelector(selectAddressesUnknownTokens)
const checkedUnknownTokenIds = useAppSelector((s) => s.assetsInfo.checkedUnknownTokenIds)
const checkedUnknownTokenIds = useAppSelector((s) => s.fungibleTokens.checkedUnknownTokenIds)
const unknownTokenIds = unknownTokens.map((token) => token.id)
const newUnknownTokens = difference(unknownTokenIds, checkedUnknownTokenIds)

Expand Down Expand Up @@ -175,9 +184,6 @@ const App = () => {

useEffect(() => {
if (network.status === 'online') {
if (assetsInfo.status === 'uninitialized' && !isLoadingTokensMetadata) {
dispatch(syncNetworkTokensInfo())
}
if (addressesStatus === 'uninitialized') {
if (!isSyncingAddressData && addressHashes.length > 0) {
const storedPendingTxs = getStoredPendingTransactions()
Expand All @@ -189,26 +195,80 @@ const App = () => {

restorePendingTransactions(mempoolTxHashes, storedPendingTxs)
})
dispatch(syncAddressesHistoricBalances())

dispatch(syncAddressesAlphHistoricBalances())
}
} else if (addressesStatus === 'initialized') {
if (!isTokensMetadataUninitialized && !isLoadingTokensMetadata && newUnknownTokens.length > 0) {
if (
!verifiedFungibleTokensNeedInitialization &&
!isLoadingUnverifiedFungibleTokens &&
newUnknownTokens.length > 0
) {
dispatch(syncUnknownTokensInfo(newUnknownTokens))
}
}
}
}, [
addressHashes.length,
addressesStatus,
assetsInfo.status,
verifiedFungibleTokensNeedInitialization,
dispatch,
isLoadingUnverifiedFungibleTokens,
isSyncingAddressData,
isLoadingTokensMetadata,
isTokensMetadataUninitialized,
network.status,
newUnknownTokens
])

// Fetch verified tokens from GitHub token-list and sync current and historical prices for each verified fungible
// token found in each address
useEffect(() => {
if (network.status === 'online' && !isLoadingVerifiedFungibleTokens) {
if (verifiedFungibleTokensNeedInitialization) {
dispatch(syncVerifiedFungibleTokens())
} else if (verifiedFungibleTokenSymbols.uninitialized.length > 0) {
const symbols = verifiedFungibleTokenSymbols.uninitialized

dispatch(syncTokenCurrentPrices({ verifiedFungibleTokenSymbols: symbols, currency: settings.fiatCurrency }))
dispatch(syncTokenPriceHistories({ verifiedFungibleTokenSymbols: symbols, currency: settings.fiatCurrency }))
}
}
}, [
dispatch,
isLoadingVerifiedFungibleTokens,
network.status,
settings.fiatCurrency,
verifiedFungibleTokenSymbols.uninitialized,
verifiedFungibleTokensNeedInitialization
])
nop33 marked this conversation as resolved.
Show resolved Hide resolved

useEffect(() => {
if (
network.status === 'online' &&
!isLoadingVerifiedFungibleTokens &&
verifiedFungibleTokenSymbols.uninitialized.length > 1
) {
console.log(
'TODO: Sync address verified tokens balance histories for',
verifiedFungibleTokenSymbols.uninitialized.filter((symbol) => symbol !== ALPH.symbol)
)
}
}, [isLoadingVerifiedFungibleTokens, network.status, verifiedFungibleTokenSymbols.uninitialized])
nop33 marked this conversation as resolved.
Show resolved Hide resolved

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

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

const refreshAddressesData = useCallback(() => {
dispatch(syncAddressesData(addressesWithPendingTxs))
}, [dispatch, addressesWithPendingTxs])
Expand Down
4 changes: 3 additions & 1 deletion apps/desktop-wallet/src/api/addresses.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,9 @@ import {

const PAGE_LIMIT = 100
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not directly linked to this PR but this could be extracted as well (I actually thought it was already). WDYT?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To be discussed later:

  • For maintainability sake, we could use comments to describe what each constant does.
  • Some constants would be in SHRD, those ones should IMO be imported and re-declared in each sub-package for clarity, instead of being imported directly in the code.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hm, why should we re-declare them? We can easily click on the constant in our IDEs and navigate to the source where we could have the comments that you mentioned.

Copy link
Member

@mvaivre mvaivre Feb 5, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I find it pretty useful to see the list of constants actually in use in the project, makes it easier to know what can be tweaked. But yeah, it's a bit more maintenance involved.


export const fetchAddressesTokens = async (addressHashes: AddressHash[]): Promise<AddressTokensSyncResult[]> => {
export const fetchAddressesTokensBalances = async (
addressHashes: AddressHash[]
): Promise<AddressTokensSyncResult[]> => {
const results = []

for (const hash of addressHashes) {
Expand Down
14 changes: 7 additions & 7 deletions apps/desktop-wallet/src/components/AssetBadge.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ import styled, { css } from 'styled-components'
import Amount from '@/components/Amount'
import AssetLogo from '@/components/AssetLogo'
import { useAppSelector } from '@/hooks/redux'
import { selectAssetInfoById, selectNFTById } from '@/storage/assets/assetsSelectors'
import { selectFungibleTokenById, selectNFTById } from '@/storage/assets/assetsSelectors'

interface AssetBadgeProps {
assetId: Asset['id']
Expand All @@ -34,28 +34,28 @@ interface AssetBadgeProps {
}

const AssetBadge = ({ assetId, amount, simple, className }: AssetBadgeProps) => {
const assetInfo = useAppSelector((s) => selectAssetInfoById(s, assetId))
const fungibleToken = useAppSelector((s) => selectFungibleTokenById(s, assetId))
const nftInfo = useAppSelector((s) => selectNFTById(s, assetId))

return (
<div
className={className}
data-tooltip-id="default"
data-tooltip-content={assetInfo?.name ?? nftInfo?.name ?? assetId}
data-tooltip-content={fungibleToken?.name ?? nftInfo?.name ?? assetId}
>
<AssetLogo
assetId={assetId}
assetImageUrl={assetInfo?.logoURI || nftInfo?.image}
assetImageUrl={fungibleToken?.logoURI || nftInfo?.image}
size={20}
assetName={assetInfo?.name}
assetName={fungibleToken?.name}
isNft={!!nftInfo}
/>
{nftInfo?.name ? (
<AssetSymbol>{nftInfo?.name}</AssetSymbol>
) : amount !== undefined ? (
<Amount value={amount} suffix={assetInfo?.symbol} decimals={assetInfo?.decimals} />
<Amount value={amount} suffix={fungibleToken?.symbol} decimals={fungibleToken?.decimals} />
) : (
!simple && assetInfo?.symbol && <AssetSymbol>{assetInfo.symbol}</AssetSymbol>
!simple && fungibleToken?.symbol && <AssetSymbol>{fungibleToken.symbol}</AssetSymbol>
)}
</div>
)
Expand Down
8 changes: 4 additions & 4 deletions apps/desktop-wallet/src/components/AssetLogo.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,18 +16,18 @@ 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 { AssetInfo, NFT } from '@alephium/shared'
import { FungibleToken, NFT } from '@alephium/shared'
import { ALPH } from '@alephium/token-list'
import { HelpCircle } from 'lucide-react'
import styled, { css } from 'styled-components'

import AlephiumLogoSVG from '@/images/alephium_logo_monochrome.svg'

interface AssetLogoProps {
assetId: AssetInfo['id']
assetImageUrl: AssetInfo['logoURI'] | NFT['image']
assetId: FungibleToken['id']
assetImageUrl: FungibleToken['logoURI'] | NFT['image']
size: number
assetName?: AssetInfo['name']
assetName?: FungibleToken['name']
isNft?: boolean
className?: string
}
Expand Down
16 changes: 6 additions & 10 deletions apps/desktop-wallet/src/components/HistoricWorthChart.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,12 +29,10 @@ import {
selectHaveHistoricBalancesLoaded,
selectIsStateUninitialized
} from '@/storage/addresses/addressesSelectors'
import { useGetHistoricalPriceQuery } from '@/storage/assets/priceApiSlice'
import { selectAlphPriceHistory } from '@/storage/prices/pricesSelectors'
import { ChartLength, DataPoint, LatestAmountPerAddress } from '@/types/chart'
import { Currency } from '@/types/settings'

interface HistoricWorthChartProps {
currency: Currency
length: ChartLength
onDataPointHover: (dataPoint?: DataPoint) => void
onWorthInBeginningOfChartChange: (worthInBeginningOfChart?: DataPoint['worth']) => void
Expand All @@ -57,7 +55,6 @@ const startingDates: Record<ChartLength, Dayjs> = {
const HistoricWorthChart = memo(function HistoricWorthChart({
addressHash,
latestWorth,
currency,
length = '1y',
onDataPointHover,
onWorthInBeginningOfChartChange
Expand All @@ -66,8 +63,7 @@ const HistoricWorthChart = memo(function HistoricWorthChart({
const addresses = useAppSelector((s) => selectAddresses(s, addressHash ?? (s.addresses.ids as AddressHash[])))
const haveHistoricBalancesLoaded = useAppSelector(selectHaveHistoricBalancesLoaded)
const stateUninitialized = useAppSelector(selectIsStateUninitialized)

const { data: alphPriceHistory } = useGetHistoricalPriceQuery({ currency, days: 365 })
const alphPriceHistory = useAppSelector(selectAlphPriceHistory)

const theme = useTheme()

Expand All @@ -90,11 +86,11 @@ const HistoricWorthChart = memo(function HistoricWorthChart({
const computeChartDataPoints = (): DataPoint[] => {
const addressesLatestAmount: LatestAmountPerAddress = {}

const dataPoints = alphPriceHistory.map(({ date, price }) => {
const dataPoints = alphPriceHistory.map(({ date, value }) => {
let totalAmountPerDate = BigInt(0)

addresses.forEach(({ hash, balanceHistory }) => {
const amountOnDate = balanceHistory.entities[date]?.balance
addresses.forEach(({ hash, alphBalanceHistory }) => {
const amountOnDate = alphBalanceHistory.entities[date]?.balance

if (amountOnDate !== undefined) {
const amount = BigInt(amountOnDate)
Expand All @@ -107,7 +103,7 @@ const HistoricWorthChart = memo(function HistoricWorthChart({

return {
date,
worth: price * parseFloat(toHumanReadableAmount(totalAmountPerDate))
worth: value * parseFloat(toHumanReadableAmount(totalAmountPerDate))
}
})

Expand Down
4 changes: 2 additions & 2 deletions apps/desktop-wallet/src/modals/AddressSelectModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ const AddressSelectModal = ({
hideAddressesWithoutAssets
}: AddressSelectModalProps) => {
const { t } = useTranslation()
const assetsInfo = useAppSelector((state) => state.assetsInfo.entities)
const fungibleTokens = useAppSelector((state) => state.fungibleTokens.entities)

const addresses = hideAddressesWithoutAssets ? filterAddressesWithoutAssets(options) : options
const [filteredAddresses, setFilteredAddresses] = useState(addresses)
Expand Down Expand Up @@ -76,7 +76,7 @@ const AddressSelectModal = ({
}

const handleSearch = (searchInput: string) =>
setFilteredAddresses(filterAddresses(addresses, searchInput.toLowerCase(), assetsInfo))
setFilteredAddresses(filterAddresses(addresses, searchInput.toLowerCase(), fungibleTokens))

return (
<SelectOptionsModal
Expand Down
14 changes: 7 additions & 7 deletions apps/desktop-wallet/src/modals/SendModals/CheckAmountsBox.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ const CheckAmountsBox = ({ assetAmounts, className }: CheckAmountsBoxProps) => {
const { t } = useTranslation()
const userSpecifiedAlphAmount = assetAmounts.find((asset) => asset.id === ALPH.id)?.amount
const { attoAlphAmount, tokens, extraAlphForDust } = getTransactionAssetAmounts(assetAmounts)
const assetsInfo = useAppSelector((s) => s.assetsInfo.entities)
const fungibleTokens = useAppSelector((s) => s.fungibleTokens.entities)
const nfts = useAppSelector((s) => s.nfts.entities)

const alphAsset = { id: ALPH.id, amount: attoAlphAmount }
Expand All @@ -51,7 +51,7 @@ const CheckAmountsBox = ({ assetAmounts, className }: CheckAmountsBoxProps) => {
return (
<Box className={className}>
{assets.map((asset, index) => {
const assetInfo = assetsInfo[asset.id]
const fungibleToken = fungibleTokens[asset.id]
const nftInfo = nfts[asset.id]

return (
Expand All @@ -60,15 +60,15 @@ const CheckAmountsBox = ({ assetAmounts, className }: CheckAmountsBoxProps) => {
<AssetAmountRow>
<AssetLogo
assetId={asset.id}
assetImageUrl={assetInfo?.logoURI ?? nftInfo?.image}
assetImageUrl={fungibleToken?.logoURI ?? nftInfo?.image}
size={30}
assetName={assetInfo?.name}
assetName={fungibleToken?.name}
/>
<AssetAmountStyled
value={BigInt(asset.amount)}
suffix={assetInfo?.symbol}
decimals={assetInfo?.decimals}
isUnknownToken={!assetInfo?.symbol}
suffix={fungibleToken?.symbol}
decimals={fungibleToken?.decimals}
isUnknownToken={!fungibleToken?.symbol}
fullPrecision
/>
{asset.id === ALPH.id && !!extraAlphForDust && (
Expand Down
Loading
Loading