From 04c2f87d05a881644e3706cc55507617a9df67c3 Mon Sep 17 00:00:00 2001 From: Nuo Xu Date: Tue, 16 Apr 2024 17:46:40 -0400 Subject: [PATCH 1/3] bitcoin balance fetching --- .../Crypto/FiltersDisplaySettingsView.swift | 2 +- .../Crypto/Stores/AccountActivityStore.swift | 26 +- .../Crypto/Stores/AccountsStore.swift | 43 ++- .../Crypto/Stores/AssetDetailStore.swift | 24 +- .../Crypto/Stores/CryptoStore.swift | 7 +- .../BraveWallet/Crypto/Stores/NFTStore.swift | 4 +- .../Crypto/Stores/PortfolioStore.swift | 58 ++-- .../BitcoinWalletServiceExtensions.swift | 21 ++ .../Extensions/BraveWalletExtensions.swift | 10 + .../Preview Content/MockKeyringService.swift | 2 + .../Preview Content/MockStores.swift | 3 + .../BraveWallet/WalletUserAssetManager.swift | 83 +++-- .../AccountActivityStoreTests.swift | 19 +- .../BraveWalletTests/AccountsStoreTests.swift | 41 ++- .../AssetDetailStoreTests.swift | 260 ++++++++++++++- .../PortfolioStoreTests.swift | 315 ++++++++++++------ .../WalletArrayExtensionTests.swift | 42 +-- 17 files changed, 750 insertions(+), 210 deletions(-) diff --git a/ios/brave-ios/Sources/BraveWallet/Crypto/FiltersDisplaySettingsView.swift b/ios/brave-ios/Sources/BraveWallet/Crypto/FiltersDisplaySettingsView.swift index 996ff023cba9..af6ff1a6f327 100644 --- a/ios/brave-ios/Sources/BraveWallet/Crypto/FiltersDisplaySettingsView.swift +++ b/ios/brave-ios/Sources/BraveWallet/Crypto/FiltersDisplaySettingsView.swift @@ -88,7 +88,7 @@ struct Filters { Preferences.Wallet.nonSelectedAccountsFilter.value = accounts .filter({ !$0.isSelected }) - .map(\.model.address) + .map(\.model.filterAccountKey) Preferences.Wallet.nonSelectedNetworksFilter.value = networks .filter({ !$0.isSelected }) diff --git a/ios/brave-ios/Sources/BraveWallet/Crypto/Stores/AccountActivityStore.swift b/ios/brave-ios/Sources/BraveWallet/Crypto/Stores/AccountActivityStore.swift index c5eea3d53a3a..90296ac3e259 100644 --- a/ios/brave-ios/Sources/BraveWallet/Crypto/Stores/AccountActivityStore.swift +++ b/ios/brave-ios/Sources/BraveWallet/Crypto/Stores/AccountActivityStore.swift @@ -44,6 +44,7 @@ class AccountActivityStore: ObservableObject, WalletObserverStore { private let blockchainRegistry: BraveWalletBlockchainRegistry private let solTxManagerProxy: BraveWalletSolanaTxManagerProxy private let ipfsApi: IpfsAPI + private let bitcoinWalletService: BraveWalletBitcoinWalletService private let assetManager: WalletUserAssetManagerType /// Cache for storing `BlockchainToken`s that are not in user assets or our token registry. /// This could occur with a dapp creating a transaction. @@ -75,6 +76,7 @@ class AccountActivityStore: ObservableObject, WalletObserverStore { blockchainRegistry: BraveWalletBlockchainRegistry, solTxManagerProxy: BraveWalletSolanaTxManagerProxy, ipfsApi: IpfsAPI, + bitcoinWalletService: BraveWalletBitcoinWalletService, userAssetManager: WalletUserAssetManagerType ) { self.account = account @@ -88,6 +90,7 @@ class AccountActivityStore: ObservableObject, WalletObserverStore { self.blockchainRegistry = blockchainRegistry self.solTxManagerProxy = solTxManagerProxy self.ipfsApi = ipfsApi + self.bitcoinWalletService = bitcoinWalletService self.assetManager = userAssetManager self._isSwapSupported = .init( wrappedValue: account.coin == .eth || account.coin == .sol @@ -223,10 +226,25 @@ class AccountActivityStore: ObservableObject, WalletObserverStore { ) self.isLoadingAccountFiat = true - let tokenBalances = await self.rpcService.fetchBalancesForTokens( - account: account, - networkAssets: allUserNetworkAssets - ) + var tokenBalances: [String: Double] = [:] + if account.coin == .btc { + let networkAsset = allUserNetworkAssets.first { + $0.network.supportedKeyrings.contains(account.keyringId.rawValue as NSNumber) + } + if let btc = networkAsset?.tokens.first, + let btcBalance = await self.bitcoinWalletService.fetchBTCBalance( + accountId: account.accountId + ) + { + tokenBalances = [btc.id: btcBalance] + } + } else { + tokenBalances = await self.rpcService.fetchBalancesForTokens( + account: account, + networkAssets: allUserNetworkAssets + ) + tokenBalanceCache.merge(with: tokenBalances) + } tokenBalanceCache.merge(with: tokenBalances) // fetch price for every user asset diff --git a/ios/brave-ios/Sources/BraveWallet/Crypto/Stores/AccountsStore.swift b/ios/brave-ios/Sources/BraveWallet/Crypto/Stores/AccountsStore.swift index ae6d66bfd8c6..9c5f9726b27f 100644 --- a/ios/brave-ios/Sources/BraveWallet/Crypto/Stores/AccountsStore.swift +++ b/ios/brave-ios/Sources/BraveWallet/Crypto/Stores/AccountsStore.swift @@ -33,7 +33,7 @@ class AccountsStore: ObservableObject, WalletObserverStore { let currencyFormatter: NumberFormatter = .usdCurrencyFormatter - /// Cache of token balances for each account. [account.address: [token.id: balance]] + /// Cache of token balances for each account. [account.cacheBalanceKey: [token.id: balance]] private var tokenBalancesCache: [String: [String: Double]] = [:] /// Cache of prices for each token. The key is the token's `assetRatioId`. private var pricesCache: [String: String] = [:] @@ -42,6 +42,7 @@ class AccountsStore: ObservableObject, WalletObserverStore { private let rpcService: BraveWalletJsonRpcService private let walletService: BraveWalletBraveWalletService private let assetRatioService: BraveWalletAssetRatioService + private let bitcoinWalletService: BraveWalletBitcoinWalletService private let userAssetManager: WalletUserAssetManagerType private var keyringServiceObserver: KeyringServiceObserver? @@ -56,12 +57,14 @@ class AccountsStore: ObservableObject, WalletObserverStore { rpcService: BraveWalletJsonRpcService, walletService: BraveWalletBraveWalletService, assetRatioService: BraveWalletAssetRatioService, + bitcoinWalletService: BraveWalletBitcoinWalletService, userAssetManager: WalletUserAssetManagerType ) { self.keyringService = keyringService self.rpcService = rpcService self.walletService = walletService self.assetRatioService = assetRatioService + self.bitcoinWalletService = bitcoinWalletService self.userAssetManager = userAssetManager self.setupObservers() } @@ -143,11 +146,25 @@ class AccountsStore: ObservableObject, WalletObserverStore { body: { group in for account in accounts { group.addTask { - let balancesForTokens: [String: Double] = await self.rpcService.fetchBalancesForTokens( - account: account, - networkAssets: allNetworkAssets - ) - return [account.address: balancesForTokens] + 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 + ) + { + balancesForTokens = [btc.id: btcBalance] + } + } else { + balancesForTokens = await self.rpcService.fetchBalancesForTokens( + account: account, + networkAssets: allNetworkAssets + ) + } + return [account.cacheBalanceKey: balancesForTokens] } } return await group.reduce( @@ -159,13 +176,13 @@ class AccountsStore: ObservableObject, WalletObserverStore { } ) for account in accounts { - if let updatedBalancesForAccount = balancesForAccounts[account.address] { + if let updatedBalancesForAccount = balancesForAccounts[account.cacheBalanceKey] { // if balance fetch failed that we already have cached, don't overwrite existing - if var existing = self.tokenBalancesCache[account.address] { + if var existing = self.tokenBalancesCache[account.cacheBalanceKey] { existing.merge(with: updatedBalancesForAccount) - self.tokenBalancesCache[account.address] = existing + self.tokenBalancesCache[account.cacheBalanceKey] = existing } else { - self.tokenBalancesCache[account.address] = updatedBalancesForAccount + self.tokenBalancesCache[account.cacheBalanceKey] = updatedBalancesForAccount } } } @@ -223,7 +240,7 @@ class AccountsStore: ObservableObject, WalletObserverStore { for account: BraveWallet.AccountInfo, tokens: [BraveWallet.BlockchainToken] ) -> [BraveWallet.BlockchainToken] { - guard let tokenBalancesForAccount = tokenBalancesCache[account.address] else { + guard let tokenBalancesForAccount = tokenBalancesCache[account.cacheBalanceKey] else { return [] } var tokensFiatForAccount: [(token: BraveWallet.BlockchainToken, fiat: Double)] = [] @@ -247,7 +264,7 @@ class AccountsStore: ObservableObject, WalletObserverStore { for account: BraveWallet.AccountInfo, tokens: [BraveWallet.BlockchainToken] ) -> Double { - guard let accountBalanceCache = tokenBalancesCache[account.address] else { return 0 } + guard let accountBalanceCache = tokenBalancesCache[account.cacheBalanceKey] else { return 0 } return accountBalanceCache.keys.reduce(0.0) { partialResult, tokenId in guard let tokenBalanceForAccount = tokenBalanceForAccount(tokenId: tokenId, account: account) else { @@ -271,7 +288,7 @@ class AccountsStore: ObservableObject, WalletObserverStore { tokenId: String, account: BraveWallet.AccountInfo ) -> Double? { - tokenBalancesCache[account.address]?[tokenId] + tokenBalancesCache[account.cacheBalanceKey]?[tokenId] } } diff --git a/ios/brave-ios/Sources/BraveWallet/Crypto/Stores/AssetDetailStore.swift b/ios/brave-ios/Sources/BraveWallet/Crypto/Stores/AssetDetailStore.swift index 2d8f2f693161..67b1a1cbb8bb 100644 --- a/ios/brave-ios/Sources/BraveWallet/Crypto/Stores/AssetDetailStore.swift +++ b/ios/brave-ios/Sources/BraveWallet/Crypto/Stores/AssetDetailStore.swift @@ -83,6 +83,7 @@ class AssetDetailStore: ObservableObject, WalletObserverStore { private let solTxManagerProxy: BraveWalletSolanaTxManagerProxy private let ipfsApi: IpfsAPI private let swapService: BraveWalletSwapService + private let bitcoinWalletService: BraveWalletBitcoinWalletService private let assetManager: WalletUserAssetManagerType /// A list of tokens that are supported with the current selected network for all supported /// on-ramp providers. @@ -138,6 +139,7 @@ class AssetDetailStore: ObservableObject, WalletObserverStore { solTxManagerProxy: BraveWalletSolanaTxManagerProxy, ipfsApi: IpfsAPI, swapService: BraveWalletSwapService, + bitcoinWalletService: BraveWalletBitcoinWalletService, userAssetManager: WalletUserAssetManagerType, assetDetailType: AssetDetailType ) { @@ -150,6 +152,7 @@ class AssetDetailStore: ObservableObject, WalletObserverStore { self.solTxManagerProxy = solTxManagerProxy self.ipfsApi = ipfsApi self.swapService = swapService + self.bitcoinWalletService = bitcoinWalletService self.assetManager = userAssetManager self.assetDetailType = assetDetailType @@ -456,19 +459,26 @@ class AssetDetailStore: ObservableObject, WalletObserverStore { @MainActor group -> [AccountBalance] in for accountAssetViewModel in accountAssetViewModels { group.addTask { @MainActor in - let balance = await self.rpcService.balance( - for: token, - in: accountAssetViewModel.account, - network: network - ) - return [AccountBalance(accountAssetViewModel.account, balance)] + var tokenBalance: Double? + if accountAssetViewModel.account.coin == .btc { + tokenBalance = await self.bitcoinWalletService.fetchBTCBalance( + accountId: accountAssetViewModel.account.accountId + ) + } else { + tokenBalance = await self.rpcService.balance( + for: token, + in: accountAssetViewModel.account, + network: network + ) + } + return [AccountBalance(accountAssetViewModel.account, tokenBalance)] } } return await group.reduce([AccountBalance](), { $0 + $1 }) } for tokenBalance in tokenBalances { if let index = accountAssetViewModels.firstIndex(where: { - $0.account.address == tokenBalance.account.address + $0.account.cacheBalanceKey == tokenBalance.account.cacheBalanceKey }) { accountAssetViewModels[index].decimalBalance = tokenBalance.balance ?? 0.0 accountAssetViewModels[index].balance = String(format: "%.4f", tokenBalance.balance ?? 0.0) diff --git a/ios/brave-ios/Sources/BraveWallet/Crypto/Stores/CryptoStore.swift b/ios/brave-ios/Sources/BraveWallet/Crypto/Stores/CryptoStore.swift index 6469fcb04314..c757ae6aad69 100644 --- a/ios/brave-ios/Sources/BraveWallet/Crypto/Stores/CryptoStore.swift +++ b/ios/brave-ios/Sources/BraveWallet/Crypto/Stores/CryptoStore.swift @@ -165,7 +165,8 @@ public class CryptoStore: ObservableObject, WalletObserverStore { keyringService: keyringService, rpcService: rpcService, walletService: walletService, - txService: txService + txService: txService, + bitcoinWalletService: bitcoinWalletService ) self.origin = origin @@ -184,6 +185,7 @@ public class CryptoStore: ObservableObject, WalletObserverStore { assetRatioService: assetRatioService, blockchainRegistry: blockchainRegistry, ipfsApi: ipfsApi, + bitcoinWalletService: bitcoinWalletService, userAssetManager: userAssetManager ) self.nftStore = .init( @@ -213,6 +215,7 @@ public class CryptoStore: ObservableObject, WalletObserverStore { rpcService: rpcService, walletService: walletService, assetRatioService: assetRatioService, + bitcoinWalletService: bitcoinWalletService, userAssetManager: userAssetManager ) self.marketStore = .init( @@ -504,6 +507,7 @@ public class CryptoStore: ObservableObject, WalletObserverStore { solTxManagerProxy: solTxManagerProxy, ipfsApi: ipfsApi, swapService: swapService, + bitcoinWalletService: bitcoinWalletService, userAssetManager: userAssetManager, assetDetailType: assetDetailType ) @@ -541,6 +545,7 @@ public class CryptoStore: ObservableObject, WalletObserverStore { blockchainRegistry: blockchainRegistry, solTxManagerProxy: solTxManagerProxy, ipfsApi: ipfsApi, + bitcoinWalletService: bitcoinWalletService, userAssetManager: userAssetManager ) accountActivityStore = store diff --git a/ios/brave-ios/Sources/BraveWallet/Crypto/Stores/NFTStore.swift b/ios/brave-ios/Sources/BraveWallet/Crypto/Stores/NFTStore.swift index 03c1eeef3e11..83d6c177119a 100644 --- a/ios/brave-ios/Sources/BraveWallet/Crypto/Stores/NFTStore.swift +++ b/ios/brave-ios/Sources/BraveWallet/Crypto/Stores/NFTStore.swift @@ -66,7 +66,9 @@ public class NFTStore: ObservableObject, WalletObserverStore { return Filters( accounts: allAccounts.map { account in .init( - isSelected: !nonSelectedAccountAddresses.contains(where: { $0 == account.address }), + isSelected: !nonSelectedAccountAddresses.contains(where: { + $0 == account.filterAccountKey + }), model: account ) }, diff --git a/ios/brave-ios/Sources/BraveWallet/Crypto/Stores/PortfolioStore.swift b/ios/brave-ios/Sources/BraveWallet/Crypto/Stores/PortfolioStore.swift index 6bab7848da0f..5c67cc3d027c 100644 --- a/ios/brave-ios/Sources/BraveWallet/Crypto/Stores/PortfolioStore.swift +++ b/ios/brave-ios/Sources/BraveWallet/Crypto/Stores/PortfolioStore.swift @@ -67,7 +67,7 @@ public struct AssetGroupViewModel: WalletAssetGroupViewModel, Identifiable, Equa let balance: Double switch groupType { case .account(let account): - balance = asset.balanceForAccounts[account.address] ?? 0 + balance = asset.balanceForAccounts[account.cacheBalanceKey] ?? 0 case .none, .network: balance = asset.totalBalance } @@ -109,7 +109,7 @@ public struct AssetViewModel: Identifiable, Equatable { let network: BraveWallet.NetworkInfo let price: String let history: [BraveWallet.AssetTimePrice] - /// Balance for each account for this asset. The key is the account address. + /// Balance for each account for this asset. The key is the account.cacheBalanceKey. let balanceForAccounts: [String: Double] /// The total balance for all accounts for this asset. var totalBalance: Double { @@ -125,7 +125,7 @@ public struct AssetViewModel: Identifiable, Equatable { let balance: Double switch groupType { case .account(let account): - balance = balanceForAccounts[account.address] ?? 0 + balance = balanceForAccounts[account.cacheBalanceKey] ?? 0 case .none, .network: balance = totalBalance } @@ -137,7 +137,7 @@ public struct AssetViewModel: Identifiable, Equatable { let balance: Double switch groupType { case .account(let account): - balance = balanceForAccounts[account.address] ?? 0 + balance = balanceForAccounts[account.cacheBalanceKey] ?? 0 case .none, .network: balance = totalBalance } @@ -287,7 +287,9 @@ public class PortfolioStore: ObservableObject, WalletObserverStore { return Filters( accounts: allAccounts.map { account in .init( - isSelected: !nonSelectedAccountAddresses.contains(where: { $0 == account.address }), + isSelected: !nonSelectedAccountAddresses.contains( + where: { $0 == account.filterAccountKey } + ), model: account ) }, @@ -317,7 +319,7 @@ public class PortfolioStore: ObservableObject, WalletObserverStore { /// Cancellable for the last running `update()` Task. private var updateTask: Task<(), Never>? - /// Cache of token balances for each account. [token.id: [account.address: balance]] + /// Cache of token balances for each account. [token.id: [account.cacheBalanceKey: balance]] private var tokenBalancesCache: [String: [String: Double]] = [:] /// Cache of prices for each token. The key is the token's `assetRatioId`. private var pricesCache: [String: String] = [:] @@ -330,6 +332,7 @@ public class PortfolioStore: ObservableObject, WalletObserverStore { private let assetRatioService: BraveWalletAssetRatioService private let blockchainRegistry: BraveWalletBlockchainRegistry private let ipfsApi: IpfsAPI + private let bitcoinWalletService: BraveWalletBitcoinWalletService private let assetManager: WalletUserAssetManagerType private var rpcServiceObserver: JsonRpcServiceObserver? private var keyringServiceObserver: KeyringServiceObserver? @@ -346,6 +349,7 @@ public class PortfolioStore: ObservableObject, WalletObserverStore { assetRatioService: BraveWalletAssetRatioService, blockchainRegistry: BraveWalletBlockchainRegistry, ipfsApi: IpfsAPI, + bitcoinWalletService: BraveWalletBitcoinWalletService, userAssetManager: WalletUserAssetManagerType ) { self.keyringService = keyringService @@ -354,6 +358,7 @@ public class PortfolioStore: ObservableObject, WalletObserverStore { self.assetRatioService = assetRatioService self.blockchainRegistry = blockchainRegistry self.ipfsApi = ipfsApi + self.bitcoinWalletService = bitcoinWalletService self.assetManager = userAssetManager // cache balance update observer @@ -503,24 +508,39 @@ public class PortfolioStore: ObservableObject, WalletObserverStore { // 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` + // a mock `rpcService` and `bitcoinWalletService` let fetchedTokenBalances = await withTaskGroup( of: [String: [String: Double]].self, - body: { @MainActor [tokenBalancesCache, rpcService, assetManager] group in + body: { + @MainActor + [tokenBalancesCache, rpcService, bitcoinWalletService, assetManager] + group in group.addTask { @MainActor in let token = tokenNetworkAccounts.token var tokenBalances = tokenBalancesCache[token.id] ?? [:] // fetch balance for this token for each account for account in tokenNetworkAccounts.accounts { - let balanceForToken = await rpcService.balance( - for: token, - in: account, - network: tokenNetworkAccounts.network - ) - tokenBalances.merge(with: [account.address: balanceForToken ?? 0]) + var balanceForToken: Double? + let tokenNetwork = tokenNetworkAccounts.network + if account.coin == .btc, + tokenNetwork.supportedKeyrings.contains( + account.keyringId.rawValue as NSNumber + ) + { + balanceForToken = await bitcoinWalletService.fetchBTCBalance( + accountId: account.accountId + ) + } else { + balanceForToken = await rpcService.balance( + for: token, + in: account, + network: tokenNetworkAccounts.network + ) + } + tokenBalances.merge(with: [account.cacheBalanceKey: balanceForToken ?? 0]) assetManager.updateBalance( for: token, - account: account.address, + account: account.cacheBalanceKey, balance: "\(balanceForToken ?? 0)", completion: nil ) @@ -705,7 +725,7 @@ public class PortfolioStore: ObservableObject, WalletObserverStore { .filter { key, value in // if we previously fetched balance for an account it will remain in cache. // filter out to avoid including in total balance - selectedAccounts.contains(where: { $0.address == key }) + selectedAccounts.contains(where: { $0.cacheBalanceKey == key }) } ) } @@ -741,7 +761,7 @@ public class PortfolioStore: ObservableObject, WalletObserverStore { .filter { key, value in // if we previously fetched balance for an account it will remain in cache. // filter out to avoid including in total balance - selectedAccounts.contains(where: { $0.address == key }) + selectedAccounts.contains(where: { $0.cacheBalanceKey == key }) } ) } @@ -775,7 +795,7 @@ public class PortfolioStore: ObservableObject, WalletObserverStore { balanceForAccounts: tokenBalancesCache[token.id, default: [:]] .filter { key, value in // only provide grouped account balance - key == account.address + key == account.cacheBalanceKey } ) } @@ -783,7 +803,7 @@ public class PortfolioStore: ObservableObject, WalletObserverStore { .optionallyFilter( shouldFilter: filters.isHidingSmallBalances, isIncluded: { asset in - let balanceForAccount = asset.balanceForAccounts[account.address] ?? 0 + let balanceForAccount = asset.balanceForAccounts[account.cacheBalanceKey] ?? 0 let value = (Double(asset.price) ?? 0) * balanceForAccount return value >= 0.05 } diff --git a/ios/brave-ios/Sources/BraveWallet/Extensions/BitcoinWalletServiceExtensions.swift b/ios/brave-ios/Sources/BraveWallet/Extensions/BitcoinWalletServiceExtensions.swift index ad2d0d905a23..dc6ecb3d7676 100644 --- a/ios/brave-ios/Sources/BraveWallet/Extensions/BitcoinWalletServiceExtensions.swift +++ b/ios/brave-ios/Sources/BraveWallet/Extensions/BitcoinWalletServiceExtensions.swift @@ -36,4 +36,25 @@ extension BraveWalletBitcoinWalletService { } ) } + + /// - Parameters: + /// - accountId: A list of `BraveWallet.AccountId` + /// + /// - Returns: The BTC balance of the given `BraveWallet.AccountId` in `Double`; Will return a nil if there is an issue fetching balance. + func fetchBTCBalance(accountId: BraveWallet.AccountId) async -> Double? { + guard let btcBalance = await self.balance(accountId: accountId).0 + else { return nil } + + let availableSatoshiString = String(btcBalance.availableBalance) + let formatter = WeiFormatter(decimalFormatStyle: .decimals(precision: 8)) + if let valueString = formatter.decimalString( + for: availableSatoshiString, + radix: .decimal, + decimals: 8 + ) { + return Double(valueString) + } else { + return nil + } + } } diff --git a/ios/brave-ios/Sources/BraveWallet/Extensions/BraveWalletExtensions.swift b/ios/brave-ios/Sources/BraveWallet/Extensions/BraveWalletExtensions.swift index adeba0ac84a0..c0bf1c5146ac 100644 --- a/ios/brave-ios/Sources/BraveWallet/Extensions/BraveWalletExtensions.swift +++ b/ios/brave-ios/Sources/BraveWallet/Extensions/BraveWalletExtensions.swift @@ -201,6 +201,16 @@ extension BraveWallet.AccountInfo { } return parentOrder } + + // The account unique key that we use to store for token balance in CD + var cacheBalanceKey: String { + coin == .btc ? accountId.uniqueKey : address + } + + // The account unique key that we use to store for user filter preferences + var filterAccountKey: String { + coin == .btc ? accountId.uniqueKey : address + } } extension BraveWallet.CoinType { diff --git a/ios/brave-ios/Sources/BraveWallet/Preview Content/MockKeyringService.swift b/ios/brave-ios/Sources/BraveWallet/Preview Content/MockKeyringService.swift index a5c43fe2b06c..d9792b1a8a88 100644 --- a/ios/brave-ios/Sources/BraveWallet/Preview Content/MockKeyringService.swift +++ b/ios/brave-ios/Sources/BraveWallet/Preview Content/MockKeyringService.swift @@ -531,6 +531,8 @@ extension BraveWallet.AllAccountsInfo { .mockSolAccount, .mockFilAccount, .mockFilTestnetAccount, + .mockBtcAccount, + .mockBtcTestnetAccount, ], selectedAccount: .mockEthAccount, ethDappSelectedAccount: .mockEthAccount, diff --git a/ios/brave-ios/Sources/BraveWallet/Preview Content/MockStores.swift b/ios/brave-ios/Sources/BraveWallet/Preview Content/MockStores.swift index f8761cac159a..d3fd1b48e458 100644 --- a/ios/brave-ios/Sources/BraveWallet/Preview Content/MockStores.swift +++ b/ios/brave-ios/Sources/BraveWallet/Preview Content/MockStores.swift @@ -153,6 +153,7 @@ extension AssetDetailStore { solTxManagerProxy: BraveWallet.TestSolanaTxManagerProxy.previewProxy, ipfsApi: TestIpfsAPI(), swapService: MockSwapService(), + bitcoinWalletService: BraveWallet.TestBitcoinWalletService.previewBitcoinWalletService, userAssetManager: TestableWalletUserAssetManager(), assetDetailType: .blockchainToken(.previewToken) ) @@ -204,6 +205,7 @@ extension AccountActivityStore { blockchainRegistry: MockBlockchainRegistry(), solTxManagerProxy: BraveWallet.TestSolanaTxManagerProxy.previewProxy, ipfsApi: TestIpfsAPI(), + bitcoinWalletService: BraveWallet.TestBitcoinWalletService.previewBitcoinWalletService, userAssetManager: TestableWalletUserAssetManager() ) } @@ -270,6 +272,7 @@ extension AccountsStore { rpcService: MockJsonRpcService(), walletService: MockBraveWalletService(), assetRatioService: MockAssetRatioService(), + bitcoinWalletService: BraveWallet.TestBitcoinWalletService.previewBitcoinWalletService, userAssetManager: TestableWalletUserAssetManager() ) } diff --git a/ios/brave-ios/Sources/BraveWallet/WalletUserAssetManager.swift b/ios/brave-ios/Sources/BraveWallet/WalletUserAssetManager.swift index 8851444e4b29..5319e1fe6e07 100644 --- a/ios/brave-ios/Sources/BraveWallet/WalletUserAssetManager.swift +++ b/ios/brave-ios/Sources/BraveWallet/WalletUserAssetManager.swift @@ -81,6 +81,7 @@ public class WalletUserAssetManager: WalletUserAssetManagerType, WalletObserverS private let rpcService: BraveWalletJsonRpcService private let walletService: BraveWalletBraveWalletService private let txService: BraveWalletTxService + private let bitcoinWalletService: BraveWalletBitcoinWalletService private var keyringServiceObserver: KeyringServiceObserver? private var txServiceObserver: TxServiceObserver? @@ -95,12 +96,14 @@ public class WalletUserAssetManager: WalletUserAssetManagerType, WalletObserverS keyringService: BraveWalletKeyringService, rpcService: BraveWalletJsonRpcService, walletService: BraveWalletBraveWalletService, - txService: BraveWalletTxService + txService: BraveWalletTxService, + bitcoinWalletService: BraveWalletBitcoinWalletService ) { self.keyringService = keyringService self.rpcService = rpcService self.walletService = walletService self.txService = txService + self.bitcoinWalletService = bitcoinWalletService setupObservers() @@ -386,30 +389,47 @@ public class WalletUserAssetManager: WalletUserAssetManagerType, WalletObserverS ) await withTaskGroup( of: Void.self, - body: { @MainActor [rpcService] group in + body: { @MainActor [rpcService, bitcoinWalletService] group in for account in accounts { guard !Task.isCancelled else { return } group.addTask { @MainActor in - let allTokenBalanceForAccount = await rpcService.fetchBalancesForTokens( - account: account, - networkAssets: allUserAssets - ) - for tokenBalanceForAccount in allTokenBalanceForAccount { - guard !Task.isCancelled else { return } - let networkAssets = allUserAssets.first { oneNetworkAssets in - oneNetworkAssets.tokens.contains { asset in - asset.id == tokenBalanceForAccount.key - } + if account.coin == .btc { + let networkAssets = allUserAssets.first { + $0.network.supportedKeyrings.contains(account.keyringId.rawValue as NSNumber) } - if let token = networkAssets?.tokens.first(where: { - $0.id == tokenBalanceForAccount.key - }) { + if let btc = networkAssets?.tokens.first, + let btcBalance = await bitcoinWalletService.fetchBTCBalance( + accountId: account.accountId + ) + { WalletUserAssetBalance.updateBalance( - for: token, - balance: String(tokenBalanceForAccount.value), - account: account.address + for: btc, + balance: String(btcBalance), + account: account.cacheBalanceKey ) } + } else { + let allTokenBalanceForAccount = await rpcService.fetchBalancesForTokens( + account: account, + networkAssets: allUserAssets + ) + for tokenBalanceForAccount in allTokenBalanceForAccount { + guard !Task.isCancelled else { return } + let networkAssets = allUserAssets.first { oneNetworkAssets in + oneNetworkAssets.tokens.contains { asset in + asset.id == tokenBalanceForAccount.key + } + } + if let token = networkAssets?.tokens.first(where: { + $0.id == tokenBalanceForAccount.key + }) { + WalletUserAssetBalance.updateBalance( + for: token, + balance: String(tokenBalanceForAccount.value), + account: account.cacheBalanceKey + ) + } + } } } } @@ -454,26 +474,33 @@ public class WalletUserAssetManager: WalletUserAssetManagerType, WalletObserverS completion: (() -> Void)? = nil ) { Task { @MainActor in - let accounts = await keyringService.allAccounts().accounts.filter { $0.coin == asset.coin } let network = await rpcService.allNetworksForSupportedCoins().first { $0.chainId.lowercased() == asset.chainId.lowercased() } - guard let network = network else { return } + let accounts = await keyringService.allAccounts().accounts.filter { $0.coin == asset.coin } + await withTaskGroup( of: Void.self, - body: { @MainActor [rpcService] group in + body: { @MainActor [rpcService, bitcoinWalletService] group in for account in accounts { // for each account group.addTask { @MainActor in - let assetBalance = await rpcService.balance( - for: asset, - in: account, - network: network - ) + var tokenBalance: Double? + if account.coin == .btc { + tokenBalance = await bitcoinWalletService.fetchBTCBalance( + accountId: account.accountId + ) + } else { + tokenBalance = await rpcService.balance( + for: asset, + in: account, + network: network + ) + } WalletUserAssetBalance.updateBalance( for: asset, - balance: String(assetBalance ?? 0), - account: account.address + balance: String(tokenBalance ?? 0), + account: account.cacheBalanceKey ) } } diff --git a/ios/brave-ios/Tests/BraveWalletTests/AccountActivityStoreTests.swift b/ios/brave-ios/Tests/BraveWalletTests/AccountActivityStoreTests.swift index b3792dd7d24b..6e8b548823f7 100644 --- a/ios/brave-ios/Tests/BraveWalletTests/AccountActivityStoreTests.swift +++ b/ios/brave-ios/Tests/BraveWalletTests/AccountActivityStoreTests.swift @@ -57,7 +57,8 @@ class AccountActivityStoreTests: XCTestCase { BraveWallet.TestBraveWalletService, BraveWallet.TestBlockchainRegistry, BraveWallet.TestAssetRatioService, BraveWallet.TestSwapService, BraveWallet.TestTxService, BraveWallet.TestSolanaTxManagerProxy, - IpfsAPI + IpfsAPI, + BraveWallet.TestBitcoinWalletService ) { let keyringService = BraveWallet.TestKeyringService() keyringService._addObserver = { _ in } @@ -165,9 +166,14 @@ class AccountActivityStoreTests: XCTestCase { let ipfsApi = TestIpfsAPI() + let bitcoinWalletService = BraveWallet.TestBitcoinWalletService() + bitcoinWalletService._balance = { + $1(.init(totalBalance: 1000, availableBalance: 1000, pendingBalance: 0, balances: [:]), nil) + } + return ( keyringService, rpcService, walletService, blockchainRegistry, assetRatioService, - swapService, txService, solTxManagerProxy, ipfsApi + swapService, txService, solTxManagerProxy, ipfsApi, bitcoinWalletService ) } @@ -208,7 +214,7 @@ class AccountActivityStoreTests: XCTestCase { let ( keyringService, rpcService, walletService, blockchainRegistry, assetRatioService, - swapService, txService, solTxManagerProxy, ipfsApi + swapService, txService, solTxManagerProxy, ipfsApi, bitcoinWalletService ) = setupServices( mockEthBalanceWei: mockEthBalanceWei, mockERC20BalanceWei: mockERC20BalanceWei, @@ -254,6 +260,7 @@ class AccountActivityStoreTests: XCTestCase { blockchainRegistry: blockchainRegistry, solTxManagerProxy: solTxManagerProxy, ipfsApi: ipfsApi, + bitcoinWalletService: bitcoinWalletService, userAssetManager: mockAssetManager ) @@ -397,7 +404,7 @@ class AccountActivityStoreTests: XCTestCase { let ( keyringService, rpcService, walletService, blockchainRegistry, assetRatioService, - swapService, txService, solTxManagerProxy, ipfsApi + swapService, txService, solTxManagerProxy, ipfsApi, bitcoinWalletService ) = setupServices( mockLamportBalance: mockLamportBalance, mockSplTokenBalances: mockSplTokenBalances, @@ -442,6 +449,7 @@ class AccountActivityStoreTests: XCTestCase { blockchainRegistry: blockchainRegistry, solTxManagerProxy: solTxManagerProxy, ipfsApi: ipfsApi, + bitcoinWalletService: bitcoinWalletService, userAssetManager: mockAssetManager ) @@ -600,7 +608,7 @@ class AccountActivityStoreTests: XCTestCase { let ( keyringService, rpcService, walletService, blockchainRegistry, assetRatioService, - swapService, txService, solTxManagerProxy, ipfsApi + swapService, txService, solTxManagerProxy, ipfsApi, bitcoinWalletService ) = setupServices( mockFilBalance: mockFilDecimalBalanceInWei, mockFilTestnetBalance: mockFilTestnetDecimalBalanceInWei, @@ -643,6 +651,7 @@ class AccountActivityStoreTests: XCTestCase { blockchainRegistry: blockchainRegistry, solTxManagerProxy: solTxManagerProxy, ipfsApi: ipfsApi, + bitcoinWalletService: bitcoinWalletService, userAssetManager: mockAssetManager ) diff --git a/ios/brave-ios/Tests/BraveWalletTests/AccountsStoreTests.swift b/ios/brave-ios/Tests/BraveWalletTests/AccountsStoreTests.swift index 5ca69406b4e9..b2f19c7ecfc1 100644 --- a/ios/brave-ios/Tests/BraveWalletTests/AccountsStoreTests.swift +++ b/ios/brave-ios/Tests/BraveWalletTests/AccountsStoreTests.swift @@ -82,8 +82,6 @@ import XCTest BraveWallet.NetworkInfo.mockFilecoinTestnet.nativeToken ] - // TODO: Update balance test once bitcoin balance fetching is integrated - // https://github.com/brave/brave-browser/issues/36966 let mockBTCBalanceAccount1: Double = 0 let mockBTCTestnetBalanceAccount1: Double = 0 let mockBTCPrice: String = "65726.00" @@ -180,8 +178,7 @@ import XCTest ].filter { $0.coin == coin } ) } - // TODO: Update balance test once bitcoin balance fetching is integrated - // https://github.com/brave/brave-browser/issues/36966 + rpcService._balance = { accountAddress, coin, chainId, completion in if coin == .eth, chainId == BraveWallet.MainnetChainId, @@ -284,11 +281,39 @@ import XCTest } } + let btcMainnetBalance: UInt64 = 1000 + let btcTestnetBalance: UInt64 = 10000 + let bitcoinWalletService = BraveWallet.TestBitcoinWalletService() + bitcoinWalletService._balance = { accountId, completion in + if accountId.uniqueKey == self.btcAccount1.accountId.uniqueKey { + completion( + .init( + totalBalance: btcMainnetBalance, + availableBalance: btcMainnetBalance, + pendingBalance: 0, + balances: [:] + ), + nil + ) + } else { + completion( + .init( + totalBalance: btcTestnetBalance, + availableBalance: btcTestnetBalance, + pendingBalance: 0, + balances: [:] + ), + nil + ) + } + } + let store = AccountsStore( keyringService: keyringService, rpcService: rpcService, walletService: walletService, assetRatioService: assetRatioService, + bitcoinWalletService: bitcoinWalletService, userAssetManager: userAssetManager ) @@ -330,13 +355,13 @@ import XCTest XCTAssertEqual(accountDetails[safe: 4]?.totalBalanceFiat, "$4.00") XCTAssertEqual(accountDetails[safe: 5]?.account, self.btcAccount1) - XCTAssertEqual(accountDetails[safe: 5]?.tokensWithBalance, []) // BTC 0 value - XCTAssertEqual(accountDetails[safe: 5]?.totalBalanceFiat, "$0.00") + XCTAssertEqual(accountDetails[safe: 5]?.tokensWithBalance, self.btcMainnetTokens) + XCTAssertEqual(accountDetails[safe: 5]?.totalBalanceFiat, "$0.66") if bitcoinTestnetEnabled { XCTAssertEqual(accountDetails[safe: 6]?.account, self.btcTestnetAccount) - XCTAssertEqual(accountDetails[safe: 6]?.tokensWithBalance, []) // BTC 0 value - XCTAssertEqual(accountDetails[safe: 6]?.totalBalanceFiat, "$0.00") + XCTAssertEqual(accountDetails[safe: 6]?.tokensWithBalance, self.btcTestnetTokens) + XCTAssertEqual(accountDetails[safe: 6]?.totalBalanceFiat, "$6.57") } }.store(in: &cancellables) diff --git a/ios/brave-ios/Tests/BraveWalletTests/AssetDetailStoreTests.swift b/ios/brave-ios/Tests/BraveWalletTests/AssetDetailStoreTests.swift index 3ec4d85abab3..68c0a9c1a687 100644 --- a/ios/brave-ios/Tests/BraveWalletTests/AssetDetailStoreTests.swift +++ b/ios/brave-ios/Tests/BraveWalletTests/AssetDetailStoreTests.swift @@ -12,7 +12,6 @@ import XCTest class AssetDetailStoreTests: XCTestCase { private var cancellables: Set = .init() - // TODO: Add a test case for fetching BTC balance in AssetDetails once issue https://github.com/brave/brave-browser/issues/36966 is integrated func testUpdateWithBlockchainToken() { let currencyFormatter = NumberFormatter().then { $0.numberStyle = .currency } let formatter = WeiFormatter(decimalFormatStyle: .decimals(precision: 18)) @@ -95,6 +94,26 @@ class AssetDetailStoreTests: XCTestCase { $1(true) } + let mockBtcBalance: Double = 0.001 + let btcBalanceSatoshi = + formatter.weiString( + from: mockBtcBalance, + radix: .decimal, + decimals: Int(BraveWallet.BlockchainToken.mockBTCToken.decimals) + ) ?? "" + let bitcoinWalletService = BraveWallet.TestBitcoinWalletService() + bitcoinWalletService._balance = { + $1( + .init( + totalBalance: UInt64(btcBalanceSatoshi) ?? 0, + availableBalance: UInt64(btcBalanceSatoshi) ?? 0, + pendingBalance: 0, + balances: [:] + ), + nil + ) + } + // setup store let store = AssetDetailStore( assetRatioService: assetRatioService, @@ -106,6 +125,7 @@ class AssetDetailStoreTests: XCTestCase { solTxManagerProxy: solTxManagerProxy, ipfsApi: TestIpfsAPI(), swapService: swapService, + bitcoinWalletService: bitcoinWalletService, userAssetManager: mockAssetManager, assetDetailType: .blockchainToken(.previewToken) ) @@ -234,6 +254,222 @@ class AssetDetailStoreTests: XCTestCase { wait(for: [assetDetailException], timeout: 1) } + func testUpdateWithBlockchainTokenBitcoin() { + let currencyFormatter = NumberFormatter().then { $0.numberStyle = .currency } + let formatter = WeiFormatter(decimalFormatStyle: .decimals(precision: 8)) + + let assetRatioService = BraveWallet.TestAssetRatioService() + let mockBtcPrice: Double = 63503 + assetRatioService._price = { _, _, _, completion in + completion( + true, + [ + .init( + fromAsset: BraveWallet.BlockchainToken.mockBTCToken.tokenId, + toAsset: "usd", + price: "\(mockBtcPrice)", + assetTimeframeChange: "-1.72" + ) + ] + ) + } + assetRatioService._priceHistory = { _, _, _, completion in + completion(true, [.init(date: Date(), price: "62391.63")]) + } + + let keyringService = BraveWallet.TestKeyringService() + keyringService._allAccounts = { completion in + completion(.mock) + } + keyringService._addObserver = { _ in } + + let rpcService = BraveWallet.TestJsonRpcService() + rpcService._allNetworks = { + $1([.mockBitcoinMainnet]) + } + rpcService._network = { + $2(.mockBitcoinMainnet) + } + + let walletService = BraveWallet.TestBraveWalletService() + walletService._defaultBaseCurrency = { + $0("usd") + } + walletService._addObserver = { _ in } + + let mockAssetManager = TestableWalletUserAssetManager() + mockAssetManager._getAllUserAssetsInNetworkAssets = { _, _ in + [NetworkAssets(network: .mockBitcoinMainnet, tokens: [.mockBTCToken], sortOrder: 0)] + } + + let txService = BraveWallet.TestTxService() + txService._allTransactionInfo = { + $3([]) + } + txService._addObserver = { _ in } + + let blockchainRegistry = BraveWallet.TestBlockchainRegistry() + blockchainRegistry._buyTokens = { + $2([.mockBTCToken]) + } + blockchainRegistry._allTokens = { + $2([.mockBTCToken]) + } + + let solTxManagerProxy = BraveWallet.TestSolanaTxManagerProxy() + + let swapService = BraveWallet.TestSwapService() + swapService._isSwapSupported = { + $1(true) + } + + let mockBtcBalance: Double = 0.0001 + let btcBalanceSatoshi = + formatter.weiString( + from: mockBtcBalance, + radix: .decimal, + decimals: Int(BraveWallet.BlockchainToken.mockBTCToken.decimals) + ) ?? "" + let formattedBthBalance = + currencyFormatter.string( + from: NSNumber(value: mockBtcBalance * mockBtcPrice) + ) ?? "" + let bitcoinWalletService = BraveWallet.TestBitcoinWalletService() + bitcoinWalletService._balance = { + $1( + .init( + totalBalance: UInt64(btcBalanceSatoshi) ?? 0, + availableBalance: UInt64(btcBalanceSatoshi) ?? 0, + pendingBalance: 0, + balances: [:] + ), + nil + ) + } + + // setup store + let store = AssetDetailStore( + assetRatioService: assetRatioService, + keyringService: keyringService, + rpcService: rpcService, + walletService: walletService, + txService: txService, + blockchainRegistry: blockchainRegistry, + solTxManagerProxy: solTxManagerProxy, + ipfsApi: TestIpfsAPI(), + swapService: swapService, + bitcoinWalletService: bitcoinWalletService, + userAssetManager: mockAssetManager, + assetDetailType: .blockchainToken(.mockBTCToken) + ) + + let assetDetailException = expectation(description: "update-blockchainToken") + assetDetailException.expectedFulfillmentCount = 12 + store.$network + .dropFirst() + .sink { network in + defer { assetDetailException.fulfill() } + XCTAssertEqual(network, .mockBitcoinMainnet) + } + .store(in: &cancellables) + store.$isBuySupported + .dropFirst() + .sink { + defer { assetDetailException.fulfill() } + XCTAssertTrue($0) + } + .store(in: &cancellables) + store.$isSendSupported + .dropFirst() + .sink { + defer { assetDetailException.fulfill() } + XCTAssertTrue($0) + } + .store(in: &cancellables) + store.$isSwapSupported + .dropFirst() + .sink { + defer { assetDetailException.fulfill() } + XCTAssertTrue($0) + } + .store(in: &cancellables) + store.$priceHistory + .dropFirst() + .sink { priceHistory in + defer { assetDetailException.fulfill() } + XCTAssertEqual(priceHistory.count, 1) + XCTAssertEqual(priceHistory[0].price, "62391.63") + } + .store(in: &cancellables) + store.$price + .dropFirst() + .sink { + defer { assetDetailException.fulfill() } + XCTAssertEqual($0, "$63,503.00") + } + .store(in: &cancellables) + store.$priceIsDown + .dropFirst() + .sink { + defer { assetDetailException.fulfill() } + XCTAssertTrue($0) + } + .store(in: &cancellables) + store.$priceDelta + .dropFirst() + .sink { + defer { assetDetailException.fulfill() } + XCTAssertEqual($0, "-1.72%") + } + .store(in: &cancellables) + store.$nonZeroBalanceAccounts + .dropFirst() + .sink { accounts in + defer { assetDetailException.fulfill() } + XCTAssertEqual(accounts.count, 1) + XCTAssertEqual(accounts[0].account, .mockBtcAccount) + XCTAssertEqual(accounts[0].balance, String(format: "%.4f", mockBtcBalance)) + XCTAssertEqual(accounts[0].fiatBalance, formattedBthBalance) + } + .store(in: &cancellables) + store.$isLoadingPrice + .dropFirst() + .collect(2) + .sink { values in + defer { assetDetailException.fulfill() } + guard let value = values.last + else { + XCTFail("Unexpected isLoadingPrice") + return + } + XCTAssertFalse(value) + } + .store(in: &cancellables) + store.$isInitialState + .dropFirst() + .sink { + defer { assetDetailException.fulfill() } + XCTAssertFalse($0) + } + .store(in: &cancellables) + store.$isLoadingChart + .dropFirst() + .collect(2) + .sink { values in + defer { assetDetailException.fulfill() } + guard let value = values.last + else { + XCTFail("Unexpected isLoadingChart") + return + } + XCTAssertFalse(value) + } + .store(in: &cancellables) + + store.update() + wait(for: [assetDetailException], timeout: 1) + } + func testUpdateWithCoinMarket() { let formatter = WeiFormatter(decimalFormatStyle: .decimals(precision: 18)) @@ -304,6 +540,26 @@ class AssetDetailStoreTests: XCTestCase { let solTxManagerProxy = BraveWallet.TestSolanaTxManagerProxy() let swapService = BraveWallet.TestSwapService() + let mockBtcBalance: Double = 0.001 + let btcBalanceSatoshi = + formatter.weiString( + from: mockBtcBalance, + radix: .decimal, + decimals: Int(BraveWallet.BlockchainToken.mockBTCToken.decimals) + ) ?? "" + let bitcoinWalletService = BraveWallet.TestBitcoinWalletService() + bitcoinWalletService._balance = { + $1( + .init( + totalBalance: UInt64(btcBalanceSatoshi) ?? 0, + availableBalance: UInt64(btcBalanceSatoshi) ?? 0, + pendingBalance: 0, + balances: [:] + ), + nil + ) + } + // setup store var store = AssetDetailStore( assetRatioService: assetRatioService, @@ -315,6 +571,7 @@ class AssetDetailStoreTests: XCTestCase { solTxManagerProxy: solTxManagerProxy, ipfsApi: TestIpfsAPI(), swapService: swapService, + bitcoinWalletService: bitcoinWalletService, userAssetManager: mockAssetManager, assetDetailType: .coinMarket(.mockCoinMarketBitcoin) ) @@ -426,6 +683,7 @@ class AssetDetailStoreTests: XCTestCase { solTxManagerProxy: solTxManagerProxy, ipfsApi: TestIpfsAPI(), swapService: swapService, + bitcoinWalletService: bitcoinWalletService, userAssetManager: mockAssetManager, assetDetailType: .coinMarket(.mockCoinMarketEth) ) diff --git a/ios/brave-ios/Tests/BraveWalletTests/PortfolioStoreTests.swift b/ios/brave-ios/Tests/BraveWalletTests/PortfolioStoreTests.swift index 79a194a4f20f..839f891757fd 100644 --- a/ios/brave-ios/Tests/BraveWalletTests/PortfolioStoreTests.swift +++ b/ios/brave-ios/Tests/BraveWalletTests/PortfolioStoreTests.swift @@ -128,9 +128,7 @@ import XCTest // FIL Asset, balance on filecoin testnet let mockFILBalanceTestnet: Double = 100 // FIL value on testnet = $400.00 - // TODO: Update balance test once bitcoin balance fetching is integrated - // https://github.com/brave/brave-browser/issues/36966 - let mockBTCBalanceAccount1: Double = 0 + let mockBTCBalanceAccount1: Double = 0.0000001 let mockBTCPrice: String = "65726.00" lazy var mockBTCAssetPrice: BraveWallet.AssetPrice = .init( fromAsset: "btc", @@ -142,7 +140,7 @@ import XCTest .init(date: Date(timeIntervalSinceNow: -1000), price: "65326.00.06"), .init(date: Date(), price: mockBTCPrice), ] - let mockBTCBalanceTestnet: Double = 0 + let mockBTCBalanceTestnet: Double = 0.00001 var totalBalance: String { let totalEthBalanceValue: Double = @@ -153,10 +151,11 @@ import XCTest let totalSolBalanceValue: Double = (Double(mockSOLAssetPrice.price) ?? 0) * mockSOLBalance let totalFilBalanceValue: Double = (Double(mockFILAssetPrice.price) ?? 0) * mockFILBalanceAccount1 - // TODO: Add bitcoin balance once bitcoin balance fetching is integrated - // https://github.com/brave/brave-browser/issues/36966 + let totalBtcBalanceValue: Double = + (Double(mockBTCAssetPrice.price) ?? 0) * mockBTCBalanceAccount1 let totalBalanceValue = - totalEthBalanceValue + totalSolBalanceValue + totalUSDCBalanceValue + totalFilBalanceValue + totalEthBalanceValue + totalSolBalanceValue + totalUSDCBalanceValue + + totalFilBalanceValue + totalBtcBalanceValue return currencyFormatter.string(from: NSNumber(value: totalBalanceValue)) ?? "" } @@ -224,7 +223,7 @@ import XCTest let mockBtcUserAssets: [BraveWallet.BlockchainToken] = [ btcMainnet.nativeToken.copy(asVisibleAsset: true) ] - let mockBtcBalanceInWei = + let mockBtcBalanceInSantoshi = formatter.weiString( from: mockBTCBalanceAccount1, radix: .decimal, @@ -234,7 +233,7 @@ import XCTest let mockBtcTestnetUserAssets: [BraveWallet.BlockchainToken] = [ btcTestnet.nativeToken.copy(asVisibleAsset: true) ] - let mockBtcTestnetBalanceInWei = + let mockBtcTestnetBalanceInSantoshi = formatter.weiString( from: mockFILBalanceTestnet, radix: .decimal, @@ -397,6 +396,54 @@ import XCTest ), ].filter { networkAsset in networks.contains(where: { $0 == networkAsset.network }) } } + + let btcBalanceInSatoshi = + formatter.weiString( + from: mockBTCBalanceAccount1, + radix: .decimal, + decimals: Int(BraveWallet.BlockchainToken.mockBTCToken.decimals) + ) ?? "" + let btcTestnetBalanceInSatoshi = + formatter.weiString( + from: mockBTCBalanceTestnet, + radix: .decimal, + decimals: Int(BraveWallet.BlockchainToken.mockBTCToken.decimals) + ) ?? "" + let bitcoinWalletService = BraveWallet.TestBitcoinWalletService() + bitcoinWalletService._balance = { accountId, completion in + if accountId.uniqueKey == self.btcAccount1.accountId.uniqueKey { + completion( + .init( + totalBalance: UInt64(btcBalanceInSatoshi) ?? 0, + availableBalance: UInt64(btcBalanceInSatoshi) ?? 0, + pendingBalance: 0, + balances: [:] + ), + nil + ) + } else if accountId.uniqueKey == self.btcAccount2.accountId.uniqueKey { + completion( + .init( + totalBalance: 0, + availableBalance: 0, + pendingBalance: 0, + balances: [:] + ), + nil + ) + } else { + completion( + .init( + totalBalance: UInt64(btcTestnetBalanceInSatoshi) ?? 0, + availableBalance: UInt64(btcTestnetBalanceInSatoshi) ?? 0, + pendingBalance: 0, + balances: [:] + ), + nil + ) + } + } + return PortfolioStore( keyringService: keyringService, rpcService: rpcService, @@ -404,6 +451,7 @@ import XCTest assetRatioService: assetRatioService, blockchainRegistry: BraveWallet.TestBlockchainRegistry(), ipfsApi: TestIpfsAPI(), + bitcoinWalletService: bitcoinWalletService, userAssetManager: mockAssetManager ) } @@ -506,8 +554,7 @@ import XCTest group.assets[safe: 3]?.quantity, String(format: "%.04f", self.mockUSDCBalanceAccount1 + self.mockUSDCBalanceAccount2) ) - - // BTC (value $0.00) + // BTC (value $0.0065726) on mainnet XCTAssertEqual( group.assets[safe: 4]?.token.symbol, BraveWallet.BlockchainToken.mockBTCToken.symbol @@ -518,8 +565,8 @@ import XCTest ) XCTAssertEqual( group.assets[safe: 4]?.history, - [] - ) // 0 balance no fetching history + self.mockBTCPriceHistory + ) XCTAssertEqual( group.assets[safe: 4]?.quantity, String(format: "%.04f", self.mockBTCBalanceAccount1) @@ -552,7 +599,7 @@ import XCTest XCTAssertEqual( balanceDifference, .init( - priceDifference: "+$53.69", + priceDifference: "+$53.70", //"+$53.69", percentageChange: "+1.55%", isBalanceUp: true ) @@ -605,7 +652,7 @@ import XCTest isHidingUnownedNFTs: store.filters.isHidingUnownedNFTs, isShowingNFTNetworkLogo: store.filters.isShowingNFTNetworkLogo, accounts: store.filters.accounts, - networks: [ethNetwork, goerliNetwork, solNetwork, filMainnet, filTestnet].map { + networks: [ethNetwork, goerliNetwork, solNetwork, filMainnet, filTestnet, btcMainnet].map { // de-select all networks with balance .init(isSelected: $0.chainId == goerliNetwork.chainId, model: $0) } @@ -641,39 +688,38 @@ import XCTest // USDC on Ethereum mainnet, SOL on Solana mainnet, ETH on Ethereum mainnet, FIL on Filecoin mainnet, FIL on Filecoin testnet, BTC on Bitcoin mainnet. No BTC on Bitcoin testnet since Bitcoin tesnet is disabled by default let assetGroupNumber = bitcoinTestnetEnabled ? 8 : 7 XCTAssertEqual(group.assets.count, assetGroupNumber) - // BTC mainnet (value = $0) + // ETH Goerli (value = $0) XCTAssertEqual( group.assets[safe: 0]?.token.symbol, - BraveWallet.BlockchainToken.mockBTCToken.symbol + BraveWallet.BlockchainToken.previewToken.symbol ) XCTAssertEqual(group.assets[safe: 0]?.quantity, String(format: "%.04f", 0)) - var offset = 0 - if bitcoinTestnetEnabled { - offset += 1 - // BTC testnet (value = $0) - XCTAssertEqual( - group.assets[safe: 1]?.token.symbol, - BraveWallet.BlockchainToken.mockBTCToken.symbol - ) - XCTAssertEqual(group.assets[safe: 1]?.quantity, String(format: "%.04f", 0)) - } - // ETH Goerli (value = $0) - offset += 1 + // BTC mainnet (value = $0.0065726) XCTAssertEqual( - group.assets[safe: offset]?.token.symbol, - BraveWallet.BlockchainToken.previewToken.symbol + group.assets[safe: 1]?.token.symbol, + BraveWallet.BlockchainToken.mockBTCToken.symbol ) - XCTAssertEqual(group.assets[safe: offset]?.quantity, String(format: "%.04f", 0)) + XCTAssertEqual(group.assets[safe: 1]?.quantity, String(format: "%.04f", 0)) // USDC (value = $0.04) - offset += 1 XCTAssertEqual( - group.assets[safe: offset]?.token.symbol, + group.assets[safe: 2]?.token.symbol, BraveWallet.BlockchainToken.mockUSDCToken.symbol ) XCTAssertEqual( - group.assets[safe: offset]?.quantity, + group.assets[safe: 2]?.quantity, String(format: "%.04f", self.mockUSDCBalanceAccount1 + self.mockUSDCBalanceAccount2) ) + var offset = 2 + if bitcoinTestnetEnabled { + offset += 1 + // BTC testnet (value = $0.65726) + XCTAssertEqual( + group.assets[safe: offset]?.token.symbol, + BraveWallet.BlockchainToken.mockBTCToken.symbol + ) + XCTAssertEqual(group.assets[safe: offset]?.quantity, String(format: "%.04f", 0)) + } + // FIL (value = $4.00) on filecoin mainnet offset += 1 XCTAssertEqual( @@ -809,7 +855,7 @@ import XCTest String(format: "%.04f", self.mockFILBalanceAccount1) ) // USDC (value = $0.04), hidden - // BTC (value = 0), hidden + // BTC (value = $0.006), hidden XCTAssertNil(group.assets[safe: 4]) }.store(in: &cancellables) isLocked = false @@ -843,7 +889,7 @@ import XCTest // test without bitcoin testnet enabled Preferences.Wallet.isBitcoinTestnetEnabled.value = bitcoinTestnetEnabled let store = setupStore() - let accountsExpectation = expectation(description: "update-accounts") + let accountsExpectation = expectation(description: "update-accounts-bitcoin-testnet") store.$assetGroups .dropFirst() .collect(2) @@ -898,35 +944,45 @@ import XCTest group.assets[safe: 3]?.quantity, String(format: "%.04f", self.mockFILBalanceAccount1) ) + var offset: Int = 4 + if bitcoinTestnetEnabled { + // BTC testnet (value = $0.65726) + XCTAssertEqual( + group.assets[safe: offset]?.token.symbol, + self.btcMainnet.nativeToken.symbol + ) + XCTAssertEqual( + group.assets[safe: offset]?.quantity, + String(format: "%.04f", self.mockBTCBalanceTestnet) + ) + offset += 1 + } // USDC (value = $0.03, ethAccount2 hidden!) XCTAssertEqual( - group.assets[safe: 4]?.token.symbol, + group.assets[safe: offset]?.token.symbol, BraveWallet.BlockchainToken.mockUSDCToken.symbol ) XCTAssertEqual( - group.assets[safe: 4]?.quantity, + group.assets[safe: offset]?.quantity, String(format: "%.04f", self.mockUSDCBalanceAccount1) ) // verify account 2 hidden - // BTC mainnet (value = $0) + offset += 1 + // BTC mainnet (value = $0.0006) XCTAssertEqual( - group.assets[safe: 5]?.token.symbol, + group.assets[safe: offset]?.token.symbol, self.btcMainnet.nativeToken.symbol ) - var goerliOffset: Int = 6 - if bitcoinTestnetEnabled { - // BTC testnet (value = $0) - XCTAssertEqual( - group.assets[safe: 6]?.token.symbol, - self.btcMainnet.nativeToken.symbol - ) - goerliOffset = 7 - } + XCTAssertEqual( + group.assets[safe: offset]?.quantity, + String(format: "%.04f", self.mockBTCBalanceAccount1) + ) + offset += 1 // ETH Goerli (value = $0) XCTAssertEqual( - group.assets[safe: goerliOffset]?.token.symbol, + group.assets[safe: offset]?.token.symbol, self.goerliNetwork.nativeToken.symbol ) - XCTAssertEqual(group.assets[safe: goerliOffset]?.quantity, String(format: "%.04f", 0)) + XCTAssertEqual(group.assets[safe: offset]?.quantity, String(format: "%.04f", 0)) }.store(in: &cancellables) isLocked = false store.saveFilters( @@ -1083,23 +1139,36 @@ import XCTest let assetGroupNumber = bitcoinTestnetEnabled ? 10 : 9 XCTAssertEqual(lastUpdatedAssetGroups.count, assetGroupNumber) - var filAccount2Offset = bitcoinTestnetEnabled ? 9 : 8 + var btcTestnetIndex = 0 + var ethAccount2Index = 4 + var btcAccount1Index = 5 + var solAccount2Index = 6 + var btcAccount2Index = 7 + var filAccount2Index = 8 + if bitcoinTestnetEnabled { + btcTestnetIndex = 4 + ethAccount2Index = 5 + btcAccount1Index = 6 + solAccount2Index = 7 + btcAccount2Index = 8 + filAccount2Index = 9 + } guard let ethAccount1Group = lastUpdatedAssetGroups[safe: 0], let solAccount1Group = lastUpdatedAssetGroups[safe: 1], let filTestnetAccountGroup = lastUpdatedAssetGroups[safe: 2], let filAccount1Group = lastUpdatedAssetGroups[safe: 3], - let ethAccount2Group = lastUpdatedAssetGroups[safe: 4], - let solAccount2Group = lastUpdatedAssetGroups[safe: 5], - let btcAccount1Group = lastUpdatedAssetGroups[safe: 6], - let btcAccount2Group = lastUpdatedAssetGroups[safe: 7], - let filAccount2Group = lastUpdatedAssetGroups[safe: filAccount2Offset] + let ethAccount2Group = lastUpdatedAssetGroups[safe: ethAccount2Index], + let btcAccount1Group = lastUpdatedAssetGroups[safe: btcAccount1Index], + let solAccount2Group = lastUpdatedAssetGroups[safe: solAccount2Index], + let btcAccount2Group = lastUpdatedAssetGroups[safe: btcAccount2Index], + let filAccount2Group = lastUpdatedAssetGroups[safe: filAccount2Index] else { XCTFail("Unexpected test result") return } if bitcoinTestnetEnabled { - guard let btcTestnetAccountGroup = lastUpdatedAssetGroups[safe: 8] + guard let btcTestnetAccountGroup = lastUpdatedAssetGroups[safe: btcTestnetIndex] else { XCTFail("Unexpected test result") return @@ -1205,6 +1274,15 @@ import XCTest ) XCTAssertEqual(ethAccount2Group.assets[safe: 2]?.quantity, String(format: "%.04f", 0)) + XCTAssertEqual(btcAccount1Group.assets[safe: 0]?.quantity, String(format: "%.04f", 0)) + XCTAssertEqual(btcAccount1Group.groupType, .account(self.btcAccount1)) + XCTAssertEqual(btcAccount1Group.assets.count, 1) + // BTC (value = $0.0065726) + XCTAssertEqual( + btcAccount1Group.assets[safe: 0]?.token.symbol, + BraveWallet.BlockchainToken.mockBTCToken.symbol + ) + XCTAssertEqual(solAccount2Group.groupType, .account(self.solAccount2)) XCTAssertEqual(solAccount2Group.assets.count, 1) // SOL (value = $0) @@ -1214,15 +1292,6 @@ import XCTest ) XCTAssertEqual(solAccount2Group.assets[safe: 0]?.quantity, String(format: "%.04f", 0)) - XCTAssertEqual(btcAccount1Group.assets[safe: 0]?.quantity, String(format: "%.04f", 0)) - XCTAssertEqual(btcAccount1Group.groupType, .account(self.btcAccount1)) - XCTAssertEqual(btcAccount1Group.assets.count, 1) - // BTC (value = $0) - XCTAssertEqual( - btcAccount1Group.assets[safe: 0]?.token.symbol, - BraveWallet.BlockchainToken.mockBTCToken.symbol - ) - XCTAssertEqual(btcAccount2Group.assets[safe: 0]?.quantity, String(format: "%.04f", 0)) XCTAssertEqual(btcAccount2Group.groupType, .account(self.btcAccount2)) XCTAssertEqual(btcAccount2Group.assets.count, 1) @@ -1283,8 +1352,10 @@ import XCTest XCTFail("Unexpected test result") return } + // grouping by .account; 1 for each of the 2 accounts selected accounts - XCTAssertEqual(lastUpdatedAssetGroups.count, 4) + let groupNumber = bitcoinTestnetEnabled ? 5 : 4 + XCTAssertEqual(lastUpdatedAssetGroups.count, groupNumber) guard let ethAccount1Group = lastUpdatedAssetGroups[safe: 0], let solAccountGroup = lastUpdatedAssetGroups[safe: 1], let filTestnetAccountGroup = lastUpdatedAssetGroups[safe: 2], @@ -1344,13 +1415,29 @@ import XCTest String(format: "%.04f", self.mockFILBalanceAccount1) ) - // ethAccount2 hidden as it's de-selected, solAccount2 hidden for small balance, filAccount2 hidden for small balance, btcAccount1/btcAccount2/btcTestAccount hidden for small balance - XCTAssertNil(lastUpdatedAssetGroups[safe: 4]) - XCTAssertNil(lastUpdatedAssetGroups[safe: 5]) - XCTAssertNil(lastUpdatedAssetGroups[safe: 6]) - XCTAssertNil(lastUpdatedAssetGroups[safe: 7]) - XCTAssertNil(lastUpdatedAssetGroups[safe: 8]) - XCTAssertNil(lastUpdatedAssetGroups[safe: 9]) + if bitcoinTestnetEnabled { + guard let btcTestnetAccountGroup = lastUpdatedAssetGroups[safe: 4] + else { + XCTFail("Unexpected test result") + return + } + + XCTAssertEqual(btcTestnetAccountGroup.groupType, .account(self.btcTestnetAccount)) + XCTAssertEqual(btcTestnetAccountGroup.assets.count, 1) + // BTC on testnet (value = $0.65726) + XCTAssertEqual( + btcTestnetAccountGroup.assets[safe: 0]?.token.symbol, + BraveWallet.BlockchainToken.mockBTCToken.symbol + ) + XCTAssertEqual( + btcTestnetAccountGroup.assets[safe: 0]?.quantity, + String(format: "%.04f", self.mockBTCBalanceTestnet) + ) + } + + // ethAccount2 hidden as it's de-selected, solAccount2 hidden for small balance, filAccount2 hidden for small balance, btcAccount1/btcAccount2 hidden for small balance + let lastIndex = bitcoinTestnetEnabled ? 5 : 4 + XCTAssertNil(lastUpdatedAssetGroups[safe: lastIndex]) } .store(in: &cancellables) store.saveFilters( @@ -1402,23 +1489,19 @@ import XCTest XCTFail("Unexpected test result") return } + // grouping by .network; 1 for each of the 2 networks (Bitcoin testnet can be disabled) // network groups order should be the same as the order of all networks in `Filters` let assetGroupNumber = bitcoinTestnetEnabled ? 7 : 6 XCTAssertEqual(lastUpdatedAssetGroups.count, assetGroupNumber) - guard let ethMainnetGroup = lastUpdatedAssetGroups[safe: 0], - let solMainnetGroup = lastUpdatedAssetGroups[safe: 1], - let filTestnetGroup = lastUpdatedAssetGroups[safe: 2], - let filMainnetGroup = lastUpdatedAssetGroups[safe: 3], - let btcMainnetGroup = lastUpdatedAssetGroups[safe: 4], - let ethGoerliGroup = lastUpdatedAssetGroups[safe: 5] - else { - XCTFail("Unexpected test result") - return - } + var btcMainnetIndex = 4 + var goerliIndex = 5 if bitcoinTestnetEnabled { - guard let btcTestnetGroup = lastUpdatedAssetGroups[safe: 6] + btcMainnetIndex = 5 + goerliIndex = 6 + + guard let btcTestnetGroup = lastUpdatedAssetGroups[safe: 4] else { XCTFail("Unexpected test result") return @@ -1433,6 +1516,17 @@ import XCTest XCTAssertEqual(btcTestnetGroup.assets[safe: 0]?.quantity, String(format: "%.04f", 0)) } + guard let ethMainnetGroup = lastUpdatedAssetGroups[safe: 0], + let solMainnetGroup = lastUpdatedAssetGroups[safe: 1], + let filTestnetGroup = lastUpdatedAssetGroups[safe: 2], + let filMainnetGroup = lastUpdatedAssetGroups[safe: 3], + let btcMainnetGroup = lastUpdatedAssetGroups[safe: btcMainnetIndex], + let ethGoerliGroup = lastUpdatedAssetGroups[safe: goerliIndex] + else { + XCTFail("Unexpected test result") + return + } + XCTAssertEqual(ethMainnetGroup.groupType, .network(.mockMainnet)) XCTAssertEqual(ethMainnetGroup.assets.count, 2) // ETH Mainnet, USDC // ETH (value ~= $2741.7510399999996) @@ -1490,6 +1584,15 @@ import XCTest String(format: "%.04f", self.mockFILBalanceAccount1) ) + XCTAssertEqual(btcMainnetGroup.groupType, .network(self.btcMainnet)) + XCTAssertEqual(btcMainnetGroup.assets.count, 1) + // BTC mainnet (value = $0.0065726) + XCTAssertEqual( + btcMainnetGroup.assets[safe: 0]?.token.symbol, + BraveWallet.BlockchainToken.mockBTCToken.symbol + ) + XCTAssertEqual(btcMainnetGroup.assets[safe: 0]?.quantity, String(format: "%.04f", 0)) + XCTAssertEqual(ethGoerliGroup.groupType, .network(.mockGoerli)) XCTAssertEqual(ethGoerliGroup.assets.count, 1) // ETH Goerli // ETH Goerli (value = $0) @@ -1499,15 +1602,6 @@ import XCTest ) XCTAssertEqual(ethGoerliGroup.assets[safe: 0]?.quantity, String(format: "%.04f", 0)) - XCTAssertEqual(btcMainnetGroup.groupType, .network(self.btcMainnet)) - XCTAssertEqual(btcMainnetGroup.assets.count, 1) - // BTC mainnet (value = $0) - XCTAssertEqual( - btcMainnetGroup.assets[safe: 0]?.token.symbol, - BraveWallet.BlockchainToken.mockBTCToken.symbol - ) - XCTAssertEqual(btcMainnetGroup.assets[safe: 0]?.quantity, String(format: "%.04f", 0)) - // Verify NFTs not used in Portfolio #7945 let noAssetsAreNFTs = lastUpdatedAssetGroups.flatMap(\.assets).allSatisfy({ !($0.token.isNft || $0.token.isErc721) @@ -1551,7 +1645,8 @@ import XCTest return } // grouping by .network; 1 group for Solana network, 1 group for Filecoin mainnet, 1 group for Filecoin testnet - XCTAssertEqual(lastUpdatedAssetGroups.count, 3) + let groupNumber = bitcoinTestnetEnabled ? 4 : 3 + XCTAssertEqual(lastUpdatedAssetGroups.count, groupNumber) guard let solMainnetGroup = lastUpdatedAssetGroups[safe: 0], let filTestnetGroup = lastUpdatedAssetGroups[safe: 1], let filMainnetGroup = lastUpdatedAssetGroups[safe: 2] @@ -1559,6 +1654,26 @@ import XCTest XCTFail("Unexpected test result") return } + if bitcoinTestnetEnabled { + guard let btcTestnetGroup = lastUpdatedAssetGroups[safe: 3] + else { + XCTFail("Unexpected test result") + return + } + + XCTAssertEqual(btcTestnetGroup.groupType, .network(.mockBitcoinTestnet)) + XCTAssertEqual(btcTestnetGroup.assets.count, 1) // BTC + // BTC (value = $0.65726) + XCTAssertEqual( + btcTestnetGroup.assets[safe: 0]?.token.symbol, + BraveWallet.BlockchainToken.mockBTCToken.symbol + ) + XCTAssertEqual( + btcTestnetGroup.assets[safe: 0]?.quantity, + String(format: "%.04f", self.mockBTCBalanceTestnet) + ) + } + XCTAssertEqual(solMainnetGroup.groupType, .network(.mockSolana)) XCTAssertEqual(solMainnetGroup.assets.count, 1) // SOL // SOL (value = $775.3) @@ -1595,12 +1710,10 @@ import XCTest String(format: "%.04f", self.mockFILBalanceAccount1) ) // eth mainnet group hidden as network de-selected - XCTAssertNil(lastUpdatedAssetGroups[safe: 3]) // goerli network group hidden for small balance - XCTAssertNil(lastUpdatedAssetGroups[safe: 4]) // Bitcoin network group hidden for small balance - XCTAssertNil(lastUpdatedAssetGroups[safe: 5]) - XCTAssertNil(lastUpdatedAssetGroups[safe: 6]) + let lastIndex = bitcoinTestnetEnabled ? 4 : 3 + XCTAssertNil(lastUpdatedAssetGroups[safe: lastIndex]) } .store(in: &cancellables) store.saveFilters( diff --git a/ios/brave-ios/Tests/BraveWalletTests/WalletArrayExtensionTests.swift b/ios/brave-ios/Tests/BraveWalletTests/WalletArrayExtensionTests.swift index df55bdd256bd..2937fb75b443 100644 --- a/ios/brave-ios/Tests/BraveWalletTests/WalletArrayExtensionTests.swift +++ b/ios/brave-ios/Tests/BraveWalletTests/WalletArrayExtensionTests.swift @@ -56,67 +56,67 @@ class WalletArrayExtensionTests: XCTestCase { let testAccount2: BraveWallet.AccountInfo = .mockBtcTestnetAccount // ETH value $1000 - var viewModel1: AssetViewModel = .init( + let viewModel1: AssetViewModel = .init( groupType: .none, token: .previewToken, network: .mockMainnet, price: "1000", history: [], - balanceForAccounts: [account1.address: 1] + balanceForAccounts: [account1.cacheBalanceKey: 1] ) // USDC value $500 - var viewModel2: AssetViewModel = .init( + let viewModel2: AssetViewModel = .init( groupType: .none, token: .mockUSDCToken, network: .mockMainnet, price: "500", history: [], - balanceForAccounts: [account1.address: 1] + balanceForAccounts: [account1.cacheBalanceKey: 1] ) // SOL value $500 - var viewModel3: AssetViewModel = .init( + let viewModel3: AssetViewModel = .init( groupType: .none, token: .mockSolToken, network: .mockSolana, price: "500", history: [], - balanceForAccounts: [account2.address: 1] + balanceForAccounts: [account2.cacheBalanceKey: 1] ) // FIL value $100 on mainnet - var viewModel4: AssetViewModel = .init( + let viewModel4: AssetViewModel = .init( groupType: .none, token: .mockFilToken, network: .mockFilecoinMainnet, price: "100", history: [], - balanceForAccounts: [account3.address: 1] + balanceForAccounts: [account3.cacheBalanceKey: 1] ) // BTC value $20000 on mainnet - var viewModel5: AssetViewModel = .init( + let viewModel5: AssetViewModel = .init( groupType: .none, token: .mockBTCToken, network: .mockBitcoinMainnet, price: "20000", history: [], - balanceForAccounts: [account4.address: 1] + balanceForAccounts: [account4.cacheBalanceKey: 1] ) // FIL value $100 on testnet - var viewModel6: AssetViewModel = .init( + let viewModel6: AssetViewModel = .init( groupType: .none, token: .mockFilToken, network: .mockFilecoinTestnet, price: "50", history: [], - balanceForAccounts: [testAccount1.address: 2] + balanceForAccounts: [testAccount1.cacheBalanceKey: 2] ) // BTC value $40000 on testnet - var viewModel7: AssetViewModel = .init( + let viewModel7: AssetViewModel = .init( groupType: .none, token: .mockBTCToken, network: .mockBitcoinTestnet, price: "20000", history: [], - balanceForAccounts: [testAccount2.address: 2] + balanceForAccounts: [testAccount2.cacheBalanceKey: 2] ) let array: [AssetViewModel] = [ viewModel1, viewModel2, @@ -153,7 +153,7 @@ class WalletArrayExtensionTests: XCTestCase { network: .mockMainnet, price: "1000", history: [], - balanceForAccounts: [account1.address: 1] + balanceForAccounts: [account1.cacheBalanceKey: 1] ) // USDC value $500 var viewModel2: AssetViewModel = .init( @@ -162,7 +162,7 @@ class WalletArrayExtensionTests: XCTestCase { network: .mockMainnet, price: "500", history: [], - balanceForAccounts: [account1.address: 1] + balanceForAccounts: [account1.cacheBalanceKey: 1] ) // SOL value $100 var viewModel3: AssetViewModel = .init( @@ -171,7 +171,7 @@ class WalletArrayExtensionTests: XCTestCase { network: .mockSolana, price: "100", history: [], - balanceForAccounts: [account2.address: 1] + balanceForAccounts: [account2.cacheBalanceKey: 1] ) // FIL value $100 on mainnet var viewModel4: AssetViewModel = .init( @@ -180,7 +180,7 @@ class WalletArrayExtensionTests: XCTestCase { network: .mockFilecoinMainnet, price: "100", history: [], - balanceForAccounts: [account3.address: 1] + balanceForAccounts: [account3.cacheBalanceKey: 1] ) // BTC value $20000 on mainnet var viewModel5: AssetViewModel = .init( @@ -189,7 +189,7 @@ class WalletArrayExtensionTests: XCTestCase { network: .mockBitcoinMainnet, price: "20000", history: [], - balanceForAccounts: [account4.address: 1] + balanceForAccounts: [account4.cacheBalanceKey: 1] ) // FIL value $100 on testnet var viewModel6: AssetViewModel = .init( @@ -198,7 +198,7 @@ class WalletArrayExtensionTests: XCTestCase { network: .mockFilecoinTestnet, price: "50", history: [], - balanceForAccounts: [testAccount1.address: 2] + balanceForAccounts: [testAccount1.cacheBalanceKey: 2] ) // BTC value $100 on testnet var viewModel7: AssetViewModel = .init( @@ -207,7 +207,7 @@ class WalletArrayExtensionTests: XCTestCase { network: .mockBitcoinTestnet, price: "20000", history: [], - balanceForAccounts: [testAccount2.address: 0.005] + balanceForAccounts: [testAccount2.cacheBalanceKey: 0.005] ) var group1: AssetGroupViewModel = .init( groupType: .account(account1), From 69fcc8d878dac1ad563f7ab2b0b96eab0630914e Mon Sep 17 00:00:00 2001 From: Nuo Xu Date: Fri, 19 Apr 2024 16:04:17 -0400 Subject: [PATCH 2/3] migrate some caching value due to empty address for bitcoin accounts. address some review comments. --- .../Crypto/FiltersDisplaySettingsView.swift | 2 +- .../Crypto/Stores/AccountActivityStore.swift | 9 ++-- .../Crypto/Stores/AccountsStore.swift | 28 +++++----- .../Crypto/Stores/AssetDetailStore.swift | 7 ++- .../Crypto/Stores/CryptoStore.swift | 2 +- .../Crypto/Stores/NFTDetailStore.swift | 6 +-- .../BraveWallet/Crypto/Stores/NFTStore.swift | 16 +++--- .../Crypto/Stores/PortfolioStore.swift | 30 ++++++----- .../Stores/SelectAccountTokenStore.swift | 23 +++++---- .../BitcoinWalletServiceExtensions.swift | 22 ++++++-- .../Extensions/BraveWalletExtensions.swift | 10 ---- .../BraveWallet/WalletPreferences.swift | 6 +++ .../BraveWallet/WalletUserAssetManager.swift | 51 ++++++++++++++++--- .../Data/models/WalletUserAssetBalance.swift | 20 ++++++++ .../AccountActivityStoreTests.swift | 4 +- .../BraveWalletTests/AccountsStoreTests.swift | 1 + .../AssetDetailStoreTests.swift | 36 ------------- .../BraveWalletTests/NFTStoreTests.swift | 1 + .../PortfolioStoreTests.swift | 3 ++ .../TransactionParserTests.swift | 5 ++ .../WalletArrayExtensionTests.swift | 42 +++++++-------- 21 files changed, 190 insertions(+), 134 deletions(-) diff --git a/ios/brave-ios/Sources/BraveWallet/Crypto/FiltersDisplaySettingsView.swift b/ios/brave-ios/Sources/BraveWallet/Crypto/FiltersDisplaySettingsView.swift index af6ff1a6f327..f1146ece8a8d 100644 --- a/ios/brave-ios/Sources/BraveWallet/Crypto/FiltersDisplaySettingsView.swift +++ b/ios/brave-ios/Sources/BraveWallet/Crypto/FiltersDisplaySettingsView.swift @@ -88,7 +88,7 @@ struct Filters { Preferences.Wallet.nonSelectedAccountsFilter.value = accounts .filter({ !$0.isSelected }) - .map(\.model.filterAccountKey) + .map(\.model.id) Preferences.Wallet.nonSelectedNetworksFilter.value = networks .filter({ !$0.isSelected }) diff --git a/ios/brave-ios/Sources/BraveWallet/Crypto/Stores/AccountActivityStore.swift b/ios/brave-ios/Sources/BraveWallet/Crypto/Stores/AccountActivityStore.swift index 90296ac3e259..01fbe6287aa0 100644 --- a/ios/brave-ios/Sources/BraveWallet/Crypto/Stores/AccountActivityStore.swift +++ b/ios/brave-ios/Sources/BraveWallet/Crypto/Stores/AccountActivityStore.swift @@ -226,6 +226,8 @@ 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 { @@ -233,7 +235,8 @@ class AccountActivityStore: ObservableObject, WalletObserverStore { } if let btc = networkAsset?.tokens.first, let btcBalance = await self.bitcoinWalletService.fetchBTCBalance( - accountId: account.accountId + accountId: account.accountId, + type: .total ) { tokenBalances = [btc.id: btcBalance] @@ -381,7 +384,7 @@ class AccountActivityStore: ObservableObject, WalletObserverStore { groupType: .none, token: token, network: networkAssets.network, - balanceForAccounts: [account.address: Int(tokenBalances[token.id] ?? 0)], + balanceForAccounts: [account.id: Int(tokenBalances[token.id] ?? 0)], nftMetadata: nftMetadata[token.id] ) ) @@ -393,7 +396,7 @@ class AccountActivityStore: ObservableObject, WalletObserverStore { network: networkAssets.network, price: tokenPrices[token.assetRatioId.lowercased()] ?? "", history: [], - balanceForAccounts: [account.address: tokenBalances[token.id] ?? 0] + balanceForAccounts: [account.id: tokenBalances[token.id] ?? 0] ) ) } diff --git a/ios/brave-ios/Sources/BraveWallet/Crypto/Stores/AccountsStore.swift b/ios/brave-ios/Sources/BraveWallet/Crypto/Stores/AccountsStore.swift index 9c5f9726b27f..90eba1c81bce 100644 --- a/ios/brave-ios/Sources/BraveWallet/Crypto/Stores/AccountsStore.swift +++ b/ios/brave-ios/Sources/BraveWallet/Crypto/Stores/AccountsStore.swift @@ -33,8 +33,9 @@ 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]] - private var tokenBalancesCache: [String: [String: Double]] = [:] + private var tokenBalancesCache: TokenBalanceCache = [:] /// Cache of prices for each token. The key is the token's `assetRatioId`. private var pricesCache: [String: String] = [:] @@ -142,10 +143,12 @@ class AccountsStore: ObservableObject, WalletObserverStore { networkAssets allNetworkAssets: [NetworkAssets] ) async { let balancesForAccounts = await withTaskGroup( - of: [String: [String: Double]].self, + 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 { @@ -153,7 +156,8 @@ class AccountsStore: ObservableObject, WalletObserverStore { } if let btc = networkAssets?.tokens.first, let btcBalance = await self.bitcoinWalletService.fetchBTCBalance( - accountId: account.accountId + accountId: account.accountId, + type: .total ) { balancesForTokens = [btc.id: btcBalance] @@ -164,11 +168,11 @@ class AccountsStore: ObservableObject, WalletObserverStore { networkAssets: allNetworkAssets ) } - return [account.cacheBalanceKey: balancesForTokens] + return [account.id: balancesForTokens] } } return await group.reduce( - into: [String: [String: Double]](), + into: TokenBalanceCache(), { partialResult, new in partialResult.merge(with: new) } @@ -176,13 +180,13 @@ class AccountsStore: ObservableObject, WalletObserverStore { } ) for account in accounts { - if let updatedBalancesForAccount = balancesForAccounts[account.cacheBalanceKey] { + if let updatedBalancesForAccount = balancesForAccounts[account.id] { // if balance fetch failed that we already have cached, don't overwrite existing - if var existing = self.tokenBalancesCache[account.cacheBalanceKey] { + if var existing = self.tokenBalancesCache[account.id] { existing.merge(with: updatedBalancesForAccount) - self.tokenBalancesCache[account.cacheBalanceKey] = existing + self.tokenBalancesCache[account.id] = existing } else { - self.tokenBalancesCache[account.cacheBalanceKey] = updatedBalancesForAccount + self.tokenBalancesCache[account.id] = updatedBalancesForAccount } } } @@ -240,7 +244,7 @@ class AccountsStore: ObservableObject, WalletObserverStore { for account: BraveWallet.AccountInfo, tokens: [BraveWallet.BlockchainToken] ) -> [BraveWallet.BlockchainToken] { - guard let tokenBalancesForAccount = tokenBalancesCache[account.cacheBalanceKey] else { + guard let tokenBalancesForAccount = tokenBalancesCache[account.id] else { return [] } var tokensFiatForAccount: [(token: BraveWallet.BlockchainToken, fiat: Double)] = [] @@ -264,7 +268,7 @@ class AccountsStore: ObservableObject, WalletObserverStore { for account: BraveWallet.AccountInfo, tokens: [BraveWallet.BlockchainToken] ) -> Double { - guard let accountBalanceCache = tokenBalancesCache[account.cacheBalanceKey] else { return 0 } + guard let accountBalanceCache = tokenBalancesCache[account.id] else { return 0 } return accountBalanceCache.keys.reduce(0.0) { partialResult, tokenId in guard let tokenBalanceForAccount = tokenBalanceForAccount(tokenId: tokenId, account: account) else { @@ -288,7 +292,7 @@ class AccountsStore: ObservableObject, WalletObserverStore { tokenId: String, account: BraveWallet.AccountInfo ) -> Double? { - tokenBalancesCache[account.cacheBalanceKey]?[tokenId] + tokenBalancesCache[account.id]?[tokenId] } } diff --git a/ios/brave-ios/Sources/BraveWallet/Crypto/Stores/AssetDetailStore.swift b/ios/brave-ios/Sources/BraveWallet/Crypto/Stores/AssetDetailStore.swift index 67b1a1cbb8bb..1c495acbad04 100644 --- a/ios/brave-ios/Sources/BraveWallet/Crypto/Stores/AssetDetailStore.swift +++ b/ios/brave-ios/Sources/BraveWallet/Crypto/Stores/AssetDetailStore.swift @@ -459,10 +459,13 @@ 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( - accountId: accountAssetViewModel.account.accountId + accountId: accountAssetViewModel.account.accountId, + type: .total ) } else { tokenBalance = await self.rpcService.balance( @@ -478,7 +481,7 @@ class AssetDetailStore: ObservableObject, WalletObserverStore { } for tokenBalance in tokenBalances { if let index = accountAssetViewModels.firstIndex(where: { - $0.account.cacheBalanceKey == tokenBalance.account.cacheBalanceKey + $0.account.id == tokenBalance.account.id }) { accountAssetViewModels[index].decimalBalance = tokenBalance.balance ?? 0.0 accountAssetViewModels[index].balance = String(format: "%.4f", tokenBalance.balance ?? 0.0) diff --git a/ios/brave-ios/Sources/BraveWallet/Crypto/Stores/CryptoStore.swift b/ios/brave-ios/Sources/BraveWallet/Crypto/Stores/CryptoStore.swift index c757ae6aad69..f726e87404a2 100644 --- a/ios/brave-ios/Sources/BraveWallet/Crypto/Stores/CryptoStore.swift +++ b/ios/brave-ios/Sources/BraveWallet/Crypto/Stores/CryptoStore.swift @@ -881,7 +881,7 @@ public class CryptoStore: ObservableObject, WalletObserverStore { for account in accounts { if let balancesForAccount = userAssetManager.getBalances( for: nil, - account: account.address + account: account.id ) { let balancesScopedForP3A = balancesForAccount.optionallyFilter( shouldFilter: !shouldCountTestNetworks, diff --git a/ios/brave-ios/Sources/BraveWallet/Crypto/Stores/NFTDetailStore.swift b/ios/brave-ios/Sources/BraveWallet/Crypto/Stores/NFTDetailStore.swift index e21edf917665..8379b111cb31 100644 --- a/ios/brave-ios/Sources/BraveWallet/Crypto/Stores/NFTDetailStore.swift +++ b/ios/brave-ios/Sources/BraveWallet/Crypto/Stores/NFTDetailStore.swift @@ -190,7 +190,7 @@ class NFTDetailStore: ObservableObject, WalletObserverStore { in: account, network: network ) - return [account.address: Int(balanceForToken ?? 0)] + return [account.id: Int(balanceForToken ?? 0)] } } return await group.reduce( @@ -203,11 +203,11 @@ class NFTDetailStore: ObservableObject, WalletObserverStore { ) } ) - if let address = nftBalances.first(where: { address, balance in + if let uniqueKey = nftBalances.first(where: { _, balance in balance > 0 })?.key, let account = accounts.first(where: { accountInfo in - accountInfo.address.caseInsensitiveCompare(address) == .orderedSame + accountInfo.id.caseInsensitiveCompare(uniqueKey) == .orderedSame }) { owner = account diff --git a/ios/brave-ios/Sources/BraveWallet/Crypto/Stores/NFTStore.swift b/ios/brave-ios/Sources/BraveWallet/Crypto/Stores/NFTStore.swift index 83d6c177119a..63f82506877f 100644 --- a/ios/brave-ios/Sources/BraveWallet/Crypto/Stores/NFTStore.swift +++ b/ios/brave-ios/Sources/BraveWallet/Crypto/Stores/NFTStore.swift @@ -67,7 +67,7 @@ public class NFTStore: ObservableObject, WalletObserverStore { accounts: allAccounts.map { account in .init( isSelected: !nonSelectedAccountAddresses.contains(where: { - $0 == account.filterAccountKey + $0 == account.id }), model: account ) @@ -337,9 +337,9 @@ public class NFTStore: ObservableObject, WalletObserverStore { body: { @MainActor group in for account in allAccounts where account.coin == nft.coin { if !forceUpdateNFTBalances, - let cachedBalance = nftBalancesCache[nft.id]?[account.address] + let cachedBalance = nftBalancesCache[nft.id]?[account.id] { // cached balance - return [account.address: cachedBalance] + return [account.id: cachedBalance] } else { // no balance for this account group.addTask { @MainActor in let balanceForToken = await rpcService.balance( @@ -347,7 +347,7 @@ public class NFTStore: ObservableObject, WalletObserverStore { in: account, network: networkForNFT ) - return [account.address: Int(balanceForToken ?? 0)] + return [account.id: Int(balanceForToken ?? 0)] } } } @@ -480,7 +480,7 @@ public class NFTStore: ObservableObject, WalletObserverStore { .flatMap { networkNFTs in networkNFTs.tokens.compactMap { token in // we need to exclude any NFT that THIS account does not own (balance is not 1) - guard let balance = nftBalancesCache[token.id]?[account.address], balance > 0 else { + guard let balance = nftBalancesCache[token.id]?[account.id], balance > 0 else { return nil } return NFTAssetViewModel( @@ -743,11 +743,11 @@ public class NFTStore: ObservableObject, WalletObserverStore { func owner(for nft: BraveWallet.BlockchainToken) -> BraveWallet.AccountInfo? { guard let allBalances = nftBalancesCache[nft.id], - let address = allBalances.first(where: { address, balance in + let uniqueKey = allBalances.first(where: { _, balance in balance > 0 })?.key else { return nil } - return allAccounts.first { $0.address.caseInsensitiveCompare(address) == .orderedSame } + return allAccounts.first { $0.id.caseInsensitiveCompare(uniqueKey) == .orderedSame } } } @@ -785,7 +785,7 @@ extension Array where Element == NFTAssetViewModel { isIncluded: { nftAsset in let balancesForSelectedAccounts = nftAsset.balanceForAccounts.filter { balance in selectedAccounts.contains(where: { account in - account.address == balance.key + account.id == balance.key }) } return balancesForSelectedAccounts.contains(where: { $0.value > 0 }) diff --git a/ios/brave-ios/Sources/BraveWallet/Crypto/Stores/PortfolioStore.swift b/ios/brave-ios/Sources/BraveWallet/Crypto/Stores/PortfolioStore.swift index 5c67cc3d027c..4ebff3dae92d 100644 --- a/ios/brave-ios/Sources/BraveWallet/Crypto/Stores/PortfolioStore.swift +++ b/ios/brave-ios/Sources/BraveWallet/Crypto/Stores/PortfolioStore.swift @@ -67,7 +67,7 @@ public struct AssetGroupViewModel: WalletAssetGroupViewModel, Identifiable, Equa let balance: Double switch groupType { case .account(let account): - balance = asset.balanceForAccounts[account.cacheBalanceKey] ?? 0 + balance = asset.balanceForAccounts[account.id] ?? 0 case .none, .network: balance = asset.totalBalance } @@ -125,7 +125,7 @@ public struct AssetViewModel: Identifiable, Equatable { let balance: Double switch groupType { case .account(let account): - balance = balanceForAccounts[account.cacheBalanceKey] ?? 0 + balance = balanceForAccounts[account.id] ?? 0 case .none, .network: balance = totalBalance } @@ -137,7 +137,7 @@ public struct AssetViewModel: Identifiable, Equatable { let balance: Double switch groupType { case .account(let account): - balance = balanceForAccounts[account.cacheBalanceKey] ?? 0 + balance = balanceForAccounts[account.id] ?? 0 case .none, .network: balance = totalBalance } @@ -288,7 +288,7 @@ public class PortfolioStore: ObservableObject, WalletObserverStore { accounts: allAccounts.map { account in .init( isSelected: !nonSelectedAccountAddresses.contains( - where: { $0 == account.filterAccountKey } + where: { $0 == account.id } ), model: account ) @@ -319,8 +319,9 @@ 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]] - private var tokenBalancesCache: [String: [String: Double]] = [:] + private var tokenBalancesCache: TokenBalanceCache = [:] /// Cache of prices for each token. The key is the token's `assetRatioId`. private var pricesCache: [String: String] = [:] /// Cache of priceHistories. The key is the token's `assetRatioId`. @@ -510,7 +511,7 @@ public class PortfolioStore: ObservableObject, WalletObserverStore { // 2. Test Cases will come here, we will fetch balance using // a mock `rpcService` and `bitcoinWalletService` let fetchedTokenBalances = await withTaskGroup( - of: [String: [String: Double]].self, + of: TokenBalanceCache.self, body: { @MainActor [tokenBalancesCache, rpcService, bitcoinWalletService, assetManager] @@ -528,7 +529,8 @@ public class PortfolioStore: ObservableObject, WalletObserverStore { ) { balanceForToken = await bitcoinWalletService.fetchBTCBalance( - accountId: account.accountId + accountId: account.accountId, + type: .total ) } else { balanceForToken = await rpcService.balance( @@ -537,10 +539,10 @@ public class PortfolioStore: ObservableObject, WalletObserverStore { network: tokenNetworkAccounts.network ) } - tokenBalances.merge(with: [account.cacheBalanceKey: balanceForToken ?? 0]) + tokenBalances.merge(with: [account.id: balanceForToken ?? 0]) assetManager.updateBalance( for: token, - account: account.cacheBalanceKey, + account: account.id, balance: "\(balanceForToken ?? 0)", completion: nil ) @@ -548,7 +550,7 @@ public class PortfolioStore: ObservableObject, WalletObserverStore { return [token.id: tokenBalances] } return await group.reduce( - into: [String: [String: Double]](), + into: TokenBalanceCache(), { partialResult, new in partialResult.merge(with: new) } @@ -725,7 +727,7 @@ public class PortfolioStore: ObservableObject, WalletObserverStore { .filter { key, value in // if we previously fetched balance for an account it will remain in cache. // filter out to avoid including in total balance - selectedAccounts.contains(where: { $0.cacheBalanceKey == key }) + selectedAccounts.contains(where: { $0.id == key }) } ) } @@ -761,7 +763,7 @@ public class PortfolioStore: ObservableObject, WalletObserverStore { .filter { key, value in // if we previously fetched balance for an account it will remain in cache. // filter out to avoid including in total balance - selectedAccounts.contains(where: { $0.cacheBalanceKey == key }) + selectedAccounts.contains(where: { $0.id == key }) } ) } @@ -795,7 +797,7 @@ public class PortfolioStore: ObservableObject, WalletObserverStore { balanceForAccounts: tokenBalancesCache[token.id, default: [:]] .filter { key, value in // only provide grouped account balance - key == account.cacheBalanceKey + key == account.id } ) } @@ -803,7 +805,7 @@ public class PortfolioStore: ObservableObject, WalletObserverStore { .optionallyFilter( shouldFilter: filters.isHidingSmallBalances, isIncluded: { asset in - let balanceForAccount = asset.balanceForAccounts[account.cacheBalanceKey] ?? 0 + let balanceForAccount = asset.balanceForAccounts[account.id] ?? 0 let value = (Double(asset.price) ?? 0) * balanceForAccount return value >= 0.05 } diff --git a/ios/brave-ios/Sources/BraveWallet/Crypto/Stores/SelectAccountTokenStore.swift b/ios/brave-ios/Sources/BraveWallet/Crypto/Stores/SelectAccountTokenStore.swift index 9f428119a141..d62bcf1d0ef8 100644 --- a/ios/brave-ios/Sources/BraveWallet/Crypto/Stores/SelectAccountTokenStore.swift +++ b/ios/brave-ios/Sources/BraveWallet/Crypto/Stores/SelectAccountTokenStore.swift @@ -74,8 +74,9 @@ class SelectAccountTokenStore: ObservableObject, WalletObserverStore { private var allNetworks: [BraveWallet.NetworkInfo] = [] private var balancesFetched: Bool = false - /// Cache of balances of each asset for each account. [account.address: [token.id: balance]] - private var balancesForAccountsCache: [String: [String: Double]] = [:] + private typealias TokenBalanceCache = [String: [String: Double]] + /// Cache of balances of each asset for each account. [account.id: [token.id: balance]] + private var balancesForAccountsCache: TokenBalanceCache = [:] /// Cache of prices of assets. The key(s) are the `BraveWallet.BlockchainToken.assetRatioId` lowercased. private var pricesForTokensCache: [String: String] = [:] /// Cache of metadata for NFTs. The key(s) is the token's `id`. @@ -226,7 +227,7 @@ class SelectAccountTokenStore: ObservableObject, WalletObserverStore { self.isLoadingBalances = true defer { isLoadingBalances = false } let balancesForAccounts = await withTaskGroup( - of: [String: [String: Double]].self, + of: TokenBalanceCache.self, body: { group in for account in allAccounts { group.addTask { // get balance for all tokens this account supports @@ -235,11 +236,11 @@ class SelectAccountTokenStore: ObservableObject, WalletObserverStore { account: account, networkAssets: networkAssets ) - return [account.address: balancesForTokens] + return [account.id: balancesForTokens] } } return await group.reduce( - into: [String: [String: Double]](), + into: TokenBalanceCache(), { partialResult, new in partialResult.merge(with: new) } @@ -247,13 +248,13 @@ class SelectAccountTokenStore: ObservableObject, WalletObserverStore { } ) for account in allAccounts { - if let updatedBalancesForAccount = balancesForAccounts[account.address] { + if let updatedBalancesForAccount = balancesForAccounts[account.id] { // if balance fetch failed that we already have cached, don't overwrite existing - if var existing = self.balancesForAccountsCache[account.address] { + if var existing = self.balancesForAccountsCache[account.id] { existing.merge(with: updatedBalancesForAccount) - self.balancesForAccountsCache[account.address] = existing + self.balancesForAccountsCache[account.id] = existing } else { - self.balancesForAccountsCache[account.address] = updatedBalancesForAccount + self.balancesForAccountsCache[account.id] = updatedBalancesForAccount } } } @@ -304,7 +305,7 @@ class SelectAccountTokenStore: ObservableObject, WalletObserverStore { selectedNetworks: [BraveWallet.NetworkInfo], allAccounts: [BraveWallet.AccountInfo], userVisibleAssets: [BraveWallet.BlockchainToken], - balancesCache: [String: [String: Double]], + balancesCache: TokenBalanceCache, balancesFetched: Bool, pricesCache: [String: String], metadataCache: [String: NFTMetadata], @@ -331,7 +332,7 @@ class SelectAccountTokenStore: ObservableObject, WalletObserverStore { // network must support account keyring tokenNetwork.supportedKeyrings.contains(account.keyringId.rawValue as NSNumber) { - let balance = balancesCache[account.address]?[token.id] ?? 0 + let balance = balancesCache[account.id]?[token.id] ?? 0 if hideZeroBalances, balance <= 0 { // token has no balance, user is hiding zero balance tokens. return nil diff --git a/ios/brave-ios/Sources/BraveWallet/Extensions/BitcoinWalletServiceExtensions.swift b/ios/brave-ios/Sources/BraveWallet/Extensions/BitcoinWalletServiceExtensions.swift index dc6ecb3d7676..4d0a5c73c40e 100644 --- a/ios/brave-ios/Sources/BraveWallet/Extensions/BitcoinWalletServiceExtensions.swift +++ b/ios/brave-ios/Sources/BraveWallet/Extensions/BitcoinWalletServiceExtensions.swift @@ -6,6 +6,12 @@ import BraveCore import Foundation +enum BTCBalanceType { + case total + case available + case pending +} + extension BraveWalletBitcoinWalletService { /// - Parameters: @@ -39,16 +45,24 @@ extension BraveWalletBitcoinWalletService { /// - Parameters: /// - accountId: A list of `BraveWallet.AccountId` - /// + /// - type: `BTCBalanceType` which indicates which btc balance it should return /// - Returns: The BTC balance of the given `BraveWallet.AccountId` in `Double`; Will return a nil if there is an issue fetching balance. - func fetchBTCBalance(accountId: BraveWallet.AccountId) async -> Double? { + func fetchBTCBalance(accountId: BraveWallet.AccountId, type: BTCBalanceType) async -> Double? { guard let btcBalance = await self.balance(accountId: accountId).0 else { return nil } - let availableSatoshiString = String(btcBalance.availableBalance) + let balanceString: String + switch type { + case .total: + balanceString = String(btcBalance.totalBalance) + case .available: + balanceString = String(btcBalance.availableBalance) + case .pending: + balanceString = String(btcBalance.pendingBalance) + } let formatter = WeiFormatter(decimalFormatStyle: .decimals(precision: 8)) if let valueString = formatter.decimalString( - for: availableSatoshiString, + for: balanceString, radix: .decimal, decimals: 8 ) { diff --git a/ios/brave-ios/Sources/BraveWallet/Extensions/BraveWalletExtensions.swift b/ios/brave-ios/Sources/BraveWallet/Extensions/BraveWalletExtensions.swift index c0bf1c5146ac..adeba0ac84a0 100644 --- a/ios/brave-ios/Sources/BraveWallet/Extensions/BraveWalletExtensions.swift +++ b/ios/brave-ios/Sources/BraveWallet/Extensions/BraveWalletExtensions.swift @@ -201,16 +201,6 @@ extension BraveWallet.AccountInfo { } return parentOrder } - - // The account unique key that we use to store for token balance in CD - var cacheBalanceKey: String { - coin == .btc ? accountId.uniqueKey : address - } - - // The account unique key that we use to store for user filter preferences - var filterAccountKey: String { - coin == .btc ? accountId.uniqueKey : address - } } extension BraveWallet.CoinType { diff --git a/ios/brave-ios/Sources/BraveWallet/WalletPreferences.swift b/ios/brave-ios/Sources/BraveWallet/WalletPreferences.swift index d0ec247879da..66d266e20c8f 100644 --- a/ios/brave-ios/Sources/BraveWallet/WalletPreferences.swift +++ b/ios/brave-ios/Sources/BraveWallet/WalletPreferences.swift @@ -164,6 +164,12 @@ extension Preferences { key: "wallet.is-bitcoin-testnet-enabled", default: false ) + + /// Used to track whether to migrate `account.address` to `account.accountId.uniqueKey` (`account.id`) + static let migrateCacheKeyCompleted = Option( + key: "wallet.migrate-cache-key-completed", + default: false + ) } } diff --git a/ios/brave-ios/Sources/BraveWallet/WalletUserAssetManager.swift b/ios/brave-ios/Sources/BraveWallet/WalletUserAssetManager.swift index 5319e1fe6e07..6e5f4c947567 100644 --- a/ios/brave-ios/Sources/BraveWallet/WalletUserAssetManager.swift +++ b/ios/brave-ios/Sources/BraveWallet/WalletUserAssetManager.swift @@ -121,7 +121,15 @@ public class WalletUserAssetManager: WalletUserAssetManagerType, WalletObserverS self.keyringServiceObserver = KeyringServiceObserver( keyringService: keyringService, _unlocked: { [weak self] in - self?.refreshBalances() + Task { @MainActor [self] in + // migrate `account.address` with `account.accountId.uniqueKey` (`account.id`) + if !Preferences.Wallet.migrateCacheKeyCompleted.value { + await self?.migrateCacheKey() + self?.refreshBalances() + } else { + self?.refreshBalances() + } + } }, _accountsChanged: { [weak self] in self?.refreshBalances() @@ -399,13 +407,14 @@ public class WalletUserAssetManager: WalletUserAssetManagerType, WalletObserverS } if let btc = networkAssets?.tokens.first, let btcBalance = await bitcoinWalletService.fetchBTCBalance( - accountId: account.accountId + accountId: account.accountId, + type: .total ) { WalletUserAssetBalance.updateBalance( for: btc, balance: String(btcBalance), - account: account.cacheBalanceKey + account: account.id ) } } else { @@ -426,7 +435,7 @@ public class WalletUserAssetManager: WalletUserAssetManagerType, WalletObserverS WalletUserAssetBalance.updateBalance( for: token, balance: String(tokenBalanceForAccount.value), - account: account.cacheBalanceKey + account: account.id ) } } @@ -488,7 +497,8 @@ public class WalletUserAssetManager: WalletUserAssetManagerType, WalletObserverS var tokenBalance: Double? if account.coin == .btc { tokenBalance = await bitcoinWalletService.fetchBTCBalance( - accountId: account.accountId + accountId: account.accountId, + type: .total ) } else { tokenBalance = await rpcService.balance( @@ -500,7 +510,7 @@ public class WalletUserAssetManager: WalletUserAssetManagerType, WalletObserverS WalletUserAssetBalance.updateBalance( for: asset, balance: String(tokenBalance ?? 0), - account: account.cacheBalanceKey + account: account.id ) } } @@ -550,6 +560,35 @@ public class WalletUserAssetManager: WalletUserAssetManagerType, WalletObserverS private func retrieveAllDataObserver() -> [WalletUserAssetDataObserver] { return dataObservers.allObjects as? [WalletUserAssetDataObserver] ?? [] } + + @MainActor private func migrateCacheKey() async { + let allAccounts = await keyringService.allAccountInfos() + // balance + if let allBalances = WalletUserAssetBalance.getBalances() { + await withTaskGroup( + of: Void.self, + body: { @MainActor group in + for balance in allBalances { + if let account = allAccounts.first(where: { $0.address == balance.accountAddress }) { + group.addTask { @MainActor in + await WalletUserAssetBalance.updateAccountAddress(for: account) + } + } + } + } + ) + } + // nonSelectedAccountsFilter + var newAddresses: [String] = [] + for address in Preferences.Wallet.nonSelectedAccountsFilter.value { + if let account = allAccounts.first(where: { $0.address == address }) { + newAddresses.append(account.id) + } + } + Preferences.Wallet.nonSelectedAccountsFilter.value = newAddresses + + Preferences.Wallet.migrateCacheKeyCompleted.value = true + } } extension WalletUserAssetManager: PreferencesObserver { diff --git a/ios/brave-ios/Sources/Data/models/WalletUserAssetBalance.swift b/ios/brave-ios/Sources/Data/models/WalletUserAssetBalance.swift index 2bb3e8dacadf..e71e5af8e59f 100644 --- a/ios/brave-ios/Sources/Data/models/WalletUserAssetBalance.swift +++ b/ios/brave-ios/Sources/Data/models/WalletUserAssetBalance.swift @@ -181,6 +181,26 @@ public final class WalletUserAssetBalance: NSManagedObject, CRUD { completion: completion ) } + + /// - Parameters: + /// - account: `BraveWallet.AccountInfo`, the account that we need to update the value of `accountAddress` + public static func updateAccountAddress( + for account: BraveWallet.AccountInfo + ) async { + await withCheckedContinuation { continuation in + DataController.perform(context: .new(inMemory: false), save: false) { context in + let predicate = NSPredicate( + format: "accountAddress == %@", + account.address + ) + if let asset = WalletUserAssetBalance.first(where: predicate, context: context) { + asset.accountAddress = account.accountId.uniqueKey + WalletUserAssetBalance.saveContext(context) + } + continuation.resume() + } + } + } } extension WalletUserAssetBalance { diff --git a/ios/brave-ios/Tests/BraveWalletTests/AccountActivityStoreTests.swift b/ios/brave-ios/Tests/BraveWalletTests/AccountActivityStoreTests.swift index 6e8b548823f7..ef25346bc045 100644 --- a/ios/brave-ios/Tests/BraveWalletTests/AccountActivityStoreTests.swift +++ b/ios/brave-ios/Tests/BraveWalletTests/AccountActivityStoreTests.swift @@ -325,7 +325,7 @@ class AccountActivityStoreTests: XCTestCase { BraveWallet.BlockchainToken.mockERC721NFTToken.symbol ) XCTAssertEqual( - lastUpdatedNFTs[safe: 0]?.balanceForAccounts[account.address], + lastUpdatedNFTs[safe: 0]?.balanceForAccounts[account.id], Int(mockNFTBalance) ) XCTAssertEqual( @@ -506,7 +506,7 @@ class AccountActivityStoreTests: XCTestCase { BraveWallet.BlockchainToken.mockSolanaNFTToken.symbol ) XCTAssertEqual( - lastUpdatedNFTs[safe: 0]?.balanceForAccounts[account.address], + lastUpdatedNFTs[safe: 0]?.balanceForAccounts[account.id], Int(mockSolanaNFTTokenBalance) ) XCTAssertEqual( diff --git a/ios/brave-ios/Tests/BraveWalletTests/AccountsStoreTests.swift b/ios/brave-ios/Tests/BraveWalletTests/AccountsStoreTests.swift index b2f19c7ecfc1..8dfdd0a173fb 100644 --- a/ios/brave-ios/Tests/BraveWalletTests/AccountsStoreTests.swift +++ b/ios/brave-ios/Tests/BraveWalletTests/AccountsStoreTests.swift @@ -24,6 +24,7 @@ import XCTest let ethAccount2 = (BraveWallet.AccountInfo.mockEthAccount.copy() as! BraveWallet.AccountInfo).then { $0.address = "mock_eth_id_2" + $0.accountId.uniqueKey = $0.address $0.name = "Ethereum Account 2" } let solAccount1: BraveWallet.AccountInfo = .mockSolAccount diff --git a/ios/brave-ios/Tests/BraveWalletTests/AssetDetailStoreTests.swift b/ios/brave-ios/Tests/BraveWalletTests/AssetDetailStoreTests.swift index 68c0a9c1a687..730191f8e153 100644 --- a/ios/brave-ios/Tests/BraveWalletTests/AssetDetailStoreTests.swift +++ b/ios/brave-ios/Tests/BraveWalletTests/AssetDetailStoreTests.swift @@ -94,25 +94,7 @@ class AssetDetailStoreTests: XCTestCase { $1(true) } - let mockBtcBalance: Double = 0.001 - let btcBalanceSatoshi = - formatter.weiString( - from: mockBtcBalance, - radix: .decimal, - decimals: Int(BraveWallet.BlockchainToken.mockBTCToken.decimals) - ) ?? "" let bitcoinWalletService = BraveWallet.TestBitcoinWalletService() - bitcoinWalletService._balance = { - $1( - .init( - totalBalance: UInt64(btcBalanceSatoshi) ?? 0, - availableBalance: UInt64(btcBalanceSatoshi) ?? 0, - pendingBalance: 0, - balances: [:] - ), - nil - ) - } // setup store let store = AssetDetailStore( @@ -540,25 +522,7 @@ class AssetDetailStoreTests: XCTestCase { let solTxManagerProxy = BraveWallet.TestSolanaTxManagerProxy() let swapService = BraveWallet.TestSwapService() - let mockBtcBalance: Double = 0.001 - let btcBalanceSatoshi = - formatter.weiString( - from: mockBtcBalance, - radix: .decimal, - decimals: Int(BraveWallet.BlockchainToken.mockBTCToken.decimals) - ) ?? "" let bitcoinWalletService = BraveWallet.TestBitcoinWalletService() - bitcoinWalletService._balance = { - $1( - .init( - totalBalance: UInt64(btcBalanceSatoshi) ?? 0, - availableBalance: UInt64(btcBalanceSatoshi) ?? 0, - pendingBalance: 0, - balances: [:] - ), - nil - ) - } // setup store var store = AssetDetailStore( diff --git a/ios/brave-ios/Tests/BraveWalletTests/NFTStoreTests.swift b/ios/brave-ios/Tests/BraveWalletTests/NFTStoreTests.swift index 364688818b42..9bbd01c40b01 100644 --- a/ios/brave-ios/Tests/BraveWalletTests/NFTStoreTests.swift +++ b/ios/brave-ios/Tests/BraveWalletTests/NFTStoreTests.swift @@ -20,6 +20,7 @@ class NFTStoreTests: XCTestCase { let ethAccount2 = (BraveWallet.AccountInfo.mockEthAccount.copy() as! BraveWallet.AccountInfo).then { $0.address = "mock_eth_id_2" + $0.accountId.uniqueKey = $0.address $0.name = "Ethereum Account 2" } let unownedEthNFT = diff --git a/ios/brave-ios/Tests/BraveWalletTests/PortfolioStoreTests.swift b/ios/brave-ios/Tests/BraveWalletTests/PortfolioStoreTests.swift index 839f891757fd..067e480ff94b 100644 --- a/ios/brave-ios/Tests/BraveWalletTests/PortfolioStoreTests.swift +++ b/ios/brave-ios/Tests/BraveWalletTests/PortfolioStoreTests.swift @@ -39,18 +39,21 @@ import XCTest let ethAccount2 = (BraveWallet.AccountInfo.mockEthAccount.copy() as! BraveWallet.AccountInfo).then { $0.address = "mock_eth_id_2" + $0.accountId.uniqueKey = $0.address $0.name = "Ethereum Account 2" } let solAccount1: BraveWallet.AccountInfo = .mockSolAccount let solAccount2 = (BraveWallet.AccountInfo.mockSolAccount.copy() as! BraveWallet.AccountInfo).then { $0.address = "mock_sol_id_2" + $0.accountId.uniqueKey = $0.address $0.name = "Solana Account 2" } let filAccount1: BraveWallet.AccountInfo = .mockFilAccount let filAccount2 = (BraveWallet.AccountInfo.mockFilAccount.copy() as! BraveWallet.AccountInfo).then { $0.address = "mock_fil_id_2" + $0.accountId.uniqueKey = $0.address $0.name = "Filecoin Account 2" } let filTestnetAccount: BraveWallet.AccountInfo = .mockFilTestnetAccount diff --git a/ios/brave-ios/Tests/BraveWalletTests/TransactionParserTests.swift b/ios/brave-ios/Tests/BraveWalletTests/TransactionParserTests.swift index eb4f8affbe95..bc191a4eda45 100644 --- a/ios/brave-ios/Tests/BraveWalletTests/TransactionParserTests.swift +++ b/ios/brave-ios/Tests/BraveWalletTests/TransactionParserTests.swift @@ -39,26 +39,31 @@ class TransactionParserTests: XCTestCase { (BraveWallet.AccountInfo.previewAccount.copy() as! BraveWallet.AccountInfo).then { $0.name = "Ethereum Account 2" $0.address = "0x0987654321098765432109876543210987654321" + $0.accountId.uniqueKey = $0.address $0.accountId.address = "0x0987654321098765432109876543210987654321" }, (BraveWallet.AccountInfo.mockSolAccount.copy() as! BraveWallet.AccountInfo).then { $0.name = "Solana Account 1" $0.address = "0xaaaaaaaaaabbbbbbbbbbccccccccccdddddddddd" + $0.accountId.uniqueKey = $0.address $0.accountId.address = "0xaaaaaaaaaabbbbbbbbbbccccccccccdddddddddd" }, (BraveWallet.AccountInfo.mockSolAccount.copy() as! BraveWallet.AccountInfo).then { $0.name = "Solana Account 2" $0.address = "0xeeeeeeeeeeffffffffff11111111112222222222" + $0.accountId.uniqueKey = $0.address $0.accountId.address = "0xeeeeeeeeeeffffffffff11111111112222222222" }, (BraveWallet.AccountInfo.mockFilTestnetAccount.copy() as! BraveWallet.AccountInfo).then { $0.name = "Filecoin Testnet 1" $0.address = "fil_testnet_address_1" + $0.accountId.uniqueKey = $0.address $0.accountId.address = "fil_testnet_address_1" }, (BraveWallet.AccountInfo.mockFilTestnetAccount.copy() as! BraveWallet.AccountInfo).then { $0.name = "Filecoin Testnet 2" $0.address = "fil_testnet_address_2" + $0.accountId.uniqueKey = $0.address $0.accountId.address = "fil_testnet_address_2" }, ] diff --git a/ios/brave-ios/Tests/BraveWalletTests/WalletArrayExtensionTests.swift b/ios/brave-ios/Tests/BraveWalletTests/WalletArrayExtensionTests.swift index 2937fb75b443..bc5e3b53df74 100644 --- a/ios/brave-ios/Tests/BraveWalletTests/WalletArrayExtensionTests.swift +++ b/ios/brave-ios/Tests/BraveWalletTests/WalletArrayExtensionTests.swift @@ -62,7 +62,7 @@ class WalletArrayExtensionTests: XCTestCase { network: .mockMainnet, price: "1000", history: [], - balanceForAccounts: [account1.cacheBalanceKey: 1] + balanceForAccounts: [account1.id: 1] ) // USDC value $500 let viewModel2: AssetViewModel = .init( @@ -71,7 +71,7 @@ class WalletArrayExtensionTests: XCTestCase { network: .mockMainnet, price: "500", history: [], - balanceForAccounts: [account1.cacheBalanceKey: 1] + balanceForAccounts: [account1.id: 1] ) // SOL value $500 let viewModel3: AssetViewModel = .init( @@ -80,7 +80,7 @@ class WalletArrayExtensionTests: XCTestCase { network: .mockSolana, price: "500", history: [], - balanceForAccounts: [account2.cacheBalanceKey: 1] + balanceForAccounts: [account2.id: 1] ) // FIL value $100 on mainnet let viewModel4: AssetViewModel = .init( @@ -89,7 +89,7 @@ class WalletArrayExtensionTests: XCTestCase { network: .mockFilecoinMainnet, price: "100", history: [], - balanceForAccounts: [account3.cacheBalanceKey: 1] + balanceForAccounts: [account3.id: 1] ) // BTC value $20000 on mainnet let viewModel5: AssetViewModel = .init( @@ -98,7 +98,7 @@ class WalletArrayExtensionTests: XCTestCase { network: .mockBitcoinMainnet, price: "20000", history: [], - balanceForAccounts: [account4.cacheBalanceKey: 1] + balanceForAccounts: [account4.id: 1] ) // FIL value $100 on testnet let viewModel6: AssetViewModel = .init( @@ -107,7 +107,7 @@ class WalletArrayExtensionTests: XCTestCase { network: .mockFilecoinTestnet, price: "50", history: [], - balanceForAccounts: [testAccount1.cacheBalanceKey: 2] + balanceForAccounts: [testAccount1.id: 2] ) // BTC value $40000 on testnet let viewModel7: AssetViewModel = .init( @@ -116,7 +116,7 @@ class WalletArrayExtensionTests: XCTestCase { network: .mockBitcoinTestnet, price: "20000", history: [], - balanceForAccounts: [testAccount2.cacheBalanceKey: 2] + balanceForAccounts: [testAccount2.id: 2] ) let array: [AssetViewModel] = [ viewModel1, viewModel2, @@ -153,7 +153,7 @@ class WalletArrayExtensionTests: XCTestCase { network: .mockMainnet, price: "1000", history: [], - balanceForAccounts: [account1.cacheBalanceKey: 1] + balanceForAccounts: [account1.id: 1] ) // USDC value $500 var viewModel2: AssetViewModel = .init( @@ -162,7 +162,7 @@ class WalletArrayExtensionTests: XCTestCase { network: .mockMainnet, price: "500", history: [], - balanceForAccounts: [account1.cacheBalanceKey: 1] + balanceForAccounts: [account1.id: 1] ) // SOL value $100 var viewModel3: AssetViewModel = .init( @@ -171,7 +171,7 @@ class WalletArrayExtensionTests: XCTestCase { network: .mockSolana, price: "100", history: [], - balanceForAccounts: [account2.cacheBalanceKey: 1] + balanceForAccounts: [account2.id: 1] ) // FIL value $100 on mainnet var viewModel4: AssetViewModel = .init( @@ -180,7 +180,7 @@ class WalletArrayExtensionTests: XCTestCase { network: .mockFilecoinMainnet, price: "100", history: [], - balanceForAccounts: [account3.cacheBalanceKey: 1] + balanceForAccounts: [account3.id: 1] ) // BTC value $20000 on mainnet var viewModel5: AssetViewModel = .init( @@ -189,7 +189,7 @@ class WalletArrayExtensionTests: XCTestCase { network: .mockBitcoinMainnet, price: "20000", history: [], - balanceForAccounts: [account4.cacheBalanceKey: 1] + balanceForAccounts: [account4.id: 1] ) // FIL value $100 on testnet var viewModel6: AssetViewModel = .init( @@ -198,7 +198,7 @@ class WalletArrayExtensionTests: XCTestCase { network: .mockFilecoinTestnet, price: "50", history: [], - balanceForAccounts: [testAccount1.cacheBalanceKey: 2] + balanceForAccounts: [testAccount1.id: 2] ) // BTC value $100 on testnet var viewModel7: AssetViewModel = .init( @@ -207,7 +207,7 @@ class WalletArrayExtensionTests: XCTestCase { network: .mockBitcoinTestnet, price: "20000", history: [], - balanceForAccounts: [testAccount2.cacheBalanceKey: 0.005] + balanceForAccounts: [testAccount2.id: 0.005] ) var group1: AssetGroupViewModel = .init( groupType: .account(account1), @@ -256,7 +256,7 @@ class WalletArrayExtensionTests: XCTestCase { network: .mockMainnet, price: "1000", history: [], - balanceForAccounts: [account1.address: 1] + balanceForAccounts: [account1.id: 1] ) // USDC value $500 viewModel2 = .init( @@ -265,7 +265,7 @@ class WalletArrayExtensionTests: XCTestCase { network: .mockMainnet, price: "500", history: [], - balanceForAccounts: [account1.address: 1] + balanceForAccounts: [account1.id: 1] ) // SOL value $100 viewModel3 = .init( @@ -274,7 +274,7 @@ class WalletArrayExtensionTests: XCTestCase { network: .mockSolana, price: "100", history: [], - balanceForAccounts: [account2.address: 1] + balanceForAccounts: [account2.id: 1] ) // FIL value $100 on mainnet viewModel4 = .init( @@ -283,7 +283,7 @@ class WalletArrayExtensionTests: XCTestCase { network: .mockFilecoinMainnet, price: "100", history: [], - balanceForAccounts: [account3.address: 1] + balanceForAccounts: [account3.id: 1] ) // BTC value $20000 on mainnet viewModel5 = .init( @@ -292,7 +292,7 @@ class WalletArrayExtensionTests: XCTestCase { network: .mockBitcoinMainnet, price: "20000", history: [], - balanceForAccounts: [account4.address: 1] + balanceForAccounts: [account4.id: 1] ) // FIL value $100 on testnet viewModel6 = .init( @@ -301,7 +301,7 @@ class WalletArrayExtensionTests: XCTestCase { network: .mockFilecoinTestnet, price: "50", history: [], - balanceForAccounts: [testAccount1.address: 2] + balanceForAccounts: [testAccount1.id: 2] ) // BTC value $100 on testnet viewModel7 = .init( @@ -310,7 +310,7 @@ class WalletArrayExtensionTests: XCTestCase { network: .mockBitcoinTestnet, price: "20000", history: [], - balanceForAccounts: [testAccount2.address: 0.005] + balanceForAccounts: [testAccount2.id: 0.005] ) group1 = .init( groupType: .network(.mockMainnet), From 437d5d8e3ee2671ce98df04041f91d3b69684b64 Mon Sep 17 00:00:00 2001 From: Nuo Xu Date: Mon, 22 Apr 2024 14:28:42 -0400 Subject: [PATCH 3/3] remove duplicated dictionary merging --- .../Sources/BraveWallet/Crypto/Stores/AccountActivityStore.swift | 1 - 1 file changed, 1 deletion(-) diff --git a/ios/brave-ios/Sources/BraveWallet/Crypto/Stores/AccountActivityStore.swift b/ios/brave-ios/Sources/BraveWallet/Crypto/Stores/AccountActivityStore.swift index 01fbe6287aa0..27e1336ab092 100644 --- a/ios/brave-ios/Sources/BraveWallet/Crypto/Stores/AccountActivityStore.swift +++ b/ios/brave-ios/Sources/BraveWallet/Crypto/Stores/AccountActivityStore.swift @@ -246,7 +246,6 @@ class AccountActivityStore: ObservableObject, WalletObserverStore { account: account, networkAssets: allUserNetworkAssets ) - tokenBalanceCache.merge(with: tokenBalances) } tokenBalanceCache.merge(with: tokenBalances)