diff --git a/UnstoppableWallet/UnstoppableWallet.xcodeproj/project.pbxproj b/UnstoppableWallet/UnstoppableWallet.xcodeproj/project.pbxproj index b49e47eafb..b7ad642b65 100644 --- a/UnstoppableWallet/UnstoppableWallet.xcodeproj/project.pbxproj +++ b/UnstoppableWallet/UnstoppableWallet.xcodeproj/project.pbxproj @@ -9152,7 +9152,6 @@ 3A73FC9C258B1AF700FE4D34 /* MarketWatchlistViewController.swift in Sources */, 11B35B086B0D62A9D7A10CD0 /* AddTokenViewModel.swift in Sources */, 11B35A42BF19B93C6005FBD9 /* AddTokenService.swift in Sources */, - D0D90ADD2B1DA7DF0047C320 /* ScriptType.swift in Sources */, 11B35D550563934444558D15 /* AddTokenViewController.swift in Sources */, 58AAABE760739C0ECC6CA720 /* DownloadService.swift in Sources */, 11B3502E7AA00ACFE8EE8CD9 /* FormTextView.swift in Sources */, @@ -10819,7 +10818,6 @@ 11B35EF70120304F6D4F5561 /* MarketMultiSortHeaderView.swift in Sources */, D05E969A2A26278D002CCD71 /* TronApproveTransactionRecord.swift in Sources */, 2FA5D40FA7CAC1BA01C0B373 /* CoinOverviewViewModel.swift in Sources */, - D0D90ADC2B1DA7DF0047C320 /* ScriptType.swift in Sources */, 2FA5DBE42827FF3D114DBF4B /* CoinOverviewService.swift in Sources */, 2FA5DF4BB73C12C8DDF42599 /* CoinOverviewModule.swift in Sources */, D05E969D2A2627AF002CCD71 /* TronContractCallTransactionRecord.swift in Sources */, diff --git a/UnstoppableWallet/UnstoppableWallet/Core/Address/AddressService.swift b/UnstoppableWallet/UnstoppableWallet/Core/Address/AddressService.swift index 45c6cf689b..0f8755d42e 100644 --- a/UnstoppableWallet/UnstoppableWallet/Core/Address/AddressService.swift +++ b/UnstoppableWallet/UnstoppableWallet/Core/Address/AddressService.swift @@ -167,25 +167,23 @@ extension AddressService { } func handleFetched(text: String) -> String { - let result = addressUriParser.parse(addressUri: text.trimmingCharacters(in: .whitespaces)) - switch result { - case .invalidBlockchainType: - showUriErrorRelay.accept(UriError.invalidBlockchainType) - return "" - case .invalidTokenType: - showUriErrorRelay.accept(UriError.invalidTokenType) - return "" - case .noUri, .wrongUri: - let text = text.trimmingCharacters(in: .whitespacesAndNewlines) - set(text: text) - return text - case let .uri(data): - - if let amount = data.amount { + do { + let result = try addressUriParser.parse(url: text.trimmingCharacters(in: .whitespaces)) + if let amount = result.amount { publishAmountRelay.accept(amount) } - set(text: data.address) - return data.address + set(text: result.address) + return result.address + } catch { + switch error { + case AddressUriParser.ParseError.noUri, AddressUriParser.ParseError.wrongUri: + let text = text.trimmingCharacters(in: .whitespacesAndNewlines) + set(text: text) + return text + default: + showUriErrorRelay.accept(error) + return "" + } } } diff --git a/UnstoppableWallet/UnstoppableWallet/Core/Address/AddressUriParser.swift b/UnstoppableWallet/UnstoppableWallet/Core/Address/AddressUriParser.swift index f1709b5d3b..0c11ee0c95 100644 --- a/UnstoppableWallet/UnstoppableWallet/Core/Address/AddressUriParser.swift +++ b/UnstoppableWallet/UnstoppableWallet/Core/Address/AddressUriParser.swift @@ -15,6 +15,38 @@ class AddressUriParser { return [prefix, s2].compactMap { $0 }.joined(separator: ":") } + private func parameters(from queryItems: [URLQueryItem]) -> (handled: [AddressUri.Field: String], unhandled: [String: String]) { + var handled = [AddressUri.Field: String]() + var unhandled = [String: String]() + + for item in queryItems { + guard let value = item.value else { continue } + if let field = AddressUri.Field(rawValue: item.name) { + handled[field] = value + } else { + unhandled[item.name] = value + } + } + + return (handled: handled, unhandled: unhandled) + } + + private func validate(parameters: [AddressUri.Field: String]) throws { + if let blockchainType, + let uid: String = parameters[.blockchainUid], + blockchainType != BlockchainType(uid: uid) + { + throw ParseError.invalidBlockchainType + } + + if let uid: String = parameters[.tokenUid], + let tokenType, + tokenType != TokenType(id: uid) + { + throw ParseError.invalidTokenType + } + } + private func fullAddress(scheme: String, address: String, uriBlockchainUid: String? = nil) -> String { // there is no explicit indication of the blockchain in the uri. We use the rules of the blockchain parser guard let uriBlockchainUid else { @@ -34,45 +66,54 @@ class AddressUriParser { return pair(BlockchainType(uid: uriBlockchainUid), address) } - func parse(addressUri: String) -> Result { - guard let components = URLComponents(string: addressUri), let scheme = components.scheme else { - return .noUri - } - if let validScheme = blockchainType?.uriScheme, components.scheme != validScheme { - return .invalidBlockchainType + private func handleDeepLink(url: String) -> AddressUri? { + guard let components = URLComponents(string: url), let scheme = components.scheme else { + return nil } - var uri = AddressUri(scheme: scheme) - guard let parameters = components.queryItems else { - uri.address = fullAddress(scheme: scheme, address: components.path) - return .uri(uri) - } + // try to parse ton deeplink + if scheme == DeepLinkManager.tonDeepLinkScheme, let tonScheme = BlockchainType.ton.uriScheme { + var uri = AddressUri(scheme: tonScheme) + uri.address = components.path.stripping(prefix: "/") - for parameter in parameters { - guard let value = parameter.value else { continue } - if let field = AddressUri.Field(rawValue: parameter.name) { - uri.parameters[field] = parameter.value - } else { - uri.unhandledParameters[parameter.name] = value + var params = parameters(from: components.queryItems ?? []) + if let amount = params.handled[.amount] { + params.handled[.amount] = TonAdapter.amount(kitAmount: amount).description } + params.handled[.blockchainUid] = BlockchainType.ton.uid + + uri.parameters = params.handled + uri.unhandledParameters = params.unhandled + + return uri } - if let uid: String = uri.value(field: .blockchainUid), - let blockchainType, - blockchainType != BlockchainType(uid: uid) - { - return .invalidBlockchainType + return nil + } + + func handleAddressUri(uri: String) throws -> AddressUri { + guard let components = URLComponents(string: uri), let scheme = components.scheme else { + throw ParseError.noUri } - if let uid: String = uri.value(field: .tokenUid), - let tokenType, - tokenType != TokenType(id: uid) - { - return .invalidTokenType + if let validScheme = blockchainType?.uriScheme, components.scheme != validScheme { + throw ParseError.invalidBlockchainType } + var uri = AddressUri(scheme: scheme) + guard let items = components.queryItems else { + uri.address = fullAddress(scheme: scheme, address: components.path) + return uri + } + + let params = parameters(from: items) + try validate(parameters: params.handled) + + uri.parameters = params.handled + uri.unhandledParameters = params.unhandled uri.address = fullAddress(scheme: scheme, address: components.path, uriBlockchainUid: uri.parameters[.blockchainUid]) - return .uri(uri) + + return uri } static func hasUriPrefix(text: String) -> Bool { @@ -81,6 +122,15 @@ class AddressUriParser { } extension AddressUriParser { + func parse(url: String) throws -> AddressUri { + // check if we try to parse deeplink address (like ton://transfer/
) + if let addressUri = handleDeepLink(url: url) { + return addressUri + } + + return try handleAddressUri(uri: url) + } + func uri(_ addressUri: AddressUri) -> String { var components = URLComponents() components.scheme = blockchainType?.uriScheme @@ -100,12 +150,11 @@ extension AddressUriParser { } extension AddressUriParser { - enum Result { + enum ParseError: Error { case wrongUri case invalidBlockchainType case invalidTokenType case noUri - case uri(AddressUri) } } diff --git a/UnstoppableWallet/UnstoppableWallet/Core/Managers/DeepLinkManager.swift b/UnstoppableWallet/UnstoppableWallet/Core/Managers/DeepLinkManager.swift index a412541e59..d50a9b2015 100644 --- a/UnstoppableWallet/UnstoppableWallet/Core/Managers/DeepLinkManager.swift +++ b/UnstoppableWallet/UnstoppableWallet/Core/Managers/DeepLinkManager.swift @@ -1,8 +1,11 @@ +import ComponentKit import Foundation import RxRelay import RxSwift class DeepLinkManager { + static let tonDeepLinkScheme = "ton" + private let newSchemeRelay = BehaviorRelay