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

ENS integration #707

Merged
merged 1 commit into from
Jul 8, 2021
Merged
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
32 changes: 32 additions & 0 deletions contracts/ens/ENS.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
pragma solidity >=0.4.24;

interface ENS {

// Logged when the owner of a node assigns a new owner to a subnode.
event NewOwner(bytes32 indexed node, bytes32 indexed label, address owner);

// Logged when the owner of a node transfers ownership to a new account.
event Transfer(bytes32 indexed node, address owner);

// Logged when the resolver for a node changes.
event NewResolver(bytes32 indexed node, address resolver);

// Logged when the TTL of a node changes
event NewTTL(bytes32 indexed node, uint64 ttl);

// Logged when an operator is added or removed.
event ApprovalForAll(address indexed owner, address indexed operator, bool approved);

function setRecord(bytes32 node, address owner, address resolver, uint64 ttl) external;
function setSubnodeRecord(bytes32 node, bytes32 label, address owner, address resolver, uint64 ttl) external;
function setSubnodeOwner(bytes32 node, bytes32 label, address owner) external returns(bytes32);
function setResolver(bytes32 node, address resolver) external;
function setOwner(bytes32 node, address owner) external;
function setTTL(bytes32 node, uint64 ttl) external;
function setApprovalForAll(address operator, bool approved) external;
function owner(bytes32 node) external view returns (address);
function resolver(bytes32 node) external view returns (address);
function ttl(bytes32 node) external view returns (uint64);
function recordExists(bytes32 node) external view returns (bool);
function isApprovedForAll(address owner, address operator) external view returns (bool);
}
3 changes: 3 additions & 0 deletions src/memefactory/shared/smart_contracts_dev.cljs
Original file line number Diff line number Diff line change
Expand Up @@ -63,4 +63,7 @@
:address "0x0000000000000000000000000000000000000000"}
:dank-faucet
{:name "DankFaucet"
:address "0x0000000000000000000000000000000000000000"}
:ens
{:name "ENS"
:address "0x0000000000000000000000000000000000000000"}})
5 changes: 4 additions & 1 deletion src/memefactory/shared/smart_contracts_prod.cljs
Original file line number Diff line number Diff line change
Expand Up @@ -65,4 +65,7 @@
:meme-auction-factory-fwd
{:name "MutableForwarder",
:address "0xb47c930fa2cce89d0a92925733ae65f72ae8914e",
:forwards-to :meme-auction-factory}})
:forwards-to :meme-auction-factory},
:ens
{:name "ENS",
:address "0x00000000000C2E074eC69A0dFb2997BA6C7d2e1e"}})
3 changes: 2 additions & 1 deletion src/memefactory/shared/smart_contracts_qa.cljs
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,5 @@
:meme-registry-fwd {:name "MutableForwarder" :address "0x798693bdb05e9359b8d30d8d052044974aedaa53" :forwards-to :meme-registry}
:meme-auction-factory-fwd {:name "MutableForwarder" :address "0xa4681530e9826a36b70b18b3b35893788ebe1f4f" :forwards-to :meme-auction-factory}
:district0x-emails {:name "District0xEmails" :address "0xaff9758d2693ce469da913dc7d64ed256f318eed"}
:dank-faucet {:name "DankFaucet" :address "0xb2295316e4012fa49bc4787157301abca9188ae4"}})
:dank-faucet {:name "DankFaucet" :address "0xb2295316e4012fa49bc4787157301abca9188ae4"}
:ens {:name "ENS" :address "0x00000000000C2E074eC69A0dFb2997BA6C7d2e1e"}})
2 changes: 1 addition & 1 deletion src/memefactory/styles/pages/mymemefolio.clj
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@
:color :mymemefolio-green
:icon "/assets/icons/portfolio2.svg"})
[:div.search-form
[:h2 {:max-width (em 13.5)
[:h2 {:max-width (em 33.5)
:position :unset
:text-overflow :ellipsis
:margin-left :auto
Expand Down
4 changes: 2 additions & 2 deletions src/memefactory/ui/components/app_layout.cljs
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
(ns memefactory.ui.components.app-layout
(:require
[district.ui.component.active-account :refer [active-account]]
[district.ui.component.active-account-balance :refer [active-account-balance]]
[district.ui.component.form.input :refer [text-input*]]
[district.ui.component.meta-tags :as meta-tags]
Expand All @@ -12,6 +11,7 @@
[district.ui.web3-accounts.subs :as accounts-subs]
[district.ui.web3-tx-log.subs :as tx-log-subs]
[memefactory.ui.components.account-balances :refer [account-balances]]
[memefactory.ui.components.ens-active-account :refer [ens-active-account]]
[memefactory.ui.contract.param-change :as param-change]
[memefactory.ui.components.general :refer [nav-anchor]]
[memefactory.ui.subs :as mf-subs]
Expand Down Expand Up @@ -121,7 +121,7 @@
(fn []
[:div.app-bar
[:div.account-section
[active-account]]
[ens-active-account]]
[:div.tracker-section
{:on-click (fn []
(if (empty? @my-addresses)
Expand Down
5 changes: 3 additions & 2 deletions src/memefactory/ui/components/challenge_list.cljs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
[district.ui.now.subs]
[district.ui.web3-accounts.subs :as accounts-subs]
[goog.string :as gstring]
[memefactory.ui.components.ens-resolver :as ens]
[memefactory.ui.components.general :refer [nav-anchor]]
[memefactory.ui.components.infinite-scroll :refer [infinite-scroll]]
[memefactory.ui.components.panels :refer [no-items-found]]
Expand Down Expand Up @@ -84,7 +85,7 @@
:query {:tab :created}
:class (str "address " (when (= (:user/address user) @(subscribe [::accounts-subs/active-account]))
"active-address"))}
(-> user :user/address)]]])
(ens/reverse-resolve (-> user :user/address))]]])


(defn challenger-info [user]
Expand All @@ -102,7 +103,7 @@
:query {:tab :curated}
:class (str "address " (when (= (:user/address user) @(subscribe [::accounts-subs/active-account]))
"active-address"))}
(-> user :user/address)]]])
(ens/reverse-resolve (-> user :user/address))]]])


