Skip to content

Commit

Permalink
[DT-1066] Add ud proxy configuration and remove default rpc url (#83) (
Browse files Browse the repository at this point in the history
…#84)

* Add interface for API key

* Add override to allow init with zns

* Removed default key and add doc

* Add versioning header

* Added new error types

* Change zlayer to znsLayer

* Remove test key
  • Loading branch information
tunguyen authored Apr 5, 2023
1 parent 80729e3 commit 8ca3d61
Show file tree
Hide file tree
Showing 13 changed files with 224 additions and 152 deletions.
103 changes: 81 additions & 22 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,19 +7,24 @@ Resolution is a library for interacting with blockchain domain names. It can be

Resolution is primarily built and maintained by [Unstoppable Domains](https://unstoppabledomains.com/).

# Installing the library
- [Installing Resolution](#installing-resolution-swift)
- [Using Resolution](#using-resolution)
- [Contributions](#contributions)
- [Free advertising for integrated apps](#free-advertising-for-integrated-apps)

# Installing resolution-swift

## Cocoa Pods

```ruby
pod 'UnstoppableDomainsResolution', '~> 5.2.1'
pod 'UnstoppableDomainsResolution', '~> 6.0.0'
```

## Swift Package Manager

```swift
package.dependencies.append(
.package(url: "https://github.com/unstoppabledomains/resolution-swift", from: "5.0.0")
.package(url: "https://github.com/unstoppabledomains/resolution-swift", from: "6.0.0")
)
```

Expand All @@ -39,44 +44,71 @@ package.dependencies.append(
)
```

# Usage
# Using Resolution

- Create an instance of the Resolution class
- Call any method of the Resolution class asyncronously

> 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.
## Initialize with Unstoppable Domains' Proxy Provider

```swift
import UnstoppableDomainsResolution

guard let resolution = try? Resolution() else {
print ("Init of Resolution instance with default parameters failed...")
// obtain a key from https://unstoppabledomains.com/partner-api-dashboard if you are a partner
guard let resolution = try? Resolution(apiKey: "<api_key>") else {
print ("Init of Resolution instance failed...")
return
}
```

## Customizing naming services
> NOTE: The `apiKey` is only used resolve domains from UNS. Behind the scene, it still uses the default ZNS (Zilliqa) RPC url. For additional control, please specify your ZNS configuration.
> NOTE: The default Infura key provided is rate limited and should only be used for testing. For production applications, please bring your own Infura or Alchemy RPC URL to prevent downtime.
```swift
import UnstoppableDomainsResolution

Version 0.3.0 introduced the `Configurations` struct that is used for configuring each connected naming service.
// obtain a key from https://unstoppabledomains.com/partner-api-dashboard if you are a partner
guard let resolution = try? Resolution(
apiKey: "<api_key>",
znsLayer: NamingServiceConfig(
providerUrl: "https://api.zilliqa.com",
network: "mainnet")
) else {
print ("Init of Resolution instance with default parameters failed...")
return
}
```

## Initialize with Custom Ethereum Configuration

The `Configurations` struct that is used for configuring each connected naming service.
Library supports three networks at the moment Ethereum, Polygon and Zilliqa. You can update each network separately.

```swift
import UnstoppableDomainsResolution

// obtain a key from https://www.infura.io
let resolution = try Resolution(configs: Configurations(
uns: UnsLocations = UnsLocations(
layer1: NamingServiceConfig(
providerUrl: "https://mainnet.infura.io/v3/3c25f57353234b1b853e9861050f4817",
providerUrl: "https://mainnet.infura.io/v3/<infura_api_key>",
network: "mainnet"),
layer2: NamingServiceConfig(
providerUrl: "https://polygon-mainnet.infura.io/v3/3c25f57353234b1b853e9861050f4817",
providerUrl: "https://polygon-mainnet.infura.io/v3/<infura_api_key>",
network: "polygon-mainnet"),
zlayer: NamingServiceConfig(
znsLayer: NamingServiceConfig(
providerUrl: "https://api.zilliqa.com",
network: "mainnet")
)
);

```

## Examples

### Getting a domain's crypto address
```swift
resolution.addr(domain: "brad.crypto", ticker: "eth") { (result) in
switch result {
case .success(let returnValue):
Expand All @@ -88,9 +120,9 @@ resolution.addr(domain: "brad.crypto", ticker: "eth") { (result) in
}
```

## Batch requesting of owners
### Batch requesting of owners

Version 0.1.3 introduced the `batchOwners(domains: _, completion: _ )` method which adds additional convenience when making multiple domain owner queries.
the `batchOwners(domains: _, completion: _ )` method adds additional convenience when making multiple domain owner queries.

> This method is only compatible with uns-based domains. Using this method with any other domain type will throw the error: `ResolutionError.methodNotSupported`.

Expand Down Expand Up @@ -118,24 +150,41 @@ resolution.locations(domains: ["brad.crypto", "homecakes.crypto"]) { result in
}
```

# Networking
## Networking

> Make sure your app has AppTransportSecurity settings to allow HTTP access to the `https://main-rpc.linkpool.io` domain.

## Custom Networking Layer
### Custom Networking Layer

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 three methods to be implemented:
* `func makeHttpPostRequest(url:, httpMethod:, httpHeaderContentType:, httpBody:, completion:)`
* `func makeHttpGetRequest(url: URL, completion:)`
* `mutating func addHeader(header: String, value: String)`

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.
Using these methods will bypass the default behavior and delegate the request to your own networking code.

For example, construct the Resolution instance like so:

```swift
guard let resolution = try? Resolution(networking: MyNetworkingApi) else {
print ("Init of Resolution instance failed...")
return
}
let customNetworking = MyNetworkingApi()
let resolution = try Resolution(configs: Configurations(
uns: UnsLocations = UnsLocations(
layer1: NamingServiceConfig(
providerUrl: "https://mainnet.infura.io/v3/<infura_api_key>",
network: "mainnet",
networking: customNetworking),
layer2: NamingServiceConfig(
providerUrl: "https://polygon-mainnet.infura.io/v3/<infura_api_key>",
network: "polygon-mainnet",
networking: customNetworking),
znsLayer: NamingServiceConfig(
providerUrl: "https://api.zilliqa.com",
network: "mainnet")
)
);
```

# Possible Errors:
## Possible Errors:

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.

Expand All @@ -159,6 +208,8 @@ enum ResolutionError: Error {
case invalidDomainName
case contractNotInitialized
case reverseResolutionNotSpecified
case unauthenticatedRequest
case requestBeingRateLimited
}
```

Expand All @@ -168,6 +219,14 @@ Please see the [Resolution-Swift Error Codes](https://docs.unstoppabledomains.co

Contributions to this library are more than welcome. The easiest way to contribute is through GitHub issues and pull requests.

## Build & test

Resolution library relies on environment variables to load TestNet RPC Urls. This way, our keys don't expose directly to the code. These environment variables are:

* L1_TEST_NET_RPC_URL
* L2_TEST_NET_RPC_URL

Use `swift build` to build, and `swift test -v` to run the tests

# Free advertising for integrated apps

Expand Down
2 changes: 1 addition & 1 deletion Resolution.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -814,7 +814,7 @@
repositoryURL = "https://github.com/attaswift/BigInt.git";
requirement = {
kind = upToNextMajorVersion;
minimumVersion = 5.2.1;
minimumVersion = 6.0.0;
};
};
B6F2076B25DBD93900140CE3 /* XCRemoteSwiftPackageReference "CryptoSwift" */ = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
"state": {
"branch": null,
"revision": "889a1ecacd73ccc189c5cb29288048f186c44ed9",
"version": "5.2.1"
"version": "6.0.0"
}
},
{
Expand Down
30 changes: 29 additions & 1 deletion Sources/UnstoppableDomainsResolution/APIRequest.swift
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ public protocol NetworkingLayer {
httpBody: Data,
completion: @escaping(Result<JsonRpcResponseArray, Error>) -> Void)
func makeHttpGetRequest(url: URL, completion: @escaping TokenUriMetadataResultConsumer )

mutating func addHeader(header: String, value: String)
}

struct APIRequest {
Expand Down Expand Up @@ -56,6 +58,8 @@ struct APIRequest {
}

public struct DefaultNetworkingLayer: NetworkingLayer {
var headers: [String: String] = [String: String]()

public init() { }

public func makeHttpPostRequest(url: URL,
Expand All @@ -66,12 +70,32 @@ public struct DefaultNetworkingLayer: NetworkingLayer {
var urlRequest = URLRequest(url: url)
urlRequest.httpMethod = httpMethod
urlRequest.addValue(httpHeaderContentType, forHTTPHeaderField: "Content-Type")

if (!self.headers.isEmpty) {
for (_, keyValue) in self.headers.enumerated() {
urlRequest.addValue(keyValue.value, forHTTPHeaderField: keyValue.key)
}
}

urlRequest.httpBody = httpBody

let dataTask = URLSession.shared.dataTask(with: urlRequest) { data, response, _ in
guard let httpResponse = response as? HTTPURLResponse,
httpResponse.statusCode == 200,
let jsonData = data else {
let jsonData = data
else {
let statusCode = (response as? HTTPURLResponse)?.statusCode

if (statusCode == 401 || statusCode == 403) {
completion(.failure(ResolutionError.unauthenticatedRequest))
return
}

if (statusCode == 429) {
completion(.failure(ResolutionError.requestBeingRateLimited))
return
}

completion(.failure(APIError.responseError))
return
}
Expand Down Expand Up @@ -118,4 +142,8 @@ public struct DefaultNetworkingLayer: NetworkingLayer {
}
dataTask.resume()
}

public mutating func addHeader(header: String, value: String) {
self.headers[header] = value
}
}
54 changes: 39 additions & 15 deletions Sources/UnstoppableDomainsResolution/Configurations.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import Foundation
public struct NamingServiceConfig {
let network: String
let providerUrl: String
let networking: NetworkingLayer
var networking: NetworkingLayer
let proxyReader: String?
let registryAddresses: [String]?

Expand All @@ -33,35 +33,59 @@ public struct NamingServiceConfig {
public struct UnsLocations {
let layer1: NamingServiceConfig
let layer2: NamingServiceConfig
let zlayer: NamingServiceConfig
let znsLayer: NamingServiceConfig

public init(
layer1: NamingServiceConfig,
layer2: NamingServiceConfig,
zlayer: NamingServiceConfig
znsLayer: NamingServiceConfig
) {
self.layer1 = layer1
self.layer2 = layer2
self.zlayer = zlayer
self.znsLayer = znsLayer
}
}

let UD_RPC_PROXY_BASE_URL = "https://api.unstoppabledomains.com/resolve"

public struct Configurations {
let uns: UnsLocations
let apiKey: String? = nil

public init(
uns: UnsLocations = UnsLocations(
layer1: NamingServiceConfig(
providerUrl: "https://mainnet.infura.io/v3/3c25f57353234b1b853e9861050f4817",
network: "mainnet"),
layer2: NamingServiceConfig(
providerUrl: "https://polygon-mainnet.infura.io/v3/3c25f57353234b1b853e9861050f4817",
network: "polygon-mainnet"),
zlayer: NamingServiceConfig(
providerUrl: "https://api.zilliqa.com",
network: "mainnet")
)
uns: UnsLocations
) {
self.uns = uns
}

public init(
apiKey: String,
znsLayer: NamingServiceConfig = NamingServiceConfig(
providerUrl: "https://api.zilliqa.com",
network: "mainnet")
) {
var networking = DefaultNetworkingLayer();
networking.addHeader(header: "Authorization", value: "Bearer \(apiKey)")
networking.addHeader(header: "X-Lib-Agent", value: Configurations.getLibVersion())

let layer1NamingService = NamingServiceConfig(
providerUrl: "\(UD_RPC_PROXY_BASE_URL)/chains/eth/rpc",
network: "mainnet",
networking: networking)

let layer2NamingService = NamingServiceConfig(
providerUrl: "\(UD_RPC_PROXY_BASE_URL)/chains/matic/rpc",
network: "polygon-mainnet",
networking: networking)

self.uns = UnsLocations(
layer1: layer1NamingService,
layer2: layer2NamingService,
znsLayer: znsLayer
)
}

static public func getLibVersion() -> String {
return "UnstoppableDomains/resolution-swift/6.0.0"
}
}
3 changes: 3 additions & 0 deletions Sources/UnstoppableDomainsResolution/Errors.swift
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@ public enum ResolutionError: Error {
case invalidDomainName
case contractNotInitialized(String)
case reverseResolutionNotSpecified
case unauthenticatedRequest
case requestBeingRateLimited


static let tooManyResponsesCode = -32005
static let badRequestOrResponseCode = -32042
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ internal class AsyncResolver {
]

if (listOfFunc.count > 2) {
functions[.zlayer] = listOfFunc[2]
functions[.znsLayer] = listOfFunc[2]
}

let queue = DispatchQueue(label: "LayerQueque")
Expand Down Expand Up @@ -62,7 +62,7 @@ internal class AsyncResolver {

private func parseResult<T>(_ results: [UNSLocation: AsyncConsumer<T>] ) throws -> T {
// filter out results that were not provided (in case some methods are not supported by some providers)
let resultsOrder = [UNSLocation.layer2, UNSLocation.layer1, UNSLocation.zlayer].filter { v in results.keys.contains(v) }
let resultsOrder = [UNSLocation.layer2, UNSLocation.layer1, UNSLocation.znsLayer].filter { v in results.keys.contains(v) }

// Omit the last result since we would have to return it regardless
for resultKey in resultsOrder.dropLast() {
Expand Down
2 changes: 1 addition & 1 deletion Sources/UnstoppableDomainsResolution/Helpers/Types.swift
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ public enum NamingServiceName: String {
public enum UNSLocation: String {
case layer1
case layer2
case zlayer
case znsLayer
}

public struct UNSContract {
Expand Down
Loading

0 comments on commit 8ca3d61

Please sign in to comment.