Update BalanceFetcher with updating tokens using .main queue #3125

pull/3130/head
Vladyslav shepitko 3 years ago
parent 9aa96d7284
commit 6702ffe641
  1. 4
      AlphaWallet.xcodeproj/project.pbxproj
  2. 358
      AlphaWallet/Core/Coordinators/WalletBalance/PrivateBalanceFetcher.swift
  3. 70
      AlphaWallet/Core/Coordinators/WalletBalance/WalletBalanceCoordinator.swift
  4. 55
      AlphaWallet/Core/Coordinators/WalletBalance/WalletBalanceFetcher.swift
  5. 2
      AlphaWallet/InCoordinator.swift
  6. 30
      AlphaWallet/Tokens/Coordinators/SingleChainTokenCoordinator.swift
  7. 55
      AlphaWallet/Tokens/Types/TokensDataStore.swift

@ -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 = "<group>"; };
87713EB3264BAB5A00B1B9CB /* ActivityPageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActivityPageView.swift; sourceTree = "<group>"; };
87713EB5264BAB6E00B1B9CB /* AlertsPageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AlertsPageView.swift; sourceTree = "<group>"; };
8777A47D26660CAA005285BD /* PrivateTokensDataStoreType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PrivateTokensDataStoreType.swift; sourceTree = "<group>"; };
877D00AE25ADF60A008E22CC /* TransactionConfiguratorTransactionsTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TransactionConfiguratorTransactionsTests.swift; sourceTree = "<group>"; };
8782035C2431E66600792F12 /* FilterTokensCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FilterTokensCoordinator.swift; sourceTree = "<group>"; };
8782035E2431FBC300792F12 /* ShowAddHideTokensViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShowAddHideTokensViewModel.swift; sourceTree = "<group>"; };
@ -4348,7 +4346,6 @@
87BB63DB265CFF2700FF702A /* WalletBalanceCoordinator.swift */,
87C65F522660DD2E00919819 /* WalletBalanceFetcher.swift */,
87BB63E1265E759700FF702A /* PrivateBalanceFetcher.swift */,
8777A47D26660CAA005285BD /* PrivateTokensDataStoreType.swift */,
);
path = WalletBalance;
sourceTree = "<group>";
@ -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 */,