(defn challenge [{:keys [:entry :include-challenger-info? :action-child]}]
Expand Down
35 changes: 35 additions & 0 deletions src/memefactory/ui/components/ens_active_account.cljs
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
(ns memefactory.ui.components.ens-active-account
(:require
[district.ui.web3-accounts.events :as accounts-events]
[district.ui.web3-accounts.subs :as accounts-subs]
[memefactory.ui.components.ens-resolver :as ens]
[re-frame.core :refer [subscribe dispatch]]
[reagent.core :as r]
[soda-ash.core :as ui]))

;; This is a copy of district.ui.component.active-account but using ENS to resolve the accounts addresses

(defn ens-active-account []
(let [accounts (subscribe [::accounts-subs/accounts])
active-acc (subscribe [::accounts-subs/active-account])]
(fn [{:keys [:select-props :single-account-props]}]
(if (seq @accounts)
[:div.active-account
(if (= 1 (count @accounts))
[:span.single-account
single-account-props
(ens/reverse-resolve @active-acc)]
[ui/Select
(r/merge-props
{:select-on-blur false
:class "active-account-select"
:value @active-acc
:on-change (fn [e data]
(dispatch [::accounts-events/set-active-account (aget data "value")]))
:fluid true
:options (doall (for [acc @accounts]
{:value acc :text (ens/reverse-resolve acc)}))}
select-props)])]
[:div
"Ethereum wallet not connected"]))))

163 changes: 163 additions & 0 deletions src/memefactory/ui/components/ens_resolver.cljs
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
(ns memefactory.ui.components.ens-resolver
(:require
[cljs-web3.core :as web3-core]
[cljs-web3.eth :as web3-eth]
[clojure.string :as string]
[district.ui.logging.events :as logging]
[district.ui.smart-contracts.queries :as contract-queries]
[district.ui.smart-contracts.subs :as contract-subs]
[district.ui.web3.queries :as web3-queries]
[district.web3-utils :as web3-utils]
[re-frame.core :as re-frame]
[re-frame.core :refer [subscribe dispatch]])

(:require-macros [reagent.ratom :refer [reaction]]))


;; This component is aimed to reverse-resolve addresses into names using the Ethereum Name Service (ENS).
;; For example, 0x12345...789.addr.reverse -> 'alice.eth'
;; (note we append ".addr.reverse" to the address to indicate this is a reverse resolution)
;;
;; ENS defines a Registry and Resolvers. Then, to make a reverse resolution we need to follow 4 steps.
;; 1) Query the ENS Registry to fetch the Resolver associated to the given address.
;; This will give us the address of the Resolver (which holds the domain info of the address),
;; if any associated.
;; 2) Query the Resolver obtained in the previous step to get the name associated to the address.
;; If the address has a name associated, the resolver will give it to us and this would complete
;; our resolution process.
;; However, ENS does not enforce the accuracy of reverse records, resulting that anyone can claim
;; that the name of their address is 'alice.eth'. Therefore, to be certain the claim is correct,
;; we must perform a forward resolution to validate that the given name is resolved to the original
;; address, thus requiring next steps.
;; 3) Query the ENS Registry to fetch the Resolver associted to the name obtained in previous step.
;; That is similar to first step, but querying the name instead of the reverse address.
;; 4) Query the Resolver obtained in the previous step to get the address associated to the name.
;; Now, if both the original address and the address the Resolver gives us match, we have
;; accomplished the name resolution


