Refactoring: Extract non datastore related code from TokenDatastore #3104

pull/3105/head
Vladyslav shepitko 3 years ago
parent 566acaab95
commit 52b700cf73
  1. 4
      AlphaWallet.xcodeproj/project.pbxproj
  2. 2
      AlphaWallet/Browser/Coordinators/QRCodeResolutionCoordinator.swift
  3. 2
      AlphaWallet/Core/Coordinators/CustomUrlSchemeCoordinator.swift
  4. 101
      AlphaWallet/Core/Coordinators/WalletBalance/PrivateBalanceFetcher.swift
  5. 359
      AlphaWallet/Core/Types/TokenProviderType.swift
  6. 9
      AlphaWallet/EtherClient/TrustClient/Models/RawTransaction.swift
  7. 7
      AlphaWallet/Market/Coordinators/UniversalLinkCoordinator.swift
  8. 7
      AlphaWallet/Tokens/Coordinators/SingleChainTokenCoordinator.swift
  9. 31
      AlphaWallet/Tokens/Logic/ContractDataDetector.swift
  10. 323
      AlphaWallet/Tokens/Types/TokensDataStore.swift
  11. 5
      AlphaWallet/Transactions/Coordinators/SingleChainTransactionEtherscanDataCoordinator.swift
  12. 2
      AlphaWallet/Transfer/Coordinators/SendCoordinator.swift

@ -877,6 +877,7 @@
87F4D41E26C26C3A00EFB9BC /* SortTokensParam.swift in Sources */ = {isa = PBXBuildFile; fileRef = 87F4D41D26C26C3A00EFB9BC /* SortTokensParam.swift */; };
87F4D42026C26C6E00EFB9BC /* EmptyFilteringResultView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 87F4D41F26C26C6E00EFB9BC /* EmptyFilteringResultView.swift */; };
87F9972824E155280092D262 /* SeedPhraseBackupIntroductionViewControllerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 87F9972724E155280092D262 /* SeedPhraseBackupIntroductionViewControllerTests.swift */; };
87FB0A8226D8D307000EC15F /* TokenProviderType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 87FB0A8126D8D307000EC15F /* TokenProviderType.swift */; };
87FBAE0124A1EE67005EF293 /* AddressOrEnsNameLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 87FBAE0024A1EE67005EF293 /* AddressOrEnsNameLabel.swift */; };
87FF2E422567F0A3002350EB /* BlockieGenerator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 87FF2E412567F0A3002350EB /* BlockieGenerator.swift */; };
AA26C61F20412A1E00318B9B /* TokensCardViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA26C61D20412A1D00318B9B /* TokensCardViewController.swift */; };
@ -1836,6 +1837,7 @@
87F4D41D26C26C3A00EFB9BC /* SortTokensParam.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SortTokensParam.swift; sourceTree = "<group>"; };
87F4D41F26C26C6E00EFB9BC /* EmptyFilteringResultView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmptyFilteringResultView.swift; sourceTree = "<group>"; };
87F9972724E155280092D262 /* SeedPhraseBackupIntroductionViewControllerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SeedPhraseBackupIntroductionViewControllerTests.swift; sourceTree = "<group>"; };
87FB0A8126D8D307000EC15F /* TokenProviderType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TokenProviderType.swift; sourceTree = "<group>"; };
87FBAE0024A1EE67005EF293 /* AddressOrEnsNameLabel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddressOrEnsNameLabel.swift; sourceTree = "<group>"; };
87FF2E412567F0A3002350EB /* BlockieGenerator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BlockieGenerator.swift; sourceTree = "<group>"; };
AA26C61D20412A1D00318B9B /* TokensCardViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TokensCardViewController.swift; sourceTree = "<group>"; };
@ -2984,6 +2986,7 @@
87D457F626677CD300BA1442 /* NativecryptoBalanceViewModel.swift */,
87C65F542661289400919819 /* Atomic.swift */,
87F4D41D26C26C3A00EFB9BC /* SortTokensParam.swift */,
87FB0A8126D8D307000EC15F /* TokenProviderType.swift */,
);
path = Types;
sourceTree = "<group>";
@ -5694,6 +5697,7 @@
5E7C7BDE0060F5C350AFD34B /* ActivityViewController.swift in Sources */,
5E7C76194F5934264E5BABC8 /* ActivityViewModel.swift in Sources */,
87F4D41A26C26C0700EFB9BC /* DropDownView.swift in Sources */,
87FB0A8226D8D307000EC15F /* TokenProviderType.swift in Sources */,
5E7C72337A4230E78009B7E5 /* TransactionDetailsViewModel.swift in Sources */,
5E7C72EF748338DDBABB53F2 /* GetBlockTimestampCoordinator.swift in Sources */,
5E7C7416B5A023776E04A21D /* Features.swift in Sources */,

