Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
0f99a80
add xp balance fetching in background
ruijialin-avalabs Oct 14, 2025
99608fc
fix import
ruijialin-avalabs Oct 14, 2025
88ab20f
add logger
ruijialin-avalabs Oct 14, 2025
62c8947
pr comment
ruijialin-avalabs Oct 16, 2025
c79a69f
fix import
ruijialin-avalabs Oct 16, 2025
0204408
extract and flatten addresses
ruijialin-avalabs Oct 16, 2025
48f5f37
fixed incorrect function signature
ruijialin-avalabs Oct 16, 2025
38a6291
address pr comments
ruijialin-avalabs Oct 16, 2025
2fbe6d6
Merge branch 'main' into cp-12207
ruijialin-avalabs Oct 16, 2025
a18f34b
call setAccounts to trigger balance fetching again
ruijialin-avalabs Oct 16, 2025
a0756ab
Merge branch 'main' into cp-12207
ruijialin-avalabs Oct 17, 2025
c7906c8
adjust fetch balance for account
ruijialin-avalabs Oct 17, 2025
1b7f47c
trigger balanceUpdate after all seedless account are fetched
ruijialin-avalabs Oct 17, 2025
e92182d
dont await to fetch all
ruijialin-avalabs Oct 17, 2025
c357981
Merge branch 'main' into cp-12207
ruijialin-avalabs Oct 17, 2025
27a0cc9
Merge branch 'main' into cp-12207
ruijialin-avalabs Oct 17, 2025
04b9bf0
Merge branch 'main' into cp-12207
ruijialin-avalabs Oct 27, 2025
0f7906d
Merge branch 'main' into cp-12207
ruijialin-avalabs Oct 28, 2025
db8ff98
show loading when balance is not loaded
ruijialin-avalabs Oct 28, 2025
ba75c22
check if balance is polling and loading
ruijialin-avalabs Oct 28, 2025
5fe8a48
update comment
ruijialin-avalabs Oct 28, 2025
8e3ec93
Merge branch 'main' into cp-12207
ruijialin-avalabs Oct 28, 2025
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
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { selectTokenVisibility } from 'store/portfolio'
import {
fetchBalanceForAccount,
QueryStatus,
selectBalanceStatus,
selectAllBalanceStatus,
selectBalanceTotalInCurrencyForAccount,
selectIsBalanceLoadedForAccount
} from 'store/balance'
Expand All @@ -19,8 +19,8 @@ export const useBalanceForAccount = (
balance: number
} => {
const dispatch = useDispatch()
const balanceStatus = useSelector(selectBalanceStatus)
const isBalanceLoading = balanceStatus !== QueryStatus.IDLE
const allBalanceStatus = useSelector(selectAllBalanceStatus)
const isBalanceLoading = allBalanceStatus !== QueryStatus.IDLE
const [isFetchingBalance, setIsFetchingBalance] = useState(true)
const tokenVisibility = useSelector(selectTokenVisibility)
const accountBalance = useSelector(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import {
selectIsAllBalancesInaccurate,
selectIsBalanceLoadedForAccount,
selectIsLoadingBalances,
selectIsPollingBalances,
selectIsRefetchingBalances
} from 'store/balance'
import { selectEnabledNetworks } from 'store/network'
Expand Down Expand Up @@ -58,6 +59,7 @@ const AssetsScreen: FC<Props> = ({
)
const isAllBalancesError = useSelector(selectIsAllBalancesError)
const isBalanceLoading = useSelector(selectIsLoadingBalances)
const isBalancePolling = useSelector(selectIsPollingBalances)
const isRefetchingBalance = useSelector(selectIsRefetchingBalances)
const tokenVisibility = useSelector(selectTokenVisibility)
const balanceTotalInCurrency = useSelector(
Expand All @@ -80,11 +82,13 @@ const AssetsScreen: FC<Props> = ({
[goToTokenManagement, view, onScrollResync]
)

const isLoadingBalance = isRefetchingBalance || isBalanceLoading
const isLoadingBalance =
isRefetchingBalance || isBalanceLoading || isBalancePolling

const isGridView = view.selected === AssetManageView.Grid
const numColumns = isGridView ? 2 : 1

// Only show loading state for initial load, not during background polling
// Only show loading state for initial load
const isInitialLoading = isLoadingBalance && !isBalanceLoaded

const hasNoAssets =
Expand Down
55 changes: 55 additions & 0 deletions packages/core-mobile/app/services/balance/BalanceService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import ModuleManager from 'vmModule/ModuleManager'
import { mapToVmNetwork } from 'vmModule/utils/mapToVmNetwork'
import { coingeckoInMemoryCache } from 'utils/coingeckoInMemoryCache'
import { NetworkVMType } from '@avalabs/core-chains-sdk'
import { chunk } from 'lodash'

export type BalancesForAccount = {
accountId: string
Expand All @@ -20,6 +21,8 @@ export type BalancesForAccount = {
error: Error | null
}

export type BalancesForXpAddress = Omit<BalancesForAccount, 'accountId'>

export class BalanceService {
async getBalancesForAccount({
network,
Expand Down Expand Up @@ -68,6 +71,58 @@ export class BalanceService {
error: null
}
}

/**
* Get balances for active addresses on XP chain
*/
async getXPBalances({
currency,
network,
addresses
}: {
currency: string
network: Network & { vmName: NetworkVMType.AVM | NetworkVMType.PVM }
addresses: string[]
}): Promise<BalancesForXpAddress[]> {
const allBalances: BalancesForXpAddress[] = []

// avalancheModule.getBalances can only process up to 64 addresses at a time, so we need to split the addresses into chunks
const chunkSize = 64
const chunks = chunk(addresses, chunkSize)

await Promise.all(
chunks.map(async c => {
const balancesResponse =
await ModuleManager.avalancheModule.getBalances({
addresses: c,
currency,
network: mapToVmNetwork(network),
storage: coingeckoInMemoryCache,
tokenTypes: [TokenType.NATIVE]
})

for (const address in balancesResponse) {
const balances = balancesResponse[address] ?? {}
if ('error' in balances) {
allBalances.push({
accountAddress: address,
chainId: network.chainId,
tokens: [],
error: balances.error as Error
})
} else {
allBalances.push({
accountAddress: address,
chainId: network.chainId,
tokens: Object.values(balances),
error: null
})
}
}
})
)
return allBalances
}
}

export default new BalanceService()
1 change: 1 addition & 0 deletions packages/core-mobile/app/services/sentry/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ export type SpanName =
* Keeps track of all possible op names
*/
export type OpName =
| 'svc.balance.get_for_xp_networks'
| 'svc.balance.get_for_account'
| 'svc.balance.get_for_address'
| 'svc.balance.btc.get'
Expand Down
1 change: 1 addition & 0 deletions packages/core-mobile/app/store/account/listeners.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ import {
migrateRemainingActiveAccounts,
shouldMigrateActiveAccounts
} from './utils'

const initAccounts = async (
_action: AnyAction,
listenerApi: AppListenerEffectAPI
Expand Down
54 changes: 52 additions & 2 deletions packages/core-mobile/app/store/account/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { Account, AccountCollection } from 'store/account/types'
import { Network, NetworkVMType } from '@avalabs/core-chains-sdk'
import WalletFactory from 'services/wallet/WalletFactory'
import { WalletType } from 'services/wallet/types'
import { uniqWith } from 'lodash'
import SeedlessWallet from 'seedless/services/wallet/SeedlessWallet'
import { transactionSnackbar } from 'common/utils/toast'
import Logger from 'utils/Logger'
Expand All @@ -15,7 +16,8 @@ import { commonStorage } from 'utils/mmkv'
import { StorageKey } from 'resources/Constants'
import { appendToStoredArray, loadArrayFromStorage } from 'utils/mmkv/storages'
import { setIsMigratingActiveAccounts } from 'store/wallet/slice'
import { setNonActiveAccounts } from './slice'
import WalletService from 'services/wallet/WalletService'
import { setAccounts, setNonActiveAccounts } from './slice'

export function getAddressByVM(
vm: VM,
Expand Down Expand Up @@ -120,7 +122,12 @@ export const migrateRemainingActiveAccounts = async ({

const accountIds = Object.keys(accounts)
if (accountIds.length > 0) {
listenerApi.dispatch(setNonActiveAccounts(accounts))
// set accounts for seedless wallet, which trigger balance update
// * seedless wallet fetches xp balances by iterating over xp addresses over all accounts
// * so we need to wait for all accounts to be fetched to update balances
walletType === WalletType.SEEDLESS
? listenerApi.dispatch(setAccounts(accounts))
: listenerApi.dispatch(setNonActiveAccounts(accounts))

recentAccountsStore.getState().addRecentAccounts(accountIds)

Expand Down Expand Up @@ -202,3 +209,46 @@ const markWalletAsMigrated = (walletId: string): void => {
walletId
)
}
export async function getAddressesForXP({
isDeveloperMode,
walletId,
walletType,
networkType,
onlyWithActivity
}: {
isDeveloperMode: boolean
walletId: string | null
walletType: WalletType | undefined
networkType: NetworkVMType.AVM | NetworkVMType.PVM
onlyWithActivity: boolean
}): Promise<string[]> {
if (!walletId) {
throw new Error('Wallet ID is required')
}
if (!walletType) {
throw new Error('Wallet type is unknown')
}
try {
const activeAddresses = await WalletService.getAddressesFromXpubXP({
walletId,
walletType,
networkType,
isTestnet: isDeveloperMode,
onlyWithActivity
})

const externalAddresses = activeAddresses.externalAddresses.map(
address => address.address
)
const internalAddresses = activeAddresses.internalAddresses.map(
address => address.address
)
return uniqWith(
[...externalAddresses, ...internalAddresses],
(a, b) => a === b
)
} catch (error) {
Logger.error('Failed to get addresses for XP', error)
throw new Error('Failed to get addresses for XP')
}
}
Loading
Loading