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

chore: release v1.776.0 #8894

Merged
merged 3 commits into from
Feb 20, 2025
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
24 changes: 12 additions & 12 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -95,18 +95,18 @@
"@shapeshiftoss/caip": "workspace:^",
"@shapeshiftoss/chain-adapters": "workspace:^",
"@shapeshiftoss/errors": "workspace:^",
"@shapeshiftoss/hdwallet-coinbase": "1.59.1",
"@shapeshiftoss/hdwallet-core": "1.59.1",
"@shapeshiftoss/hdwallet-keepkey": "1.59.1",
"@shapeshiftoss/hdwallet-keepkey-webusb": "1.59.1",
"@shapeshiftoss/hdwallet-keplr": "1.59.1",
"@shapeshiftoss/hdwallet-ledger": "1.59.1",
"@shapeshiftoss/hdwallet-ledger-webusb": "1.59.1",
"@shapeshiftoss/hdwallet-metamask-multichain": "1.59.1",
"@shapeshiftoss/hdwallet-native": "1.59.1",
"@shapeshiftoss/hdwallet-native-vault": "1.59.1",
"@shapeshiftoss/hdwallet-phantom": "1.59.1",
"@shapeshiftoss/hdwallet-walletconnectv2": "1.59.1",
"@shapeshiftoss/hdwallet-coinbase": "1.59.2",
"@shapeshiftoss/hdwallet-core": "1.59.2",
"@shapeshiftoss/hdwallet-keepkey": "1.59.2",
"@shapeshiftoss/hdwallet-keepkey-webusb": "1.59.2",
"@shapeshiftoss/hdwallet-keplr": "1.59.2",
"@shapeshiftoss/hdwallet-ledger": "1.59.2",
"@shapeshiftoss/hdwallet-ledger-webusb": "1.59.2",
"@shapeshiftoss/hdwallet-metamask-multichain": "1.59.2",
"@shapeshiftoss/hdwallet-native": "1.59.2",
"@shapeshiftoss/hdwallet-native-vault": "1.59.2",
"@shapeshiftoss/hdwallet-phantom": "1.59.2",
"@shapeshiftoss/hdwallet-walletconnectv2": "1.59.2",
"@shapeshiftoss/swapper": "workspace:^",
"@shapeshiftoss/types": "workspace:^",
"@shapeshiftoss/unchained-client": "workspace:^",
Expand Down
137 changes: 92 additions & 45 deletions src/components/ManageAccountsDrawer/components/ImportAccounts.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import { fromAccountId } from '@shapeshiftoss/caip'
import { isLedger } from '@shapeshiftoss/hdwallet-ledger'
import { MetaMaskMultiChainHDWallet } from '@shapeshiftoss/hdwallet-metamask-multichain'
import type { Asset } from '@shapeshiftoss/types'
import { useInfiniteQuery, useQuery, useQueryClient } from '@tanstack/react-query'
import { useInfiniteQuery, useQueries, useQuery, useQueryClient } from '@tanstack/react-query'
import { useCallback, useEffect, useMemo, useState } from 'react'
import { useTranslate } from 'react-polyglot'
import { accountManagement } from 'react-queries/queries/accountManagement'
Expand All @@ -29,9 +29,13 @@ import {
import { useToggle } from 'hooks/useToggle/useToggle'
import { useWallet } from 'hooks/useWallet/useWallet'
import { fromBaseUnit } from 'lib/math'
import { fetchPortalsAccount } from 'lib/portals/utils'
import { isUtxoAccountId } from 'lib/utils/utxo'
import { portfolio, portfolioApi } from 'state/slices/portfolioSlice/portfolioSlice'
import { accountIdToLabel } from 'state/slices/portfolioSlice/utils'
import { selectNftCollections } from 'state/apis/nft/selectors'
import { assets as assetSlice } from 'state/slices/assetsSlice/assetsSlice'
import { portfolio } from 'state/slices/portfolioSlice/portfolioSlice'
import type { Portfolio } from 'state/slices/portfolioSlice/portfolioSliceCommon'
import { accountIdToLabel, accountToPortfolio, makeAssets } from 'state/slices/portfolioSlice/utils'
import {
selectAccountIdsByChainId,
selectFeeAssetByChainId,
Expand Down Expand Up @@ -69,7 +73,14 @@ const TableRowAccount = forwardRef<TableRowAccountProps, 'div'>(({ asset, accoun
const pubkey = useMemo(() => fromAccountId(accountId).account, [accountId])
const isUtxoAccount = useMemo(() => isUtxoAccountId(accountId), [accountId])

const { data: account, isLoading } = useQuery(accountManagement.getAccount(accountId))
const { data: account } = useQuery({
...accountManagement.getAccount(accountId),
staleTime: Infinity,
// Never garbage collect me, I'm a special snowflake
gcTime: Infinity,
// Yes, we do refetch on mount despite having an Infinity stale time. Stale then fresh FTW.
refetchOnMount: 'always',
})

const assetBalanceCryptoPrecision = useMemo(() => {
if (!account) return '0'
Expand All @@ -88,11 +99,7 @@ const TableRowAccount = forwardRef<TableRowAccountProps, 'div'>(({ asset, accoun
</InlineCopyButton>
</Td>
<Td textAlign='right'>
{isLoading ? (
<Skeleton height='24px' width='100%' />
) : (
<Amount.Crypto value={assetBalanceCryptoPrecision} symbol={asset.symbol} />
)}
<Amount.Crypto value={assetBalanceCryptoPrecision} symbol={asset.symbol} />
</Td>
</>
)
Expand Down Expand Up @@ -176,37 +183,60 @@ const LoadingRow = ({ numRows }: { numRows: number }) => {
}

export const ImportAccounts = ({ chainId, onClose, isOpen }: ImportAccountsProps) => {
const [isAutoDiscovering, setIsAutoDiscovering] = useState(true)
const [queryEnabled, setQueryEnabled] = useState(false)
const [isSubmitting, setIsSubmitting] = useState(false)
const [toggledAccountIds, setToggledAccountIds] = useState<Set<AccountId>>(new Set())

const translate = useTranslate()
const dispatch = useAppDispatch()
const queryClient = useQueryClient()
const {
state: { wallet, deviceId: walletDeviceId },
} = useWallet()
const asset = useAppSelector(state => selectFeeAssetByChainId(state, chainId))
const { isSnapInstalled } = useIsSnapInstalled()
const isLedgerWallet = useMemo(() => wallet && isLedger(wallet), [wallet])
const isMetaMaskMultichainWallet = useMemo(
() => wallet instanceof MetaMaskMultiChainHDWallet,
[wallet],
)

const nftCollectionsById = useAppSelector(selectNftCollections)
const asset = useAppSelector(state => selectFeeAssetByChainId(state, chainId))

// Prefetch Portals account data, ish. At this point, we already have querydata for all *enabled* AccountIds,
// so this will really fetch it for the newly toggled ones
useQueries({
queries: Array.from(toggledAccountIds).map(accountId => {
const { chainId, account: pubkey } = fromAccountId(accountId)

return {
queryFn: () => fetchPortalsAccount(chainId, pubkey),
queryKey: ['portalsAccount', chainId, pubkey],
// Assume that this is static as far as our lifecycle is concerned.
// This may seem like a dangerous stretch, but it pragmatically is not:
// This is fetched for a given account fetch, and the only flow there would be a refetch would really be if the user disabled an account, then re-enabled it.
// It's an uncommon enough flow that we could compromise on it and make the experience better for all other cases by leveraging cached data.
// Most importantly, even if a user were to do this, the worst case senario wouldn't be one: all we fetch here is LP tokens meta, which won't change
// the second time around and not the 420th time around either
staleTime: Infinity,
}
}),
})

const chainNamespaceDisplayName = asset?.networkName ?? ''
const [autoFetching, setAutoFetching] = useState(true)
const [queryEnabled, setQueryEnabled] = useState(false)
const [isSubmitting, setIsSubmitting] = useState(false)
const [toggledAccountIds, setToggledAccountIds] = useState<Set<AccountId>>(new Set())

// reset component state when chainId changes
useEffect(() => {
setAutoFetching(true)
setIsAutoDiscovering(true)
setToggledAccountIds(new Set())
}, [chainId])

// initial fetch to detect the number of accounts based on the "first empty account" heuristic
const {
data: accounts,
fetchNextPage,
isLoading,
isFetching,
isFetching: isAccountsFetching,
} = useInfiniteQuery({
queryKey: ['accountIdWithActivityAndMetadata', chainId, walletDeviceId, wallet !== null],
queryFn: async ({ pageParam: accountNumber }) => {
Expand Down Expand Up @@ -246,7 +276,7 @@ export const ImportAccounts = ({ chainId, onClose, isOpen }: ImportAccountsProps
if (isMetaMaskMultichainWallet && !isSnapInstalled) return

if (!isLedgerWallet) {
setAutoFetching(true)
setIsAutoDiscovering(true)
setQueryEnabled(true)
return
}
Expand All @@ -255,7 +285,7 @@ export const ImportAccounts = ({ chainId, onClose, isOpen }: ImportAccountsProps
// is open on the device. This is to prevent the cache from creating invalid state where the app
// on the device is not open but the cache thinks it is.
queryClient.resetQueries({ queryKey: ['accountIdWithActivityAndMetadata'] }).then(() => {
setAutoFetching(true)
setIsAutoDiscovering(true)
setQueryEnabled(true)
})
}, [queryEnabled, isLedgerWallet, isMetaMaskMultichainWallet, isSnapInstalled, queryClient])
Expand All @@ -265,7 +295,7 @@ export const ImportAccounts = ({ chainId, onClose, isOpen }: ImportAccountsProps

// Handle initial automatic loading
useEffect(() => {
if (isFetching || isLoading || !autoFetching || !accounts || !queryEnabled) return
if (isAccountsFetching || !isAutoDiscovering || !accounts || !queryEnabled) return

// Check if the most recently fetched account has activity
const isLastAccountActive = accounts.pages[
Expand All @@ -281,22 +311,21 @@ export const ImportAccounts = ({ chainId, onClose, isOpen }: ImportAccountsProps
fetchNextPage()
} else {
// Stop auto-fetching and switch to manual mode
setAutoFetching(false)
setIsAutoDiscovering(false)
}
}, [
accounts,
fetchNextPage,
autoFetching,
isFetching,
isLoading,
isAutoDiscovering,
isAccountsFetching,
queryEnabled,
existingAccountIdsForChain,
])

const handleLoadMore = useCallback(() => {
if (isFetching || isLoading || autoFetching) return
if (isAccountsFetching || isAutoDiscovering) return
fetchNextPage()
}, [autoFetching, isFetching, isLoading, fetchNextPage])
}, [isAutoDiscovering, isAccountsFetching, fetchNextPage])

const handleToggleAccountIds = useCallback((accountIds: AccountId[]) => {
setToggledAccountIds(previousState => {
Expand Down Expand Up @@ -333,9 +362,33 @@ export const ImportAccounts = ({ chainId, onClose, isOpen }: ImportAccountsProps
if (isEnabled) {
return
}
await dispatch(
portfolioApi.endpoints.getAccount.initiate({ accountId, upsertOnFetch: true }),
)

// "Fetch" the query leveraging the existing cached data
const account = await queryClient.fetchQuery({
...accountManagement.getAccount(accountId),
staleTime: Infinity,
// Never garbage collect me, I'm a special snowflake
gcTime: Infinity,
})

const data = await (async (): Promise<Portfolio> => {
const { chainId, account: pubkey } = fromAccountId(accountId)
const state = store.getState()
const portfolioAccounts = { [pubkey]: account }
const assets = await makeAssets({ chainId, pubkey, state, portfolioAccounts })
const assetIds = state.assets.ids

// upsert placeholder assets
if (assets) dispatch(assetSlice.actions.upsertAssets(assets))

return accountToPortfolio({
portfolioAccounts,
assetIds: assetIds.concat(assets?.ids ?? []),
nftCollectionsById,
})
})()

dispatch(portfolio.actions.upsertPortfolio(data))
}),
)

Expand Down Expand Up @@ -368,21 +421,15 @@ export const ImportAccounts = ({ chainId, onClose, isOpen }: ImportAccountsProps
setToggledAccountIds(new Set())

setIsSubmitting(false)
}, [toggledAccountIds, accounts, dispatch, walletDeviceId])
}, [toggledAccountIds, accounts, dispatch, walletDeviceId, nftCollectionsById, queryClient])

const handleDoneClick = useCallback(async () => {
await handleUpdateAccounts()
const handleCommit = useCallback(() => {
// Do not await me, no need to run this on the next tick. This commits the selection in the background and should be turbo fast
// This is technically async, but at this stage, most underlying react-queries should already be cached
handleUpdateAccounts()
onClose()
}, [handleUpdateAccounts, onClose])

const handleDrawerClose = useCallback(() => {
onClose()
// Do *not* return the promise here, this is a non-async callback and should stay that way,
// not to slow things down visually.
// The accounts adding *should* run in the background.
handleUpdateAccounts()
}, [onClose, handleUpdateAccounts])

const accountRows = useMemo(() => {
if (!asset || !accounts) return null
return accounts.pages.map(({ accountIdWithActivityAndMetadata }, accountNumber) => {
Expand Down Expand Up @@ -415,7 +462,7 @@ export const ImportAccounts = ({ chainId, onClose, isOpen }: ImportAccountsProps
}

return (
<DrawerWrapper isOpen={isOpen} onClose={handleDrawerClose}>
<DrawerWrapper isOpen={isOpen} onClose={handleCommit}>
<DrawerContentWrapper
title={translate('accountManagement.importAccounts.title', { chainNamespaceDisplayName })}
description={translate('accountManagement.importAccounts.description')}
Expand All @@ -432,8 +479,8 @@ export const ImportAccounts = ({ chainId, onClose, isOpen }: ImportAccountsProps
</Button>
<Button
colorScheme='blue'
onClick={handleDoneClick}
isDisabled={isFetching || isLoading || autoFetching || isSubmitting || !accounts}
onClick={handleCommit}
isDisabled={isSubmitting || !accounts}
_disabled={disabledProps}
>
{translate('common.done')}
Expand All @@ -446,7 +493,7 @@ export const ImportAccounts = ({ chainId, onClose, isOpen }: ImportAccountsProps
<Table variant='simple' size={tableSize}>
<Tbody>
{accountRows}
{(isFetching || isLoading || autoFetching) && (
{(isAccountsFetching || isAutoDiscovering) && (
<LoadingRow
numRows={
accounts?.pages[accounts.pages.length - 1]?.accountIdWithActivityAndMetadata
Expand All @@ -465,7 +512,7 @@ export const ImportAccounts = ({ chainId, onClose, isOpen }: ImportAccountsProps
colorScheme='gray'
onClick={handleLoadMore}
isDisabled={
isFetching || isLoading || autoFetching || isSubmitting || !supportsMultiAccount
isAccountsFetching || isAutoDiscovering || isSubmitting || !supportsMultiAccount
}
_disabled={disabledProps}
>
Expand Down
15 changes: 10 additions & 5 deletions src/components/ManageAccountsDrawer/helpers.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import type { ChainId } from '@shapeshiftoss/caip'
import { fromAccountId } from '@shapeshiftoss/caip'
import type { HDWallet } from '@shapeshiftoss/hdwallet-core'
import { matchSorter } from 'match-sorter'
import { accountManagement } from 'react-queries/queries/accountManagement'
import { getChainAdapterManager } from 'context/PluginProvider/chainAdapterSingleton'
import { queryClient } from 'context/QueryClientProvider/queryClient'
import { deriveAccountIdsAndMetadata } from 'lib/account/account'
import { assertGetChainAdapter, isSome } from 'lib/utils'
import { isSome } from 'lib/utils'
import { checkAccountHasActivity } from 'state/slices/portfolioSlice/utils'

export const filterChainIdsBySearchTerm = (search: string, chainIds: ChainId[]) => {
Expand Down Expand Up @@ -42,9 +43,13 @@ export const getAccountIdsWithActivityAndMetadata = async (

return Promise.all(
Object.entries(accountIdsAndMetadata).map(async ([accountId, accountMetadata]) => {
const { account: pubkey } = fromAccountId(accountId)
const adapter = assertGetChainAdapter(chainId)
const account = await adapter.getAccount(pubkey)
const account = await queryClient.fetchQuery({
...accountManagement.getAccount(accountId),
staleTime: Infinity,
// Never garbage collect me, I'm a special snowflake
gcTime: Infinity,
})

const hasActivity = checkAccountHasActivity(account)

return { accountId, accountMetadata, hasActivity }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ export const fetchIsSmartContractAddressQuery = (userAddress: string, chainId: C
queryFn: () => isSmartContractAddress(userAddress, chainId),
// Assume if an address isn't a sc, it never will be in the lifetime of a tab
staleTime: Infinity,
// And don't garbage collect me either, this isn't guaranteed to have listeners
gcTime: Infinity,
})
}
export const useIsSmartContractAddress = (address: string, chainId: ChainId) => {
Expand All @@ -41,6 +43,8 @@ export const useIsSmartContractAddress = (address: string, chainId: ChainId) =>
: skipToken,
// Assume if an address isn't a sc, it never will be in the lifetime of a tab
staleTime: Infinity,
// Seriously. It never will.
gcTime: Infinity,
})

return query
Expand Down
4 changes: 2 additions & 2 deletions src/lib/account/evm.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,8 @@ import {
} from '@shapeshiftoss/hdwallet-core'
import { MetaMaskMultiChainHDWallet } from '@shapeshiftoss/hdwallet-metamask-multichain'
import type { AccountMetadataById } from '@shapeshiftoss/types'
import { fetchIsSmartContractAddressQuery } from 'hooks/useIsSmartContractAddress/useIsSmartContractAddress'
import { canAddMetaMaskAccount } from 'hooks/useIsSnapInstalled/useIsSnapInstalled'
import { isSmartContractAddress } from 'lib/address/utils'
import { assertGetEvmChainAdapter } from 'lib/utils/evm'

import type { DeriveAccountIdsAndMetadata } from './account'
Expand Down Expand Up @@ -72,7 +72,7 @@ export const deriveEvmAccountIdsAndMetadata: DeriveAccountIdsAndMetadata = async

for (const accountId of Object.keys(result)) {
const { chainId, account } = fromAccountId(accountId)
if (await isSmartContractAddress(account, chainId)) {
if (await fetchIsSmartContractAddressQuery(account, chainId)) {
maybeWalletConnectV2SmartContractAccountId = accountId
break
}
Expand Down
2 changes: 2 additions & 0 deletions src/lib/portals/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -255,6 +255,8 @@ export const getPortalTokens = async (
const portalsPlatforms = await queryClient.fetchQuery({
queryFn: () => fetchPortalsPlatforms(),
queryKey: ['portalsPlatforms'],
// This should effectively be considered static as far as the lifecycle of the app/our usage is concerned
staleTime: Infinity,
})
const chainId = nativeAsset.chainId

Expand Down
1 change: 1 addition & 0 deletions src/pages/Markets/hooks/usePortalsAssetsQuery.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ export const usePortalsAssetsQuery = ({
const { data: portalsPlatformsData } = useQuery({
queryKey: ['portalsPlatforms'],
queryFn: enabled ? () => fetchPortalsPlatforms() : skipToken,
// This should effectively be considered static as far as the lifecycle of the app/our usage is concerned
staleTime: Infinity,
})

Expand Down
Loading
Loading