(defn assoc-ens-name [db addr name]
(assoc-in db [:memefactory.ui.components.ens-resolved-address (keyword addr)] name))


;; Subscription handler to fetch the name associated to a given address
(re-frame/reg-sub-raw
::ens-name
(fn [db [_ addr]]
(let [addr (string/lower-case addr)]
(dispatch [::reverse-resolve-address addr])
(reaction (get-in @db [:memefactory.ui.components.ens-resolved-address (keyword addr)])))))


(defn reverse-resolve
"Convenience function to resolve an address to a name,
showing the address until is resolved or if cannot be resolved at all.

Parameters:
addr - the address to be reverse-resolved
"
[addr]
(or (and @(subscribe [::contract-subs/contract-abi :ens]) ; make sure contract is already loaded
(some? addr)
@(subscribe [::ens-name addr]))
addr))

;; Reduced version of the Resolver contract ABI for ENS addr/name resolution.
;; Note this is limited to addr() and name() methods
(def abi-resolver (js/JSON.parse "[{\"constant\":true,\"inputs\":[{\"name\":\"node\",\"type\":\"bytes32\"}],\"name\":\"addr\",\"outputs\":[{\"name\":\"ret\",\"type\":\"address\"}],\"payable\":false,\"type\":\"function\"},{\"constant\":true,\"inputs\":[{\"name\":\"node\",\"type\":\"bytes32\"}],\"name\":\"name\",\"outputs\":[{\"name\":\"ret\",\"type\":\"string\"}],\"payable\":false,\"type\":\"function\"}]"))

(defn namehash-join [node name]
(subs (web3-core/sha3 (str node (subs (web3-core/sha3 name) 2)) {:encoding :hex}) 2))

