Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[WIP] ENS resolution #87

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -89,4 +89,6 @@ fastlane/test_output

iOSInjectionProject/

.idea/
.idea/
.DS_Store
tmp
1 change: 0 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
# UnstoppableDomainsResolution

[![Get help on Discord](https://img.shields.io/badge/Get%20help%20on-Discord-blueviolet)](https://discord.gg/b6ZVxSZ9Hn)
[![Unstoppable Domains Documentation](https://img.shields.io/badge/Documentation-unstoppabledomains.com-blue)](https://docs.unstoppabledomains.com/)

Resolution is a library for interacting with blockchain domain names. It can be used to retrieve payment addresses and IPFS hashes for decentralized websites.
Expand Down
26 changes: 17 additions & 9 deletions Sources/UnstoppableDomainsResolution/Configurations.swift
Original file line number Diff line number Diff line change
Expand Up @@ -50,41 +50,49 @@ let UD_RPC_PROXY_BASE_URL = "https://api.unstoppabledomains.com/resolve"

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

public init(
uns: UnsLocations
uns: UnsLocations,
ens: NamingServiceConfig
) {
self.uns = uns
self.ens = ens
}

public init(
apiKey: String,
znsLayer: NamingServiceConfig = NamingServiceConfig(
providerUrl: "https://api.zilliqa.com",
network: "mainnet"),
ens: NamingServiceConfig = NamingServiceConfig(
providerUrl: "https://mainnet.infura.io/v3/<api_key>",
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)

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
)

self.ens = ens
}

static public func getLibVersion() -> String {
return "UnstoppableDomains/resolution-swift/6.1.0"
}
Expand Down
1 change: 1 addition & 0 deletions Sources/UnstoppableDomainsResolution/Helpers/Types.swift
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ internal typealias AsyncConsumer<T> = (T?, Error?)

public enum NamingServiceName: String {
case uns
case ens
case zns
}

Expand Down
177 changes: 177 additions & 0 deletions Sources/UnstoppableDomainsResolution/NamingServices/ENS.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
import Foundation

internal class ENS: CommonNamingService, NamingService {
let network: String
let registryAddress: String
let registryMap: [String: String] = [
"mainnet": "0x00000000000C2E074eC69A0dFb2997BA6C7d2e1e",
"ropsten": "0x00000000000C2E074eC69A0dFb2997BA6C7d2e1e",
"rinkeby": "0x00000000000C2E074eC69A0dFb2997BA6C7d2e1e",
"goerli": "0x00000000000C2E074eC69A0dFb2997BA6C7d2e1e"
]

init(_ config: NamingServiceConfig) throws {
self.network = config.network.isEmpty
? try Self.getNetworkName(providerUrl: config.providerUrl, networking: config.networking)
: config.network

var registryAddress: String? = registryMap[self.network]
if config.registryAddresses != nil && !config.registryAddresses!.isEmpty {
registryAddress = config.registryAddresses![0]
}

guard registryAddress != nil else {
throw ResolutionError.registryAddressIsNotProvided
}
self.registryAddress = registryAddress!
super.init(name: .ens, providerUrl: config.providerUrl, networking: config.networking)
}

func isSupported(domain: String) -> Bool {
return domain ~= "^[^-]*[^-]*\\.(eth|luxe|xyz|kred|addr\\.reverse)$"
}

func owner(domain: String) throws -> String {
let tokenId = super.namehash(domain: domain)
guard let ownerAddress = try askRegistryContract(for: "owner", with: [tokenId]),
Utillities.isNotEmpty(ownerAddress) else {
throw ResolutionError.unregisteredDomain
}
return ownerAddress
}

func batchOwners(domains: [String]) throws -> [String: String?] {
throw ResolutionError.methodNotSupported
}

func addr(domain: String, ticker: String) throws -> String {
guard ticker.uppercased() == "ETH" else {
throw ResolutionError.recordNotSupported
}
let tokenId = super.namehash(domain: domain)
let resolverAddress = try resolver(tokenId: tokenId)
let resolverContract = try super.buildContract(address: resolverAddress, type: .resolver)

guard let dict = try resolverContract.callMethod(methodName: "addr", args: [tokenId, ethCoinIndex]) as? [String: Data],
let dataAddress = dict["0"],
let address = EthereumAddress(dataAddress),
Utillities.isNotEmpty(address.address) else {
throw ResolutionError.recordNotFound(self.name.rawValue)
}
return address.address
}

func addr(domain: String, network: String, token: String) throws -> String {
return "NA"
}

// MARK: - Get Record
func record(domain: String, key: String) throws -> String {
let tokenId = super.namehash(domain: domain)
return try self.record(tokenId: tokenId, key: key)
}

func record(tokenId: String, key: String) throws -> String {
if key == "ipfs.html.value" {
let hash = try self.getContentHash(tokenId: tokenId)
return hash
}

let resolverAddress = try resolver(tokenId: tokenId)
let resolverContract = try super.buildContract(address: resolverAddress, type: .resolver)

let ensKeyName = self.fromUDNameToEns(record: key)

guard let dict = try resolverContract.callMethod(methodName: "text", args: [tokenId, ensKeyName]) as? [String: String],
let result = dict["0"],
Utillities.isNotEmpty(result) else {
throw ResolutionError.recordNotFound(self.name.rawValue)
}
return result
}

func records(keys: [String], for domain: String) throws -> [String: String] {
throw ResolutionError.methodNotSupported
}

func allRecords(domain: String) throws -> [String: String] {
throw ResolutionError.methodNotSupported
}

func getTokenUri(tokenId: String) throws -> String {
throw ResolutionError.methodNotSupported
}

func getDomainName(tokenId: String) throws -> String {
throw ResolutionError.methodNotSupported
}

func locations(domains: [String]) throws -> [String: Location] {
throw ResolutionError.methodNotSupported
}

// MARK: - get Resolver
func resolver(domain: String) throws -> String {
let tokenId = super.namehash(domain: domain)
return try self.resolver(tokenId: tokenId)
}

func resolver(tokenId: String) throws -> String {
guard let resolverAddress = try askRegistryContract(for: "resolver", with: [tokenId]),
Utillities.isNotEmpty(resolverAddress) else {
throw ResolutionError.unspecifiedResolver(self.name.rawValue)
}
return resolverAddress
}

// MARK: - Helper functions
private func askRegistryContract(for methodName: String, with args: [String]) throws -> String? {
let registryContract: Contract = try super.buildContract(address: self.registryAddress, type: .ensRegistry)
guard let ethereumAddress = try registryContract.callMethod(methodName: methodName, args: args) as? [String: EthereumAddress],
let address = ethereumAddress["0"] else {
return nil
}
return address.address
}

private func fromUDNameToEns(record: String) -> String {
let mapper: [String: String] = [
"ipfs.redirect_domain.value": "url",
"whois.email.value": "email",
"gundb.username.value": "gundb_username",
"gundb.public_key.value": "gundb_public_key"
]
return mapper[record] ?? record
}

/*
//https://ethereum.stackexchange.com/questions/17094/how-to-store-ipfs-hash-using-bytes32
getIpfsHashFromBytes32(bytes32Hex) {
// Add our default ipfs values for first 2 bytes:
// function:0x12=sha2, size:0x20=256 bits
// and cut off leading "0x"
const hashHex = "1220" + bytes32Hex.slice(2)
const hashBytes = Buffer.from(hashHex, 'hex');
const hashStr = bs58.encode(hashBytes)
return hashStr
}
*/
private func getContentHash(tokenId: String) throws -> String {
let resolverAddress = try resolver(tokenId: tokenId)
let resolverContract = try super.buildContract(address: resolverAddress, type: .resolver)

let hash = try resolverContract.callMethod(methodName: "contenthash", args: [tokenId]) as? [String: Any]
guard let data = hash?["0"] as? Data else {
throw ResolutionError.recordNotFound(self.name.rawValue)
}

let contentHash = [UInt8](data)
guard let codec = Array(contentHash[0..<1]).last,
codec == 0xE3 // 'ipfs-ns'
else {
throw ResolutionError.recordNotFound(self.name.rawValue)
}

return Base58.base58Encode(Array(contentHash[4..<contentHash.count]))
}
}
17 changes: 15 additions & 2 deletions Sources/UnstoppableDomainsResolution/Resolution.swift
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,8 @@ public class Resolution {
public func addr(domain: String, ticker: String, completion: @escaping StringResultConsumer ) {
do {
let preparedDomain = try self.prepare(domain: domain)
let result = try self.getServiceOf(domain: domain).addr(domain: preparedDomain, ticker: ticker)
let service = try self.getServiceOf(domain: domain)
let result = try service.addr(domain: preparedDomain, ticker: ticker)
completion(.success(result))
} catch {
self.catchError(error, completion: completion)
Expand Down Expand Up @@ -454,20 +455,32 @@ public class Resolution {
private func constructNetworkServices(_ configs: Configurations) throws -> [NamingService] {
var networkServices: [NamingService] = []
var errorService: Error?

do {
networkServices.append(try UNS(configs))
} catch {
errorService = error
}


do {
networkServices.append(try ENS(configs.ens))
} catch {
errorService = error
}

if let error = errorService {
throw error
}

return networkServices
}

/// This returns the naming service
private func getServiceOf(domain: String) throws -> NamingService {
if domain ~= "^[^-]*[^-]*\\.(eth|luxe|xyz|kred|addr\\.reverse)$" {
return try self.findService(name: .ens)
}

return try self.findService(name: .uns)
}

Expand Down
Loading
Loading