status-react/src/status_im/ethereum/ens.cljs

167 lines
4.3 KiB
Clojure

(ns status-im.ethereum.ens
"
https://docs.ens.domains/en/latest/index.html
https://eips.ethereum.org/EIPS/eip-137
https://eips.ethereum.org/EIPS/eip-181
"
(:refer-clojure :exclude [name])
(:require [clojure.string :as string]
[status-im.ethereum.core :as ethereum]
[status-im.ethereum.json-rpc :as json-rpc]))
;; this is the addresses of ens registries for the different networks
(def ens-registries
{:mainnet "0x00000000000C2E074eC69A0dFb2997BA6C7d2e1e"
:testnet "0x00000000000C2E074eC69A0dFb2997BA6C7d2e1e"
:rinkeby "0x00000000000C2E074eC69A0dFb2997BA6C7d2e1e"})
(def default-namehash "0000000000000000000000000000000000000000000000000000000000000000")
(def default-address "0x0000000000000000000000000000000000000000")
(def default-key "0x0400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000")
(defn namehash
[s]
(ethereum/normalized-hex
(if (string/blank? s)
default-namehash
(let [[label remainder] (-> s
string/lower-case
(string/split #"\." 2))]
(ethereum/sha3 (+ (namehash remainder)
(subs (ethereum/sha3 label) 2)))))))
;; Registry contract
(defn resolver
[registry ens-name cb]
(json-rpc/eth-call
{:contract registry
:method "resolver(bytes32)"
:params [(namehash ens-name)]
:outputs ["address"]
:on-success
(fn [[address]]
(cb (when-not (= address default-address) address)))}))
(defn owner
[registry ens-name cb]
(json-rpc/eth-call
{:contract registry
:method "owner(bytes32)"
:params [(namehash ens-name)]
:outputs ["address"]
:on-success
(fn [[address]]
(cb address))}))
(defn ttl
[registry ens-name cb]
(json-rpc/eth-call
{:contract registry
:method "ttl(bytes32)"
:params [(namehash ens-name)]
:outputs ["uint256"]
:on-success
(fn [[ttl]]
(cb ttl))}))
;; Resolver contract
;; Resolver must implement EIP65 (supportsInterface). When interacting with an unknown resolver it's safer to rely on it.
(def addr-hash "0x3b3b57de")
(defn addr
[resolver ens-name cb]
(json-rpc/eth-call
{:contract resolver
:method "addr(bytes32)"
:params [(namehash ens-name)]
:outputs ["address"]
:on-success
(fn [[address]]
(cb address))}))
(def name-hash "0x691f3431")
;; Defined by https://eips.ethereum.org/EIPS/eip-181
(defn name
[resolver ens-name cb]
(json-rpc/eth-call
{:contract resolver
:method "name(bytes32)"
:params [(namehash ens-name)]
:outputs ["string"]
:on-success
(fn [[name]]
(cb name))}))
(defn contenthash
[resolver ens-name cb]
(json-rpc/eth-call
{:contract resolver
:method "contenthash(bytes32)"
:params [(namehash ens-name)]
:on-success
(fn [raw-hash]
;; NOTE: it would be better if our abi-spec/decode was able to do that
(let [hash (subs raw-hash 130)
[cid & hash-and-zeros] (string/split hash "1b20")
hash (str "0x" cid "1b20" (subs (apply str hash-and-zeros) 0 64))]
(cb hash)))}))
(defn content
[resolver ens-name cb]
(json-rpc/eth-call
{:contract resolver
:method "content(bytes32)"
:params [(namehash ens-name)]
:on-success
(fn [hash]
(cb hash))}))
(def ABI-hash "0x2203ab56")
(def pubkey-hash "0xc8690233")
(defn uncompressed-public-key
[x y]
(when (and x y)
(str "0x04" x y)))
(defn valid-eth-name-prefix?
[prefix]
(not
(or (string/blank? prefix)
(string/ends-with? prefix ".")
(string/includes? prefix ".."))))
(defn is-valid-eth-name?
[ens-name]
(and ens-name
(string? ens-name)
(string/ends-with? ens-name ".eth")))
(defn pubkey
[resolver ens-name cb]
(json-rpc/eth-call
{:contract resolver
:method "pubkey(bytes32)"
:params [(namehash ens-name)]
:outputs ["bytes32" "bytes32"]
:on-success
(fn [[x y]]
(let [public-key (uncompressed-public-key x y)]
(cb public-key)))}))
(defn get-addr
[registry ens-name cb]
{:pre [(is-valid-eth-name? ens-name)]}
(resolver registry
ens-name
#(addr % ens-name cb)))
(defn get-owner
[registry ens-name cb]
{:pre [(is-valid-eth-name? ens-name)]}
(owner registry ens-name cb))