diff --git a/Adamant.xcodeproj/project.pbxproj b/Adamant.xcodeproj/project.pbxproj index 2b7a02368..6ff5739da 100644 --- a/Adamant.xcodeproj/project.pbxproj +++ b/Adamant.xcodeproj/project.pbxproj @@ -296,6 +296,7 @@ 93C7944E2B077C1F00408826 /* DashSendRawTransactionDTO.swift in Sources */ = {isa = PBXBuildFile; fileRef = 93C7944D2B077C1F00408826 /* DashSendRawTransactionDTO.swift */; }; 93CC8DC7296F00D6003772BF /* ChatTransactionContainerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 93CC8DC6296F00D6003772BF /* ChatTransactionContainerView.swift */; }; 93CC8DC9296F01DE003772BF /* ChatTransactionContainerView+Model.swift in Sources */ = {isa = PBXBuildFile; fileRef = 93CC8DC8296F01DE003772BF /* ChatTransactionContainerView+Model.swift */; }; + 93CC94C12B17EE73004842AC /* EthApiCore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 93CC94C02B17EE73004842AC /* EthApiCore.swift */; }; 93CCAE752B06CC3600EA5B94 /* LskNodeApiService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 93CCAE742B06CC3600EA5B94 /* LskNodeApiService.swift */; }; 93CCAE772B06D6CC00EA5B94 /* LskServiceApiService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 93CCAE762B06D6CC00EA5B94 /* LskServiceApiService.swift */; }; 93CCAE792B06D81D00EA5B94 /* DogeApiService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 93CCAE782B06D81D00EA5B94 /* DogeApiService.swift */; }; @@ -920,6 +921,7 @@ 93C7944D2B077C1F00408826 /* DashSendRawTransactionDTO.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DashSendRawTransactionDTO.swift; sourceTree = ""; }; 93CC8DC6296F00D6003772BF /* ChatTransactionContainerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatTransactionContainerView.swift; sourceTree = ""; }; 93CC8DC8296F01DE003772BF /* ChatTransactionContainerView+Model.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ChatTransactionContainerView+Model.swift"; sourceTree = ""; }; + 93CC94C02B17EE73004842AC /* EthApiCore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EthApiCore.swift; sourceTree = ""; }; 93CCAE742B06CC3600EA5B94 /* LskNodeApiService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LskNodeApiService.swift; sourceTree = ""; }; 93CCAE762B06D6CC00EA5B94 /* LskServiceApiService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LskServiceApiService.swift; sourceTree = ""; }; 93CCAE782B06D81D00EA5B94 /* DogeApiService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DogeApiService.swift; sourceTree = ""; }; @@ -2175,6 +2177,7 @@ E993302121354BC300CD5200 /* EthWalletFactory.swift */, E940087A2114ED0600CD2D67 /* EthWalletService.swift */, 93FC169C2B019F440062B507 /* EthApiService.swift */, + 93CC94C02B17EE73004842AC /* EthApiCore.swift */, 4186B333294200C5006594A3 /* EthWalletService+DynamicConstants.swift */, E9AA8BF9212C166600F9249F /* EthWalletService+Send.swift */, E9FEECA52143C300007DD7C8 /* EthWalletService+RichMessageProvider.swift */, @@ -3025,6 +3028,7 @@ E9E7CD8B20026B0600DFC4DB /* AccountService.swift in Sources */, 41E3C9CC2A0E20F500AF0985 /* AdamantCoinTools.swift in Sources */, 93ADE0712ACA66AF008ED641 /* VibrationSelectionViewModel.swift in Sources */, + 93CC94C12B17EE73004842AC /* EthApiCore.swift in Sources */, 93FC169D2B019F440062B507 /* EthApiService.swift in Sources */, 9371130F2996EDA900F64CF9 /* ChatRefreshMock.swift in Sources */, 93547BCA29E2262D00B0914B /* WelcomeViewController.swift in Sources */, diff --git a/Adamant/Modules/Wallets/Ethereum/EthApiCore.swift b/Adamant/Modules/Wallets/Ethereum/EthApiCore.swift new file mode 100644 index 000000000..aa59f1e7e --- /dev/null +++ b/Adamant/Modules/Wallets/Ethereum/EthApiCore.swift @@ -0,0 +1,99 @@ +// +// EthApiCore.swift +// Adamant +// +// Created by Andrew G on 30.11.2023. +// Copyright © 2023 Adamant. All rights reserved. +// + +import CommonKit +import Foundation +import web3swift +import Web3Core + +actor EthApiCore { + let apiCore: APICoreProtocol + private(set) var keystoreManager: KeystoreManager? + private var web3Cache: [URL: Web3] = .init() + + func performRequest( + node: Node, + _ body: @escaping @Sendable (_ web3: Web3) async throws -> Success + ) async -> WalletServiceResult { + switch await getWeb3(node: node) { + case let .success(web3): + do { + return .success(try await body(web3)) + } catch { + return .failure(mapError(error)) + } + case let .failure(error): + return .failure(error) + } + } + + func setKeystoreManager(_ keystoreManager: KeystoreManager) { + self.keystoreManager = keystoreManager + web3Cache = .init() + } + + init(apiCore: APICoreProtocol) { + self.apiCore = apiCore + } +} + +extension EthApiCore: BlockchainHealthCheckableService { + func getStatusInfo(node: Node) async -> WalletServiceResult { + await performRequest(node: node) { web3 in + let startTimestamp = Date.now.timeIntervalSince1970 + let height = try await web3.eth.blockNumber() + let ping = Date.now.timeIntervalSince1970 - startTimestamp + + return .init( + ping: ping, + height: Int(height.asDouble()), + wsEnabled: false, + wsPort: nil, + version: nil + ) + } + } +} + +private extension EthApiCore { + func getWeb3(node: Node) async -> WalletServiceResult { + guard let url = node.asURL() else { + return .failure(.internalError(.endpointBuildFailed)) + } + + if let web3 = web3Cache[url] { + return .success(web3) + } + + do { + let web3 = try await Web3.new(url) + web3.addKeystoreManager(keystoreManager) + web3Cache[url] = web3 + return .success(web3) + } catch { + return .failure(.internalError( + message: error.localizedDescription, + error: error + )) + } + } +} + +private func mapError(_ error: Error) -> WalletServiceError { + if let error = error as? Web3Error { + return error.asWalletServiceError() + } else if let error = error as? ApiServiceError { + return error.asWalletServiceError() + } else if let error = error as? WalletServiceError { + return error + } else if let _ = error as? URLError { + return .networkError + } else { + return .remoteServiceError(message: error.localizedDescription) + } +} diff --git a/Adamant/Modules/Wallets/Ethereum/EthApiService.swift b/Adamant/Modules/Wallets/Ethereum/EthApiService.swift index 867059ec7..701bad3d2 100644 --- a/Adamant/Modules/Wallets/Ethereum/EthApiService.swift +++ b/Adamant/Modules/Wallets/Ethereum/EthApiService.swift @@ -11,63 +11,6 @@ import Foundation import web3swift import Web3Core -@MainActor -final class EthApiCore: BlockchainHealthCheckableService { - let apiCore: APICoreProtocol - private(set) var keystoreManager: KeystoreManager? - - func makeWeb3(node: Node) async -> WalletServiceResult { - do { - guard let url = node.asURL() else { throw InternalAPIError.endpointBuildFailed } - let web3 = try await Web3.new(url) - web3.addKeystoreManager(keystoreManager) - return .success(web3) - } catch { - return .failure(.internalError(message: error.localizedDescription, error: error)) - } - } - - func performRequest( - node: Node, - _ body: @escaping @Sendable (_ web3: Web3) async throws -> Success - ) async -> WalletServiceResult { - switch await makeWeb3(node: node) { - case let .success(web3): - do { - return .success(try await body(web3)) - } catch { - return .failure(mapError(error)) - } - case let .failure(error): - return .failure(error) - } - } - - func getStatusInfo(node: Node) async -> WalletServiceResult { - await performRequest(node: node) { web3 in - let startTimestamp = Date.now.timeIntervalSince1970 - let height = try await web3.eth.blockNumber() - let ping = Date.now.timeIntervalSince1970 - startTimestamp - - return .init( - ping: ping, - height: Int(height.asDouble()), - wsEnabled: false, - wsPort: nil, - version: nil - ) - } - } - - func setKeystoreManager(_ keystoreManager: KeystoreManager) { - self.keystoreManager = keystoreManager - } - - nonisolated init(apiCore: APICoreProtocol) { - self.apiCore = apiCore - } -} - class EthApiService: WalletApiService { let api: BlockchainHealthCheckWrapper @@ -113,17 +56,3 @@ class EthApiService: WalletApiService { await api.service.setKeystoreManager(keystoreManager) } } - -private func mapError(_ error: Error) -> WalletServiceError { - if let error = error as? Web3Error { - return error.asWalletServiceError() - } else if let error = error as? ApiServiceError { - return error.asWalletServiceError() - } else if let error = error as? WalletServiceError { - return error - } else if let _ = error as? URLError { - return .networkError - } else { - return .remoteServiceError(message: error.localizedDescription) - } -} diff --git a/Adamant/Modules/Wallets/Ethereum/EthWalletService+RichMessageProviderWithStatusCheck.swift b/Adamant/Modules/Wallets/Ethereum/EthWalletService+RichMessageProviderWithStatusCheck.swift index f0871929b..64c08e907 100644 --- a/Adamant/Modules/Wallets/Ethereum/EthWalletService+RichMessageProviderWithStatusCheck.swift +++ b/Adamant/Modules/Wallets/Ethereum/EthWalletService+RichMessageProviderWithStatusCheck.swift @@ -71,7 +71,7 @@ private extension EthWalletService { func getTransactionInfo(hash: String, web3: Web3) async throws -> EthTransactionInfo { try await withThrowingTaskGroup( of: EthTransactionInfoElement.self, - returning: EthTransactionInfo.self + returning: Atomic.self ) { group in group.addTask(priority: .userInitiated) { .details(try await web3.eth.transactionDetails(hash)) @@ -81,15 +81,17 @@ private extension EthWalletService { .receipt(try await web3.eth.transactionReceipt(hash)) } - return try await group.reduce(into: .init()) { result, value in + return try await group.reduce( + into: .init(wrappedValue: .init()) + ) { result, value in switch value { case let .receipt(receipt): - result.receipt = receipt + result.wrappedValue.receipt = receipt case let .details(details): - result.details = details + result.wrappedValue.details = details } } - } + }.wrappedValue } func getStatus(