├── Tests ├── LinuxMain.swift ├── ResolutionTests │ ├── XCTestManifests.swift │ └── Info.plist └── EthereumABITests │ └── Info.plist ├── .swiftpm └── xcode │ ├── package.xcworkspace │ └── contents.xcworkspacedata │ └── xcshareddata │ └── xcschemes │ └── UnstoppableDomainsResolution.xcscheme ├── Resolution.xcodeproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ ├── WorkspaceSettings.xcsettings │ │ ├── IDEWorkspaceChecks.plist │ │ └── swiftpm │ │ └── Package.resolved └── xcshareddata │ └── xcschemes │ └── resolution.xcscheme ├── .github └── workflows │ └── swift.yml ├── Sources ├── UnstoppableDomainsResolution │ ├── Helpers │ │ ├── UsdtVersion.swift │ │ ├── DNS │ │ │ ├── DnsRecordsError.swift │ │ │ ├── DnsType.swift │ │ │ └── DnsUtils.swift │ │ ├── Types.swift │ │ └── Utilities.swift │ ├── resolution.h │ ├── ABI │ │ ├── EthereumABI │ │ │ ├── ABI.swift │ │ │ ├── ABITypeParser.swift │ │ │ ├── ABIParsing.swift │ │ │ ├── ABIParameterTypes.swift │ │ │ ├── ABIExtensions.swift │ │ │ ├── ABIElements.swift │ │ │ ├── ABIDecoder.swift │ │ │ └── ABIEncoder.swift │ │ ├── ABICoder.swift │ │ └── JSON_RPC.swift │ ├── Protocols │ │ └── NamingService.swift │ ├── Errors.swift │ ├── Configurations.swift │ ├── Resources │ │ ├── ENS │ │ │ ├── ensRegistry.json │ │ │ └── ensResolver.json │ │ └── CNS │ │ │ ├── network-config.json │ │ │ ├── cnsResolver.json │ │ │ └── cnsProxyReader.json │ ├── Support │ │ └── Base58Swift │ │ │ ├── README.md │ │ │ └── Base58.swift │ ├── ContractZNS.swift │ ├── APIRequest.swift │ ├── Contract.swift │ └── NamingServices │ │ ├── ZNS.swift │ │ ├── CommonNamingService.swift │ │ ├── ENS.swift │ │ └── CNS.swift └── Info.plist ├── .swiftlint.yml ├── .travis.yml ├── Package.resolved ├── LICENSE.md ├── UnstoppableDomainsResolution.podspec ├── Package.swift ├── .gitignore ├── CHANGELOG.md └── README.md /Tests/LinuxMain.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | 3 | import ResolutionTests 4 | 5 | var tests = [XCTestCaseEntry]() 6 | tests += ResolutionTests.allTests() 7 | tests += EthereumABITests.allTests() 8 | XCTMain(tests) 9 | -------------------------------------------------------------------------------- /.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Resolution.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Tests/ResolutionTests/XCTestManifests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | 3 | #if !canImport(ObjectiveC) 4 | public func allTests() -> [XCTestCaseEntry] { 5 | return [ 6 | testCase(ResolutionTests.allTests), 7 | testCase(EthereumABITests.allTests) 8 | ] 9 | } 10 | #endif 11 | -------------------------------------------------------------------------------- /Resolution.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreviewsEnabled 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Resolution.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /.github/workflows/swift.yml: -------------------------------------------------------------------------------- 1 | name: Swift 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | branches: [ master ] 8 | 9 | jobs: 10 | build: 11 | 12 | runs-on: macos-latest 13 | 14 | steps: 15 | - uses: actions/checkout@v2 16 | - name: Build 17 | run: swift build -v 18 | - name: Run tests 19 | run: swift test -v 20 | -------------------------------------------------------------------------------- /Sources/UnstoppableDomainsResolution/Helpers/UsdtVersion.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UsdtVersion.swift 3 | // UnstoppableDomainsResolution 4 | // 5 | // Created by Johnny Good on 1/6/21. 6 | // Copyright © 2021 Unstoppable Domains. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | public enum UsdtVersion: String { 11 | case ERC20 12 | case OMNI 13 | case TRON 14 | case EOS 15 | } 16 | -------------------------------------------------------------------------------- /Sources/UnstoppableDomainsResolution/Helpers/DNS/DnsRecordsError.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DnsRecordsError.swift 3 | // UnstoppableDomainsResolution 4 | // 5 | // Created by Johnny Good on 12/18/20. 6 | // Copyright © 2020 Unstoppable Domains. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | public enum DnsRecordsError: Error { 12 | case dnsRecordCorrupted(recordType: DnsType) 13 | case inconsistentTtl(recordType: DnsType) 14 | } 15 | -------------------------------------------------------------------------------- /.swiftlint.yml: -------------------------------------------------------------------------------- 1 | line_length: 2 | warning: 140 3 | error: 200 4 | ignores_comments: true 5 | ignores_urls: true 6 | ignores_function_declarations: true 7 | ### works up until this config rule 8 | ignores_interpolated_strings: true 9 | 10 | identifier_name: 11 | excluded: 12 | - v 13 | - t 14 | - s 15 | - c 16 | - b 17 | - d 18 | - i 19 | - h 20 | - el 21 | - bn 22 | - to 23 | - id 24 | - tx 25 | - vc 26 | - ok 27 | 28 | excluded: 29 | - Pods 30 | - .build 31 | - Tests -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | # references: 2 | # * https://www.objc.io/issues/6-build-tools/travis-ci/ 3 | # * https://github.com/supermarin/xcpretty#usage 4 | 5 | osx_image: xcode7.3 6 | language: objective-c 7 | # cache: cocoapods 8 | # podfile: Example/Podfile 9 | # before_install: 10 | # - gem install cocoapods # Since Travis is not always on latest version 11 | # - pod install --project-directory=Example 12 | script: 13 | - set -o pipefail && xcodebuild test -enableCodeCoverage YES -workspace Example/resolution.xcworkspace -scheme resolution-Example -sdk iphonesimulator9.3 ONLY_ACTIVE_ARCH=NO | xcpretty 14 | - pod lib lint 15 | -------------------------------------------------------------------------------- /Sources/UnstoppableDomainsResolution/resolution.h: -------------------------------------------------------------------------------- 1 | // 2 | // resolution.h 3 | // resolution 4 | // 5 | // Created by Johnny Good on 8/11/20. 6 | // Copyright © 2020 Unstoppable Domains. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | //! Project version number for resolution. 12 | FOUNDATION_EXPORT double resolutionVersionNumber; 13 | 14 | //! Project version string for resolution. 15 | FOUNDATION_EXPORT const unsigned char resolutionVersionString[]; 16 | 17 | // In this header, you should import all the public headers of your framework using statements like #import 18 | 19 | 20 | -------------------------------------------------------------------------------- /Sources/UnstoppableDomainsResolution/Helpers/Types.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Types.swift 3 | // Resolution 4 | // 5 | // Created by Serg Merenkov on 9/8/20. 6 | // Copyright © 2020 Unstoppable Domains. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | public typealias StringResultConsumer = (Result) -> Void 12 | public typealias StringsArrayResultConsumer = (Result<[String?], ResolutionError>) -> Void 13 | public typealias DictionaryResultConsumer = (Result<[String: String], ResolutionError>) -> Void 14 | public typealias DnsRecordsResultConsumer = (Result<[DnsRecord], Error>) -> Void 15 | public let ethCoinIndex = 60 16 | -------------------------------------------------------------------------------- /Sources/UnstoppableDomainsResolution/ABI/EthereumABI/ABI.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ABI.swift 3 | // resolution 4 | // 5 | // Created by Serg Merenkov on 2/8/21. 6 | // Copyright © 2021 Unstoppable Domains. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | public struct ABI { 12 | 13 | } 14 | 15 | protocol ABIElementPropertiesProtocol { 16 | var isStatic: Bool {get} 17 | var isArray: Bool {get} 18 | var isTuple: Bool {get} 19 | var arraySize: ABI.Element.ArraySize {get} 20 | var subtype: ABI.Element.ParameterType? {get} 21 | var memoryUsage: UInt64 {get} 22 | var emptyValue: Any {get} 23 | } 24 | 25 | protocol ABIEncoding { 26 | var abiRepresentation: String {get} 27 | } 28 | 29 | protocol ABIValidation { 30 | var isValid: Bool {get} 31 | } 32 | -------------------------------------------------------------------------------- /Tests/EthereumABITests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /Tests/ResolutionTests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | $(PRODUCT_BUNDLE_PACKAGE_TYPE) 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /Sources/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | $(PRODUCT_BUNDLE_PACKAGE_TYPE) 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | $(CURRENT_PROJECT_VERSION) 21 | 22 | 23 | -------------------------------------------------------------------------------- /Sources/UnstoppableDomainsResolution/Protocols/NamingService.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NamingService.swift 3 | // resolution 4 | // 5 | // Created by Johnny Good on 8/11/20. 6 | // Copyright © 2020 Unstoppable Domains. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | protocol NamingService { 12 | var name: String { get } 13 | var network: String { get } 14 | var providerUrl: String { get } 15 | 16 | func namehash(domain: String) -> String 17 | func isSupported(domain: String) -> Bool 18 | 19 | func owner(domain: String) throws -> String 20 | func addr(domain: String, ticker: String) throws -> String 21 | func resolver(domain: String) throws -> String 22 | 23 | func batchOwners(domains: [String]) throws -> [String?] 24 | 25 | func record(domain: String, key: String) throws -> String 26 | func records(keys: [String], for domain: String) throws -> [String: String] 27 | } 28 | -------------------------------------------------------------------------------- /Package.resolved: -------------------------------------------------------------------------------- 1 | { 2 | "object": { 3 | "pins": [ 4 | { 5 | "package": "BigInt", 6 | "repositoryURL": "https://github.com/attaswift/BigInt.git", 7 | "state": { 8 | "branch": null, 9 | "revision": "889a1ecacd73ccc189c5cb29288048f186c44ed9", 10 | "version": "5.2.1" 11 | } 12 | }, 13 | { 14 | "package": "CryptoSwift", 15 | "repositoryURL": "https://github.com/krzyzanowskim/CryptoSwift.git", 16 | "state": { 17 | "branch": null, 18 | "revision": "3a2acbb32ab68215ee1596ee6004da8e90c3721b", 19 | "version": "1.0.0" 20 | } 21 | }, 22 | { 23 | "package": "EthereumAddress", 24 | "repositoryURL": "https://github.com/shamatar/EthereumAddress.git", 25 | "state": { 26 | "branch": null, 27 | "revision": "a8eb4fc1dc03c068bf88585d8f5a2dd3572664ca", 28 | "version": "1.3.0" 29 | } 30 | } 31 | ] 32 | }, 33 | "version": 1 34 | } 35 | -------------------------------------------------------------------------------- /Resolution.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved: -------------------------------------------------------------------------------- 1 | { 2 | "object": { 3 | "pins": [ 4 | { 5 | "package": "BigInt", 6 | "repositoryURL": "https://github.com/attaswift/BigInt.git", 7 | "state": { 8 | "branch": null, 9 | "revision": "889a1ecacd73ccc189c5cb29288048f186c44ed9", 10 | "version": "5.2.1" 11 | } 12 | }, 13 | { 14 | "package": "CryptoSwift", 15 | "repositoryURL": "https://github.com/krzyzanowskim/CryptoSwift.git", 16 | "state": { 17 | "branch": null, 18 | "revision": "3a2acbb32ab68215ee1596ee6004da8e90c3721b", 19 | "version": "1.0.0" 20 | } 21 | }, 22 | { 23 | "package": "EthereumAddress", 24 | "repositoryURL": "https://github.com/shamatar/EthereumAddress.git", 25 | "state": { 26 | "branch": null, 27 | "revision": "a8eb4fc1dc03c068bf88585d8f5a2dd3572664ca", 28 | "version": "1.3.0" 29 | } 30 | } 31 | ] 32 | }, 33 | "version": 1 34 | } 35 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Unstoppable Domains 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /UnstoppableDomainsResolution.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |spec| 2 | spec.name = "UnstoppableDomainsResolution" 3 | spec.version = "0.3.7" 4 | spec.summary = "Swift framework for resolving Unstoppable domains." 5 | 6 | spec.description = <<-DESC 7 | This framework helps to resolve a decentralised domain names such as brad.crypto 8 | DESC 9 | 10 | spec.homepage = "https://github.com/unstoppabledomains/resolution-swift" 11 | 12 | spec.license = { :type => "MIT", :file => "LICENSE.md" } 13 | 14 | spec.author = { "JohnnyJumper" => "jeyhunt@gmail.com", "Sergei Merenkov" => "mer.sergei@gmail.com", "Roman Medvid" => "roman@unstoppabledomains.com" } 15 | spec.social_media_url = 'https://twitter.com/unstoppableweb' 16 | 17 | spec.ios.deployment_target = "11.0" 18 | 19 | spec.swift_version = '5.0' 20 | 21 | spec.source = { :git => "https://github.com/unstoppabledomains/resolution-swift.git", :tag => spec.version } 22 | spec.source_files = "Sources/UnstoppableDomainsResolution/**/*.swift" 23 | 24 | spec.resources = "Sources/UnstoppableDomainsResolution/Resources/**/*.json" 25 | 26 | spec.dependency 'EthereumAddress', '~> 1.3' 27 | spec.dependency 'CryptoSwift', '~> 1.0' 28 | spec.dependency 'BigInt' 29 | end 30 | -------------------------------------------------------------------------------- /Sources/UnstoppableDomainsResolution/Helpers/Utilities.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Utilities.swift 3 | // resolution 4 | // 5 | // Created by Johnny Good on 8/19/20. 6 | // Copyright © 2020 Unstoppable Domains. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | internal class Utillities { 12 | static func isNotEmpty(_ value: String?) -> Bool { 13 | guard let value = value else { return false } 14 | return Self.isNotEmpty(value) 15 | } 16 | 17 | static func isNotEmpty(_ value: String) -> Bool { 18 | let nullValues = [ 19 | "0x0000000000000000000000000000000000000000", 20 | "0x0000000000000000000000000000000000000000000000000000000000000000" 21 | ] 22 | return !(value.isEmpty || nullValues.contains(value)) 23 | } 24 | 25 | static func isNotEmpty(_ array: [Any]) -> Bool { 26 | return array.count > 0 27 | } 28 | } 29 | 30 | extension String { 31 | static func ~= (lhs: String, rhs: String) -> Bool { 32 | guard let regex = try? NSRegularExpression(pattern: rhs) else { return false } 33 | let range = NSRange(location: 0, length: lhs.utf16.count) 34 | return regex.firstMatch(in: lhs, options: [], range: range) != nil 35 | } 36 | 37 | func addHexPrefix() -> String { 38 | if !self.hasPrefix("0x") { 39 | return "0x" + self 40 | } 41 | return self 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /Sources/UnstoppableDomainsResolution/Errors.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Errors.swift 3 | // resolution 4 | // 5 | // Created by Johnny Good on 8/11/20. 6 | // Copyright © 2020 Unstoppable Domains. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | public enum ResolutionError: Error { 12 | case unregisteredDomain 13 | case unsupportedDomain 14 | case recordNotFound 15 | case recordNotSupported 16 | case unsupportedNetwork 17 | case unspecifiedResolver 18 | case unknownError(Error) 19 | case proxyReaderNonInitialized 20 | case inconsistenDomainArray 21 | case methodNotSupported 22 | case tooManyResponses 23 | case badRequestOrResponse 24 | case unsupportedServiceName 25 | 26 | static let tooManyResponsesCode = -32005 27 | static let badRequestOrResponseCode = -32042 28 | 29 | static func parse (errorResponse: NetworkErrorResponse) -> ResolutionError? { 30 | let error = errorResponse.error 31 | if error.code == tooManyResponsesCode { 32 | return .tooManyResponses 33 | } 34 | if error.code == badRequestOrResponseCode { 35 | return .badRequestOrResponse 36 | } 37 | return nil 38 | } 39 | } 40 | 41 | struct NetworkErrorResponse: Decodable { 42 | var jsonrpc: String 43 | var id: String 44 | var error: ErrorId 45 | } 46 | 47 | struct ErrorId: Codable { 48 | var code: Int 49 | var message: String 50 | } 51 | -------------------------------------------------------------------------------- /Sources/UnstoppableDomainsResolution/Configurations.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Configuration.swift 3 | // UnstoppableDomainsResolution 4 | // 5 | // Created by Johnny Good on 2/16/21. 6 | // Copyright © 2021 Unstoppable Domains. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | public struct NamingServiceConfig { 12 | let network: String 13 | let providerUrl: String 14 | let networking: NetworkingLayer 15 | 16 | public init( 17 | providerUrl: String, 18 | network: String = "", 19 | networking: NetworkingLayer = DefaultNetworkingLayer() 20 | ) { 21 | self.network = network 22 | self.providerUrl = providerUrl 23 | self.networking = networking 24 | } 25 | } 26 | 27 | public struct Configurations { 28 | let cns: NamingServiceConfig 29 | let zns: NamingServiceConfig 30 | let ens: NamingServiceConfig 31 | 32 | public init( 33 | cns: NamingServiceConfig = NamingServiceConfig( 34 | providerUrl: "https://mainnet.infura.io/v3/3c25f57353234b1b853e9861050f4817", 35 | network: "mainnet"), 36 | ens: NamingServiceConfig = NamingServiceConfig( 37 | providerUrl: "https://mainnet.infura.io/v3/d423cf2499584d7fbe171e33b42cfbee", 38 | network: "mainnet"), 39 | zns: NamingServiceConfig = NamingServiceConfig( 40 | providerUrl: "https://api.zilliqa.com", 41 | network: "mainnet") 42 | ) { 43 | self.ens = ens 44 | self.cns = cns 45 | self.zns = zns 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:5.3 2 | // The swift-tools-version declares the minimum version of Swift required to build this package. 3 | 4 | import PackageDescription 5 | 6 | let package = Package( 7 | name: "UnstoppableDomainsResolution", 8 | platforms: [.macOS(.v10_15), .iOS(.v11) ], 9 | products: [ 10 | .library( 11 | name: "UnstoppableDomainsResolution", 12 | type: nil, 13 | targets: ["UnstoppableDomainsResolution"]) 14 | ], 15 | dependencies: [ 16 | .package(url: "https://github.com/krzyzanowskim/CryptoSwift.git", from: "1.0.0"), 17 | .package(url: "https://github.com/attaswift/BigInt.git", from: "5.0.0"), 18 | .package(url: "https://github.com/shamatar/EthereumAddress.git", from: "1.3.0") 19 | ], 20 | targets: [ 21 | .target( 22 | name: "UnstoppableDomainsResolution", 23 | dependencies: ["CryptoSwift", "BigInt", "EthereumAddress"], 24 | resources: [ 25 | .process("Resources/CNS/supported-keys.json"), 26 | .process("Resources/CNS/cnsProxyReader.json"), 27 | .process("Resources/CNS/cnsRegistry.json"), 28 | .process("Resources/CNS/cnsResolver.json"), 29 | .process("Resources/CNS/network-config.json"), 30 | .process("Resources/ENS/ensRegistry.json"), 31 | .process("Resources/ENS/ensResolver.json") 32 | ], 33 | swiftSettings: [.define("INSIDE_PM")] 34 | ), 35 | .testTarget( 36 | name: "ResolutionTests", 37 | dependencies: ["UnstoppableDomainsResolution"], 38 | exclude:["Info.plist"], 39 | swiftSettings: [.define("INSIDE_PM")] 40 | ) 41 | ] 42 | ) 43 | -------------------------------------------------------------------------------- /Sources/UnstoppableDomainsResolution/Helpers/DNS/DnsType.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DnsType.swift 3 | // UnstoppableDomainsResolution 4 | // 5 | // Created by Johnny Good on 12/17/20. 6 | // Copyright © 2020 Unstoppable Domains. All rights reserved. 7 | // 8 | // swiftlint:disable identifier_name 9 | 10 | import Foundation 11 | 12 | public enum DnsType: String { 13 | case A 14 | case AAAA 15 | case AFSDB 16 | case APL 17 | case CAA 18 | case CDNSKEY 19 | case CDS 20 | case CERT 21 | case CNAME 22 | case CSYNC 23 | case DHCID 24 | case DLV 25 | case DNAME 26 | case DNSKEY 27 | case DS 28 | case EUI48 29 | case EUI64 30 | case HINFO 31 | case HIP 32 | case HTTPS 33 | case IPSECKEY 34 | case KEY 35 | case KX 36 | case LOC 37 | case MX 38 | case NAPTR 39 | case NS 40 | case NSEC 41 | case NSEC3 42 | case NSEC3PARAM 43 | case OPENPGPKEY 44 | case PTR 45 | case RP 46 | case RRSIG 47 | case SIG 48 | case SMIMEA 49 | case SOA 50 | case SRV 51 | case SSHFP 52 | case SVCB 53 | case TA 54 | case TKEY 55 | case TLSA 56 | case TSIG 57 | case TXT 58 | case URI 59 | case ZONEMD 60 | 61 | static func getCryptoRecords(types: [DnsType], ttl: Bool) -> [String] { 62 | var cryptoRecords: [String] = [] 63 | if ttl { 64 | cryptoRecords.append("dns.ttl") 65 | } 66 | for type in types { 67 | if ttl { 68 | cryptoRecords.append(self.getCryptoRecord(type: type, with: ttl)) 69 | } 70 | cryptoRecords.append(self.getCryptoRecord(type: type)) 71 | } 72 | return cryptoRecords 73 | } 74 | 75 | static func getCryptoRecord(type: DnsType, with ttl: Bool = false) -> String { 76 | if ttl { 77 | return "dns.\(type).ttl" 78 | } 79 | return "dns.\(type)" 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | # 3 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 4 | 5 | ## User settings 6 | xcuserdata/ 7 | 8 | ## compatibility with Xcode 8 and earlier (ignoring not required starting Xcode 9) 9 | *.xcscmblueprint 10 | *.xccheckout 11 | 12 | ## compatibility with Xcode 3 and earlier (ignoring not required starting Xcode 4) 13 | build/ 14 | DerivedData/ 15 | *.moved-aside 16 | *.pbxuser 17 | !default.pbxuser 18 | *.mode1v3 19 | !default.mode1v3 20 | *.mode2v3 21 | !default.mode2v3 22 | *.perspectivev3 23 | !default.perspectivev3 24 | 25 | ## Obj-C/Swift specific 26 | *.hmap 27 | 28 | ## App packaging 29 | *.ipa 30 | *.dSYM.zip 31 | *.dSYM 32 | 33 | ## Playgrounds 34 | timeline.xctimeline 35 | playground.xcworkspace 36 | 37 | # Swift Package Manager 38 | # 39 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. 40 | # Packages/ 41 | # Package.pins 42 | # Package.resolved 43 | # *.xcodeproj 44 | # 45 | # Xcode automatically generates this directory with a .xcworkspacedata file and xcuserdata 46 | # hence it is not needed unless you have added a package configuration file to your project 47 | # .swiftpm 48 | 49 | .build/ 50 | 51 | # CocoaPods 52 | # 53 | # We recommend against adding the Pods directory to your .gitignore. However 54 | # you should judge for yourself, the pros and cons are mentioned at: 55 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 56 | # 57 | # Pods/ 58 | # 59 | # Add this line if you want to avoid checking in source code from the Xcode workspace 60 | # *.xcworkspace 61 | 62 | # Carthage 63 | # 64 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 65 | # Carthage/Checkouts 66 | 67 | Carthage/Build/ 68 | 69 | # Accio dependency management 70 | Dependencies/ 71 | .accio/ 72 | 73 | # fastlane 74 | # 75 | # It is recommended to not store the screenshots in the git repo. 76 | # Instead, use fastlane to re-generate the screenshots whenever they are needed. 77 | # For more information about the recommended setup visit: 78 | # https://docs.fastlane.tools/best-practices/source-control/#source-control 79 | 80 | fastlane/report.xml 81 | fastlane/Preview.html 82 | fastlane/screenshots/**/*.png 83 | fastlane/test_output 84 | 85 | # Code Injection 86 | # 87 | # After new code Injection tools there's a generated folder /iOSInjectionProject 88 | # https://github.com/johnno1962/injectionforxcode 89 | 90 | iOSInjectionProject/ 91 | -------------------------------------------------------------------------------- /Sources/UnstoppableDomainsResolution/ABI/ABICoder.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ABICoder.swift 3 | // resolution 4 | // 5 | // Created by Johnny Good on 8/17/20. 6 | // Copyright © 2020 Unstoppable Domains. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | typealias ABIContract = [ABI.Element] 12 | 13 | enum ABICoderError: Error { 14 | case wrongABIInterfaceForMethod(method: String) 15 | case couldNotEncode(method: String, args: [Any]) 16 | case couldNotDecode(method: String, value: String) 17 | } 18 | 19 | // swiftlint:disable identifier_name 20 | internal class ABICoder { 21 | let abi: ABIContract 22 | 23 | private var methods: [String: ABI.Element] { 24 | var toReturn = [String: ABI.Element]() 25 | for m in self.abi { 26 | switch m { 27 | case .function(let function): 28 | guard let name = function.name else {continue} 29 | toReturn[name] = m 30 | default: 31 | continue 32 | } 33 | } 34 | return toReturn 35 | } 36 | 37 | init(_ abi: ABIContract) { 38 | self.abi = abi 39 | } 40 | 41 | // MARK: - Decode Block 42 | public func decode(_ data: String, from method: String) throws -> Any { 43 | 44 | if method == "fallback" { 45 | return [String: Any]() 46 | } 47 | 48 | guard let function = methods[method] else { 49 | throw ABICoderError.wrongABIInterfaceForMethod(method: method) 50 | } 51 | guard case .function = function else { 52 | throw ABICoderError.wrongABIInterfaceForMethod(method: method) 53 | } 54 | guard let decoded = function.decodeReturnData(Data(hex: data)) else { 55 | throw ABICoderError.couldNotDecode(method: method, value: data) 56 | } 57 | 58 | return decoded 59 | } 60 | 61 | // MARK: - Encode Block 62 | public func encode(method: String, args: [Any]) throws -> String { 63 | 64 | let argsObjects = args.map({$0 as AnyObject}) 65 | 66 | let foundMethod = self.methods.filter { (key, _) -> Bool in 67 | return key == method 68 | } 69 | guard foundMethod.count == 1 else { 70 | throw ABICoderError.wrongABIInterfaceForMethod(method: method) 71 | } 72 | 73 | let abiMethod = foundMethod[method] 74 | guard let encodedData = abiMethod?.encodeParameters(argsObjects) else { 75 | throw ABICoderError.couldNotEncode(method: method, args: args) 76 | } 77 | 78 | let encoded = encodedData.toHexString().addHexPrefix() 79 | return encoded 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /Sources/UnstoppableDomainsResolution/Resources/ENS/ensRegistry.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "constant": true, 4 | "inputs": [{ "name": "node", "type": "bytes32" }], 5 | "name": "resolver", 6 | "outputs": [{ "name": "", "type": "address" }], 7 | "payable": false, 8 | "type": "function", 9 | }, 10 | { 11 | "constant": true, 12 | "inputs": [{ "name": "node", "type": "bytes32" }], 13 | "name": "owner", 14 | "outputs": [{ "name": "", "type": "address" }], 15 | "payable": false, 16 | "type": "function", 17 | }, 18 | { 19 | "constant": false, 20 | "inputs": [ 21 | { "name": "node", "type": "bytes32" }, 22 | { "name": "label", "type": "bytes32" }, 23 | { "name": "owner", "type": "address" }, 24 | ], 25 | "name": "setSubnodeOwner", 26 | "outputs": [], 27 | "payable": false, 28 | "type": "function", 29 | }, 30 | { 31 | "constant": false, 32 | "inputs": [ 33 | { "name": "node", "type": "bytes32" }, 34 | { "name": "ttl", "type": "uint64" }, 35 | ], 36 | "name": "setTTL", 37 | "outputs": [], 38 | "payable": false, 39 | "type": "function", 40 | }, 41 | { 42 | "constant": true, 43 | "inputs": [{ "name": "node", "type": "bytes32" }], 44 | "name": "ttl", 45 | "outputs": [{ "name": "", "type": "uint64" }], 46 | "payable": false, 47 | "type": "function", 48 | }, 49 | { 50 | "constant": false, 51 | "inputs": [ 52 | { "name": "node", "type": "bytes32" }, 53 | { "name": "resolver", "type": "address" }, 54 | ], 55 | "name": "setResolver", 56 | "outputs": [], 57 | "payable": false, 58 | "type": "function", 59 | }, 60 | { 61 | "constant": false, 62 | "inputs": [ 63 | { "name": "node", "type": "bytes32" }, 64 | { "name": "owner", "type": "address" }, 65 | ], 66 | "name": "setOwner", 67 | "outputs": [], 68 | "payable": false, 69 | "type": "function", 70 | }, 71 | { 72 | "anonymous": false, 73 | "inputs": [ 74 | { "indexed": true, "name": "node", "type": "bytes32" }, 75 | { "indexed": false, "name": "owner", "type": "address" }, 76 | ], 77 | "name": "Transfer", 78 | "type": "event", 79 | }, 80 | { 81 | "anonymous": false, 82 | "inputs": [ 83 | { "indexed": true, "name": "node", "type": "bytes32" }, 84 | { "indexed": true, "name": "label", "type": "bytes32" }, 85 | { "indexed": false, "name": "owner", "type": "address" }, 86 | ], 87 | "name": "NewOwner", 88 | "type": "event", 89 | }, 90 | { 91 | "anonymous": false, 92 | "inputs": [ 93 | { "indexed": true, "name": "node", "type": "bytes32" }, 94 | { "indexed": false, "name": "resolver", "type": "address" }, 95 | ], 96 | "name": "NewResolver", 97 | "type": "event", 98 | }, 99 | { 100 | "anonymous": false, 101 | "inputs": [ 102 | { "indexed": true, "name": "node", "type": "bytes32" }, 103 | { "indexed": false, "name": "ttl", "type": "uint64" }, 104 | ], 105 | "name": "NewTTL", 106 | "type": "event", 107 | }, 108 | ] 109 | -------------------------------------------------------------------------------- /Resolution.xcodeproj/xcshareddata/xcschemes/resolution.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 33 | 39 | 40 | 41 | 42 | 43 | 53 | 54 | 60 | 61 | 67 | 68 | 69 | 70 | 72 | 73 | 76 | 77 | 78 | -------------------------------------------------------------------------------- /Sources/UnstoppableDomainsResolution/Support/Base58Swift/README.md: -------------------------------------------------------------------------------- 1 | # Base58Swift 2 | 3 | [![Build Status](https://travis-ci.org/keefertaylor/Base58Swift.svg?branch=master)](https://travis-ci.org/keefertaylor/Base58Swift) 4 | [![codecov](https://codecov.io/gh/keefertaylor/Base58Swift/branch/master/graph/badge.svg)](https://codecov.io/gh/keefertaylor/Base58Swift) 5 | [![Carthage Compatible](https://img.shields.io/badge/Carthage-compatible-4BC51D.svg?style=flat)](https://github.com/Carthage/Carthage) 6 | [![Version](https://img.shields.io/cocoapods/v/Base58Swift.svg?style=flat)](http://cocoapods.org/pods/Base58Swift) 7 | [![License](https://img.shields.io/cocoapods/l/Base58Swift.svg?style=flat)](http://cocoapods.org/pods/Base58Swift) 8 | 9 | Base58Swift is a Swift library that implements Base58 / Base58Check encodings for cryptocurrencies. It is based off of [go-base-58](https://github.com/jbenet/go-base58) with some added functions. 10 | 11 | Donations help me find time to work on Base58Swift. If you find the library useful, please consider donating to support ongoing develoment. 12 | 13 | |Currency| Address | 14 | |---------|---| 15 | | __Tezos__ | tz1SNXT8yZCwTss2YcoFi3qbXvTZiCojx833 | 16 | | __Bitcoin__ | 1CdPoF9cvw3YEiuRCHxdsGpvb5tSUYBBo | 17 | | __Bitcoin Cash__ | qqpr9are9gzs5r0q7hy3gdehj3w074pyqsrhpdmxg6 | 18 | 19 | 20 | ## Installation 21 | ### CocoaPods 22 | Base58Swift supports installation via CocoaPods. You can depend on Base58Swift by adding the following to your Podfile: 23 | 24 | ``` 25 | pod "Base58Swift" 26 | ``` 27 | 28 | ### Carthage 29 | 30 | If you use [Carthage](https://github.com/Carthage/Carthage) to manage your dependencies, simply add 31 | Base58Swift to your `Cartfile`: 32 | 33 | ``` 34 | github "keefertaylor/Base58Swift" 35 | ``` 36 | 37 | If you use Carthage to build your dependencies, make sure you have added `BigInt.framework` and `SipHash.framework`, to the "_Linked Frameworks and Libraries_" section of your target, and have included them in your Carthage framework copying build phase. 38 | 39 | ### Swift Package Manager 40 | 41 | Add the following to the `dependencies` section of your `Package.swift` file: 42 | 43 | ```swift 44 | .package(url: "https://github.com/keefertaylor/Base58Swift.git", from: "2.1.0") 45 | ``` 46 | 47 | ## Usage 48 | 49 | Base58Swift provides a static utility class, `Base58`, which provides encoding and decoding functions. 50 | 51 | To encode / decode in Base58: 52 | ```swift 53 | let bytes: [UInt8] = [255, 254, 253, 252] 54 | 55 | let encodedString = Base58.encode(bytes)! 56 | let decodedBytes = Base58.decode(encodedString)! 57 | 58 | print(encodedString) // 7YXVWT 59 | print(decodedBytes) // [255, 254, 253, 252] 60 | ``` 61 | 62 | To encode / decode in Base58Check: 63 | ```swift 64 | let bytes: [UInt8] = [255, 254, 253, 252] 65 | 66 | let encodedString = Base58.base58CheckEncode(bytes)! 67 | let decodedBytes = Base58.base58CheckDecode(encodedString)! 68 | 69 | print(encodedString) // jpUz5f99p1R 70 | print(decodedBytes) // [255, 254, 253, 252] 71 | ``` 72 | 73 | ## Contributing 74 | 75 | Pull requests are welcome. 76 | 77 | To get set up: 78 | ```shell 79 | $ brew install xcodegen # if you don't already have it 80 | $ xcodegen generate # Generate an XCode project from Project.yml 81 | $ open Base58Swift.xcodeproj 82 | ``` 83 | 84 | ## License 85 | 86 | MIT 87 | -------------------------------------------------------------------------------- /Sources/UnstoppableDomainsResolution/ABI/JSON_RPC.swift: -------------------------------------------------------------------------------- 1 | // 2 | // JSON_RPC.swift 3 | // resolution 4 | // 5 | // Created by Johnny Good on 8/19/20. 6 | // Copyright © 2020 Unstoppable Domains. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | public struct JsonRpcPayload: Codable { 12 | let jsonrpc, id, method: String 13 | let params: [ParamElement] 14 | 15 | enum CodingKeys: String, CodingKey { 16 | case jsonrpc 17 | case id 18 | case method 19 | case params 20 | } 21 | 22 | init(jsonrpc: String, id: String, method: String, params: [ParamElement]) { 23 | self.jsonrpc = jsonrpc 24 | self.id = id 25 | self.method = method 26 | self.params = params 27 | } 28 | 29 | init (id: String, data: String, to address: String) { 30 | self.init(jsonrpc: "2.0", 31 | id: id, 32 | method: "eth_call", 33 | params: [ 34 | ParamElement.paramClass(ParamClass(data: data, to: address)), 35 | ParamElement.string("latest") 36 | ]) 37 | } 38 | } 39 | 40 | public enum ParamElement: Codable { 41 | case paramClass(ParamClass) 42 | case string(String) 43 | case array([ParamElement]) 44 | case dictionary([String: ParamElement]) 45 | 46 | public init(from decoder: Decoder) throws { 47 | let container = try decoder.singleValueContainer() 48 | if let elem = try? container.decode(String.self) { 49 | self = .string(elem) 50 | return 51 | } 52 | if let elem = try? container.decode(ParamClass.self) { 53 | self = .paramClass(elem) 54 | return 55 | } 56 | if let elem = try? container.decode(Array.self) { 57 | self = .array(elem) 58 | return 59 | } 60 | if let elem = try? container.decode([String: ParamElement].self) { 61 | self = .dictionary(elem) 62 | return 63 | } 64 | 65 | throw DecodingError.typeMismatch(ParamElement.self, 66 | DecodingError.Context( 67 | codingPath: decoder.codingPath, 68 | debugDescription: "Wrong type for ParamElement") 69 | ) 70 | } 71 | 72 | public func encode(to encoder: Encoder) throws { 73 | var container = encoder.singleValueContainer() 74 | switch self { 75 | case .paramClass(let elem): 76 | try container.encode(elem) 77 | case .string(let elem): 78 | try container.encode(elem) 79 | case .array(let array): 80 | try container.encode(array) 81 | case .dictionary(let dict): 82 | try container.encode(dict) 83 | } 84 | } 85 | } 86 | 87 | public struct ParamClass: Codable { 88 | let data: String 89 | let to: String 90 | } 91 | 92 | public struct JsonRpcResponse: Decodable { 93 | let jsonrpc: String 94 | let id: String 95 | let result: ParamElement 96 | 97 | enum CodingKeys: String, CodingKey { 98 | case jsonrpc 99 | case id 100 | case result 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /Sources/UnstoppableDomainsResolution/ContractZNS.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Contract.swift 3 | // resolution 4 | // 5 | // Created by Serg Merenkov on 9/8/20. 6 | // Copyright © 2020 Unstoppable Domains. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | internal class ContractZNS { 12 | let address: String 13 | let providerUrl: String 14 | let networking: NetworkingLayer 15 | 16 | init(providerUrl: String, address: String, networking: NetworkingLayer) { 17 | self.address = address 18 | self.providerUrl = providerUrl 19 | self.networking = networking 20 | } 21 | 22 | func fetchSubState(field: String, keys: [String]) throws -> Any { 23 | 24 | let body: JsonRpcPayload = JsonRpcPayload( 25 | jsonrpc: "2.0", 26 | id: "1", 27 | method: "GetSmartContractSubState", 28 | params: [ 29 | ParamElement.string(self.address), 30 | ParamElement.string(field), 31 | ParamElement.array(keys.map { ParamElement.string($0) }) 32 | ] 33 | ) 34 | let response = try postRequest(body)! 35 | 36 | guard case let ParamElement.dictionary(dict) = response, 37 | let results = self.reduce(dict: dict)[field] as? [String: Any] else { 38 | print("Invalid response, can't process") 39 | return response 40 | } 41 | 42 | return results 43 | } 44 | 45 | private func postRequest(_ body: JsonRpcPayload) throws -> Any? { 46 | let postRequest = APIRequest(providerUrl, networking: networking) 47 | var resp: JsonRpcResponse? 48 | var err: Error? 49 | let semaphore = DispatchSemaphore(value: 0) 50 | try postRequest.post(body, completion: {result in 51 | switch result { 52 | case .success(let response): 53 | resp = response[0] 54 | case .failure(let error): 55 | err = error 56 | } 57 | semaphore.signal() 58 | }) 59 | semaphore.wait() 60 | guard err == nil else { 61 | throw err! 62 | } 63 | return resp?.result 64 | } 65 | 66 | // MARK: - PRIVATE Helper functions 67 | 68 | private func reduce(dict: [String: ParamElement]) -> [String: Any] { 69 | return dict.reduce(into: [String: Any]()) { dict, pair in 70 | let (key, value) = pair 71 | 72 | switch value { 73 | case .paramClass(let elem): 74 | dict[key] = elem 75 | case .string(let elem): 76 | dict[key] = elem 77 | case .array(let array): 78 | dict[key] = self.map(array: array) 79 | case .dictionary(let dictionary): 80 | dict[key] = self.reduce(dict: dictionary) 81 | } 82 | } 83 | } 84 | 85 | private func map(array: [ParamElement]) -> [Any] { 86 | return array.map { (value) -> Any in 87 | switch value { 88 | case .paramClass(let elem): 89 | return elem 90 | case .string(let elem): 91 | return elem 92 | case .array(let array): 93 | return self.map(array: array) 94 | case .dictionary(let dictionary): 95 | return self.reduce(dict: dictionary) 96 | } 97 | } 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Resolution-swift CHANGELOG 2 | ## 0.3.7 3 | - Zns fix ([#40](https://github.com/unstoppabledomains/resolution-swift/pull/40)) via [@merenkoff](https://github.com/merenkoff) 4 | - Warning in project due to incorrect podspec #38 ([#39](https://github.com/unstoppabledomains/resolution-swift/pull/39)) via [@merenkoff](https://github.com/merenkoff) 5 | 6 | ## 0.3.6 7 | - Zilliqa testnet support. ([#37](https://github.com/unstoppabledomains/resolution-swift/pull/37)) via [@merenkoff](https://github.com/merenkoff) 8 | - UnregisteredDomain instead of UnspecifiedResolver ([#36](https://github.com/unstoppabledomains/resolution-swift/pull/36)) via [@JohnnyJumper](https://github.com/JohnnyJumper) 9 | 10 | ## 0.3.5 11 | * Introduce DomainResolution#getMultiChainAddress general method to fetch a ticker address from specific chain 12 | * Deprecate DomainResolution#getUsdt method in favor of DomainResolution#getMultiChainAddress 13 | 14 | - General multichain support ([#33](https://github.com/unstoppabledomains/resolution-swift/pull/33)) via [@JohnnyJumper](https://github.com/JohnnyJumper) 15 | - Auto network ([#32](https://github.com/unstoppabledomains/resolution-swift/pull/32)) via [@JohnnyJumper](https://github.com/JohnnyJumper) 16 | - Move Base58 lib from dependencies to internal sources. 17 | 18 | ## 0.3.0 19 | - [Customizing naming services](https://github.com/unstoppabledomains/resolution-swift#customizing-naming-services) 20 | Version 0.3.0 introduced the `Configurations` struct that is used for configuring each connected naming service. 21 | Library can offer three naming services at the moment: 22 | 23 | * `cns` resolves `.crypto` domains, 24 | * `ens` resolves `.eth` domains, 25 | * `zns` resolves `.zil` domains 26 | 27 | *By default, each of them is using the mainnet network via infura provider. 28 | Unstoppable domains are using the infura key with no restriction for CNS. 29 | Unstoppable domains recommends setting up your own provider for ENS, as we don't guarantee ENS Infura key availability. 30 | You can update each naming service separately* 31 | 32 | - Update keys ([#30](https://github.com/unstoppabledomains/resolution-swift/pull/30)) via [@JohnnyJumper](https://github.com/JohnnyJumper) 33 | - Change ABI codec dependency ([#31](https://github.com/unstoppabledomains/resolution-swift/pull/31)) via [@merenkoff](https://github.com/merenkoff) 34 | 35 | ## 0.2.0 36 | - Added correct initialization of Resolution object if `rinkeby` test network used([#27](https://github.com/unstoppabledomains/resolution-swift/pull/27)) via [@merenkoff](https://github.com/merenkoff) 37 | - Dns records support ([#25](https://github.com/unstoppabledomains/resolution-swift/pull/25)) via [@JohnnyJumper](https://github.com/JohnnyJumper) 38 | - Resolution#usdt ([#26](https://github.com/unstoppabledomains/resolution-swift/pull/26)) via [@JohnnyJumper](https://github.com/JohnnyJumper) 39 | - CNS supports both `mainnet` and `rinkeby` from network-config.json file ([#23](https://github.com/unstoppabledomains/resolution-swift/pull/23)) via [@rommex](https://github.com/rommex) 40 | - BadRequestOrResponse is Handled ([#21](https://github.com/unstoppabledomains/resolution-swift/pull/21)) via [@rommex](https://github.com/rommex) 41 | - Proxy reader 2 0 support ([#22](https://github.com/unstoppabledomains/resolution-swift/pull/22)) via [@rommex](https://github.com/rommex) 42 | 43 | ## 0.1.6 44 | - Downgrade minimum support version ([#20](https://github.com/unstoppabledomains/resolution-swift/pull/20)) via [@vladyslav-iosdev](https://github.com/vladyslav-iosdev) 45 | - iOS11 support in swiftPM 46 | 47 | ## 0.1.4 48 | - Batch Request for owners of domains 49 | 50 | ## 0.1.2 51 | - Ininial release 52 | -------------------------------------------------------------------------------- /Sources/UnstoppableDomainsResolution/APIRequest.swift: -------------------------------------------------------------------------------- 1 | // 2 | // APIRequest.swift 3 | // resolution 4 | // 5 | // Created by Johnny Good on 8/19/20. 6 | // Copyright © 2020 Unstoppable Domains. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | public enum APIError: Error { 12 | case responseError 13 | case decodingError 14 | case encodingError 15 | } 16 | 17 | public typealias JsonRpcResponseArray = [JsonRpcResponse] 18 | 19 | public protocol NetworkingLayer { 20 | func makeHttpPostRequest (url: URL, 21 | httpMethod: String, 22 | httpHeaderContentType: String, 23 | httpBody: Data, 24 | completion: @escaping(Result) -> Void) 25 | } 26 | 27 | struct APIRequest { 28 | let url: URL 29 | let networking: NetworkingLayer 30 | 31 | init(_ endpoint: String, networking: NetworkingLayer) { 32 | self.url = URL(string: endpoint)! 33 | self.networking = networking 34 | } 35 | 36 | func post(_ body: JsonRpcPayload, completion: @escaping(Result) -> Void ) throws { 37 | do { 38 | networking.makeHttpPostRequest(url: self.url, 39 | httpMethod: "POST", 40 | httpHeaderContentType: "application/json", 41 | httpBody: try JSONEncoder().encode(body), 42 | completion: completion) 43 | } catch { throw APIError.encodingError } 44 | } 45 | 46 | func post(_ bodyArray: [JsonRpcPayload], completion: @escaping(Result) -> Void ) throws { 47 | do { 48 | networking.makeHttpPostRequest(url: self.url, 49 | httpMethod: "POST", 50 | httpHeaderContentType: "application/json", 51 | httpBody: try JSONEncoder().encode(bodyArray), 52 | completion: completion) 53 | } catch { throw APIError.encodingError } 54 | } 55 | } 56 | 57 | public struct DefaultNetworkingLayer: NetworkingLayer { 58 | public init() { } 59 | 60 | public func makeHttpPostRequest(url: URL, 61 | httpMethod: String, 62 | httpHeaderContentType: String, 63 | httpBody: Data, 64 | completion: @escaping(Result) -> Void) { 65 | var urlRequest = URLRequest(url: url) 66 | urlRequest.httpMethod = httpMethod 67 | urlRequest.addValue(httpHeaderContentType, forHTTPHeaderField: "Content-Type") 68 | urlRequest.httpBody = httpBody 69 | 70 | let dataTask = URLSession.shared.dataTask(with: urlRequest) { data, response, _ in 71 | guard let httpResponse = response as? HTTPURLResponse, 72 | httpResponse.statusCode == 200, 73 | let jsonData = data else { 74 | completion(.failure(APIError.responseError)) 75 | return 76 | } 77 | 78 | do { 79 | let result = try JSONDecoder().decode(JsonRpcResponseArray.self, from: jsonData) 80 | completion(.success(result)) 81 | } catch { 82 | do { 83 | let result = try JSONDecoder().decode(JsonRpcResponse.self, from: jsonData) 84 | completion(.success([result])) 85 | } catch { 86 | if let errorResponse = try? JSONDecoder().decode(NetworkErrorResponse.self, from: jsonData), 87 | let errorExplained = ResolutionError.parse(errorResponse: errorResponse) { 88 | completion(.failure(errorExplained)) 89 | } else { 90 | completion(.failure(APIError.decodingError)) 91 | } 92 | } 93 | } 94 | } 95 | dataTask.resume() 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /Sources/UnstoppableDomainsResolution/Helpers/DNS/DnsUtils.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DnsUtils.swift 3 | // UnstoppableDomainsResolution 4 | // 5 | // Created by Johnny Good on 12/18/20. 6 | // Copyright © 2020 Unstoppable Domains. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | public struct DnsRecord: Equatable { 12 | var ttl: Int 13 | var type: String 14 | var data: String 15 | 16 | static public func == (lhs: DnsRecord, rhs: DnsRecord) -> Bool { 17 | return lhs.ttl == rhs.ttl && lhs.type == rhs.type && lhs.data == rhs.data 18 | } 19 | } 20 | 21 | public class DnsUtils { 22 | init() {} 23 | 24 | static let DefaultTtl: Int = 300 25 | 26 | public func toList(map: [String: String]) throws -> [DnsRecord] { 27 | let dnsTypes = self.getAllDnsTypes(map: map) 28 | var recordList: [DnsRecord] = [] 29 | for type in dnsTypes { 30 | recordList += try self.constructDnsRecord(map: map, type: type) 31 | } 32 | return recordList 33 | } 34 | 35 | public func toMap(records: [DnsRecord]) throws -> [String: String] { 36 | var map: [String: String] = [:] 37 | for record in records { 38 | 39 | if let ttlInMap = map["dns.\(record.type).ttl"], 40 | let ttl = Int(ttlInMap) { 41 | 42 | guard ttl == record.ttl else { 43 | throw DnsRecordsError.inconsistentTtl(recordType: DnsType(rawValue: record.type)!) 44 | } 45 | } 46 | 47 | guard let dnsArrayInMap: String = map["dns.\(record.type)"] else { 48 | map["dns.\(record.type)"] = self.toJsonString(from: [record.data]) 49 | map["dns.\(record.type).ttl"] = String(record.ttl) 50 | continue 51 | } 52 | var dnsArray = try toStringArray(fromJsonString: dnsArrayInMap) 53 | dnsArray.append(record.data) 54 | map["dns.\(record.type)"] = toJsonString(from: dnsArray) 55 | } 56 | return map 57 | } 58 | 59 | private func constructDnsRecord(map: [String: String], type: DnsType) throws -> [DnsRecord] { 60 | var dnsRecords: [DnsRecord] = [] 61 | let ttl: Int = self.parseTtl(map: map, type: type) 62 | guard let jsonValueString: String = map["dns.\(type)"] else { 63 | return [] 64 | } 65 | do { 66 | let recordDataArray = try self.toStringArray(fromJsonString: jsonValueString) 67 | for record in recordDataArray { 68 | dnsRecords.append(DnsRecord(ttl: ttl, type: "\(type)", data: record)) 69 | } 70 | return dnsRecords 71 | } catch { 72 | throw DnsRecordsError.dnsRecordCorrupted(recordType: type) 73 | } 74 | } 75 | 76 | private func getAllDnsTypes(map: [String: String]) -> [DnsType] { 77 | var types: Set = [] 78 | for (key, _) in map { 79 | let chunks: [String] = key.components(separatedBy: ".") 80 | if chunks.count >= 1 && chunks[1] != "ttl" { 81 | if let type = DnsType(rawValue: chunks[1]) { 82 | types.insert(type) 83 | } 84 | } 85 | } 86 | return Array(types) 87 | } 88 | 89 | private func parseTtl(map: [String: String], type: DnsType) -> Int { 90 | if let recordTtl = Int(map["dns.\(type).ttl"]!) { 91 | return recordTtl 92 | } 93 | if let defaultRecordTtl = Int(map["dns.ttl"]!) { 94 | return defaultRecordTtl 95 | } 96 | return DnsUtils.DefaultTtl 97 | } 98 | 99 | private func toJsonString(from object: Any) -> String? { 100 | guard let data = try? JSONSerialization.data(withJSONObject: object, options: []) else { 101 | return nil 102 | } 103 | return String(data: data, encoding: String.Encoding.utf8) 104 | } 105 | 106 | private func toStringArray(fromJsonString str: String) throws -> [String] { 107 | let data = Data(str.utf8) 108 | // swiftlint:disable force_cast 109 | return try JSONSerialization.jsonObject(with: data) as! [String] 110 | // swiftlint:enable force_cast 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /Sources/UnstoppableDomainsResolution/Resources/CNS/network-config.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "1.1.0", 3 | "networks": { 4 | "1": { 5 | "contracts": { 6 | "Registry": { 7 | "address": "0xD1E5b0FF1287aA9f9A268759062E4Ab08b9Dacbe", 8 | "legacyAddresses": [] 9 | }, 10 | "SignatureController": { 11 | "address": "0x82EF94294C95aD0930055f31e53A34509227c5f7", 12 | "legacyAddresses": [] 13 | }, 14 | "MintingController": { 15 | "address": "0xb0EE56339C3253361730F50c08d3d7817ecD60Ca", 16 | "legacyAddresses": [] 17 | }, 18 | "WhitelistedMinter": { 19 | "address": "0xd3fF3377b0ceade1303dAF9Db04068ef8a650757", 20 | "legacyAddresses": [] 21 | }, 22 | "URIPrefixController": { 23 | "address": "0x09B091492759737C03da9dB7eDF1CD6BCC3A9d91", 24 | "legacyAddresses": [] 25 | }, 26 | "DomainZoneController": { 27 | "address": "0xeA70777e28E00E81f58b8921fC47F78B8a72eFE7", 28 | "legacyAddresses": [] 29 | }, 30 | "Resolver": { 31 | "address": "0xb66DcE2DA6afAAa98F2013446dBCB0f4B0ab2842", 32 | "legacyAddresses": [ 33 | "0xa1cac442be6673c49f8e74ffc7c4fd746f3cbd0d", 34 | "0x878bc2f3f717766ab69c0a5f9a6144931e61aed3" 35 | ] 36 | }, 37 | "ProxyReader": { 38 | "address": "0xa6E7cEf2EDDEA66352Fd68E5915b60BDbb7309f5", 39 | "legacyAddresses": [ 40 | "0x7ea9Ee21077F84339eDa9C80048ec6db678642B1" 41 | ] 42 | }, 43 | "TwitterValidationOperator": { 44 | "address": "0xbb486C6E9cF1faA86a6E3eAAFE2e5665C0507855", 45 | "legacyAddresses": [] 46 | }, 47 | "FreeMinter": { 48 | "address": "0x1fC985cAc641ED5846b631f96F35d9b48Bc3b834", 49 | "legacyAddresses": [] 50 | } 51 | } 52 | }, 53 | "4": { 54 | "contracts": { 55 | "Registry": { 56 | "address": "0xAad76bea7CFEc82927239415BB18D2e93518ecBB", 57 | "legacyAddresses": [] 58 | }, 59 | "SignatureController": { 60 | "address": "0x66a5e3e2C27B4ce4F46BBd975270BE154748D164", 61 | "legacyAddresses": [] 62 | }, 63 | "MintingController": { 64 | "address": "0x51765307AeB3Df2E647014a2C501d5324212467c", 65 | "legacyAddresses": [] 66 | }, 67 | "WhitelistedMinter": { 68 | "address": "0xbcB32f13f90978a9e059E8Cb40FaA9e6619d98e7", 69 | "legacyAddresses": [] 70 | }, 71 | "URIPrefixController": { 72 | "address": "0xe1d2e4B9f0518CA5c803073C3dFa886470627237", 73 | "legacyAddresses": [] 74 | }, 75 | "DomainZoneController": { 76 | "address": "0x6f8F96A566663C1d4fEe70edD37E9b62Fe39dE5D", 77 | "legacyAddresses": [] 78 | }, 79 | "Resolver": { 80 | "address": "0x95AE1515367aa64C462c71e87157771165B1287A", 81 | "legacyAddresses": [] 82 | }, 83 | "ProxyReader": { 84 | "address": "0x3A2e74CF832cbA3d77E72708d55370119E4323a6", 85 | "legacyAddresses": [] 86 | }, 87 | "TwitterValidationOperator": { 88 | "address": "0x1CB337b3b208dc29a6AcE8d11Bb591b66c5Dd83d", 89 | "legacyAddresses": [] 90 | }, 91 | "FreeMinter": { 92 | "address": "0x84214215904cDEbA9044ECf95F3eBF009185AAf4", 93 | "legacyAddresses": [] 94 | } 95 | } 96 | } 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /Sources/UnstoppableDomainsResolution/Contract.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Contract.swift 3 | // resolution 4 | // 5 | // Created by Johnny Good on 8/12/20. 6 | // Copyright © 2020 Unstoppable Domains. All rights reserved. 7 | // 8 | 9 | struct IdentifiableResult { 10 | var id: String 11 | var result: T 12 | } 13 | 14 | import Foundation 15 | internal class Contract { 16 | let batchIdOffset = 128 17 | 18 | static let ownersKey = "owners" 19 | static let resolversKey = "resolvers" 20 | static let valuesKey = "values" 21 | 22 | let address: String 23 | let providerUrl: String 24 | let coder: ABICoder 25 | let networking: NetworkingLayer 26 | 27 | init(providerUrl: String, address: String, abi: ABIContract, networking: NetworkingLayer) { 28 | self.address = address 29 | self.providerUrl = providerUrl 30 | self.coder = ABICoder(abi) 31 | self.networking = networking 32 | } 33 | 34 | func callMethod(methodName: String, args: [Any]) throws -> Any { 35 | let encodedData = try self.coder.encode(method: methodName, args: args) 36 | let body = JsonRpcPayload(id: "1", data: encodedData, to: address) 37 | let response = try postRequest(body)! 38 | return try self.coder.decode(response, from: methodName) 39 | } 40 | 41 | func callBatchMethod(methodName: String, argsArray: [[Any]]) throws -> [IdentifiableResult] { 42 | let encodedDataArray = try argsArray.map { try self.coder.encode(method: methodName, args: $0) } 43 | let bodyArray: [JsonRpcPayload] = encodedDataArray.enumerated() 44 | .map { JsonRpcPayload(id: String($0.offset + batchIdOffset), 45 | data: $0.element, 46 | to: address) } 47 | let response = try postBatchRequest(bodyArray) 48 | return try response.map { 49 | guard let responseElement = $0 else { 50 | throw ResolutionError.recordNotSupported 51 | } 52 | 53 | var res: Any? 54 | do { 55 | res = try self.coder.decode(responseElement.result, from: methodName) 56 | } catch ABICoderError.couldNotDecode { 57 | res = nil 58 | } 59 | return IdentifiableResult(id: responseElement.id, result: res) 60 | } 61 | } 62 | 63 | private func postRequest(_ body: JsonRpcPayload) throws -> String? { 64 | let postRequest = APIRequest(providerUrl, networking: networking) 65 | var resp: JsonRpcResponseArray? 66 | var err: Error? 67 | let semaphore = DispatchSemaphore(value: 0) 68 | try postRequest.post(body, completion: {result in 69 | switch result { 70 | case .success(let response): 71 | resp = response 72 | case .failure(let error): 73 | err = error 74 | } 75 | semaphore.signal() 76 | }) 77 | semaphore.wait() 78 | guard err == nil else { 79 | throw err! 80 | } 81 | switch resp?[0].result { 82 | case .string(let result): 83 | return result 84 | default: 85 | return nil 86 | } 87 | } 88 | 89 | private func postBatchRequest(_ bodyArray: [JsonRpcPayload]) throws -> [IdentifiableResult?] { 90 | let postRequest = APIRequest(providerUrl, networking: networking) 91 | var resp: JsonRpcResponseArray? 92 | var err: Error? 93 | let semaphore = DispatchSemaphore(value: 0) 94 | try postRequest.post(bodyArray, completion: {result in 95 | switch result { 96 | case .success(let response): 97 | resp = response 98 | case .failure(let error): 99 | err = error 100 | } 101 | semaphore.signal() 102 | }) 103 | semaphore.wait() 104 | guard err == nil else { 105 | throw err! 106 | } 107 | 108 | guard let responseArray = resp else { throw APIError.decodingError } 109 | 110 | let parsedResponseArray: [IdentifiableResult?] = responseArray.map { 111 | if case let ParamElement.string( stringResult ) = $0.result { 112 | return IdentifiableResult(id: $0.id, result: stringResult) 113 | } 114 | return nil 115 | } 116 | return parsedResponseArray 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /.swiftpm/xcode/xcshareddata/xcschemes/UnstoppableDomainsResolution.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 29 | 35 | 36 | 37 | 43 | 49 | 50 | 51 | 52 | 53 | 58 | 59 | 61 | 67 | 68 | 69 | 70 | 71 | 81 | 82 | 88 | 89 | 95 | 96 | 97 | 98 | 100 | 101 | 104 | 105 | 106 | -------------------------------------------------------------------------------- /Sources/UnstoppableDomainsResolution/Support/Base58Swift/Base58.swift: -------------------------------------------------------------------------------- 1 | // Copyright Keefer Taylor, 2019. 2 | 3 | import BigInt 4 | import CommonCrypto 5 | import Foundation 6 | 7 | /// A static utility class which provides Base58 encoding and decoding functionality. 8 | public enum Base58 { 9 | /// Length of checksum appended to Base58Check encoded strings. 10 | private static let checksumLength = 4 11 | 12 | private static let alphabet = [UInt8]("123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz".utf8) 13 | private static let zero = BigUInt(0) 14 | private static let radix = BigUInt(alphabet.count) 15 | 16 | /// Encode the given bytes into a Base58Check encoded string. 17 | /// - Parameter bytes: The bytes to encode. 18 | /// - Returns: A base58check encoded string representing the given bytes, or nil if encoding failed. 19 | public static func base58CheckEncode(_ bytes: [UInt8]) -> String { 20 | let checksum = calculateChecksum(bytes) 21 | let checksummedBytes = bytes + checksum 22 | return Base58.base58Encode(checksummedBytes) 23 | } 24 | 25 | /// Decode the given Base58Check encoded string to bytes. 26 | /// - Parameter input: A base58check encoded input string to decode. 27 | /// - Returns: Bytes representing the decoded input, or nil if decoding failed. 28 | public static func base58CheckDecode(_ input: String) -> [UInt8]? { 29 | guard let decodedChecksummedBytes = base58Decode(input) else { 30 | return nil 31 | } 32 | 33 | let decodedChecksum = decodedChecksummedBytes.suffix(checksumLength) 34 | let decodedBytes = decodedChecksummedBytes.prefix(upTo: decodedChecksummedBytes.count - checksumLength) 35 | let calculatedChecksum = calculateChecksum([UInt8](decodedBytes)) 36 | 37 | guard decodedChecksum.elementsEqual(calculatedChecksum, by: { $0 == $1 }) else { 38 | return nil 39 | } 40 | return Array(decodedBytes) 41 | } 42 | 43 | /// Encode the given bytes to a Base58 encoded string. 44 | /// - Parameter bytes: The bytes to encode. 45 | /// - Returns: A base58 encoded string representing the given bytes, or nil if encoding failed. 46 | public static func base58Encode(_ bytes: [UInt8]) -> String { 47 | var answer: [UInt8] = [] 48 | var integerBytes = BigUInt(Data(bytes)) 49 | 50 | while integerBytes > 0 { 51 | let (quotient, remainder) = integerBytes.quotientAndRemainder(dividingBy: radix) 52 | answer.insert(alphabet[Int(remainder)], at: 0) 53 | integerBytes = quotient 54 | } 55 | 56 | let prefix = Array(bytes.prefix { $0 == 0 }).map { _ in alphabet[0] } 57 | answer.insert(contentsOf: prefix, at: 0) 58 | 59 | // swiftlint:disable force_unwrapping 60 | // Force unwrap as the given alphabet will always decode to UTF8. 61 | return String(bytes: answer, encoding: String.Encoding.utf8)! 62 | // swiftlint:enable force_unwrapping 63 | } 64 | 65 | /// Decode the given base58 encoded string to bytes. 66 | /// - Parameter input: The base58 encoded input string to decode. 67 | /// - Returns: Bytes representing the decoded input, or nil if decoding failed. 68 | public static func base58Decode(_ input: String) -> [UInt8]? { 69 | var answer = zero 70 | var i = BigUInt(1) 71 | let byteString = [UInt8](input.utf8) 72 | 73 | for char in byteString.reversed() { 74 | guard let alphabetIndex = alphabet.firstIndex(of: char) else { 75 | return nil 76 | } 77 | answer += (i * BigUInt(alphabetIndex)) 78 | i *= radix 79 | } 80 | 81 | let bytes = answer.serialize() 82 | return Array(byteString.prefix { i in i == alphabet[0] }) + bytes 83 | } 84 | 85 | /// Calculate a checksum for a given input by hashing twice and then taking the first four bytes. 86 | /// - Parameter input: The input bytes. 87 | /// - Returns: A byte array representing the checksum of the input bytes. 88 | private static func calculateChecksum(_ input: [UInt8]) -> [UInt8] { 89 | let hashedData = sha256(input) 90 | let doubleHashedData = sha256(hashedData) 91 | let doubleHashedArray = Array(doubleHashedData) 92 | return Array(doubleHashedArray.prefix(checksumLength)) 93 | } 94 | 95 | /// Create a sha256 hash of the given data. 96 | /// - Parameter data: Input data to hash. 97 | /// - Returns: A sha256 hash of the input data. 98 | private static func sha256(_ data: [UInt8]) -> [UInt8] { 99 | let res = NSMutableData(length: Int(CC_SHA256_DIGEST_LENGTH))! 100 | CC_SHA256( 101 | (Data(data) as NSData).bytes, 102 | CC_LONG(data.count), 103 | res.mutableBytes.assumingMemoryBound(to: UInt8.self) 104 | ) 105 | return [UInt8](res as Data) 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /Sources/UnstoppableDomainsResolution/NamingServices/ZNS.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ZNS.swift 3 | // Resolution 4 | // 5 | // Created by Serg Merenkov on 9/8/20. 6 | // Copyright © 2020 Unstoppable Domains. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | internal class ZNS: CommonNamingService, NamingService { 12 | var network: String 13 | 14 | let registryAddress: String 15 | let registryMap: [String: String] = [ 16 | "mainnet": "0x9611c53be6d1b32058b2747bdececed7e1216793", 17 | "testnet": "0xB925adD1d5EaF13f40efD43451bF97A22aB3d727" 18 | ] 19 | 20 | init(_ config: NamingServiceConfig) throws { 21 | 22 | guard let registryAddress = registryMap[config.network] else { 23 | throw ResolutionError.unsupportedNetwork 24 | } 25 | self.network = config.network 26 | 27 | self.registryAddress = registryAddress 28 | super.init(name: "ZNS", providerUrl: config.providerUrl, networking: config.networking) 29 | } 30 | 31 | func isSupported(domain: String) -> Bool { 32 | return domain.hasSuffix(".zil") 33 | } 34 | 35 | func owner(domain: String) throws -> String { 36 | let recordAddresses = try self.recordsAddresses(domain: domain) 37 | let (ownerAddress, _ ) = recordAddresses 38 | guard Utillities.isNotEmpty(ownerAddress) else { 39 | throw ResolutionError.unregisteredDomain 40 | } 41 | 42 | return ownerAddress 43 | } 44 | 45 | func batchOwners(domains: [String]) throws -> [String?] { 46 | throw ResolutionError.methodNotSupported 47 | } 48 | 49 | func addr(domain: String, ticker: String) throws -> String { 50 | let key = "crypto.\(ticker.uppercased()).address" 51 | let result = try record(domain: domain, key: key) 52 | return result 53 | } 54 | 55 | func record(domain: String, key: String) throws -> String { 56 | let records = try self.records(keys: [key], for: domain) 57 | 58 | guard 59 | let record = records[key] else { 60 | throw ResolutionError.recordNotFound 61 | } 62 | 63 | return record 64 | } 65 | 66 | func records(keys: [String], for domain: String) throws -> [String: String] { 67 | guard let records = try self.records(address: try resolver(domain: domain), keys: []) as? [String: String] else { 68 | throw ResolutionError.recordNotFound 69 | } 70 | let filtered = records.filter { keys.contains($0.key) } 71 | return filtered 72 | } 73 | 74 | // MARK: - get Resolver 75 | func resolver(domain: String) throws -> String { 76 | let recordAddresses = try self.recordsAddresses(domain: domain) 77 | let (_, resolverAddress ) = recordAddresses 78 | guard Utillities.isNotEmpty(resolverAddress) else { 79 | throw ResolutionError.unspecifiedResolver 80 | } 81 | 82 | return resolverAddress 83 | } 84 | 85 | // MARK: - CommonNamingService 86 | override func childHash(parent: [UInt8], label: [UInt8]) -> [UInt8] { 87 | return (parent + label.sha2(.sha256)).sha2(.sha256) 88 | } 89 | 90 | // MARK: - Helper functions 91 | 92 | private func recordsAddresses(domain: String) throws -> (String, String) { 93 | 94 | if !self.isSupported(domain: domain) { 95 | throw ResolutionError.unsupportedDomain 96 | } 97 | 98 | let namehash = self.namehash(domain: domain) 99 | let records = try self.records(address: self.registryAddress, keys: [namehash]) 100 | 101 | guard 102 | let record = records[namehash] as? [String: Any], 103 | let arguments = record["arguments"] as? [Any], arguments.count == 2, 104 | let ownerAddress = arguments[0] as? String, let resolverAddress = arguments[1] as? String 105 | else { 106 | throw ResolutionError.unregisteredDomain 107 | } 108 | 109 | return (ownerAddress, resolverAddress) 110 | } 111 | 112 | private func records(address: String, keys: [String] = []) throws -> [String: Any] { 113 | let resolverContract: ContractZNS = self.buildContract(address: address) 114 | 115 | guard let records = try resolverContract.fetchSubState( 116 | field: "records", 117 | keys: keys 118 | ) as? [String: Any] 119 | else { 120 | throw ResolutionError.unspecifiedResolver 121 | } 122 | 123 | return records 124 | } 125 | 126 | func buildContract(address: String) -> ContractZNS { 127 | return ContractZNS(providerUrl: self.providerUrl, address: address.replacingOccurrences(of: "0x", with: ""), networking: networking) 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /Sources/UnstoppableDomainsResolution/ABI/EthereumABI/ABITypeParser.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ABITypeParser.swift 3 | // resolution 4 | // 5 | // Created by Serg Merenkov on 2/8/21. 6 | // Copyright © 2021 Unstoppable Domains. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | public struct ABITypeParser { 12 | 13 | private enum BaseParameterType: String { 14 | case address 15 | case uint 16 | case int 17 | case bool 18 | case function 19 | case bytes 20 | case string 21 | case tuple 22 | } 23 | 24 | static func baseTypeMatch(from string: String, length: UInt64 = 0) -> ABI.Element.ParameterType? { 25 | switch BaseParameterType(rawValue: string) { 26 | case .address?: 27 | return .address 28 | case .uint?: 29 | return .uint(bits: length == 0 ? 256: length) 30 | case .int?: 31 | return .int(bits: length == 0 ? 256: length) 32 | case .bool?: 33 | return .bool 34 | case .function?: 35 | return .function 36 | case .bytes?: 37 | if length == 0 { 38 | return .dynamicBytes 39 | } 40 | return .bytes(length: length) 41 | case .string?: 42 | return .string 43 | case .tuple?: 44 | return .tuple(types: [ABI.Element.ParameterType]()) 45 | default: 46 | return nil 47 | } 48 | } 49 | 50 | public static func parseTypeString(_ string: String) throws -> ABI.Element.ParameterType { 51 | let (type, tail) = recursiveParseType(string) 52 | guard let t = type, tail == nil else {throw ABI.ParsingError.elementTypeInvalid} 53 | return t 54 | } 55 | // swiftlint:disable force_try 56 | static func recursiveParseType(_ string: String) -> (type: ABI.Element.ParameterType?, tail: String?) { 57 | let matcher = try! NSRegularExpression( 58 | pattern: ABI.TypeParsingExpressions.typeEatingRegex, 59 | options: NSRegularExpression.Options.dotMatchesLineSeparators 60 | ) 61 | let match = matcher.matches(in: string, options: NSRegularExpression.MatchingOptions.anchored, range: string.fullNSRange) 62 | guard match.count == 1 else { 63 | return (nil, nil) 64 | } 65 | var tail: String = "" 66 | var type: ABI.Element.ParameterType? 67 | guard match[0].numberOfRanges >= 1 else {return (nil, nil)} 68 | guard let baseTypeRange = Range(match[0].range(at: 1), in: string) else {return (nil, nil)} 69 | let baseTypeString = String(string[baseTypeRange]) 70 | if match[0].numberOfRanges >= 2, let exactTypeRange = Range(match[0].range(at: 2), in: string) { 71 | let typeString = String(string[exactTypeRange]) 72 | if match[0].numberOfRanges >= 3, let lengthRange = Range(match[0].range(at: 3), in: string) { 73 | let lengthString = String(string[lengthRange]) 74 | guard let typeLength = UInt64(lengthString) else {return (nil, nil)} 75 | guard let baseType = baseTypeMatch(from: typeString, length: typeLength) else {return (nil, nil)} 76 | type = baseType 77 | } else { 78 | guard let baseType = baseTypeMatch(from: typeString, length: 0) else {return (nil, nil)} 79 | type = baseType 80 | } 81 | } else { 82 | guard let baseType = baseTypeMatch(from: baseTypeString, length: 0) else {return (nil, nil)} 83 | type = baseType 84 | } 85 | tail = string.replacingCharacters(in: string.range(of: baseTypeString)!, with: "") 86 | if tail == "" { 87 | return (type, nil) 88 | } 89 | return recursiveParseArray(baseType: type!, string: tail) 90 | } 91 | 92 | static func recursiveParseArray(baseType: ABI.Element.ParameterType, string: String) -> (type: ABI.Element.ParameterType?, tail: String?) { 93 | let matcher = try! NSRegularExpression( 94 | pattern: ABI.TypeParsingExpressions.arrayEatingRegex, 95 | options: NSRegularExpression.Options.dotMatchesLineSeparators 96 | ) 97 | let match = matcher.matches(in: string, options: NSRegularExpression.MatchingOptions.anchored, range: string.fullNSRange) 98 | guard match.count == 1 else {return (nil, nil)} 99 | var tail: String = "" 100 | var type: ABI.Element.ParameterType? 101 | guard match[0].numberOfRanges >= 1 else {return (nil, nil)} 102 | guard let baseArrayRange = Range(match[0].range(at: 1), in: string) else {return (nil, nil)} 103 | let baseArrayString = String(string[baseArrayRange]) 104 | if match[0].numberOfRanges >= 2, let exactArrayRange = Range(match[0].range(at: 2), in: string) { 105 | let lengthString = String(string[exactArrayRange]) 106 | guard let arrayLength = UInt64(lengthString) else {return (nil, nil)} 107 | let baseType = ABI.Element.ParameterType.array(type: baseType, length: arrayLength) 108 | type = baseType 109 | } else { 110 | let baseType = ABI.Element.ParameterType.array(type: baseType, length: 0) 111 | type = baseType 112 | } 113 | tail = string.replacingCharacters(in: string.range(of: baseArrayString)!, with: "") 114 | if tail == "" { 115 | return (type, nil) 116 | } 117 | return recursiveParseArray(baseType: type!, string: tail) 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /Sources/UnstoppableDomainsResolution/NamingServices/CommonNamingService.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CommonNamingService.swift 3 | // resolution 4 | // 5 | // Created by Johnny Good on 8/19/20. 6 | // Copyright © 2020 Unstoppable Domains. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | class CommonNamingService { 12 | static let hexadecimalPrefix = "0x" 13 | static let jsonExtension = "json" 14 | 15 | let name: String 16 | let providerUrl: String 17 | let networking: NetworkingLayer 18 | 19 | enum ContractType: String { 20 | case registry = "Registry" 21 | case resolver = "Resolver" 22 | case proxyReader = "ProxyReader" 23 | 24 | var name: String { 25 | self.rawValue 26 | } 27 | } 28 | 29 | init(name: String, providerUrl: String, networking: NetworkingLayer) { 30 | self.name = name 31 | self.providerUrl = providerUrl 32 | self.networking = networking 33 | } 34 | 35 | func buildContract(address: String, type: ContractType) throws -> Contract { 36 | let jsonFileName: String 37 | 38 | let nameLowCased = name.lowercased() 39 | switch type { 40 | case .registry: 41 | jsonFileName = "\(nameLowCased)Registry" 42 | case .resolver: 43 | jsonFileName = "\(nameLowCased)Resolver" 44 | case .proxyReader: 45 | jsonFileName = "\(nameLowCased)ProxyReader" 46 | } 47 | 48 | let abi: ABIContract = try parseAbi(fromFile: jsonFileName)! 49 | return Contract(providerUrl: self.providerUrl, address: address, abi: abi, networking: networking) 50 | } 51 | 52 | func parseAbi(fromFile name: String) throws -> ABIContract? { 53 | #if INSIDE_PM 54 | let bundler = Bundle.module 55 | #else 56 | let bundler = Bundle(for: type(of: self)) 57 | #endif 58 | if let filePath = bundler.url(forResource: name, withExtension: "json") { 59 | let data = try Data(contentsOf: filePath) 60 | let jsonDecoder = JSONDecoder() 61 | let abi = try jsonDecoder.decode([ABI.Record].self, from: data) 62 | let abiNative = try abi.map({ (record) -> ABI.Element in 63 | return try record.parse() 64 | }) 65 | 66 | return abiNative 67 | } 68 | return nil 69 | } 70 | 71 | func namehash(domain: String) -> String { 72 | var node = [UInt8].init(repeating: 0x0, count: 32) 73 | if domain.count > 0 { 74 | node = domain.split(separator: ".") 75 | .map { Array($0.utf8)} 76 | .reversed() 77 | .reduce(node) { return self.childHash(parent: $0, label: $1)} 78 | } 79 | return "\(Self.hexadecimalPrefix)\(node.toHexString())" 80 | } 81 | 82 | func childHash(parent: [UInt8], label: [UInt8]) -> [UInt8] { 83 | let childHash = label.sha3(.keccak256) 84 | return (parent + childHash).sha3(.keccak256) 85 | } 86 | } 87 | 88 | extension CommonNamingService { 89 | static let networkConfigFileName = "network-config" 90 | static let networkIds = ["mainnet": "1", 91 | "ropsten": "3", 92 | "rinkeby": "4", 93 | "goerli": "5"] 94 | 95 | struct NewtorkConfigJson: Decodable { 96 | let version: String 97 | let networks: [String: ContractsEntry] 98 | } 99 | 100 | struct ContractsEntry: Decodable { 101 | let contracts: [String: ContractAddressEntry] 102 | } 103 | 104 | struct ContractAddressEntry: Decodable { 105 | let address: String 106 | let legacyAddresses: [String] 107 | } 108 | 109 | static func parseContractAddresses(network: String) throws -> [String: ContractAddressEntry]? { 110 | #if INSIDE_PM 111 | let bundler = Bundle.module 112 | #else 113 | let bundler = Bundle(for: self) 114 | #endif 115 | 116 | guard let idString = networkIds[network] else { throw ResolutionError.unsupportedNetwork } 117 | 118 | if let filePath = bundler.url(forResource: Self.networkConfigFileName, withExtension: "json") { 119 | guard let data = try? Data(contentsOf: filePath) else { return nil } 120 | guard let info = try? JSONDecoder().decode(NewtorkConfigJson.self, from: data) else { return nil } 121 | guard let currentNetwork = info.networks[idString] else { 122 | return nil 123 | } 124 | return currentNetwork.contracts 125 | } 126 | return nil 127 | } 128 | 129 | static func getNetworkId(providerUrl: String, networking: NetworkingLayer) throws -> String { 130 | let url = URL(string: providerUrl)! 131 | let payload: JsonRpcPayload = JsonRpcPayload(jsonrpc: "2.0", id: "67", method: "net_version", params: []) 132 | 133 | var resp: JsonRpcResponseArray? 134 | var err: Error? 135 | let semaphore = DispatchSemaphore(value: 0) 136 | 137 | networking.makeHttpPostRequest( 138 | url: url, 139 | httpMethod: "POST", 140 | httpHeaderContentType: "application/json", 141 | httpBody: try JSONEncoder().encode(payload) 142 | ) { result in 143 | switch result { 144 | case .success(let response): 145 | resp = response 146 | case .failure(let error): 147 | err = error 148 | } 149 | semaphore.signal() 150 | } 151 | semaphore.wait() 152 | guard err == nil else { 153 | throw err! 154 | } 155 | switch resp?[0].result { 156 | case .string(let result): 157 | return networkIds.key(forValue: result) ?? "" 158 | default: 159 | return "" 160 | } 161 | } 162 | } 163 | 164 | fileprivate extension Dictionary where Value: Equatable { 165 | func key(forValue value: Value) -> Key? { 166 | return first { $0.1 == value }?.0 167 | } 168 | } 169 | -------------------------------------------------------------------------------- /Sources/UnstoppableDomainsResolution/NamingServices/ENS.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CNS.swift 3 | // resolution 4 | // 5 | // Created by Serg Merenkov on 9/14/20. 6 | // Copyright © 2020 Unstoppable Domains. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import EthereumAddress 11 | 12 | internal class ENS: CommonNamingService, NamingService { 13 | let network: String 14 | let registryAddress: String 15 | let registryMap: [String: String] = [ 16 | "mainnet": "0x00000000000C2E074eC69A0dFb2997BA6C7d2e1e", 17 | "ropsten": "0x00000000000C2E074eC69A0dFb2997BA6C7d2e1e", 18 | "rinkeby": "0x00000000000C2E074eC69A0dFb2997BA6C7d2e1e", 19 | "goerli": "0x00000000000C2E074eC69A0dFb2997BA6C7d2e1e" 20 | ] 21 | 22 | init(_ config: NamingServiceConfig) throws { 23 | 24 | self.network = config.network.isEmpty 25 | ? try Self.getNetworkId(providerUrl: config.providerUrl, networking: config.networking) 26 | : config.network 27 | 28 | guard let registryAddress = registryMap[self.network] else { 29 | throw ResolutionError.unsupportedNetwork 30 | } 31 | self.registryAddress = registryAddress 32 | super.init(name: "ENS", providerUrl: config.providerUrl, networking: config.networking) 33 | } 34 | 35 | func isSupported(domain: String) -> Bool { 36 | return domain ~= "^[^-]*[^-]*\\.(eth|luxe|xyz|kred|addr\\.reverse)$" 37 | } 38 | 39 | func owner(domain: String) throws -> String { 40 | let tokenId = super.namehash(domain: domain) 41 | guard let ownerAddress = try askRegistryContract(for: "owner", with: [tokenId]), 42 | Utillities.isNotEmpty(ownerAddress) else { 43 | throw ResolutionError.unregisteredDomain 44 | } 45 | return ownerAddress 46 | } 47 | 48 | func batchOwners(domains: [String]) throws -> [String?] { 49 | throw ResolutionError.methodNotSupported 50 | } 51 | 52 | func addr(domain: String, ticker: String) throws -> String { 53 | guard ticker.uppercased() == "ETH" else { 54 | throw ResolutionError.recordNotSupported 55 | } 56 | 57 | let tokenId = super.namehash(domain: domain) 58 | let resolverAddress = try resolver(tokenId: tokenId) 59 | let resolverContract = try super.buildContract(address: resolverAddress, type: .resolver) 60 | 61 | guard let dict = try resolverContract.callMethod(methodName: "addr", args: [tokenId, ethCoinIndex]) as? [String: Data], 62 | let dataAddress = dict["0"], 63 | let address = EthereumAddress(dataAddress), 64 | Utillities.isNotEmpty(address.address) else { 65 | throw ResolutionError.recordNotFound 66 | } 67 | return address.address 68 | } 69 | 70 | // MARK: - Get Record 71 | func record(domain: String, key: String) throws -> String { 72 | let tokenId = super.namehash(domain: domain) 73 | return try self.record(tokenId: tokenId, key: key) 74 | } 75 | 76 | func record(tokenId: String, key: String) throws -> String { 77 | if key == "ipfs.html.value" { 78 | let hash = try self.getContentHash(tokenId: tokenId) 79 | return hash 80 | } 81 | 82 | let resolverAddress = try resolver(tokenId: tokenId) 83 | let resolverContract = try super.buildContract(address: resolverAddress, type: .resolver) 84 | 85 | let ensKeyName = self.fromUDNameToEns(record: key) 86 | 87 | guard let dict = try resolverContract.callMethod(methodName: "text", args: [tokenId, ensKeyName]) as? [String: String], 88 | let result = dict["0"], 89 | Utillities.isNotEmpty(result) else { 90 | throw ResolutionError.recordNotFound 91 | } 92 | return result 93 | } 94 | 95 | func records(keys: [String], for domain: String) throws -> [String: String] { 96 | // TODO: Add some batch request and collect all keys by few request 97 | throw ResolutionError.recordNotSupported 98 | } 99 | 100 | // MARK: - get Resolver 101 | func resolver(domain: String) throws -> String { 102 | let tokenId = super.namehash(domain: domain) 103 | return try self.resolver(tokenId: tokenId) 104 | } 105 | 106 | func resolver(tokenId: String) throws -> String { 107 | guard let resolverAddress = try askRegistryContract(for: "resolver", with: [tokenId]), 108 | Utillities.isNotEmpty(resolverAddress) else { 109 | throw ResolutionError.unspecifiedResolver 110 | } 111 | return resolverAddress 112 | } 113 | 114 | // MARK: - Helper functions 115 | private func askRegistryContract(for methodName: String, with args: [String]) throws -> String? { 116 | let registryContract: Contract = try super.buildContract(address: self.registryAddress, type: .registry) 117 | guard let ethereumAddress = try registryContract.callMethod(methodName: methodName, args: args) as? [String: EthereumAddress], 118 | let address = ethereumAddress["0"] else { 119 | return nil 120 | } 121 | return address.address 122 | } 123 | 124 | private func fromUDNameToEns(record: String) -> String { 125 | let mapper: [String: String] = [ 126 | "ipfs.redirect_domain.value": "url", 127 | "whois.email.value": "email", 128 | "gundb.username.value": "gundb_username", 129 | "gundb.public_key.value": "gundb_public_key" 130 | ] 131 | return mapper[record] ?? record 132 | } 133 | 134 | /* 135 | //https://ethereum.stackexchange.com/questions/17094/how-to-store-ipfs-hash-using-bytes32 136 | getIpfsHashFromBytes32(bytes32Hex) { 137 | // Add our default ipfs values for first 2 bytes: 138 | // function:0x12=sha2, size:0x20=256 bits 139 | // and cut off leading "0x" 140 | const hashHex = "1220" + bytes32Hex.slice(2) 141 | const hashBytes = Buffer.from(hashHex, 'hex'); 142 | const hashStr = bs58.encode(hashBytes) 143 | return hashStr 144 | } 145 | */ 146 | private func getContentHash(tokenId: String) throws -> String { 147 | 148 | let resolverAddress = try resolver(tokenId: tokenId) 149 | let resolverContract = try super.buildContract(address: resolverAddress, type: .resolver) 150 | 151 | let hash = try resolverContract.callMethod(methodName: "contenthash", args: [tokenId]) as? [String: Any] 152 | guard let data = hash?["0"] as? Data else { 153 | throw ResolutionError.recordNotFound 154 | } 155 | 156 | let contentHash = [UInt8](data) 157 | guard let codec = Array(contentHash[0..<1]).last, 158 | codec == 0xE3 // 'ipfs-ns' 159 | else { 160 | throw ResolutionError.recordNotFound 161 | } 162 | 163 | return Base58.base58Encode(Array(contentHash[4.. ABI.Element { 41 | let typeString = self.type != nil ? self.type! : "function" 42 | guard let type = ABI.ElementType(rawValue: typeString) else { 43 | throw ABI.ParsingError.elementTypeInvalid 44 | } 45 | return try parseToElement(from: self, type: type) 46 | } 47 | } 48 | 49 | private func parseToElement(from abiRecord: ABI.Record, type: ABI.ElementType) throws -> ABI.Element { 50 | switch type { 51 | case .function: 52 | let function = try parseFunction(abiRecord: abiRecord) 53 | return ABI.Element.function(function) 54 | case .constructor: 55 | let constructor = try parseConstructor(abiRecord: abiRecord) 56 | return ABI.Element.constructor(constructor) 57 | case .fallback: 58 | let fallback = try parseFallback(abiRecord: abiRecord) 59 | return ABI.Element.fallback(fallback) 60 | case .event: 61 | let event = try parseEvent(abiRecord: abiRecord) 62 | return ABI.Element.event(event) 63 | } 64 | 65 | } 66 | 67 | private func parseFunction(abiRecord: ABI.Record) throws -> ABI.Element.Function { 68 | let inputs = try abiRecord.inputs?.map({ (input: ABI.Input) throws -> ABI.Element.InOut in 69 | let nativeInput = try input.parse() 70 | return nativeInput 71 | }) 72 | let abiInputs = inputs != nil ? inputs! : [ABI.Element.InOut]() 73 | let outputs = try abiRecord.outputs?.map({ (output: ABI.Output) throws -> ABI.Element.InOut in 74 | let nativeOutput = try output.parse() 75 | return nativeOutput 76 | }) 77 | let abiOutputs = outputs != nil ? outputs! : [ABI.Element.InOut]() 78 | let name = abiRecord.name != nil ? abiRecord.name! : "" 79 | let payable = abiRecord.stateMutability != nil ? 80 | (abiRecord.stateMutability == "payable" || abiRecord.payable!) : false 81 | let constant = (abiRecord.constant == true || abiRecord.stateMutability == "view" || abiRecord.stateMutability == "pure") 82 | let functionElement = ABI.Element.Function(name: name, inputs: abiInputs, outputs: abiOutputs, constant: constant, payable: payable) 83 | return functionElement 84 | } 85 | 86 | private func parseFallback(abiRecord: ABI.Record) throws -> ABI.Element.Fallback { 87 | let payable = (abiRecord.stateMutability == "payable" || abiRecord.payable!) 88 | var constant = abiRecord.constant == true 89 | if abiRecord.stateMutability == "view" || abiRecord.stateMutability == "pure" { 90 | constant = true 91 | } 92 | let functionElement = ABI.Element.Fallback(constant: constant, payable: payable) 93 | return functionElement 94 | } 95 | 96 | private func parseConstructor(abiRecord: ABI.Record) throws -> ABI.Element.Constructor { 97 | let inputs = try abiRecord.inputs?.map({ (input: ABI.Input) throws -> ABI.Element.InOut in 98 | let nativeInput = try input.parse() 99 | return nativeInput 100 | }) 101 | let abiInputs = inputs != nil ? inputs! : [ABI.Element.InOut]() 102 | var payable = false 103 | if abiRecord.payable != nil { 104 | payable = abiRecord.payable! 105 | } 106 | if abiRecord.stateMutability == "payable" { 107 | payable = true 108 | } 109 | let constant = false 110 | let functionElement = ABI.Element.Constructor(inputs: abiInputs, constant: constant, payable: payable) 111 | return functionElement 112 | } 113 | 114 | private func parseEvent(abiRecord: ABI.Record) throws -> ABI.Element.Event { 115 | let inputs = try abiRecord.inputs?.map({ (input: ABI.Input) throws -> ABI.Element.Event.Input in 116 | let nativeInput = try input.parseForEvent() 117 | return nativeInput 118 | }) 119 | let abiInputs = inputs != nil ? inputs! : [ABI.Element.Event.Input]() 120 | let name = abiRecord.name != nil ? abiRecord.name! : "" 121 | let anonymous = abiRecord.anonymous != nil ? abiRecord.anonymous! : false 122 | let functionElement = ABI.Element.Event(name: name, inputs: abiInputs, anonymous: anonymous) 123 | return functionElement 124 | } 125 | 126 | extension ABI.Input { 127 | func parse() throws -> ABI.Element.InOut { 128 | let name = self.name != nil ? self.name! : "" 129 | let parameterType = try ABITypeParser.parseTypeString(self.type) 130 | if case .tuple(types: _) = parameterType { 131 | let components = try self.components?.compactMap({ (inp: ABI.Input) throws -> ABI.Element.ParameterType in 132 | let input = try inp.parse() 133 | return input.type 134 | }) 135 | let type = ABI.Element.ParameterType.tuple(types: components!) 136 | let nativeInput = ABI.Element.InOut(name: name, type: type) 137 | return nativeInput 138 | } else { 139 | let nativeInput = ABI.Element.InOut(name: name, type: parameterType) 140 | return nativeInput 141 | } 142 | } 143 | 144 | func parseForEvent() throws -> ABI.Element.Event.Input { 145 | let name = self.name != nil ? self.name! : "" 146 | let parameterType = try ABITypeParser.parseTypeString(self.type) 147 | let indexed = self.indexed == true 148 | return ABI.Element.Event.Input(name: name, type: parameterType, indexed: indexed) 149 | } 150 | } 151 | 152 | extension ABI.Output { 153 | func parse() throws -> ABI.Element.InOut { 154 | let name = self.name != nil ? self.name! : "" 155 | let parameterType = try ABITypeParser.parseTypeString(self.type) 156 | switch parameterType { 157 | case .tuple(types: _): 158 | let components = try self.components?.compactMap({ (inp: ABI.Output) throws -> ABI.Element.ParameterType in 159 | let input = try inp.parse() 160 | return input.type 161 | }) 162 | let type = ABI.Element.ParameterType.tuple(types: components!) 163 | let nativeInput = ABI.Element.InOut(name: name, type: type) 164 | return nativeInput 165 | case .array(type: let subtype, length: let length): 166 | switch subtype { 167 | case .tuple(types: _): 168 | let components = try self.components?.compactMap({ (inp: ABI.Output) throws -> ABI.Element.ParameterType in 169 | let input = try inp.parse() 170 | return input.type 171 | }) 172 | let nestedSubtype = ABI.Element.ParameterType.tuple(types: components!) 173 | let properType = ABI.Element.ParameterType.array(type: nestedSubtype, length: length) 174 | let nativeInput = ABI.Element.InOut(name: name, type: properType) 175 | return nativeInput 176 | default: 177 | let nativeInput = ABI.Element.InOut(name: name, type: parameterType) 178 | return nativeInput 179 | } 180 | default: 181 | let nativeInput = ABI.Element.InOut(name: name, type: parameterType) 182 | return nativeInput 183 | } 184 | } 185 | } 186 | -------------------------------------------------------------------------------- /Sources/UnstoppableDomainsResolution/ABI/EthereumABI/ABIParameterTypes.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ABIParameterTypes.swift 3 | // resolution 4 | // 5 | // Created by Serg Merenkov on 2/8/21. 6 | // Copyright © 2021 Unstoppable Domains. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import BigInt 11 | import EthereumAddress 12 | 13 | extension ABI.Element { 14 | 15 | /// Specifies the type that parameters in a contract have. 16 | public enum ParameterType: ABIElementPropertiesProtocol { 17 | case uint(bits: UInt64) 18 | case int(bits: UInt64) 19 | case address 20 | case function 21 | case bool 22 | case bytes(length: UInt64) 23 | indirect case array(type: ParameterType, length: UInt64) 24 | case dynamicBytes 25 | case string 26 | indirect case tuple(types: [ParameterType]) 27 | 28 | var isStatic: Bool { 29 | switch self { 30 | case .string: 31 | return false 32 | case .dynamicBytes: 33 | return false 34 | case .array(type: let type, length: let length): 35 | if length == 0 { 36 | return false 37 | } 38 | if !type.isStatic { 39 | return false 40 | } 41 | return true 42 | case .tuple(types: let types): 43 | for t in types { 44 | // swiftlint:disable for_where 45 | if !t.isStatic { 46 | return false 47 | } 48 | } 49 | return true 50 | case .bytes(length: _): 51 | return true 52 | default: 53 | return true 54 | } 55 | } 56 | 57 | var isArray: Bool { 58 | switch self { 59 | case .array(type: _, length: _): 60 | return true 61 | default: 62 | return false 63 | } 64 | } 65 | 66 | var isTuple: Bool { 67 | switch self { 68 | case .tuple: 69 | return true 70 | default: 71 | return false 72 | } 73 | } 74 | 75 | var subtype: ABI.Element.ParameterType? { 76 | switch self { 77 | case .array(type: let type, length: _): 78 | return type 79 | default: 80 | return nil 81 | } 82 | } 83 | 84 | var memoryUsage: UInt64 { 85 | switch self { 86 | case .array(_, length: let length): 87 | if length == 0 { 88 | return 32 89 | } 90 | if self.isStatic { 91 | return 32*length 92 | } 93 | return 32 94 | case .tuple(types: let types): 95 | if !self.isStatic { 96 | return 32 97 | } 98 | var sum: UInt64 = 0 99 | for t in types { 100 | sum += t.memoryUsage 101 | } 102 | return sum 103 | default: 104 | return 32 105 | } 106 | } 107 | 108 | var emptyValue: Any { 109 | switch self { 110 | case .uint(bits: _): 111 | return BigUInt(0) 112 | case .int(bits: _): 113 | return BigUInt(0) 114 | case .address: 115 | return EthereumAddress("0x0000000000000000000000000000000000000000") 116 | case .function: 117 | return Data(repeating: 0x00, count: 24) 118 | case .bool: 119 | return false 120 | case .bytes(length: let length): 121 | return Data(repeating: 0x00, count: Int(length)) 122 | case .array(type: let type, length: let length): 123 | let emptyValueOfType = type.emptyValue 124 | return Array.init(repeating: emptyValueOfType, count: Int(length)) 125 | case .dynamicBytes: 126 | return Data() 127 | case .string: 128 | return "" 129 | case .tuple(types: _): 130 | return [Any]() 131 | } 132 | } 133 | 134 | var arraySize: ABI.Element.ArraySize { 135 | switch self { 136 | case .array(type: _, length: let length): 137 | if length == 0 { 138 | return ArraySize.dynamicSize 139 | } 140 | return ArraySize.staticSize(length) 141 | default: 142 | return ArraySize.notArray 143 | } 144 | } 145 | } 146 | 147 | } 148 | 149 | extension ABI.Element.ParameterType: Equatable { 150 | public static func == (lhs: ABI.Element.ParameterType, rhs: ABI.Element.ParameterType) -> Bool { 151 | switch (lhs, rhs) { 152 | case let (.uint(length1), .uint(length2)): 153 | return length1 == length2 154 | case let (.int(length1), .int(length2)): 155 | return length1 == length2 156 | case (.address, .address): 157 | return true 158 | case (.bool, .bool): 159 | return true 160 | case let (.bytes(length1), .bytes(length2)): 161 | return length1 == length2 162 | case (.function, .function): 163 | return true 164 | case let (.array(type1, length1), .array(type2, length2)): 165 | return type1 == type2 && length1 == length2 166 | case (.dynamicBytes, .dynamicBytes): 167 | return true 168 | case (.string, .string): 169 | return true 170 | default: 171 | return false 172 | } 173 | } 174 | } 175 | 176 | extension ABI.Element.Function { 177 | public var signature: String { 178 | return "\(name ?? "")(\(inputs.map { $0.type.abiRepresentation }.joined(separator: ",")))" 179 | } 180 | 181 | public var methodString: String { 182 | return String(signature.sha3(.keccak256).prefix(8)) 183 | } 184 | 185 | public var methodEncoding: Data { 186 | return signature.data(using: .ascii)!.sha3(.keccak256)[0...3] 187 | } 188 | } 189 | 190 | // MARK: - Event topic 191 | extension ABI.Element.Event { 192 | public var signature: String { 193 | return "\(name)(\(inputs.map { $0.type.abiRepresentation }.joined(separator: ",")))" 194 | } 195 | 196 | public var topic: Data { 197 | return signature.data(using: .ascii)!.sha3(.keccak256) 198 | } 199 | } 200 | 201 | extension ABI.Element.ParameterType: ABIEncoding { 202 | public var abiRepresentation: String { 203 | switch self { 204 | case .uint(let bits): 205 | return "uint\(bits)" 206 | case .int(let bits): 207 | return "int\(bits)" 208 | case .address: 209 | return "address" 210 | case .bool: 211 | return "bool" 212 | case .bytes(let length): 213 | return "bytes\(length)" 214 | case .dynamicBytes: 215 | return "bytes" 216 | case .function: 217 | return "function" 218 | case .array(type: let type, length: let length): 219 | if length == 0 { 220 | return "\(type.abiRepresentation)[]" 221 | } 222 | return "\(type.abiRepresentation)[\(length)]" 223 | case .tuple(types: let types): 224 | let typesRepresentation = types.map({return $0.abiRepresentation}) 225 | let typesJoined = typesRepresentation.joined(separator: ",") 226 | return "tuple(\(typesJoined))" 227 | case .string: 228 | return "string" 229 | } 230 | } 231 | } 232 | 233 | extension ABI.Element.ParameterType: ABIValidation { 234 | public var isValid: Bool { 235 | switch self { 236 | case .uint(let bits), .int(let bits): 237 | return bits > 0 && bits <= 256 && bits % 8 == 0 238 | case .bytes(let length): 239 | return length > 0 && length <= 32 240 | case .array(type: let type, _): 241 | return type.isValid 242 | case .tuple(types: let types): 243 | for t in types { 244 | if !t.isValid { 245 | return false 246 | } 247 | } 248 | return true 249 | default: 250 | return true 251 | } 252 | } 253 | } 254 | -------------------------------------------------------------------------------- /Sources/UnstoppableDomainsResolution/ABI/EthereumABI/ABIExtensions.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ABIExtensions.swift 3 | // resolution 4 | // 5 | // Created by Serg Merenkov on 2/8/21. 6 | // Copyright © 2021 Unstoppable Domains. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import BigInt 11 | 12 | extension Data { 13 | func setLengthLeft(_ toBytes: UInt64, isNegative: Bool = false ) -> Data? { 14 | let existingLength = UInt64(self.count) 15 | if existingLength == toBytes { 16 | return Data(self) 17 | } else if existingLength > toBytes { 18 | return nil 19 | } 20 | var data: Data 21 | if isNegative { 22 | data = Data(repeating: UInt8(255), count: Int(toBytes - existingLength)) 23 | } else { 24 | data = Data(repeating: UInt8(0), count: Int(toBytes - existingLength)) 25 | } 26 | data.append(self) 27 | return data 28 | } 29 | 30 | func setLengthRight(_ toBytes: UInt64, isNegative: Bool = false ) -> Data? { 31 | let existingLength = UInt64(self.count) 32 | if existingLength == toBytes { 33 | return Data(self) 34 | } else if existingLength > toBytes { 35 | return nil 36 | } 37 | var data: Data = Data() 38 | data.append(self) 39 | if isNegative { 40 | data.append(Data(repeating: UInt8(255), count: Int(toBytes - existingLength))) 41 | } else { 42 | data.append(Data(repeating: UInt8(0), count: Int(toBytes - existingLength))) 43 | } 44 | return data 45 | } 46 | 47 | static func fromHex(_ hex: String) -> Data? { 48 | let string = hex.lowercased().stripHexPrefix() 49 | let array = [UInt8](hex: string) 50 | if array.count == 0 { 51 | if hex == "0x" || hex == "" { 52 | return Data() 53 | } else { 54 | return nil 55 | } 56 | } 57 | return Data(array) 58 | } 59 | } 60 | 61 | extension BigInt { 62 | func toTwosComplement() -> Data { 63 | if self.sign == BigInt.Sign.plus { 64 | return self.magnitude.serialize() 65 | } else { 66 | let serializedLength = self.magnitude.serialize().count 67 | let MAX = BigUInt(1) << (serializedLength*8) 68 | let twoComplement = MAX - self.magnitude 69 | return twoComplement.serialize() 70 | } 71 | } 72 | } 73 | 74 | extension BigUInt { 75 | func abiEncode(bits: UInt64) -> Data? { 76 | let data = self.serialize() 77 | let paddedLength = UInt64((bits + 7) / 8) 78 | let padded = data.setLengthLeft(paddedLength) 79 | return padded 80 | } 81 | } 82 | 83 | extension BigInt { 84 | func abiEncode(bits: UInt64) -> Data? { 85 | let isNegative = self < (BigInt(0)) 86 | let data = self.toTwosComplement() 87 | let paddedLength = UInt64((bits + 7) / 8) 88 | let padded = data.setLengthLeft(paddedLength, isNegative: isNegative) 89 | return padded 90 | } 91 | } 92 | 93 | extension BigInt { 94 | static func fromTwosComplement(data: Data) -> BigInt { 95 | let isPositive = ((data[0] & 128) >> 7) == 0 96 | if isPositive { 97 | let magnitude = BigUInt(data) 98 | return BigInt(magnitude) 99 | } else { 100 | let MAX = (BigUInt(1) << (data.count*8)) 101 | let magnitude = MAX - BigUInt(data) 102 | let bigint = BigInt(0) - BigInt(magnitude) 103 | return bigint 104 | } 105 | } 106 | } 107 | 108 | extension String { 109 | var fullRange: Range { 110 | return startIndex.. Index? { 118 | guard let range = range(of: String(char)) else { 119 | return nil 120 | } 121 | return range.lowerBound 122 | } 123 | 124 | subscript (bounds: CountableClosedRange) -> String { 125 | let start = index(self.startIndex, offsetBy: bounds.lowerBound) 126 | let end = index(self.startIndex, offsetBy: bounds.upperBound) 127 | return String(self[start...end]) 128 | } 129 | 130 | subscript (bounds: CountableRange) -> String { 131 | let start = index(self.startIndex, offsetBy: bounds.lowerBound) 132 | let end = index(self.startIndex, offsetBy: bounds.upperBound) 133 | return String(self[start..) -> String { 137 | let start = index(self.startIndex, offsetBy: bounds.lowerBound) 138 | let end = self.endIndex 139 | return String(self[start.. String { 143 | let stringLength = self.count 144 | if stringLength < toLength { 145 | return String(repeatElement(character, count: toLength - stringLength)) + self 146 | } else { 147 | return String(self.suffix(toLength)) 148 | } 149 | } 150 | 151 | func hasHexPrefix() -> Bool { 152 | return self.hasPrefix("0x") 153 | } 154 | 155 | func stripHexPrefix() -> String { 156 | if self.hasPrefix("0x") { 157 | let indexStart = self.index(self.startIndex, offsetBy: 2) 158 | return String(self[indexStart...]) 159 | } 160 | return self 161 | } 162 | 163 | func matchingStrings(regex: String) -> [[String]] { 164 | guard let regex = try? NSRegularExpression(pattern: regex, options: []) else { return [] } 165 | let nsString = self as NSString 166 | let results = regex.matches(in: self, options: [], range: NSRange(location: 0, length: nsString.length)) 167 | return results.map { result in 168 | (0.. Range? { 176 | guard 177 | let from16 = utf16.index(utf16.startIndex, offsetBy: nsRange.location, limitedBy: utf16.endIndex), 178 | let to16 = utf16.index(utf16.startIndex, offsetBy: nsRange.location + nsRange.length, limitedBy: utf16.endIndex), 179 | let from = from16.samePosition(in: self), 180 | let to = to16.samePosition(in: self) 181 | else { return nil } 182 | return from ..< to 183 | } 184 | 185 | var asciiValue: Int { 186 | let s = self.unicodeScalars 187 | return Int(s[s.startIndex].value) 188 | } 189 | } 190 | 191 | extension Character { 192 | var asciiValue: Int { 193 | let s = String(self).unicodeScalars 194 | return Int(s[s.startIndex].value) 195 | } 196 | } 197 | 198 | extension Array where Element == UInt8 { 199 | init(hex: String) { 200 | self.init() 201 | self.reserveCapacity(hex.unicodeScalars.lazy.underestimatedCount) 202 | var buffer: UInt8? 203 | var skip = hex.hasPrefix("0x") ? 2 : 0 204 | for char in hex.unicodeScalars.lazy { 205 | guard skip == 0 else { 206 | skip -= 1 207 | continue 208 | } 209 | guard char.value >= 48 && char.value <= 102 else { 210 | removeAll() 211 | return 212 | } 213 | let v: UInt8 214 | let c: UInt8 = UInt8(char.value) 215 | switch c { 216 | case let c where c <= 57: 217 | v = c - 48 218 | case let c where c >= 65 && c <= 70: 219 | v = c - 55 220 | case let c where c >= 97: 221 | v = c - 87 222 | default: 223 | removeAll() 224 | return 225 | } 226 | if let b = buffer { 227 | append(b << 4 | v) 228 | buffer = nil 229 | } else { 230 | buffer = v 231 | } 232 | } 233 | if let b = buffer { 234 | append(b) 235 | } 236 | } 237 | 238 | func toHexString() -> String { 239 | return `lazy`.reduce("") { 240 | var s = String($1, radix: 16) 241 | if s.count == 1 { 242 | s = "0" + s 243 | } 244 | return $0 + s 245 | } 246 | } 247 | } 248 | -------------------------------------------------------------------------------- /Sources/UnstoppableDomainsResolution/NamingServices/CNS.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CNS.swift 3 | // resolution 4 | // 5 | // Created by Johnny Good on 8/11/20. 6 | // Copyright © 2020 Unstoppable Domains. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import EthereumAddress 11 | 12 | internal class CNS: CommonNamingService, NamingService { 13 | struct ContractAddresses { 14 | let registryAddress: String 15 | let resolverAddress: String 16 | let proxyReaderAddress: String 17 | } 18 | 19 | static let specificDomain = ".crypto" 20 | static let name = "CNS" 21 | 22 | static let getDataForManyMethodName = "getDataForMany" 23 | 24 | let network: String 25 | let contracts: ContractAddresses 26 | var proxyReaderContract: Contract? 27 | 28 | init(_ config: NamingServiceConfig) throws { 29 | 30 | self.network = config.network.isEmpty 31 | ? try Self.getNetworkId(providerUrl: config.providerUrl, networking: config.networking) 32 | : config.network 33 | 34 | guard let contractsContainer = try Self.parseContractAddresses(network: network), 35 | let registry = contractsContainer[ContractType.registry.name]?.address, 36 | let resolver = contractsContainer[ContractType.resolver.name]?.address, 37 | let proxyReader = contractsContainer[ContractType.proxyReader.name]?.address else { throw ResolutionError.unsupportedNetwork } 38 | self.contracts = ContractAddresses(registryAddress: registry, resolverAddress: resolver, proxyReaderAddress: proxyReader) 39 | 40 | super.init(name: Self.name, providerUrl: config.providerUrl, networking: config.networking) 41 | proxyReaderContract = try super.buildContract(address: self.contracts.proxyReaderAddress, type: .proxyReader) 42 | } 43 | 44 | func isSupported(domain: String) -> Bool { 45 | return domain.hasSuffix(Self.specificDomain) 46 | } 47 | 48 | struct OwnerResolverRecord { 49 | let owner: String 50 | let resolver: String 51 | let record: String 52 | } 53 | 54 | // MARK: - geters of Owner and Resolver 55 | func owner(domain: String) throws -> String { 56 | let tokenId = super.namehash(domain: domain) 57 | let res: Any 58 | do { 59 | res = try self.getDataForMany(keys: [Contract.ownersKey], for: [tokenId]) 60 | } catch { 61 | if error is ABICoderError { 62 | throw ResolutionError.unregisteredDomain 63 | } 64 | throw error 65 | } 66 | guard let rec = self.unfoldForMany(contractResult: res, key: Contract.ownersKey), 67 | rec.count > 0 else { 68 | throw ResolutionError.unregisteredDomain 69 | } 70 | 71 | guard Utillities.isNotEmpty(rec[0]) else { 72 | throw ResolutionError.unregisteredDomain 73 | } 74 | return rec[0] 75 | } 76 | 77 | func batchOwners(domains: [String]) throws -> [String?] { 78 | let tokenIds = domains.map { super.namehash(domain: $0) } 79 | let res: Any 80 | do { 81 | res = try self.getDataForMany(keys: [Contract.ownersKey], for: tokenIds) 82 | } catch { 83 | throw error 84 | } 85 | guard let data = res as? [String: Any], 86 | let ownersFolded = data["1"] as? [Any] else { 87 | return [] 88 | } 89 | return ownersFolded.map { let address = unfoldAddress($0) 90 | return Utillities.isNotEmpty(address) ? address : nil 91 | } 92 | } 93 | 94 | func resolver(domain: String) throws -> String { 95 | let tokenId = super.namehash(domain: domain) 96 | return try self.resolver(tokenId: tokenId) 97 | } 98 | 99 | func resolver(tokenId: String) throws -> String { 100 | let res: Any 101 | do { 102 | res = try self.getDataForMany(keys: [Contract.resolversKey], for: [tokenId]) 103 | } catch { 104 | if error is ABICoderError { 105 | throw ResolutionError.unspecifiedResolver 106 | } 107 | throw error 108 | } 109 | guard let rec = self.unfoldForMany(contractResult: res, key: Contract.resolversKey), 110 | rec.count > 0 else { 111 | throw ResolutionError.unspecifiedResolver 112 | } 113 | guard Utillities.isNotEmpty(rec[0]) else { 114 | throw ResolutionError.unspecifiedResolver 115 | } 116 | return rec[0] 117 | } 118 | 119 | func addr(domain: String, ticker: String) throws -> String { 120 | let key = "crypto.\(ticker.uppercased()).address" 121 | let result = try record(domain: domain, key: key) 122 | return result 123 | } 124 | 125 | // MARK: - Get Record 126 | func record(domain: String, key: String) throws -> String { 127 | let tokenId = super.namehash(domain: domain) 128 | let result = try record(tokenId: tokenId, key: key) 129 | guard Utillities.isNotEmpty(result) else { 130 | throw ResolutionError.recordNotFound 131 | } 132 | return result 133 | } 134 | 135 | func record(tokenId: String, key: String) throws -> String { 136 | let result: OwnerResolverRecord 137 | do { 138 | result = try self.getOwnerResolverRecord(tokenId: tokenId, key: key) 139 | } catch { 140 | if error is ABICoderError { 141 | throw ResolutionError.unspecifiedResolver 142 | } 143 | throw error 144 | } 145 | guard Utillities.isNotEmpty(result.owner) else { throw ResolutionError.unregisteredDomain } 146 | guard Utillities.isNotEmpty(result.resolver) else { throw ResolutionError.unspecifiedResolver } 147 | 148 | return result.record 149 | } 150 | 151 | func records(keys: [String], for domain: String) throws -> [String: String] { 152 | let tokenId = super.namehash(domain: domain) 153 | guard let dict = try proxyReaderContract?.callMethod(methodName: "getMany", args: [keys, tokenId]) as? [String: [String]], 154 | let result = dict["0"] 155 | else { 156 | throw ResolutionError.recordNotFound 157 | } 158 | 159 | let returnValue = zip(keys, result).reduce(into: [String: String]()) { dict, pair in 160 | let (key, value) = pair 161 | dict[key] = value 162 | } 163 | return returnValue 164 | } 165 | 166 | // MARK: - Helper functions 167 | private func unfoldAddress (_ incomingData: T) -> String? { 168 | if let eth = incomingData as? EthereumAddress { 169 | return eth.address 170 | } 171 | if let str = incomingData as? String { 172 | return str 173 | } 174 | return nil 175 | } 176 | 177 | private func unfoldAddressForMany (_ incomingData: T) -> [String]? { 178 | if let ethArray = incomingData as? [EthereumAddress] { 179 | return ethArray.map { $0.address } 180 | } 181 | if let strArray = incomingData as? [String] { 182 | return strArray 183 | } 184 | return nil 185 | } 186 | 187 | private func unfoldForMany(contractResult: Any, key: String = "0") -> [String]? { 188 | if let dict = contractResult as? [String: Any], 189 | let element = dict[key] { 190 | return unfoldAddressForMany(element) 191 | } 192 | return nil 193 | } 194 | 195 | private func getOwnerResolverRecord(tokenId: String, key: String) throws -> OwnerResolverRecord { 196 | let res = try self.getDataForMany(keys: [key], for: [tokenId]) 197 | if let dict = res as? [String: Any] { 198 | if let owners = unfoldAddressForMany(dict[Contract.ownersKey]), 199 | let resolvers = unfoldAddressForMany(dict[Contract.resolversKey]), 200 | let valuesArray = dict[Contract.valuesKey] as? [[String]] { 201 | guard Utillities.isNotEmpty(owners[0]) else { 202 | throw ResolutionError.unregisteredDomain 203 | } 204 | 205 | guard Utillities.isNotEmpty(resolvers[0]), 206 | valuesArray.count > 0, 207 | valuesArray[0].count > 0 else { 208 | throw ResolutionError.unspecifiedResolver 209 | } 210 | 211 | let record = valuesArray[0][0] 212 | return OwnerResolverRecord(owner: owners[0], resolver: resolvers[0], record: record) 213 | } 214 | } 215 | throw ResolutionError.unregisteredDomain 216 | } 217 | 218 | private func getDataForMany(keys: [String], for tokenIds: [String]) throws -> Any { 219 | if let result = try proxyReaderContract? 220 | .callMethod(methodName: Self.getDataForManyMethodName, 221 | args: [keys, tokenIds]) { return result } 222 | throw ResolutionError.proxyReaderNonInitialized 223 | } 224 | } 225 | -------------------------------------------------------------------------------- /Sources/UnstoppableDomainsResolution/Resources/CNS/cnsResolver.json: -------------------------------------------------------------------------------- 1 | [{ 2 | "inputs": [{ 3 | "internalType": "contract Registry", 4 | "name": "registry", 5 | "type": "address" 6 | }, { 7 | "internalType": "contract MintingController", 8 | "name": "mintingController", 9 | "type": "address" 10 | }], 11 | "payable": false, 12 | "stateMutability": "nonpayable", 13 | "type": "constructor" 14 | }, { 15 | "anonymous": false, 16 | "inputs": [{ 17 | "indexed": true, 18 | "internalType": "uint256", 19 | "name": "tokenId", 20 | "type": "uint256" 21 | }, { 22 | "indexed": true, 23 | "internalType": "string", 24 | "name": "keyIndex", 25 | "type": "string" 26 | }, { 27 | "indexed": false, 28 | "internalType": "string", 29 | "name": "key", 30 | "type": "string" 31 | }], 32 | "name": "NewKey", 33 | "type": "event" 34 | }, { 35 | "anonymous": false, 36 | "inputs": [{ 37 | "indexed": true, 38 | "internalType": "uint256", 39 | "name": "tokenId", 40 | "type": "uint256" 41 | }], 42 | "name": "ResetRecords", 43 | "type": "event" 44 | }, { 45 | "anonymous": false, 46 | "inputs": [{ 47 | "indexed": true, 48 | "internalType": "uint256", 49 | "name": "tokenId", 50 | "type": "uint256" 51 | }, { 52 | "indexed": true, 53 | "internalType": "string", 54 | "name": "keyIndex", 55 | "type": "string" 56 | }, { 57 | "indexed": true, 58 | "internalType": "string", 59 | "name": "valueIndex", 60 | "type": "string" 61 | }, { 62 | "indexed": false, 63 | "internalType": "string", 64 | "name": "key", 65 | "type": "string" 66 | }, { 67 | "indexed": false, 68 | "internalType": "string", 69 | "name": "value", 70 | "type": "string" 71 | }], 72 | "name": "Set", 73 | "type": "event" 74 | }, { 75 | "constant": true, 76 | "inputs": [{ 77 | "internalType": "string", 78 | "name": "key", 79 | "type": "string" 80 | }, { 81 | "internalType": "uint256", 82 | "name": "tokenId", 83 | "type": "uint256" 84 | }], 85 | "name": "get", 86 | "outputs": [{ 87 | "internalType": "string", 88 | "name": "", 89 | "type": "string" 90 | }], 91 | "payable": false, 92 | "stateMutability": "view", 93 | "type": "function" 94 | }, { 95 | "constant": true, 96 | "inputs": [{ 97 | "internalType": "uint256", 98 | "name": "keyHash", 99 | "type": "uint256" 100 | }, { 101 | "internalType": "uint256", 102 | "name": "tokenId", 103 | "type": "uint256" 104 | }], 105 | "name": "getByHash", 106 | "outputs": [{ 107 | "internalType": "string", 108 | "name": "key", 109 | "type": "string" 110 | }, { 111 | "internalType": "string", 112 | "name": "value", 113 | "type": "string" 114 | }], 115 | "payable": false, 116 | "stateMutability": "view", 117 | "type": "function" 118 | }, { 119 | "constant": true, 120 | "inputs": [{ 121 | "internalType": "string[]", 122 | "name": "keys", 123 | "type": "string[]" 124 | }, { 125 | "internalType": "uint256", 126 | "name": "tokenId", 127 | "type": "uint256" 128 | }], 129 | "name": "getMany", 130 | "outputs": [{ 131 | "internalType": "string[]", 132 | "name": "", 133 | "type": "string[]" 134 | }], 135 | "payable": false, 136 | "stateMutability": "view", 137 | "type": "function" 138 | }, { 139 | "constant": true, 140 | "inputs": [{ 141 | "internalType": "uint256[]", 142 | "name": "keyHashes", 143 | "type": "uint256[]" 144 | }, { 145 | "internalType": "uint256", 146 | "name": "tokenId", 147 | "type": "uint256" 148 | }], 149 | "name": "getManyByHash", 150 | "outputs": [{ 151 | "internalType": "string[]", 152 | "name": "keys", 153 | "type": "string[]" 154 | }, { 155 | "internalType": "string[]", 156 | "name": "values", 157 | "type": "string[]" 158 | }], 159 | "payable": false, 160 | "stateMutability": "view", 161 | "type": "function" 162 | }, { 163 | "constant": true, 164 | "inputs": [{ 165 | "internalType": "uint256", 166 | "name": "keyHash", 167 | "type": "uint256" 168 | }], 169 | "name": "hashToKey", 170 | "outputs": [{ 171 | "internalType": "string", 172 | "name": "", 173 | "type": "string" 174 | }], 175 | "payable": false, 176 | "stateMutability": "view", 177 | "type": "function" 178 | }, { 179 | "constant": true, 180 | "inputs": [{ 181 | "internalType": "uint256[]", 182 | "name": "hashes", 183 | "type": "uint256[]" 184 | }], 185 | "name": "hashesToKeys", 186 | "outputs": [{ 187 | "internalType": "string[]", 188 | "name": "", 189 | "type": "string[]" 190 | }], 191 | "payable": false, 192 | "stateMutability": "view", 193 | "type": "function" 194 | }, { 195 | "constant": true, 196 | "inputs": [{ 197 | "internalType": "uint256", 198 | "name": "tokenId", 199 | "type": "uint256" 200 | }], 201 | "name": "nonceOf", 202 | "outputs": [{ 203 | "internalType": "uint256", 204 | "name": "", 205 | "type": "uint256" 206 | }], 207 | "payable": false, 208 | "stateMutability": "view", 209 | "type": "function" 210 | }, { 211 | "constant": false, 212 | "inputs": [{ 213 | "internalType": "string[]", 214 | "name": "keys", 215 | "type": "string[]" 216 | }, { 217 | "internalType": "string[]", 218 | "name": "values", 219 | "type": "string[]" 220 | }, { 221 | "internalType": "uint256", 222 | "name": "tokenId", 223 | "type": "uint256" 224 | }], 225 | "name": "preconfigure", 226 | "outputs": [], 227 | "payable": false, 228 | "stateMutability": "nonpayable", 229 | "type": "function" 230 | }, { 231 | "constant": false, 232 | "inputs": [{ 233 | "internalType": "string[]", 234 | "name": "keys", 235 | "type": "string[]" 236 | }, { 237 | "internalType": "string[]", 238 | "name": "values", 239 | "type": "string[]" 240 | }, { 241 | "internalType": "uint256", 242 | "name": "tokenId", 243 | "type": "uint256" 244 | }], 245 | "name": "reconfigure", 246 | "outputs": [], 247 | "payable": false, 248 | "stateMutability": "nonpayable", 249 | "type": "function" 250 | }, { 251 | "constant": false, 252 | "inputs": [{ 253 | "internalType": "string[]", 254 | "name": "keys", 255 | "type": "string[]" 256 | }, { 257 | "internalType": "string[]", 258 | "name": "values", 259 | "type": "string[]" 260 | }, { 261 | "internalType": "uint256", 262 | "name": "tokenId", 263 | "type": "uint256" 264 | }, { 265 | "internalType": "bytes", 266 | "name": "signature", 267 | "type": "bytes" 268 | }], 269 | "name": "reconfigureFor", 270 | "outputs": [], 271 | "payable": false, 272 | "stateMutability": "nonpayable", 273 | "type": "function" 274 | }, { 275 | "constant": true, 276 | "inputs": [], 277 | "name": "registry", 278 | "outputs": [{ 279 | "internalType": "address", 280 | "name": "", 281 | "type": "address" 282 | }], 283 | "payable": false, 284 | "stateMutability": "view", 285 | "type": "function" 286 | }, { 287 | "constant": false, 288 | "inputs": [{ 289 | "internalType": "uint256", 290 | "name": "tokenId", 291 | "type": "uint256" 292 | }], 293 | "name": "reset", 294 | "outputs": [], 295 | "payable": false, 296 | "stateMutability": "nonpayable", 297 | "type": "function" 298 | }, { 299 | "constant": false, 300 | "inputs": [{ 301 | "internalType": "uint256", 302 | "name": "tokenId", 303 | "type": "uint256" 304 | }, { 305 | "internalType": "bytes", 306 | "name": "signature", 307 | "type": "bytes" 308 | }], 309 | "name": "resetFor", 310 | "outputs": [], 311 | "payable": false, 312 | "stateMutability": "nonpayable", 313 | "type": "function" 314 | }, { 315 | "constant": false, 316 | "inputs": [{ 317 | "internalType": "string", 318 | "name": "key", 319 | "type": "string" 320 | }, { 321 | "internalType": "string", 322 | "name": "value", 323 | "type": "string" 324 | }, { 325 | "internalType": "uint256", 326 | "name": "tokenId", 327 | "type": "uint256" 328 | }], 329 | "name": "set", 330 | "outputs": [], 331 | "payable": false, 332 | "stateMutability": "nonpayable", 333 | "type": "function" 334 | }, { 335 | "constant": false, 336 | "inputs": [{ 337 | "internalType": "string", 338 | "name": "key", 339 | "type": "string" 340 | }, { 341 | "internalType": "string", 342 | "name": "value", 343 | "type": "string" 344 | }, { 345 | "internalType": "uint256", 346 | "name": "tokenId", 347 | "type": "uint256" 348 | }, { 349 | "internalType": "bytes", 350 | "name": "signature", 351 | "type": "bytes" 352 | }], 353 | "name": "setFor", 354 | "outputs": [], 355 | "payable": false, 356 | "stateMutability": "nonpayable", 357 | "type": "function" 358 | }, { 359 | "constant": false, 360 | "inputs": [{ 361 | "internalType": "string[]", 362 | "name": "keys", 363 | "type": "string[]" 364 | }, { 365 | "internalType": "string[]", 366 | "name": "values", 367 | "type": "string[]" 368 | }, { 369 | "internalType": "uint256", 370 | "name": "tokenId", 371 | "type": "uint256" 372 | }], 373 | "name": "setMany", 374 | "outputs": [], 375 | "payable": false, 376 | "stateMutability": "nonpayable", 377 | "type": "function" 378 | }, { 379 | "constant": false, 380 | "inputs": [{ 381 | "internalType": "string[]", 382 | "name": "keys", 383 | "type": "string[]" 384 | }, { 385 | "internalType": "string[]", 386 | "name": "values", 387 | "type": "string[]" 388 | }, { 389 | "internalType": "uint256", 390 | "name": "tokenId", 391 | "type": "uint256" 392 | }, { 393 | "internalType": "bytes", 394 | "name": "signature", 395 | "type": "bytes" 396 | }], 397 | "name": "setManyFor", 398 | "outputs": [], 399 | "payable": false, 400 | "stateMutability": "nonpayable", 401 | "type": "function" 402 | }] 403 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # UnstoppableDomainsResolution 2 | 3 | [![Get help on Discord](https://img.shields.io/badge/Get%20help%20on-Discord-blueviolet)](https://discord.gg/b6ZVxSZ9Hn) 4 | [![Unstoppable Domains Documentation](https://img.shields.io/badge/Documentation-unstoppabledomains.com-blue)](https://docs.unstoppabledomains.com/) 5 | 6 | Resolution is a library for interacting with blockchain domain names. It can be used to retrieve [payment addresses](https://unstoppabledomains.com/features#Add-Crypto-Addresses), IPFS hashes for [decentralized websites](https://unstoppabledomains.com/features#Build-Website), and GunDB usernames for [decentralized chat](https://unstoppabledomains.com/chat). 7 | 8 | Resolution is primarily built and maintained by [Unstoppable Domains](https://unstoppabledomains.com/). 9 | 10 | Resoultion supports decentralized domains across three main zones: 11 | 12 | - Crypto Name Service (CNS) 13 | - `.crypto` 14 | - Zilliqa Name Service (ZNS) 15 | - `.zil` 16 | - Ethereum Name Service (ENS) 17 | - `.eth` 18 | - `.kred` 19 | - `.xyz` 20 | - `.luxe` 21 | 22 | # Installation into the project 23 | 24 | ## Cocoa Pods 25 | 26 | ```ruby 27 | pod 'UnstoppableDomainsResolution', '~> 0.3.7' 28 | ``` 29 | 30 | ## Swift Package Manager 31 | 32 | ```swift 33 | package.dependencies.append( 34 | .package(url: "https://github.com/unstoppabledomains/resolution-swift", from: "0.3.7") 35 | ) 36 | ``` 37 | 38 | # Usage 39 | 40 | - Create an instance of the Resolution class 41 | - Call any method of the Resolution class asyncronously 42 | 43 | > NOTE: make sure an instance of the Resolution class is not deallocated until the asyncronous call brings in the result. Your code is the **only owner** of the instance so keep it as long as you need it. 44 | 45 | # Common examples 46 | 47 | > NOTE as of 26 November 2020: since the service at https://main-rpc.linkpool.io seems to be unstable it is highly recommended that you instantiate the Resolution instance with an Infura URL, like shown below. 48 | 49 | ```swift 50 | import UnstoppableDomainsResolution 51 | 52 | guard let resolution = try? Resolution() else { 53 | print ("Init of Resolution instance with default parameters failed...") 54 | return 55 | } 56 | 57 | // Or, if you want to use a specific providerUrl and network: 58 | guard let resolution = try? Resolution(providerUrl: "https://mainnet.infura.io/v3/", network: "mainnet") else { 59 | print ("Init of Resolution instance with custom parameters failed...") 60 | return 61 | } 62 | 63 | resolution.addr(domain: "brad.crypto", ticker: "btc") { result in 64 | switch result { 65 | case .success(let returnValue): 66 | // bc1q359khn0phg58xgezyqsuuaha28zkwx047c0c3y 67 | let btcAddress = returnValue 68 | case .failure(let error): 69 | print("Expected btc Address, but got \(error)") 70 | } 71 | } 72 | 73 | resolution.addr(domain: "brad.crypto", ticker: "eth") { result in 74 | switch result { 75 | case .success(let returnValue): 76 | // 0x8aaD44321A86b170879d7A244c1e8d360c99DdA8 77 | let ethAddress = returnValue 78 | case .failure(let error): 79 | print("Expected eth Address, but got \(error)") 80 | } 81 | } 82 | 83 | resolution.multiChainAddress(domain: "brad.crypto", ticker: "USDT", chain: "ERC20") { result in 84 | switch result { 85 | case .success(let returnValue): 86 | // 0x8aaD44321A86b170879d7A244c1e8d360c99DdA8 87 | let usdtErc20Address = returnValue 88 | case .failure(let error): 89 | print("Expected eth Address, but got \(error)") 90 | } 91 | } 92 | 93 | resolution.multiChainAddress(domain: "brad.crypto", ticker: "USDT", chain: "OMNI") { result in 94 | switch result { 95 | case .success(let returnValue): 96 | // 1FoWyxwPXuj4C6abqwhjDWdz6D4PZgYRjA 97 | let usdtOmniAddress = returnValue 98 | case .failure(let error): 99 | print("Expected Omni Address, but got \(error)") 100 | } 101 | } 102 | 103 | resolution.owner(domain: "brad.crypto") { result in 104 | switch result { 105 | case .success(let returnValue): 106 | // 0x8aaD44321A86b170879d7A244c1e8d360c99DdA8 107 | let domainOwner = returnValue 108 | case .failure(let error): 109 | XCTFail("Expected owner, but got \(error)") 110 | } 111 | } 112 | 113 | // Lookup specific records 114 | resolution.record(domain: "ryan.crypto", record: "custom.record.value") { result in 115 | switch result { 116 | case .success(let returnValue): 117 | // Example custom record value 118 | let recordValue = returnValue 119 | case .failure(let error): 120 | print("Expected record value, but got \(error)") 121 | } 122 | } 123 | ``` 124 | 125 | ## Customizing naming services 126 | Version 0.3.0 introduced the `Configurations` struct that is used for configuring each connected naming service. 127 | Library can offer three naming services at the moment: 128 | 129 | * `cns` resolves `.crypto` domains, 130 | * `ens` resolves `.eth` domains, 131 | * `zns` resolves `.zil` domains 132 | 133 | By default, each of them is using the mainnet network via infura provider. 134 | Unstoppable domains are using the infura key with no restriction for CNS. 135 | Unstoppable domains recommends setting up your own provider for ENS, as we don't guarantee ENS Infura key availability. 136 | You can update each naming service separately 137 | 138 | ```swift 139 | let resolution = try Resolution(configs: Configurations( 140 | cns: NamingServiceConfig( 141 | providerUrl: "https://rinkeby.infura.io/v3/3c25f57353234b1b853e9861050f4817", 142 | network: "rinkeby" 143 | ) 144 | ) 145 | ); 146 | 147 | // domain udtestdev-creek.crypto exists only on the rinkeby network. 148 | 149 | resolution.addr(domain: "udtestdev-creek.crypto", ticker: "eth") { (result) in 150 | switch result { 151 | case .success(let returnValue): 152 | ethAddress = returnValue 153 | domainReceived.fulfill() 154 | case .failure(let error): 155 | XCTFail("Expected Eth Address, but got \(error)") 156 | } 157 | } 158 | 159 | // naming services that hasn't been touched by Configrations struct are using default settings 160 | // the following will look up monkybrain.eth on the mainnet via infura provider 161 | 162 | resolution.addr(domain: "monkybrain.eth", ticker: "eth") { (result) in 163 | switch result { 164 | case .success(let returnValue): 165 | ethENSAddress = returnValue 166 | domainEthReceived.fulfill() 167 | case .failure(let error): 168 | XCTFail("Expected Eth Address, but got \(error)") 169 | } 170 | } 171 | ``` 172 | 173 | ## Batch requesting of owners 174 | 175 | Version 0.1.3 introduced the `batchOwners(domains: _, completion: _ )` method which adds additional convenience when making multiple domain owner queries. 176 | 177 | > This method is only compatible with CNS-based domains. Using this method with any other domain type will throw the error: `ResolutionError.methodNotSupported`. 178 | 179 | As opposed to the single `owner(domain: _, completion: _)` method, this batch request will return an array of owners `[String?]`. If the the domain is not registered or its value is `null`, the corresponding array element of the response will be `nil` without throwing an error. 180 | 181 | ```swift 182 | resolution.batchOwners(domains: ["brad.crypto", "otherbrad.crypto"]) { result in 183 | switch result { 184 | case .success(let returnValue): 185 | // returnValue: [String?] = 186 | let domainOwner = returnValue 187 | case .failure(let error): 188 | XCTFail("Expected owner, but got \(error)") 189 | } 190 | } 191 | ``` 192 | 193 | # Networking 194 | 195 | > Make sure your app has AppTransportSecurity settings to allow HTTP access to the `https://main-rpc.linkpool.io` domain. 196 | 197 | ## Custom Networking Layer 198 | 199 | By default, this library uses the native iOS networking API to connect to the internet. If you want the library to use your own networking layer instead, you must conform your networking layer to the `NetworkingLayer` protocol. This protocol requires only one method to be implemented: `makeHttpPostRequest(url:, httpMethod:, httpHeaderContentType:, httpBody:, completion:)`. Using this method will bypass the default behavior and delegate the request to your own networking code. 200 | 201 | For example, construct the Resolution instance like so: 202 | 203 | ```swift 204 | guard let resolution = try? Resolution(networking: MyNetworkingApi) else { 205 | print ("Init of Resolution instance failed...") 206 | return 207 | } 208 | ``` 209 | 210 | # Possible Errors: 211 | 212 | If the domain you are attempting to resolve is not registered or doesn't contain the information you are requesting, this framework will return a `ResolutionError` with the possible causes below. We advise creating customized errors in your app based on the return value of the error. 213 | 214 | ``` 215 | enum ResolutionError: Error { 216 | case unregisteredDomain 217 | case unsupportedDomain 218 | case recordNotFound 219 | case recordNotSupported 220 | case unsupportedNetwork 221 | case unspecifiedResolver 222 | case unknownError(Error) 223 | case proxyReaderNonInitialized 224 | case inconsistenDomainArray 225 | case methodNotSupported 226 | } 227 | ``` 228 | 229 | # Contributions 230 | 231 | Contributions to this library are more than welcome. The easiest way to contribute is through GitHub issues and pull requests. 232 | 233 | 234 | # Free advertising for integrated apps 235 | 236 | Once your app has a working Unstoppable Domains integration, [register it here](https://unstoppabledomains.com/app-submission). Registered apps appear on the Unstoppable Domains [homepage](https://unstoppabledomains.com/) and [Applications](https://unstoppabledomains.com/apps) page — putting your app in front of tens of thousands of potential customers per day. 237 | 238 | Also, every week we select a newly-integrated app to feature in the Unstoppable Update newsletter. This newsletter is delivered to straight into the inbox of ~100,000 crypto fanatics — all of whom could be new customers to grow your business. 239 | 240 | # Get help 241 | [Join our discord community](https://discord.com/invite/b6ZVxSZ9Hn) and ask questions. 242 | -------------------------------------------------------------------------------- /Sources/UnstoppableDomainsResolution/ABI/EthereumABI/ABIElements.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ABIElements.swift 3 | // resolution 4 | // 5 | // Created by Serg Merenkov on 2/8/21. 6 | // Copyright © 2021 Unstoppable Domains. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import BigInt 11 | 12 | // swiftlint:disable nesting cyclomatic_complexity function_body_length 13 | public extension ABI { 14 | // JSON Decoding 15 | struct Input: Decodable { 16 | public var name: String? 17 | public var type: String 18 | public var indexed: Bool? 19 | public var components: [Input]? 20 | } 21 | 22 | struct Output: Decodable { 23 | public var name: String? 24 | public var type: String 25 | public var components: [Output]? 26 | } 27 | 28 | struct Record: Decodable { 29 | public var name: String? 30 | public var type: String? 31 | public var payable: Bool? 32 | public var constant: Bool? 33 | public var stateMutability: String? 34 | public var inputs: [ABI.Input]? 35 | public var outputs: [ABI.Output]? 36 | public var anonymous: Bool? 37 | } 38 | 39 | enum Element { 40 | public enum ArraySize { // bytes for convenience 41 | case staticSize(UInt64) 42 | case dynamicSize 43 | case notArray 44 | } 45 | 46 | case function(Function) 47 | case constructor(Constructor) 48 | case fallback(Fallback) 49 | case event(Event) 50 | 51 | public enum StateMutability { 52 | case payable 53 | case mutating 54 | case view 55 | case pure 56 | 57 | var isConstant: Bool { 58 | switch self { 59 | case .payable: 60 | return false 61 | case .mutating: 62 | return false 63 | default: 64 | return true 65 | } 66 | } 67 | 68 | var isPayable: Bool { 69 | switch self { 70 | case .payable: 71 | return true 72 | default: 73 | return false 74 | } 75 | } 76 | } 77 | 78 | public struct InOut { 79 | public let name: String 80 | public let type: ParameterType 81 | 82 | public init(name: String, type: ParameterType) { 83 | self.name = name 84 | self.type = type 85 | } 86 | } 87 | 88 | public struct Function { 89 | public let name: String? 90 | public let inputs: [InOut] 91 | public let outputs: [InOut] 92 | public let stateMutability: StateMutability? = nil 93 | public let constant: Bool 94 | public let payable: Bool 95 | 96 | public init(name: String?, inputs: [InOut], outputs: [InOut], constant: Bool, payable: Bool) { 97 | self.name = name 98 | self.inputs = inputs 99 | self.outputs = outputs 100 | self.constant = constant 101 | self.payable = payable 102 | } 103 | } 104 | 105 | public struct Constructor { 106 | public let inputs: [InOut] 107 | public let constant: Bool 108 | public let payable: Bool 109 | public init(inputs: [InOut], constant: Bool, payable: Bool) { 110 | self.inputs = inputs 111 | self.constant = constant 112 | self.payable = payable 113 | } 114 | } 115 | 116 | public struct Fallback { 117 | public let constant: Bool 118 | public let payable: Bool 119 | 120 | public init(constant: Bool, payable: Bool) { 121 | self.constant = constant 122 | self.payable = payable 123 | } 124 | } 125 | 126 | public struct Event { 127 | public let name: String 128 | public let inputs: [Input] 129 | public let anonymous: Bool 130 | 131 | public init(name: String, inputs: [Input], anonymous: Bool) { 132 | self.name = name 133 | self.inputs = inputs 134 | self.anonymous = anonymous 135 | } 136 | 137 | public struct Input { 138 | public let name: String 139 | public let type: ParameterType 140 | public let indexed: Bool 141 | 142 | public init(name: String, type: ParameterType, indexed: Bool) { 143 | self.name = name 144 | self.type = type 145 | self.indexed = indexed 146 | } 147 | } 148 | } 149 | } 150 | } 151 | 152 | extension ABI.Element { 153 | public func encodeParameters(_ parameters: [AnyObject]) -> Data? { 154 | switch self { 155 | case .constructor(let constructor): 156 | guard parameters.count == constructor.inputs.count else {return nil} 157 | guard let data = ABIEncoder.encode(types: constructor.inputs, values: parameters) else {return nil} 158 | return data 159 | case .event: 160 | return nil 161 | case .fallback: 162 | return nil 163 | case .function(let function): 164 | guard parameters.count == function.inputs.count else {return nil} 165 | let signature = function.methodEncoding 166 | guard let data = ABIEncoder.encode(types: function.inputs, values: parameters) else {return nil} 167 | return signature + data 168 | } 169 | } 170 | } 171 | 172 | extension ABI.Element { 173 | public func decodeReturnData(_ data: Data) -> [String: Any]? { 174 | switch self { 175 | case .constructor: 176 | return nil 177 | case .event: 178 | return nil 179 | case .fallback: 180 | return nil 181 | case .function(let function): 182 | if data.count == 0 && function.outputs.count == 1 { 183 | let name = "0" 184 | let value = function.outputs[0].type.emptyValue 185 | var returnArray = [String: Any]() 186 | returnArray[name] = value 187 | if function.outputs[0].name != "" { 188 | returnArray[function.outputs[0].name] = value 189 | } 190 | return returnArray 191 | } 192 | 193 | guard function.outputs.count*32 <= data.count else {return nil} 194 | var returnArray = [String: Any]() 195 | var i = 0 196 | guard let values = ABIDecoder.decode(types: function.outputs, data: data) else {return nil} 197 | for output in function.outputs { 198 | let name = "\(i)" 199 | returnArray[name] = values[i] 200 | if output.name != "" { 201 | returnArray[output.name] = values[i] 202 | } 203 | i += 1 204 | } 205 | return returnArray 206 | } 207 | } 208 | 209 | public func decodeInputData(_ rawData: Data) -> [String: Any]? { 210 | var data = rawData 211 | var sig: Data? 212 | switch rawData.count % 32 { 213 | case 0: 214 | break 215 | case 4: 216 | sig = rawData[0 ..< 4] 217 | data = Data(rawData[4 ..< rawData.count]) 218 | default: 219 | return nil 220 | } 221 | switch self { 222 | case .constructor(let function): 223 | if data.count == 0 && function.inputs.count == 1 { 224 | let name = "0" 225 | let value = function.inputs[0].type.emptyValue 226 | var returnArray = [String: Any]() 227 | returnArray[name] = value 228 | if function.inputs[0].name != "" { 229 | returnArray[function.inputs[0].name] = value 230 | } 231 | return returnArray 232 | } 233 | 234 | guard function.inputs.count*32 <= data.count else {return nil} 235 | var returnArray = [String: Any]() 236 | var i = 0 237 | guard let values = ABIDecoder.decode(types: function.inputs, data: data) else {return nil} 238 | for input in function.inputs { 239 | let name = "\(i)" 240 | returnArray[name] = values[i] 241 | if input.name != "" { 242 | returnArray[input.name] = values[i] 243 | } 244 | i += 1 245 | } 246 | return returnArray 247 | case .event: 248 | return nil 249 | case .fallback: 250 | return nil 251 | case .function(let function): 252 | if sig != nil && sig != function.methodEncoding { 253 | return nil 254 | } 255 | if data.count == 0 && function.inputs.count == 1 { 256 | let name = "0" 257 | let value = function.inputs[0].type.emptyValue 258 | var returnArray = [String: Any]() 259 | returnArray[name] = value 260 | if function.inputs[0].name != "" { 261 | returnArray[function.inputs[0].name] = value 262 | } 263 | return returnArray 264 | } 265 | 266 | guard function.inputs.count*32 <= data.count else {return nil} 267 | var returnArray = [String: Any]() 268 | var i = 0 269 | guard let values = ABIDecoder.decode(types: function.inputs, data: data) else {return nil} 270 | for input in function.inputs { 271 | let name = "\(i)" 272 | returnArray[name] = values[i] 273 | if input.name != "" { 274 | returnArray[input.name] = values[i] 275 | } 276 | i += 1 277 | } 278 | return returnArray 279 | } 280 | } 281 | } 282 | 283 | extension ABI.Element.Event { 284 | public func decodeReturnedLogs(eventLogTopics: [Data], eventLogData: Data) -> [String: Any]? { 285 | guard 286 | let eventContent = ABIDecoder.decodeLog(event: self, eventLogTopics: eventLogTopics, eventLogData: eventLogData) 287 | else { 288 | return nil 289 | } 290 | return eventContent 291 | } 292 | } 293 | -------------------------------------------------------------------------------- /Sources/UnstoppableDomainsResolution/Resources/ENS/ensResolver.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "constant": true, 4 | "inputs": [{ "internalType": "bytes4", "name": "interfaceID", "type": "bytes4" }], 5 | "name": "supportsInterface", 6 | "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], 7 | "payable": false, 8 | "stateMutability": "pure", 9 | "type": "function", 10 | }, 11 | { 12 | "constant": false, 13 | "inputs": [ 14 | { "internalType": "bytes32", "name": "node", "type": "bytes32" }, 15 | { "internalType": "string", "name": "key", "type": "string" }, 16 | { "internalType": "string", "name": "value", "type": "string" }, 17 | ], 18 | "name": "setText", 19 | "outputs": [], 20 | "payable": false, 21 | "stateMutability": "nonpayable", 22 | "type": "function", 23 | }, 24 | { 25 | "constant": true, 26 | "inputs": [ 27 | { "internalType": "bytes32", "name": "node", "type": "bytes32" }, 28 | { "internalType": "bytes4", "name": "interfaceID", "type": "bytes4" }, 29 | ], 30 | "name": "interfaceImplementer", 31 | "outputs": [{ "internalType": "address", "name": "", "type": "address" }], 32 | "payable": false, 33 | "stateMutability": "view", 34 | "type": "function", 35 | }, 36 | { 37 | "constant": true, 38 | "inputs": [ 39 | { "internalType": "bytes32", "name": "node", "type": "bytes32" }, 40 | { "internalType": "uint256", "name": "contentTypes", "type": "uint256" }, 41 | ], 42 | "name": "ABI", 43 | "outputs": [ 44 | { "internalType": "uint256", "name": "", "type": "uint256" }, 45 | { "internalType": "bytes", "name": "", "type": "bytes" }, 46 | ], 47 | "payable": false, 48 | "stateMutability": "view", 49 | "type": "function", 50 | }, 51 | { 52 | "constant": false, 53 | "inputs": [ 54 | { "internalType": "bytes32", "name": "node", "type": "bytes32" }, 55 | { "internalType": "bytes32", "name": "x", "type": "bytes32" }, 56 | { "internalType": "bytes32", "name": "y", "type": "bytes32" }, 57 | ], 58 | "name": "setPubkey", 59 | "outputs": [], 60 | "payable": false, 61 | "stateMutability": "nonpayable", 62 | "type": "function", 63 | }, 64 | { 65 | "constant": false, 66 | "inputs": [ 67 | { "internalType": "bytes32", "name": "node", "type": "bytes32" }, 68 | { "internalType": "bytes", "name": "hash", "type": "bytes" }, 69 | ], 70 | "name": "setContenthash", 71 | "outputs": [], 72 | "payable": false, 73 | "stateMutability": "nonpayable", 74 | "type": "function", 75 | }, 76 | { 77 | "constant": false, 78 | "inputs": [ 79 | { "internalType": "bytes32", "name": "node", "type": "bytes32" }, 80 | { "internalType": "address", "name": "target", "type": "address" }, 81 | { "internalType": "bool", "name": "isAuthorised", "type": "bool" }, 82 | ], 83 | "name": "setAuthorisation", 84 | "outputs": [], 85 | "payable": false, 86 | "stateMutability": "nonpayable", 87 | "type": "function", 88 | }, 89 | { 90 | "constant": true, 91 | "inputs": [ 92 | { "internalType": "bytes32", "name": "node", "type": "bytes32" }, 93 | { "internalType": "string", "name": "key", "type": "string" }, 94 | ], 95 | "name": "text", 96 | "outputs": [{ "internalType": "string", "name": "", "type": "string" }], 97 | "payable": false, 98 | "stateMutability": "view", 99 | "type": "function", 100 | }, 101 | { 102 | "constant": false, 103 | "inputs": [ 104 | { "internalType": "bytes32", "name": "node", "type": "bytes32" }, 105 | { "internalType": "uint256", "name": "contentType", "type": "uint256" }, 106 | { "internalType": "bytes", "name": "data", "type": "bytes" }, 107 | ], 108 | "name": "setABI", 109 | "outputs": [], 110 | "payable": false, 111 | "stateMutability": "nonpayable", 112 | "type": "function", 113 | }, 114 | { 115 | "constant": true, 116 | "inputs": [{ "internalType": "bytes32", "name": "node", "type": "bytes32" }], 117 | "name": "name", 118 | "outputs": [{ "internalType": "string", "name": "", "type": "string" }], 119 | "payable": false, 120 | "stateMutability": "view", 121 | "type": "function", 122 | }, 123 | { 124 | "constant": false, 125 | "inputs": [ 126 | { "internalType": "bytes32", "name": "node", "type": "bytes32" }, 127 | { "internalType": "string", "name": "name", "type": "string" }, 128 | ], 129 | "name": "setName", 130 | "outputs": [], 131 | "payable": false, 132 | "stateMutability": "nonpayable", 133 | "type": "function", 134 | }, 135 | { 136 | "constant": false, 137 | "inputs": [ 138 | { "internalType": "bytes32", "name": "node", "type": "bytes32" }, 139 | { "internalType": "uint256", "name": "coinType", "type": "uint256" }, 140 | { "internalType": "bytes", "name": "a", "type": "bytes" }, 141 | ], 142 | "name": "setAddr", 143 | "outputs": [], 144 | "payable": false, 145 | "stateMutability": "nonpayable", 146 | "type": "function", 147 | }, 148 | { 149 | "constant": true, 150 | "inputs": [{ "internalType": "bytes32", "name": "node", "type": "bytes32" }], 151 | "name": "contenthash", 152 | "outputs": [{ "internalType": "bytes", "name": "", "type": "bytes" }], 153 | "payable": false, 154 | "stateMutability": "view", 155 | "type": "function", 156 | }, 157 | { 158 | "constant": true, 159 | "inputs": [{ "internalType": "bytes32", "name": "node", "type": "bytes32" }], 160 | "name": "pubkey", 161 | "outputs": [ 162 | { "internalType": "bytes32", "name": "x", "type": "bytes32" }, 163 | { "internalType": "bytes32", "name": "y", "type": "bytes32" }, 164 | ], 165 | "payable": false, 166 | "stateMutability": "view", 167 | "type": "function", 168 | }, 169 | { 170 | "constant": false, 171 | "inputs": [ 172 | { "internalType": "bytes32", "name": "node", "type": "bytes32" }, 173 | { "internalType": "address", "name": "a", "type": "address" }, 174 | ], 175 | "name": "setAddr", 176 | "outputs": [], 177 | "payable": false, 178 | "stateMutability": "nonpayable", 179 | "type": "function", 180 | }, 181 | { 182 | "constant": false, 183 | "inputs": [ 184 | { "internalType": "bytes32", "name": "node", "type": "bytes32" }, 185 | { "internalType": "bytes4", "name": "interfaceID", "type": "bytes4" }, 186 | { "internalType": "address", "name": "implementer", "type": "address" }, 187 | ], 188 | "name": "setInterface", 189 | "outputs": [], 190 | "payable": false, 191 | "stateMutability": "nonpayable", 192 | "type": "function", 193 | }, 194 | { 195 | "constant": true, 196 | "inputs": [ 197 | { "internalType": "bytes32", "name": "node", "type": "bytes32" }, 198 | { "internalType": "uint256", "name": "coinType", "type": "uint256" }, 199 | ], 200 | "name": "addr", 201 | "outputs": [{ "internalType": "bytes", "name": "", "type": "bytes" }], 202 | "payable": false, 203 | "stateMutability": "view", 204 | "type": "function", 205 | }, 206 | { 207 | "constant": true, 208 | "inputs": [ 209 | { "internalType": "bytes32", "name": "", "type": "bytes32" }, 210 | { "internalType": "address", "name": "", "type": "address" }, 211 | { "internalType": "address", "name": "", "type": "address" }, 212 | ], 213 | "name": "authorisations", 214 | "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], 215 | "payable": false, 216 | "stateMutability": "view", 217 | "type": "function", 218 | }, 219 | { 220 | "inputs": [{ "internalType": "contract ENS", "name": "_ens", "type": "address" }], 221 | "payable": false, 222 | "stateMutability": "nonpayable", 223 | "type": "constructor", 224 | }, 225 | { 226 | "anonymous": false, 227 | "inputs": [ 228 | { "indexed": true, "internalType": "bytes32", "name": "node", "type": "bytes32" }, 229 | { 230 | "indexed": true, 231 | "internalType": "address", 232 | "name": "owner", 233 | "type": "address", 234 | }, 235 | { 236 | "indexed": true, 237 | "internalType": "address", 238 | "name": "target", 239 | "type": "address", 240 | }, 241 | { 242 | "indexed": false, 243 | "internalType": "bool", 244 | "name": "isAuthorised", 245 | "type": "bool", 246 | }, 247 | ], 248 | "name": "AuthorisationChanged", 249 | "type": "event", 250 | }, 251 | { 252 | "anonymous": false, 253 | "inputs": [ 254 | { "indexed": true, "internalType": "bytes32", "name": "node", "type": "bytes32" }, 255 | { 256 | "indexed": false, 257 | "internalType": "string", 258 | "name": "indexedKey", 259 | "type": "string", 260 | }, 261 | { "indexed": false, "internalType": "string", "name": "key", "type": "string" }, 262 | ], 263 | "name": "TextChanged", 264 | "type": "event", 265 | }, 266 | { 267 | "anonymous": false, 268 | "inputs": [ 269 | { "indexed": true, "internalType": "bytes32", "name": "node", "type": "bytes32" }, 270 | { "indexed": false, "internalType": "bytes32", "name": "x", "type": "bytes32" }, 271 | { "indexed": false, "internalType": "bytes32", "name": "y", "type": "bytes32" }, 272 | ], 273 | "name": "PubkeyChanged", 274 | "type": "event", 275 | }, 276 | { 277 | "anonymous": false, 278 | "inputs": [ 279 | { "indexed": true, "internalType": "bytes32", "name": "node", "type": "bytes32" }, 280 | { "indexed": false, "internalType": "string", "name": "name", "type": "string" }, 281 | ], 282 | "name": "NameChanged", 283 | "type": "event", 284 | }, 285 | { 286 | "anonymous": false, 287 | "inputs": [ 288 | { "indexed": true, "internalType": "bytes32", "name": "node", "type": "bytes32" }, 289 | { 290 | "indexed": true, 291 | "internalType": "bytes4", 292 | "name": "interfaceID", 293 | "type": "bytes4", 294 | }, 295 | { 296 | "indexed": false, 297 | "internalType": "address", 298 | "name": "implementer", 299 | "type": "address", 300 | }, 301 | ], 302 | "name": "InterfaceChanged", 303 | "type": "event", 304 | }, 305 | { 306 | "anonymous": false, 307 | "inputs": [ 308 | { "indexed": true, "internalType": "bytes32", "name": "node", "type": "bytes32" }, 309 | { "indexed": false, "internalType": "bytes", "name": "hash", "type": "bytes" }, 310 | ], 311 | "name": "ContenthashChanged", 312 | "type": "event", 313 | }, 314 | { 315 | "anonymous": false, 316 | "inputs": [ 317 | { "indexed": true, "internalType": "bytes32", "name": "node", "type": "bytes32" }, 318 | { "indexed": false, "internalType": "address", "name": "a", "type": "address" }, 319 | ], 320 | "name": "AddrChanged", 321 | "type": "event", 322 | }, 323 | { 324 | "anonymous": false, 325 | "inputs": [ 326 | { "indexed": true, "internalType": "bytes32", "name": "node", "type": "bytes32" }, 327 | { 328 | "indexed": false, 329 | "internalType": "uint256", 330 | "name": "coinType", 331 | "type": "uint256", 332 | }, 333 | { 334 | "indexed": false, 335 | "internalType": "bytes", 336 | "name": "newAddress", 337 | "type": "bytes", 338 | }, 339 | ], 340 | "name": "AddressChanged", 341 | "type": "event", 342 | }, 343 | { 344 | "anonymous": false, 345 | "inputs": [ 346 | { "indexed": true, "internalType": "bytes32", "name": "node", "type": "bytes32" }, 347 | { 348 | "indexed": true, 349 | "internalType": "uint256", 350 | "name": "contentType", 351 | "type": "uint256", 352 | }, 353 | ], 354 | "name": "ABIChanged", 355 | "type": "event", 356 | }, 357 | ] 358 | -------------------------------------------------------------------------------- /Sources/UnstoppableDomainsResolution/ABI/EthereumABI/ABIDecoder.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ABIDecoder.swift 3 | // resolution 4 | // 5 | // Created by Serg Merenkov on 2/8/21. 6 | // Copyright © 2021 Unstoppable Domains. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import BigInt 11 | import EthereumAddress 12 | 13 | public struct ABIDecoder { 14 | 15 | } 16 | 17 | // swiftlint:disable cyclomatic_complexity function_body_length 18 | extension ABIDecoder { 19 | public static func decode(types: [ABI.Element.InOut], data: Data) -> [AnyObject]? { 20 | let params = types.compactMap { (el) -> ABI.Element.ParameterType in 21 | return el.type 22 | } 23 | return decode(types: params, data: data) 24 | } 25 | 26 | public static func decode(types: [ABI.Element.ParameterType], data: Data) -> [AnyObject]? { 27 | var toReturn = [AnyObject]() 28 | var consumed: UInt64 = 0 29 | for i in 0 ..< types.count { 30 | let (v, c) = decodeSingleType(type: types[i], data: data, pointer: consumed) 31 | guard let valueUnwrapped = v, let consumedUnwrapped = c else {return nil} 32 | toReturn.append(valueUnwrapped) 33 | consumed += consumedUnwrapped 34 | } 35 | guard toReturn.count == types.count else {return nil} 36 | return toReturn 37 | } 38 | 39 | public static func decodeSingleType(type: ABI.Element.ParameterType, data: Data, pointer: UInt64 = 0) -> (value: AnyObject?, bytesConsumed: UInt64?) { 40 | let (elData, nextPtr) = followTheData(type: type, data: data, pointer: pointer) 41 | guard let elementItself = elData, let nextElementPointer = nextPtr else { 42 | return (nil, nil) 43 | } 44 | switch type { 45 | case .uint(let bits): 46 | guard elementItself.count >= 32 else {break} 47 | let mod = BigUInt(1) << bits 48 | let dataSlice = elementItself[0 ..< 32] 49 | let v = BigUInt(dataSlice) % mod 50 | return (v as AnyObject, type.memoryUsage) 51 | case .int(let bits): 52 | guard elementItself.count >= 32 else {break} 53 | let mod = BigInt(1) << bits 54 | let dataSlice = elementItself[0 ..< 32] 55 | let v = BigInt.fromTwosComplement(data: dataSlice) % mod 56 | return (v as AnyObject, type.memoryUsage) 57 | case .address: 58 | guard elementItself.count >= 32 else {break} 59 | let dataSlice = elementItself[12 ..< 32] 60 | let address = EthereumAddress(dataSlice) 61 | return (address as AnyObject, type.memoryUsage) 62 | case .bool: 63 | guard elementItself.count >= 32 else {break} 64 | let dataSlice = elementItself[0 ..< 32] 65 | let v = BigUInt(dataSlice) 66 | if v == BigUInt(1) { 67 | return (true as AnyObject, type.memoryUsage) 68 | } else if v == BigUInt(0) { 69 | return (false as AnyObject, type.memoryUsage) 70 | } 71 | case .bytes(let length): 72 | guard elementItself.count >= 32 else {break} 73 | let dataSlice = elementItself[0 ..< length] 74 | return (dataSlice as AnyObject, type.memoryUsage) 75 | case .string: 76 | guard elementItself.count >= 32 else {break} 77 | var dataSlice = elementItself[0 ..< 32] 78 | let length = UInt64(BigUInt(dataSlice)) 79 | guard elementItself.count >= 32+length else {break} 80 | dataSlice = elementItself[32 ..< 32 + length] 81 | guard let string = String(data: dataSlice, encoding: .utf8) else {break} 82 | 83 | return (string as AnyObject, type.memoryUsage) 84 | case .dynamicBytes: 85 | 86 | guard elementItself.count >= 32 else {break} 87 | var dataSlice = elementItself[0 ..< 32] 88 | let length = UInt64(BigUInt(dataSlice)) 89 | guard elementItself.count >= 32+length else {break} 90 | dataSlice = elementItself[32 ..< 32 + length] 91 | 92 | return (dataSlice as AnyObject, type.memoryUsage) 93 | case .array(type: let subType, length: let length): 94 | switch type.arraySize { 95 | case .dynamicSize: 96 | 97 | if subType.isStatic { 98 | 99 | guard elementItself.count >= 32 else {break} 100 | var dataSlice = elementItself[0 ..< 32] 101 | let length = UInt64(BigUInt(dataSlice)) 102 | guard elementItself.count >= 32 + subType.memoryUsage*length else {break} 103 | dataSlice = elementItself[32 ..< 32 + subType.memoryUsage*length] 104 | var subpointer: UInt64 = 32 105 | var toReturn = [AnyObject]() 106 | for _ in 0 ..< length { 107 | let (v, c) = decodeSingleType(type: subType, data: elementItself, pointer: subpointer) 108 | guard let valueUnwrapped = v, let consumedUnwrapped = c else {break} 109 | toReturn.append(valueUnwrapped) 110 | subpointer += consumedUnwrapped 111 | } 112 | return (toReturn as AnyObject, type.memoryUsage) 113 | } else { 114 | 115 | guard elementItself.count >= 32 else {break} 116 | var dataSlice = elementItself[0 ..< 32] 117 | let length = UInt64(BigUInt(dataSlice)) 118 | guard elementItself.count >= 32 else {break} 119 | dataSlice = Data(elementItself[32 ..< elementItself.count]) 120 | var subpointer: UInt64 = 0 121 | var toReturn = [AnyObject]() 122 | 123 | for _ in 0 ..< length { 124 | let (v, c) = decodeSingleType(type: subType, data: dataSlice, pointer: subpointer) 125 | guard let valueUnwrapped = v, let consumedUnwrapped = c else {break} 126 | toReturn.append(valueUnwrapped) 127 | subpointer += consumedUnwrapped 128 | } 129 | return (toReturn as AnyObject, nextElementPointer) 130 | } 131 | case .staticSize(let staticLength): 132 | 133 | guard length == staticLength else {break} 134 | var toReturn = [AnyObject]() 135 | var consumed: UInt64 = 0 136 | for _ in 0 ..< length { 137 | let (v, c) = decodeSingleType(type: subType, data: elementItself, pointer: consumed) 138 | guard let valueUnwrapped = v, let consumedUnwrapped = c else {return (nil, nil)} 139 | toReturn.append(valueUnwrapped) 140 | consumed += consumedUnwrapped 141 | } 142 | if subType.isStatic { 143 | return (toReturn as AnyObject, consumed) 144 | } else { 145 | return (toReturn as AnyObject, nextElementPointer) 146 | } 147 | case .notArray: 148 | break 149 | } 150 | case .tuple(types: let subTypes): 151 | 152 | var toReturn = [AnyObject]() 153 | var consumed: UInt64 = 0 154 | for i in 0 ..< subTypes.count { 155 | let (v, c) = decodeSingleType(type: subTypes[i], data: elementItself, pointer: consumed) 156 | guard let valueUnwrapped = v, let consumedUnwrapped = c else {return (nil, nil)} 157 | toReturn.append(valueUnwrapped) 158 | consumed += consumedUnwrapped 159 | } 160 | 161 | if type.isStatic { 162 | return (toReturn as AnyObject, consumed) 163 | } else { 164 | return (toReturn as AnyObject, nextElementPointer) 165 | } 166 | case .function: 167 | 168 | guard elementItself.count >= 32 else {break} 169 | let dataSlice = elementItself[8 ..< 32] 170 | 171 | return (dataSlice as AnyObject, type.memoryUsage) 172 | } 173 | return (nil, nil) 174 | } 175 | 176 | fileprivate static func followTheData(type: ABI.Element.ParameterType, data: Data, pointer: UInt64 = 0) -> (elementEncoding: Data?, nextElementPointer: UInt64?) { 177 | 178 | if type.isStatic { 179 | guard data.count >= pointer + type.memoryUsage else {return (nil, nil)} 180 | let elementItself = data[pointer ..< pointer + type.memoryUsage] 181 | let nextElement = pointer + type.memoryUsage 182 | 183 | return (Data(elementItself), nextElement) 184 | } else { 185 | guard data.count >= pointer + type.memoryUsage else {return (nil, nil)} 186 | let dataSlice = data[pointer ..< pointer + type.memoryUsage] 187 | let bn = BigUInt(dataSlice) 188 | if bn > UINT64_MAX || bn >= data.count { 189 | // there are ERC20 contracts that use bytes32 intead of string. Let's be optimistic and return some data 190 | if case .string = type { 191 | let nextElement = pointer + type.memoryUsage 192 | let preambula = BigUInt(32).abiEncode(bits: 256)! 193 | return (preambula + Data(dataSlice), nextElement) 194 | } else if case .dynamicBytes = type { 195 | let nextElement = pointer + type.memoryUsage 196 | let preambula = BigUInt(32).abiEncode(bits: 256)! 197 | return (preambula + Data(dataSlice), nextElement) 198 | } 199 | return (nil, nil) 200 | } 201 | let elementPointer = UInt64(bn) 202 | let elementItself = data[elementPointer ..< UInt64(data.count)] 203 | let nextElement = pointer + type.memoryUsage 204 | 205 | return (Data(elementItself), nextElement) 206 | } 207 | } 208 | 209 | public static func decodeLog(event: ABI.Element.Event, eventLogTopics: [Data], eventLogData: Data) -> [String: Any]? { 210 | if event.topic != eventLogTopics[0] && !event.anonymous { 211 | return nil 212 | } 213 | var eventContent = [String: Any]() 214 | eventContent["name"]=event.name 215 | let logs = eventLogTopics 216 | let dataForProcessing = eventLogData 217 | let indexedInputs = event.inputs.filter { (inp) -> Bool in 218 | return inp.indexed 219 | } 220 | if logs.count == 1 && indexedInputs.count > 0 { 221 | return nil 222 | } 223 | let nonIndexedInputs = event.inputs.filter { (inp) -> Bool in 224 | return !inp.indexed 225 | } 226 | let nonIndexedTypes = nonIndexedInputs.compactMap { (inp) -> ABI.Element.ParameterType in 227 | return inp.type 228 | } 229 | guard logs.count == indexedInputs.count + 1 else {return nil} 230 | var indexedValues = [AnyObject]() 231 | for i in 0 ..< indexedInputs.count { 232 | let data = logs[i+1] 233 | let input = indexedInputs[i] 234 | if !input.type.isStatic || input.type.isArray || input.type.memoryUsage != 32 { 235 | let (v, _) = ABIDecoder.decodeSingleType(type: .bytes(length: 32), data: data) 236 | guard let valueUnwrapped = v else {return nil} 237 | indexedValues.append(valueUnwrapped) 238 | } else { 239 | let (v, _) = ABIDecoder.decodeSingleType(type: input.type, data: data) 240 | guard let valueUnwrapped = v else {return nil} 241 | indexedValues.append(valueUnwrapped) 242 | } 243 | } 244 | let v = ABIDecoder.decode(types: nonIndexedTypes, data: dataForProcessing) 245 | guard let nonIndexedValues = v else {return nil} 246 | var indexedInputCounter = 0 247 | var nonIndexedInputCounter = 0 248 | for i in 0 ..< event.inputs.count { 249 | let el = event.inputs[i] 250 | if el.indexed { 251 | let name = "\(i)" 252 | let value = indexedValues[indexedInputCounter] 253 | eventContent[name] = value 254 | if el.name != "" { 255 | eventContent[el.name] = value 256 | } 257 | indexedInputCounter += 1 258 | } else { 259 | let name = "\(i)" 260 | let value = nonIndexedValues[nonIndexedInputCounter] 261 | eventContent[name] = value 262 | if el.name != "" { 263 | eventContent[el.name] = value 264 | } 265 | nonIndexedInputCounter += 1 266 | } 267 | } 268 | return eventContent 269 | } 270 | } 271 | -------------------------------------------------------------------------------- /Sources/UnstoppableDomainsResolution/ABI/EthereumABI/ABIEncoder.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ABIEncoder.swift 3 | // resolution 4 | // 5 | // Created by Serg Merenkov on 2/8/21. 6 | // Copyright © 2021 Unstoppable Domains. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import BigInt 11 | import EthereumAddress 12 | 13 | public struct ABIEncoder { 14 | 15 | } 16 | 17 | // swiftlint:disable cyclomatic_complexity function_body_length unneeded_break_in_switch 18 | extension ABIEncoder { 19 | public static func convertToBigUInt(_ value: AnyObject) -> BigUInt? { 20 | switch value { 21 | case let v as BigUInt: 22 | return v 23 | case let v as BigInt: 24 | switch v.sign { 25 | case .minus: 26 | return nil 27 | case .plus: 28 | return v.magnitude 29 | } 30 | case let v as String: 31 | let base10 = BigUInt(v, radix: 10) 32 | if base10 != nil { 33 | return base10! 34 | } 35 | let base16 = BigUInt(v.stripHexPrefix(), radix: 16) 36 | if base16 != nil { 37 | return base16! 38 | } 39 | break 40 | case let v as UInt: 41 | return BigUInt(v) 42 | case let v as UInt8: 43 | return BigUInt(v) 44 | case let v as UInt16: 45 | return BigUInt(v) 46 | case let v as UInt32: 47 | return BigUInt(v) 48 | case let v as UInt64: 49 | return BigUInt(v) 50 | case let v as Int: 51 | return BigUInt(v) 52 | case let v as Int8: 53 | return BigUInt(v) 54 | case let v as Int16: 55 | return BigUInt(v) 56 | case let v as Int32: 57 | return BigUInt(v) 58 | case let v as Int64: 59 | return BigUInt(v) 60 | default: 61 | return nil 62 | } 63 | return nil 64 | } 65 | 66 | public static func convertToBigInt(_ value: AnyObject) -> BigInt? { 67 | switch value { 68 | case let v as BigUInt: 69 | return BigInt(v) 70 | case let v as BigInt: 71 | return v 72 | case let v as String: 73 | let base10 = BigInt(v, radix: 10) 74 | if base10 != nil { 75 | return base10 76 | } 77 | let base16 = BigInt(v.stripHexPrefix(), radix: 16) 78 | if base16 != nil { 79 | return base16 80 | } 81 | break 82 | case let v as UInt: 83 | return BigInt(v) 84 | case let v as UInt8: 85 | return BigInt(v) 86 | case let v as UInt16: 87 | return BigInt(v) 88 | case let v as UInt32: 89 | return BigInt(v) 90 | case let v as UInt64: 91 | return BigInt(v) 92 | case let v as Int: 93 | return BigInt(v) 94 | case let v as Int8: 95 | return BigInt(v) 96 | case let v as Int16: 97 | return BigInt(v) 98 | case let v as Int32: 99 | return BigInt(v) 100 | case let v as Int64: 101 | return BigInt(v) 102 | default: 103 | return nil 104 | } 105 | return nil 106 | } 107 | 108 | public static func convertToData(_ value: AnyObject) -> Data? { 109 | switch value { 110 | case let d as Data: 111 | return d 112 | case let d as String: 113 | if d.hasHexPrefix() { 114 | let hex = Data.fromHex(d) 115 | if hex != nil { 116 | return hex 117 | } 118 | } 119 | let str = d.data(using: .utf8) 120 | if str != nil { 121 | return str 122 | } 123 | case let d as [UInt8]: 124 | return Data(d) 125 | case let d as EthereumAddress: 126 | return d.addressData 127 | case let d as [IntegerLiteralType]: 128 | var bytesArray = [UInt8]() 129 | for el in d { 130 | guard el >= 0, el <= 255 else {return nil} 131 | bytesArray.append(UInt8(el)) 132 | } 133 | return Data(bytesArray) 134 | default: 135 | return nil 136 | } 137 | return nil 138 | } 139 | 140 | public static func encode(types: [ABI.Element.InOut], values: [AnyObject]) -> Data? { 141 | guard types.count == values.count else {return nil} 142 | let params = types.compactMap { (el) -> ABI.Element.ParameterType in 143 | return el.type 144 | } 145 | return encode(types: params, values: values) 146 | } 147 | 148 | public static func encode(types: [ABI.Element.ParameterType], values: [AnyObject]) -> Data? { 149 | guard types.count == values.count else {return nil} 150 | var tails = [Data]() 151 | var heads = [Data]() 152 | for i in 0 ..< types.count { 153 | let enc = encodeSingleType(type: types[i], value: values[i]) 154 | guard let encoding = enc else {return nil} 155 | if types[i].isStatic { 156 | heads.append(encoding) 157 | tails.append(Data()) 158 | } else { 159 | heads.append(Data(repeating: 0x0, count: 32)) 160 | tails.append(encoding) 161 | } 162 | } 163 | var headsConcatenated = Data() 164 | for h in heads { 165 | headsConcatenated.append(h) 166 | } 167 | var tailsPointer = BigUInt(headsConcatenated.count) 168 | headsConcatenated = Data() 169 | var tailsConcatenated = Data() 170 | for i in 0 ..< types.count { 171 | let head = heads[i] 172 | let tail = tails[i] 173 | if !types[i].isStatic { 174 | guard let newHead = tailsPointer.abiEncode(bits: 256) else {return nil} 175 | headsConcatenated.append(newHead) 176 | tailsConcatenated.append(tail) 177 | tailsPointer += BigUInt(tail.count) 178 | } else { 179 | headsConcatenated.append(head) 180 | tailsConcatenated.append(tail) 181 | } 182 | } 183 | return headsConcatenated + tailsConcatenated 184 | } 185 | 186 | public static func encodeSingleType(type: ABI.Element.ParameterType, value: AnyObject) -> Data? { 187 | switch type { 188 | case .uint: 189 | if let biguint = convertToBigUInt(value) { 190 | return biguint.abiEncode(bits: 256) 191 | } 192 | if let bigint = convertToBigInt(value) { 193 | return bigint.abiEncode(bits: 256) 194 | } 195 | case .int: 196 | if let biguint = convertToBigUInt(value) { 197 | return biguint.abiEncode(bits: 256) 198 | } 199 | if let bigint = convertToBigInt(value) { 200 | return bigint.abiEncode(bits: 256) 201 | } 202 | case .address: 203 | if let string = value as? String { 204 | guard let address = EthereumAddress(string) else {return nil} 205 | let data = address.addressData 206 | return data.setLengthLeft(32) 207 | } else if let address = value as? EthereumAddress { 208 | guard address.isValid else {break} 209 | let data = address.addressData 210 | return data.setLengthLeft(32) 211 | } else if let data = value as? Data { 212 | return data.setLengthLeft(32) 213 | } 214 | case .bool: 215 | if let bool = value as? Bool { 216 | if bool { 217 | return BigUInt(1).abiEncode(bits: 256) 218 | } else { 219 | return BigUInt(0).abiEncode(bits: 256) 220 | } 221 | } 222 | case .bytes(let length): 223 | guard let data = convertToData(value) else {break} 224 | if data.count > length {break} 225 | return data.setLengthRight(32) 226 | case .string: 227 | if let string = value as? String { 228 | var dataGuess: Data? 229 | if string.hasHexPrefix() { 230 | dataGuess = Data.fromHex(string.lowercased().stripHexPrefix()) 231 | } else { 232 | dataGuess = string.data(using: .utf8) 233 | } 234 | guard let data = dataGuess else {break} 235 | let minLength = ((data.count + 31) / 32)*32 236 | guard let paddedData = data.setLengthRight(UInt64(minLength)) else {break} 237 | let length = BigUInt(data.count) 238 | guard let head = length.abiEncode(bits: 256) else {break} 239 | let total = head+paddedData 240 | return total 241 | } 242 | case .dynamicBytes: 243 | guard let data = convertToData(value) else {break} 244 | let minLength = ((data.count + 31) / 32)*32 245 | guard let paddedData = data.setLengthRight(UInt64(minLength)) else {break} 246 | let length = BigUInt(data.count) 247 | guard let head = length.abiEncode(bits: 256) else {break} 248 | let total = head+paddedData 249 | return total 250 | case .array(type: let subType, length: let length): 251 | switch type.arraySize { 252 | case .dynamicSize: 253 | guard length == 0 else {break} 254 | guard let val = value as? [AnyObject] else {break} 255 | guard let lengthEncoding = BigUInt(val.count).abiEncode(bits: 256) else {break} 256 | if subType.isStatic { 257 | // work in a previous context 258 | var toReturn = Data() 259 | for i in 0 ..< val.count { 260 | let enc = encodeSingleType(type: subType, value: val[i]) 261 | guard let encoding = enc else {break} 262 | toReturn.append(encoding) 263 | } 264 | let total = lengthEncoding + toReturn 265 | 266 | return total 267 | } else { 268 | // create new context 269 | var tails = [Data]() 270 | var heads = [Data]() 271 | for i in 0 ..< val.count { 272 | let enc = encodeSingleType(type: subType, value: val[i]) 273 | guard let encoding = enc else {return nil} 274 | heads.append(Data(repeating: 0x0, count: 32)) 275 | tails.append(encoding) 276 | } 277 | var headsConcatenated = Data() 278 | for h in heads { 279 | headsConcatenated.append(h) 280 | } 281 | var tailsPointer = BigUInt(headsConcatenated.count) 282 | headsConcatenated = Data() 283 | var tailsConcatenated = Data() 284 | for i in 0 ..< val.count { 285 | let head = heads[i] 286 | let tail = tails[i] 287 | if tail != Data() { 288 | guard let newHead = tailsPointer.abiEncode(bits: 256) else {return nil} 289 | headsConcatenated.append(newHead) 290 | tailsConcatenated.append(tail) 291 | tailsPointer += BigUInt(tail.count) 292 | } else { 293 | headsConcatenated.append(head) 294 | tailsConcatenated.append(tail) 295 | } 296 | } 297 | let total = lengthEncoding + headsConcatenated + tailsConcatenated 298 | 299 | return total 300 | } 301 | case .staticSize(let staticLength): 302 | guard staticLength != 0 else {break} 303 | guard let val = value as? [AnyObject] else {break} 304 | guard staticLength == val.count else {break} 305 | if subType.isStatic { 306 | // work in a previous context 307 | var toReturn = Data() 308 | for i in 0 ..< val.count { 309 | let enc = encodeSingleType(type: subType, value: val[i]) 310 | guard let encoding = enc else {break} 311 | toReturn.append(encoding) 312 | } 313 | 314 | let total = toReturn 315 | return total 316 | } else { 317 | // create new context 318 | var tails = [Data]() 319 | var heads = [Data]() 320 | for i in 0 ..< val.count { 321 | let enc = encodeSingleType(type: subType, value: val[i]) 322 | guard let encoding = enc else {return nil} 323 | heads.append(Data(repeating: 0x0, count: 32)) 324 | tails.append(encoding) 325 | } 326 | var headsConcatenated = Data() 327 | for h in heads { 328 | headsConcatenated.append(h) 329 | } 330 | var tailsPointer = BigUInt(headsConcatenated.count) 331 | headsConcatenated = Data() 332 | var tailsConcatenated = Data() 333 | for i in 0 ..< val.count { 334 | let tail = tails[i] 335 | guard let newHead = tailsPointer.abiEncode(bits: 256) else {return nil} 336 | headsConcatenated.append(newHead) 337 | tailsConcatenated.append(tail) 338 | tailsPointer += BigUInt(tail.count) 339 | } 340 | let total = headsConcatenated + tailsConcatenated 341 | 342 | return total 343 | } 344 | case .notArray: 345 | break 346 | } 347 | case .tuple(types: let subTypes): 348 | var tails = [Data]() 349 | var heads = [Data]() 350 | guard let val = value as? [AnyObject] else {break} 351 | for i in 0 ..< subTypes.count { 352 | let enc = encodeSingleType(type: subTypes[i], value: val[i]) 353 | guard let encoding = enc else {return nil} 354 | if subTypes[i].isStatic { 355 | heads.append(encoding) 356 | tails.append(Data()) 357 | } else { 358 | heads.append(Data(repeating: 0x0, count: 32)) 359 | tails.append(encoding) 360 | } 361 | } 362 | var headsConcatenated = Data() 363 | for h in heads { 364 | headsConcatenated.append(h) 365 | } 366 | var tailsPointer = BigUInt(headsConcatenated.count) 367 | headsConcatenated = Data() 368 | var tailsConcatenated = Data() 369 | for i in 0 ..< subTypes.count { 370 | let head = heads[i] 371 | let tail = tails[i] 372 | if !subTypes[i].isStatic { 373 | guard let newHead = tailsPointer.abiEncode(bits: 256) else {return nil} 374 | headsConcatenated.append(newHead) 375 | tailsConcatenated.append(tail) 376 | tailsPointer += BigUInt(tail.count) 377 | } else { 378 | headsConcatenated.append(head) 379 | tailsConcatenated.append(tail) 380 | } 381 | } 382 | let total = headsConcatenated + tailsConcatenated 383 | return total 384 | case .function: 385 | if let data = value as? Data { 386 | return data.setLengthLeft(32) 387 | } 388 | } 389 | return nil 390 | } 391 | } 392 | -------------------------------------------------------------------------------- /Sources/UnstoppableDomainsResolution/Resources/CNS/cnsProxyReader.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "inputs": [ 4 | { 5 | "internalType": "contract Registry", 6 | "name": "registry", 7 | "type": "address" 8 | } 9 | ], 10 | "payable": false, 11 | "stateMutability": "nonpayable", 12 | "type": "constructor" 13 | }, 14 | { 15 | "constant": true, 16 | "inputs": [], 17 | "name": "NAME", 18 | "outputs": [ 19 | { 20 | "internalType": "string", 21 | "name": "", 22 | "type": "string" 23 | } 24 | ], 25 | "payable": false, 26 | "stateMutability": "view", 27 | "type": "function" 28 | }, 29 | { 30 | "constant": true, 31 | "inputs": [], 32 | "name": "VERSION", 33 | "outputs": [ 34 | { 35 | "internalType": "string", 36 | "name": "", 37 | "type": "string" 38 | } 39 | ], 40 | "payable": false, 41 | "stateMutability": "view", 42 | "type": "function" 43 | }, 44 | { 45 | "constant": true, 46 | "inputs": [ 47 | { 48 | "internalType": "bytes4", 49 | "name": "interfaceId", 50 | "type": "bytes4" 51 | } 52 | ], 53 | "name": "supportsInterface", 54 | "outputs": [ 55 | { 56 | "internalType": "bool", 57 | "name": "", 58 | "type": "bool" 59 | } 60 | ], 61 | "payable": false, 62 | "stateMutability": "view", 63 | "type": "function" 64 | }, 65 | { 66 | "constant": true, 67 | "inputs": [], 68 | "name": "name", 69 | "outputs": [ 70 | { 71 | "internalType": "string", 72 | "name": "", 73 | "type": "string" 74 | } 75 | ], 76 | "payable": false, 77 | "stateMutability": "view", 78 | "type": "function" 79 | }, 80 | { 81 | "constant": true, 82 | "inputs": [], 83 | "name": "symbol", 84 | "outputs": [ 85 | { 86 | "internalType": "string", 87 | "name": "", 88 | "type": "string" 89 | } 90 | ], 91 | "payable": false, 92 | "stateMutability": "view", 93 | "type": "function" 94 | }, 95 | { 96 | "constant": true, 97 | "inputs": [ 98 | { 99 | "internalType": "uint256", 100 | "name": "tokenId", 101 | "type": "uint256" 102 | } 103 | ], 104 | "name": "tokenURI", 105 | "outputs": [ 106 | { 107 | "internalType": "string", 108 | "name": "", 109 | "type": "string" 110 | } 111 | ], 112 | "payable": false, 113 | "stateMutability": "view", 114 | "type": "function" 115 | }, 116 | { 117 | "constant": true, 118 | "inputs": [ 119 | { 120 | "internalType": "address", 121 | "name": "spender", 122 | "type": "address" 123 | }, 124 | { 125 | "internalType": "uint256", 126 | "name": "tokenId", 127 | "type": "uint256" 128 | } 129 | ], 130 | "name": "isApprovedOrOwner", 131 | "outputs": [ 132 | { 133 | "internalType": "bool", 134 | "name": "", 135 | "type": "bool" 136 | } 137 | ], 138 | "payable": false, 139 | "stateMutability": "view", 140 | "type": "function" 141 | }, 142 | { 143 | "constant": true, 144 | "inputs": [ 145 | { 146 | "internalType": "uint256", 147 | "name": "tokenId", 148 | "type": "uint256" 149 | } 150 | ], 151 | "name": "resolverOf", 152 | "outputs": [ 153 | { 154 | "internalType": "address", 155 | "name": "", 156 | "type": "address" 157 | } 158 | ], 159 | "payable": false, 160 | "stateMutability": "view", 161 | "type": "function" 162 | }, 163 | { 164 | "constant": true, 165 | "inputs": [ 166 | { 167 | "internalType": "uint256", 168 | "name": "tokenId", 169 | "type": "uint256" 170 | }, 171 | { 172 | "internalType": "string", 173 | "name": "label", 174 | "type": "string" 175 | } 176 | ], 177 | "name": "childIdOf", 178 | "outputs": [ 179 | { 180 | "internalType": "uint256", 181 | "name": "", 182 | "type": "uint256" 183 | } 184 | ], 185 | "payable": false, 186 | "stateMutability": "view", 187 | "type": "function" 188 | }, 189 | { 190 | "constant": true, 191 | "inputs": [ 192 | { 193 | "internalType": "address", 194 | "name": "account", 195 | "type": "address" 196 | } 197 | ], 198 | "name": "isController", 199 | "outputs": [ 200 | { 201 | "internalType": "bool", 202 | "name": "", 203 | "type": "bool" 204 | } 205 | ], 206 | "payable": false, 207 | "stateMutability": "view", 208 | "type": "function" 209 | }, 210 | { 211 | "constant": true, 212 | "inputs": [ 213 | { 214 | "internalType": "address", 215 | "name": "owner", 216 | "type": "address" 217 | } 218 | ], 219 | "name": "balanceOf", 220 | "outputs": [ 221 | { 222 | "internalType": "uint256", 223 | "name": "", 224 | "type": "uint256" 225 | } 226 | ], 227 | "payable": false, 228 | "stateMutability": "view", 229 | "type": "function" 230 | }, 231 | { 232 | "constant": true, 233 | "inputs": [ 234 | { 235 | "internalType": "uint256", 236 | "name": "tokenId", 237 | "type": "uint256" 238 | } 239 | ], 240 | "name": "ownerOf", 241 | "outputs": [ 242 | { 243 | "internalType": "address", 244 | "name": "", 245 | "type": "address" 246 | } 247 | ], 248 | "payable": false, 249 | "stateMutability": "view", 250 | "type": "function" 251 | }, 252 | { 253 | "constant": true, 254 | "inputs": [ 255 | { 256 | "internalType": "uint256", 257 | "name": "tokenId", 258 | "type": "uint256" 259 | } 260 | ], 261 | "name": "getApproved", 262 | "outputs": [ 263 | { 264 | "internalType": "address", 265 | "name": "", 266 | "type": "address" 267 | } 268 | ], 269 | "payable": false, 270 | "stateMutability": "view", 271 | "type": "function" 272 | }, 273 | { 274 | "constant": true, 275 | "inputs": [ 276 | { 277 | "internalType": "address", 278 | "name": "owner", 279 | "type": "address" 280 | }, 281 | { 282 | "internalType": "address", 283 | "name": "operator", 284 | "type": "address" 285 | } 286 | ], 287 | "name": "isApprovedForAll", 288 | "outputs": [ 289 | { 290 | "internalType": "bool", 291 | "name": "", 292 | "type": "bool" 293 | } 294 | ], 295 | "payable": false, 296 | "stateMutability": "view", 297 | "type": "function" 298 | }, 299 | { 300 | "constant": true, 301 | "inputs": [], 302 | "name": "root", 303 | "outputs": [ 304 | { 305 | "internalType": "uint256", 306 | "name": "", 307 | "type": "uint256" 308 | } 309 | ], 310 | "payable": false, 311 | "stateMutability": "view", 312 | "type": "function" 313 | }, 314 | { 315 | "constant": true, 316 | "inputs": [ 317 | { 318 | "internalType": "uint256", 319 | "name": "tokenId", 320 | "type": "uint256" 321 | } 322 | ], 323 | "name": "nonceOf", 324 | "outputs": [ 325 | { 326 | "internalType": "uint256", 327 | "name": "", 328 | "type": "uint256" 329 | } 330 | ], 331 | "payable": false, 332 | "stateMutability": "view", 333 | "type": "function" 334 | }, 335 | { 336 | "constant": true, 337 | "inputs": [], 338 | "name": "registry", 339 | "outputs": [ 340 | { 341 | "internalType": "address", 342 | "name": "", 343 | "type": "address" 344 | } 345 | ], 346 | "payable": false, 347 | "stateMutability": "view", 348 | "type": "function" 349 | }, 350 | { 351 | "constant": true, 352 | "inputs": [ 353 | { 354 | "internalType": "string", 355 | "name": "key", 356 | "type": "string" 357 | }, 358 | { 359 | "internalType": "uint256", 360 | "name": "tokenId", 361 | "type": "uint256" 362 | } 363 | ], 364 | "name": "get", 365 | "outputs": [ 366 | { 367 | "internalType": "string", 368 | "name": "", 369 | "type": "string" 370 | } 371 | ], 372 | "payable": false, 373 | "stateMutability": "view", 374 | "type": "function" 375 | }, 376 | { 377 | "constant": true, 378 | "inputs": [ 379 | { 380 | "internalType": "string[]", 381 | "name": "keys", 382 | "type": "string[]" 383 | }, 384 | { 385 | "internalType": "uint256", 386 | "name": "tokenId", 387 | "type": "uint256" 388 | } 389 | ], 390 | "name": "getMany", 391 | "outputs": [ 392 | { 393 | "internalType": "string[]", 394 | "name": "", 395 | "type": "string[]" 396 | } 397 | ], 398 | "payable": false, 399 | "stateMutability": "view", 400 | "type": "function" 401 | }, 402 | { 403 | "constant": true, 404 | "inputs": [ 405 | { 406 | "internalType": "uint256", 407 | "name": "keyHash", 408 | "type": "uint256" 409 | }, 410 | { 411 | "internalType": "uint256", 412 | "name": "tokenId", 413 | "type": "uint256" 414 | } 415 | ], 416 | "name": "getByHash", 417 | "outputs": [ 418 | { 419 | "internalType": "string", 420 | "name": "key", 421 | "type": "string" 422 | }, 423 | { 424 | "internalType": "string", 425 | "name": "value", 426 | "type": "string" 427 | } 428 | ], 429 | "payable": false, 430 | "stateMutability": "view", 431 | "type": "function" 432 | }, 433 | { 434 | "constant": true, 435 | "inputs": [ 436 | { 437 | "internalType": "uint256[]", 438 | "name": "keyHashes", 439 | "type": "uint256[]" 440 | }, 441 | { 442 | "internalType": "uint256", 443 | "name": "tokenId", 444 | "type": "uint256" 445 | } 446 | ], 447 | "name": "getManyByHash", 448 | "outputs": [ 449 | { 450 | "internalType": "string[]", 451 | "name": "keys", 452 | "type": "string[]" 453 | }, 454 | { 455 | "internalType": "string[]", 456 | "name": "values", 457 | "type": "string[]" 458 | } 459 | ], 460 | "payable": false, 461 | "stateMutability": "view", 462 | "type": "function" 463 | }, 464 | { 465 | "constant": false, 466 | "inputs": [ 467 | { 468 | "internalType": "string[]", 469 | "name": "keys", 470 | "type": "string[]" 471 | }, 472 | { 473 | "internalType": "uint256", 474 | "name": "tokenId", 475 | "type": "uint256" 476 | } 477 | ], 478 | "name": "getData", 479 | "outputs": [ 480 | { 481 | "internalType": "address", 482 | "name": "resolver", 483 | "type": "address" 484 | }, 485 | { 486 | "internalType": "address", 487 | "name": "owner", 488 | "type": "address" 489 | }, 490 | { 491 | "internalType": "string[]", 492 | "name": "values", 493 | "type": "string[]" 494 | } 495 | ], 496 | "payable": false, 497 | "stateMutability": "nonpayable", 498 | "type": "function" 499 | }, 500 | { 501 | "constant": false, 502 | "inputs": [ 503 | { 504 | "internalType": "string[]", 505 | "name": "keys", 506 | "type": "string[]" 507 | }, 508 | { 509 | "internalType": "uint256[]", 510 | "name": "tokenIds", 511 | "type": "uint256[]" 512 | } 513 | ], 514 | "name": "getDataForMany", 515 | "outputs": [ 516 | { 517 | "internalType": "address[]", 518 | "name": "resolvers", 519 | "type": "address[]" 520 | }, 521 | { 522 | "internalType": "address[]", 523 | "name": "owners", 524 | "type": "address[]" 525 | }, 526 | { 527 | "internalType": "string[][]", 528 | "name": "values", 529 | "type": "string[][]" 530 | } 531 | ], 532 | "payable": false, 533 | "stateMutability": "nonpayable", 534 | "type": "function" 535 | }, 536 | { 537 | "constant": false, 538 | "inputs": [ 539 | { 540 | "internalType": "uint256[]", 541 | "name": "keyHashes", 542 | "type": "uint256[]" 543 | }, 544 | { 545 | "internalType": "uint256", 546 | "name": "tokenId", 547 | "type": "uint256" 548 | } 549 | ], 550 | "name": "getDataByHash", 551 | "outputs": [ 552 | { 553 | "internalType": "address", 554 | "name": "resolver", 555 | "type": "address" 556 | }, 557 | { 558 | "internalType": "address", 559 | "name": "owner", 560 | "type": "address" 561 | }, 562 | { 563 | "internalType": "string[]", 564 | "name": "keys", 565 | "type": "string[]" 566 | }, 567 | { 568 | "internalType": "string[]", 569 | "name": "values", 570 | "type": "string[]" 571 | } 572 | ], 573 | "payable": false, 574 | "stateMutability": "nonpayable", 575 | "type": "function" 576 | }, 577 | { 578 | "constant": false, 579 | "inputs": [ 580 | { 581 | "internalType": "uint256[]", 582 | "name": "keyHashes", 583 | "type": "uint256[]" 584 | }, 585 | { 586 | "internalType": "uint256[]", 587 | "name": "tokenIds", 588 | "type": "uint256[]" 589 | } 590 | ], 591 | "name": "getDataByHashForMany", 592 | "outputs": [ 593 | { 594 | "internalType": "address[]", 595 | "name": "resolvers", 596 | "type": "address[]" 597 | }, 598 | { 599 | "internalType": "address[]", 600 | "name": "owners", 601 | "type": "address[]" 602 | }, 603 | { 604 | "internalType": "string[][]", 605 | "name": "keys", 606 | "type": "string[][]" 607 | }, 608 | { 609 | "internalType": "string[][]", 610 | "name": "values", 611 | "type": "string[][]" 612 | } 613 | ], 614 | "payable": false, 615 | "stateMutability": "nonpayable", 616 | "type": "function" 617 | }, 618 | { 619 | "constant": false, 620 | "inputs": [ 621 | { 622 | "internalType": "uint256[]", 623 | "name": "tokenIds", 624 | "type": "uint256[]" 625 | } 626 | ], 627 | "name": "ownerOfForMany", 628 | "outputs": [ 629 | { 630 | "internalType": "address[]", 631 | "name": "owners", 632 | "type": "address[]" 633 | } 634 | ], 635 | "payable": false, 636 | "stateMutability": "nonpayable", 637 | "type": "function" 638 | } 639 | ] 640 | --------------------------------------------------------------------------------