diff --git a/Telegram-iOS/en.lproj/Localizable.strings b/Telegram-iOS/en.lproj/Localizable.strings index a5e7d7ae17..95c92c2f98 100644 --- a/Telegram-iOS/en.lproj/Localizable.strings +++ b/Telegram-iOS/en.lproj/Localizable.strings @@ -4879,3 +4879,4 @@ Any member of this group will be able to see messages in the channel."; "Wallet.Send.ErrorInvalidAddress" = "Invalid wallet address. Please correct and try again."; "Wallet.Send.ErrorDecryptionFailed" = "Encryption error. Please check device passcode settings and try again."; +"Wallet.Send.SendAnyway" = "Send Anyway"; diff --git a/submodules/TelegramCore/TelegramCore/Wallets.swift b/submodules/TelegramCore/TelegramCore/Wallets.swift index 7cbec491b3..f8ecbecc92 100644 --- a/submodules/TelegramCore/TelegramCore/Wallets.swift +++ b/submodules/TelegramCore/TelegramCore/Wallets.swift @@ -292,7 +292,7 @@ public final class TonInstance { } } - fileprivate func sendGramsFromWallet(keychain: TonKeychain, serverSalt: Data, walletInfo: WalletInfo, fromAddress: String, toAddress: String, amount: Int64, textMessage: String, randomId: Int64) -> Signal { + fileprivate func sendGramsFromWallet(keychain: TonKeychain, serverSalt: Data, walletInfo: WalletInfo, fromAddress: String, toAddress: String, amount: Int64, textMessage: String, forceIfDestinationNotInitialized: Bool, timeout: Int32, randomId: Int64) -> Signal { return keychain.decrypt(walletInfo.encryptedSecret) |> mapError { _ -> SendGramsFromWalletError in return .secretDecryptionFailed @@ -304,7 +304,7 @@ public final class TonInstance { self.impl.with { impl in impl.withInstance { ton in - let cancel = ton.sendGrams(from: key, localPassword: serverSalt, fromAddress: fromAddress, toAddress: toAddress, amount: amount, textMessage: textMessage, forceIfDestinationNotInitialized: true, timeout: 0, randomId: randomId).start(next: { result in + let cancel = ton.sendGrams(from: key, localPassword: serverSalt, fromAddress: fromAddress, toAddress: toAddress, amount: amount, textMessage: textMessage, forceIfDestinationNotInitialized: forceIfDestinationNotInitialized, timeout: timeout, randomId: randomId).start(next: { result in guard let result = result as? TONSendGramsResult else { subscriber.putError(.generic) return @@ -314,6 +314,8 @@ public final class TonInstance { if let error = error as? TONError { if error.text == "Failed to parse account address" { subscriber.putError(.invalidAddress) + } else if error.text.hasPrefix("DANGEROUS_TRANSACTION") { + subscriber.putError(.destinationIsNotInitialized) } else { subscriber.putError(.generic) } @@ -699,9 +701,10 @@ public enum SendGramsFromWalletError { case generic case secretDecryptionFailed case invalidAddress + case destinationIsNotInitialized } -public func sendGramsFromWallet(network: Network, tonInstance: TonInstance, keychain: TonKeychain, walletInfo: WalletInfo, toAddress: String, amount: Int64, textMessage: String, randomId: Int64) -> Signal { +public func sendGramsFromWallet(network: Network, tonInstance: TonInstance, keychain: TonKeychain, walletInfo: WalletInfo, toAddress: String, amount: Int64, textMessage: String, forceIfDestinationNotInitialized: Bool, timeout: Int32, randomId: Int64) -> Signal { return getServerWalletSalt(network: network) |> mapError { _ -> SendGramsFromWalletError in return .generic @@ -710,7 +713,7 @@ public func sendGramsFromWallet(network: Network, tonInstance: TonInstance, keyc return walletAddress(publicKey: walletInfo.publicKey, tonInstance: tonInstance) |> castError(SendGramsFromWalletError.self) |> mapToSignal { fromAddress in - return tonInstance.sendGramsFromWallet(keychain: keychain, serverSalt: serverSalt, walletInfo: walletInfo, fromAddress: fromAddress, toAddress: toAddress, amount: amount, textMessage: textMessage, randomId: randomId) + return tonInstance.sendGramsFromWallet(keychain: keychain, serverSalt: serverSalt, walletInfo: walletInfo, fromAddress: fromAddress, toAddress: toAddress, amount: amount, textMessage: textMessage, forceIfDestinationNotInitialized: forceIfDestinationNotInitialized, timeout: timeout, randomId: randomId) } } } diff --git a/submodules/WalletUI/BUCK b/submodules/WalletUI/BUCK index 383583f827..5df0b63dba 100644 --- a/submodules/WalletUI/BUCK +++ b/submodules/WalletUI/BUCK @@ -28,6 +28,7 @@ static_library( "//submodules/TelegramStringFormatting:TelegramStringFormatting", "//submodules/GlassButtonNode:GlassButtonNode", "//submodules/UrlHandling:UrlHandling", + "//submodules/LocalAuth:LocalAuth", ], frameworks = [ "$SDKROOT/System/Library/Frameworks/Foundation.framework", diff --git a/submodules/WalletUI/Sources/WalletSplashScreen.swift b/submodules/WalletUI/Sources/WalletSplashScreen.swift index 77dd752291..6bd9f3fc3e 100644 --- a/submodules/WalletUI/Sources/WalletSplashScreen.swift +++ b/submodules/WalletUI/Sources/WalletSplashScreen.swift @@ -88,50 +88,7 @@ public final class WalletSplashScreen: ViewController { self.navigationItem.setRightBarButton(UIBarButtonItem(title: self.presentationData.strings.Wallet_Intro_ImportExisting, style: .plain, target: self, action: #selector(self.importPressed)), animated: false) case let .sending(walletInfo, address, amount, textMessage, randomId): self.navigationItem.setLeftBarButton(UIBarButtonItem(customDisplayNode: ASDisplayNode())!, animated: false) - - let _ = (sendGramsFromWallet(network: self.context.account.network, tonInstance: self.tonContext.instance, keychain: self.tonContext.keychain, walletInfo: walletInfo, toAddress: address, amount: amount, textMessage: textMessage, randomId: randomId) - |> deliverOnMainQueue).start(error: { [weak self] error in - guard let strongSelf = self else { - return - } - let text: String - switch error { - case .generic: - text = strongSelf.presentationData.strings.Login_UnknownError - case .invalidAddress: - text = strongSelf.presentationData.strings.Wallet_Send_ErrorInvalidAddress - case .secretDecryptionFailed: - text = strongSelf.presentationData.strings.Wallet_Send_ErrorDecryptionFailed - } - let controller = textAlertController(context: strongSelf.context, title: nil, text: text, actions: [TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: { - if let navigationController = strongSelf.navigationController as? NavigationController { - navigationController.popViewController(animated: true) - } - })]) - strongSelf.present(controller, in: .window(.root)) - }, completed: { [weak self] in - guard let strongSelf = self else { - return - } - if let navigationController = strongSelf.navigationController as? NavigationController { - var controllers = navigationController.viewControllers - controllers = controllers.filter { controller in - if controller is WalletSplashScreen { - return false - } - if controller is WalletSendScreen { - return false - } - if controller is WalletInfoScreen { - return false - } - return true - } - controllers.append(WalletSplashScreen(context: strongSelf.context, tonContext: strongSelf.tonContext, mode: .sent(walletInfo, amount), walletCreatedPreloadState: strongSelf.walletCreatedPreloadState)) - strongSelf.view.endEditing(true) - navigationController.setViewControllers(controllers, animated: true) - } - }) + self.sendGrams(walletInfo: walletInfo, address: address, amount: amount, textMessage: textMessage, forceIfDestinationNotInitialized: false, randomId: randomId) case .sent, .created: self.navigationItem.setLeftBarButton(UIBarButtonItem(customDisplayNode: ASDisplayNode())!, animated: false) case .restoreFailed, .secureStorageNotAvailable, .secureStorageReset: @@ -155,6 +112,70 @@ public final class WalletSplashScreen: ViewController { self.push(WalletWordCheckScreen(context: self.context, tonContext: self.tonContext, mode: .import, walletCreatedPreloadState: nil)) } + private func sendGrams(walletInfo: WalletInfo, address: String, amount: Int64, textMessage: String, forceIfDestinationNotInitialized: Bool, randomId: Int64) { + let _ = (sendGramsFromWallet(network: self.context.account.network, tonInstance: self.tonContext.instance, keychain: self.tonContext.keychain, walletInfo: walletInfo, toAddress: address, amount: amount, textMessage: textMessage, forceIfDestinationNotInitialized: forceIfDestinationNotInitialized, timeout: 0, randomId: randomId) + |> deliverOnMainQueue).start(error: { [weak self] error in + guard let strongSelf = self else { + return + } + let text: String + switch error { + case .generic: + text = strongSelf.presentationData.strings.Login_UnknownError + case .invalidAddress: + text = strongSelf.presentationData.strings.Wallet_Send_ErrorInvalidAddress + case .secretDecryptionFailed: + text = strongSelf.presentationData.strings.Wallet_Send_ErrorDecryptionFailed + case .destinationIsNotInitialized: + if !forceIfDestinationNotInitialized { + text = "This address belongs to an empty wallet. Are you sure you want to transfer grams to it?" + let controller = textAlertController(context: strongSelf.context, title: "Warning", text: text, actions: [ + TextAlertAction(type: .genericAction, title: strongSelf.presentationData.strings.Common_Cancel, action: { + if let navigationController = strongSelf.navigationController as? NavigationController { + navigationController.popViewController(animated: true) + } + }), + TextAlertAction(type: .defaultAction, title: "Send Anyway", action: { + self?.sendGrams(walletInfo: walletInfo, address: address, amount: amount, textMessage: textMessage, forceIfDestinationNotInitialized: true, randomId: randomId) + }) + ]) + strongSelf.present(controller, in: .window(.root)) + return + } else { + text = strongSelf.presentationData.strings.Login_UnknownError + } + } + let controller = textAlertController(context: strongSelf.context, title: nil, text: text, actions: [TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: { + if let navigationController = strongSelf.navigationController as? NavigationController { + navigationController.popViewController(animated: true) + } + })]) + strongSelf.present(controller, in: .window(.root)) + }, completed: { [weak self] in + guard let strongSelf = self else { + return + } + if let navigationController = strongSelf.navigationController as? NavigationController { + var controllers = navigationController.viewControllers + controllers = controllers.filter { controller in + if controller is WalletSplashScreen { + return false + } + if controller is WalletSendScreen { + return false + } + if controller is WalletInfoScreen { + return false + } + return true + } + controllers.append(WalletSplashScreen(context: strongSelf.context, tonContext: strongSelf.tonContext, mode: .sent(walletInfo, amount), walletCreatedPreloadState: strongSelf.walletCreatedPreloadState)) + strongSelf.view.endEditing(true) + navigationController.setViewControllers(controllers, animated: true) + } + }) + } + override public func loadDisplayNode() { self.displayNode = WalletSplashScreenNode(account: self.context.account, presentationData: self.presentationData, mode: self.mode, action: { [weak self] in guard let strongSelf = self else { diff --git a/submodules/WalletUI/Sources/WalletWordCheckScreen.swift b/submodules/WalletUI/Sources/WalletWordCheckScreen.swift index ccad6ba5a5..578aae6cf1 100644 --- a/submodules/WalletUI/Sources/WalletWordCheckScreen.swift +++ b/submodules/WalletUI/Sources/WalletWordCheckScreen.swift @@ -2175,6 +2175,7 @@ public final class WalletWordCheckScreen: ViewController { TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: { }) ], actionLayout: .vertical), in: .window(.root)) + return } let _ = (importWallet(postbox: strongSelf.context.account.postbox, network: strongSelf.context.account.network, tonInstance: strongSelf.tonContext.instance, keychain: strongSelf.tonContext.keychain, wordList: enteredWords) |> deliverOnMainQueue).start(next: { walletInfo in @@ -2719,15 +2720,15 @@ private final class WalletWordCheckScreenNode: ViewControllerTracingNode, UIScro } } focused = { [weak self] node in - guard let strongSelf = self else { - return - } - if node.isLast { - UIView.animate(withDuration: 0.3, animations: { - strongSelf.scrollNode.view.scrollRectToVisible(strongSelf.buttonNode.frame.insetBy(dx: 0.0, dy: -10.0), animated: false) - }) - } else { - strongSelf.scrollNode.view.scrollRectToVisible(node.frame.insetBy(dx: 0.0, dy: -10.0), animated: true) + DispatchQueue.main.async { + guard let strongSelf = self else { + return + } + if node.isLast { + strongSelf.scrollNode.view.scrollRectToVisible(strongSelf.buttonNode.frame.insetBy(dx: 0.0, dy: -10.0), animated: true) + } else { + strongSelf.scrollNode.view.scrollRectToVisible(node.frame.insetBy(dx: 0.0, dy: -10.0), animated: true) + } } } pasteWords = { [weak self] wordList in