Skip to content

Instantly share code, notes, and snippets.

@kohlivarun5
Last active July 19, 2022 02:28
Show Gist options
  • Save kohlivarun5/a767680f91394eeec34588e96bed812c to your computer and use it in GitHub Desktop.
Save kohlivarun5/a767680f91394eeec34588e96bed812c to your computer and use it in GitHub Desktop.
ENS Domains
//
// ENSContract.swift
// NFTY
//
// Created by Varun Kohli on 4/19/22.
//
// https://gist.github.com/hewigovens/9d078eb6b4e028ec78bce6abab71d980
import Foundation
// https://gist.github.com/kohlivarun5/a767680f91394eeec34588e96bed812c
import CryptoSwift
import Web3
import Web3ContractABI
import PromiseKit
// https://github.com/ethereum/EIPs/blob/master/EIPS/eip-137.md
// https://docs.ens.domains/contract-api-reference/reverseregistrar
// print(ENSContract.namehash("chilluminati.eth"))
// https://etherscan.io/address/0x00000000000c2e074ec69a0dfb2997ba6c7d2e1e#readContract
// -> resolver for namehash
// resolver : https://etherscan.io/address/0x4976fb03C32e5B8cfe2b6cCB31c09Ba78EBaBa41#readProxyContract
// -> add for namehash
// print(ENSContract.namehash("Ae71923d145ec0eAEDb2CF8197A08f12525Bddf4.addr.reverse".lowercased()))
// https://etherscan.io/address/0x00000000000c2e074ec69a0dfb2997ba6c7d2e1e#readContract
// -> resolver for namehash (lowercased address +++)
// resolver : https://etherscan.io/address/0x4976fb03C32e5B8cfe2b6cCB31c09Ba78EBaBa41#readProxyContract
// -> add for namehash
class ENSContract : EthereumContract {
let address: EthereumAddress?
let eth: Web3.Eth
let events: [SolidityEvent] = []
init(eth:Web3.Eth) {
self.address = try? EthereumAddress(hex:"0x00000000000C2E074eC69A0dFb2997BA6C7d2e1e",eip55:true)
self.eth = eth
}
static public func namehash(_ name:String) -> SolidityWrappedValue {
var node = Array<UInt8>.init(repeating: 0x0, count: 32)
if name.count > 0 {
node = name.split(separator: ".")
.map { Array($0.utf8).sha3(.keccak256) }
.reversed()
.reduce(node) { return ($0 + $1).sha3(.keccak256) }
}
return SolidityWrappedValue.fixedBytes(Data(node))
}
static private var resolverCache : [String:EthereumAddress?] = [:]
public func resolver(namehash:SolidityWrappedValue) -> Promise<EthereumAddress?> {
if let resolver = ENSContract.resolverCache[namehash.value.abiEncode(dynamic: false)!] { return Promise.value(resolver) }
let inputs = [SolidityFunctionParameter(name: "node", type: .bytes(length: 32))]
let outputs = [SolidityFunctionParameter(name: "address", type: .address)]
let method = SolidityConstantFunction(name: "resolver", inputs: inputs, outputs: outputs, handler: self)
print("calling ens resolver")
return method.invoke(namehash.value).call()
.map(on:DispatchQueue.global(qos:.userInteractive)) { outputs in
return outputs["address"] as? EthereumAddress
}
.map {
ENSContract.resolverCache[namehash.value.abiEncode(dynamic: false)!] = $0
return $0
}
}
class AddrResolverContract : EthereumContract {
let eth: Web3.Eth
let events: [SolidityEvent] = []
let address: EthereumAddress?
init(address:EthereumAddress?,eth:Web3.Eth) {
self.address = address
self.eth = eth
}
func addr(namehash:SolidityWrappedValue) -> Promise<EthereumAddress?> {
let inputs = [SolidityFunctionParameter(name: "input", type: .bytes(length: 32))]
let outputs = [SolidityFunctionParameter(name: "addr", type: .address)]
let method = SolidityConstantFunction(name: "addr", inputs: inputs, outputs: outputs, handler: self)
print("calling ens resolver.addr")
return method.invoke(namehash.value).call()
.map(on:DispatchQueue.global(qos:.userInteractive)) { outputs in
return outputs["addr"] as? EthereumAddress
}
}
func text(namehash:SolidityWrappedValue,key:String) -> Promise<String?> {
let inputs = [
SolidityFunctionParameter(name: "node", type: .bytes(length: 32)),
SolidityFunctionParameter(name: "key", type: .string)
]
let outputs = [SolidityFunctionParameter(name: "value", type: .string)]
let method = SolidityConstantFunction(name: "text", inputs: inputs, outputs: outputs, handler: self)
print("calling ens resolver.text")
return method.invoke(namehash.value,key).call()
.map(on:DispatchQueue.global(qos:.userInteractive)) { outputs in
return outputs["value"] as? String
}
}
// function setText(bytes32 node, string calldata key, string calldata value) external authorised(node) {
func setText(from:EthereumAddress,namehash:SolidityWrappedValue,key:String,value:String) -> EthereumTransaction {
let inputs = [
SolidityFunctionParameter(name: "node", type: .bytes(length: 32)),
SolidityFunctionParameter(name: "key", type: .string),
SolidityFunctionParameter(name: "value", type: .string),
]
let method = SolidityPayableFunction(name: "setText", inputs: inputs, outputs: [], handler: self)
return method.invoke(namehash.value,key,value).createTransaction(
nonce:nil,
from: from,
value:nil,
gas: 200000,
gasPrice: nil)!
}
}
class NameResolverContract : EthereumContract {
let eth: Web3.Eth
let events: [SolidityEvent] = []
let address: EthereumAddress?
init(address:EthereumAddress?,eth:Web3.Eth) {
self.address = address
self.eth = eth
}
func name(namehash:SolidityWrappedValue) -> Promise<String?> {
let inputs = [SolidityFunctionParameter(name: "input", type: .bytes(length: 32))]
let outputs = [SolidityFunctionParameter(name: "name", type: .string)]
let method = SolidityConstantFunction(name: "name", inputs: inputs, outputs: outputs, handler: self)
print("calling ens resolver.name")
return method.invoke(namehash.value).call()
.map(on:DispatchQueue.global(qos:.userInteractive)) { outputs in
return outputs["name"] as? String
}
}
}
static private func nameToOwner(_ name:String,eth:Web3.Eth) -> Promise<EthereumAddress?> {
let contract = ENSContract(eth: eth)
let namehash = ENSContract.namehash(name)
return contract.resolver(namehash:namehash)
.then { (address:EthereumAddress?) -> Promise<EthereumAddress?> in
let contract = AddrResolverContract(address:address, eth: eth);
return contract.addr(namehash:namehash)
}
}
static private func nameOfOwner(_ address:EthereumAddress,eth:Web3.Eth) -> Promise<String?> {
let name = try! address.makeBytes().toHexString() + ".addr.reverse"
let namehash = ENSContract.namehash(name.lowercased())
let contract = ENSContract(eth: eth)
return contract.resolver(namehash:namehash)
.then { (address:EthereumAddress?) -> Promise<String?> in
let contract = NameResolverContract(address: address, eth: eth)
return contract.name(namehash:namehash)
}
}
static private func avatarOwnerOfNamehash(_ nameHash:SolidityWrappedValue,eth:Web3.Eth) -> Promise<(EthereumAddress?,String?)> {
let contract = ENSContract(eth: eth)
return contract.resolver(namehash:nameHash)
.then { (address:EthereumAddress?) -> Promise<(EthereumAddress?,String?)> in
let contract = AddrResolverContract(address:address, eth: eth);
return contract.text(namehash:nameHash,key:"avatar").then { avatar in
return contract.addr(namehash:nameHash)
.map { ($0,avatar) }
}
}
}
static private func avatarOfOwner(_ name:String,eth:Web3.Eth) -> Promise<String?> {
let contract = ENSContract(eth: eth)
let namehash = ENSContract.namehash(name)
return contract.resolver(namehash:namehash)
.then { (address:EthereumAddress?) -> Promise<String?> in
let contract = AddrResolverContract(address:address, eth: eth);
return contract.text(namehash:namehash,key:"avatar")
}
}
static func setAvatar(_ name:String,from:EthereumAddress,avatar:NFT,eth:Web3.Eth) -> Promise<EthereumTransaction> {
let contract = ENSContract(eth: eth)
let namehash = ENSContract.namehash(name)
return contract.resolver(namehash:namehash)
.map { (address:EthereumAddress?) -> EthereumTransaction in
let contract = AddrResolverContract(address:address, eth: eth);
// eip155:1/[NFT standard]:[contract address for NFT collection]/[token ID or the number it is in the collection]
let avatarValue = "eip155:1/erc721:\(avatar.address)/\(avatar.tokenId)"
print("Setting avatar = \(avatarValue)")
return contract.setText(from:from,namehash:namehash,key:"avatar",value:avatarValue)
}
}
}
// 0x74D0A6358d96c3c24Ea0116D9B33Dfdd9912aEd8
class ENSWrapper : EthereumContract {
static let shared = ENSWrapper(eth: web3.eth)
let ensAddress : EthereumAddress
let address: EthereumAddress?
let eth: Web3.Eth
let events: [SolidityEvent] = []
init(eth:Web3.Eth) {
self.ensAddress = try! EthereumAddress(hex:"0x00000000000C2E074eC69A0dFb2997BA6C7d2e1e",eip55:true)
self.address = try? EthereumAddress(hex:"0x74D0A6358d96c3c24Ea0116D9B33Dfdd9912aEd8",eip55:true)
self.eth = eth
}
// function textAddrOfName(address ensAddress,bytes32 node,string calldata key) public view returns (string memory,address) {
public func textAddrOfName(namehash:SolidityWrappedValue,key:String,block:BigUInt?) -> Promise<(String,EthereumAddress?)> {
let inputs = [
SolidityFunctionParameter(name: "ensAddress", type: .address),
SolidityFunctionParameter(name: "node", type: .bytes(length: 32)),
SolidityFunctionParameter(name: "key", type: .string)
]
let outputs = [
SolidityFunctionParameter(name: "text", type: .string),
SolidityFunctionParameter(name: "owner", type: .address),
]
let method = SolidityConstantFunction(name: "textAddrOfName", inputs: inputs, outputs: outputs, handler: self)
print("calling ENSWrapper.textAddrOfName")
return method.invoke(self.ensAddress,namehash.value,key).call(block: block.map { .block($0) } ?? .latest)
.map(on:DispatchQueue.global(qos:.userInteractive)) { outputs -> (String,EthereumAddress?) in
let (text,owner) = (outputs["text"] as! String,outputs["owner"] as! EthereumAddress)
print("textAddrOfName returned with \(key)=\(text),owner=\(owner.hex(eip55: true))")
return (text,owner.hex(eip55: true) == ETH_ADDRESS ? nil : owner)
}
}
// function textOfName(address ensAddress,bytes32 node,string calldata key) public view returns (string memory) {
private func textOfName(namehash:SolidityWrappedValue,key:String) -> Promise<String> {
let inputs = [
SolidityFunctionParameter(name: "ensAddress", type: .address),
SolidityFunctionParameter(name: "node", type: .bytes(length: 32)),
SolidityFunctionParameter(name: "key", type: .string)
]
let outputs = [
SolidityFunctionParameter(name: "text", type: .string)
]
let method = SolidityConstantFunction(name: "textOfName", inputs: inputs, outputs: outputs, handler: self)
print("calling ENSWrapper.textAddrOfName")
return method.invoke(self.ensAddress,namehash.value,key).call()
.map(on:DispatchQueue.global(qos:.userInteractive)) { outputs in
let text = outputs["text"] as! String
print("textOfName returned with \(key)=\(text)")
return text
}
}
// function nameOfAdr(address ensAddress,bytes32 node) public view returns (string memory) {
private func nameOfAdr(namehash:SolidityWrappedValue) -> Promise<(String)> {
let inputs = [
SolidityFunctionParameter(name: "ensAddress", type: .address),
SolidityFunctionParameter(name: "node", type: .bytes(length: 32)),
]
let outputs = [
SolidityFunctionParameter(name: "name", type: .string)
]
let method = SolidityConstantFunction(name: "nameOfAdr", inputs: inputs, outputs: outputs, handler: self)
print("calling ENSWrapper.nameOfAdr")
return method.invoke(self.ensAddress,namehash.value).call()
.map(on:DispatchQueue.global(qos:.userInteractive)) { outputs -> String in
let name = outputs["name"] as! String
print("nameOfAdr returned with name=\(name)")
return name
}
}
// function addrOfName(address ensAddress,bytes32 node) public view returns (address) {
private func addrOfName(namehash:SolidityWrappedValue) -> Promise<(EthereumAddress?)> {
let inputs = [
SolidityFunctionParameter(name: "ensAddress", type: .address),
SolidityFunctionParameter(name: "node", type: .bytes(length: 32)),
]
let outputs = [
SolidityFunctionParameter(name: "address", type: .address)
]
let method = SolidityConstantFunction(name: "addrOfName", inputs: inputs, outputs: outputs, handler: self)
print("calling ENSWrapper.addrOfName")
return method.invoke(self.ensAddress,namehash.value).call()
.map(on:DispatchQueue.global(qos:.userInteractive)) { outputs -> EthereumAddress? in
let address = outputs["address"] as! EthereumAddress
print("addrOfName returned with address=\(address)")
return address.hex(eip55: true) == ETH_ADDRESS ? nil : address
}
}
public func nameOfOwner(_ address:EthereumAddress,eth:Web3.Eth) -> Promise<String?> {
let name = try! address.makeBytes().toHexString() + ".addr.reverse"
let namehash = ENSContract.namehash(name.lowercased())
return self.nameOfAdr(namehash: namehash).map { $0.isEmpty ? nil : $0 }
}
public func avatarOfOwner(_ name:String,eth:Web3.Eth) -> Promise<String?> {
let namehash = ENSContract.namehash(name)
return self.textOfName(namehash: namehash,key: "avatar").map { $0.isEmpty ? nil : $0 }
}
public func nameToOwner(_ name:String,eth:Web3.Eth) -> Promise<EthereumAddress?> {
let namehash = ENSContract.namehash(name)
return self.addrOfName(namehash: namehash)
}
}
//
// main.swift
// DownloadCollection
//
// Created by Varun Kohli on 7/21/21.
//
import Foundation
import PromiseKit
import BigInt
import Web3
import Web3ContractABI
// var web3 = Web3(rpcURL: "https://mainnet.infura.io/v3/c2b9ecfefe934b1ba89dc49532f44bf5")
struct Downloader {
let collection : IpfsDownloader
let firstIndex : Int
let lastIndex : Int
}
let downloaders = [
/*
Downloader(
collection:IpfsDownloader(
name:"0N1 Force",baseUri:"ipfs://QmXgSuLPGuxxRuAana7JdoWmaS25oAcXv3x2pYMN9kVfg3"),
firstIndex:1,
lastIndex:7777
),
Downloader(
collection:IpfsDownloader(
name:"DJENERATES",baseUri:"https://ipfs.io/ipfs/QmRPGJWkqdF9hhqrNjwGW7tuFHduSrtoeDA2PtnU65HYjX"),
firstIndex:1,
lastIndex:7699
),
Downloader(
collection:IpfsDownloader(
name:"Craniums",baseUri:"https://raw.githubusercontent.com/recklesslabs/wickedcraniums/main"),
firstIndex:0,
lastIndex:10761
),
Downloader(
collection:IpfsDownloader(
name:"DJENERATES",baseUri:"https://ipfs.io/ipfs/QmRPGJWkqdF9hhqrNjwGW7tuFHduSrtoeDA2PtnU65HYjX"),
firstIndex:1,
lastIndex:10000
),
Downloader(
collection:IpfsDownloader(
name:"MutantApes",baseUri:"https://boredapeyachtclub.com/api/mutants"),
firstIndex:0,
lastIndex:10000
)
Downloader(
collection:IpfsDownloader(
name:"MutantApes",baseUri:"https://boredapeyachtclub.com/api/mutants"),
firstIndex:0,
lastIndex:10000
),
*/
Downloader(
collection:IpfsDownloader(
name:"0xApes",baseUri:"ipfs://QmPyYXzqU7tsTxQ7WFN32iUKRy78T6ZiFGxYiJiv1TcoKE/"), // TODO
firstIndex:10038,
lastIndex:20145
)
]
var web3 = Web3(rpcURL: "https://mainnet.infura.io/v3/b4287cfd0a6b4849bd0ca79e144d3921")
let hash = ENSContract.namehash("chilluminati.eth")
print("chilluminati namehash=\(try! ABI.encodeParameter(hash))")
let contract = ENSContract(eth: web3.eth)
print(try? hang(
ENSContract.nameToOwner("chilluminati.eth", eth: web3.eth)
.then { (address:EthereumAddress?) -> Promise<String?> in
print("Owner=",address?.hex(eip55: true));
switch(address) {
case .some(let address):
return ENSContract.avatarOfOwner("chilluminati.eth", eth: web3.eth)
.map {
print("name=",$0);
return $0
}
case .none:
return Promise.value(nil)
}
}
)
)
// print(ENSContract.namehash("chilluminati.eth"))
// https://etherscan.io/address/0x00000000000c2e074ec69a0dfb2997ba6c7d2e1e#readContract
// -> resolver for namehash
// resolver : https://etherscan.io/address/0x4976fb03C32e5B8cfe2b6cCB31c09Ba78EBaBa41#readProxyContract
// -> add for namehash
// print(ENSContract.namehash("Ae71923d145ec0eAEDb2CF8197A08f12525Bddf4.addr.reverse".lowercased()))
// https://etherscan.io/address/0x00000000000c2e074ec69a0dfb2997ba6c7d2e1e#readContract
// -> resolver for namehash (lowercased address +++)
// resolver : https://etherscan.io/address/0x4976fb03C32e5B8cfe2b6cCB31c09Ba78EBaBa41#readProxyContract
// -> add for namehash
//
// THIS IS DEPRECATED, Use ipget to download directly
// DOwnload using cli: ./ipget --progress QmPyYXzqU7tsTxQ7WFN32iUKRy78T6ZiFGxYiJiv1TcoKE
/*
try? downloaders.forEach { downloader in
let firstIndex = downloader.firstIndex
let lastIndex = downloader.lastIndex
let collectionName = downloader.collection.name
let minFileSize = 1000
let parallelCount = 2//downloader.collection.baseUri.contains("ipfs://") ? 5 : 1
print("Started downloading collection:\(collectionName)")
func saveToken(_ tokenId : Int) -> Promise<Void> {
return after(seconds:0.001)
.then { _ in
downloader.collection.tokenData(BigUInt(tokenId))
.map { data -> Void in
print("Downloaded \(tokenId)")
// let filename = getImageFileName(collectionName,UInt(tokenId))
// try! data.image.write(to: filename)
saveJSON(getAttributesFileName(collectionName,UInt(tokenId)),data.attributes)
}
}
}
var tokenId = firstIndex
var prev : [Promise<Int>] = Array(repeating:Promise.value(tokenId), count: parallelCount)
for index in 0...(parallelCount-1) {
prev[index] = Promise.value(firstIndex + index)
}
let fileManager = FileManager.default
do {
try fileManager.createDirectory(at: getImageDirectory(collectionName), withIntermediateDirectories: true, attributes: nil)
try fileManager.createDirectory(at: getAttributesDirectory(collectionName), withIntermediateDirectories: true, attributes: nil)
} catch {
print(error)
}
while tokenId <= lastIndex {
for index in 0...(parallelCount-1) {
if (index > lastIndex) { continue }
let next = prev[index].then { tokenId -> Promise<Int> in
// print(tokenId,count)
let fileName = getImageFileName(collectionName,UInt(tokenId)).path
let attrFileName = getAttributesFileName(collectionName,UInt(tokenId)).path
let p =
// fileManager.fileExists(atPath:fileName)
// && (minFileSize < (try! fileManager.attributesOfItem(atPath:fileName))[FileAttributeKey.size] as! UInt64)
fileManager.fileExists(atPath:attrFileName)
? Promise.value(tokenId+parallelCount)
: saveToken(tokenId).map { tokenId + parallelCount }
return p.map { index in
print("Done \(index)")
return index
}
}
prev[index] = next
}
tokenId+=parallelCount
}
try hang(when(fulfilled:prev))
print("Done downloading. Verifing now")
var indexMissing = false
for index in firstIndex...lastIndex {
let path = getImageFileName(collectionName,UInt(index)).path
indexMissing = indexMissing || !fileManager.fileExists(atPath:path)
let attr = try fileManager.attributesOfItem(atPath: path)
if (minFileSize > attr[FileAttributeKey.size] as! UInt64) {
print("Token=\(index) is empty")
try fileManager.removeItem(atPath: path)
}
}
print("All downloaded=\(downloader.collection.name)")
}
*/
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment