├── 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 | [](https://travis-ci.org/keefertaylor/Base58Swift)
4 | [](https://codecov.io/gh/keefertaylor/Base58Swift)
5 | [](https://github.com/Carthage/Carthage)
6 | [](http://cocoapods.org/pods/Base58Swift)
7 | [](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 | [](https://discord.gg/b6ZVxSZ9Hn)
4 | [](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 |
--------------------------------------------------------------------------------