(defn build-namehash [name]
(str "0x" (reduce namehash-join (apply str (repeat 32 "00")) (rseq (string/split name #"\.")))))

(def interceptors [re-frame/trim-v])


;; Handler for (reverse-)resolving an address to a name.
;; Queries the ENS Registry to get the resolver associated to the given address
(re-frame/reg-event-fx
::reverse-resolve-address
interceptors
(fn [{:keys [:db]} [addr]]
(let [instance (contract-queries/instance db :ens)
namehash (build-namehash (str (string/lower-case (web3-utils/remove-0x addr)) ".addr.reverse" ))
data {:addr addr :namehash namehash}]
{:web3/call
{:web3 (web3-queries/web3 db)
:fns [{:instance instance
:fn :resolver
:args [namehash]
:on-success [::resolver-get-address data]
:on-error [::logging/error "Error calling ENS Registry" data ::reverse-resolve-address]}]}})))


;; Handler for processing the response coming from the ENS registry, which indicates
;; the address of the resolver for the reverse address, if any.
;; It makes a query to the resolver to ask for the name of the address we want to reverse-resolve
(re-frame/reg-event-fx
::resolver-get-address
interceptors
(fn [{:keys [:db]} [{:keys [:addr :namehash] :as data} resolver-addr]]
(when (not (web3-utils/empty-address? resolver-addr))
(let [instance (web3-eth/contract-at (web3-queries/web3 db) abi-resolver resolver-addr)]
{:web3/call
{:web3 (web3-queries/web3 db)
:fns [{:instance instance
:fn :name
:args [namehash]
:on-success [::validate-reverse-address data]
:on-error [::logging/error "Error calling ENS Resolver" (merge data {:resolver-addr resolver-addr}) ::resolver-get-address]}]}}))))


;; Handler for processing the response coming from the Resolver, which
;; indicates the name associated to a given (reverse) address, if any.
;; If the address is resolved to a name, it triggers a forward name resolution
;; to validate the name by querying the ENS Registry
(re-frame/reg-event-fx
::validate-reverse-address
interceptors
(fn [{:keys [:db]} [{:keys [:addr :namehash] :as data} name]]
(when (not (string/blank? name))
(let [instance (contract-queries/instance db :ens)
namehash (build-namehash name)
data {:name name :namehash namehash :original-addr addr}]
{:web3/call
{:web3 (web3-queries/web3 db)
:fns [{:instance instance
:fn :resolver
:args [namehash]
:on-success [::resolver-get-name data]
:on-error [::logging/error "Error calling ENS Registry" data ::validate-reverse-address]}]}}))))


;; Handler for processing the response coming from the ENS Registry, which indicates
;; the address of the resolver associated to a given domain name, if any.
;; It makes a query to the speficied address to ask for the addres of the name want to forward-resolve
(re-frame/reg-event-fx
::resolver-get-name
interceptors
(fn [{:keys [:db]} [{:keys [:name :namehash :original-addr] :as data} resolver-addr]]
(when (not (web3-utils/empty-address? resolver-addr))
(let [instance (web3-eth/contract-at (web3-queries/web3 db) abi-resolver resolver-addr)]
{:web3/call
{:web3 (web3-queries/web3 db)
:fns [{:instance instance
:fn :addr
:args [namehash]
:on-success [::validate-address-name data]
:on-error [::logging/error "Error calling ENS Resolver" (merge data {:resolver-addr resolver-addr}) ::resolver-get-name]}]}}))))


;; Handler for processing the response coming from the Resolver, which
;; indicates the address associated to a given name, if any.
;; It validates the forward-resolved address matches the original address
;; and sets the address-to-name relationship in the db to notify all
;; subscribers
(re-frame/reg-event-fx
::validate-address-name
interceptors
(fn [{:keys [:db]} [{:keys [:name :namehash :original-addr]} addr]]
(when (= original-addr (string/lower-case addr))
{:db (assoc-ens-name db addr name)})))
9 changes: 5 additions & 4 deletions src/memefactory/ui/components/tiles.cljs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
[district.ui.web3-tx-id.subs :as tx-id-subs]
[goog.string :as gstring]
[memefactory.shared.utils :as shared-utils]
[memefactory.ui.components.ens-resolver :as ens]
[memefactory.ui.components.general :refer [nav-anchor]]
[memefactory.ui.contract.meme-auction :as meme-auction]
[memefactory.ui.utils :as ui-utils :refer [format-price]]
Expand Down Expand Up @@ -149,8 +150,8 @@
[nav-anchor {:route :route.memefolio/index
:params {:address (:user/address (:meme-auction/seller meme-auction))}
:query {:tab :selling}
:title (str "Go to the Memefolio of " seller-address)}
seller-address]]
:title (str "Go to the Memefolio of " (ens/reverse-resolve seller-address))}
(ens/reverse-resolve seller-address)]]
[:li [:label "Current Price:"] [:span (format-price price)]]
[:li [:label "Start Price:"] [:span (format-price (:meme-auction/start-price meme-auction))]]
[:li [:label "End Price:"] [:span (format-price (:meme-auction/end-price meme-auction))]]
Expand Down Expand Up @@ -239,8 +240,8 @@
[nav-anchor {:route :route.memefolio/index
:params {:address creator-address}
:query {:tab :created}
:title (str "Go to the Memefolio of " creator-address)}
creator-address]]
:title (str "Go to the Memefolio of " (ens/reverse-resolve creator-address))}
(ens/reverse-resolve creator-address)]]
[:li [:label "Created:"]
(let [formated-time (-> (time/time-remaining (t/date-time (gql-utils/gql-date->date created-on)) (t/now))
(dissoc :seconds)
Expand Down
3 changes: 2 additions & 1 deletion src/memefactory/ui/leaderboard/collectors_page.cljs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
[district.ui.web3-accounts.subs :as accounts-subs]
[goog.string :as gstring]
[memefactory.ui.components.app-layout :refer [app-layout]]
[memefactory.ui.components.ens-resolver :as ens]
[memefactory.ui.components.general :refer [nav-anchor]]
[memefactory.ui.components.infinite-scroll :refer [infinite-scroll]]
[memefactory.ui.components.panels :refer [no-items-found]]
Expand Down Expand Up @@ -52,7 +53,7 @@
:params {:address address}
:query {:tab :collected}
:class "user-address"}
address]
(ens/reverse-resolve address)]
[:ul ;; TODO complete these after Matus comments
[:li "Unique Memes: " [:span.unique (gstring/format "%d/%d (%d%%)"
total-collected-memes
Expand Down
3 changes: 2 additions & 1 deletion src/memefactory/ui/leaderboard/creators_page.cljs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
[district.ui.web3-accounts.subs :as accounts-subs]
[goog.string :as gstring]
[memefactory.ui.components.app-layout :refer [app-layout]]
[memefactory.ui.components.ens-resolver :as ens]
[memefactory.ui.components.general :refer [nav-anchor]]
[memefactory.ui.components.infinite-scroll :refer [infinite-scroll]]
[memefactory.ui.components.panels :refer [no-items-found]]
Expand Down Expand Up @@ -53,7 +54,7 @@
:params {:address address}
:query {:tab :created}
:class "user-address"}
address]
(ens/reverse-resolve address)]
[:ul
[:li "Earned: " [:span.earned (format-price creator-total-earned)]]
[:li "Success Rate: " [:span.success-rate (gstring/format "%d/%d (%d%%)"
Expand Down
3 changes: 2 additions & 1 deletion src/memefactory/ui/leaderboard/curators_page.cljs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
[district.ui.graphql.subs :as gql]
[district.ui.web3-accounts.subs :as accounts-subs]
[memefactory.ui.components.app-layout :refer [app-layout]]
[memefactory.ui.components.ens-resolver :as ens]
[memefactory.ui.components.general :refer [dank-with-logo]]
[memefactory.ui.components.general :refer [nav-anchor]]
[memefactory.ui.components.infinite-scroll :refer [infinite-scroll]]
Expand Down Expand Up @@ -100,7 +101,7 @@
:params {:address (:user/address curator)}
:query {:tab :curated}}
[:h3.address
(:user/address curator)]]
(ens/reverse-resolve (:user/address curator))]]

[:h4.challenges "CHALLENGES"]
[:p "Success rate: "
Expand Down
Loading