diff --git a/ios/Podfile.lock b/ios/Podfile.lock
index a85de240a..b761b4cf7 100644
--- a/ios/Podfile.lock
+++ b/ios/Podfile.lock
@@ -861,9 +861,9 @@ SPEC CHECKSUMS:
FBLazyVector: f1897022b53abf1469d6ad692ee2c69f57d967f3
FBReactNativeSpec: 627fd07f1b9d498c9fa572e76d7f1a6b1ee9a444
fmt: ff9d55029c625d3757ed641535fd4a75fedc7ce9
- glog: 04b94705f318337d7ead9e6d17c019bd9b1f6b1b
+ glog: 791fe035093b84822da7f0870421a25839ca7870
helium-react-native-sdk: 32c0a7e3abc733a7f3d291013b2db31475fc6980
- hermes-engine: 0784cadad14b011580615c496f77e0ae112eed75
+ hermes-engine: 7a53ccac09146018a08239c5425625fdb79a6162
libevent: 4049cae6c81cdb3654a443be001fb9bdceff7913
MapboxCommon: fdf7fd31c90b7b607cd9c63e37797f023c01d860
MapboxCoreMaps: 24270c7c6b8cb71819fc2f3c549db9620ee4d019
@@ -871,7 +871,7 @@ SPEC CHECKSUMS:
MapboxMobileEvents: de50b3a4de180dd129c326e09cd12c8adaaa46d6
MultiplatformBleAdapter: 5a6a897b006764392f9cef785e4360f54fb9477d
OneSignalXCFramework: 81ceac017a290f23793443323090cfbe888f74ea
- RCT-Folly: 424b8c9a7a0b9ab2886ffe9c3b041ef628fd4fb1
+ RCT-Folly: 85766c3226c7ec638f05ad7cb3cf6a268d6c4241
RCTRequired: bd6045fbd511da5efe6db89eecb21e4e36bd7cbf
RCTTypeSafety: c06d9f906faa69dd1c88223204c3a24767725fd8
React: b9ea33557ef1372af247f95d110fbdea114ed3b2
diff --git a/src/features/account/AccountManageTokenListScreen.tsx b/src/features/account/AccountManageTokenListScreen.tsx
index f286f7b47..4d48ada77 100644
--- a/src/features/account/AccountManageTokenListScreen.tsx
+++ b/src/features/account/AccountManageTokenListScreen.tsx
@@ -13,14 +13,20 @@ import { usePublicKey } from '@hooks/usePublicKey'
import CheckBox from '@react-native-community/checkbox'
import { useNavigation } from '@react-navigation/native'
import { PublicKey } from '@solana/web3.js'
+import { useAccountStorage } from '@storage/AccountStorageProvider'
import { useVisibleTokens } from '@storage/TokensProvider'
import { useColors, useHitSlop } from '@theme/themeHooks'
import { useBalance } from '@utils/Balance'
import { humanReadable } from '@utils/solanaUtils'
import BN from 'bn.js'
import React, { memo, useCallback, useMemo } from 'react'
+import { useAsyncCallback } from 'react-async-hook'
+import { RefreshControl } from 'react-native'
import { FlatList } from 'react-native-gesture-handler'
import { Edge } from 'react-native-safe-area-context'
+import { useSolana } from '../../solana/SolanaProvider'
+import { syncTokenAccounts } from '../../store/slices/balancesSlice'
+import { useAppDispatch } from '../../store/store'
import { HomeNavigationProp } from '../home/homeTypes'
import AccountTokenCurrencyBalance from './AccountTokenCurrencyBalance'
import { getSortValue } from './AccountTokenList'
@@ -120,7 +126,18 @@ const AccountManageTokenListScreen: React.FC = () => {
return getSortValue(b) - getSortValue(a)
})
}, [tokenAccounts])
+ const dispatch = useAppDispatch()
+ const { anchorProvider, cluster } = useSolana()
+ const { currentAccount } = useAccountStorage()
+ const colors = useColors()
+ const { loading: refetchingTokens, execute: refetchTokens } =
+ useAsyncCallback(async () => {
+ if (!anchorProvider || !currentAccount || !cluster) return
+ await dispatch(
+ syncTokenAccounts({ cluster, acct: currentAccount, anchorProvider }),
+ )
+ })
const renderItem = useCallback(
// eslint-disable-next-line react/no-unused-prop-types
({ index, item: token }: { index: number; item: string }) => {
@@ -163,6 +180,15 @@ const AccountManageTokenListScreen: React.FC = () => {
+ }
data={mints}
renderItem={renderItem}
keyExtractor={keyExtractor}
diff --git a/src/features/account/AccountTokenList.tsx b/src/features/account/AccountTokenList.tsx
index c8169c06d..b952ae756 100644
--- a/src/features/account/AccountTokenList.tsx
+++ b/src/features/account/AccountTokenList.tsx
@@ -3,15 +3,24 @@ import Text from '@components/Text'
import TouchableOpacityBox from '@components/TouchableOpacityBox'
import { BottomSheetFlatList } from '@gorhom/bottom-sheet'
import { BottomSheetFlatListProps } from '@gorhom/bottom-sheet/lib/typescript/components/bottomSheetScrollable/types'
+import { useAccountFetchCache } from '@helium/account-fetch-cache-hooks'
import { DC_MINT, HNT_MINT, IOT_MINT, MOBILE_MINT } from '@helium/spl-utils'
import { useNavigation } from '@react-navigation/native'
+import { getAssociatedTokenAddressSync } from '@solana/spl-token'
import { PublicKey } from '@solana/web3.js'
-import { useVisibleTokens, DEFAULT_TOKENS } from '@storage/TokensProvider'
+import { useAccountStorage } from '@storage/AccountStorageProvider'
+import { DEFAULT_TOKENS, useVisibleTokens } from '@storage/TokensProvider'
+import { useColors } from '@theme/themeHooks'
import { useBalance } from '@utils/Balance'
import { times } from 'lodash'
-import React, { useCallback, useMemo } from 'react'
+import React, { useCallback, useEffect, useMemo } from 'react'
+import { useAsyncCallback } from 'react-async-hook'
import { useTranslation } from 'react-i18next'
+import { AppState, RefreshControl } from 'react-native'
import { useSafeAreaInsets } from 'react-native-safe-area-context'
+import { useSolana } from '../../solana/SolanaProvider'
+import { syncTokenAccounts } from '../../store/slices/balancesSlice'
+import { useAppDispatch } from '../../store/store'
import { HomeNavigationProp } from '../home/homeTypes'
import TokenListItem, { TokenSkeleton } from './TokenListItem'
@@ -33,6 +42,46 @@ const AccountTokenList = ({ onLayout }: Props) => {
const navigation = useNavigation()
const { t } = useTranslation()
const { visibleTokens } = useVisibleTokens()
+ const { currentAccount } = useAccountStorage()
+ const dispatch = useAppDispatch()
+ const { anchorProvider, cluster } = useSolana()
+ const colors = useColors()
+ const cache = useAccountFetchCache()
+
+ const { loading: refetchingTokens, execute: refetchTokens } =
+ useAsyncCallback(async () => {
+ if (!anchorProvider || !currentAccount || !cluster) return
+ await dispatch(
+ syncTokenAccounts({ cluster, acct: currentAccount, anchorProvider }),
+ )
+ await Promise.all(
+ [...visibleTokens].map((mintStr) => {
+ // Trigger a refetch on all visible token accounts
+ return cache.search(
+ getAssociatedTokenAddressSync(
+ new PublicKey(mintStr),
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+ new PublicKey(currentAccount!.solanaAddress!),
+ ),
+ undefined,
+ false,
+ true,
+ )
+ }),
+ )
+ })
+
+ // Trigger refresh when the app comes into the foreground from the background
+ useEffect(() => {
+ const listener = AppState.addEventListener('change', (state) => {
+ if (state === 'active') {
+ refetchTokens()
+ }
+ })
+ return () => {
+ listener.remove()
+ }
+ }, [refetchTokens])
const onManageTokenList = useCallback(() => {
navigation.navigate('AccountManageTokenListScreen')
@@ -107,6 +156,17 @@ const AccountTokenList = ({ onLayout }: Props) => {
onLayout={onLayout}
data={mints}
numColumns={2}
+ refreshControl={
+
+ }
+ refreshing={refetchingTokens}
+ onRefresh={refetchTokens}
columnWrapperStyle={{
flexDirection: 'column',
}}