Skip to content

Commit

Permalink
[DT 1354] Add new addr override api (#86)
Browse files Browse the repository at this point in the history
* Add new addr api

* Fix getAddress

* Update readme

* Update version

* Update uns-config
  • Loading branch information
tunguyen authored Jul 10, 2023
1 parent 4ebd726 commit 0a52621
Show file tree
Hide file tree
Showing 13 changed files with 1,327 additions and 750 deletions.
193 changes: 181 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,14 @@ Resolution is primarily built and maintained by [Unstoppable Domains](https://un
## Cocoa Pods

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

## Swift Package Manager

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

Expand All @@ -46,8 +46,8 @@ package.dependencies.append(

# Using Resolution

- Create an instance of the Resolution class
- Call any method of the Resolution class asyncronously
- 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.
Expand All @@ -56,7 +56,7 @@ package.dependencies.append(
```swift
import UnstoppableDomainsResolution

// obtain a key from https://unstoppabledomains.com/partner-api-dashboard if you are a partner
// obtain a key by following this document https://docs.unstoppabledomains.com/domain-distribution-and-management/quickstart/retrieve-an-api-key/#api-key
guard let resolution = try? Resolution(apiKey: "<api_key>") else {
print ("Init of Resolution instance failed...")
return
Expand All @@ -68,7 +68,7 @@ guard let resolution = try? Resolution(apiKey: "<api_key>") else {
```swift
import UnstoppableDomainsResolution

This comment has been minimized.

Copy link
@JADEP246

JADEP246 Jan 21, 2024

Hello


// obtain a key from https://unstoppabledomains.com/partner-api-dashboard if you are a partner
// obtain a key by following this document https://docs.unstoppabledomains.com/domain-distribution-and-management/quickstart/retrieve-an-api-key/#api-key
guard let resolution = try? Resolution(
apiKey: "<api_key>",
znsLayer: NamingServiceConfig(
Expand Down Expand Up @@ -108,6 +108,15 @@ let resolution = try Resolution(configs: Configurations(
## Examples

### Getting a domain's crypto address

**`addr(domain: String, ticker: String)`**

This API is used to retrieve wallet address for single address record. (See
[Cryptocurrency payment](https://docs.unstoppabledomains.com/resolution/guides/records-reference/#cryptocurrency-payments)
section for the record format)

With `brad.crypto` has `crypto.ETH.address` on-chain:

```swift
resolution.addr(domain: "brad.crypto", ticker: "eth") { (result) in
switch result {
Expand All @@ -120,6 +129,164 @@ resolution.addr(domain: "brad.crypto", ticker: "eth") { (result) in
}
```

**`multiChainAddress(domain: String, ticker: String, chain: String)`**

This API is used to retrieve wallet address for multi-chain address records.
(See
[multi-chain currency](https://docs.unstoppabledomains.com/resolution/guides/records-reference/#multi-chain-currencies))

With `brad.crypto` has `crypto.USDT.version.ERC20.address` and `crypto.USDT.version.OMNI.address` on-chain:

```swift
resolution.multiChainAddress(domain: "brad.crypto", ticker: "USDT", chain: "ERC20") { (result) in
switch result {
case .success(let returnValue):
ethAddress = returnValue
domainReceived.fulfill()
case .failure(let error):
XCTFail("Expected Eth Address, but got \(error)")
}
}
```

**`addr(domain: String, network: String, token: String)`**

This comment has been minimized.

Copy link
@JADEP246

JADEP246 Jan 21, 2024

jadenb3003.nft


This (Beta) API can be used to retrieve wallet address for single chain and multi-chain address records.

With `brad.crypto` has `crypto.ETH.address` and `crypto.USDT.version.ERC20.address` on-chain:

```swift
// single chain address
resolution.addr(domain: "brad.crypto", network: "eth", token: "eth") { (result) in
switch result {
case .success(let returnValue):
ethAddress = returnValue
domainReceived.fulfill()
case .failure(let error):
XCTFail("Expected Eth Address, but got \(error)")
}
}

// multi-chain address
resolution.multiChainAddress(domain: "brad.crypto", ticker: "ETH", chain: "USDT") { (result) in
switch result {
case .success(let returnValue):
usdtAddress = returnValue
domainReceived.fulfill()
case .failure(let error):
XCTFail("Expected USDT Address, but got \(error)")
}
}
```

> **Note** that the API will infer `ERC20` standard as `ETH` network.

The API can also be used by crypto exchanges to infer wallet addresses. In
centralized exchanges, users have same wallet addresses on different networks
with same wallet family.

With `brad.crypto` only has `token.EVM.address` record on-chain.
The API resolves to same wallet address for tokens live on EVM compatible networks.

```swift
// infer USDT on ETH network
resolution.multiChainAddress(domain: "brad.crypto", network: "ETH", token: "USDT") { (result) in
switch result {
case .success(let returnValue):
usdtOnEthAddress = returnValue
domainReceived.fulfill()
case .failure(let error):
XCTFail("Expected USDT Address, but got \(error)")
}
}

// infer ETH token on ETH network
resolution.multiChainAddress(domain: "brad.crypto", network: "ETH", token: "ETH") { (result) in
switch result {
case .success(let returnValue):
ethTokenOnEthAddress = returnValue
domainReceived.fulfill()
case .failure(let error):
XCTFail("Expected Eth Address, but got \(error)")
}
}

// infer USDT token on ETH network
resolution.multiChainAddress(domain: "brad.crypto", network: "AVAX", token: "USDT") { (result) in
switch result {
case .success(let returnValue):
usdtOnAvaxAddress = returnValue
domainReceived.fulfill()
case .failure(let error):
XCTFail("Expected USDT Address, but got \(error)")
}
}

```

With `brad.crypto` only has `token.EVM.ETH.address`
record on chain. The API resolves to the same wallet address for tokens
specifically on Ethereum network.

```swift
// infer USDT on ETH network
resolution.multiChainAddress(domain: "brad.crypto", network: "ETH", token: "USDT") { (result) in
switch result {
case .success(let returnValue):
usdtOnEthAddress = returnValue
domainReceived.fulfill()
case .failure(let error):
XCTFail("Expected USDT Address, but got \(error)")
}
}

// infer ETH token on ETH network
resolution.multiChainAddress(domain: "brad.crypto", network: "ETH", token: "ETH") { (result) in
switch result {
case .success(let returnValue):
ethTokenOnEthAddress = returnValue
domainReceived.fulfill()
case .failure(let error):
XCTFail("Expected Eth Address, but got \(error)")
}
}

// the API try to derive address for Avalanche network
resolution.multiChainAddress(domain: "brad.crypto", network: "AVAX", token: "USDT") { (result) in
switch result {
case .success(let returnValue):
usdtOnAvaxAddress = returnValue
domainReceived.fulfill()
case .failure(let error):
XCTFail("Expected USDT Address, but got \(error)")
}
}

```

The API is compatible with other address formats. If a domain has multiple
address formats set, it will follow the algorithm described as follow:

if a domain has following records set:

```
token.EVM.address
crypto.USDC.version.ERC20.address
token.EVM.ETH.USDC.address
crypto.USDC.address
token.EVM.ETH.address
```

`getAddress(domain, 'ETH', 'USDC')` will lookup records in the following order:

```
1. token.EVM.ETH.USDC.address
2. crypto.USDC.address
3. crypto.USDC.version.ERC20.address
4. token.EVM.ETH.address
5. token.EVM.address
```

### Batch requesting of owners

the `batchOwners(domains: _, completion: _ )` method adds additional convenience when making multiple domain owner queries.
Expand Down Expand Up @@ -156,10 +323,11 @@ resolution.locations(domains: ["brad.crypto", "homecakes.crypto"]) { result in

### 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 three methods to be implemented:

- `func makeHttpPostRequest(url:, httpMethod:, httpHeaderContentType:, httpBody:, completion:)`
- `func makeHttpGetRequest(url: URL, completion:)`
- `mutating func addHeader(header: String, value: String)`

Using these methods will bypass the default behavior and delegate the request to your own networking code.

Expand Down Expand Up @@ -223,8 +391,8 @@ Contributions to this library are more than welcome. The easiest way to contribu

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
- L1_TEST_NET_RPC_URL
- L2_TEST_NET_RPC_URL

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

Expand All @@ -235,6 +403,7 @@ Once your app has a working Unstoppable Domains integration, [register it here](
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.

# Get help

[Join our discord community](https://discord.gg/unstoppabledomains) and ask questions.

# Help us improve
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
"state": {
"branch": null,
"revision": "889a1ecacd73ccc189c5cb29288048f186c44ed9",
"version": "6.0.0"
"version": "6.1.0"
}
},
{
Expand Down
2 changes: 1 addition & 1 deletion Sources/UnstoppableDomainsResolution/Configurations.swift
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,6 @@ public struct Configurations {
}

static public func getLibVersion() -> String {
return "UnstoppableDomains/resolution-swift/6.0.0"
return "UnstoppableDomains/resolution-swift/6.1.0"
}
}
13 changes: 13 additions & 0 deletions Sources/UnstoppableDomainsResolution/NamingServices/UNS.swift
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,19 @@ internal class UNS: CommonNamingService, NamingService {
)
}

func addr(domain: String, network: String, token: String) throws -> String {
return try asyncResolver.safeResolve(
listOfFunc: [{try self.layer1.addr(domain: domain, network: network, token: token)},
{try self.layer2.addr(domain: domain, network: network, token: token)},
{
if self.znsLayer.isSupported(domain: domain) {
return try self.znsLayer.addr(domain: domain, network: network, token: token)
}
throw ResolutionError.unregisteredDomain
}]
)
}

func resolver(domain: String) throws -> String {
return try asyncResolver.safeResolve(
listOfFunc: [{try self.layer1.resolver(domain: domain)},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ internal class UNSLayer: CommonNamingService {
static let NewURIEventSignature = "0xc5beef08f693b11c316c0c8394a377a0033c9cf701b8cd8afd79cecef60c3952"
static let getDataForManyMethodName = "getDataForMany"
static let reverseOfMethodName = "reverseOf"
static let getAddressMethodName: String = "getAddress"
static let tokenURIMethodName = "tokenURI"
static let registryOfMethodName = "registryOf"
static let existName = "exists"
Expand Down Expand Up @@ -143,6 +144,41 @@ internal class UNSLayer: CommonNamingService {
return result
}

func addr(domain: String, network: String, token: String) throws -> String {
let tokenId = super.namehash(domain: domain)
let ownerRes: Any
do {
ownerRes = try self.getDataForMany(keys: [Contract.resolversKey, Contract.ownersKey], for: [tokenId])
guard let owners = self.unfoldForMany(contractResult: ownerRes, key: Contract.ownersKey),
Utillities.isNotEmpty(owners[0]) else {
throw ResolutionError.unregisteredDomain
}
} catch {
if error is ABICoderError {
throw ResolutionError.unspecifiedResolver(self.layer.rawValue)
}
throw error
}
let res = try getAddress(domain: domain, network: network, token: token) as? [String: Any]

if let val = res?["0"] as? String {
if (!val.isEmpty) {
return val
}
}
return ""
}

func getAddress(domain: String, network: String, token: String) throws -> Any {
let tokenId = super.namehash(domain: domain)
if let result = try proxyReaderContract?
.callMethod(methodName: Self.getAddressMethodName,
args: [network, token, tokenId]) {
return result
}
throw ResolutionError.proxyReaderNonInitialized
}

// MARK: - Get Record
func record(domain: String, key: String) throws -> String {
let tokenId = super.namehash(domain: domain)
Expand All @@ -154,7 +190,6 @@ internal class UNSLayer: CommonNamingService {
}

func allRecords(domain: String) throws -> [String: String] {
let tokenId = super.namehash(domain: domain)
let commonRecordsKeys = try Self.parseRecordKeys()
let mergedRecords = Array(Set(commonRecordsKeys!))
return try self.records(keys: mergedRecords, for: domain).filter { !$0.value.isEmpty }
Expand Down
4 changes: 4 additions & 0 deletions Sources/UnstoppableDomainsResolution/NamingServices/ZNS.swift
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,10 @@ internal class ZNS: CommonNamingService, NamingService {
return result
}

func addr(domain: String, network: String, token: String) throws -> String {
throw ResolutionError.methodNotSupported
}

func record(domain: String, key: String) throws -> String {
let records = try self.records(keys: [key], for: domain)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ protocol NamingService {

func owner(domain: String) throws -> String
func addr(domain: String, ticker: String) throws -> String
func addr(domain: String, network: String, token: String) throws -> String
func resolver(domain: String) throws -> String

func batchOwners(domains: [String]) throws -> [String: String?]
Expand Down
15 changes: 15 additions & 0 deletions Sources/UnstoppableDomainsResolution/Resolution.swift
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,21 @@ public class Resolution {
}
}

/// Resolves give `domain` name to a specific `currency address` if exists
/// - Parameter domain: - domain name to be resolved
/// - Parameter network: - blockchain network the token is created on
/// - Parameter ticker: - currency ticker USDT, MATIC
/// - Parameter completion: A callback that resolves `Result` with an `address` or `Error`
public func addr(domain: String, network: String, token: String, completion: @escaping StringResultConsumer ) {
do {
let preparedDomain = try self.prepare(domain: domain)
let result = try self.getServiceOf(domain: domain).addr(domain: preparedDomain, network: network, token: token)
completion(.success(result))
} catch {
self.catchError(error, completion: completion)
}
}

/// Resolves a resolver address of a `domain`
/// - Parameter domain: - domain name to be resolved
/// - Parameter completion: A callback that resolves `Result` with a `resolver address` for a specific domain or `Error`
Expand Down
Loading

0 comments on commit 0a52621

Please sign in to comment.