From 8c7f41658ff4dd1ecf821dca4516435e084872f1 Mon Sep 17 00:00:00 2001 From: Nuo Xu Date: Fri, 7 Jun 2024 11:54:04 -0400 Subject: [PATCH 1/5] apply persistent token/account balance for the rest of the wallet. --- .../Crypto/Stores/AccountActivityStore.swift | 19 ++-- .../Crypto/Stores/AccountsStore.swift | 74 ++++++++++---- .../Crypto/Stores/AssetDetailStore.swift | 17 ++-- .../Crypto/Stores/NFTDetailStore.swift | 19 ++-- .../BraveWallet/Crypto/Stores/NFTStore.swift | 99 ++++++++++++------- .../Crypto/Stores/PortfolioStore.swift | 12 ++- .../Stores/SelectAccountTokenStore.swift | 27 +++-- .../Crypto/Stores/SendTokenStore.swift | 17 +++- .../Crypto/Stores/SwapTokenStore.swift | 90 ++++++++++------- .../Stores/TransactionConfirmationStore.swift | 11 ++- .../Extensions/RpcServiceExtensions.swift | 25 ----- 11 files changed, 259 insertions(+), 151 deletions(-) diff --git a/ios/brave-ios/Sources/BraveWallet/Crypto/Stores/AccountActivityStore.swift b/ios/brave-ios/Sources/BraveWallet/Crypto/Stores/AccountActivityStore.swift index 2f34460b3be4..d0565f2a7c3c 100644 --- a/ios/brave-ios/Sources/BraveWallet/Crypto/Stores/AccountActivityStore.swift +++ b/ios/brave-ios/Sources/BraveWallet/Crypto/Stores/AccountActivityStore.swift @@ -228,8 +228,6 @@ class AccountActivityStore: ObservableObject, WalletObserverStore { ) self.isLoadingAccountFiat = true - // TODO: cleanup with balance caching with issue - // https://github.com/brave/brave-browser/issues/36764 var tokenBalances: [String: Double] = [:] if account.coin == .btc { let networkAsset = allUserNetworkAssets.first { @@ -244,10 +242,19 @@ class AccountActivityStore: ObservableObject, WalletObserverStore { tokenBalances = [btcToken.id: btcTotalBalance] } } else { - tokenBalances = await self.rpcService.fetchBalancesForTokens( - account: account, - networkAssets: allUserNetworkAssets - ) + if let accountBalances = self.assetManager.getBalances(for: nil, account: account.id) { + tokenBalances = accountBalances.reduce(into: [String: Double]()) { + let tokenId = + $1.contractAddress + $1.chainId + + $1.symbol + $1.tokenId + $0[tokenId] = Double($1.balance) ?? 0 + } + } else { + tokenBalances = await self.rpcService.fetchBalancesForTokens( + account: account, + networkAssets: allUserNetworkAssets + ) + } } tokenBalanceCache.merge(with: tokenBalances) diff --git a/ios/brave-ios/Sources/BraveWallet/Crypto/Stores/AccountsStore.swift b/ios/brave-ios/Sources/BraveWallet/Crypto/Stores/AccountsStore.swift index dd4caca6b155..6ad8abe9ddce 100644 --- a/ios/brave-ios/Sources/BraveWallet/Crypto/Stores/AccountsStore.swift +++ b/ios/brave-ios/Sources/BraveWallet/Crypto/Stores/AccountsStore.swift @@ -34,7 +34,7 @@ class AccountsStore: ObservableObject, WalletObserverStore { let currencyFormatter: NumberFormatter = .usdCurrencyFormatter private typealias TokenBalanceCache = [String: [String: Double]] - /// Cache of token balances for each account. [account.cacheBalanceKey: [token.id: balance]] + /// Cache of token balances for each account. `[account.id: [token.id: balance]]` private var tokenBalancesCache: TokenBalanceCache = [:] /// Cache of prices for each token. The key is the token's `assetRatioId`. private var pricesCache: [String: String] = [:] @@ -141,35 +141,69 @@ class AccountsStore: ObservableObject, WalletObserverStore { for accounts: [BraveWallet.AccountInfo], networkAssets allNetworkAssets: [NetworkAssets] ) async { + // Update BTC account balance + if accounts.contains(where: { $0.coin == .btc }) { + await withTaskGroup( + of: [String: [String: Double]].self + ) { [bitcoinWalletService] group in + for account in accounts where account.coin == .btc { + group.addTask { + let btcBalance = + await bitcoinWalletService.fetchBTCBalance( + accountId: account.accountId, + type: .total + ) ?? 0 + if let btcToken = allNetworkAssets.first(where: { + $0.network.supportedKeyrings.contains( + account.keyringId.rawValue as NSNumber + ) + })?.tokens.first { + return [account.id: [btcToken.id: btcBalance]] + } + return [:] + } + } + for await accountBTCBalances in group { + tokenBalancesCache.merge(with: accountBTCBalances) + } + } + } + // Update non-BTC account balance let balancesForAccounts = await withTaskGroup( of: TokenBalanceCache.self, body: { group in - for account in accounts { - group.addTask { - // TODO: cleanup with balance caching with issue - // https://github.com/brave/brave-browser/issues/36764 - var balancesForTokens: [String: Double] = [:] - if account.coin == .btc { - let networkAssets = allNetworkAssets.first { - $0.network.supportedKeyrings.contains(account.keyringId.rawValue as NSNumber) - } - if let btc = networkAssets?.tokens.first, - let btcBalance = await self.bitcoinWalletService.fetchBTCBalance( - accountId: account.accountId, - type: .total - ) - { - balancesForTokens = [btc.id: btcBalance] - } - } else { + for account in accounts where account.coin != .btc { + if let allTokenBalance = self.userAssetManager.getBalances( + for: nil, + account: account.id + ) { + var result: [String: Double] = [:] + for balancePerToken in allTokenBalance { + let tokenId = + balancePerToken.contractAddress + balancePerToken.chainId + + balancePerToken.symbol + balancePerToken.tokenId + result.merge(with: [ + tokenId: Double(balancePerToken.balance) ?? 0 + ]) + } + self.tokenBalancesCache.merge(with: [account.id: result]) + } else { + // 1. We have a user asset from CD but wallet has never + // fetched it's balance. Should never happen. But we will fetch its + // balance and cache it in CD. + // 2. Test Cases will come here, we will fetch balance using + // a mock `rpcService` and `bitcoinWalletService` + group.addTask { + var balancesForTokens: [String: Double] = [:] balancesForTokens = await self.rpcService.fetchBalancesForTokens( account: account, networkAssets: allNetworkAssets ) + return [account.id: balancesForTokens] } - return [account.id: balancesForTokens] } } + return await group.reduce( into: TokenBalanceCache(), { partialResult, new in diff --git a/ios/brave-ios/Sources/BraveWallet/Crypto/Stores/AssetDetailStore.swift b/ios/brave-ios/Sources/BraveWallet/Crypto/Stores/AssetDetailStore.swift index 44b09d8c5247..bc0f7fe888ca 100644 --- a/ios/brave-ios/Sources/BraveWallet/Crypto/Stores/AssetDetailStore.swift +++ b/ios/brave-ios/Sources/BraveWallet/Crypto/Stores/AssetDetailStore.swift @@ -457,8 +457,6 @@ class AssetDetailStore: ObservableObject, WalletObserverStore { @MainActor group -> [AccountBalance] in for accountAssetViewModel in accountAssetViewModels { group.addTask { @MainActor in - // TODO: cleanup with balance caching with issue - // https://github.com/brave/brave-browser/issues/36764 var tokenBalance: Double? if accountAssetViewModel.account.coin == .btc { tokenBalance = await self.bitcoinWalletService.fetchBTCBalance( @@ -466,11 +464,18 @@ class AssetDetailStore: ObservableObject, WalletObserverStore { type: .total ) } else { - tokenBalance = await self.rpcService.balance( + if let assetBalancePerAccount = self.assetManager.getBalances( for: token, - in: accountAssetViewModel.account, - network: network - ) + account: accountAssetViewModel.account.id + )?.first { + tokenBalance = Double(assetBalancePerAccount.balance) + } else { + tokenBalance = await self.rpcService.balance( + for: token, + in: accountAssetViewModel.account, + network: network + ) + } } return [AccountBalance(accountAssetViewModel.account, tokenBalance)] } diff --git a/ios/brave-ios/Sources/BraveWallet/Crypto/Stores/NFTDetailStore.swift b/ios/brave-ios/Sources/BraveWallet/Crypto/Stores/NFTDetailStore.swift index 4472ae518bcb..ee4601ddf152 100644 --- a/ios/brave-ios/Sources/BraveWallet/Crypto/Stores/NFTDetailStore.swift +++ b/ios/brave-ios/Sources/BraveWallet/Crypto/Stores/NFTDetailStore.swift @@ -182,15 +182,22 @@ class NFTDetailStore: ObservableObject, WalletObserverStore { let accounts = await keyringService.allAccounts().accounts let nftBalances: [String: Int] = await withTaskGroup( of: [String: Int].self, - body: { @MainActor [rpcService, nft] group in + body: { @MainActor [assetManager, rpcService, nft] group in for account in accounts where account.coin == nft.coin { group.addTask { @MainActor in - let balanceForToken = await rpcService.balance( + if let assetBalance = assetManager.getBalances( for: nft, - in: account, - network: network - ) - return [account.id: Int(balanceForToken ?? 0)] + account: account.id + )?.first { + return [account.id: (assetBalance.balance as NSString).integerValue] + } else { + let balanceForToken = await rpcService.balance( + for: nft, + in: account, + network: network + ) + return [account.id: Int(balanceForToken ?? 0)] + } } } return await group.reduce( diff --git a/ios/brave-ios/Sources/BraveWallet/Crypto/Stores/NFTStore.swift b/ios/brave-ios/Sources/BraveWallet/Crypto/Stores/NFTStore.swift index 46140ad7bf85..8821114588e0 100644 --- a/ios/brave-ios/Sources/BraveWallet/Crypto/Stores/NFTStore.swift +++ b/ios/brave-ios/Sources/BraveWallet/Crypto/Stores/NFTStore.swift @@ -308,55 +308,80 @@ public class NFTStore: ObservableObject, WalletObserverStore { // if we're not hiding unowned or grouping by account, balance isn't needed if filters.isHidingUnownedNFTs || filters.groupBy == .accounts { let allAccounts = filters.accounts.map(\.model) - nftBalancesCache = await withTaskGroup( + + let fetchedNFTBlances = await withTaskGroup( of: [String: [String: Int]].self, - body: { @MainActor [nftBalancesCache, rpcService] group in - for nft in allUserNFTs { // for all NFTs that have not yet been fetched its balance - guard let networkForNFT = allNetworks.first(where: { $0.chainId == nft.chainId }) - else { - continue - } - group.addTask { @MainActor in - let updatedBalances = await withTaskGroup( - of: [String: Int].self, - body: { @MainActor group in - for account in allAccounts where account.coin == nft.coin { - if !forceUpdateNFTBalances, - let cachedBalance = nftBalancesCache[nft.id]?[account.id] - { // cached balance - return [account.id: cachedBalance] - } else { // no balance for this account - group.addTask { @MainActor in - let balanceForToken = await rpcService.balance( - for: nft, - in: account, - network: networkForNFT - ) - return [account.id: Int(balanceForToken ?? 0)] - } - } - } - return await group.reduce( - into: [String: Int](), - { partialResult, new in - partialResult.merge(with: new) - } + body: { @MainActor [rpcService, assetManager] group in + for nft in allUserNFTs { + if let nftBalances = assetManager.getBalances( + for: nft, + account: nil + ), + !nftBalances.isEmpty, + !forceUpdateNFTBalances + { + var result: [String: Int] = [:] + for balancePerAccount in nftBalances { + result.merge(with: [ + balancePerAccount.accountAddress: + (balancePerAccount.balance as NSString).integerValue + ]) + } + nftBalancesCache.merge(with: [nft.id: result]) + } else { + // 1. Force to fetch NFT balance + // 2. Spam NFT + // 3. We have a user asset from CD but wallet has never + // fetched it's balance. Should never happen. But we will fetch its + // balance and cache it in CD. + // 4. Test Cases will come here, we will fetch balance using + // a mock `rpcService` and `bitcoinWalletService + guard let networkForNFT = allNetworks.first(where: { $0.chainId == nft.chainId }) + else { + continue + } + group.addTask { @MainActor in + var nftBalances: [String: Int] = [:] + for account in allAccounts where account.coin == nft.coin { + var balanceForNFT: Int? + let balanceInDouble = await rpcService.balance( + for: nft, + in: account, + network: networkForNFT + ) + balanceForNFT = Int(balanceInDouble ?? 0) + nftBalances.merge(with: [account.id: balanceForNFT ?? 0]) + assetManager.updateBalance( + for: nft, + account: account.id, + balance: "\(balanceForNFT ?? 0)", + completion: nil ) } - ) - var tokenBalances = nftBalancesCache[nft.id] ?? [:] - tokenBalances.merge(with: updatedBalances) - return [nft.id: tokenBalances] + return [nft.id: nftBalances] + } } } + return await group.reduce( - into: [String: [String: Int]](), + into: [:], { partialResult, new in partialResult.merge(with: new) } ) } ) + for nft in allUserNFTs { + if let updatedBalancesForNFT = fetchedNFTBlances[nft.id] { + // if balance fetch failed that we already have cached, don't overwrite existing + if var existing = self.nftBalancesCache[nft.id] { + existing.merge(with: updatedBalancesForNFT) + self.nftBalancesCache[nft.id] = existing + } else { + self.nftBalancesCache[nft.id] = updatedBalancesForNFT + } + } + } } guard !Task.isCancelled else { return } let (userNFTGroupsWithBalance, _) = buildNFTGroupModels( diff --git a/ios/brave-ios/Sources/BraveWallet/Crypto/Stores/PortfolioStore.swift b/ios/brave-ios/Sources/BraveWallet/Crypto/Stores/PortfolioStore.swift index a79d484a7741..8cfa43a5d708 100644 --- a/ios/brave-ios/Sources/BraveWallet/Crypto/Stores/PortfolioStore.swift +++ b/ios/brave-ios/Sources/BraveWallet/Crypto/Stores/PortfolioStore.swift @@ -314,7 +314,7 @@ public class PortfolioStore: ObservableObject, WalletObserverStore { /// Cancellable for the last running `update()` Task. private var updateTask: Task<(), Never>? private typealias TokenBalanceCache = [String: [String: Double]] - /// Cache of token balances for each account. [token.id: [account.cacheBalanceKey: balance]] + /// Cache of token balances for each account. [token.id: [account.id: balance]] private var tokenBalancesCache: TokenBalanceCache = [:] /// Cache of prices for each token. The key is the token's `assetRatioId`. private var pricesCache: [String: String] = [:] @@ -469,10 +469,14 @@ public class PortfolioStore: ObservableObject, WalletObserverStore { token: token, network: networkAssets.network, accounts: selectedAccounts.filter { - if token.coin == .fil { + switch token.coin { + case .fil, .btc, .zec: return $0.keyringId - == BraveWallet.KeyringId.keyringId(for: token.coin, on: token.chainId) - } else { + == BraveWallet.KeyringId.keyringId( + for: token.coin, + on: token.chainId + ) + default: return $0.coin == token.coin } } diff --git a/ios/brave-ios/Sources/BraveWallet/Crypto/Stores/SelectAccountTokenStore.swift b/ios/brave-ios/Sources/BraveWallet/Crypto/Stores/SelectAccountTokenStore.swift index 651b219dc33e..1ebd9ade5be3 100644 --- a/ios/brave-ios/Sources/BraveWallet/Crypto/Stores/SelectAccountTokenStore.swift +++ b/ios/brave-ios/Sources/BraveWallet/Crypto/Stores/SelectAccountTokenStore.swift @@ -248,13 +248,26 @@ class SelectAccountTokenStore: ObservableObject, WalletObserverStore { of: TokenBalanceCache.self, body: { group in for account in allAccounts where account.coin != .btc { - group.addTask { // get balance for all tokens this account supports - let balancesForTokens: [String: Double] = await self.rpcService - .fetchBalancesForTokens( - account: account, - networkAssets: networkAssets - ) - return [account.id: balancesForTokens] + if let allTokenBalance = assetManager.getBalances(for: nil, account: account.id) { + var result: [String: Double] = [:] + for balancePerToken in allTokenBalance { + let tokenId = + balancePerToken.contractAddress + balancePerToken.chainId + + balancePerToken.symbol + balancePerToken.tokenId + result.merge(with: [ + tokenId: Double(balancePerToken.balance) ?? 0 + ]) + } + balancesForAccountsCache.merge(with: [account.id: result]) + } else { + group.addTask { // get balance for all tokens this account supports + let balancesForTokens: [String: Double] = await self.rpcService + .fetchBalancesForTokens( + account: account, + networkAssets: networkAssets + ) + return [account.id: balancesForTokens] + } } } return await group.reduce( diff --git a/ios/brave-ios/Sources/BraveWallet/Crypto/Stores/SendTokenStore.swift b/ios/brave-ios/Sources/BraveWallet/Crypto/Stores/SendTokenStore.swift index 4d7b11c9f981..a8a969fda320 100644 --- a/ios/brave-ios/Sources/BraveWallet/Crypto/Stores/SendTokenStore.swift +++ b/ios/brave-ios/Sources/BraveWallet/Crypto/Stores/SendTokenStore.swift @@ -392,12 +392,19 @@ public class SendTokenStore: ObservableObject, WalletObserverStore { } self.btcBalances[selectedAccount.id] = btcBalances } else { - balance = await self.rpcService.balance( + if let assetBalance = self.assetManager.getBalances( for: selectedSendToken, - in: selectedAccount.address, - network: network, - decimalFormatStyle: .decimals(precision: Int(selectedSendToken.decimals)) - ) + account: selectedAccount.id + )?.first { + balance = BDouble(assetBalance.balance) + } else { + balance = await self.rpcService.balance( + for: selectedSendToken, + in: selectedAccount.address, + network: network, + decimalFormatStyle: .decimals(precision: Int(selectedSendToken.decimals)) + ) + } } if selectedSendToken.isErc721 || selectedSendToken.isNft, diff --git a/ios/brave-ios/Sources/BraveWallet/Crypto/Stores/SwapTokenStore.swift b/ios/brave-ios/Sources/BraveWallet/Crypto/Stores/SwapTokenStore.swift index 1c05c6b5c938..60a00dd8dbe3 100644 --- a/ios/brave-ios/Sources/BraveWallet/Crypto/Stores/SwapTokenStore.swift +++ b/ios/brave-ios/Sources/BraveWallet/Crypto/Stores/SwapTokenStore.swift @@ -302,13 +302,20 @@ public class SwapTokenStore: ObservableObject, WalletObserverStore { } rpcService.network(coin: token.coin, origin: nil) { [weak self] network in - self?.rpcService.balance( + if let assetBalance = self?.assetManager.getBalances( for: token, - in: account.address, - network: network, - decimalFormatStyle: .decimals(precision: Int(token.decimals)) - ) { balance in - completion(balance) + account: account.id + )?.first(where: { $0.chainId == network.chainId }) { + completion(BDouble(assetBalance.balance)) + } else { + self?.rpcService.balance( + for: token, + in: account.address, + network: network, + decimalFormatStyle: .decimals(precision: Int(token.decimals)) + ) { balance in + completion(balance) + } } } } @@ -685,22 +692,29 @@ public class SwapTokenStore: ObservableObject, WalletObserverStore { let network = await rpcService.network(coin: accountInfo.coin, origin: nil) // Check if balance available to pay for gas - let (ethBalanceString, _, _) = await rpcService.balance( - address: accountInfo.address, - coin: network.coin, - chainId: network.chainId - ) + let ethBalance: BDouble + if let assetBalance = assetManager.getBalances( + for: network.nativeToken, + account: accountInfo.id + )?.first(where: { $0.chainId == network.chainId }) { + ethBalance = BDouble(assetBalance.balance) ?? 0 + } else { + let (ethBalanceString, _, _) = await rpcService.balance( + address: accountInfo.address, + coin: network.coin, + chainId: network.chainId + ) + let balanceFormatter = WalletAmountFormatter(decimalFormatStyle: .balance) + ethBalance = + BDouble( + balanceFormatter.decimalString( + for: ethBalanceString.removingHexPrefix, + radix: .hex, + decimals: 18 + ) ?? "" + ) ?? 0 + } let fee = gasLimit * gasPrice - let balanceFormatter = WalletAmountFormatter(decimalFormatStyle: .balance) - let ethBalance = - BDouble( - balanceFormatter.decimalString( - for: ethBalanceString.removingHexPrefix, - radix: .hex, - decimals: 18 - ) - ?? "" - ) ?? 0 if fromToken.symbol == network.symbol { if ethBalance < fee + sellAmountValue { self.state = .error(Strings.Wallet.insufficientFundsForGas) @@ -817,19 +831,27 @@ public class SwapTokenStore: ObservableObject, WalletObserverStore { // Check if balance available to pay for gas if route.fromToken.coin == .eth { - let (ethBalanceString, _, _) = await rpcService.balance( - address: accountInfo.address, - coin: network.coin, - chainId: network.chainId - ) - let ethBalance = - BDouble( - walletAmountFormatter.decimalString( - for: ethBalanceString.removingHexPrefix, - radix: .hex, - decimals: 18 - ) ?? "" - ) ?? 0 + let ethBalance: BDouble + if let assetBalance = assetManager.getBalances( + for: network.nativeToken, + account: accountInfo.id + )?.first(where: { $0.chainId == network.chainId }) { + ethBalance = BDouble(assetBalance.balance) ?? 0 + } else { + let (ethBalanceString, _, _) = await rpcService.balance( + address: accountInfo.address, + coin: network.coin, + chainId: network.chainId + ) + ethBalance = + BDouble( + walletAmountFormatter.decimalString( + for: ethBalanceString.removingHexPrefix, + radix: .hex, + decimals: 18 + ) ?? "" + ) ?? 0 + } let feeTotal: Double = step.estimate.gasCosts.reduce(Double(0)) { total, cost in total + (Double(cost.amount) ?? 0) } diff --git a/ios/brave-ios/Sources/BraveWallet/Crypto/Stores/TransactionConfirmationStore.swift b/ios/brave-ios/Sources/BraveWallet/Crypto/Stores/TransactionConfirmationStore.swift index e687349bfee5..f78cc44eec26 100644 --- a/ios/brave-ios/Sources/BraveWallet/Crypto/Stores/TransactionConfirmationStore.swift +++ b/ios/brave-ios/Sources/BraveWallet/Crypto/Stores/TransactionConfirmationStore.swift @@ -457,7 +457,16 @@ public class TransactionConfirmationStore: ObservableObject, WalletObserverStore gasBalancesForChain[account.id] = availableBTCBalance } } else { - if let gasTokenBalance = await rpcService.balance(for: token, in: account, network: network) { + if let assetBalance = assetManager.getBalances( + for: token, + account: account.id + )?.first(where: { $0.chainId == network.chainId }) { + gasBalancesForChain[account.id] = Double(assetBalance.balance) ?? 0 + } else if let gasTokenBalance = await rpcService.balance( + for: token, + in: account, + network: network + ) { gasBalancesForChain[account.id] = gasTokenBalance } } diff --git a/ios/brave-ios/Sources/BraveWallet/Extensions/RpcServiceExtensions.swift b/ios/brave-ios/Sources/BraveWallet/Extensions/RpcServiceExtensions.swift index 249dd06953e6..be47b38f6da8 100644 --- a/ios/brave-ios/Sources/BraveWallet/Extensions/RpcServiceExtensions.swift +++ b/ios/brave-ios/Sources/BraveWallet/Extensions/RpcServiceExtensions.swift @@ -293,31 +293,6 @@ extension BraveWalletJsonRpcService { } } - /// Returns the total balance for a given token for all of the given accounts - @MainActor func fetchTotalBalance( - token: BraveWallet.BlockchainToken, - network: BraveWallet.NetworkInfo, - accounts: [BraveWallet.AccountInfo] - ) async -> Double { - let balancesForAsset = await withTaskGroup( - of: [Double].self, - body: { @MainActor group in - for account in accounts { - group.addTask { @MainActor in - let balance = await self.balance( - for: token, - in: account, - network: network - ) - return [balance ?? 0] - } - } - return await group.reduce([Double](), { $0 + $1 }) - } - ) - return balancesForAsset.reduce(0, +) - } - /// Returns the total balance for a given account for all of the given network assets func fetchBalancesForTokens( account: BraveWallet.AccountInfo, From e4254cc7d3114d5204f2d86b88ab4ac0505f637f Mon Sep 17 00:00:00 2001 From: Nuo Xu Date: Mon, 10 Jun 2024 22:50:48 -0400 Subject: [PATCH 2/5] implement WalletUserAssetDataObserver to the stores that need to update after cached balances are refreshed. --- .../Crypto/Stores/AccountActivityStore.swift | 1 + .../BraveWallet/Crypto/Stores/AccountsStore.swift | 10 ++++++++++ .../Crypto/Stores/AssetDetailStore.swift | 10 ++++++++++ .../Crypto/Stores/NFTDetailStore.swift | 15 ++++++++++++--- .../Crypto/Stores/SendTokenStore.swift | 10 ++++++++++ .../Crypto/Stores/SwapTokenStore.swift | 14 ++++++++++++++ .../Stores/TransactionConfirmationStore.swift | 14 ++++++++++++++ .../BraveWallet/WalletUserAssetManager.swift | 1 + 8 files changed, 72 insertions(+), 3 deletions(-) diff --git a/ios/brave-ios/Sources/BraveWallet/Crypto/Stores/AccountActivityStore.swift b/ios/brave-ios/Sources/BraveWallet/Crypto/Stores/AccountActivityStore.swift index d0565f2a7c3c..c0601e24d533 100644 --- a/ios/brave-ios/Sources/BraveWallet/Crypto/Stores/AccountActivityStore.swift +++ b/ios/brave-ios/Sources/BraveWallet/Crypto/Stores/AccountActivityStore.swift @@ -515,6 +515,7 @@ class AccountActivityStore: ObservableObject, WalletObserverStore { extension AccountActivityStore: WalletUserAssetDataObserver { public func cachedBalanceRefreshed() { + update() } public func userAssetUpdated() { diff --git a/ios/brave-ios/Sources/BraveWallet/Crypto/Stores/AccountsStore.swift b/ios/brave-ios/Sources/BraveWallet/Crypto/Stores/AccountsStore.swift index 6ad8abe9ddce..9c76e773bcec 100644 --- a/ios/brave-ios/Sources/BraveWallet/Crypto/Stores/AccountsStore.swift +++ b/ios/brave-ios/Sources/BraveWallet/Crypto/Stores/AccountsStore.swift @@ -72,6 +72,7 @@ class AccountsStore: ObservableObject, WalletObserverStore { func setupObservers() { guard !isObserving else { return } + self.userAssetManager.addUserAssetDataObserver(self) self.keyringServiceObserver = KeyringServiceObserver( keyringService: keyringService, _accountsChanged: { [weak self] in @@ -326,3 +327,12 @@ class AccountsStore: ObservableObject, WalletObserverStore { tokenBalancesCache[account.id]?[tokenId] } } + +extension AccountsStore: WalletUserAssetDataObserver { + func cachedBalanceRefreshed() { + update() + } + + func userAssetUpdated() { + } +} diff --git a/ios/brave-ios/Sources/BraveWallet/Crypto/Stores/AssetDetailStore.swift b/ios/brave-ios/Sources/BraveWallet/Crypto/Stores/AssetDetailStore.swift index bc0f7fe888ca..23283d6ee988 100644 --- a/ios/brave-ios/Sources/BraveWallet/Crypto/Stores/AssetDetailStore.swift +++ b/ios/brave-ios/Sources/BraveWallet/Crypto/Stores/AssetDetailStore.swift @@ -170,6 +170,7 @@ class AssetDetailStore: ObservableObject, WalletObserverStore { func setupObservers() { guard !isObserving else { return } + self.assetManager.addUserAssetDataObserver(self) self.keyringServiceObserver = KeyringServiceObserver( keyringService: keyringService, _accountsChanged: { [weak self] in @@ -699,3 +700,12 @@ extension AssetDetailStore: BraveWalletBraveWalletServiceObserver { func onResetWallet() { } } + +extension AssetDetailStore: WalletUserAssetDataObserver { + func cachedBalanceRefreshed() { + update() + } + + func userAssetUpdated() { + } +} diff --git a/ios/brave-ios/Sources/BraveWallet/Crypto/Stores/NFTDetailStore.swift b/ios/brave-ios/Sources/BraveWallet/Crypto/Stores/NFTDetailStore.swift index ee4601ddf152..7435f77d1ef1 100644 --- a/ios/brave-ios/Sources/BraveWallet/Crypto/Stores/NFTDetailStore.swift +++ b/ios/brave-ios/Sources/BraveWallet/Crypto/Stores/NFTDetailStore.swift @@ -118,6 +118,8 @@ class NFTDetailStore: ObservableObject, WalletObserverStore { } func setupObservers() { + guard !isObserving else { return } + self.assetManager.addUserAssetDataObserver(self) self.txServiceObserver = TxServiceObserver( txService: txService, _onTransactionStatusChanged: { [weak self] txInfo in @@ -142,9 +144,7 @@ class NFTDetailStore: ObservableObject, WalletObserverStore { networkInfo = network } - if owner == nil { - updateOwner() - } + updateOwner() if nftMetadata == nil { isLoading = true @@ -224,3 +224,12 @@ class NFTDetailStore: ObservableObject, WalletObserverStore { } } } + +extension NFTDetailStore: WalletUserAssetDataObserver { + func cachedBalanceRefreshed() { + update() + } + + func userAssetUpdated() { + } +} diff --git a/ios/brave-ios/Sources/BraveWallet/Crypto/Stores/SendTokenStore.swift b/ios/brave-ios/Sources/BraveWallet/Crypto/Stores/SendTokenStore.swift index a8a969fda320..4a553d770f08 100644 --- a/ios/brave-ios/Sources/BraveWallet/Crypto/Stores/SendTokenStore.swift +++ b/ios/brave-ios/Sources/BraveWallet/Crypto/Stores/SendTokenStore.swift @@ -226,6 +226,7 @@ public class SendTokenStore: ObservableObject, WalletObserverStore { func setupObservers() { guard !isObserving else { return } + self.assetManager.addUserAssetDataObserver(self) self.keyringServiceObserver = KeyringServiceObserver( keyringService: keyringService, _selectedWalletAccountChanged: { [weak self] _ in @@ -924,3 +925,12 @@ public class SendTokenStore: ObservableObject, WalletObserverStore { return await rpcService.fetchNFTMetadata(tokens: tokens, ipfsApi: ipfsApi) } } + +extension SendTokenStore: WalletUserAssetDataObserver { + public func cachedBalanceRefreshed() { + update() + } + + public func userAssetUpdated() { + } +} diff --git a/ios/brave-ios/Sources/BraveWallet/Crypto/Stores/SwapTokenStore.swift b/ios/brave-ios/Sources/BraveWallet/Crypto/Stores/SwapTokenStore.swift index 60a00dd8dbe3..777ed5a5ff91 100644 --- a/ios/brave-ios/Sources/BraveWallet/Crypto/Stores/SwapTokenStore.swift +++ b/ios/brave-ios/Sources/BraveWallet/Crypto/Stores/SwapTokenStore.swift @@ -244,6 +244,7 @@ public class SwapTokenStore: ObservableObject, WalletObserverStore { func setupObservers() { guard !isObserving else { return } + self.assetManager.addUserAssetDataObserver(self) self.keyringServiceObserver = KeyringServiceObserver( keyringService: keyringService, _selectedWalletAccountChanged: { [weak self] account in @@ -1237,3 +1238,16 @@ public class SwapTokenStore: ObservableObject, WalletObserverStore { } #endif } + +extension SwapTokenStore: WalletUserAssetDataObserver { + public func cachedBalanceRefreshed() { + if let token = selectedFromToken { + fetchTokenBalance(for: token) { [weak self] balance in + self?.selectedFromTokenBalance = balance + } + } + } + + public func userAssetUpdated() { + } +} diff --git a/ios/brave-ios/Sources/BraveWallet/Crypto/Stores/TransactionConfirmationStore.swift b/ios/brave-ios/Sources/BraveWallet/Crypto/Stores/TransactionConfirmationStore.swift index f78cc44eec26..03c1f3c02af2 100644 --- a/ios/brave-ios/Sources/BraveWallet/Crypto/Stores/TransactionConfirmationStore.swift +++ b/ios/brave-ios/Sources/BraveWallet/Crypto/Stores/TransactionConfirmationStore.swift @@ -210,6 +210,7 @@ public class TransactionConfirmationStore: ObservableObject, WalletObserverStore func setupObservers() { guard !isObserving else { return } + self.assetManager.addUserAssetDataObserver(self) self.txServiceObserver = TxServiceObserver( txService: txService, _onNewUnapprovedTx: { _ in @@ -1002,3 +1003,16 @@ struct TransactionProviderError { let code: Int let message: String } + +extension TransactionConfirmationStore: WalletUserAssetDataObserver { + public func cachedBalanceRefreshed() { + updateTransaction( + with: activeTransaction, + shouldFetchCurrentAllowance: false, + shouldFetchGasTokenBalance: true + ) + } + + public func userAssetUpdated() { + } +} diff --git a/ios/brave-ios/Sources/BraveWallet/WalletUserAssetManager.swift b/ios/brave-ios/Sources/BraveWallet/WalletUserAssetManager.swift index 568dc3a4328c..6fb834016ed3 100644 --- a/ios/brave-ios/Sources/BraveWallet/WalletUserAssetManager.swift +++ b/ios/brave-ios/Sources/BraveWallet/WalletUserAssetManager.swift @@ -115,6 +115,7 @@ public class WalletUserAssetManager: WalletUserAssetManagerType, WalletObserverS keyringServiceObserver = nil txServiceObserver = nil walletServiceObserver = nil + dataObservers.removeAllObjects() } public func setupObservers() { From b42025e7edbf2290fd421d9c9bc8d8f9c731f327 Mon Sep 17 00:00:00 2001 From: Nuo Xu Date: Tue, 11 Jun 2024 23:22:52 -0400 Subject: [PATCH 3/5] address review comments --- .../Crypto/Stores/SelectAccountTokenStore.swift | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/ios/brave-ios/Sources/BraveWallet/Crypto/Stores/SelectAccountTokenStore.swift b/ios/brave-ios/Sources/BraveWallet/Crypto/Stores/SelectAccountTokenStore.swift index 1ebd9ade5be3..581cb5bde041 100644 --- a/ios/brave-ios/Sources/BraveWallet/Crypto/Stores/SelectAccountTokenStore.swift +++ b/ios/brave-ios/Sources/BraveWallet/Crypto/Stores/SelectAccountTokenStore.swift @@ -129,6 +129,7 @@ class SelectAccountTokenStore: ObservableObject, WalletObserverStore { func setupObservers() { guard !isObserving else { return } + self.assetManager.addUserAssetDataObserver(self) self.walletServiceObserver = WalletServiceObserver( walletService: walletService, _onDefaultBaseCurrencyChanged: { [weak self] currency in @@ -444,3 +445,12 @@ class SelectAccountTokenStore: ObservableObject, WalletObserverStore { return accountSections } } + +extension SelectAccountTokenStore: WalletUserAssetDataObserver { + func cachedBalanceRefreshed() { + setup() + } + + func userAssetUpdated() { + } +} From 1b31dc51367aa2d2f9e2074863f438baf13079e5 Mon Sep 17 00:00:00 2001 From: Nuo Xu Date: Thu, 13 Jun 2024 14:14:57 -0400 Subject: [PATCH 4/5] address more review comments. --- .../Crypto/Stores/AccountActivityStore.swift | 30 +++++++++++++------ .../Stores/SelectAccountTokenStore.swift | 9 +++++- .../AccountActivityStoreTests.swift | 6 ++-- 3 files changed, 32 insertions(+), 13 deletions(-) diff --git a/ios/brave-ios/Sources/BraveWallet/Crypto/Stores/AccountActivityStore.swift b/ios/brave-ios/Sources/BraveWallet/Crypto/Stores/AccountActivityStore.swift index c0601e24d533..fa956f0eda67 100644 --- a/ios/brave-ios/Sources/BraveWallet/Crypto/Stores/AccountActivityStore.swift +++ b/ios/brave-ios/Sources/BraveWallet/Crypto/Stores/AccountActivityStore.swift @@ -202,13 +202,6 @@ class AccountActivityStore: ObservableObject, WalletObserverStore { let allTokens = await blockchainRegistry.allTokens(in: networksForAccountCoin).flatMap( \.tokens ) - (self.userAssets, self.userNFTs) = buildAssetsAndNFTs( - userNetworkAssets: allUserNetworkAssets, - tokenBalances: tokenBalanceCache, - tokenPrices: tokenPricesCache, - nftMetadata: nftMetadataCache, - btcBalances: btcBalancesCache - ) let allAccountsForCoin = await keyringService.allAccounts().accounts.filter { $0.coin == account.coin } @@ -257,6 +250,25 @@ class AccountActivityStore: ObservableObject, WalletObserverStore { } } tokenBalanceCache.merge(with: tokenBalances) + // update assets, NFTs, transactions after balance fetch + guard !Task.isCancelled else { return } + (self.userAssets, self.userNFTs) = buildAssetsAndNFTs( + userNetworkAssets: allUserNetworkAssets, + tokenBalances: tokenBalanceCache, + tokenPrices: tokenPricesCache, + nftMetadata: nftMetadataCache, + btcBalances: btcBalancesCache + ) + self.transactionSections = buildTransactionSections( + transactions: transactions, + networksForCoin: [account.coin: networksForAccountCoin], + accountInfos: allAccountsForCoin, + userAssets: allUserAssets, + allTokens: allTokens, + tokenPrices: tokenPricesCache, + nftMetadata: nftMetadataCache, + solEstimatedTxFees: solEstimatedTxFeesCache + ) // fetch price for every user asset let prices: [String: String] = await assetRatioService.fetchPrices( @@ -279,8 +291,8 @@ class AccountActivityStore: ObservableObject, WalletObserverStore { self.accountTotalFiat = currencyFormatter.formatAsFiat(totalFiat) ?? "$0.00" self.isLoadingAccountFiat = false - guard !Task.isCancelled else { return } // update assets, NFTs, transactions after balance & price fetch + guard !Task.isCancelled else { return } (self.userAssets, self.userNFTs) = buildAssetsAndNFTs( userNetworkAssets: allUserNetworkAssets, tokenBalances: tokenBalanceCache, @@ -305,7 +317,7 @@ class AccountActivityStore: ObservableObject, WalletObserverStore { ipfsApi: ipfsApi ) nftMetadataCache.merge(with: allNFTMetadata) - + // update assets, NFTs, transactions after balance & price & metadata fetch guard !Task.isCancelled else { return } (self.userAssets, self.userNFTs) = buildAssetsAndNFTs( userNetworkAssets: allUserNetworkAssets, diff --git a/ios/brave-ios/Sources/BraveWallet/Crypto/Stores/SelectAccountTokenStore.swift b/ios/brave-ios/Sources/BraveWallet/Crypto/Stores/SelectAccountTokenStore.swift index 581cb5bde041..0c46f806eecc 100644 --- a/ios/brave-ios/Sources/BraveWallet/Crypto/Stores/SelectAccountTokenStore.swift +++ b/ios/brave-ios/Sources/BraveWallet/Crypto/Stores/SelectAccountTokenStore.swift @@ -448,7 +448,14 @@ class SelectAccountTokenStore: ObservableObject, WalletObserverStore { extension SelectAccountTokenStore: WalletUserAssetDataObserver { func cachedBalanceRefreshed() { - setup() + Task { @MainActor in + let allNetworks = await rpcService.allNetworksForSupportedCoins() + let allNetworkAssets = assetManager.getAllUserAssetsInNetworkAssetsByVisibility( + networks: allNetworks, + visible: true + ) + fetchAccountBalances(networkAssets: allNetworkAssets) + } } func userAssetUpdated() { diff --git a/ios/brave-ios/Tests/BraveWalletTests/AccountActivityStoreTests.swift b/ios/brave-ios/Tests/BraveWalletTests/AccountActivityStoreTests.swift index 926ed5887eab..8118d25e8e88 100644 --- a/ios/brave-ios/Tests/BraveWalletTests/AccountActivityStoreTests.swift +++ b/ios/brave-ios/Tests/BraveWalletTests/AccountActivityStoreTests.swift @@ -349,7 +349,7 @@ class AccountActivityStoreTests: XCTestCase { XCTAssertTrue(accountActivityStore.transactionSections.isEmpty) accountActivityStore.$transactionSections .dropFirst() - .collect(3) + .collect(4) .sink { transactionSectionsCollected in defer { transactionSectionsExpectation.fulfill() } guard let transactionSections = transactionSectionsCollected.last else { @@ -544,7 +544,7 @@ class AccountActivityStoreTests: XCTestCase { XCTAssertTrue(accountActivityStore.transactionSections.isEmpty) accountActivityStore.$transactionSections .dropFirst() - .collect(3) + .collect(4) .sink { transactionSectionsCollected in defer { transactionSectionsExpectation.fulfill() } guard let transactionSections = transactionSectionsCollected.last else { @@ -721,7 +721,7 @@ class AccountActivityStoreTests: XCTestCase { XCTAssertTrue(accountActivityStore.transactionSections.isEmpty) accountActivityStore.$transactionSections .dropFirst() - .collect(3) + .collect(4) .sink { transactionSectionsCollected in defer { transactionSectionsExpectation.fulfill() } guard let transactionSections = transactionSectionsCollected.last else { From e624059f347af3c68f2184df694a15ecd849470d Mon Sep 17 00:00:00 2001 From: Nuo Xu Date: Thu, 13 Jun 2024 17:08:53 -0400 Subject: [PATCH 5/5] no need to build transaction section after balance fetched since it does not need balance values. --- .../Crypto/Stores/AccountActivityStore.swift | 12 +----------- .../BraveWalletTests/AccountActivityStoreTests.swift | 6 +++--- 2 files changed, 4 insertions(+), 14 deletions(-) diff --git a/ios/brave-ios/Sources/BraveWallet/Crypto/Stores/AccountActivityStore.swift b/ios/brave-ios/Sources/BraveWallet/Crypto/Stores/AccountActivityStore.swift index fa956f0eda67..573c02c4bdd7 100644 --- a/ios/brave-ios/Sources/BraveWallet/Crypto/Stores/AccountActivityStore.swift +++ b/ios/brave-ios/Sources/BraveWallet/Crypto/Stores/AccountActivityStore.swift @@ -250,7 +250,7 @@ class AccountActivityStore: ObservableObject, WalletObserverStore { } } tokenBalanceCache.merge(with: tokenBalances) - // update assets, NFTs, transactions after balance fetch + // update assets, NFTs, after balance fetch guard !Task.isCancelled else { return } (self.userAssets, self.userNFTs) = buildAssetsAndNFTs( userNetworkAssets: allUserNetworkAssets, @@ -259,16 +259,6 @@ class AccountActivityStore: ObservableObject, WalletObserverStore { nftMetadata: nftMetadataCache, btcBalances: btcBalancesCache ) - self.transactionSections = buildTransactionSections( - transactions: transactions, - networksForCoin: [account.coin: networksForAccountCoin], - accountInfos: allAccountsForCoin, - userAssets: allUserAssets, - allTokens: allTokens, - tokenPrices: tokenPricesCache, - nftMetadata: nftMetadataCache, - solEstimatedTxFees: solEstimatedTxFeesCache - ) // fetch price for every user asset let prices: [String: String] = await assetRatioService.fetchPrices( diff --git a/ios/brave-ios/Tests/BraveWalletTests/AccountActivityStoreTests.swift b/ios/brave-ios/Tests/BraveWalletTests/AccountActivityStoreTests.swift index 8118d25e8e88..926ed5887eab 100644 --- a/ios/brave-ios/Tests/BraveWalletTests/AccountActivityStoreTests.swift +++ b/ios/brave-ios/Tests/BraveWalletTests/AccountActivityStoreTests.swift @@ -349,7 +349,7 @@ class AccountActivityStoreTests: XCTestCase { XCTAssertTrue(accountActivityStore.transactionSections.isEmpty) accountActivityStore.$transactionSections .dropFirst() - .collect(4) + .collect(3) .sink { transactionSectionsCollected in defer { transactionSectionsExpectation.fulfill() } guard let transactionSections = transactionSectionsCollected.last else { @@ -544,7 +544,7 @@ class AccountActivityStoreTests: XCTestCase { XCTAssertTrue(accountActivityStore.transactionSections.isEmpty) accountActivityStore.$transactionSections .dropFirst() - .collect(4) + .collect(3) .sink { transactionSectionsCollected in defer { transactionSectionsExpectation.fulfill() } guard let transactionSections = transactionSectionsCollected.last else { @@ -721,7 +721,7 @@ class AccountActivityStoreTests: XCTestCase { XCTAssertTrue(accountActivityStore.transactionSections.isEmpty) accountActivityStore.$transactionSections .dropFirst() - .collect(4) + .collect(3) .sink { transactionSectionsCollected in defer { transactionSectionsExpectation.fulfill() } guard let transactionSections = transactionSectionsCollected.last else {