@ -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<Bool?>] = []
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<Bool?> {
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<Bool?> in
tokensDatastore.updateTokenPromise(primaryKey: etherToken.primaryKey, action: .value(balance.value))
}).recover(on: queue, { _ -> Guarantee<Bool?> in
return .value(nil)
})
}
private func refreshBalance(tokenObjects: [Activity.AssignedToken], group: DispatchGroup) {
private func refreshBalance(tokenObjects: [Activity.AssignedToken], group: DispatchGroup) -> Promise<Bool?> {
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<Bool?> in
let resolved = value.compactMap { $0.optionalValue }.flatMap { $0 }
return self.tokensDatastore.batchUpdateTokenPromise(resolved).recover { _ -> Guarantee<Bool?> 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<TokenBatchOperation?> {
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<TokenBatchOperation?> 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<TokenBatchOperation?> 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<TokenBatchOperation?> 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<Void> {
let erc721Promise = updateNonOpenSeaErc721Balance(contract: contract, tokens: tokens, tokensDatastore: tokensDatastore)
let erc1155Promise: Promise<Void> = Promise.value(())
private func updateNonOpenSeaNonFungiblesBalance2(erc721Or1115ContractNotFoundInOpenSea contract: AlphaWallet.Address, tokens: [Activity.AssignedToken]) -> Promise<[TokenBatchOperation]> {
let erc721Promise = updateNonOpenSeaErc721Balance2(contract: contract, tokens: tokens)
let erc1155Promise: Promise<TokenBatchOperation?> = 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<Void> {
private func updateNonOpenSeaErc721Balance2(contract: AlphaWallet.Address, tokens: [Activity.AssignedToken]) -> Promise<TokenBatchOperation?> {
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<String>] = tokenIds.map { self.fetchNonFungibleJson(forTokenId: $0, address: contract, tokens: tokens) }
return when(fulfilled: guarantees)
}.then { jsons -> Promise<Void> in
return Promise<Void> { 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<TokenBatchOperation?> in
return .value(nil)
}
}
private func updateNonOpenSeaErc1155Balance(contract: AlphaWallet.Address, tokens: [Activity.AssignedToken], tokensDatastore: PrivateTokensDatastoreType) -> Promise<Void> {
private func updateNonOpenSeaErc1155Balance(contract: AlphaWallet.Address, tokens: [Activity.AssignedToken]) -> Promise<Void> {
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<Erc1155TokenIds.ContractsTokenIdsAndValues> {
firstly {
functional.fetchUnknownErc1155ContractsDetails(contractsTokenIdsAndValue: contractsTokenIdsAndValue, tokens: tokens, server: server, account: account, assetDefinitionStore: assetDefinitionStore)
}.then { tokensToAdd -> Promise<Erc1155TokenIds.ContractsTokenIdsAndValues> in
}.then(on: .main, { tokensToAdd -> Promise<Erc1155TokenIds.ContractsTokenIdsAndValues> in
let (promise, seal) = Promise<Erc1155TokenIds.ContractsTokenIdsAndValues>.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<Void> {
let group: DispatchGroup = .init()
let (promise, seal) = Promise<Void>.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<String> {
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<String> {
@ -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<String> {
@ -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

@ -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<WalletSummary> = .init(walletSummary)
private (set) lazy var subscribableWalletsSummary: Subscribable<WalletSummary> = .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<ServerDictionary<[TokenMappedToTicker]>> {
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<Void> 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<WalletSummary> {
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)
})
}
}

@ -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<WalletBalance> = .init(balance)
let tokensChangeSubscribable: Subscribable<Void> = .init(nil)
private var services: ServerDictionary<WalletBalanceFetcherSubServices> = .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<WalletBalance> { 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<BalanceBaseViewModel> {
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<BalanceBaseViewModel>(balanceViewModel(key: tokenObject))
let subscribable = Subscribable<BalanceBaseViewModel>(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<Activity.AssignedToken>()
for var tokenObject in tokenObjects {

@ -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
}

@ -146,8 +146,8 @@ class SingleChainTokenCoordinator: Coordinator {
let detectedContracts = contracts
return strongSelf.contractsForTransactedTokens(detectedContracts: detectedContracts, storage: strongSelf.storage).then(on: strongSelf.queue, { contractsToAdd -> Promise<Bool> in
let promises = contractsToAdd.compactMap { each -> Promise<PrepareToAddTokenData> in
strongSelf.prepareToAddToken(for: each, server: strongSelf.server, storage: strongSelf.storage)
let promises = contractsToAdd.compactMap { each -> Promise<BatchObject> in
strongSelf.fetchBatchObjectFromContractData(for: each, server: strongSelf.server, storage: strongSelf.storage)
}
return when(resolved: promises).then(on: .main, { values -> Promise<Bool> 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<SingleChainTokenCoordinator.PrepareToAddTokenData>] in
contracts.map { [weak self] each -> Promise<PrepareToAddTokenData> in
contractsToAutodetectTokens(withContracts: contractsToDetect, storage: storage).map(on: queue, { contracts -> [Promise<SingleChainTokenCoordinator.BatchObject>] in
contracts.map { [weak self] each -> Promise<BatchObject> in
guard let strongSelf = self else { return .init(error: PMKError.cancelled) }
return strongSelf.tokenProvider.getTokenType(for: each).then { tokenType -> Promise<PrepareToAddTokenData> in
return strongSelf.tokenProvider.getTokenType(for: each).then { tokenType -> Promise<BatchObject> 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<PrepareToAddTokenData> in
return balanceCoordinator.getERC875TokenBalance(for: address, contract: each).then { balance -> Promise<BatchObject> 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<PrepareToAddTokenData> in
}.recover { _ -> Guarantee<BatchObject> in
return .value(.none)
}
case .erc20:
let balanceCoordinator = GetERC20BalanceCoordinator(forServer: server)
return balanceCoordinator.getBalance(for: address, contract: each).then { balance -> Promise<PrepareToAddTokenData> in
return balanceCoordinator.getBalance(for: address, contract: each).then { balance -> Promise<BatchObject> 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<PrepareToAddTokenData> in
}.recover { _ -> Guarantee<BatchObject> 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 <PrepareToAddTokenData> {
private func fetchBatchObjectFromContractData(for contract: AlphaWallet.Address, onlyIfThereIsABalance: Bool = false, server: RPCServer, storage: TokensDataStore) -> Promise <BatchObject> {
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()

@ -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<Bool?> {
func batchUpdateTokenPromise(_ actions: [PrivateBalanceFetcher.TokenBatchOperation]) -> Promise<Bool?> {
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
// swiftlint:enable file_length

Loading…
Cancel
Save