From b2ce5f941e4f0017bd184e432523ae78ca778d6b Mon Sep 17 00:00:00 2001 From: Vladimir Klevtsov Date: Tue, 13 May 2025 05:55:09 +0200 Subject: [PATCH 1/3] [trello.com/c/iFPfEBu8] remove double apiService.getAccount(byPassphrase: passphrase) --- .../Modules/Login/LoginViewController.swift | 38 ++++++++++--------- 1 file changed, 20 insertions(+), 18 deletions(-) diff --git a/Adamant/Modules/Login/LoginViewController.swift b/Adamant/Modules/Login/LoginViewController.swift index 4e89526d0..fe61b3df5 100644 --- a/Adamant/Modules/Login/LoginViewController.swift +++ b/Adamant/Modules/Login/LoginViewController.swift @@ -496,13 +496,9 @@ extension LoginViewController { dialogService.showProgress(withMessage: String.adamant.login.loggingInProgressMessage, userInteractionEnable: false) Task { - let result = await apiService.getAccount(byPassphrase: passphrase) + let result = await loginIntoExistingAccount(passphrase: passphrase) - switch result { - case .success: - loginIntoExistingAccount(passphrase: passphrase) - - case .failure(let error): + if case .failure(let error) = result { dialogService.showRichError(error: error) } } @@ -527,28 +523,34 @@ extension LoginViewController { } @MainActor - private func loginIntoExistingAccount(passphrase: String) { - Task { - do { - let result = try await accountService.loginWith(passphrase: passphrase, password: .empty) + private func loginIntoExistingAccount(passphrase: String) async -> ApiServiceResult { + do { + let loginResult = try await accountService.loginWith(passphrase: passphrase, password: .empty) + dialogService.dismissProgress() + + switch loginResult { + case let .success(account, alert): if let nav = navigationController { nav.popViewController(animated: true) } else { dismiss(animated: true, completion: nil) } - dialogService.dismissProgress() - - if case .success(_, let alert) = result, - let alert = alert - { + if let alert { dialogService.showAlert(title: alert.title, message: alert.message, style: UIAlertController.Style.alert, actions: nil, from: nil) } - } catch { - dialogService.dismissProgress() - dialogService.showRichError(error: error) + + return .success(account) + + case let .failure(accountError): + return .failure(.internalError(message: accountError.localizedDescription, error: accountError)) } + + } catch { + dialogService.dismissProgress() + dialogService.showRichError(error: error) + return .failure(.internalError(message: error.localizedDescription, error: error)) } } } From a1bd6a2de39e936567f75a8ea6c64170a7e228ea Mon Sep 17 00:00:00 2001 From: Vladimir Klevtsov Date: Fri, 30 May 2025 04:44:32 +0200 Subject: [PATCH 2/3] [trello.com/c/iFPfEBu8] add local address generate --- .../Sources/CommonKit/Core/NativeAdamantCore.swift | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/CommonKit/Sources/CommonKit/Core/NativeAdamantCore.swift b/CommonKit/Sources/CommonKit/Core/NativeAdamantCore.swift index 1fc1b1816..7cb8b32ec 100644 --- a/CommonKit/Sources/CommonKit/Core/NativeAdamantCore.swift +++ b/CommonKit/Sources/CommonKit/Core/NativeAdamantCore.swift @@ -8,6 +8,7 @@ import CryptoSwift import Foundation +import BigInt /* * Native Adamanat Core @@ -210,6 +211,19 @@ public final class NativeAdamantCore: AdamantCore { return seed.sha256() } + + // MARK: Address + public func getAddressFromPublicKey(_ publicKeyHex: String) -> String? { + let publicKey = Data(hex: publicKeyHex) + let hash = publicKey.sha256() + + let reversedBytes = (0..<8).map { i in + hash[7 - i] + } + let number = BigUInt(Data(reversedBytes)) + + return "U" + number.description + } public init() {} } From 2dd0e319f8220c808d7f093034f652ab29b7fe5c Mon Sep 17 00:00:00 2001 From: Vladimir Klevtsov Date: Fri, 30 May 2025 06:01:51 +0200 Subject: [PATCH 3/3] [trello.com/c/iFPfEBu8] first step for faster login --- .../Login/LoginViewController+Pinpad.swift | 52 ++++------ .../Modules/Login/LoginViewController.swift | 57 +++-------- Adamant/ServiceProtocols/AccountService.swift | 6 +- Adamant/Services/AdamantAccountService.swift | 98 ++++++++----------- .../CommonKit/Core/NativeAdamantCore.swift | 2 +- .../CommonKit/Protocols/AdamantCore.swift | 2 + 6 files changed, 82 insertions(+), 135 deletions(-) diff --git a/Adamant/Modules/Login/LoginViewController+Pinpad.swift b/Adamant/Modules/Login/LoginViewController+Pinpad.swift index 3bec4339d..202b2ecc8 100644 --- a/Adamant/Modules/Login/LoginViewController+Pinpad.swift +++ b/Adamant/Modules/Login/LoginViewController+Pinpad.swift @@ -68,8 +68,16 @@ extension LoginViewController { Task { do { - let result = try await accountService.loginWithStoredAccount() - handleSavedAccountLoginResult(result) + try await accountService.loginWithStoredAccount() + dialogService.dismissProgress() + + guard let presenter = presentingViewController else { + return + } + + presenter.dismiss(animated: true, completion: nil) + + presentUpdateV12AlertIfNeeded(presenter: presenter) } catch { dialogService.showRichError(error: error) @@ -80,36 +88,16 @@ extension LoginViewController { } } - private func handleSavedAccountLoginResult(_ result: AccountServiceResult) { - switch result { - case .success(_, let alert): - dialogService.dismissProgress() - - let alertVc: UIAlertController? - if let alert = alert { - alertVc = UIAlertController(title: alert.title, message: alert.message, preferredStyleSafe: .alert, source: nil) - alertVc!.addAction(UIAlertAction(title: String.adamant.alert.ok, style: .default)) - } else { - alertVc = nil - } - - guard let presenter = presentingViewController else { - return - } - presenter.dismiss(animated: true, completion: nil) - - if let alertVc = alertVc { - alertVc.modalPresentationStyle = .overFullScreen - presenter.present(alertVc, animated: true, completion: nil) - } - - case .failure(let error): - dialogService.showRichError(error: error) - - if let pinpad = presentedViewController as? PinpadViewController { - pinpad.clearPin() - } - } + private func presentUpdateV12AlertIfNeeded(presenter: UIViewController) { + let alertVc = UIAlertController( + title: String.adamant.accountService.updateAlertTitleV12, + message: String.adamant.accountService.updateAlertMessageV12, + preferredStyleSafe: .alert, + source: nil + ) + alertVc.addAction(UIAlertAction(title: String.adamant.alert.ok, style: .default)) + alertVc.modalPresentationStyle = .overFullScreen + presenter.present(alertVc, animated: true, completion: nil) } } diff --git a/Adamant/Modules/Login/LoginViewController.swift b/Adamant/Modules/Login/LoginViewController.swift index fe61b3df5..d37ddefe5 100644 --- a/Adamant/Modules/Login/LoginViewController.swift +++ b/Adamant/Modules/Login/LoginViewController.swift @@ -492,66 +492,39 @@ extension LoginViewController { dialogService.showWarning(withMessage: AccountServiceError.invalidPassphrase.localized) return } - - dialogService.showProgress(withMessage: String.adamant.login.loggingInProgressMessage, userInteractionEnable: false) - + Task { - let result = await loginIntoExistingAccount(passphrase: passphrase) - - if case .failure(let error) = result { - dialogService.showRichError(error: error) - } + await loginIntoExistingAccount(passphrase: passphrase) } } - + func generateNewPassphrase() { let passphrase = (try? Mnemonic.generate().joined(separator: " ")) ?? .empty - + hideNewPassphrase = false - + form.rowBy(tag: Rows.saveYourPassphraseAlert.tag)?.evaluateHidden() - + if let row: PassphraseRow = form.rowBy(tag: Rows.newPassphrase.tag) { row.value = passphrase row.updateCell() row.evaluateHidden() } - + if let row = form.rowBy(tag: Rows.generateNewPassphraseButton.tag), let indexPath = row.indexPath { tableView.scrollToRow(at: indexPath, at: .none, animated: true) } } - + @MainActor - private func loginIntoExistingAccount(passphrase: String) async -> ApiServiceResult { - do { - let loginResult = try await accountService.loginWith(passphrase: passphrase, password: .empty) - - dialogService.dismissProgress() - - switch loginResult { - case let .success(account, alert): - if let nav = navigationController { - nav.popViewController(animated: true) - } else { - dismiss(animated: true, completion: nil) - } - - if let alert { - dialogService.showAlert(title: alert.title, message: alert.message, style: UIAlertController.Style.alert, actions: nil, from: nil) - } - - return .success(account) - - case let .failure(accountError): - return .failure(.internalError(message: accountError.localizedDescription, error: accountError)) + private func loginIntoExistingAccount(passphrase: String) async { + try? await accountService.loginWith(passphrase: passphrase, password: .empty) + + if let nav = navigationController { + nav.popViewController(animated: true) + } else { + dismiss(animated: true, completion: nil) } - - } catch { - dialogService.dismissProgress() - dialogService.showRichError(error: error) - return .failure(.internalError(message: error.localizedDescription, error: error)) - } } } diff --git a/Adamant/ServiceProtocols/AccountService.swift b/Adamant/ServiceProtocols/AccountService.swift index 87ecb8086..e52bbfa9a 100644 --- a/Adamant/ServiceProtocols/AccountService.swift +++ b/Adamant/ServiceProtocols/AccountService.swift @@ -161,16 +161,16 @@ protocol AccountService: AnyObject, Sendable { func update(_ completion: (@Sendable (AccountServiceResult) -> Void)?) /// Login into Adamant using passphrase. - func loginWith(passphrase: String, password: String) async throws -> AccountServiceResult + func loginWith(passphrase: String, password: String) async throws /// Login into Adamant using previously logged account - func loginWithStoredAccount() async throws -> AccountServiceResult + func loginWithStoredAccount() async throws /// Logout @MainActor func logout() async /// Reload current wallets state - func reloadWallets() async + //func reloadWallets() async // MARK: Stay in functions diff --git a/Adamant/Services/AdamantAccountService.swift b/Adamant/Services/AdamantAccountService.swift index 40d8d03ae..f1475f7e7 100644 --- a/Adamant/Services/AdamantAccountService.swift +++ b/Adamant/Services/AdamantAccountService.swift @@ -80,7 +80,7 @@ final class AdamantAccountService: AccountService, @unchecked Sendable { }.store(in: &subscriptions) setupSecureStore() - } + } } // MARK: - Saved data @@ -296,7 +296,7 @@ extension AdamantAccountService { extension AdamantAccountService { // MARK: Passphrase @MainActor - func loginWith(passphrase: String, password: String) async throws -> AccountServiceResult { + func loginWith(passphrase: String, password: String) async throws { guard AdamantUtilities.validateAdamantPassphrase(passphrase: passphrase) else { throw AccountServiceError.invalidPassphrase } @@ -305,7 +305,7 @@ extension AdamantAccountService { throw AccountServiceError.internalError(message: "Failed to generate keypair for passphrase", error: nil) } - let account = try await loginWith(keypair: keypair) + try await loginWith(keypair: keypair) // MARK: Drop saved accs if let storedPassphrase = self.getSavedPassphrase(), @@ -320,16 +320,11 @@ extension AdamantAccountService { dropSavedAccount() } - // Update and initiate wallet services self.passphrase = passphrase - - _ = await initWallets() - - return .success(account: account, alert: nil) } // MARK: Pincode - func loginWith(pincode: String) async throws -> AccountServiceResult { + func loginWith(pincode: String) async throws { guard let storePin = SecureStore.get(.pin) else { throw AccountServiceError.invalidPassphrase } @@ -338,12 +333,12 @@ extension AdamantAccountService { throw AccountServiceError.invalidPassphrase } - return try await loginWithStoredAccount() + try await loginWithStoredAccount() } // MARK: Biometry @MainActor - func loginWithStoredAccount() async throws -> AccountServiceResult { + func loginWithStoredAccount() async throws { if let passphrase = getSavedPassphrase() { let account = try await loginWith(passphrase: passphrase, password: .empty) return account @@ -367,68 +362,59 @@ extension AdamantAccountService { wallet.core.setInitiationFailed(reason: .adamant.accountService.reloginToInitiateWallets) } - return .success(account: account, alert: alert) } throw AccountServiceError.invalidPassphrase } // MARK: Keypair - private func loginWith(keypair: Keypair) async throws -> AdamantAccount { + private func loginWith(keypair: Keypair) async throws { switch state { - case .isLoggingIn: - throw AccountServiceError.internalError(message: "Service is busy", error: nil) - - // Logout first - case .updating, .loggedIn: - await logout() - - // Go login - case .notLogged: - break + case .isLoggingIn: + throw AccountServiceError.internalError(message: "Service is busy", error: nil) + + // Logout first + case .updating, .loggedIn: + await logout() + + // Go login + case .notLogged: + break } state = .isLoggingIn + let address = adamantCore.getAddressFromPublicKey(keypair.publicKey) + self.keypair = keypair + //self.address = address + + let userInfo = [AdamantUserInfoKey.AccountService.loggedAccountAddress: address] + NotificationCenter.default.post( + name: Notification.Name.AdamantAccountService.userLoggedIn, + object: self, + userInfo: userInfo + ) + + self.state = .loggedIn do { - let account = try await apiService.getAccount(byPublicKey: keypair.publicKey).get() - self.account = account - self.keypair = keypair - markBalanceAsFresh() - - let userInfo = [AdamantUserInfoKey.AccountService.loggedAccountAddress: account.address] - - NotificationCenter.default.post( - name: Notification.Name.AdamantAccountService.userLoggedIn, - object: self, - userInfo: userInfo - ) - - self.state = .loggedIn - return account - } catch let error as ApiServiceError { - self.state = .notLogged - - switch error { - case .accountNotFound: - throw AccountServiceError.wrongPassphrase - - default: - throw AccountServiceError.apiError(error: error) + Task.detached(priority: .medium) { [weak self] in + guard let self else { return } + do { + let fetchedAccount = try await self.apiService.getAccount(byPublicKey: keypair.publicKey).get() + self.account = fetchedAccount + markBalanceAsFresh() + await initWallets() + } catch { + throw AccountServiceError.internalError(message: "Failed to fetch account in background", error: error) + } } - } catch { - throw AccountServiceError.internalError(message: error.localizedDescription, error: error) } } - func reloadWallets() async { - _ = await initWallets() - } - - func initWallets() async -> [WalletAccount?] { + func initWallets() async { guard let passphrase = passphrase else { print("No passphrase found") - return [] + return } return await withTaskGroup(of: WalletAccount?.self) { group in @@ -447,8 +433,6 @@ extension AdamantAccountService { for await wallet in group { wallets.append(wallet) } - - return wallets } } } diff --git a/CommonKit/Sources/CommonKit/Core/NativeAdamantCore.swift b/CommonKit/Sources/CommonKit/Core/NativeAdamantCore.swift index 7cb8b32ec..ca103963b 100644 --- a/CommonKit/Sources/CommonKit/Core/NativeAdamantCore.swift +++ b/CommonKit/Sources/CommonKit/Core/NativeAdamantCore.swift @@ -213,7 +213,7 @@ public final class NativeAdamantCore: AdamantCore { } // MARK: Address - public func getAddressFromPublicKey(_ publicKeyHex: String) -> String? { + public func getAddressFromPublicKey(_ publicKeyHex: String) -> String { let publicKey = Data(hex: publicKeyHex) let hash = publicKey.sha256() diff --git a/CommonKit/Sources/CommonKit/Protocols/AdamantCore.swift b/CommonKit/Sources/CommonKit/Protocols/AdamantCore.swift index e98c053a2..6711ef9fd 100644 --- a/CommonKit/Sources/CommonKit/Protocols/AdamantCore.swift +++ b/CommonKit/Sources/CommonKit/Protocols/AdamantCore.swift @@ -35,6 +35,8 @@ public protocol AdamantCore: AnyObject, Sendable { senderPublicKey senderKeyHex: String, privateKey privateKeyHex: String ) -> Data? + + func getAddressFromPublicKey(_ publicKeyHex: String) -> String } public protocol SignableTransaction {