Skip to content

Commit

Permalink
fix: balances in account pickers
Browse files Browse the repository at this point in the history
  • Loading branch information
0xKheops committed Nov 28, 2023
1 parent 371f463 commit 15667ae
Show file tree
Hide file tree
Showing 5 changed files with 116 additions and 49 deletions.
31 changes: 23 additions & 8 deletions apps/extension/src/core/domains/balances/handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,10 @@ import { AddressesByToken } from "@talismn/balances"
import { Token } from "@talismn/chaindata-provider"
import { MessageTypes, RequestTypes, ResponseType } from "core/types"

import { enabledChainsStore, isChainEnabled } from "../chains/store.enabledChains"
import { enabledEvmNetworksStore, isEvmNetworkEnabled } from "../ethereum/store.enabledEvmNetworks"
import { enabledTokensStore, isTokenEnabled } from "../tokens/store.enabledTokens"

export class BalancesHandler extends ExtensionHandler {
public async handle<TMessageType extends MessageTypes>(
id: string,
Expand Down Expand Up @@ -49,11 +53,15 @@ export class BalancesHandler extends ExtensionHandler {
// Collect the required data from chaindata.
//

const [chains, evmNetworks, tokens] = await Promise.all([
chaindataProvider.chains(),
chaindataProvider.evmNetworks(),
chaindataProvider.tokens(),
])
const [chains, evmNetworks, tokens, enabledTokens, enabledChains, enabledEvmNetworks] =
await Promise.all([
chaindataProvider.chains(),
chaindataProvider.evmNetworks(),
chaindataProvider.tokens(),
enabledTokensStore.get(),
enabledChainsStore.get(),
enabledEvmNetworksStore.get(),
])

//
// Convert the inputs of `addressesByChain` and `addressesAndEvmNetworks` into what we need
Expand All @@ -63,11 +71,13 @@ export class BalancesHandler extends ExtensionHandler {
const addressesByToken: AddressesByToken<Token> = [
...Object.entries(addressesByChain)
// convert chainIds into chains
.map(([chainId, addresses]) => [chains[chainId], addresses] as const),
.map(([chainId, addresses]) => [chains[chainId], addresses] as const)
.filter(([chain]) => isChainEnabled(chain, enabledChains)),

...addressesAndEvmNetworks.evmNetworks
// convert evmNetworkIds into evmNetworks
.map(({ id }) => [evmNetworks[id], addressesAndEvmNetworks.addresses] as const),
.map(({ id }) => [evmNetworks[id], addressesAndEvmNetworks.addresses] as const)
.filter(([evmNetwork]) => isEvmNetworkEnabled(evmNetwork, enabledEvmNetworks)),
]
// filter out requested chains/evmNetworks which don't exist
.filter(([chainOrNetwork]) => chainOrNetwork !== undefined)
Expand All @@ -76,7 +86,12 @@ export class BalancesHandler extends ExtensionHandler {

// convert chains and evmNetworks into a list of tokenIds
.flatMap(([chainOrNetwork, addresses]) =>
(chainOrNetwork.tokens || []).map(({ id: tokenId }) => [tokenId, addresses] as const)
Object.values(tokens)
.filter(
(t) => t.chain?.id === chainOrNetwork.id || t.evmNetwork?.id === chainOrNetwork.id
)
.filter((t) => isTokenEnabled(t, enabledTokens))
.map((t) => [t.id, addresses] as const)
)

// collect all of the addresses for each tokenId into a map of { [tokenId]: addresses }
Expand Down
43 changes: 28 additions & 15 deletions apps/extension/src/ui/domains/Account/DerivedAccountPickerBase.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,14 +25,19 @@ const PagerButton: FC<{ disabled?: boolean; children: ReactNode; onClick?: () =>
</button>
)

const AccountButtonShimmer = () => (
const AccountButtonShimmer: FC<{ withBalances: boolean }> = ({ withBalances }) => (
<div className={"bg-grey-850 flex h-32 w-full items-center gap-8 rounded px-8"}>
<div className="bg-grey-750 inline-block h-16 w-16 animate-pulse rounded-full"></div>
<div className="flex grow flex-col gap-2">
<div className="rounded-xs bg-grey-750 h-[1.6rem] w-[13rem] animate-pulse"></div>
<div className="rounded-xs bg-grey-750 h-[1.4rem] w-[6.8rem] animate-pulse"></div>
</div>
<div className="rounded-xs bg-grey-750 h-[1.8rem] w-[6.8rem] animate-pulse"></div>
<div
className={classNames(
"rounded-xs bg-grey-750 h-[1.8rem] w-[6.8rem] animate-pulse",
!withBalances && "invisible"
)}
></div>
<div className="rounded-xs bg-grey-750 h-[2rem] w-[2rem] animate-pulse"></div>
</div>
)
Expand All @@ -46,6 +51,7 @@ const AccountButton: FC<AccountButtonProps> = ({
selected,
onClick,
isBalanceLoading,
withBalances,
}) => {
const { balanceDetails, totalUsd } = useBalanceDetails(balances)

Expand All @@ -66,18 +72,20 @@ const AccountButton: FC<AccountButtonProps> = ({
</div>
</div>
<div className="flex items-center justify-end gap-2">
<Tooltip>
<TooltipTrigger asChild>
<span className={classNames(isBalanceLoading && "animate-pulse")}>
<Fiat className="leading-none" amount={totalUsd} />
</span>
</TooltipTrigger>
{balanceDetails && (
<TooltipContent>
<div className="whitespace-pre-wrap text-right">{balanceDetails}</div>
</TooltipContent>
)}
</Tooltip>
{withBalances && (
<Tooltip>
<TooltipTrigger asChild>
<span className={classNames(isBalanceLoading && "animate-pulse")}>
<Fiat className="leading-none" amount={totalUsd} />
</span>
</TooltipTrigger>
{balanceDetails && (
<TooltipContent>
<div className="whitespace-pre-wrap text-right">{balanceDetails}</div>
</TooltipContent>
)}
</Tooltip>
)}
</div>
<div className="flex w-12 shrink-0 flex-col items-center justify-center">
{connected ? (
Expand All @@ -96,16 +104,19 @@ export type DerivedAccountBase = AccountJson & {
address: string
balances: Balances
isBalanceLoading: boolean

connected?: boolean
selected?: boolean
}

type AccountButtonProps = DerivedAccountBase & {
withBalances: boolean
onClick: () => void
}

type DerivedAccountPickerBaseProps = {
accounts: (DerivedAccountBase | null)[]
withBalances: boolean
canPageBack?: boolean
disablePaging?: boolean
onPagerFirstClick?: () => void
Expand All @@ -122,6 +133,7 @@ export const DerivedAccountPickerBase: FC<DerivedAccountPickerBaseProps> = ({
onPagerPrevClick,
onPagerNextClick,
onAccountClick,
withBalances = true,
}) => {
const handleToggleAccount = useCallback(
(acc: DerivedAccountBase) => () => {
Expand All @@ -137,11 +149,12 @@ export const DerivedAccountPickerBase: FC<DerivedAccountPickerBaseProps> = ({
account ? (
<AccountButton
key={account.address}
withBalances={withBalances}
{...account}
onClick={handleToggleAccount(account)}
/>
) : (
<AccountButtonShimmer key={i} />
<AccountButtonShimmer key={i} withBalances={withBalances} />
)
)}
</div>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,17 @@
import { formatSuri } from "@core/domains/accounts/helpers"
import { AccountAddressType, RequestAccountCreateFromSuri } from "@core/domains/accounts/types"
import { AddressesAndEvmNetwork } from "@core/domains/balances/types"
import { isChainEnabled } from "@core/domains/chains/store.enabledChains"
import { getEthDerivationPath } from "@core/domains/ethereum/helpers"
import { isEvmNetworkEnabled } from "@core/domains/ethereum/store.enabledEvmNetworks"
import { AddressesByChain } from "@core/types/base"
import { convertAddress } from "@talisman/util/convertAddress"
import { api } from "@ui/api"
import useAccounts from "@ui/hooks/useAccounts"
import useBalancesByParams from "@ui/hooks/useBalancesByParams"
import useChains from "@ui/hooks/useChains"
import { useEnabledChainsState } from "@ui/hooks/useEnabledChainsState"
import { useEnabledEvmNetworksState } from "@ui/hooks/useEnabledEvmNetworksState"
import { useEvmNetworks } from "@ui/hooks/useEvmNetworks"
import { FC, useCallback, useEffect, useMemo, useState } from "react"

Expand Down Expand Up @@ -70,9 +74,12 @@ const useDerivedAccounts = (
}
}, [itemsPerPage, mnemonic, name, pageIndex, type])

const { chains } = useChains("all")
const { chains } = useChains("enabledWithoutTestnets")
const { evmNetworks } = useEvmNetworks("enabledWithoutTestnets")

const enabledChains = useEnabledChainsState()
const enabledEvmNetworks = useEnabledEvmNetworksState()

const { expectedBalancesCount, addressesByChain, addressesAndEvmNetworks } = useMemo(() => {
const expectedBalancesCount =
type === "ethereum"
Expand All @@ -87,21 +94,23 @@ const useDerivedAccounts = (

const evmNetworkIds = type === "ethereum" ? BALANCE_CHECK_EVM_NETWORK_IDS : []
const chainIds = type === "ethereum" ? [] : BALANCE_CHECK_SUBSTRATE_CHAIN_IDS
const testChains = (chains || []).filter((chain) => chainIds.includes(chain.id))

const addressesByChain: AddressesByChain =
type === "ethereum"
? {}
: testChains.reduce(
(prev, curr) => ({
...prev,
[curr.id]: derivedAccounts
.filter((acc) => !!acc)
.map((acc) => acc as DerivedFromMnemonicAccount)
.map((account) => convertAddress(account.address, curr.prefix)),
}),
{}
)
: (chains || [])
.filter((chain) => chainIds.includes(chain.id))
.filter((chain) => isChainEnabled(chain, enabledChains))
.reduce(
(prev, curr) => ({
...prev,
[curr.id]: derivedAccounts
.filter((acc) => !!acc)
.map((acc) => acc as DerivedFromMnemonicAccount)
.map((account) => convertAddress(account.address, curr.prefix)),
}),
{}
)

const addressesAndEvmNetworks: AddressesAndEvmNetwork =
type === "ethereum"
Expand All @@ -112,6 +121,7 @@ const useDerivedAccounts = (
.filter(Boolean) as string[],
evmNetworks: (evmNetworks || [])
.filter((chain) => evmNetworkIds.includes(chain.id))
.filter((chain) => isEvmNetworkEnabled(chain, enabledEvmNetworks))
.map(({ id, nativeToken }) => ({
id,
nativeToken: { id: nativeToken?.id as string },
Expand All @@ -124,7 +134,14 @@ const useDerivedAccounts = (
addressesByChain,
addressesAndEvmNetworks,
}
}, [chains, derivedAccounts, evmNetworks, type])
}, [chains, derivedAccounts, enabledChains, enabledEvmNetworks, evmNetworks, type])

const withBalances = useMemo(
() =>
(addressesByChain && Object.values(addressesByChain).some((addresses) => addresses.length)) ||
!!addressesAndEvmNetworks?.evmNetworks.length,
[addressesAndEvmNetworks?.evmNetworks.length, addressesByChain]
)

const balances = useBalancesByParams({
addressesByChain,
Expand Down Expand Up @@ -176,6 +193,7 @@ const useDerivedAccounts = (

return {
accounts,
withBalances,
error,
}
}
Expand All @@ -198,7 +216,7 @@ export const DerivedFromMnemonicAccountPicker: FC<DerivedAccountPickerProps> = (
const itemsPerPage = 5
const [pageIndex, setPageIndex] = useState(0)
const [selectedAccounts, setSelectedAccounts] = useState<RequestAccountCreateFromSuri[]>([])
const { accounts, error } = useDerivedAccounts(
const { accounts, withBalances, error } = useDerivedAccounts(
name,
mnemonic,
type,
Expand Down Expand Up @@ -232,6 +250,7 @@ export const DerivedFromMnemonicAccountPicker: FC<DerivedAccountPickerProps> = (
<>
<DerivedAccountPickerBase
accounts={accounts}
withBalances={withBalances}
canPageBack={pageIndex > 0}
onAccountClick={handleToggleAccount}
onPagerFirstClick={handlePageFirst}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
import { DEBUG } from "@core/constants"
import { AddressesAndEvmNetwork } from "@core/domains/balances/types"
import { getEthLedgerDerivationPath } from "@core/domains/ethereum/helpers"
import { isEvmNetworkEnabled } from "@core/domains/ethereum/store.enabledEvmNetworks"
import { LedgerEthDerivationPathType } from "@core/domains/ethereum/types"
import { convertAddress } from "@talisman/util/convertAddress"
import { LedgerAccountDefEthereum } from "@ui/domains/Account/AccountAdd/AccountAddLedger/context"
import { useLedgerEthereum } from "@ui/hooks/ledger/useLedgerEthereum"
import useAccounts from "@ui/hooks/useAccounts"
import useBalancesByParams from "@ui/hooks/useBalancesByParams"
import { useEnabledEvmNetworksState } from "@ui/hooks/useEnabledEvmNetworksState"
import { useEvmNetworks } from "@ui/hooks/useEvmNetworks"
import { FC, useCallback, useEffect, useMemo, useState } from "react"
import { useTranslation } from "react-i18next"
Expand Down Expand Up @@ -73,6 +75,7 @@ const useLedgerEthereumAccounts = (
const { evmNetworks } = useEvmNetworks("enabledWithoutTestnets")

// which balances to fetch
const enabledEvmNetworks = useEnabledEvmNetworksState()
const addressesAndEvmNetworks = useMemo(() => {
// start fetching balances only when all accounts are known to prevent recreating subscription 5 times
if (derivedAccounts.filter(Boolean).length < derivedAccounts.length) return undefined
Expand All @@ -84,11 +87,17 @@ const useLedgerEthereumAccounts = (
.filter(Boolean) as string[],
evmNetworks: (evmNetworks || [])
.filter((chain) => BALANCE_CHECK_EVM_NETWORK_IDS.includes(chain.id))
.filter((chain) => isEvmNetworkEnabled(chain, enabledEvmNetworks))
.map(({ id, nativeToken }) => ({ id, nativeToken: { id: nativeToken?.id as string } })),
}

return result
}, [derivedAccounts, evmNetworks])
}, [derivedAccounts, enabledEvmNetworks, evmNetworks])

const withBalances = useMemo(
() => !!addressesAndEvmNetworks?.evmNetworks.length,
[addressesAndEvmNetworks?.evmNetworks.length]
)

const balances = useBalancesByParams({ addressesAndEvmNetworks })

Expand Down Expand Up @@ -127,6 +136,7 @@ const useLedgerEthereumAccounts = (

return {
accounts,
withBalances,
isBusy,
error,
connectionStatus,
Expand All @@ -149,7 +159,7 @@ export const LedgerEthereumAccountPicker: FC<LedgerEthereumAccountPickerProps> =
const itemsPerPage = 5
const [pageIndex, setPageIndex] = useState(0)
const [selectedAccounts, setSelectedAccounts] = useState<LedgerAccountDefEthereum[]>([])
const { accounts, error, isBusy } = useLedgerEthereumAccounts(
const { accounts, error, isBusy, withBalances } = useLedgerEthereumAccounts(
name,
derivationPathType,
selectedAccounts,
Expand Down Expand Up @@ -178,6 +188,7 @@ export const LedgerEthereumAccountPicker: FC<LedgerEthereumAccountPickerProps> =
<>
<DerivedAccountPickerBase
accounts={accounts}
withBalances={withBalances}
canPageBack={pageIndex > 0}
disablePaging={isBusy}
onAccountClick={handleToggleAccount}
Expand Down
Loading

0 comments on commit 15667ae

Please sign in to comment.