From 74fe726b2fba0baf519b7de438d19b105635fd64 Mon Sep 17 00:00:00 2001 From: Vladyslav shepitko Date: Wed, 4 Nov 2020 10:34:13 +0200 Subject: [PATCH] Add support for .crypto (UnstoppableDomains) domain lookup #1931 --- AlphaWallet.xcodeproj/project.pbxproj | 12 ++ AlphaWallet/Core/DomainResolver.swift | 117 ++++++++++++++++++ .../Core/Views/AddressOrEnsNameLabel.swift | 24 ++-- .../Coordinators/GetENSOwnerCoordinator.swift | 2 +- Podfile | 3 +- Podfile.lock | 24 +++- 6 files changed, 168 insertions(+), 14 deletions(-) create mode 100644 AlphaWallet/Core/DomainResolver.swift diff --git a/AlphaWallet.xcodeproj/project.pbxproj b/AlphaWallet.xcodeproj/project.pbxproj index e65b06cda..216ef7de8 100644 --- a/AlphaWallet.xcodeproj/project.pbxproj +++ b/AlphaWallet.xcodeproj/project.pbxproj @@ -677,6 +677,7 @@ 871E684B24E574F100F220A7 /* ActivityIndicatorControl.swift in Sources */ = {isa = PBXBuildFile; fileRef = 871E684824E574F000F220A7 /* ActivityIndicatorControl.swift */; }; 8733474E24ED008A002D649D /* TransactionConfirmationRowInfoView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8733474D24ED008A002D649D /* TransactionConfirmationRowInfoView.swift */; }; 873F8063246E8E3E00EEE5EF /* SelectCurrencyButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 873F8062246E8E3E00EEE5EF /* SelectCurrencyButton.swift */; }; + 8743CB50255059780039E469 /* DomainResolver.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8743CB4F255059780039E469 /* DomainResolver.swift */; }; 874DED0C24C05E88006C8FCE /* TransactionConfirmationViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 874DED0A24C05E88006C8FCE /* TransactionConfirmationViewModel.swift */; }; 874DED1524C1BAFF006C8FCE /* SelectAssetCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 874DED1424C1BAFF006C8FCE /* SelectAssetCoordinator.swift */; }; 874DED1724C1BB0E006C8FCE /* SelectAssetViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 874DED1624C1BB0E006C8FCE /* SelectAssetViewController.swift */; }; @@ -1490,6 +1491,7 @@ 871E684824E574F000F220A7 /* ActivityIndicatorControl.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ActivityIndicatorControl.swift; sourceTree = ""; }; 8733474D24ED008A002D649D /* TransactionConfirmationRowInfoView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TransactionConfirmationRowInfoView.swift; sourceTree = ""; }; 873F8062246E8E3E00EEE5EF /* SelectCurrencyButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SelectCurrencyButton.swift; sourceTree = ""; }; + 8743CB4F255059780039E469 /* DomainResolver.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DomainResolver.swift; sourceTree = ""; }; 874DED0A24C05E88006C8FCE /* TransactionConfirmationViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TransactionConfirmationViewModel.swift; sourceTree = ""; }; 874DED1424C1BAFF006C8FCE /* SelectAssetCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SelectAssetCoordinator.swift; sourceTree = ""; }; 874DED1624C1BB0E006C8FCE /* SelectAssetViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SelectAssetViewController.swift; sourceTree = ""; }; @@ -2694,6 +2696,7 @@ 5E7C799E4784815CB0202820 /* Core.swift */, 5E7C7543079DF1C7CA998A2D /* Views */, 5E7C70526F00B835220DC0E2 /* Features.swift */, + 8743CB4F255059780039E469 /* DomainResolver.swift */, ); path = Core; sourceTree = ""; @@ -4090,8 +4093,11 @@ "${BUILT_PRODUCTS_DIR}/AWSSNS/AWSSNS.framework", "${BUILT_PRODUCTS_DIR}/Alamofire/Alamofire.framework", "${BUILT_PRODUCTS_DIR}/Alamofire-Synchronous/Alamofire_Synchronous.framework", + "${BUILT_PRODUCTS_DIR}/Base58Swift/Base58Swift.framework", "${BUILT_PRODUCTS_DIR}/BigInt/BigInt.framework", "${BUILT_PRODUCTS_DIR}/CryptoSwift/CryptoSwift.framework", + "${BUILT_PRODUCTS_DIR}/EthereumABI/EthereumABI.framework", + "${BUILT_PRODUCTS_DIR}/EthereumAddress/EthereumAddress.framework", "${BUILT_PRODUCTS_DIR}/Eureka/Eureka.framework", "${BUILT_PRODUCTS_DIR}/JSONRPCKit/JSONRPCKit.framework", "${BUILT_PRODUCTS_DIR}/JavaScriptKit/JavaScriptKit.framework", @@ -4119,6 +4125,7 @@ "${BUILT_PRODUCTS_DIR}/TrezorCrypto/TrezorCrypto.framework", "${BUILT_PRODUCTS_DIR}/TrustKeystore/TrustKeystore.framework", "${BUILT_PRODUCTS_DIR}/TrustWalletCore/WalletCore.framework", + "${BUILT_PRODUCTS_DIR}/UnstoppableDomainsResolution/UnstoppableDomainsResolution.framework", "${BUILT_PRODUCTS_DIR}/libsodium/libsodium.framework", "${BUILT_PRODUCTS_DIR}/secp256k1_ios/secp256k1_ios.framework", "${BUILT_PRODUCTS_DIR}/web3swift/web3swift.framework", @@ -4130,8 +4137,11 @@ "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/AWSSNS.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Alamofire.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Alamofire_Synchronous.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Base58Swift.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/BigInt.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/CryptoSwift.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/EthereumABI.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/EthereumAddress.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Eureka.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/JSONRPCKit.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/JavaScriptKit.framework", @@ -4159,6 +4169,7 @@ "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/TrezorCrypto.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/TrustKeystore.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/WalletCore.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/UnstoppableDomainsResolution.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/libsodium.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/secp256k1_ios.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/web3swift.framework", @@ -4431,6 +4442,7 @@ 293112121FC4F48400966EEA /* ServiceProvider.swift in Sources */, 2912CD2F1F6A83A100C6CBE3 /* ImportWalletViewController.swift in Sources */, 874DED1724C1BB0E006C8FCE /* SelectAssetViewController.swift in Sources */, + 8743CB50255059780039E469 /* DomainResolver.swift in Sources */, 2963B6AD1F981A96003063C1 /* TransactionAppearance.swift in Sources */, 29850D2B1F6B30FF00791A49 /* TransactionViewController.swift in Sources */, 296AF9AB1F7380920058AF78 /* GetTransactionCountRequest.swift in Sources */, diff --git a/AlphaWallet/Core/DomainResolver.swift b/AlphaWallet/Core/DomainResolver.swift new file mode 100644 index 000000000..d60fb771a --- /dev/null +++ b/AlphaWallet/Core/DomainResolver.swift @@ -0,0 +1,117 @@ +// +// DomainResolver.swift +// AlphaWallet +// +// Created by Vladyslav Shepitko on 02.11.2020. +// + +import Foundation +import UnstoppableDomainsResolution +import PromiseKit +import web3swift + +extension Resolution { + convenience init?(server: RPCServer) { + guard let networkName = server.unstoppableDomainLookupName else { return nil } + try? self.init(providerUrl: server.rpcURL.absoluteString, network: networkName) + } +} + +class DomainResolver { + + private struct ENSLookupKey: Hashable { + let name: String + let server: RPCServer + } + + private enum AnyError: Error { + case failureToResolve + case invalidAddress + case invalidInput + } + + private let server: RPCServer + private static var cache: [ENSLookupKey: AlphaWallet.Address] = [:] + private var resolution: Resolution? + private let ticker: String = "eth" //Not sure what `ticker` do we need to use here + + init(server: RPCServer) { + self.server = server + self.resolution = Resolution(server: server) + } + + func resolveAddress(_ input: String) -> Promise { + //if already an address, send back the address + if let value = AlphaWallet.Address(string: input) { + return .value(value) + } + + let node = input.lowercased().nameHash + if let value = cachedResult(forNode: node) { + return .value(value) + } + + guard let resolution = resolution else { return .init(error: AnyError.invalidAddress) } + + return Promise { seal in + resolution.addr(domain: input, ticker: self.ticker) { result in + switch result { + case .success(let value): + if let address = AlphaWallet.Address(string: value), CryptoAddressValidator.isValidAddress(value) { + self.cache(forNode: node, result: address) + + seal.fulfill(address) + } else { + seal.reject(AnyError.invalidAddress) + } + case .failure(let error): + seal.reject(error) + } + } + } + } + + private func cachedResult(forNode node: String) -> AlphaWallet.Address? { + return DomainResolver.cache[ENSLookupKey(name: node, server: server)] + } + + private func cache(forNode node: String, result: AlphaWallet.Address) { + DomainResolver.cache[ENSLookupKey(name: node, server: server)] = result + } +} + +extension GetENSAddressCoordinator { + + func getENSAddressFromResolverPromise(value: String) -> Promise { + + enum AnyError: Error { + case addressNotFound + } + + return Promise { seal in + GetENSAddressCoordinator(server: server).getENSAddressFromResolver(for: value) { result in + if let address = result.value, CryptoAddressValidator.isValidAddress(address.address) { + seal.fulfill(address) + } else { + seal.reject(AnyError.addressNotFound) + } + } + } + } +} + +fileprivate extension RPCServer { + //These strings need to match what is used by UnstoppableDomains's code (look up where it's used) + var unstoppableDomainLookupName: String? { + switch self { + case .main: return "mainnet" + case .kovan: return "kovan" + case .ropsten: return "ropsten" + case .rinkeby: return "rinkeby" + case .poa, .sokol, .classic, .callisto, .xDai, .goerli, .artis_sigma1, .artis_tau1, .binance_smart_chain, .binance_smart_chain_testnet: + return nil + case .custom: + return nil + } + } +} diff --git a/AlphaWallet/Core/Views/AddressOrEnsNameLabel.swift b/AlphaWallet/Core/Views/AddressOrEnsNameLabel.swift index ba554b278..e29ea6c63 100644 --- a/AlphaWallet/Core/Views/AddressOrEnsNameLabel.swift +++ b/AlphaWallet/Core/Views/AddressOrEnsNameLabel.swift @@ -6,6 +6,7 @@ // import UIKit +import PromiseKit class AddressOrEnsNameLabel: UILabel { @@ -103,10 +104,11 @@ class AddressOrEnsNameLabel: UILabel { func resolve(_ value: String, completion: @escaping ((AddressOrEnsResolution) -> Void)) { clear() - + + let server = serverToResolveEns if let address = AlphaWallet.Address(string: value) { inResolvingState = true - ENSReverseLookupCoordinator(server: serverToResolveEns).getENSNameFromResolver(forAddress: address) { [weak self] result in + ENSReverseLookupCoordinator(server: server).getENSNameFromResolver(forAddress: address) { [weak self] result in guard let strongSelf = self else { return } strongSelf.inResolvingState = false @@ -119,18 +121,20 @@ class AddressOrEnsNameLabel: UILabel { } else if value.contains(".") { inResolvingState = true - GetENSAddressCoordinator(server: serverToResolveEns).getENSAddressFromResolver(for: value) { [weak self] result in - guard let strongSelf = self else { return } - strongSelf.inResolvingState = false - - if let address = result.value, CryptoAddressValidator.isValidAddress(address.address) { - completion(.resolved(.address(AlphaWallet.Address(address: address)))) - } else { - completion(.resolved(.none)) + DomainResolver(server: server).resolveAddress(value).recover { _ -> Promise in + return GetENSAddressCoordinator(server: server).getENSAddressFromResolverPromise(value: value).map { address in + AlphaWallet.Address(address: address) } + }.done { address in + completion(.resolved(.address(address))) + }.catch { _ in + completion(.resolved(.none)) + }.finally { + self.inResolvingState = false } } else { completion(.invalidInput) } } } + diff --git a/AlphaWallet/Tokens/Coordinators/GetENSOwnerCoordinator.swift b/AlphaWallet/Tokens/Coordinators/GetENSOwnerCoordinator.swift index 4874e9d4a..ff232ef94 100644 --- a/AlphaWallet/Tokens/Coordinators/GetENSOwnerCoordinator.swift +++ b/AlphaWallet/Tokens/Coordinators/GetENSOwnerCoordinator.swift @@ -30,7 +30,7 @@ class GetENSAddressCoordinator { private static let DELAY_AFTER_STOP_TYPING_TO_START_RESOLVING_ENS_NAME = TimeInterval(0.5) private var toStartResolvingEnsNameTimer: Timer? - private let server: RPCServer + private (set) var server: RPCServer init(server: RPCServer) { self.server = server diff --git a/Podfile b/Podfile index 3ba5c650c..85bf49d9e 100644 --- a/Podfile +++ b/Podfile @@ -4,7 +4,7 @@ source 'https://cdn.cocoapods.org/' target 'AlphaWallet' do use_frameworks! - pod 'BigInt', '~> 3.0' + pod 'BigInt', '~> 3.1' pod 'R.swift' pod 'JSONRPCKit', '~> 2.0.0' pod 'APIKit' @@ -37,6 +37,7 @@ target 'AlphaWallet' do pod 'TrustWalletCore' pod 'AWSSNS' pod 'Mixpanel-swift' + pod 'UnstoppableDomainsResolution', '0.1.6' # pod 'AWSCognito' target 'AlphaWalletTests' do inherit! :search_paths diff --git a/Podfile.lock b/Podfile.lock index 7999df6bc..ee33a5d80 100644 --- a/Podfile.lock +++ b/Podfile.lock @@ -7,9 +7,17 @@ PODS: - AWSCore (2.18.0) - AWSSNS (2.18.0): - AWSCore (= 2.18.0) + - Base58Swift (2.1.7): + - BigInt (~> 3.1) - BigInt (3.1.0): - SipHash (~> 1.2) - CryptoSwift (1.3.2) + - EthereumABI (1.3.0): + - BigInt (~> 3.1) + - CryptoSwift (~> 1.0) + - EthereumAddress (~> 1.3) + - EthereumAddress (1.3.0): + - CryptoSwift (~> 1.0) - Eureka (5.2.1) - iOSSnapshotTestCase (6.2.0): - iOSSnapshotTestCase/SwiftSupport (= 6.2.0) @@ -79,6 +87,9 @@ PODS: - TrustWalletCore/Types - TrustWalletCore/Types (2.3.3): - SwiftProtobuf + - UnstoppableDomainsResolution (0.1.6): + - Base58Swift (~> 2.1) + - EthereumABI (~> 1.3) - web3swift (0.9.0): - Alamofire (~> 4.7) - Alamofire-Synchronous (~> 4.0) @@ -93,7 +104,7 @@ DEPENDENCIES: - AlphaWalletWeb3Provider (from `https://github.com/AlphaWallet/AlphaWallet-web3-provider`, commit `1c1aafb566361e7067e69f6e38b0fdc30b801429`) - APIKit - AWSSNS - - BigInt (~> 3.0) + - BigInt (~> 3.1) - CryptoSwift (~> 1.0) - Eureka (from `https://github.com/xmartlabs/Eureka.git`, commit `5c54e2607632ce586010e50e91d9adcb6bb3909e`) - iOSSnapshotTestCase @@ -121,6 +132,7 @@ DEPENDENCIES: - TrezorCrypto (from `https://github.com/AlphaWallet/trezor-crypto-ios.git`, commit `50c16ba5527e269bbc838e80aee5bac0fe304cc7`) - TrustKeystore (from `https://github.com/alpha-wallet/trust-keystore.git`, commit `c0bdc4f6ffc117b103e19d17b83109d4f5a0e764`) - TrustWalletCore + - UnstoppableDomainsResolution (= 0.1.6) - web3swift (from `https://github.com/AlphaWallet/web3swift.git`, commit `169e50e29b60df72351c689a002005d2e2bc7559`) SPEC REPOS: @@ -130,8 +142,11 @@ SPEC REPOS: - APIKit - AWSCore - AWSSNS + - Base58Swift - BigInt - CryptoSwift + - EthereumABI + - EthereumAddress - iOSSnapshotTestCase - JavaScriptKit - JSONRPCKit @@ -156,6 +171,7 @@ SPEC REPOS: - SwiftyJSON - SWXMLHash - TrustWalletCore + - UnstoppableDomainsResolution EXTERNAL SOURCES: AlphaWalletWeb3Provider: @@ -227,8 +243,11 @@ SPEC CHECKSUMS: APIKit: 9e1a4069608bf0ae5238811e6cfc26928ad4d01e AWSCore: e8cc7fd06efa211328942cc7c9f6dddbe889a115 AWSSNS: d3ed65985986d5393de2793259e457e466997b55 + Base58Swift: 149c9dd95d8712f00e4695b36baafb3031927256 BigInt: 76b5dfdfa3e2e478d4ffdf161aeede5502e2742f CryptoSwift: 093499be1a94b0cae36e6c26b70870668cb56060 + EthereumABI: c021720744d260b87def6aa45d94554d260cb5a9 + EthereumAddress: 39fe8e11cf04e4e9902b55ae653dbc4e0aee5f30 Eureka: c883105488e05bc65539f583246ecf9657cabbfe iOSSnapshotTestCase: 9ab44cb5aa62b84d31847f40680112e15ec579a6 JavaScriptKit: 33a7abbd6e03bf12e3f19f0f8ee753e748a361c1 @@ -261,8 +280,9 @@ SPEC CHECKSUMS: TrezorCrypto: bfeea47a052dca2c77d4a39e1e183865e52de14d TrustKeystore: 3d8b4571c66648fb985015c96b3185440bb837fe TrustWalletCore: 84a87886e55b4efa875b452926d3ba58a32359c8 + UnstoppableDomainsResolution: dc89a8d9e51f3786b46274b457875a231d5bdbb8 web3swift: 06118d4c4edc801444aaa995bbbddeda176b97ef -PODFILE CHECKSUM: d284670f6269632d01f9c22fd6699ba293e4f2e6 +PODFILE CHECKSUM: 9b09ec864c2168207fdd3d0a3cbaddbbf0f696e8 COCOAPODS: 1.10.0