diff --git a/AlphaWallet.xcodeproj/project.pbxproj b/AlphaWallet.xcodeproj/project.pbxproj index e7ca0b527..12b2ac525 100644 --- a/AlphaWallet.xcodeproj/project.pbxproj +++ b/AlphaWallet.xcodeproj/project.pbxproj @@ -784,7 +784,6 @@ 87713EB2264BAB4700B1B9CB /* TokenInfoPageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 87713EB1264BAB4700B1B9CB /* TokenInfoPageView.swift */; }; 87713EB4264BAB5A00B1B9CB /* ActivityPageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 87713EB3264BAB5A00B1B9CB /* ActivityPageView.swift */; }; 87713EB6264BAB6E00B1B9CB /* AlertsPageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 87713EB5264BAB6E00B1B9CB /* AlertsPageView.swift */; }; - 8777A47E26660CAA005285BD /* PrivateTokensDataStoreType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8777A47D26660CAA005285BD /* PrivateTokensDataStoreType.swift */; }; 877D00AF25ADF60A008E22CC /* TransactionConfiguratorTransactionsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 877D00AE25ADF60A008E22CC /* TransactionConfiguratorTransactionsTests.swift */; }; 8782035D2431E66600792F12 /* FilterTokensCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8782035C2431E66600792F12 /* FilterTokensCoordinator.swift */; }; 8782035F2431FBC300792F12 /* ShowAddHideTokensViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8782035E2431FBC300792F12 /* ShowAddHideTokensViewModel.swift */; }; @@ -1748,7 +1747,6 @@ 87713EB1264BAB4700B1B9CB /* TokenInfoPageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TokenInfoPageView.swift; sourceTree = ""; }; 87713EB3264BAB5A00B1B9CB /* ActivityPageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActivityPageView.swift; sourceTree = ""; }; 87713EB5264BAB6E00B1B9CB /* AlertsPageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AlertsPageView.swift; sourceTree = ""; }; - 8777A47D26660CAA005285BD /* PrivateTokensDataStoreType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PrivateTokensDataStoreType.swift; sourceTree = ""; }; 877D00AE25ADF60A008E22CC /* TransactionConfiguratorTransactionsTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TransactionConfiguratorTransactionsTests.swift; sourceTree = ""; }; 8782035C2431E66600792F12 /* FilterTokensCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FilterTokensCoordinator.swift; sourceTree = ""; }; 8782035E2431FBC300792F12 /* ShowAddHideTokensViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShowAddHideTokensViewModel.swift; sourceTree = ""; }; @@ -4348,7 +4346,6 @@ 87BB63DB265CFF2700FF702A /* WalletBalanceCoordinator.swift */, 87C65F522660DD2E00919819 /* WalletBalanceFetcher.swift */, 87BB63E1265E759700FF702A /* PrivateBalanceFetcher.swift */, - 8777A47D26660CAA005285BD /* PrivateTokensDataStoreType.swift */, ); path = WalletBalance; sourceTree = ""; @@ -5135,7 +5132,6 @@ 29B933F81F8609FF009FCABB /* PaymentFlow.swift in Sources */, 29A13E331F6B1B7A00E432A2 /* AppStyle.swift in Sources */, 737EEDDA201BE3A8009D9D5D /* Lock.swift in Sources */, - 8777A47E26660CAA005285BD /* PrivateTokensDataStoreType.swift in Sources */, 29FF12F81F747D6C00AFD326 /* Error.swift in Sources */, 29AD8A061F93DC8C008E10E7 /* PushDevice.swift in Sources */, 294DFBA91FE6EBFB004CEB56 /* NewTokenViewController.swift in Sources */, diff --git a/AlphaWallet/Core/Coordinators/WalletBalance/PrivateBalanceFetcher.swift b/AlphaWallet/Core/Coordinators/WalletBalance/PrivateBalanceFetcher.swift index 61be15416..1d300946a 100644 --- a/AlphaWallet/Core/Coordinators/WalletBalance/PrivateBalanceFetcher.swift +++ b/AlphaWallet/Core/Coordinators/WalletBalance/PrivateBalanceFetcher.swift @@ -24,6 +24,7 @@ protocol PrivateBalanceFetcherType: AnyObject { func refreshBalance(updatePolicy: PrivateBalanceFetcher.RefreshBalancePolicy, force: Bool) } +// swiftlint:disable type_body_length class PrivateBalanceFetcher: PrivateBalanceFetcherType { typealias TokenIdMetaData = (contract: AlphaWallet.Address, tokenId: BigUInt, json: String, value: BigInt) @@ -36,30 +37,31 @@ class PrivateBalanceFetcher: PrivateBalanceFetcherType { weak var erc721TokenIdsFetcher: Erc721TokenIdsFetcher? private lazy var tokenProvider: TokenProviderType = { - return TokenProvider(account: account, server: server, queue: backgroundQueue) + return TokenProvider(account: account, server: server, queue: queue) }() private let account: Wallet private let openSea: OpenSea - private let backgroundQueue: DispatchQueue + private let queue: DispatchQueue private let server: RPCServer private lazy var etherToken = Activity.AssignedToken(tokenObject: TokensDataStore.etherToken(forServer: server)) private var isRefeshingBalance: Bool = false weak var delegate: PrivateTokensDataStoreDelegate? private var enabledObjectsObservation: NotificationToken? - private let tokensDatastore: PrivateTokensDatastoreType + private let tokensDatastore: TokensDataStore private let assetDefinitionStore: AssetDefinitionStore - init(account: Wallet, tokensDatastore: PrivateTokensDatastoreType, server: RPCServer, assetDefinitionStore: AssetDefinitionStore, queue: DispatchQueue) { + init(account: Wallet, tokensDatastore: TokensDataStore, server: RPCServer, assetDefinitionStore: AssetDefinitionStore, queue: DispatchQueue) { self.account = account self.server = server - self.backgroundQueue = queue + self.queue = queue self.openSea = OpenSea.createInstance(forServer: server) self.tokensDatastore = tokensDatastore self.assetDefinitionStore = assetDefinitionStore + //NOTE: fire refresh balance only for initial scope, and while adding new tokens - enabledObjectsObservation = tokensDatastore.enabledObjects.observe(on: backgroundQueue) { [weak self] change in + enabledObjectsObservation = tokensDatastore.enabledObjectResults.observe(on: queue) { [weak self] change in guard let strongSelf = self else { return } switch change { @@ -91,28 +93,25 @@ class PrivateBalanceFetcher: PrivateBalanceFetcherType { } func refreshBalance(updatePolicy: RefreshBalancePolicy, force: Bool = false) { - let tokenObjects = tokensDatastore.enabledObjects.map { Activity.AssignedToken(tokenObject: $0) } - refreshBalance(tokenObjects: Array(tokenObjects), updatePolicy: .all, force: false) + Promise<[Activity.AssignedToken]> { seal in + DispatchQueue.main.async { [weak self] in + guard let strongSelf = self else { return seal.reject(PMKError.cancelled) } + let tokenObjects = strongSelf.tokensDatastore.tokenObjects + + seal.fulfill(tokenObjects) + } + }.done(on: queue, { tokenObjects in + self.refreshBalance(tokenObjects: tokenObjects, updatePolicy: .all, force: false) + }).cauterize() } - private func refreshBalanceForNonErc721Or1155Tokens(tokens: [Activity.AssignedToken], group: DispatchGroup) { + private func refreshBalanceForNonErc721Or1155Tokens(tokens: [Activity.AssignedToken]) -> Promise<[PrivateBalanceFetcher.TokenBatchOperation]> { assert(!tokens.contains { $0.isERC721Or1155AndNotForTickets }) - for tokenObject in tokens { - group.enter() + let promises = tokens.map { getBalanceForNonErc721Or1155Tokens(forToken: $0) } - refreshBalanceForNonErc721Or1155Tokens(forToken: tokenObject, tokensDatastore: tokensDatastore) { [weak self] balanceValueHasChange in - guard let strongSelf = self, let delegate = strongSelf.delegate else { - group.leave() - return - } - - if let value = balanceValueHasChange, value { - delegate.didUpdate(in: strongSelf) - } - - group.leave() - } + return when(resolved: promises).map { values -> [PrivateBalanceFetcher.TokenBatchOperation] in + return values.compactMap { $0.optionalValue }.compactMap { $0 } } } @@ -127,147 +126,153 @@ class PrivateBalanceFetcher: PrivateBalanceFetcherType { isRefeshingBalance = true let group: DispatchGroup = .init() + var promises: [Promise] = [] switch updatePolicy { case .all: - refreshEthBalance(etherToken: etherToken, group: group) - refreshBalance(tokenObjects: tokenObjects, group: group) + promises += [refreshEthBalance(etherToken: etherToken)] + promises += [refreshBalance(tokenObjects: tokenObjects, group: group)] case .ercTokens: - refreshBalance(tokenObjects: tokenObjects, group: group) + promises += [refreshBalance(tokenObjects: tokenObjects, group: group)] case .eth: - refreshEthBalance(etherToken: etherToken, group: group) + promises += [refreshEthBalance(etherToken: etherToken)] } - group.notify(queue: backgroundQueue) { + group.notify(queue: queue) { self.isRefeshingBalance = false } + + firstly { + when(resolved: promises).map(on: queue, { values -> Bool? in + //NOTE: taking for first element means that values was updated + return values.compactMap { $0.optionalValue }.compactMap { $0 }.first + }) + }.done(on: queue, { balanceValueHasChange in + self.isRefeshingBalance = false + + if let value = balanceValueHasChange, value { + self.delegate.flatMap { $0.didUpdate(in: self) } + } + }) } - private func refreshEthBalance(etherToken: Activity.AssignedToken, group: DispatchGroup) { + private func refreshEthBalance(etherToken: Activity.AssignedToken) -> Promise { let tokensDatastore = self.tokensDatastore - group.enter() - tokenProvider.getEthBalance(for: account.address) { [weak self] result in - switch result { - case .success(let balance): - tokensDatastore.update(primaryKey: etherToken.primaryKey, action: .value(balance.value)) { balanceValueHasChange in - guard let strongSelf = self, let delegate = strongSelf.delegate else { - group.leave() - return - } - - if let value = balanceValueHasChange, value { - delegate.didUpdate(in: strongSelf) - } - group.leave() - } - case .failure: - group.leave() - } - } + return tokenProvider.getEthBalance(for: account.address).then(on: queue, { balance -> Promise in + tokensDatastore.updateTokenPromise(primaryKey: etherToken.primaryKey, action: .value(balance.value)) + }).recover(on: queue, { _ -> Guarantee in + return .value(nil) + }) } - private func refreshBalance(tokenObjects: [Activity.AssignedToken], group: DispatchGroup) { + private func refreshBalance(tokenObjects: [Activity.AssignedToken], group: DispatchGroup) -> Promise { let updateTokens = tokenObjects.filter { $0 != etherToken } let notErc721Or1155Tokens = updateTokens.filter { !$0.isERC721Or1155AndNotForTickets } let erc721Or1155Tokens = updateTokens.filter { $0.isERC721Or1155AndNotForTickets } - refreshBalanceForNonErc721Or1155Tokens(tokens: notErc721Or1155Tokens, group: group) - refreshBalanceForErc721Or1155Tokens(tokens: erc721Or1155Tokens, group: group) + let promise1 = refreshBalanceForNonErc721Or1155Tokens(tokens: notErc721Or1155Tokens) + let promise2 = refreshBalanceForErc721Or1155Tokens(tokens: erc721Or1155Tokens) + + return when(resolved: [promise1, promise2]).then(on: queue, { value -> Promise in + let resolved = value.compactMap { $0.optionalValue }.flatMap { $0 } + + return self.tokensDatastore.batchUpdateTokenPromise(resolved).recover { _ -> Guarantee in + return .value(nil) + } + }) } - private func refreshBalanceForNonErc721Or1155Tokens(forToken tokenObject: Activity.AssignedToken, tokensDatastore: PrivateTokensDatastoreType, completion: @escaping (Bool?) -> Void) { + enum TokenBatchOperation { + case add(ERCToken, shouldUpdateBalance: Bool) + case update(tokenObject: Activity.AssignedToken, action: TokensDataStore.TokenUpdateAction) + } + + private func getBalanceForNonErc721Or1155Tokens(forToken tokenObject: Activity.AssignedToken) -> Promise { switch tokenObject.type { case .nativeCryptocurrency: - completion(nil) + return .value(nil) case .erc20: - tokenProvider.getERC20Balance(for: tokenObject.contractAddress, completion: { result in - switch result { - case .success(let balance): - tokensDatastore.update(primaryKey: tokenObject.primaryKey, action: .value(balance), completion: completion) - case .failure: - completion(nil) - } - }) + return tokenProvider.getERC20Balance(for: tokenObject.contractAddress).map(on: queue, { value -> TokenBatchOperation in + return .update(tokenObject: tokenObject, action: .value(value)) + }).recover { _ -> Promise in + return .value(nil) + } case .erc875: - tokenProvider.getERC875Balance(for: tokenObject.contractAddress, completion: { result in - switch result { - case .success(let balance): - tokensDatastore.update(primaryKey: tokenObject.primaryKey, action: .nonFungibleBalance(balance), completion: completion) - case .failure: - completion(nil) - } - }) + return tokenProvider.getERC875Balance(for: tokenObject.contractAddress).map(on: queue, { balance -> TokenBatchOperation in + return .update(tokenObject: tokenObject, action: .nonFungibleBalance(balance)) + }).recover { _ -> Promise in + return .value(nil) + } case .erc721, .erc1155: - break + return .value(nil) case .erc721ForTickets: - tokenProvider.getERC721ForTicketsBalance(for: tokenObject.contractAddress, completion: { result in - switch result { - case .success(let balance): - tokensDatastore.update(primaryKey: tokenObject.primaryKey, action: .nonFungibleBalance(balance), completion: completion) - case .failure: - completion(nil) - } - }) + return tokenProvider.getERC721ForTicketsBalance(for: tokenObject.contractAddress).map(on: queue, { balance -> TokenBatchOperation in + return .update(tokenObject: tokenObject, action: .nonFungibleBalance(balance)) + }).recover { _ -> Promise in + return .value(nil) + } } } - private func refreshBalanceForErc721Or1155Tokens(tokens: [Activity.AssignedToken], group: DispatchGroup) { + private func refreshBalanceForErc721Or1155Tokens(tokens: [Activity.AssignedToken]) -> Promise<[PrivateBalanceFetcher.TokenBatchOperation]> { assert(!tokens.contains { !$0.isERC721Or1155AndNotForTickets }) - firstly { + return firstly { getTokensFromOpenSea() - }.done { [weak self] contractToOpenSeaNonFungibles in - guard let strongSelf = self else { return } + }.then(on: queue, { [weak self] contractToOpenSeaNonFungibles -> Guarantee<[PrivateBalanceFetcher.TokenBatchOperation]> in + guard let strongSelf = self else { return .value([]) } + let erc721Or1155ContractsFoundInOpenSea = Array(contractToOpenSeaNonFungibles.keys).map { $0 } let erc721Or1155ContractsNotFoundInOpenSea = tokens.map { $0.contractAddress } - erc721Or1155ContractsFoundInOpenSea - strongSelf.updateNonOpenSeaNonFungiblesBalance(contracts: erc721Or1155ContractsNotFoundInOpenSea, tokens: tokens, group: group) - strongSelf.updateOpenSeaNonFungiblesBalanceAndAttributes(contractToOpenSeaNonFungibles: contractToOpenSeaNonFungibles, tokens: tokens, group: group) - }.cauterize() + let p1 = strongSelf.updateNonOpenSeaNonFungiblesBalance2(contracts: erc721Or1155ContractsNotFoundInOpenSea, tokens: tokens) + let p2 = strongSelf.updateOpenSeaNonFungiblesBalanceAndAttributes(contractToOpenSeaNonFungibles: contractToOpenSeaNonFungibles, tokens: tokens) + + return when(resolved: [p1, p2]).map(on: strongSelf.queue, { results -> [PrivateBalanceFetcher.TokenBatchOperation] in + return results.compactMap { $0.optionalValue }.flatMap { $0 } + }) + }) } - private func updateNonOpenSeaNonFungiblesBalance(contracts: [AlphaWallet.Address], tokens: [Activity.AssignedToken], group: DispatchGroup) { - let promises = contracts.map { updateNonOpenSeaNonFungiblesBalance(erc721Or1115ContractNotFoundInOpenSea: $0, tokens: tokens, tokensDatastore: tokensDatastore) } - group.enter() + private func updateNonOpenSeaNonFungiblesBalance2(contracts: [AlphaWallet.Address], tokens: [Activity.AssignedToken]) -> Promise<[PrivateBalanceFetcher.TokenBatchOperation]> { + let promises = contracts.map { updateNonOpenSeaNonFungiblesBalance2(erc721Or1115ContractNotFoundInOpenSea: $0, tokens: tokens) } - firstly { - when(resolved: promises) - }.done { _ in - group.leave() - } + return firstly { + when(fulfilled: promises) + }.map(on: queue, { results in + return results.compactMap { $0 }.flatMap { $0 } + }) } - private func updateNonOpenSeaNonFungiblesBalance(erc721Or1115ContractNotFoundInOpenSea contract: AlphaWallet.Address, tokens: [Activity.AssignedToken], tokensDatastore: PrivateTokensDatastoreType) -> Promise { - let erc721Promise = updateNonOpenSeaErc721Balance(contract: contract, tokens: tokens, tokensDatastore: tokensDatastore) - let erc1155Promise: Promise = Promise.value(()) + private func updateNonOpenSeaNonFungiblesBalance2(erc721Or1115ContractNotFoundInOpenSea contract: AlphaWallet.Address, tokens: [Activity.AssignedToken]) -> Promise<[TokenBatchOperation]> { + let erc721Promise = updateNonOpenSeaErc721Balance2(contract: contract, tokens: tokens) + let erc1155Promise: Promise = Promise.value(nil) + return firstly { - when(fulfilled: erc721Promise, erc1155Promise) - }.map { _, _ in - //no-op - }.asVoid() + when(fulfilled: [erc721Promise, erc1155Promise]).map(on: queue, { results -> [TokenBatchOperation] in + return results.compactMap { $0 } + }) + } } - private func updateNonOpenSeaErc721Balance(contract: AlphaWallet.Address, tokens: [Activity.AssignedToken], tokensDatastore: PrivateTokensDatastoreType) -> Promise { + private func updateNonOpenSeaErc721Balance2(contract: AlphaWallet.Address, tokens: [Activity.AssignedToken]) -> Promise { guard let erc721TokenIdsFetcher = erc721TokenIdsFetcher else { return Promise { _ in } } return firstly { erc721TokenIdsFetcher.tokenIdsForErc721Token(contract: contract, inAccount: account.address) - }.then { tokenIds -> Promise<[String]> in + }.then(on: queue, { tokenIds -> Promise<[String]> in let guarantees: [Guarantee] = tokenIds.map { self.fetchNonFungibleJson(forTokenId: $0, address: contract, tokens: tokens) } return when(fulfilled: guarantees) - }.then { jsons -> Promise in - return Promise { seal in - guard let tokenObject = tokens.first(where: { $0.contractAddress.sameContract(as: contract) }) else { - seal.fulfill(()) - return - } - tokensDatastore.update(primaryKey: tokenObject.primaryKey, action: .nonFungibleBalance(jsons)) { _ in - seal.fulfill(()) - } + }).map(on: queue, { jsons -> TokenBatchOperation? in + guard let tokenObject = tokens.first(where: { $0.contractAddress.sameContract(as: contract) }) else { + return nil } - }.asVoid() + return .update(tokenObject: tokenObject, action: .nonFungibleBalance(jsons)) + }).recover { _ -> Guarantee in + return .value(nil) + } } - private func updateNonOpenSeaErc1155Balance(contract: AlphaWallet.Address, tokens: [Activity.AssignedToken], tokensDatastore: PrivateTokensDatastoreType) -> Promise { + private func updateNonOpenSeaErc1155Balance(contract: AlphaWallet.Address, tokens: [Activity.AssignedToken]) -> Promise { guard let contractsTokenIdsAndValue = Erc1155TokenIdsFetcher(address: account.address, server: server).readJson() else { return .value(()) } @@ -284,13 +289,12 @@ class PrivateBalanceFetcher: PrivateBalanceFetcherType { private func addUnknownErc1155ContractsToDatabase(contractsTokenIdsAndValue: Erc1155TokenIds.ContractsTokenIdsAndValues, tokens: [Activity.AssignedToken]) -> Promise { firstly { functional.fetchUnknownErc1155ContractsDetails(contractsTokenIdsAndValue: contractsTokenIdsAndValue, tokens: tokens, server: server, account: account, assetDefinitionStore: assetDefinitionStore) - }.then { tokensToAdd -> Promise in + }.then(on: .main, { tokensToAdd -> Promise in let (promise, seal) = Promise.pending() - self.tokensDatastore.addCustom(tokens: tokensToAdd, shouldUpdateBalance: false) { - seal.fulfill(contractsTokenIdsAndValue) - } + self.tokensDatastore.addCustom(tokens: tokensToAdd, shouldUpdateBalance: false) + seal.fulfill(contractsTokenIdsAndValue) return promise - } + }) } private func fetchErc1155NonFungibleJsons(contractsTokenIdsAndValue: Erc1155TokenIds.ContractsTokenIdsAndValues, tokens: [Activity.AssignedToken]) -> Promise<[TokenIdMetaData]> { @@ -308,7 +312,6 @@ class PrivateBalanceFetcher: PrivateBalanceFetcherType { } private func updateErc1155TokenIdBalancesInDatabase(tokenIdsData: [TokenIdMetaData], tokens: [Activity.AssignedToken]) -> Promise { - let group: DispatchGroup = .init() let (promise, seal) = Promise.pending() var contractsTokenIdsAndValue: [AlphaWallet.Address: [BigUInt: String]] = .init() @@ -323,18 +326,11 @@ class PrivateBalanceFetcher: PrivateBalanceFetcherType { return promise } let jsons: [String] = Array(tokenIdsAndJsons.values) - group.enter() - tokensDatastore.update(primaryKey: tokenObject.primaryKey, action: .nonFungibleBalance(jsons)) { _ in - group.leave() - } - group.enter() - tokensDatastore.update(primaryKey: tokenObject.primaryKey, action: .type(.erc1155)) { _ in - group.leave() - } - } - group.notify(queue: .main) { - seal.fulfill(()) + tokensDatastore.update(primaryKey: tokenObject.primaryKey, action: .nonFungibleBalance(jsons)) + tokensDatastore.update(primaryKey: tokenObject.primaryKey, action: .type(.erc1155)) } + + seal.fulfill(()) return promise } @@ -342,11 +338,11 @@ class PrivateBalanceFetcher: PrivateBalanceFetcherType { private func fetchNonFungibleJson(forTokenId tokenId: String, address: AlphaWallet.Address, tokens: [Activity.AssignedToken]) -> Guarantee { firstly { Erc721Contract(server: server).getErc721TokenUri(for: tokenId, contract: address) - }.then { + }.then(on: queue, { self.fetchTokenJson(forTokenId: tokenId, uri: $0, address: address, tokens: tokens) - }.recover { e in + }).recover(on: queue, { _ in return self.generateTokenJsonFallback(forTokenId: tokenId, address: address, tokens: tokens) - } + }) } private func fetchTokenJson(forTokenId tokenId: String, uri originalUri: URL, address: AlphaWallet.Address, tokens: [TokenObject]) -> Promise { @@ -356,7 +352,7 @@ class PrivateBalanceFetcher: PrivateBalanceFetcherType { return firstly { //Must not use `SessionManager.default.request` or `Alamofire.request` which uses the former. See comment in var sessionManagerWithDefaultHttpHeaders.request(uri, method: .get).responseData() - }.map { data, _ in + }.map(on: queue, { data, _ in if let json = try? JSON(data: data) { if json["error"] == "Internal Server Error" { throw Error() @@ -383,7 +379,7 @@ class PrivateBalanceFetcher: PrivateBalanceFetcherType { } else { throw Error() } - } + }) } private func generateTokenJsonFallback(forTokenId tokenId: String, address: AlphaWallet.Address, tokens: [Activity.AssignedToken]) -> Guarantee { @@ -409,7 +405,7 @@ class PrivateBalanceFetcher: PrivateBalanceFetcherType { return firstly { //Must not use `SessionManager.default.request` or `Alamofire.request` which uses the former. See comment in var sessionManagerWithDefaultHttpHeaders.request(uri, method: .get).responseData() - }.map { data, _ in + }.map(on: queue, { data, _ in if let json = try? JSON(data: data) { if json["error"] == "Internal Server Error" { throw Error() @@ -437,56 +433,59 @@ class PrivateBalanceFetcher: PrivateBalanceFetcherType { } else { throw Error() } - } + }) } - private func updateOpenSeaNonFungiblesBalanceAndAttributes(contractToOpenSeaNonFungibles: [AlphaWallet.Address: [OpenSeaNonFungible]], tokens: [Activity.AssignedToken], group: DispatchGroup) { - for (contract, openSeaNonFungibles) in contractToOpenSeaNonFungibles { - var listOfJson = [String]() - var anyNonFungible: OpenSeaNonFungible? - for each in openSeaNonFungibles { - if let encodedJson = try? JSONEncoder().encode(each), let jsonString = String(data: encodedJson, encoding: .utf8) { - anyNonFungible = each - listOfJson.append(jsonString) + private func updateOpenSeaNonFungiblesBalanceAndAttributes(contractToOpenSeaNonFungibles: [AlphaWallet.Address: [OpenSeaNonFungible]], tokens: [Activity.AssignedToken]) -> Promise<[TokenBatchOperation]> { + return Promise<[TokenBatchOperation]> { seal in + var actions: [TokenBatchOperation] = [] + for (contract, openSeaNonFungibles) in contractToOpenSeaNonFungibles { + var listOfJson = [String]() + var anyNonFungible: OpenSeaNonFungible? + for each in openSeaNonFungibles { + if let encodedJson = try? JSONEncoder().encode(each), let jsonString = String(data: encodedJson, encoding: .utf8) { + anyNonFungible = each + listOfJson.append(jsonString) + } else { + //no op + } + } + let tokenType: TokenType + if let anyNonFungible = anyNonFungible { + tokenType = anyNonFungible.tokenType.asTokenType } else { - //no op + //Default to ERC721 because this is what we supported (from OpenSea) before adding ERC1155 support + tokenType = .erc721 } - } - let tokenType: TokenType - if let anyNonFungible = anyNonFungible { - tokenType = anyNonFungible.tokenType.asTokenType - } else { - //Default to ERC721 because this is what we supported (from OpenSea) before adding ERC1155 support - tokenType = .erc721 - } - if let tokenObject = tokens.first(where: { $0.contractAddress.sameContract(as: contract) }) { - switch tokenObject.type { - case .nativeCryptocurrency, .erc721, .erc875, .erc721ForTickets, .erc1155: - break - case .erc20: - group.enter() - tokensDatastore.update(primaryKey: tokenObject.primaryKey, action: .type(tokenType)) { _ in group.leave() } - } - group.enter() - tokensDatastore.update(primaryKey: tokenObject.primaryKey, action: .nonFungibleBalance(listOfJson)) { _ in group.leave() } - if let anyNonFungible = anyNonFungible { - group.enter() - tokensDatastore.update(primaryKey: tokenObject.primaryKey, action: .name(anyNonFungible.contractName)) { _ in group.leave() } + if let tokenObject = tokens.first(where: { $0.contractAddress.sameContract(as: contract) }) { + switch tokenObject.type { + case .nativeCryptocurrency, .erc721, .erc875, .erc721ForTickets, .erc1155: + break + case .erc20: + tokensDatastore.update(primaryKey: tokenObject.primaryKey, action: .type(tokenType)) + actions += [.update(tokenObject: tokenObject, action: .type(tokenType))] + } + actions += [.update(tokenObject: tokenObject, action: .nonFungibleBalance(listOfJson))] + if let anyNonFungible = anyNonFungible { + actions += [.update(tokenObject: tokenObject, action: .name(anyNonFungible.contractName))] + } + } else { + let token = ERCToken( + contract: contract, + server: server, + name: openSeaNonFungibles[0].contractName, + symbol: openSeaNonFungibles[0].symbol, + decimals: 0, + type: tokenType, + balance: listOfJson + ) + + actions += [.add(token, shouldUpdateBalance: tokenType.shouldUpdateBalanceWhenDetected)] } - } else { - let token = ERCToken( - contract: contract, - server: server, - name: openSeaNonFungibles[0].contractName, - symbol: openSeaNonFungibles[0].symbol, - decimals: 0, - type: tokenType, - balance: listOfJson - ) - group.enter() - tokensDatastore.addCustom(token: token, shouldUpdateBalance: tokenType.shouldUpdateBalanceWhenDetected) { group.leave() } } + + seal.fulfill(actions) } } @@ -552,3 +551,4 @@ fileprivate extension PrivateBalanceFetcher.functional { return promise } } +// swiftlint:enable type_body_length diff --git a/AlphaWallet/Core/Coordinators/WalletBalance/WalletBalanceCoordinator.swift b/AlphaWallet/Core/Coordinators/WalletBalance/WalletBalanceCoordinator.swift index ae5d5baba..3e94bb87b 100644 --- a/AlphaWallet/Core/Coordinators/WalletBalance/WalletBalanceCoordinator.swift +++ b/AlphaWallet/Core/Coordinators/WalletBalance/WalletBalanceCoordinator.swift @@ -20,6 +20,7 @@ protocol WalletBalanceCoordinatorType: AnyObject { func refreshEthBalance() func transactionsStorage(wallet: Wallet, server: RPCServer) -> TransactionsStorage + func tokensDatastore(wallet: Wallet, server: RPCServer) -> TokensDataStore } class WalletBalanceCoordinator: NSObject, WalletBalanceCoordinatorType { @@ -33,7 +34,7 @@ class WalletBalanceCoordinator: NSObject, WalletBalanceCoordinatorType { private var balanceFetchers: [Wallet: WalletBalanceFetcherType] = [:] private lazy var servers: [RPCServer] = config.enabledServers - private (set) lazy var subscribableWalletsSummary: Subscribable = .init(walletSummary) + private (set) lazy var subscribableWalletsSummary: Subscribable = .init(nil) private let queue: DispatchQueue = DispatchQueue(label: "com.WalletBalanceCoordinator.updateQueue") init(keystore: Keystore, config: Config, assetDefinitionStore: AssetDefinitionStore, coinTickersFetcher: CoinTickersFetcherType) { @@ -93,6 +94,7 @@ class WalletBalanceCoordinator: NSObject, WalletBalanceCoordinatorType { func start() { queue.async { [weak self] in self?.fetchTokenPrices() + self?.notifyWalletSummary() } } @@ -100,24 +102,37 @@ class WalletBalanceCoordinator: NSObject, WalletBalanceCoordinatorType { balanceFetchers[wallet]!.transactionsStorage(server: server) } - private var availableTokenObjects: ServerDictionary<[TokenMappedToTicker]> { - let uniqueTokenObjectsOfAllWallets = Set(balanceFetchers.flatMap { (_, value) in value.tokenObjects }) + func tokensDatastore(wallet: Wallet, server: RPCServer) -> TokensDataStore { + balanceFetchers[wallet]!.tokensDatastore(server: server) + } + + private var availableTokenObjects: Promise> { + Promise<[Activity.AssignedToken]> { seal in + DispatchQueue.main.async { [weak self] in + guard let strongSelf = self else { return seal.reject(PMKError.cancelled) } + let tokenObjects = strongSelf.balanceFetchers.map { $0.value.tokenObjects }.flatMap { $0 } - var tokens = ServerDictionary<[TokenMappedToTicker]>() - for each in uniqueTokenObjectsOfAllWallets { - var array: [TokenMappedToTicker] - if let value = tokens[safe: each.server] { - array = value - } else { - array = .init() + seal.fulfill(tokenObjects) } + }.map(on: queue, { objects -> ServerDictionary<[TokenMappedToTicker]> in + let uniqueTokenObjectsOfAllWallets = Set(objects) - array.append(TokenMappedToTicker(token: each)) + var tokens = ServerDictionary<[TokenMappedToTicker]>() - tokens[each.server] = array - } + for each in uniqueTokenObjectsOfAllWallets { + var array: [TokenMappedToTicker] + if let value = tokens[safe: each.server] { + array = value + } else { + array = .init() + } - return tokens + array.append(TokenMappedToTicker(token: each)) + + tokens[each.server] = array + } + return tokens + }) } private func createWalletBalanceFetcher(wallet: Wallet) -> WalletBalanceFetcherType { @@ -142,21 +157,30 @@ class WalletBalanceCoordinator: NSObject, WalletBalanceCoordinatorType { private func fetchTokenPrices() { firstly { - coinTickersFetcher.fetchPrices(forTokens: availableTokenObjects) - }.done(on: queue, { _ in - //no-op - }).catch { _ in + availableTokenObjects + }.then(on: queue, { values -> Promise in + self.coinTickersFetcher.fetchPrices(forTokens: values) + }).done(on: queue, { _ in //no-op - } + }).cauterize() } private func notifyWalletSummary() { - subscribableWalletsSummary.value = walletSummary + walletSummary.done(on: queue, { [weak self] summary in + self?.subscribableWalletsSummary.value = summary + }).cauterize() } - private var walletSummary: WalletSummary { - let balances = balanceFetchers.map { $0.value.balance } - return .init(balances: balances) + private var walletSummary: Promise { + Promise<[WalletBalance]> { seal in + DispatchQueue.main.async { [weak self] in + guard let strongSelf = self else { return seal.reject(PMKError.cancelled) } + let balances = strongSelf.balanceFetchers.map { $0.value.balance } + seal.fulfill(balances) + } + }.map(on: queue, { balances -> WalletSummary in + return WalletSummary(balances: balances) + }) } } diff --git a/AlphaWallet/Core/Coordinators/WalletBalance/WalletBalanceFetcher.swift b/AlphaWallet/Core/Coordinators/WalletBalance/WalletBalanceFetcher.swift index 1b95cf4db..addf6bf64 100644 --- a/AlphaWallet/Core/Coordinators/WalletBalance/WalletBalanceFetcher.swift +++ b/AlphaWallet/Core/Coordinators/WalletBalance/WalletBalanceFetcher.swift @@ -8,6 +8,7 @@ import UIKit import RealmSwift import BigInt +import PromiseKit protocol WalletBalanceFetcherDelegate: AnyObject { func didAddToken(in fetcher: WalletBalanceFetcherType) @@ -30,8 +31,9 @@ protocol WalletBalanceFetcherType: AnyObject { func refreshEthBalance() func refreshBalance() func transactionsStorage(server: RPCServer) -> TransactionsStorage + func tokensDatastore(server: RPCServer) -> TokensDataStore } -typealias WalletBalanceFetcherSubServices = (tokensDataStore: PrivateTokensDatastoreType, balanceFetcher: PrivateBalanceFetcherType, transactionsStorage: TransactionsStorage) +typealias WalletBalanceFetcherSubServices = (tokensDataStore: TokensDataStore, balanceFetcher: PrivateBalanceFetcherType, transactionsStorage: TransactionsStorage) class WalletBalanceFetcher: NSObject, WalletBalanceFetcherType { private static let updateBalanceInterval: TimeInterval = 60 @@ -39,7 +41,6 @@ class WalletBalanceFetcher: NSObject, WalletBalanceFetcherType { private let wallet: Wallet private let assetDefinitionStore: AssetDefinitionStore private (set) lazy var subscribableWalletBalance: Subscribable = .init(balance) - let tokensChangeSubscribable: Subscribable = .init(nil) private var services: ServerDictionary = .init() var tokenObjects: [Activity.AssignedToken] { @@ -64,7 +65,7 @@ class WalletBalanceFetcher: NSObject, WalletBalanceFetcherType { for each in servers { let transactionsStorage = TransactionsStorage(realm: realm, server: each, delegate: nil) - let tokensDatastore: PrivateTokensDatastoreType = PrivateTokensDatastore(realm: realm, server: each, queue: queue) + let tokensDatastore = TokensDataStore(realm: realm, account: wallet, server: each) let balanceFetcher = PrivateBalanceFetcher(account: wallet, tokensDatastore: tokensDatastore, server: each, assetDefinitionStore: assetDefinitionStore, queue: queue) balanceFetcher.erc721TokenIdsFetcher = transactionsStorage balanceFetcher.delegate = self @@ -86,13 +87,17 @@ class WalletBalanceFetcher: NSObject, WalletBalanceFetcherType { services[server].transactionsStorage } + func tokensDatastore(server: RPCServer) -> TokensDataStore { + services[server].tokensDataStore + } + func update(servers: [RPCServer]) { for each in servers { if services[safe: each] != nil { //no-op } else { let transactionsStorage = TransactionsStorage(realm: realm, server: each, delegate: nil) - let tokensDatastore: PrivateTokensDatastoreType = PrivateTokensDatastore(realm: realm, server: each, queue: queue) + let tokensDatastore = TokensDataStore(realm: realm, account: wallet, server: each) let balanceFetcher = PrivateBalanceFetcher(account: wallet, tokensDatastore: tokensDatastore, server: each, assetDefinitionStore: assetDefinitionStore, queue: queue) balanceFetcher.erc721TokenIdsFetcher = transactionsStorage balanceFetcher.delegate = self @@ -108,20 +113,33 @@ class WalletBalanceFetcher: NSObject, WalletBalanceFetcherType { } private func notifyUpdateTokenBalancesSubscribers() { - for each in cache.values { - guard let tokensDatastore = services[safe: each.key.server] else { continue } - guard let tokenObject = tokensDatastore.tokensDataStore.tokenObject(contract: each.key.address) else { - continue - } + DispatchQueue.main.async { [weak self] in + guard let strongSelf = self else { return } + + for (key, each) in strongSelf.cache.values { + guard let tokensDatastore = strongSelf.services[safe: key.server] else { continue } + guard let tokenObject = tokensDatastore.tokensDataStore.token(forContract: key.address) else { continue } - each.value.1.value = balanceViewModel(key: tokenObject) + each.1.value = strongSelf.balanceViewModel(key: tokenObject) + } } } private func notifyUpdateBalance() { - subscribableWalletBalance.value = balance + Promise { seal in + DispatchQueue.main.async { [weak self] in + guard let strongSelf = self else { return seal.reject(PMKError.cancelled) } + let value = strongSelf.balance - delegate?.didUpdate(in: self) + seal.fulfill(value) + } + }.get(on: .main, { [weak self] balance in + self?.subscribableWalletBalance.value = balance + }).done(on: queue, { [weak self] _ in + guard let strongSelf = self else { return } + + strongSelf.delegate.flatMap { $0.didUpdate(in: strongSelf) } + }).cauterize() } private func balanceViewModel(key tokenObject: TokenObject) -> BalanceBaseViewModel? { @@ -149,16 +167,17 @@ class WalletBalanceFetcher: NSObject, WalletBalanceFetcherType { } func subscribableTokenBalance(addressAndRPCServer: AddressAndRPCServer) -> Subscribable { - guard let tokensDatastore = services[safe: addressAndRPCServer.server] else { return .init(nil) } + guard let services = services[safe: addressAndRPCServer.server] else { return .init(nil) } - guard let tokenObject = tokensDatastore.0.tokenObject(contract: addressAndRPCServer.address) else { + guard let tokenObject = services.tokensDataStore.token(forContract: addressAndRPCServer.address) else { return .init(nil) } if let value = cache[addressAndRPCServer] { return value.1 } else { - let subscribable = Subscribable(balanceViewModel(key: tokenObject)) + let subscribable = Subscribable(nil) + let observation = tokenObject.observe(on: queue) { [weak self] change in guard let strongSelf = self else { return } @@ -175,12 +194,18 @@ class WalletBalanceFetcher: NSObject, WalletBalanceFetcherType { } cache[addressAndRPCServer] = (observation, subscribable) + let balance = balanceViewModel(key: tokenObject) + + queue.async { + subscribable.value = balance + } return subscribable } } var balance: WalletBalance { + let tokenObjects = services.compactMap { $0.value.0.enabledObject }.flatMap { $0 }.map{ Activity.AssignedToken(tokenObject: $0) } var balances = Set() for var tokenObject in tokenObjects { diff --git a/AlphaWallet/InCoordinator.swift b/AlphaWallet/InCoordinator.swift index 4a27dfb36..243c6a2de 100644 --- a/AlphaWallet/InCoordinator.swift +++ b/AlphaWallet/InCoordinator.swift @@ -238,7 +238,7 @@ class InCoordinator: NSObject, Coordinator { } private func createTokensDatastore(forConfig config: Config, server: RPCServer) -> TokensDataStore { - let storage = TokensDataStore(realm: realm, account: wallet, server: server) + let storage = walletBalanceCoordinator.tokensDatastore(wallet: wallet, server: server) return storage } diff --git a/AlphaWallet/Tokens/Coordinators/SingleChainTokenCoordinator.swift b/AlphaWallet/Tokens/Coordinators/SingleChainTokenCoordinator.swift index 5870e8617..29788816c 100644 --- a/AlphaWallet/Tokens/Coordinators/SingleChainTokenCoordinator.swift +++ b/AlphaWallet/Tokens/Coordinators/SingleChainTokenCoordinator.swift @@ -146,8 +146,8 @@ class SingleChainTokenCoordinator: Coordinator { let detectedContracts = contracts return strongSelf.contractsForTransactedTokens(detectedContracts: detectedContracts, storage: strongSelf.storage).then(on: strongSelf.queue, { contractsToAdd -> Promise in - let promises = contractsToAdd.compactMap { each -> Promise in - strongSelf.prepareToAddToken(for: each, server: strongSelf.server, storage: strongSelf.storage) + let promises = contractsToAdd.compactMap { each -> Promise in + strongSelf.fetchBatchObjectFromContractData(for: each, server: strongSelf.server, storage: strongSelf.storage) } return when(resolved: promises).then(on: .main, { values -> Promise in @@ -220,33 +220,33 @@ class SingleChainTokenCoordinator: Coordinator { private func autoDetectTokensImpl(withContracts contractsToDetect: [(name: String, contract: AlphaWallet.Address)], server: RPCServer, completion: @escaping () -> Void) { let address = keystore.currentWallet.address - contractsToAutodetectTokens(withContracts: contractsToDetect, storage: storage).map(on: queue, { contracts -> [Promise] in - contracts.map { [weak self] each -> Promise in + contractsToAutodetectTokens(withContracts: contractsToDetect, storage: storage).map(on: queue, { contracts -> [Promise] in + contracts.map { [weak self] each -> Promise in guard let strongSelf = self else { return .init(error: PMKError.cancelled) } - return strongSelf.tokenProvider.getTokenType(for: each).then { tokenType -> Promise in + return strongSelf.tokenProvider.getTokenType(for: each).then { tokenType -> Promise in switch tokenType { case .erc875: //TODO long and very similar code below. Extract function let balanceCoordinator = GetERC875BalanceCoordinator(forServer: server) - return balanceCoordinator.getERC875TokenBalance(for: address, contract: each).then { balance -> Promise in + return balanceCoordinator.getERC875TokenBalance(for: address, contract: each).then { balance -> Promise in if balance.isEmpty { return .value(.none) } else { - return strongSelf.prepareToAddToken(for: each, server: server, storage: strongSelf.storage) + return strongSelf.fetchBatchObjectFromContractData(for: each, server: server, storage: strongSelf.storage) } - }.recover { _ -> Guarantee in + }.recover { _ -> Guarantee in return .value(.none) } case .erc20: let balanceCoordinator = GetERC20BalanceCoordinator(forServer: server) - return balanceCoordinator.getBalance(for: address, contract: each).then { balance -> Promise in + return balanceCoordinator.getBalance(for: address, contract: each).then { balance -> Promise in if balance > 0 { - return strongSelf.prepareToAddToken(for: each, server: server, storage: strongSelf.storage) + return strongSelf.fetchBatchObjectFromContractData(for: each, server: server, storage: strongSelf.storage) } else { return .value(.none) } - }.recover { _ -> Guarantee in + }.recover { _ -> Guarantee in return .value(.none) } case .erc721: @@ -282,7 +282,7 @@ class SingleChainTokenCoordinator: Coordinator { }).cauterize().finally(completion) } - enum PrepareToAddTokenData { + enum BatchObject { case ercToken(ERCToken) case tokenObject(TokenObject) case delegateContracts([DelegateContract]) @@ -299,7 +299,7 @@ class SingleChainTokenCoordinator: Coordinator { } } - private func prepareToAddToken(for contract: AlphaWallet.Address, onlyIfThereIsABalance: Bool = false, server: RPCServer, storage: TokensDataStore) -> Promise { + private func fetchBatchObjectFromContractData(for contract: AlphaWallet.Address, onlyIfThereIsABalance: Bool = false, server: RPCServer, storage: TokensDataStore) -> Promise { return Promise { seal in fetchContractData(for: contract) { data in DispatchQueue.main.async { @@ -351,7 +351,7 @@ class SingleChainTokenCoordinator: Coordinator { private func addToken(for contract: AlphaWallet.Address, onlyIfThereIsABalance: Bool = false, server: RPCServer, storage: TokensDataStore, completion: @escaping (TokenObject?) -> Void) { firstly { - prepareToAddToken(for: contract, server: server, storage: storage) + fetchBatchObjectFromContractData(for: contract, server: server, storage: storage) }.map(on: .main, { operation -> [TokenObject] in return storage.addBatchObjectsOperation(values: [operation]) }).done(on: .main, { tokenObjects in @@ -391,7 +391,7 @@ class SingleChainTokenCoordinator: Coordinator { } } } - }).get(on: .main, { [weak self] tokenObject in + }).get(on: .main, { [weak self] _ in guard let strongSelf = self else { return } strongSelf.notifyTokensDidChange() diff --git a/AlphaWallet/Tokens/Types/TokensDataStore.swift b/AlphaWallet/Tokens/Types/TokensDataStore.swift index 260dc2eee..b19b55b0a 100644 --- a/AlphaWallet/Tokens/Types/TokensDataStore.swift +++ b/AlphaWallet/Tokens/Types/TokensDataStore.swift @@ -197,6 +197,33 @@ class TokensDataStore: NSObject { return newToken } + func addCustom(tokens: [ERCToken], completion: @escaping () -> Void) { + DispatchQueue.main.async { + self.addCustom(tokens: tokens, shouldUpdateBalance: true) + + completion() + } + } + + func addCustom(token: ERCToken, completion: @escaping () -> Void) { + addCustom(tokens: [token], completion: completion) + } + + @discardableResult func addCustom(tokens: [ERCToken], shouldUpdateBalance: Bool) -> [TokenObject] { + let newTokens = tokens.compactMap { TokensDataStore.tokenObject(ercToken: $0, shouldUpdateBalance: shouldUpdateBalance) } + add(tokens: newTokens) + + return newTokens + } + + @discardableResult func addCustom(token: ERCToken) -> TokenObject { + let newToken = TokensDataStore.tokenObject(ercToken: token, shouldUpdateBalance: true) + + add(tokens: [newToken]) + + return newToken + } + private static func tokenObject(ercToken token: ERCToken, shouldUpdateBalance: Bool) -> TokenObject { let newToken = TokenObject( contract: token.contract, @@ -229,7 +256,7 @@ class TokensDataStore: NSObject { return tokenObject } - @discardableResult func addBatchObjectsOperation(values: [SingleChainTokenCoordinator.PrepareToAddTokenData]) -> [TokenObject] { + @discardableResult func addBatchObjectsOperation(values: [SingleChainTokenCoordinator.BatchObject]) -> [TokenObject] { guard !values.isEmpty else { return [] } var tokenObjects: [TokenObject] = [] @@ -392,7 +419,7 @@ class TokensDataStore: NSObject { } } - func batchUpdateTokenPromise(_ actions: [(tokenObject: Activity.AssignedToken, action: TokensDataStore.TokenUpdateAction)]) -> Promise { + func batchUpdateTokenPromise(_ actions: [PrivateBalanceFetcher.TokenBatchOperation]) -> Promise { return Promise { seal in DispatchQueue.main.async { [weak self] in guard let strongSelf = self else { return seal.reject(PMKError.cancelled) } @@ -401,7 +428,17 @@ class TokensDataStore: NSObject { var result: Bool? for each in actions { - let value = strongSelf.updateTokenUnsafe(primaryKey: each.tokenObject.primaryKey, action: each.action) + var value: Bool? + switch each { + case .add(let token, let shouldUpdateBalance): + let newToken = TokensDataStore.tokenObject(ercToken: token, shouldUpdateBalance: shouldUpdateBalance) + strongSelf.unsafeAddTokenOperation(tokenObject: newToken, realm: strongSelf.realm) + + value = true + case .update(let tokenObject, let action): + value = strongSelf.updateTokenUnsafe(primaryKey: tokenObject.primaryKey, action: action) + } + if result == nil { result = value } @@ -426,7 +463,7 @@ class TokensDataStore: NSObject { } } - @discardableResult private func update(primaryKey: String, action: TokenUpdateAction) -> Bool? { + @discardableResult func update(primaryKey: String, action: TokenUpdateAction) -> Bool? { var result: Bool? realm.beginWrite() @@ -495,15 +532,9 @@ class TokensDataStore: NSObject { return result } -} +} // swiftlint:enable type_body_length -extension Realm { - var threadSafe: Realm { - try! Realm(configuration: self.configuration) - } -} - extension TokenObject { var addressAndRPCServer: AddressAndRPCServer { return .init(address: contractAddress, server: server) @@ -526,4 +557,4 @@ extension TokensDataStore.functional { } } } -// swiftlint:enable file_length \ No newline at end of file +// swiftlint:enable file_length