[fix 8853] fix change of whisper id in ens registration

A user can type in their existing name in the registration flow. Status can
confirm if they own it. After signing a transaction, the user can update the
Whisper ID to their new one.

Instead of using a hardcoded contract for stateofus, the standard `owner`
method is called to find the resolver contract of a ens name.
This allows users to set the pubkey even for ens names that are not
subdomains of stateofus

Signed-off-by: yenda <eric@status.im>
This commit is contained in:
yenda 2019-09-02 10:40:13 +02:00
parent 1f24722db0
commit 7b6dfad702
No known key found for this signature in database
GPG Key ID: 0095623C0069DCE6
24 changed files with 784 additions and 548 deletions

View File

@ -1,10 +1,11 @@
(ns status-im.ens.core
(:require [clojure.string :as string]
[re-frame.core :as re-frame]
[status-im.multiaccounts.update.core :as multiaccounts.update]
[status-im.ens.db :as ens.db]
[status-im.ethereum.abi-spec :as abi-spec]
[status-im.ethereum.contracts :as contracts]
[status-im.ethereum.core :as ethereum]
[status-im.ethereum.eip55 :as eip55]
[status-im.ethereum.ens :as ens]
[status-im.ethereum.resolver :as resolver]
[status-im.ethereum.stateofus :as stateofus]
@ -12,7 +13,8 @@
[status-im.utils.fx :as fx]
[status-im.utils.money :as money]
[status-im.signing.core :as signing]
[status-im.ethereum.eip55 :as eip55])
[status-im.multiaccounts.update.core :as multiaccounts.update]
[taoensso.timbre :as log])
(:refer-clojure :exclude [name]))
(defn fullname [custom-domain? username]
@ -21,51 +23,116 @@
(stateofus/subdomain username)))
(re-frame/reg-fx
:ens/resolve-address
::resolve-address
(fn [[registry name cb]]
(ens/get-addr registry name cb)))
(re-frame/reg-fx
:ens/resolve-pubkey
::resolve-pubkey
(fn [[registry name cb]]
(resolver/pubkey registry name cb)))
(defn- final-state? [state]
(#{:saved :registered :registration-failed} state))
(defn assoc-state-for [db username state]
(cond-> (assoc-in db [:ens/registration :states username] state)
(final-state? state) (update :ens/registration dissoc :registering?)))
(defn assoc-details-for [db username k v]
(assoc-in db [:ens/names :details username k] v))
(defn assoc-username-candidate [db username]
(assoc-in db [:ens/registration :username-candidate] username))
(defn empty-username-candidate [db] (assoc-username-candidate db ""))
(fx/defn set-state
{:events [:ens/set-state]}
{:events [::name-resolved]}
[{:keys [db]} username state]
{:db (assoc-state-for db username state)})
(when (= username
(get-in db [:ens/registration :username]))
{:db (assoc-in db [:ens/registration :state] state)}))
(defn- on-resolve [registry custom-domain? username address public-key s]
(fx/defn on-resolver-found
{:events [::resolver-found]}
[{:keys [db] :as cofx} resolver-contract]
(let [{:keys [state username custom-domain?]} (:ens/registration db)
{:keys [public-key]} (:multiaccount db)
{:keys [x y]} (ethereum/coordinates public-key)
namehash (ens/namehash (str username (when-not custom-domain?
".stateofus.eth")))]
(signing/eth-transaction-call
cofx
{:contract resolver-contract
:method "setPubkey(bytes32,bytes32,bytes32)"
:params [namehash x y]
:on-result [::save-username custom-domain? username]
:on-error [::on-registration-failure]})))
(fx/defn save-username
{:events [::save-username]}
[{:keys [db] :as cofx} custom-domain? username]
(let [name (fullname custom-domain? username)
names (get-in db [:multiaccount :usernames] [])
new-names (conj names name)]
(multiaccounts.update/multiaccount-update cofx
(cond-> {:usernames new-names}
(empty? names) (assoc :preferred-name name))
{:on-success #(re-frame/dispatch [::username-saved])})))
(fx/defn on-input-submitted
{:events [::input-submitted ::input-icon-pressed]}
[{:keys [db] :as cofx}]
(let [{:keys [state username custom-domain?]} (:ens/registration db)
registry-contract (get ens/ens-registries (ethereum/chain-keyword db))
ens-name (str username (when-not custom-domain?
".stateofus.eth"))]
(case state
(:available :owned)
(navigation/navigate-to-cofx cofx :ens-checkout {})
:connected-with-different-key
(ens/resolver registry-contract ens-name
#(re-frame/dispatch [::resolver-found %]))
:connected
(save-username cofx custom-domain? username)
;; for other states, we do nothing
nil)))
(fx/defn username-saved
{:events [::username-saved]}
[{:keys [db] :as cofx}]
;; we reset navigation so that navigate back doesn't return
;; into the registration flow
(navigation/navigate-reset cofx
{:index 1
:key :profile-stack
:actions [{:routeName :my-profile}
{:routeName :ens-confirmation}]}))
(defn- on-resolve
[registry custom-domain? username address public-key response]
(cond
(= (eip55/address->checksum address) (eip55/address->checksum s))
;; if we get an address back, we try to get the public key associated
;; with the username as well
(= (eip55/address->checksum address)
(eip55/address->checksum response))
(resolver/pubkey registry (fullname custom-domain? username)
#(re-frame/dispatch [:ens/set-state username (if (= % public-key) :connected :owned)]))
#(re-frame/dispatch [::name-resolved username
(cond
(not public-key) :owned
(= % public-key) :connected
:else :connected-with-different-key)]))
(and (nil? s) (not custom-domain?)) ;; No address for a stateofus subdomain: it can be registered
(re-frame/dispatch [:ens/set-state username :registrable])
;; No address for a stateofus subdomain: it can be registered
(and (nil? response) (not custom-domain?))
(re-frame/dispatch [::name-resolved username :available])
:else
(re-frame/dispatch [:ens/set-state username :unregistrable])))
(re-frame/dispatch [::name-resolved username :taken])))
(defn registration-cost
[chain-id]
(case chain-id
3 50
1 10))
(fx/defn register-name
{:events [:ens/register]}
[{:keys [db] :as cofx} {:keys [amount contract custom-domain? username address public-key]}]
(let [{:keys [x y]} (ethereum/coordinates public-key)]
{:events [::register-name-pressed]}
[{:keys [db] :as cofx}]
(let [{:keys [custom-domain? username]}
(:ens/registration db)
{:keys [address public-key]} (:multiaccount db)
chain (ethereum/chain-keyword db)
chain-id (ethereum/chain-id db)
contract (get stateofus/registrars chain)
amount (registration-cost chain-id)
{:keys [x y]} (ethereum/coordinates public-key)]
(signing/eth-transaction-call
cofx
{:contract (contracts/get-address db :status/snt)
@ -74,8 +141,8 @@
(money/unit->token amount 18)
(abi-spec/encode "register(bytes32,address,bytes32,bytes32)"
[(ethereum/sha3 username) address x y])]
:on-result [:ens/save-username custom-domain? username]
:on-error [:ens/on-registration-failure]})))
:on-result [::save-username custom-domain? username]
:on-error [::on-registration-failure]})))
(defn- valid-custom-domain? [username]
(and (ens/is-valid-eth-name? username)
@ -86,63 +153,65 @@
(valid-custom-domain? username)
(stateofus/valid-username? username)))
(defn- state [custom-domain? username]
(defn- state [custom-domain? username usernames]
(cond
(string/blank? username) :initial
(> 4 (count username)) :too-short
(valid-username? custom-domain? username) :valid
(or (string/blank? username)
(> 4 (count username))) :too-short
(valid-username? custom-domain? username)
(if (usernames (fullname custom-domain? username))
:already-added
:searching)
:else :invalid))
(fx/defn set-username-candidate
{:events [:ens/set-username-candidate]}
[{:keys [db]} custom-domain? username]
(let [state (state custom-domain? username)
valid? (valid-username? custom-domain? username)
name (fullname custom-domain? username)]
{:events [::set-username-candidate]}
[{:keys [db]} username]
(let [{:keys [custom-domain?]} (:ens/registration db)
usernames (into #{} (get-in db [:multiaccount :usernames]))
state (state custom-domain? username usernames)]
(merge
{:db (-> db
(assoc-username-candidate username)
(assoc-state-for username state))}
(when (and name (= :valid state))
(let [{:keys [multiaccount]} db
{:keys [public-key]} multiaccount
{:db (update db :ens/registration assoc
:username username
:state state)}
(when (= state :searching)
(let [{:keys [multiaccount]} db
{:keys [public-key]} multiaccount
address (ethereum/default-address db)
registry (get ens/ens-registries (ethereum/chain-keyword db))]
{:ens/resolve-address [registry name #(on-resolve registry custom-domain? username address public-key %)]})))))
{::resolve-address [registry
(fullname custom-domain? username)
#(on-resolve registry custom-domain? username address public-key %)]})))))
(fx/defn clear-cache-and-navigate-back
{:events [:ens/clear-cache-and-navigate-back]}
(fx/defn return-to-ens-main-screen
{:events [::got-it-pressed ::cancel-pressed]}
[{:keys [db] :as cofx} _]
(fx/merge cofx
{:db (assoc db :ens/registration nil)} ;; Clear cache
(navigation/navigate-back)))
;; clear registration data
{:db (dissoc db :ens/registration)}
;; we reset navigation so that navigate back doesn't return
;; into the registration flow
(navigation/navigate-reset {:index 1
:key :profile-stack
:actions [{:routeName :my-profile}
{:routeName :ens-main}]})))
(fx/defn switch-domain-type
{:events [:ens/switch-domain-type]}
[{:keys [db]} _]
{:db (-> (update-in db [:ens/registration :custom-domain?] not)
(empty-username-candidate))})
{:events [::switch-domain-type]}
[{:keys [db] :as cofx} _]
(fx/merge cofx
{:db (-> db
(update :ens/registration dissoc :username :state)
(update-in [:ens/registration :custom-domain?] not))}))
(fx/defn save-preferred-name
{:events [:ens/save-preferred-name]}
{:events [::save-preferred-name]}
[{:keys [db] :as cofx} name]
(multiaccounts.update/multiaccount-update cofx
{:preferred-name name}
{}))
(fx/defn save-username
{:events [:ens/save-username]}
[{:keys [db] :as cofx} custom-domain? username]
(let [name (fullname custom-domain? username)
names (get-in db [:multiaccount :usernames] [])
new-names (conj names name)]
(multiaccounts.update/multiaccount-update cofx
(cond-> {:usernames new-names}
(empty? names) (assoc :preferred-name name))
{:on-success #(re-frame/dispatch [:ens/set-state username :saved])})))
(fx/defn switch-show-username
{:events [:ens/switch-show-username]}
{:events [::switch-show-username]}
[{:keys [db] :as cofx} _]
(let [show-name? (not (get-in db [:multiaccount :show-name?]))]
(multiaccounts.update/multiaccount-update cofx
@ -153,26 +222,33 @@
"TODO not sure there is actually anything to do here
it should only be called if the user cancels the signing
Actual registration failure has not been implemented properly"
{:events [:ens/on-registration-failure]}
{:events [::on-registration-failure]}
[{:keys [db]} username])
(fx/defn store-name-detail
{:events [:ens/store-name-detail]}
[{:keys [db]} name k v]
{:db (assoc-details-for db name k v)})
(fx/defn store-name-address
{:events [::address-resolved]}
[{:keys [db]} username address]
{:db (assoc-in db [:ens/names username :address] address)})
(fx/defn store-name-public-key
{:events [::public-key-resolved]}
[{:keys [db]} username public-key]
{:db (assoc-in db [:ens/names username :public-key] public-key)})
(fx/defn navigate-to-name
{:events [:ens/navigate-to-name]}
[{:keys [db] :as cofx} name]
{:events [::navigate-to-name]}
[{:keys [db] :as cofx} username]
(let [registry (get ens/ens-registries (ethereum/chain-keyword db))]
(fx/merge cofx
{:ens/resolve-address [registry name #(re-frame/dispatch [:ens/store-name-detail name :address %])]
:ens/resolve-pubkey [registry name #(re-frame/dispatch [:ens/store-name-detail name :public-key %])]}
(navigation/navigate-to-cofx :ens-name-details name))))
{::resolve-address [registry username
#(re-frame/dispatch [::address-resolved username %])]
::resolve-pubkey [registry username
#(re-frame/dispatch [::public-key-resolved username %])]}
(navigation/navigate-to-cofx :ens-name-details username))))
(fx/defn start-registration
{:events [::add-username-pressed ::get-started-pressed]}
[{:keys [db] :as cofx}]
(fx/merge cofx
{:db (assoc-in db [:ens/registration :registering?] true)}
(navigation/navigate-to-cofx :ens-register {})))
(set-username-candidate (get-in db [:ens/registration :username] ""))
(navigation/navigate-to-cofx :ens-search {})))

14
src/status_im/ens/db.cljs Normal file
View File

@ -0,0 +1,14 @@
(ns status-im.ens.db
(:require [cljs.spec.alpha :as spec]))
(spec/def ::state #{:too-short
:already-added
:searching
:invalid
:available
:taken
:owned
:connected
:connected-with-different-key})
(spec/def :ens/registration (spec/keys :opt-un [::state]))

View File

@ -100,6 +100,10 @@
[{:networks/keys [current-network networks]}]
(network->chain-keyword (get networks current-network)))
(defn chain-id
[{:networks/keys [current-network networks]}]
(network->chain-id (get networks current-network)))
(defn snt-symbol [db]
(case (chain-keyword db)
:mainnet :SNT

View File

@ -125,7 +125,8 @@
(defn uncompressed-public-key
[x y]
(str "0x04" x y))
(when (and x y)
(str "0x04" x y)))
(defn valid-eth-name-prefix?
[prefix]

View File

@ -141,3 +141,9 @@
(fn [params]
(doseq [param params]
(call param))))
(re-frame/reg-fx
::eth-call
(fn [params]
(doseq [param params]
(eth-call param))))

View File

@ -12,7 +12,9 @@
(def registrars
{:mainnet "0xDB5ac1a559b02E12F29fC0eC0e37Be8E046DEF49"
:testnet "0x11d9F481effd20D76cEE832559bd9Aca25405841"})
;;NOTE: can be enabled for testing builds
;;:testnet "0x11d9F481effd20D76cEE832559bd9Aca25405841"
})
(defn lower-case? [s]
(when s
@ -21,4 +23,4 @@
(defn valid-username? [username]
(boolean
(and (lower-case? username)
(re-find #"^[a-z0-9]+$" username))))
(re-find #"^[a-z0-9]+$" username))))

View File

@ -50,7 +50,8 @@
status-im.ui.screens.hardwallet.connect.subs
status-im.ui.screens.hardwallet.settings.subs
status-im.ui.screens.hardwallet.pin.subs
status-im.ui.screens.hardwallet.setup.subs))
status-im.ui.screens.hardwallet.setup.subs
[status-im.ens.core :as ens]))
;; TOP LEVEL ===========================================================================================================
@ -425,6 +426,13 @@
(fn [{:keys [public-key]}]
public-key))
(re-frame/reg-sub
:multiaccount/default-address
:<- [:multiaccount]
(fn [{:keys [accounts]}]
(ethereum/normalized-address
(:address (ethereum/get-default-account accounts)))))
(re-frame/reg-sub
:sign-in-enabled?
:<- [:multiaccounts/login]
@ -1845,40 +1853,58 @@
(:show-name? multiaccount)))
(re-frame/reg-sub
:ens.registration/screen
:ens/search-screen
:<- [:ens/registration]
(fn [{:keys [custom-domain? username state]}]
{:state state
:username username
:custom-domain? custom-domain?}))
(defn- ens-amount-label
[chain-id]
(str (ens/registration-cost chain-id)
(case chain-id
3 " STT"
1 " SNT"
"")))
(re-frame/reg-sub
:ens/checkout-screen
:<- [:ens/registration]
:<- [:ens.stateofus/registrar]
:<- [:multiaccount]
:<- [:multiaccount/default-address]
:<- [:multiaccount/public-key]
:<- [:chain-id]
(fn [[{:keys [custom-domain? username-candidate registering?] :as ens}
registrar {:keys [accounts public-key]} chain-id]]
(let [amount (case chain-id
3 50
1 10
0)
amount-label (str amount (case chain-id
3 " STT"
1 " SNT"
""))]
{:state (get-in ens [:states username-candidate])
:registering? registering?
:username username-candidate
:custom-domain? (or custom-domain? false)
:contract registrar
:address (:address (ethereum/get-default-account accounts))
:public-key public-key
:amount amount
:amount-label amount-label})))
(fn [[{:keys [custom-domain? username]}
registrar default-address public-key chain-id]]
{:address default-address
:username username
:public-key public-key
:custom-domain? custom-domain?
:contract registrar
:amount-label (ens-amount-label chain-id)}))
(re-frame/reg-sub
:ens/confirmation-screen
:<- [:ens/registration]
(fn [{:keys [username state] :as ens}]
{:state state
:username username}))
(re-frame/reg-sub
:ens.name/screen
:<- [:get-screen-params :ens-name-details]
:<- [:ens/names]
(fn [[name ens]]
(let [{:keys [address public-key]} (get-in ens [:details name])]
{:name name
:address address
:public-key public-key})))
(let [{:keys [address public-key]} (get ens name)
pending? (nil? address)]
(cond-> {:name name
:custom-domain? (not (string/ends-with? name ".stateofus.eth"))}
pending?
(assoc :pending? true)
(not pending?)
(assoc :address address
:public-key public-key)))))
(re-frame/reg-sub
:ens.main/screen

View File

@ -53,9 +53,14 @@
[{:keys [label type theme disabled? on-press accessibility-label style] :or {type :main theme :blue}}]
(let [label (utils.label/stringify label)]
[react/touchable-opacity (merge {:on-press on-press :disabled disabled? :active-pacity 0.5 :style style}
(when accessibility-label
{:accessibility-label accessibility-label}))
[react/touchable-opacity (cond-> {:on-press on-press
:active-opacity 0.5
:style style}
;;NOTE `:disabled` must be of type boolean
disabled?
(assoc :disabled (boolean disabled?))
accessibility-label
(assoc :accessibility-label accessibility-label))
[react/view {:style (style-container type disabled? theme)}
[react/view {:flex-direction :row :align-items :center}
(when (= type :previous)
@ -74,4 +79,4 @@
label]
(when (= type :next)
[vector-icons/icon :main-icons/next {:container-style {:width 24 :height 24 :margin-left 4}
:color (if disabled? colors/gray colors/blue)}])]]]))
:color (if disabled? colors/gray colors/blue)}])]]]))

View File

@ -39,10 +39,11 @@
:else [icon])])
(defn- title-row [{:keys [title title-color-override title-prefix
title-prefix-width title-prefix-height
title-accessibility-label title-row-accessory]}
type icon? disabled? theme subtitle content]
(defn- title-row
[{:keys [title title-color-override title-prefix
title-prefix-width title-prefix-height
title-accessibility-label title-row-accessory]}
type icon? disabled? theme subtitle content]
[react/view styles/title-row-container
(when title-prefix
(cond

View File

@ -178,8 +178,7 @@
:padding-left 12
:padding-right 16
:margin-right 12
:text-align-vertical :center
:color colors/gray})
:text-align-vertical :center})
(defn quoted-message-container [outgoing]
{:margin-bottom 6

View File

@ -12,6 +12,7 @@
status-im.ui.screens.profile.db
status-im.network.module
status-im.mailserver.db
status-im.ens.db
status-im.browser.db
status-im.ui.screens.add-new.db
status-im.ui.screens.add-new.new-public-chat.db
@ -313,6 +314,7 @@
:chat/bot-db
:chat/id->command
:chat/access-scope->command-id
:ens/registration
:wallet/wallet
:prices/prices
:prices/prices-loading?

View File

@ -1,48 +1,4 @@
(ns status-im.ui.screens.ens.views
"
+-------------+
| Initial |
+-----+-------+
|
| Typing
|
v
+--------------+ +----------------+
| Valid | | Invalid/reason |
+------+-------+ +-------+--------+
| |
+----------+----------+
|
| Checking
|
|
v
+------------------------------------------+
| +--------------+ +----------------+ |
| | Unregistrable| | Registrable | | +-----------------------------------+ +-------------+
| +--------------+ +----------------+ | | Connected/details | | Not owned |
| | | (none, address, public+key, all) | +-------------+
| | +----------+------------------------+
| Name available | |
+-------------------+----------------------+ |
| |
| |
| |
| Registering | Connecting
| (on-chain, 1 tx) | (on-chain, 1tx per info to connect)
| |
+-----------------------+------------------+
|
|
| Saving
|
|
+-------+-----+
| Saved |
+-------------+
"
(:require [re-frame.core :as re-frame]
[reagent.core :as reagent]
[status-im.ens.core :as ens]
@ -62,13 +18,12 @@
[status-im.ui.components.toolbar.actions :as actions]
[status-im.ui.components.toolbar.view :as toolbar]
[status-im.ui.screens.chat.message.message :as message]
[status-im.ui.screens.profile.components.views :as profile.components])
[status-im.ui.screens.profile.components.views :as profile.components]
[status-im.utils.navigation :as navigation])
(:require-macros [status-im.utils.views :as views]))
;; Components
(defn- button [{:keys [on-press] :as m} label]
(defn- button
[{:keys [on-press] :as m} label]
[components.common/button (merge {:button-style {:margin-vertical 8
:padding-horizontal 32
:justify-content :center
@ -77,67 +32,398 @@
:label label}
m)])
(defn- link [{:keys [on-press]} label]
[react/touchable-opacity {:on-press on-press :style {:justify-content :center}}
(defn- link
[{:keys [on-press]} label]
[react/touchable-opacity {:on-press on-press
:style {:justify-content :center}}
[react/text {:style {:color colors/blue}}
label]])
(defn- section [{:keys [title content]}]
[react/view {:style {:margin-horizontal 16 :align-items :flex-start}}
(defn- section
[{:keys [title content]}]
[react/view {:style {:margin-horizontal 16
:align-items :flex-start}}
[react/text {:style {:color colors/gray :font-size 15}}
title]
[react/view {:margin-top 8 :padding-horizontal 16 :padding-vertical 12 :border-width 1 :border-radius 12
[react/view {:margin-top 8
:padding-horizontal 16
:padding-vertical 12
:border-width 1
:border-radius 12
:border-color colors/gray-lighter}
[react/text {:style {:font-size 15}}
content]]])
;; Name details
(defn- domain-label
[custom-domain?]
(if custom-domain?
(i18n/label :t/ens-custom-domain)
(str "." stateofus/domain)))
(views/defview name-details []
(views/letsubs [{:keys [name address public-key]} [:ens.name/screen]]
(let [pending? (nil? address)]
(defn- domain-switch-label
[custom-domain?]
(if custom-domain?
(i18n/label :t/ens-want-domain)
(i18n/label :t/ens-want-custom-domain)))
(defn- big-blue-icon
[state]
[react/view {:style {:margin-top 68
:margin-bottom 24
:width 60
:height 60
:border-radius 30
:background-color colors/blue
:align-items :center
:justify-content :center}}
[vector-icons/icon
(case state
(:available :connected :connected-with-different-key :owned)
:main-icons/check
(:taken :error)
:main-icons/cancel
:main-icons/username)
{:color colors/white}]])
(defn- toolbar []
[toolbar/toolbar nil
[toolbar/nav-button (actions/back #(re-frame/dispatch [:navigate-back]))]
[toolbar/content-title (i18n/label :t/ens-your-username)]])
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;; SEARCH SCREEN
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(defn- icon-wrapper [color icon]
[react/view {:style {:margin-right 16
:margin-top 11
:width 32
:height 32
:border-radius 25
:align-items :center
:justify-content :center
:background-color color}}
icon])
(defn- input-icon
[state]
(case state
:searching
[icon-wrapper colors/gray
[react/activity-indicator {:color colors/white}]]
(:available :connected :connected-with-different-key :owned)
[react/touchable-highlight
{:on-press #(re-frame/dispatch [::ens/input-icon-pressed])}
[icon-wrapper colors/blue
[vector-icons/icon :main-icons/arrow-right {:color colors/white}]]]
[icon-wrapper colors/gray
[vector-icons/icon :main-icons/arrow-right {:color colors/white}]]))
(defn help-message-text-element
([label]
[react/text {:style {:flex 1
:margin-top 16
:margin-horizontal 16
:font-size 14
:text-align :center}}
(i18n/label label)])
([label second-label]
[react/nested-text {:style {:flex 1
:margin-top 16
:margin-horizontal 16
:font-size 14
:text-align :center}}
(i18n/label label) " "
[{:style {:font-weight "700"}}
(i18n/label second-label)]]))
(defn help-message
[state custom-domain?]
(case state
:already-added
[help-message-text-element :t/ens-username-already-added]
:available
[help-message-text-element :t/ens-username-available]
:owned
[help-message-text-element
:t/ens-username-owned
:t/ens-username-continue]
:connected
[help-message-text-element
:t/ens-username-connected
:t/ens-username-connected-continue]
:connected-with-different-key
[help-message-text-element
:t/ens-username-owned
:t/ens-username-connected-with-different-key]
(if custom-domain?
(case state
:too-short
[help-message-text-element :t/ens-custom-username-hints]
:invalid
[help-message-text-element :t/ens-custom-username-hints]
:taken
[help-message-text-element :t/ens-custom-username-taken]
[react/text ""])
(case state
:too-short
[help-message-text-element :t/ens-username-hints]
:invalid
[help-message-text-element :t/ens-username-invalid]
:taken
[help-message-text-element :t/ens-username-taken]
[react/text ""]))))
(defn- username-input
[username state placeholder]
(let [input-ref (atom nil)]
(fn [username state placeholder]
[react/view {:flex-direction :row :justify-content :center}
;;NOTE required so that the keyboards shows up when navigating
;;back from checkout screen
[:> navigation/navigation-events
{:on-did-focus
(fn []
(.focus @input-ref))}]
;;NOTE setting the key as placeholder forces the component to remount
;;when the placeholder changes, this prevents the placeholder from
;;disappearing when switching between stateofus and custom domain
^{:key placeholder}
[react/text-input
{:ref #(reset! input-ref %)
:on-change-text #(re-frame/dispatch [::ens/set-username-candidate %])
:on-submit-editing #(re-frame/dispatch [::ens/input-submitted])
:auto-capitalize :none
:auto-complete-type "off"
:auto-focus true
:auto-correct false
:keyboard-type :visible-password
:default-value ""
:text-align :center
:placeholder placeholder
:placeholder-text-color colors/text-gray
:style {:flex 1
:font-size 22
:padding-left 48}}]
[input-icon state]])))
(views/defview search []
(views/letsubs [{:keys [state custom-domain? username]}
[:ens/search-screen]]
[react/keyboard-avoiding-view {:flex 1}
[status-bar/status-bar {:type :main}]
[toolbar]
[react/scroll-view {:style {:flex 1}
;;NOTE required so that switching custom-domain
;;works on first tap and persists keyboard
;;instead of dismissing keyboard and requiring two taps
:keyboardShouldPersistTaps :always}
[react/view {:style {:flex 1}}
[status-bar/status-bar {:type :main}]
[toolbar/simple-toolbar
name]
[react/scroll-view {:style {:flex 1}}
[react/view {:style {:flex 1 :margin-horizontal 16}}
[react/view {:flex-direction :row :align-items :center :margin-top 20}
[react/view {:style {:margin-right 16}}
[components.common/logo
{:size 40
:icon-size 16}]]
[react/text {:style {:typography :title}}
(if pending?
(i18n/label :t/ens-transaction-pending)
(str (i18n/label :t/ens-10-SNT) ", deposit unlocked"))]]]
[react/view {:style {:margin-top 22}}
(when-not pending?
[section {:title (i18n/label :t/ens-wallet-address)
:content (ethereum/normalized-address address)}])
(when-not pending?
[react/view {:style {:margin-top 14}}
[section {:title (i18n/label :t/key)
:content public-key}]])
[react/view {:style {:margin-top 16 :margin-bottom 32}}
[list/big-list-item {:text (i18n/label :t/ens-remove-username)
:subtext (i18n/label :t/ens-remove-hints)
:text-color colors/gray
:text-style {:font-weight "500"}
:icon :main-icons/close
:icon-color colors/gray
:hide-chevron? true}]
[react/view {:style {:margin-top 18}}
[list/big-list-item {:text (i18n/label :t/ens-release-username)
:text-color colors/gray
:text-style {:font-weight "500"}
:subtext (i18n/label :t/ens-locked)
:icon :main-icons/delete
:icon-color colors/gray
:active? false
:hide-chevron? true}]]]]]])))
[react/view {:style {:flex 1
:align-items :center
:justify-content :center}}
[big-blue-icon state]
[username-input username state (if custom-domain?
"vitalik94.domain.eth"
"vitalik94")]
[react/view {:style {:height 36
:align-items :center
:justify-content :space-between
:padding-horizontal 12
:margin-top 24
:margin-horizontal 16
:border-color colors/gray-lighter
:border-radius 20
:border-width 1
:flex-direction :row}}
[react/text {:style {:font-size 13
:typography :main-medium}}
(domain-label custom-domain?)]
[react/view {:flex 1 :min-width 24}]
[react/touchable-highlight {:on-press #(re-frame/dispatch [::ens/switch-domain-type])}
[react/text {:style {:color colors/blue
:font-size 12
:typography :main-medium}
:number-of-lines 2}
(domain-switch-label custom-domain?)]]]]
[help-message state custom-domain?]]]]))
;; Terms
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;; CHECKOUT SCREEN
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(defn- agreement [checked contract]
[react/view {:flex-direction :row
:margin-left 26 ;; 10 for checkbox + 16
:margin-right 16
:margin-top 14
:align-items :flex-start
:justify-content :center}
[checkbox/checkbox {:checked? @checked
:style {:padding 0}
:on-value-change #(reset! checked %)}]
[react/view {:style {:padding-left 10}}
[react/view {:style {:flex-direction :row}}
[react/text
(i18n/label :t/ens-agree-to)]
[link {:on-press #(re-frame/dispatch [:navigate-to :ens-terms {:contract contract}])}
(i18n/label :t/ens-terms-registration)]]
[react/text
(i18n/label :t/ens-understand)]]])
(defn- registration-bottom-bar
[checked? amount-label]
[react/view {:style {:height 60
:background-color colors/white
:border-top-width 1
:border-top-color colors/gray-lighter}}
[react/view {:style {:margin-horizontal 16
:flex-direction :row
:justify-content :space-between}}
[react/view {:flex-direction :row}
[react/view {:style {:margin-top 12 :margin-right 8}}
[components.common/logo
{:size 36
:icon-size 16}]]
[react/view {:flex-direction :column :margin-vertical 8}
[react/text {:style {:font-size 15}}
amount-label]
[react/text {:style {:color colors/gray :font-size 15}}
(i18n/label :t/ens-deposit)]]]
[button {:disabled? (not @checked?)
:label-style (when (not @checked?) {:color colors/gray})
:on-press #(re-frame/dispatch [::ens/register-name-pressed])}
(i18n/label :t/ens-register)]]])
(defn- registration
[checked contract address public-key]
[react/view {:style {:flex 1 :margin-top 24}}
[section {:title (i18n/label :t/ens-wallet-address)
:content address}]
[react/view {:style {:margin-top 14}}
[section {:title (i18n/label :t/key)
:content public-key}]]
[agreement checked contract]])
(views/defview checkout []
(views/letsubs [{:keys [username address custom-domain? public-key
contract amount-label]}
[:ens/checkout-screen]]
(let [checked? (reagent/atom false)]
[react/keyboard-avoiding-view {:flex 1}
[status-bar/status-bar {:type :main}]
[toolbar]
[react/scroll-view {:style {:flex 1}}
[react/view {:style {:flex 1
:align-items :center
:justify-content :center}}
[big-blue-icon nil]
[react/text {:text-align :center
:style {:flex 1
:font-size 22
:padding-horizontal 48}}
username]
[react/view {:style {:height 36
:align-items :center
:justify-content :space-between
:padding-horizontal 12
:margin-top 24
:margin-horizontal 16
:border-color colors/gray-lighter :border-radius 20
:border-width 1
:flex-direction :row}}
[react/text {:style {:font-size 13
:typography :main-medium}}
(domain-label custom-domain?)]
[react/view {:flex 1 :min-width 24}]]]
[registration checked? contract address public-key]]
[registration-bottom-bar checked? amount-label]])))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;; CONFIRMATION SCREEN
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(defn- finalized-icon
[state]
(case state
:registration-failed
[react/view {:style {:width 40 :height 40 :border-radius 30 :background-color colors/red-light
:align-items :center :justify-content :center}}
[vector-icons/icon :main-icons/warning {:color colors/red}]]
[react/view {:style {:width 40 :height 40 :border-radius 30 :background-color colors/gray-lighter
:align-items :center :justify-content :center}}
[vector-icons/icon :main-icons/check {:color colors/blue}]]))
(defn- final-state-label
[state username]
(case state
:available
(i18n/label :t/ens-username-registration-confirmation
{:username (stateofus/subdomain username)})
:connected-with-different-key
(i18n/label :t/ens-username-connection-confirmation
{:username (stateofus/subdomain username)})
:connected
(i18n/label :t/ens-saved-title)
;;NOTE: this state can't be reached atm
:registration-failed
(i18n/label :t/ens-registration-failed-title)))
(defn- final-state-details
[state username]
(case state
:available
[react/text {:style {:color colors/gray :font-size 15 :text-align :center}}
(i18n/label :t/ens-username-you-can-follow-progress)]
:connected-with-different-key
[react/text {:style {:color colors/gray :font-size 15 :text-align :center}}
(i18n/label :t/ens-username-you-can-follow-progress)]
:connected
[react/nested-text
{:style {:font-size 15 :text-align :center}}
(stateofus/subdomain username)
[{:style {:color colors/gray}}
(i18n/label :t/ens-saved)]]
;;NOTE: this state can't be reached atm
:registration-failed
[react/text {:style {:color colors/gray :font-size 14}}
(i18n/label :t/ens-registration-failed)]))
(views/defview confirmation []
(views/letsubs [{:keys [state username]} [:ens/confirmation-screen]]
[react/keyboard-avoiding-view {:flex 1}
[status-bar/status-bar {:type :main}]
[toolbar]
[react/view {:style {:flex 1
:align-items :center
:justify-content :center}}
[finalized-icon state]
[react/text {:style {:typography :header
:margin-top 32
:margin-horizontal 32
:text-align :center}}
(final-state-label state username)]
[react/view {:align-items :center
:margin-horizontal 32
:margin-top 12
:margin-bottom 20
:justify-content :center}
[final-state-details state username]]
(if (= state :registration-failed)
[react/view
[button {:on-press #(re-frame/dispatch [::ens/retry-pressed])}
(i18n/label :t/retry)]
[button {:background? false
:on-press #(re-frame/dispatch [::ens/cancel-pressed])}
(i18n/label :t/cancel)]]
[button {:on-press #(re-frame/dispatch [::ens/got-it-pressed])}
(i18n/label :t/ens-got-it)])]]))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;; TERMS SCREEN
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(defn- term-point [content]
[react/view {:style {:flex 1 :margin-top 24 :margin-horizontal 16 :flex-direction :row}}
@ -187,273 +473,78 @@
[link {:on-press #(.openURL react/linking (etherscan-url (:mainnet ethereum.ens/ens-registries)))}
(i18n/label :t/etherscan-lookup)]]]]))
;; Registration
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;; NAME DETAILS SCREEN
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(defn- valid-domain? [state]
(#{:registrable :owned :connected} state))
(defn- main-icon [state]
(cond
(valid-domain? state) :main-icons/check
(= state :unregistrable) :main-icons/cancel
:else :main-icons/username))
(defn- icon-wrapper [color icon]
[react/view {:style {:margin-right 10 :width 32 :height 32 :border-radius 25
:align-items :center :justify-content :center :background-color color}}
icon])
(defn- input-action [{:keys [state custom-domain? username]}]
(if (= state :connected)
;; Already registered, just need to save the contact
[:ens/save-username custom-domain? username]
[:ens/set-state username :registering]))
(defn- disabled-input-action []
[icon-wrapper colors/gray
[vector-icons/icon :main-icons/arrow-right {:color colors/white}]])
(defn- input-icon [{:keys [state custom-domain? username] :as props} usernames]
(cond
(= state :registering)
nil
(= state :valid)
[icon-wrapper colors/blue
[react/activity-indicator {:color colors/white}]]
(valid-domain? state)
(let [name (ens/fullname custom-domain? username)]
(if (contains? (set usernames) name)
[disabled-input-action]
[react/touchable-highlight {:on-press #(re-frame/dispatch (input-action props))}
[icon-wrapper colors/blue
[vector-icons/icon :main-icons/arrow-right {:color colors/white}]]]))
:else
[disabled-input-action]))
(defn- default-name [custom-domain?]
(if custom-domain?
"vitalik94.domain.eth"
"vitalik94"))
(defn- domain-label [custom-domain?]
(if custom-domain?
(i18n/label :t/ens-custom-domain)
(str "." stateofus/domain)))
(defn- domain-switch-label [custom-domain?]
(if custom-domain?
(i18n/label :t/ens-want-domain)
(i18n/label :t/ens-want-custom-domain)))
(defn- help-message [state custom-domain?]
(case state
(:initial :too-short)
(if custom-domain?
(i18n/label :t/ens-custom-username-hints)
(i18n/label :t/ens-username-hints))
:invalid
(if custom-domain?
(i18n/label :t/ens-custom-username-hints)
(i18n/label :t/ens-username-invalid))
:unregistrable
(if custom-domain?
(i18n/label :t/ens-custom-username-unregistrable)
(i18n/label :t/ens-username-unregistrable))
:registrable
(i18n/label :t/ens-username-registrable)
:owned
(i18n/label :t/ens-username-owned)
:connected
(i18n/label :t/ens-username-connected)
""))
(defn- on-username-change [custom-domain? username]
(re-frame/dispatch [:ens/set-username-candidate custom-domain? username]))
(defn- on-registration [props]
(re-frame/dispatch [:ens/register props]))
(defn- agreement [{:keys [checked contract]}]
[react/view {:flex-direction :row :margin-horizontal 20 :margin-top 14 :align-items :flex-start :justify-content :center}
[checkbox/checkbox {:checked? @checked
:style {:padding 0}
:on-value-change #(reset! checked %)}]
[react/view {:style {:padding-left 10}}
[react/view {:style {:flex-direction :row}}
[react/text
(i18n/label :t/ens-agree-to)]
[link {:on-press #(re-frame/dispatch [:navigate-to :ens-terms {:contract contract}])}
(i18n/label :t/ens-terms-registration)]]
[react/text
(i18n/label :t/ens-understand)]]])
(defn- registration-bottom-bar [{:keys [checked amount-label] :as props}]
[react/view {:style {:height 60
:background-color colors/white
:border-top-width 1
:border-top-color colors/gray-lighter}}
[react/view {:style {:margin-horizontal 16
:flex-direction :row
:justify-content :space-between}}
[react/view {:flex-direction :row}
[react/view {:style {:margin-top 12 :margin-right 8}}
[components.common/logo
{:size 36
:icon-size 16}]]
[react/view {:flex-direction :column :margin-vertical 8}
[react/text {:style {:font-size 15}}
amount-label]
[react/text {:style {:color colors/gray :font-size 15}}
(i18n/label :t/ens-deposit)]]]
[button {:disabled? (not @checked)
:label-style (when (not @checked) {:color colors/gray})
:on-press #(on-registration props)}
(i18n/label :t/ens-register)]]])
(defn- registration [{:keys [address public-key] :as props}]
[react/view {:style {:flex 1 :margin-top 24}}
[section {:title (i18n/label :t/ens-wallet-address)
:content address}]
[react/view {:style {:margin-top 14}}
[section {:title (i18n/label :t/key)
:content public-key}]]
[agreement props]])
(defn- icon [{:keys [state]}]
[react/view {:style {:margin-top 68 :margin-bottom 24 :width 60 :height 60 :border-radius 30
:background-color colors/blue :align-items :center :justify-content :center}}
[vector-icons/icon (main-icon state) {:color colors/white}]])
(defn- username-input [{:keys [custom-domain? username state] :as props} usernames]
[react/view {:flex-direction :row :justify-content :center}
[react/text-input {:on-change-text #(on-username-change custom-domain? %)
:on-submit-editing #(on-registration props)
:auto-capitalize :none
:auto-correct false
:default-value username
:auto-focus true
:text-align :center
:placeholder (default-name custom-domain?)
:style {:flex 1 :font-size 22
(if (= state :registering) :padding-horizontal :padding-left) 48}}]
[input-icon props usernames]])
(defn- final-state-label [state]
(case state
:registered
(i18n/label :t/ens-registered-title)
:saved
(i18n/label :t/ens-saved-title)
:registration-failed
(i18n/label :t/ens-registration-failed-title)
""))
(defn- final-state-details [{:keys [state username]}]
(case state
:registered
[react/text {:style {:color colors/gray :font-size 14}}
(i18n/label :t/ens-registered)]
:registration-failed
[react/text {:style {:color colors/gray :font-size 14}}
(i18n/label :t/ens-registration-failed)]
:saved
[react/view {:style {:flex-direction :row :align-items :center}}
[react/nested-text
{:style {}}
(stateofus/subdomain username)
[{:style {:color colors/gray}}
(i18n/label :t/ens-saved)]]]
[react/view {:flex-direction :row :margin-left 6 :margin-top 14 :align-items :center}
[react/text
(str (i18n/label :t/ens-terms-registration) " ->")]]))
(defn- finalized-icon [{:keys [state]}]
(case state
:registration-failed
[react/view {:style {:width 40 :height 40 :border-radius 30 :background-color colors/red-light
:align-items :center :justify-content :center}}
[vector-icons/icon :main-icons/warning {:color colors/red}]]
[react/view {:style {:width 40 :height 40 :border-radius 30 :background-color colors/gray-lighter
:align-items :center :justify-content :center}}
[vector-icons/icon :main-icons/check {:color colors/blue}]]))
(defn- registration-finalized [{:keys [state username] :as props}]
[react/view {:style {:flex 1 :align-items :center :justify-content :center}}
[finalized-icon props]
[react/text {:style {:typography :header :margin-top 32 :margin-horizontal 32 :text-align :center}}
(final-state-label state)]
[react/view {:align-items :center :margin-horizontal 32 :margin-top 12 :margin-bottom 20 :justify-content :center}
[final-state-details props]]
(if (= state :registration-failed)
[react/view
[button {:on-press #(re-frame/dispatch [:ens/set-state username :registering])}
(i18n/label :t/retry)]
[button {:background? false
:on-press #(re-frame/dispatch [:ens/clear-cache-and-navigate-back])}
(i18n/label :t/cancel)]]
[button {:on-press #(re-frame/dispatch [:ens/clear-cache-and-navigate-back])}
(i18n/label :t/ens-got-it)])])
(views/defview registration-pending [{:keys [state custom-domain?] :as props} usernames]
(views/letsubs [usernames [:multiaccount/usernames]]
(views/defview name-details []
(views/letsubs [{:keys [name address custom-domain? public-key pending?]}
[:ens.name/screen]]
[react/view {:style {:flex 1}}
[status-bar/status-bar {:type :main}]
[toolbar/simple-toolbar
name]
[react/scroll-view {:style {:flex 1}}
[react/view {:style {:flex 1}}
[react/view {:style {:flex 1 :align-items :center :justify-content :center}}
[icon props]
[username-input props usernames]
[react/view {:style {:height 36 :align-items :center :justify-content :space-between :padding-horizontal 12
:margin-top 24 :margin-horizontal 16 :border-color colors/gray-lighter :border-radius 20
:border-width 1 :flex-direction :row}}
[react/text {:style {:font-size 12 :typography :main-medium}}
(domain-label custom-domain?)]
[react/view {:flex 1 :min-width 24}]
(when-not (= state :registering)
;; Domain type is not shown during registration
[react/touchable-highlight {:on-press #(re-frame/dispatch [:ens/switch-domain-type])}
[react/text {:style {:color colors/blue :font-size 12 :typography :main-medium} :number-of-lines 2}
(domain-switch-label custom-domain?)]])]]
(if (= state :registering)
[registration props]
[react/text {:style {:flex 1 :margin-top 16 :margin-horizontal 16 :font-size 14 :text-align :center}}
(help-message state custom-domain?)])]]
(when (= state :registering)
[registration-bottom-bar props])]))
(when-not custom-domain?
[react/view {:style {:flex 1 :margin-horizontal 16}}
[react/view {:flex-direction :row :align-items :center :margin-top 20}
[react/view {:style {:margin-right 16}}
[components.common/logo
{:size 40
:icon-size 16}]]
[react/text {:style {:typography :title}}
(if pending?
(i18n/label :t/ens-transaction-pending)
(str (i18n/label :t/ens-10-SNT) ", deposit unlocked"))]]])
[react/view {:style {:margin-top 22}}
(when-not pending?
[section {:title (i18n/label :t/ens-wallet-address)
:content (ethereum/normalized-address address)}])
(when-not pending?
[react/view {:style {:margin-top 14}}
[section {:title (i18n/label :t/key)
:content public-key}]])
[react/view {:style {:margin-top 16 :margin-bottom 32}}
[list/big-list-item {:text (i18n/label :t/ens-remove-username)
:subtext (i18n/label :t/ens-remove-hints)
:text-color colors/gray
:text-style {:font-weight "500"}
:icon :main-icons/close
:icon-color colors/gray
:hide-chevron? true}]
(when-not custom-domain?
[react/view {:style {:margin-top 18}}
[list/big-list-item {:text (i18n/label :t/ens-release-username)
:text-color colors/gray
:text-style {:font-weight "500"}
:subtext (i18n/label :t/ens-locked)
:icon :main-icons/delete
:icon-color colors/gray
:active? false
:hide-chevron? true}]])]]]]))
(defn- toolbar []
[toolbar/toolbar nil
[toolbar/nav-button (actions/back #(re-frame/dispatch [:ens/clear-cache-and-navigate-back]))]
[toolbar/content-title (i18n/label :t/ens-your-username)]])
(views/defview register []
(views/letsubs [{:keys [address state registering?] :as props} [:ens.registration/screen]]
(let [checked (reagent/atom false)
props (merge props {:checked checked :address (ethereum/normalized-address address)})]
[react/keyboard-avoiding-view {:flex 1}
[status-bar/status-bar {:type :main}]
[toolbar]
;; NOTE: this view is used both for finalized and pending registration
;; and when the registration data is cleared for a brief moment state
;; is nil and registration-pending show which triggers the keyboard
;; and it's ugly
;; TODO: something less crazy with proper separated views and routes
(if registering?
[registration-pending props]
[registration-finalized props])])))
;; Welcome
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;; WELCOME SCREEN
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(defn- welcome-item [{:keys [icon-label title]} content]
[react/view {:style {:flex 1 :margin-top 24 :margin-left 16 :flex-direction :row}}
[react/view {:style {:height 40 :width 40 :border-radius 25 :border-width 1 :border-color colors/gray-lighter
:align-items :center :justify-content :center}}
[react/view {:style {:flex 1
:margin-top 24
:margin-left 16
:flex-direction :row}}
[react/view {:style {:height 40
:width 40
:border-radius 25
:border-width 1
:border-color colors/gray-lighter
:align-items :center
:justify-content :center}}
[react/text {:style {:typography :title}}
icon-label]]
[react/view {:style {:flex 1 :margin-horizontal 16}}
[react/text {:style {:font-size 15 :typography :main-semibold}}
[react/view {:style {:flex 1
:margin-horizontal 16}}
[react/text {:style {:font-size 15
:typography :main-semibold}}
title]
content]])
@ -512,7 +603,7 @@
[react/scroll-view {:style {:flex 1}}
[react/view {:style {:flex 1}}
(for [name names]
(let [action #(do (re-frame/dispatch [:ens/save-preferred-name name])
(let [action #(do (re-frame/dispatch [::ens/save-preferred-name name])
(re-frame/dispatch [:bottom-sheet/hide-sheet]))]
^{:key name}
[react/touchable-highlight {:on-press action}
@ -535,23 +626,34 @@
[react/view {:style {:margin-top 8}}
(for [name names]
^{:key name}
[name-item {:name name :action #(re-frame/dispatch [:ens/navigate-to-name name])}])]
[name-item {:name name :action #(re-frame/dispatch [::ens/navigate-to-name name])}])]
[react/text {:style {:color colors/gray :font-size 15}}
(i18n/label :t/ens-no-usernames)])]
[react/view {:style {:padding-top 22 :border-color colors/gray-lighter :border-top-width 1}}
[react/text {:style {:color colors/gray :margin-horizontal 16}}
(i18n/label :t/ens-chat-settings)]
(when (> (count names) 1)
[profile.components/settings-item {:label-kw :ens-primary-username
:value preferred-name
:action-fn #(re-frame/dispatch [:bottom-sheet/show-sheet
{:content (fn [] (name-list names preferred-name))
:content-height (+ 72 (* (min 4 (count names)) 64))}])}])
[profile.components/settings-switch-item {:label-kw :ens-show-username
:action-fn #(re-frame/dispatch [:ens/switch-show-username])
:value show?}]]
(let [message (merge {:from public-key :last-in-group? true :display-username? true :display-photo? true :username name
:content {:text (i18n/label :t/ens-test-message)} :content-type "text/plain" :timestamp-str "9:41 AM"}
[profile.components/settings-item
{:label-kw :ens-primary-username
:value preferred-name
:action-fn #(re-frame/dispatch [:bottom-sheet/show-sheet
{:content
(fn [] (name-list names preferred-name))
:content-height
(+ 72 (* (min 4 (count names)) 64))}])}])
[profile.components/settings-switch-item
{:label-kw :ens-show-username
:action-fn #(re-frame/dispatch [::ens/switch-show-username])
:value show?}]]
(let [message (merge {:from public-key
:last-in-group? true
:display-username? true
:display-photo? true
:alias name
:content {:text (i18n/label :t/ens-test-message)
:name (when show? preferred-name)}
:content-type "text/plain"
:timestamp-str "9:41 AM"}
(when show?
{:name preferred-name}))]
[message/message-body message

View File

@ -107,26 +107,26 @@
[:tribute-to-talk.ui/menu-item-pressed])}
opts)])
(defn- flat-list-content [preferred-name registrar tribute-to-talk
active-contacts-count show-backup-seed?
keycard-account?]
[(cond-> {:title (or preferred-name :t/ens-usernames)
:subtitle (if (boolean registrar)
(defn- flat-list-content
[preferred-name registrar tribute-to-talk
active-contacts-count show-backup-seed?
keycard-account?]
[(cond-> {:title (or (when registrar preferred-name)
:t/ens-usernames)
:subtitle (if registrar
(if preferred-name
:t/ens-your-your-name
:t/ens-usernames-details)
:t/ens-network-restriction)
:subtitle-max-lines (if (boolean registrar)
:subtitle-max-lines (if registrar
(if preferred-name 1 2)
1)
:accessibility-label :ens-button
:container-margin-top 8
:disabled? (if (boolean registrar)
false
((complement boolean) preferred-name))
:disabled? (not registrar)
:accessories [:chevron]
:icon :main-icons/username}
(or (boolean registrar) preferred-name)
registrar
(assoc :on-press #(re-frame/dispatch [:navigate-to :ens-main registrar])))
;; TODO replace this with list-item config map
;; left it as it is because not sure how to enable it for testing

View File

@ -6,7 +6,9 @@
:screens (cond-> [:my-profile
:contacts-list
:ens-main
:ens-register
:ens-search
:ens-checkout
:ens-confirmation
:ens-terms
:ens-name-details
:blocked-users-list

View File

@ -161,7 +161,9 @@
:my-profile profile.user/my-profile
:contacts-list contacts-list/contacts-list
:ens-main ens/main
:ens-register ens/register
:ens-search ens/search
:ens-checkout ens/checkout
:ens-confirmation ens/confirmation
:ens-terms ens/terms
:ens-name-details ens/name-details
:blocked-users-list contacts-list/blocked-users-list

View File

@ -193,24 +193,25 @@ class TestProfileSingleDevice(SingleDeviceTestCase):
def test_can_add_existing_ens(self):
sign_in = SignInView(self.driver)
home = sign_in.recover_access(ens_user['passphrase'])
home.just_fyi('switching to Mainnet')
profile = home.profile_button.click()
profile.switch_network('Mainnet with upstream RPC')
home.profile_button.click()
profile.element_by_text('ENS usernames').click()
dapp_view = DappsView(self.driver)
# check if your name can be added via "ENS usernames" dapp in Profile
dapp_view.just_fyi('check if your name can be added via "ENS usernames" in Profile')
dapp_view.element_by_text('Get started').click()
dapp_view.ens_name.set_value(ens_user['ens'])
if not dapp_view.element_by_text_part('is owned by you').is_element_displayed():
self.errors.append('Owned username is not shown in ENS Dapp.')
dapp_view.check_ens_name.click()
dapp_view.check_ens_name.click()
if not dapp_view.element_by_text_part('Username added').is_element_displayed():
self.errors.append('No message "Username added" after resolving own username')
dapp_view.element_by_text('Ok, got it').click()
# check that after adding username is shown in "ENS usernames" and profile
dapp_view.just_fyi('check that after adding username is shown in "ENS usernames" and profile')
if not dapp_view.element_by_text(ens_user['ens']).is_element_displayed():
self.errors.append('No ENS name is shown in own "ENS usernames" after adding')
dapp_view.back_button.click()
@ -707,6 +708,7 @@ class TestProfileMultipleDevice(MultipleDeviceTestCase):
home_1 = sign_in_1.recover_access(user_1['passphrase'])
home_2 = sign_in_2.create_user()
home_1.just_fyi('switching to mainnet and add ENS')
profile_1 = sign_in_1.profile_button.click()
profile_1.switch_network('Mainnet with upstream RPC')
home_1.profile_button.click()
@ -715,11 +717,11 @@ class TestProfileMultipleDevice(MultipleDeviceTestCase):
dapp_view_1.element_by_text('Get started').click()
dapp_view_1.ens_name.set_value(ens_user['ens'])
dapp_view_1.check_ens_name.click()
dapp_view_1.check_ens_name.click()
dapp_view_1.element_by_text('Ok, got it').click()
dapp_view_1.back_button.click()
profile_1.home_button.click()
home_2.just_fyi('joining same public chat, checking default username on message')
chat_name = home_1.get_public_chat_name()
chat_2 = home_2.join_public_chat(chat_name)
chat_1 = home_1.join_public_chat(chat_name)
@ -729,6 +731,7 @@ class TestProfileMultipleDevice(MultipleDeviceTestCase):
self.errors.append('Default username is not shown in public chat')
chat_2.send_message('message from device 2')
home_1.just_fyi('set ENS name for public chat and check it from device2')
chat_1.get_back_to_home_view()
home_1.profile_button.click()
profile_1.element_by_text('Your ENS name').click()

View File

@ -373,7 +373,7 @@
:ens-chat-settings
:ens-custom-domain
:ens-custom-username-hints
:ens-custom-username-unregistrable
:ens-custom-username-taken
:ens-deposit
:ens-displayed-with
:ens-get-name
@ -384,8 +384,6 @@
:ens-powered-by
:ens-primary-username
:ens-register
:ens-registered
:ens-registered-title
:ens-registration-failed
:ens-registration-failed-title
:ens-release-username
@ -414,8 +412,8 @@
:ens-username-hints
:ens-username-invalid
:ens-username-owned
:ens-username-registrable
:ens-username-unregistrable
:ens-username-available
:ens-username-taken
:ens-usernames
:ens-usernames-details
:ens-wallet-address

View File

@ -349,19 +349,17 @@
"ens-chat-settings": "Chat settings",
"ens-custom-domain": "Custom domain",
"ens-custom-username-hints": "Type the entire username including the custom domain like username.domain.eth",
"ens-custom-username-unregistrable": "Username doesnt belong to you :(",
"ens-custom-username-taken": "Username doesnt belong to you :(",
"ens-deposit": "Deposit",
"ens-displayed-with": "Your messages are displayed to others with",
"ens-get-name": "Get a universal username",
"ens-got-it": "Ok, got it",
"ens-locked": "Username locked. You wont be able to release it until 25.05.2020",
"ens-network-restriction": "Only available on Mainnet or ropsten",
"ens-network-restriction": "Only available on Mainnet",
"ens-no-usernames": "You don't have any username connected",
"ens-powered-by": "Powered by Ethereum Name Services",
"ens-primary-username": "Primary username",
"ens-register": "Register",
"ens-registered": "You can follow the progress in the Transaction History section of your wallet.",
"ens-registered-title": "Nice!\nThe name is yours once the transaction is complete.",
"ens-registration-failed": "To register the username, please try again.",
"ens-registration-failed-title": "Transaction failed",
"ens-release-username": "Release username",
@ -386,12 +384,14 @@
"ens-transaction-pending": "Transaction pending...",
"ens-understand": "I understand that my wallet address will be publicly connected to my username.",
"ens-username": "ENS username",
"ens-username-available": "✓ Username available!",
"ens-username-connected": "This user name is owned by you and connected with your Chat key.",
"ens-username-connection-confirmation": "{{username}} will be connected once the transaction is complete.",
"ens-username-hints": "At least 4 characters. Latin letters, numbers, and lowercase only.",
"ens-username-invalid": "Letters and numbers only",
"ens-username-invalid": "Letters and numbers only.",
"ens-username-owned": "✓ Username is owned by you. ",
"ens-username-registrable": "✓ Username available!",
"ens-username-unregistrable": "Username already taken:(",
"ens-username-registration-confirmation": "Nice! You own {{username}} once the transaction is complete.",
"ens-username-you-can-follow-progress": "You can follow the progress in the Transaction History section of your wallet.",
"ens-usernames": "ENS usernames",
"ens-usernames-details": "Register a universal username to be easily recognized by other users",
"ens-wallet-address": "Wallet address",
@ -409,6 +409,11 @@
"ens-your-username": "Your username",
"ens-your-usernames": "Your usernames",
"ens-your-your-name": "Your ENS name",
"ens-username-already-added": "Username is already connected with your key and can be used inside Status.",
"ens-username-connected-continue": "Continue to set `Show my ENS username in chats`.",
"ens-username-connected-with-different-key": "Continuing will require a transaction to connect the username with your current key.",
"ens-username-owned-continue": "Continuing will connect this username with your key.",
"ens-username-taken": "Username already taken :(",
"enter-12-words": "Enter the 12 words of your seed phrase, separated by single spaces",
"enter-contact-code": "Enter chat key or username",
"enter-ens-or-contact-code": "Enter ENS username or chat key",

View File

@ -340,7 +340,7 @@
"ens-chat-settings": "Paramètres de Chat",
"ens-custom-domain": "Domaine personnalisé",
"ens-custom-username-hints": "Tapez le nom d'utilisateur complet, y compris le domaine personnalisé comme nom d'utilisateur.domaine.eth.eth",
"ens-custom-username-unregistrable": "Le nom d'utilisateur ne vous appartient pas :(",
"ens-custom-username-taken": "Le nom d'utilisateur ne vous appartient pas :(",
"ens-deposit": "Dépôt",
"ens-displayed-with": "Vos messages sont affichés aux autres avec",
"ens-get-name": "Obtenir un nom d'utilisateur universel",
@ -351,8 +351,6 @@
"ens-powered-by": "Propulsé par Ethereum Name Services",
"ens-primary-username": "Nom d'utilisateur principal",
"ens-register": "S'inscrire",
"ens-registered": "Vous pouvez suivre l'évolution dans la section Historique des transactions de votre portefeuille.",
"ens-registered-title": "Joli !\nLe nom est le vôtre une fois la transaction terminée.",
"ens-registration-failed": "Pour enregistrer le nom d'utilisateur, veuillez réessayer.",
"ens-registration-failed-title": "La transaction a échoué",
"ens-release-username": "Nom d'utilisateur libre",
@ -380,8 +378,8 @@
"ens-username-hints": "Au moins 4 caractères. Lettres latines, chiffres et minuscules seulement.",
"ens-username-invalid": "Lettres et chiffres uniquement",
"ens-username-owned": "✓ Le nom d'utilisateur vous appartient.",
"ens-username-registrable": "✓ Nom d'utilisateur disponible!",
"ens-username-unregistrable": "Nom d'utilisateur déjà pris :(",
"ens-username-available": "✓ Nom d'utilisateur disponible!",
"ens-username-taken": "Nom d'utilisateur déjà pris :(",
"ens-usernames": "Noms d'utilisateurs ENS",
"ens-usernames-details": "Enregistrer un nom d'utilisateur universel pour être facilement reconnu par les autres utilisateurs",
"ens-wallet-address": "Adresse portefeuille",

View File

@ -337,7 +337,7 @@
"ens-chat-settings": "チャット設定",
"ens-custom-domain": "カスタムドメイン",
"ens-custom-username-hints": "userame.domain.ethのようにカスタムドメインを含むユーザー名全体を入力してください",
"ens-custom-username-unregistrable": "このユーザー名はあなたのものではありません :(",
"ens-custom-username-taken": "このユーザー名はあなたのものではありません :(",
"ens-deposit": "デポジット",
"ens-displayed-with": "あなたのメッセージは他の人に表示されます",
"ens-get-name": "ユニバーサルユーザー名を取得",
@ -348,8 +348,6 @@
"ens-powered-by": "Ethereum Name Servicesを利用しています",
"ens-primary-username": "プライマリーユーザー名",
"ens-register": "登録",
"ens-registered": "ウォレットの取引履歴セクションで進行状況を追うことができます。",
"ens-registered-title": "いいね! \n取引が完了したらユーザー名はあなたのものです。",
"ens-registration-failed": "ユーザー名を登録するには、もう1度やり直してください。",
"ens-registration-failed-title": "トランザクション処理が失敗しました",
"ens-release-username": "ユーザー名を手放す",
@ -378,8 +376,8 @@
"ens-username-hints": "少なくとも4文字のラテン文字、数字、および小文字のみ",
"ens-username-invalid": "文字と数字のみ",
"ens-username-owned": "✓このユーザー名はあなたが所有しています。 ",
"ens-username-registrable": "✓このユーザー名は利用可能です!",
"ens-username-unregistrable": "ユーザー名はすでに使用されています :(",
"ens-username-available": "✓このユーザー名は利用可能です!",
"ens-username-taken": "ユーザー名はすでに使用されています :(",
"ens-usernames": "ENSユーザー名",
"ens-usernames-details": "他のユーザーが簡単に認識できるユニバーサルユーザー名を登録する",
"ens-wallet-address": "ウォレットアドレス",

View File

@ -348,7 +348,7 @@
"ens-chat-settings": "채팅 설정",
"ens-custom-domain": "커스텀 도메인",
"ens-custom-username-hints": "username.domain.eth와 같은 커스텀 도메인 형식의 사용자 이름을 입력해주세요",
"ens-custom-username-unregistrable": "사용자 이름을 등록할 수 없습니다 :(",
"ens-custom-username-taken": "사용자 이름을 등록할 수 없습니다 :(",
"ens-deposit": "입금",
"ens-displayed-with": "귀하의 메시지가 다른 사용자에게 다음과 같이 표시됩니다",
"ens-get-name": "유니버셜 사용자 이름 등록",
@ -359,8 +359,6 @@
"ens-powered-by": "Powered by Ethereum Name Services",
"ens-primary-username": "주 사용자 이름",
"ens-register": "등록하기",
"ens-registered": "지갑의 거래내역 섹션에서 진행 상황을 확인할 수 있습니다.",
"ens-registered-title": "좋습니다! \n거래가 완료되면 이름이 등록됩니다.",
"ens-registration-failed": "사용자 이름을 등록하려면, 다시 시도해주세요",
"ens-registration-failed-title": "거래 실패",
"ens-release-username": "사용자 이름 계약 해지",
@ -389,8 +387,8 @@
"ens-username-hints": "최소 4자 이상이어야 하며, 알파벳 소문자 및 숫자만 가능합니다.",
"ens-username-invalid": "문자와 숫자만 가능",
"ens-username-owned": "✓ 이미 소유한 이름입니다.",
"ens-username-registrable": "✓ 사용자 이름을 사용할 수 있습니다!",
"ens-username-unregistrable": "이미 사용 중인 이름입니다 :(",
"ens-username-available": "✓ 사용자 이름을 사용할 수 있습니다!",
"ens-username-taken": "이미 사용 중인 이름입니다 :(",
"ens-usernames": "ENS 사용자 이름",
"ens-usernames-details": "검색 가능한 아이디 등록",
"ens-wallet-address": "지갑 주소",
@ -1090,4 +1088,4 @@
"your-data-belongs-to-you-description": "스테이터스는 사용자가 시드 시드 구문을 잃어버릴 경우, 계정을 복구하는 것을 도와드릴 수 없습니다. 데이터 보안을 위해 시드 구문을 따로 백업해 두시는 것을 권장합니다.",
"your-recovery-phrase": "시드 구문",
"your-recovery-phrase-description": "위 12단어가 사용자의 시드 구문입니다. 이 구문은 사용자의 지갑을 증명하기 위해 반드시 필요하며, 이번 한번만 확인할 수 있습니다. 지갑을 분실하거나 재설치하는 경우 반드시 필요하므로 안전한 장소에 보관하세요."
}
}

View File

@ -365,7 +365,7 @@
"ens-chat-settings": "Настройки чата",
"ens-custom-domain": "Пользовательский домен",
"ens-custom-username-hints": "Введите полное имя пользователя, включая пользовательский домен, например username.domain.eth",
"ens-custom-username-unregistrable": "Имя пользователя не принадлежит вам :(",
"ens-custom-username-taken": "Имя пользователя не принадлежит вам :(",
"ens-deposit": "Депозит",
"ens-displayed-with": "Ваши сообщения остальным пользователям показываются с ",
"ens-get-name": "Получить универсальное имя пользователя",
@ -376,8 +376,6 @@
"ens-powered-by": "При поддержке Ethereum Name Services",
"ens-primary-username": "Основное имя пользователя",
"ens-register": "Зарегистрировать",
"ens-registered": "Вы можете следить за ходом выполнения в разделе \"История транзакций\" вашего кошелька.",
"ens-registered-title": "Отлично!\nИмя будет вашим, как только транзакция будет завершена.",
"ens-registration-failed": "Чтобы зарегистрировать имя пользователя, пожалуйста, попробуйте еще раз.",
"ens-registration-failed-title": "Не удалось выполнить транзакцию",
"ens-release-username": "Освободить имя пользователя",
@ -406,8 +404,8 @@
"ens-username-hints": "Как минимум 4 символа. Только латинские буквы, цифры и строчные буквы",
"ens-username-invalid": "Только буквы и цифры",
"ens-username-owned": "✓ Имя пользователя принадлежит вам.",
"ens-username-registrable": "✓ Имя пользователя доступно!",
"ens-username-unregistrable": "Имя пользователя уже занято:(",
"ens-username-available": "✓ Имя пользователя доступно!",
"ens-username-taken": "Имя пользователя уже занято:(",
"ens-usernames": "ENS имена пользователей",
"ens-usernames-details": "Зарегистрируйте универсальное имя пользователя, чтобы его могли легко узнать другие пользователи.",
"ens-wallet-address": "Адрес кошелька",

View File

@ -347,7 +347,7 @@
"ens-chat-settings": "聊天设置",
"ens-custom-domain": "自定义域名",
"ens-custom-username-hints": "输入完整用户名包括自定义域例如username.domain.eth",
"ens-custom-username-unregistrable": "用户名不属于您:(",
"ens-custom-username-taken": "用户名不属于您:(",
"ens-deposit": "充值",
"ens-displayed-with": "您的消息会显示给其他人",
"ens-get-name": "获取通用用户名",
@ -358,8 +358,6 @@
"ens-powered-by": "由以太坊名称服务提供支持",
"ens-primary-username": "主用户名",
"ens-register": "注册",
"ens-registered": "您可以在钱包的\"交易记录\"部分关注进度。",
"ens-registered-title": "干得漂亮!\n交易完成后,该名称归您所有。",
"ens-registration-failed": "要注册用户名,请重试。",
"ens-registration-failed-title": "交易失败",
"ens-release-username": "释放用户名",
@ -388,8 +386,8 @@
"ens-username-hints": "至少 4 个字符。仅限拉丁字母、数字和小写字母。",
"ens-username-invalid": "只限字母和数字",
"ens-username-owned": "✓用户名归您所有。",
"ens-username-registrable": "✓用户名可用!",
"ens-username-unregistrable": "用户名已被占用:(",
"ens-username-available": "✓用户名可用!",
"ens-username-taken": "用户名已被占用:(",
"ens-usernames": "ENS用户名",
"ens-usernames-details": "注册通用用户名,以便其他用户轻松找到你",
"ens-wallet-address": "钱包地址",

View File

@ -318,7 +318,7 @@
"ens-chat-settings": "聊天设置",
"ens-custom-domain": "自定义域名",
"ens-custom-username-hints": "输入完整用户名包括自定义域例如username.domain.eth",
"ens-custom-username-unregistrable": "用户名不属于您:(",
"ens-custom-username-taken": "用户名不属于您:(",
"ens-deposit": "充值",
"ens-displayed-with": "您的消息会显示给其他人",
"ens-get-name": "获取通用用户名",
@ -329,8 +329,6 @@
"ens-powered-by": "由以太坊名称服务提供支持",
"ens-primary-username": "主用户名",
"ens-register": "注册",
"ens-registered": "您可以在钱包的\"交易记录\"部分关注进度。",
"ens-registered-title": "干得漂亮!\n交易完成后,该名称归您所有。",
"ens-registration-failed": "要注册用户名,请重试。",
"ens-registration-failed-title": "交易失败",
"ens-release-username": "释放用户名",
@ -358,8 +356,8 @@
"ens-username-hints": "至少 4 个字符。仅限拉丁字母、数字和小写字母。",
"ens-username-invalid": "只限字母和数字",
"ens-username-owned": "✓用户名归您所有。",
"ens-username-registrable": "✓用户名可用!",
"ens-username-unregistrable": "用户名已被占用:(",
"ens-username-available": "✓用户名可用!",
"ens-username-taken": "用户名已被占用:(",
"ens-usernames": "ENS用户名",
"ens-usernames-details": "注册通用用户名,以便其他用户轻松找到你",
"ens-wallet-address": "钱包地址",