@ -251,7 +251,7 @@ extension QRCodeResolutionCoordinator: ScanQRCodeCoordinatorDelegate {
return .value((transactionType, token))
} else {
return Promise { resolver in
ContractDataDetector(address: contract, storage: storage, assetDefinitionStore: assetDefinitionStore).fetch { result in
ContractDataDetector(address: contract, account: storage.account, server: storage.server, assetDefinitionStore: assetDefinitionStore).fetch { result in
switch result {
case .name, .symbol, .balance, .decimals, .nonFungibleTokenComplete, .delegateTokenComplete, .failed:
resolver.reject(CheckEIP681Error.contractInvalid)

@ -39,7 +39,7 @@ class CustomUrlSchemeCoordinator: Coordinator {
if tokensDatastore.token(forContract: contract) != nil {
strongSelf.openSendPayFlowFor(server: server, contract: contract, recipient: recipient, amount: amount)
} else {
ContractDataDetector(address: contract, storage: tokensDatastore, assetDefinitionStore: strongSelf.assetDefinitionStore).fetch { data in
ContractDataDetector(address: contract, account: tokensDatastore.account, server: tokensDatastore.server, assetDefinitionStore: strongSelf.assetDefinitionStore).fetch { data in
switch data {
case .name, .symbol, .balance, .decimals:
break

@ -33,32 +33,11 @@ class PrivateBalanceFetcher: PrivateBalanceFetcherType {
}()
weak var erc721TokenIdsFetcher: Erc721TokenIdsFetcher?
private lazy var getNativeCryptoCurrencyBalanceCoordinator: GetNativeCryptoCurrencyBalanceCoordinator = {
return GetNativeCryptoCurrencyBalanceCoordinator(forServer: server, queue: backgroundQueue)
}()
private lazy var getERC20BalanceCoordinator: GetERC20BalanceCoordinator = {
return GetERC20BalanceCoordinator(forServer: server, queue: backgroundQueue)
}()
private lazy var getERC875BalanceCoordinator: GetERC875BalanceCoordinator = {
return GetERC875BalanceCoordinator(forServer: server, queue: backgroundQueue)
}()
private lazy var getERC721ForTicketsBalanceCoordinator: GetERC721ForTicketsBalanceCoordinator = {
return GetERC721ForTicketsBalanceCoordinator(forServer: server, queue: backgroundQueue)
}()
private lazy var getERC721BalanceCoordinator: GetERC721BalanceCoordinator = {
return GetERC721BalanceCoordinator(forServer: server, queue: backgroundQueue)
private lazy var tokenProvider: TokenProviderType = {
return TokenProvider(account: account, server: server, queue: backgroundQueue)
}()
private let account: Wallet
private let numberOfTimesToRetryFetchContractData = 2
private var chainId: Int {
return server.chainID
}
private let openSea: OpenSea
private let backgroundQueue: DispatchQueue
@ -99,72 +78,8 @@ class PrivateBalanceFetcher: PrivateBalanceFetcherType {
}
}
private func getERC20Balance(for address: AlphaWallet.Address, completion: @escaping (ResultResult<BigInt, AnyError>.t) -> Void) {
withRetry(times: numberOfTimesToRetryFetchContractData) { [weak self] triggerRetry in
guard let strongSelf = self else { return }
strongSelf.getERC20BalanceCoordinator.getBalance(for: strongSelf.account.address, contract: address) { result in
switch result {
case .success:
completion(result)
case .failure:
if !triggerRetry() {
completion(result)
}
}
}
}
}
private func getERC875Balance(for address: AlphaWallet.Address, completion: @escaping (ResultResult<[String], AnyError>.t) -> Void) {
withRetry(times: numberOfTimesToRetryFetchContractData) { [weak self] triggerRetry in
guard let strongSelf = self else { return }
strongSelf.getERC875BalanceCoordinator.getERC875TokenBalance(for: strongSelf.account.address, contract: address) { result in
switch result {
case .success:
completion(result)
case .failure:
if !triggerRetry() {
completion(result)
}
}
}
}
}
private func getERC721ForTicketsBalance(for address: AlphaWallet.Address, completion: @escaping (ResultResult<[String], AnyError>.t) -> Void) {
withRetry(times: numberOfTimesToRetryFetchContractData) { [weak self] triggerRetry in
guard let strongSelf = self else { return }
strongSelf.getERC721ForTicketsBalanceCoordinator.getERC721ForTicketsTokenBalance(for: strongSelf.account.address, contract: address) { result in
switch result {
case .success:
completion(result)
case .failure:
if !triggerRetry() {
completion(result)
}
}
}
}
}
private func getERC721Balance(for address: AlphaWallet.Address, completion: @escaping (ResultResult<[String], AnyError>.t) -> Void) {
withRetry(times: numberOfTimesToRetryFetchContractData) { [weak self] triggerRetry in
guard let strongSelf = self else { return }
strongSelf.getERC721BalanceCoordinator.getERC721TokenBalance(for: strongSelf.account.address, contract: address) { result in
switch result {
case .success(let balance):
if balance >= Int.max {
completion(.failure(AnyError(Web3Error(description: ""))))
} else {
completion(.success([String](repeating: "0", count: Int(balance))))
}
case .failure(let error):
if !triggerRetry() {
completion(.failure(error))
}
}
}
}
deinit {
enabledObjectsObservation.flatMap{ $0.invalidate() }
}
private func getTokensFromOpenSea() -> OpenSea.PromiseResult {
@ -228,7 +143,7 @@ class PrivateBalanceFetcher: PrivateBalanceFetcherType {
private func refreshEthBalance(etherToken: Activity.AssignedToken, group: DispatchGroup) {
let tokensDatastore = self.tokensDatastore
group.enter()
getNativeCryptoCurrencyBalanceCoordinator.getBalance(for: account.address) { [weak self] result in
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
@ -264,7 +179,7 @@ class PrivateBalanceFetcher: PrivateBalanceFetcherType {
case .nativeCryptocurrency:
completion(nil)
case .erc20:
getERC20Balance(for: tokenObject.contractAddress, completion: { result in
tokenProvider.getERC20Balance(for: tokenObject.contractAddress, completion: { result in
switch result {
case .success(let balance):
tokensDatastore.update(primaryKey: tokenObject.primaryKey, action: .value(balance), completion: completion)
@ -273,7 +188,7 @@ class PrivateBalanceFetcher: PrivateBalanceFetcherType {
}
})
case .erc875:
getERC875Balance(for: tokenObject.contractAddress, completion: { result in
tokenProvider.getERC875Balance(for: tokenObject.contractAddress, completion: { result in
switch result {
case .success(let balance):
tokensDatastore.update(primaryKey: tokenObject.primaryKey, action: .nonFungibleBalance(balance), completion: completion)
@ -284,7 +199,7 @@ class PrivateBalanceFetcher: PrivateBalanceFetcherType {
case .erc721:
break
case .erc721ForTickets:
getERC721ForTicketsBalance(for: tokenObject.contractAddress, completion: { result in
tokenProvider.getERC721ForTicketsBalance(for: tokenObject.contractAddress, completion: { result in
switch result {
case .success(let balance):
tokensDatastore.update(primaryKey: tokenObject.primaryKey, action: .nonFungibleBalance(balance), completion: completion)

@ -0,0 +1,359 @@
//
// TokenProviderType.swift
// AlphaWallet
//
// Created by Vladyslav Shepitko on 27.08.2021.
//
import PromiseKit
import Result
import BigInt
// NOTE: Think about the name, more fittable name is needed
protocol TokenProviderType: class {
func getContractName(for address: AlphaWallet.Address, completion: @escaping (ResultResult<String, AnyError>.t) -> Void)
func getContractSymbol(for address: AlphaWallet.Address, completion: @escaping (ResultResult<String, AnyError>.t) -> Void)
func getDecimals(for address: AlphaWallet.Address, completion: @escaping (ResultResult<UInt8, AnyError>.t) -> Void)
func getContractName(for address: AlphaWallet.Address) -> Promise<String>
func getContractSymbol(for address: AlphaWallet.Address) -> Promise<String>
func getDecimals(for address: AlphaWallet.Address) -> Promise<UInt8>
func getTokenType(for address: AlphaWallet.Address) -> Promise<TokenType>
func getERC20Balance(for address: AlphaWallet.Address, completion: @escaping (ResultResult<BigInt, AnyError>.t) -> Void)
func getERC875Balance(for address: AlphaWallet.Address, completion: @escaping (ResultResult<[String], AnyError>.t) -> Void)
func getERC721ForTicketsBalance(for address: AlphaWallet.Address, completion: @escaping (ResultResult<[String], AnyError>.t) -> Void)
func getERC721Balance(for address: AlphaWallet.Address, completion: @escaping (ResultResult<[String], AnyError>.t) -> Void)
func getTokenType(for address: AlphaWallet.Address, completion: @escaping (TokenType) -> Void)
func getEthBalance(for address: AlphaWallet.Address, completion: @escaping (ResultResult<Balance, AnyError>.t) -> Void)
}
class TokenProvider: TokenProviderType {
static let fetchContractDataTimeout = TimeInterval(4)
private lazy var getNameCoordinator: GetNameCoordinator = {
return GetNameCoordinator(forServer: server)
}()
private lazy var getSymbolCoordinator: GetSymbolCoordinator = {
return GetSymbolCoordinator(forServer: server)
}()
private lazy var getNativeCryptoCurrencyBalanceCoordinator: GetNativeCryptoCurrencyBalanceCoordinator = {
return GetNativeCryptoCurrencyBalanceCoordinator(forServer: server, queue: queue)
}()
private lazy var getERC20BalanceCoordinator: GetERC20BalanceCoordinator = {
return GetERC20BalanceCoordinator(forServer: server, queue: queue)
}()
private lazy var getERC875BalanceCoordinator: GetERC875BalanceCoordinator = {
return GetERC875BalanceCoordinator(forServer: server, queue: queue)
}()
private lazy var getERC721ForTicketsBalanceCoordinator: GetERC721ForTicketsBalanceCoordinator = {
return GetERC721ForTicketsBalanceCoordinator(forServer: server, queue: queue)
}()
private lazy var getIsERC875ContractCoordinator: GetIsERC875ContractCoordinator = {
return GetIsERC875ContractCoordinator(forServer: server)
}()
private lazy var getERC721BalanceCoordinator: GetERC721BalanceCoordinator = {
return GetERC721BalanceCoordinator(forServer: server, queue: queue)
}()
private lazy var getIsERC721ForTicketsContractCoordinator: GetIsERC721ForTicketsContractCoordinator = {
return GetIsERC721ForTicketsContractCoordinator(forServer: server)
}()
private lazy var getIsERC721ContractCoordinator: GetIsERC721ContractCoordinator = {
return GetIsERC721ContractCoordinator(forServer: server)
}()
private lazy var getDecimalsCoordinator: GetDecimalsCoordinator = {
return GetDecimalsCoordinator(forServer: server)
}()
private let account: Wallet
private let numberOfTimesToRetryFetchContractData = 2
private let server: RPCServer
private let queue: DispatchQueue?
init(account: Wallet, server: RPCServer, queue: DispatchQueue? = .none) {
self.account = account
self.queue = queue
self.server = server
}
func getContractName(for address: AlphaWallet.Address,
completion: @escaping (ResultResult<String, AnyError>.t) -> Void) {
withRetry(times: numberOfTimesToRetryFetchContractData) { [weak self] triggerRetry in
guard let strongSelf = self else { return }
strongSelf.getNameCoordinator.getName(for: address) { (result) in
switch result {
case .success:
completion(result)
case .failure:
if !triggerRetry() {
completion(result)
}
}
}
}
}
func getEthBalance(for address: AlphaWallet.Address, completion: @escaping (ResultResult<Balance, AnyError>.t) -> Void) {
getNativeCryptoCurrencyBalanceCoordinator.getBalance(for: address, completion: completion)
}
func getContractSymbol(for address: AlphaWallet.Address,
completion: @escaping (ResultResult<String, AnyError>.t) -> Void) {
withRetry(times: numberOfTimesToRetryFetchContractData) { [weak self] triggerRetry in
guard let strongSelf = self else { return }
strongSelf.getSymbolCoordinator.getSymbol(for: address) { result in
switch result {
case .success:
completion(result)
case .failure:
if !triggerRetry() {
completion(result)
}
}
}
}
}
func getDecimals(for address: AlphaWallet.Address,
completion: @escaping (ResultResult<UInt8, AnyError>.t) -> Void) {
withRetry(times: numberOfTimesToRetryFetchContractData) { [weak self] triggerRetry in
guard let strongSelf = self else { return }
strongSelf.getDecimalsCoordinator.getDecimals(for: address) { result in
switch result {
case .success:
completion(result)
case .failure:
if !triggerRetry() {
completion(result)
}
}
}
}
}
func getContractName(for address: AlphaWallet.Address) -> Promise<String> {
Promise { seal in
getContractName(for: address) { (result) in
switch result {
case .success(let name):
seal.fulfill(name)
case .failure(let error):
seal.reject(error)
}
}
}
}
func getContractSymbol(for address: AlphaWallet.Address) -> Promise<String> {
Promise { seal in
getContractSymbol(for: address) { result in
switch result {
case .success(let name):
seal.fulfill(name)
case .failure(let error):
seal.reject(error)
}
}
}
}
func getDecimals(for address: AlphaWallet.Address) -> Promise<UInt8> {
Promise { seal in
getDecimals(for: address) { result in
switch result {
case .success(let name):
seal.fulfill(name)
case .failure(let error):
seal.reject(error)
}
}
}
}
func getTokenType(for address: AlphaWallet.Address) -> Promise<TokenType> {
Promise { seal in
getTokenType(for: address) { tokenType in
seal.fulfill(tokenType)
}
}
}
func getERC20Balance(for address: AlphaWallet.Address, completion: @escaping (ResultResult<BigInt, AnyError>.t) -> Void) {
withRetry(times: numberOfTimesToRetryFetchContractData) { [weak self] triggerRetry in
guard let strongSelf = self else { return }
strongSelf.getERC20BalanceCoordinator.getBalance(for: strongSelf.account.address, contract: address) { result in
switch result {
case .success:
completion(result)
case .failure:
if !triggerRetry() {
completion(result)
}
}
}
}
}
func getERC875Balance(for address: AlphaWallet.Address,
completion: @escaping (ResultResult<[String], AnyError>.t) -> Void) {
withRetry(times: numberOfTimesToRetryFetchContractData) { [weak self] triggerRetry in
guard let strongSelf = self else { return }
strongSelf.getERC875BalanceCoordinator.getERC875TokenBalance(for: strongSelf.account.address, contract: address) { result in
switch result {
case .success:
completion(result)
case .failure:
if !triggerRetry() {
completion(result)
}
}
}
}
}
func getERC721ForTicketsBalance(for address: AlphaWallet.Address,
completion: @escaping (ResultResult<[String], AnyError>.t) -> Void) {
withRetry(times: numberOfTimesToRetryFetchContractData) { [weak self] triggerRetry in
guard let strongSelf = self else { return }
strongSelf.getERC721ForTicketsBalanceCoordinator.getERC721ForTicketsTokenBalance(for: strongSelf.account.address, contract: address) { result in
switch result {
case .success:
completion(result)
case .failure:
if !triggerRetry() {
completion(result)
}
}
}
}
}
//TODO should callers call tokenURI and so on, instead?
func getERC721Balance(for address: AlphaWallet.Address, completion: @escaping (ResultResult<[String], AnyError>.t) -> Void) {
withRetry(times: numberOfTimesToRetryFetchContractData) { [weak self] triggerRetry in
guard let strongSelf = self else { return }
strongSelf.getERC721BalanceCoordinator.getERC721TokenBalance(for: strongSelf.account.address, contract: address) { result in
switch result {
case .success(let balance):
if balance >= Int.max {
completion(.failure(AnyError(Web3Error(description: ""))))
} else {
completion(.success([String](repeating: "0", count: Int(balance))))
}
case .failure(let error):
if !triggerRetry() {
completion(.failure(error))
}
}
}
}
}
func getTokenType(for address: AlphaWallet.Address, completion: @escaping (TokenType) -> Void) {
let isErc875Promise = Promise<Bool> { seal in
withRetry(times: numberOfTimesToRetryFetchContractData) { [weak self] triggerRetry in
guard let strongSelf = self else { return }
//Function hash is "0x4f452b9a". This might cause many "execution reverted" RPC errors
//TODO rewrite flow so we reduce checks for this as it causes too many "execution reverted" RPC errors and looks scary when we look in Charles proxy. Maybe check for ERC20 (via EIP165) as well as ERC721 in parallel first, then fallback to this ERC875 check
strongSelf.getIsERC875ContractCoordinator.getIsERC875Contract(for: address) { [weak self] result in
guard self != nil else { return }
switch result {
case .success(let isERC875):
if isERC875 {
seal.fulfill(true)
} else {
seal.fulfill(false)
}
case .failure:
if !triggerRetry() {
seal.fulfill(false)
}
}
}
}
}
enum Erc721Type {
case erc721
case erc721ForTickets
case notErc721
}
let isErc721Promise = Promise<Erc721Type> { seal in
withRetry(times: numberOfTimesToRetryFetchContractData) { [weak self] triggerRetry in
guard let strongSelf = self else { return }
strongSelf.getIsERC721ContractCoordinator.getIsERC721Contract(for: address) { [weak self] result in
guard let strongSelf = self else { return }
switch result {
case .success(let isERC721):
if isERC721 {
withRetry(times: strongSelf.numberOfTimesToRetryFetchContractData) { [weak self] triggerRetry2 in
guard let strongSelf = self else { return }
strongSelf.getIsERC721ForTicketsContractCoordinator.getIsERC721ForTicketContract(for: address) { result in
switch result {
case .success(let isERC721ForTickets):
if isERC721ForTickets {
seal.fulfill(.erc721ForTickets)
} else {
seal.fulfill(.erc721)
}
case .failure:
if !triggerRetry2() {
seal.fulfill(.erc721)
}
}
}
}
} else {
seal.fulfill(.notErc721)
}
case .failure:
if !triggerRetry() {
seal.fulfill(.notErc721)
}
}
}
}
}
firstly {
isErc721Promise
}.done { isErc721 in
switch isErc721 {
case .erc721:
completion(.erc721)
case .erc721ForTickets:
completion(.erc721ForTickets)
case .notErc721:
break
}
}.cauterize()
firstly {
isErc875Promise
}.done { isErc875 in
if isErc875 {
completion(.erc875)
} else {
//no-op
}
}.cauterize()
firstly {
when(fulfilled: isErc875Promise.asVoid(), isErc721Promise.asVoid())
}.done { _, _ in
if isErc875Promise.value == false && isErc721Promise.value == .notErc721 {
completion(.erc20)
} else {
//no-op
}
}.cauterize()
}
}

@ -103,10 +103,11 @@ extension TransactionInstance {
let result = LocalizedOperationObjectInstance(from: transaction.from, to: recipient.eip55String, contract: contract, type: operationType.rawValue, value: String(value), tokenId: "", symbol: token.symbol, name: token.name, decimals: token.decimals)
return .value([result])
} else {
let getContractName = tokensStorage.getContractName(for: contract)
let getContractSymbol = tokensStorage.getContractSymbol(for: contract)
let getDecimals = tokensStorage.getDecimals(for: contract)
let getTokenType = tokensStorage.getTokenType(for: contract)
let tokenProvider: TokenProviderType = TokenProvider(account: tokensStorage.account, server: tokensStorage.server)
let getContractName = tokenProvider.getContractName(for: contract)
let getContractSymbol = tokenProvider.getContractSymbol(for: contract)
let getDecimals = tokenProvider.getDecimals(for: contract)
let getTokenType = tokenProvider.getTokenType(for: contract)
return firstly {
when(fulfilled: getContractName, getContractSymbol, getDecimals, getTokenType)

@ -509,6 +509,7 @@ class UniversalLinkCoordinator: Coordinator {
}
let tokensDatastore = strongSelf.tokensDatastore
let tokenProvider: TokenProviderType = TokenProvider(account: tokensDatastore.account, server: tokensDatastore.server)
if let existingToken = tokensDatastore.token(forContract: contractAddress) {
let name = XMLHandler(token: existingToken, assetDefinitionStore: strongSelf.assetDefinitionStore).getLabel(fallback: existingToken.name)
makeTokenHolder(name: name, symbol: existingToken.symbol)
@ -516,9 +517,9 @@ class UniversalLinkCoordinator: Coordinator {
let localizedTokenTypeName = R.string.localizable.tokensTitlecase()
makeTokenHolder(name: localizedTokenTypeName, symbol: "")
let getContractName = tokensDatastore.getContractName(for: contractAddress)
let getContractSymbol = tokensDatastore.getContractSymbol(for: contractAddress)
let getTokenType = tokensDatastore.getTokenType(for: contractAddress)
let getContractName = tokenProvider.getContractName(for: contractAddress)
let getContractSymbol = tokenProvider.getContractSymbol(for: contractAddress)
let getTokenType = tokenProvider.getTokenType(for: contractAddress)
firstly {
when(fulfilled: getContractName, getContractSymbol, getTokenType)
}.done { name, symbol, type in

@ -205,8 +205,9 @@ class SingleChainTokenCoordinator: Coordinator {
completion()
return
}
let tokenProvider: TokenProviderType = TokenProvider(account: storage.account, server: storage.server)
for each in contracts {
storage.getTokenType(for: each) { tokenType in
tokenProvider.getTokenType(for: each) { tokenType in
switch tokenType {
case .erc875:
//TODO long and very similar code below. Extract function
@ -358,7 +359,7 @@ class SingleChainTokenCoordinator: Coordinator {
}
func fetchContractData(for address: AlphaWallet.Address, completion: @escaping (ContractData) -> Void) {
ContractDataDetector(address: address, storage: storage, assetDefinitionStore: assetDefinitionStore).fetch(completion: completion)
ContractDataDetector(address: address, account: session.account, server: session.server, assetDefinitionStore: assetDefinitionStore).fetch(completion: completion)
}
func showTokenList(for type: PaymentFlow, token: TokenObject, navigationController: UINavigationController) {
@ -706,4 +707,4 @@ extension SingleChainTokenCoordinator: TransactionInProgressCoordinatorDelegate
func transactionInProgressDidDismiss(in coordinator: TransactionInProgressCoordinator) {
removeCoordinator(coordinator)
}
}
}

@ -17,7 +17,7 @@ enum ContractData {
class ContractDataDetector {
private let address: AlphaWallet.Address
private let storage: TokensDataStore
private let tokenProvider: TokenProviderType
private let assetDefinitionStore: AssetDefinitionStore
private let namePromise: Promise<String>
private let symbolPromise: Promise<String>
@ -27,13 +27,22 @@ class ContractDataDetector {
private var failed = false
private var completion: ((ContractData) -> Void)?
init(address: AlphaWallet.Address, storage: TokensDataStore, assetDefinitionStore: AssetDefinitionStore) {
init(address: AlphaWallet.Address, account: Wallet, server: RPCServer, assetDefinitionStore: AssetDefinitionStore) {
self.address = address
self.storage = storage
self.tokenProvider = TokenProvider(account: account, server: server)
self.assetDefinitionStore = assetDefinitionStore
namePromise = storage.getContractName(for: address)
symbolPromise = storage.getContractSymbol(for: address)
tokenTypePromise = storage.getTokenType(for: address)
namePromise = tokenProvider.getContractName(for: address)
symbolPromise = tokenProvider.getContractSymbol(for: address)
tokenTypePromise = tokenProvider.getTokenType(for: address)
}
init(address: AlphaWallet.Address, tokenProvider: TokenProviderType, assetDefinitionStore: AssetDefinitionStore) {
self.address = address
self.tokenProvider = tokenProvider
self.assetDefinitionStore = assetDefinitionStore
namePromise = tokenProvider.getContractName(for: address)
symbolPromise = tokenProvider.getContractSymbol(for: address)
tokenTypePromise = tokenProvider.getTokenType(for: address)
}
/// Failure to obtain contract data may be due to no-connectivity. So we should check .failed(networkReachable: Bool)
@ -68,7 +77,7 @@ class ContractDataDetector {
}.done { tokenType in
switch tokenType {
case .erc875:
self.storage.getERC875Balance(for: self.address) { result in
self.tokenProvider.getERC875Balance(for: self.address) { result in
switch result {
case .success(let balance):
self.nonFungibleBalanceSeal.fulfill(balance)
@ -79,7 +88,7 @@ class ContractDataDetector {
}
}
case .erc721:
self.storage.getERC721Balance(for: self.address) { result in
self.tokenProvider.getERC721Balance(for: self.address) { result in
switch result {
case .success(let balance):
self.nonFungibleBalanceSeal.fulfill(balance)
@ -90,7 +99,7 @@ class ContractDataDetector {
}
}
case .erc721ForTickets:
self.storage.getERC721ForTicketsBalance(for: self.address) { result in
self.tokenProvider.getERC721ForTicketsBalance(for: self.address) { result in
switch result {
case .success(let balance):
self.nonFungibleBalanceSeal.fulfill(balance)
@ -101,7 +110,7 @@ class ContractDataDetector {
}
}
case .erc20:
self.storage.getDecimals(for: self.address) { result in
self.tokenProvider.getDecimals(for: self.address) { result in
switch result {
case .success(let decimal):
self.decimalsSeal.fulfill(decimal)
@ -168,4 +177,4 @@ class ContractDataDetector {
}
}
}
}
}

@ -29,51 +29,10 @@ class TokensDataStore {
return SessionManager(configuration: configuration)
}()
private lazy var getNameCoordinator: GetNameCoordinator = {
return GetNameCoordinator(forServer: server)
private lazy var tokenProvider: TokenProviderType = {
return TokenProvider(account: account, server: server)
}()
private lazy var getSymbolCoordinator: GetSymbolCoordinator = {
return GetSymbolCoordinator(forServer: server)
}()
private lazy var getNativeCryptoCurrencyBalanceCoordinator: GetNativeCryptoCurrencyBalanceCoordinator = {
return GetNativeCryptoCurrencyBalanceCoordinator(forServer: server)
}()
private lazy var getERC20BalanceCoordinator: GetERC20BalanceCoordinator = {
return GetERC20BalanceCoordinator(forServer: server)
}()
private lazy var getERC875BalanceCoordinator: GetERC875BalanceCoordinator = {
return GetERC875BalanceCoordinator(forServer: server)
}()
private lazy var getERC721ForTicketsBalanceCoordinator: GetERC721ForTicketsBalanceCoordinator = {
return GetERC721ForTicketsBalanceCoordinator(forServer: server)
}()
private lazy var getIsERC875ContractCoordinator: GetIsERC875ContractCoordinator = {
return GetIsERC875ContractCoordinator(forServer: server)
}()
private lazy var getERC721BalanceCoordinator: GetERC721BalanceCoordinator = {
return GetERC721BalanceCoordinator(forServer: server)
}()
private lazy var getIsERC721ForTicketsContractCoordinator: GetIsERC721ForTicketsContractCoordinator = {
return GetIsERC721ForTicketsContractCoordinator(forServer: server)
}()
private lazy var getIsERC721ContractCoordinator: GetIsERC721ContractCoordinator = {
return GetIsERC721ContractCoordinator(forServer: server)
}()
private lazy var getDecimalsCoordinator: GetDecimalsCoordinator = {
return GetDecimalsCoordinator(forServer: server)
}()
private let account: Wallet
private let assetDefinitionStore: AssetDefinitionStore
private let realm: Realm
private var pricesTimer = Timer()
@ -91,7 +50,9 @@ class TokensDataStore {
private let openSea: OpenSea
private let queue = DispatchQueue.global()
let account: Wallet
let server: RPCServer
weak var delegate: TokensDataStoreDelegate?
weak var priceDelegate: TokensDataStorePriceDelegate?
weak var erc721TokenIdsFetcher: Erc721TokenIdsFetcher?
@ -220,279 +181,11 @@ class TokensDataStore {
refreshBalance()
}
func getContractName(for address: AlphaWallet.Address,
completion: @escaping (ResultResult<String, AnyError>.t) -> Void) {
withRetry(times: numberOfTimesToRetryFetchContractData) { [weak self] triggerRetry in
guard let strongSelf = self else { return }
strongSelf.getNameCoordinator.getName(for: address) { (result) in
switch result {
case .success:
completion(result)
case .failure:
if !triggerRetry() {
completion(result)
}
}
}
}
}
func getContractSymbol(for address: AlphaWallet.Address,
completion: @escaping (ResultResult<String, AnyError>.t) -> Void) {
withRetry(times: numberOfTimesToRetryFetchContractData) { [weak self] triggerRetry in
guard let strongSelf = self else { return }
strongSelf.getSymbolCoordinator.getSymbol(for: address) { result in
switch result {
case .success:
completion(result)
case .failure:
if !triggerRetry() {
completion(result)
}
}
}
}
}
func getDecimals(for address: AlphaWallet.Address,
completion: @escaping (ResultResult<UInt8, AnyError>.t) -> Void) {
withRetry(times: numberOfTimesToRetryFetchContractData) { [weak self] triggerRetry in
guard let strongSelf = self else { return }
strongSelf.getDecimalsCoordinator.getDecimals(for: address) { result in
switch result {
case .success:
completion(result)
case .failure:
if !triggerRetry() {
completion(result)
}
}
}
}
}
func getContractName(for address: AlphaWallet.Address) -> Promise<String> {
Promise { seal in
getContractName(for: address) { (result) in
switch result {
case .success(let name):
seal.fulfill(name)
case .failure(let error):
seal.reject(error)
}
}
}
}
func getContractSymbol(for address: AlphaWallet.Address) -> Promise<String> {
Promise { seal in
getContractSymbol(for: address) { result in
switch result {
case .success(let name):
seal.fulfill(name)
case .failure(let error):
seal.reject(error)
}
}
}
}
func getDecimals(for address: AlphaWallet.Address) -> Promise<UInt8> {
Promise { seal in
getDecimals(for: address) { result in
switch result {
case .success(let name):
seal.fulfill(name)
case .failure(let error):
seal.reject(error)
}
}
}
}
func getTokenType(for address: AlphaWallet.Address) -> Promise<TokenType> {
Promise { seal in
getTokenType(for: address) { tokenType in
seal.fulfill(tokenType)
}
}
}
func getERC20Balance(for address: AlphaWallet.Address, completion: @escaping (ResultResult<BigInt, AnyError>.t) -> Void) {
withRetry(times: numberOfTimesToRetryFetchContractData) { [weak self] triggerRetry in
guard let strongSelf = self else { return }
strongSelf.getERC20BalanceCoordinator.getBalance(for: strongSelf.account.address, contract: address) { result in
switch result {
case .success:
completion(result)
case .failure:
if !triggerRetry() {
completion(result)
}
}
}
}
}
func getERC875Balance(for address: AlphaWallet.Address,
completion: @escaping (ResultResult<[String], AnyError>.t) -> Void) {
withRetry(times: numberOfTimesToRetryFetchContractData) { [weak self] triggerRetry in
guard let strongSelf = self else { return }
strongSelf.getERC875BalanceCoordinator.getERC875TokenBalance(for: strongSelf.account.address, contract: address) { result in
switch result {
case .success:
completion(result)
case .failure:
if !triggerRetry() {
completion(result)
}
}
}
}
}
func getERC721ForTicketsBalance(for address: AlphaWallet.Address,
completion: @escaping (ResultResult<[String], AnyError>.t) -> Void) {
withRetry(times: numberOfTimesToRetryFetchContractData) { [weak self] triggerRetry in
guard let strongSelf = self else { return }
strongSelf.getERC721ForTicketsBalanceCoordinator.getERC721ForTicketsTokenBalance(for: strongSelf.account.address, contract: address) { result in
switch result {
case .success:
completion(result)
case .failure:
if !triggerRetry() {
completion(result)
}
}
}
}
}
//TODO should callers call tokenURI and so on, instead?
func getERC721Balance(for address: AlphaWallet.Address, completion: @escaping (ResultResult<[String], AnyError>.t) -> Void) {
withRetry(times: numberOfTimesToRetryFetchContractData) { [weak self] triggerRetry in
guard let strongSelf = self else { return }
strongSelf.getERC721BalanceCoordinator.getERC721TokenBalance(for: strongSelf.account.address, contract: address) { result in
switch result {
case .success(let balance):
if balance >= Int.max {
completion(.failure(AnyError(Web3Error(description: ""))))
} else {
completion(.success([String](repeating: "0", count: Int(balance))))
}
case .failure(let error):
if !triggerRetry() {
completion(.failure(error))
}
}
}
}
}
private func getTokensFromOpenSea() -> OpenSea.PromiseResult {
//TODO when we no longer create multiple instances of TokensDataStore, we don't have to use singleton for OpenSea class. This was to avoid fetching multiple times from OpenSea concurrently
return openSea.makeFetchPromise(forOwner: account.address)
}
func getTokenType(for address: AlphaWallet.Address, completion: @escaping (TokenType) -> Void) {
let isErc875Promise = Promise<Bool> { seal in
withRetry(times: numberOfTimesToRetryFetchContractData) { [weak self] triggerRetry in
guard let strongSelf = self else { return }
//Function hash is "0x4f452b9a". This might cause many "execution reverted" RPC errors
//TODO rewrite flow so we reduce checks for this as it causes too many "execution reverted" RPC errors and looks scary when we look in Charles proxy. Maybe check for ERC20 (via EIP165) as well as ERC721 in parallel first, then fallback to this ERC875 check
strongSelf.getIsERC875ContractCoordinator.getIsERC875Contract(for: address) { [weak self] result in
guard self != nil else { return }
switch result {
case .success(let isERC875):
if isERC875 {
seal.fulfill(true)
} else {
seal.fulfill(false)
}
case .failure:
if !triggerRetry() {
seal.fulfill(false)
}
}
}
}
}
enum Erc721Type {
case erc721
case erc721ForTickets
case notErc721
}
let isErc721Promise = Promise<Erc721Type> { seal in
withRetry(times: numberOfTimesToRetryFetchContractData) { [weak self] triggerRetry in
guard let strongSelf = self else { return }
strongSelf.getIsERC721ContractCoordinator.getIsERC721Contract(for: address) { [weak self] result in
guard let strongSelf = self else { return }
switch result {
case .success(let isERC721):
if isERC721 {
withRetry(times: strongSelf.numberOfTimesToRetryFetchContractData) { [weak self] triggerRetry2 in
guard let strongSelf = self else { return }
strongSelf.getIsERC721ForTicketsContractCoordinator.getIsERC721ForTicketContract(for: address) { result in
switch result {
case .success(let isERC721ForTickets):
if isERC721ForTickets {
seal.fulfill(.erc721ForTickets)
} else {
seal.fulfill(.erc721)
}
case .failure:
if !triggerRetry2() {
seal.fulfill(.erc721)
}
}
}
}
} else {
seal.fulfill(.notErc721)
}
case .failure:
if !triggerRetry() {
seal.fulfill(.notErc721)
}
}
}
}
}
firstly {
isErc721Promise
}.done { isErc721 in
switch isErc721 {
case .erc721:
completion(.erc721)
case .erc721ForTickets:
completion(.erc721ForTickets)
case .notErc721:
break
}
}.cauterize()
firstly {
isErc875Promise
}.done { isErc875 in
if isErc875 {
completion(.erc875)
} else {
//no-op
}
}.cauterize()
firstly {
when(fulfilled: isErc875Promise.asVoid(), isErc721Promise.asVoid())
}.done { _, _ in
if isErc875Promise.value == false && isErc721Promise.value == .notErc721 {
completion(.erc20)
} else {
//no-op
}
}.cauterize()
}
func tokenThreadSafe(forContract contract: AlphaWallet.Address) -> TokenObject? {
realm.threadSafe.objects(TokenObject.self)
.filter("contract = '\(contract.eip55String)'")
@ -540,7 +233,7 @@ class TokensDataStore {
case .nativeCryptocurrency:
completion()
case .erc20:
getERC20Balance(for: tokenObject.contractAddress, completion: { [weak self] result in
tokenProvider.getERC20Balance(for: tokenObject.contractAddress, completion: { [weak self] result in
defer { completion() }
guard let strongSelf = self else { return }
switch result {
@ -551,7 +244,7 @@ class TokensDataStore {
}
})
case .erc875:
getERC875Balance(for: tokenObject.contractAddress, completion: { [weak self] result in
tokenProvider.getERC875Balance(for: tokenObject.contractAddress, completion: { [weak self] result in
defer { completion() }
guard let strongSelf = self else { return }
switch result {
@ -564,7 +257,7 @@ class TokensDataStore {
case .erc721:
break
case .erc721ForTickets:
getERC721ForTicketsBalance(for: tokenObject.contractAddress, completion: { [weak self] result in
tokenProvider.getERC721ForTicketsBalance(for: tokenObject.contractAddress, completion: { [weak self] result in
defer { completion() }
guard let strongSelf = self else { return }
switch result {
@ -710,7 +403,7 @@ class TokensDataStore {
}
func refreshETHBalance() {
getNativeCryptoCurrencyBalanceCoordinator.getBalance(for: account.address) { [weak self] result in
tokenProvider.getEthBalance(for: account.address) { [weak self] result in
guard let strongSelf = self else { return }
switch result {
case .success(let balance):

@ -30,6 +30,7 @@ class SingleChainTransactionEtherscanDataCoordinator: SingleChainTransactionData
private var isFetchingLatestTransactions = false
var coordinators: [Coordinator] = []
weak var delegate: SingleChainTransactionDataCoordinatorDelegate?
private lazy var tokenProvider: TokenProviderType = TokenProvider(account: session.account, server: session.server)
required init(
session: WalletSession,
@ -181,7 +182,7 @@ class SingleChainTransactionEtherscanDataCoordinator: SingleChainTransactionData
//The fetch ERC20 transactions endpoint from Etherscan returns only ERC20 token transactions but the Blockscout version also includes ERC721 transactions too (so it's likely other types that it can detect will be returned too); thus we check the token type rather than assume that they are all ERC20
let contracts = Array(Set(filteredTransactions.compactMap { $0.localizedOperations.first?.contractAddress }))
let tokenTypePromises = contracts.map { self.tokensStorage.getTokenType(for: $0) }
let tokenTypePromises = contracts.map { self.tokenProvider.getTokenType(for: $0) }
when(fulfilled: tokenTypePromises).map { tokenTypes in
let contractsToTokenTypes = Dictionary(uniqueKeysWithValues: zip(contracts, tokenTypes))
@ -492,4 +493,4 @@ extension SingleChainTransactionEtherscanDataCoordinator.functional {
return results
}
}
}
}

@ -135,7 +135,7 @@ extension SendCoordinator: SendViewControllerDelegate {
}
func lookup(contract: AlphaWallet.Address, in viewController: SendViewController, completion: @escaping (ContractData) -> Void) {
ContractDataDetector(address: contract, storage: storage, assetDefinitionStore: assetDefinitionStore).fetch(completion: completion)
ContractDataDetector(address: contract, account: session.account, server: session.server, assetDefinitionStore: assetDefinitionStore).fetch(completion: completion)
}
}

Loading…
Cancel
Save