[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 (ns status-im.ens.core
(:require [clojure.string :as string] (:require [clojure.string :as string]
[re-frame.core :as re-frame] [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.abi-spec :as abi-spec]
[status-im.ethereum.contracts :as contracts] [status-im.ethereum.contracts :as contracts]
[status-im.ethereum.core :as ethereum] [status-im.ethereum.core :as ethereum]
[status-im.ethereum.eip55 :as eip55]
[status-im.ethereum.ens :as ens] [status-im.ethereum.ens :as ens]
[status-im.ethereum.resolver :as resolver] [status-im.ethereum.resolver :as resolver]
[status-im.ethereum.stateofus :as stateofus] [status-im.ethereum.stateofus :as stateofus]
@ -12,7 +13,8 @@
[status-im.utils.fx :as fx] [status-im.utils.fx :as fx]
[status-im.utils.money :as money] [status-im.utils.money :as money]
[status-im.signing.core :as signing] [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])) (:refer-clojure :exclude [name]))
(defn fullname [custom-domain? username] (defn fullname [custom-domain? username]
@ -21,51 +23,116 @@
(stateofus/subdomain username))) (stateofus/subdomain username)))
(re-frame/reg-fx (re-frame/reg-fx
:ens/resolve-address ::resolve-address
(fn [[registry name cb]] (fn [[registry name cb]]
(ens/get-addr registry name cb))) (ens/get-addr registry name cb)))
(re-frame/reg-fx (re-frame/reg-fx
:ens/resolve-pubkey ::resolve-pubkey
(fn [[registry name cb]] (fn [[registry name cb]]
(resolver/pubkey 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 (fx/defn set-state
{:events [:ens/set-state]} {:events [::name-resolved]}
[{:keys [db]} username state] [{: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 (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) (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 ;; No address for a stateofus subdomain: it can be registered
(re-frame/dispatch [:ens/set-state username :registrable]) (and (nil? response) (not custom-domain?))
(re-frame/dispatch [::name-resolved username :available])
:else :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 (fx/defn register-name
{:events [:ens/register]} {:events [::register-name-pressed]}
[{:keys [db] :as cofx} {:keys [amount contract custom-domain? username address public-key]}] [{:keys [db] :as cofx}]
(let [{:keys [x y]} (ethereum/coordinates public-key)] (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 (signing/eth-transaction-call
cofx cofx
{:contract (contracts/get-address db :status/snt) {:contract (contracts/get-address db :status/snt)
@ -74,8 +141,8 @@
(money/unit->token amount 18) (money/unit->token amount 18)
(abi-spec/encode "register(bytes32,address,bytes32,bytes32)" (abi-spec/encode "register(bytes32,address,bytes32,bytes32)"
[(ethereum/sha3 username) address x y])] [(ethereum/sha3 username) address x y])]
:on-result [:ens/save-username custom-domain? username] :on-result [::save-username custom-domain? username]
:on-error [:ens/on-registration-failure]}))) :on-error [::on-registration-failure]})))
(defn- valid-custom-domain? [username] (defn- valid-custom-domain? [username]
(and (ens/is-valid-eth-name? username) (and (ens/is-valid-eth-name? username)
@ -86,63 +153,65 @@
(valid-custom-domain? username) (valid-custom-domain? username)
(stateofus/valid-username? username))) (stateofus/valid-username? username)))
(defn- state [custom-domain? username] (defn- state [custom-domain? username usernames]
(cond (cond
(string/blank? username) :initial (or (string/blank? username)
(> 4 (count username)) :too-short (> 4 (count username))) :too-short
(valid-username? custom-domain? username) :valid (valid-username? custom-domain? username)
(if (usernames (fullname custom-domain? username))
:already-added
:searching)
:else :invalid)) :else :invalid))
(fx/defn set-username-candidate (fx/defn set-username-candidate
{:events [:ens/set-username-candidate]} {:events [::set-username-candidate]}
[{:keys [db]} custom-domain? username] [{:keys [db]} username]
(let [state (state custom-domain? username) (let [{:keys [custom-domain?]} (:ens/registration db)
valid? (valid-username? custom-domain? username) usernames (into #{} (get-in db [:multiaccount :usernames]))
name (fullname custom-domain? username)] state (state custom-domain? username usernames)]
(merge (merge
{:db (-> db {:db (update db :ens/registration assoc
(assoc-username-candidate username) :username username
(assoc-state-for username state))} :state state)}
(when (and name (= :valid state)) (when (= state :searching)
(let [{:keys [multiaccount]} db (let [{:keys [multiaccount]} db
{:keys [public-key]} multiaccount {:keys [public-key]} multiaccount
address (ethereum/default-address db) address (ethereum/default-address db)
registry (get ens/ens-registries (ethereum/chain-keyword 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 (fx/defn return-to-ens-main-screen
{:events [:ens/clear-cache-and-navigate-back]} {:events [::got-it-pressed ::cancel-pressed]}
[{:keys [db] :as cofx} _] [{:keys [db] :as cofx} _]
(fx/merge cofx (fx/merge cofx
{:db (assoc db :ens/registration nil)} ;; Clear cache ;; clear registration data
(navigation/navigate-back))) {: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 (fx/defn switch-domain-type
{:events [:ens/switch-domain-type]} {:events [::switch-domain-type]}
[{:keys [db]} _] [{:keys [db] :as cofx} _]
{:db (-> (update-in db [:ens/registration :custom-domain?] not) (fx/merge cofx
(empty-username-candidate))}) {:db (-> db
(update :ens/registration dissoc :username :state)
(update-in [:ens/registration :custom-domain?] not))}))
(fx/defn save-preferred-name (fx/defn save-preferred-name
{:events [:ens/save-preferred-name]} {:events [::save-preferred-name]}
[{:keys [db] :as cofx} name] [{:keys [db] :as cofx} name]
(multiaccounts.update/multiaccount-update cofx (multiaccounts.update/multiaccount-update cofx
{:preferred-name name} {: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 (fx/defn switch-show-username
{:events [:ens/switch-show-username]} {:events [::switch-show-username]}
[{:keys [db] :as cofx} _] [{:keys [db] :as cofx} _]
(let [show-name? (not (get-in db [:multiaccount :show-name?]))] (let [show-name? (not (get-in db [:multiaccount :show-name?]))]
(multiaccounts.update/multiaccount-update cofx (multiaccounts.update/multiaccount-update cofx
@ -153,26 +222,33 @@
"TODO not sure there is actually anything to do here "TODO not sure there is actually anything to do here
it should only be called if the user cancels the signing it should only be called if the user cancels the signing
Actual registration failure has not been implemented properly" Actual registration failure has not been implemented properly"
{:events [:ens/on-registration-failure]} {:events [::on-registration-failure]}
[{:keys [db]} username]) [{:keys [db]} username])
(fx/defn store-name-detail (fx/defn store-name-address
{:events [:ens/store-name-detail]} {:events [::address-resolved]}
[{:keys [db]} name k v] [{:keys [db]} username address]
{:db (assoc-details-for db name k v)}) {: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 (fx/defn navigate-to-name
{:events [:ens/navigate-to-name]} {:events [::navigate-to-name]}
[{:keys [db] :as cofx} name] [{:keys [db] :as cofx} username]
(let [registry (get ens/ens-registries (ethereum/chain-keyword db))] (let [registry (get ens/ens-registries (ethereum/chain-keyword db))]
(fx/merge cofx (fx/merge cofx
{:ens/resolve-address [registry name #(re-frame/dispatch [:ens/store-name-detail name :address %])] {::resolve-address [registry username
:ens/resolve-pubkey [registry name #(re-frame/dispatch [:ens/store-name-detail name :public-key %])]} #(re-frame/dispatch [::address-resolved username %])]
(navigation/navigate-to-cofx :ens-name-details name)))) ::resolve-pubkey [registry username
#(re-frame/dispatch [::public-key-resolved username %])]}
(navigation/navigate-to-cofx :ens-name-details username))))
(fx/defn start-registration (fx/defn start-registration
{:events [::add-username-pressed ::get-started-pressed]} {:events [::add-username-pressed ::get-started-pressed]}
[{:keys [db] :as cofx}] [{:keys [db] :as cofx}]
(fx/merge cofx (fx/merge cofx
{:db (assoc-in db [:ens/registration :registering?] true)} (set-username-candidate (get-in db [:ens/registration :username] ""))
(navigation/navigate-to-cofx :ens-register {}))) (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]}] [{:networks/keys [current-network networks]}]
(network->chain-keyword (get networks current-network))) (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] (defn snt-symbol [db]
(case (chain-keyword db) (case (chain-keyword db)
:mainnet :SNT :mainnet :SNT

View File

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

View File

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

View File

@ -12,7 +12,9 @@
(def registrars (def registrars
{:mainnet "0xDB5ac1a559b02E12F29fC0eC0e37Be8E046DEF49" {:mainnet "0xDB5ac1a559b02E12F29fC0eC0e37Be8E046DEF49"
:testnet "0x11d9F481effd20D76cEE832559bd9Aca25405841"}) ;;NOTE: can be enabled for testing builds
;;:testnet "0x11d9F481effd20D76cEE832559bd9Aca25405841"
})
(defn lower-case? [s] (defn lower-case? [s]
(when s (when s

View File

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

View File

@ -53,9 +53,14 @@
[{:keys [label type theme disabled? on-press accessibility-label style] :or {type :main theme :blue}}] [{:keys [label type theme disabled? on-press accessibility-label style] :or {type :main theme :blue}}]
(let [label (utils.label/stringify label)] (let [label (utils.label/stringify label)]
[react/touchable-opacity (merge {:on-press on-press :disabled disabled? :active-pacity 0.5 :style style} [react/touchable-opacity (cond-> {:on-press on-press
(when accessibility-label :active-opacity 0.5
{:accessibility-label accessibility-label})) :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 {:style (style-container type disabled? theme)}
[react/view {:flex-direction :row :align-items :center} [react/view {:flex-direction :row :align-items :center}
(when (= type :previous) (when (= type :previous)

View File

@ -39,7 +39,8 @@
:else [icon])]) :else [icon])])
(defn- title-row [{:keys [title title-color-override title-prefix (defn- title-row
[{:keys [title title-color-override title-prefix
title-prefix-width title-prefix-height title-prefix-width title-prefix-height
title-accessibility-label title-row-accessory]} title-accessibility-label title-row-accessory]}
type icon? disabled? theme subtitle content] type icon? disabled? theme subtitle content]

View File

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

View File

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

View File

@ -1,48 +1,4 @@
(ns status-im.ui.screens.ens.views (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] (:require [re-frame.core :as re-frame]
[reagent.core :as reagent] [reagent.core :as reagent]
[status-im.ens.core :as ens] [status-im.ens.core :as ens]
@ -62,13 +18,12 @@
[status-im.ui.components.toolbar.actions :as actions] [status-im.ui.components.toolbar.actions :as actions]
[status-im.ui.components.toolbar.view :as toolbar] [status-im.ui.components.toolbar.view :as toolbar]
[status-im.ui.screens.chat.message.message :as message] [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])) (: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 [components.common/button (merge {:button-style {:margin-vertical 8
:padding-horizontal 32 :padding-horizontal 32
:justify-content :center :justify-content :center
@ -77,67 +32,398 @@
:label label} :label label}
m)]) m)])
(defn- link [{:keys [on-press]} label] (defn- link
[react/touchable-opacity {:on-press on-press :style {:justify-content :center}} [{:keys [on-press]} label]
[react/touchable-opacity {:on-press on-press
:style {:justify-content :center}}
[react/text {:style {:color colors/blue}} [react/text {:style {:color colors/blue}}
label]]) label]])
(defn- section [{:keys [title content]}] (defn- section
[react/view {:style {:margin-horizontal 16 :align-items :flex-start}} [{:keys [title content]}]
[react/view {:style {:margin-horizontal 16
:align-items :flex-start}}
[react/text {:style {:color colors/gray :font-size 15}} [react/text {:style {:color colors/gray :font-size 15}}
title] 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} :border-color colors/gray-lighter}
[react/text {:style {:font-size 15}} [react/text {:style {:font-size 15}}
content]]]) content]]])
;; Name details (defn- domain-label
[custom-domain?]
(if custom-domain?
(i18n/label :t/ens-custom-domain)
(str "." stateofus/domain)))
(views/defview name-details [] (defn- domain-switch-label
(views/letsubs [{:keys [name address public-key]} [:ens.name/screen]] [custom-domain?]
(let [pending? (nil? address)] (if custom-domain?
[react/view {:style {:flex 1}} (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}] [status-bar/status-bar {:type :main}]
[toolbar/simple-toolbar [toolbar]
name] [react/scroll-view {:style {:flex 1}
[react/scroll-view {:style {:flex 1}} ;;NOTE required so that switching custom-domain
[react/view {:style {:flex 1 :margin-horizontal 16}} ;;works on first tap and persists keyboard
[react/view {:flex-direction :row :align-items :center :margin-top 20} ;;instead of dismissing keyboard and requiring two taps
[react/view {:style {:margin-right 16}} :keyboardShouldPersistTaps :always}
[react/view {:style {:flex 1}}
[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?]]]]))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;; 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 [components.common/logo
{:size 40 {:size 36
:icon-size 16}]] :icon-size 16}]]
[react/text {:style {:typography :title}} [react/view {:flex-direction :column :margin-vertical 8}
(if pending? [react/text {:style {:font-size 15}}
(i18n/label :t/ens-transaction-pending) amount-label]
(str (i18n/label :t/ens-10-SNT) ", deposit unlocked"))]]] [react/text {:style {:color colors/gray :font-size 15}}
[react/view {:style {:margin-top 22}} (i18n/label :t/ens-deposit)]]]
(when-not pending? [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) [section {:title (i18n/label :t/ens-wallet-address)
:content (ethereum/normalized-address address)}]) :content address}]
(when-not pending?
[react/view {:style {:margin-top 14}} [react/view {:style {:margin-top 14}}
[section {:title (i18n/label :t/key) [section {:title (i18n/label :t/key)
:content public-key}]]) :content public-key}]]
[react/view {:style {:margin-top 16 :margin-bottom 32}} [agreement checked contract]])
[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}]]]]]])))
;; Terms (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] (defn- term-point [content]
[react/view {:style {:flex 1 :margin-top 24 :margin-horizontal 16 :flex-direction :row}} [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)))} [link {:on-press #(.openURL react/linking (etherscan-url (:mainnet ethereum.ens/ens-registries)))}
(i18n/label :t/etherscan-lookup)]]]])) (i18n/label :t/etherscan-lookup)]]]]))
;; Registration ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;; NAME DETAILS SCREEN
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(defn- valid-domain? [state] (views/defview name-details []
(#{:registrable :owned :connected} state)) (views/letsubs [{:keys [name address custom-domain? public-key pending?]}
[:ens.name/screen]]
(defn- main-icon [state] [react/view {:style {:flex 1}}
(cond [status-bar/status-bar {:type :main}]
(valid-domain? state) :main-icons/check [toolbar/simple-toolbar
(= state :unregistrable) :main-icons/cancel name]
:else :main-icons/username)) [react/scroll-view {:style {:flex 1}}
(when-not custom-domain?
(defn- icon-wrapper [color icon] [react/view {:style {:flex 1 :margin-horizontal 16}}
[react/view {:style {:margin-right 10 :width 32 :height 32 :border-radius 25 [react/view {:flex-direction :row :align-items :center :margin-top 20}
:align-items :center :justify-content :center :background-color color}} [react/view {:style {:margin-right 16}}
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 [components.common/logo
{:size 36 {:size 40
:icon-size 16}]] :icon-size 16}]]
[react/view {:flex-direction :column :margin-vertical 8} [react/text {:style {:typography :title}}
[react/text {:style {:font-size 15}} (if pending?
amount-label] (i18n/label :t/ens-transaction-pending)
[react/text {:style {:color colors/gray :font-size 15}} (str (i18n/label :t/ens-10-SNT) ", deposit unlocked"))]]])
(i18n/label :t/ens-deposit)]]] [react/view {:style {:margin-top 22}}
[button {:disabled? (not @checked) (when-not pending?
: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) [section {:title (i18n/label :t/ens-wallet-address)
:content address}] :content (ethereum/normalized-address address)}])
(when-not pending?
[react/view {:style {:margin-top 14}} [react/view {:style {:margin-top 14}}
[section {:title (i18n/label :t/key) [section {:title (i18n/label :t/key)
:content public-key}]] :content public-key}]])
[agreement props]]) [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- icon [{:keys [state]}] ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
[react/view {:style {:margin-top 68 :margin-bottom 24 :width 60 :height 60 :border-radius 30 ;;; WELCOME SCREEN
: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]]
[react/view {:style {:flex 1}}
[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])]))
(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
(defn- welcome-item [{:keys [icon-label title]} content] (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 {:flex 1
[react/view {:style {:height 40 :width 40 :border-radius 25 :border-width 1 :border-color colors/gray-lighter :margin-top 24
:align-items :center :justify-content :center}} :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}} [react/text {:style {:typography :title}}
icon-label]] icon-label]]
[react/view {:style {:flex 1 :margin-horizontal 16}} [react/view {:style {:flex 1
[react/text {:style {:font-size 15 :typography :main-semibold}} :margin-horizontal 16}}
[react/text {:style {:font-size 15
:typography :main-semibold}}
title] title]
content]]) content]])
@ -512,7 +603,7 @@
[react/scroll-view {:style {:flex 1}} [react/scroll-view {:style {:flex 1}}
[react/view {:style {:flex 1}} [react/view {:style {:flex 1}}
(for [name names] (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]))] (re-frame/dispatch [:bottom-sheet/hide-sheet]))]
^{:key name} ^{:key name}
[react/touchable-highlight {:on-press action} [react/touchable-highlight {:on-press action}
@ -535,23 +626,34 @@
[react/view {:style {:margin-top 8}} [react/view {:style {:margin-top 8}}
(for [name names] (for [name names]
^{:key name} ^{: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}} [react/text {:style {:color colors/gray :font-size 15}}
(i18n/label :t/ens-no-usernames)])] (i18n/label :t/ens-no-usernames)])]
[react/view {:style {:padding-top 22 :border-color colors/gray-lighter :border-top-width 1}} [react/view {:style {:padding-top 22 :border-color colors/gray-lighter :border-top-width 1}}
[react/text {:style {:color colors/gray :margin-horizontal 16}} [react/text {:style {:color colors/gray :margin-horizontal 16}}
(i18n/label :t/ens-chat-settings)] (i18n/label :t/ens-chat-settings)]
(when (> (count names) 1) (when (> (count names) 1)
[profile.components/settings-item {:label-kw :ens-primary-username [profile.components/settings-item
{:label-kw :ens-primary-username
:value preferred-name :value preferred-name
:action-fn #(re-frame/dispatch [:bottom-sheet/show-sheet :action-fn #(re-frame/dispatch [:bottom-sheet/show-sheet
{:content (fn [] (name-list names preferred-name)) {:content
:content-height (+ 72 (* (min 4 (count names)) 64))}])}]) (fn [] (name-list names preferred-name))
[profile.components/settings-switch-item {:label-kw :ens-show-username :content-height
:action-fn #(re-frame/dispatch [:ens/switch-show-username]) (+ 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?}]] :value show?}]]
(let [message (merge {:from public-key :last-in-group? true :display-username? true :display-photo? true :username name (let [message (merge {:from public-key
:content {:text (i18n/label :t/ens-test-message)} :content-type "text/plain" :timestamp-str "9:41 AM"} :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? (when show?
{:name preferred-name}))] {:name preferred-name}))]
[message/message-body message [message/message-body message

View File

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

View File

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

View File

@ -161,7 +161,9 @@
:my-profile profile.user/my-profile :my-profile profile.user/my-profile
:contacts-list contacts-list/contacts-list :contacts-list contacts-list/contacts-list
:ens-main ens/main :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-terms ens/terms
:ens-name-details ens/name-details :ens-name-details ens/name-details
:blocked-users-list contacts-list/blocked-users-list :blocked-users-list contacts-list/blocked-users-list

View File

@ -193,24 +193,25 @@ class TestProfileSingleDevice(SingleDeviceTestCase):
def test_can_add_existing_ens(self): def test_can_add_existing_ens(self):
sign_in = SignInView(self.driver) sign_in = SignInView(self.driver)
home = sign_in.recover_access(ens_user['passphrase']) home = sign_in.recover_access(ens_user['passphrase'])
home.just_fyi('switching to Mainnet')
profile = home.profile_button.click() profile = home.profile_button.click()
profile.switch_network('Mainnet with upstream RPC') profile.switch_network('Mainnet with upstream RPC')
home.profile_button.click() home.profile_button.click()
profile.element_by_text('ENS usernames').click() profile.element_by_text('ENS usernames').click()
dapp_view = DappsView(self.driver) 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.element_by_text('Get started').click()
dapp_view.ens_name.set_value(ens_user['ens']) dapp_view.ens_name.set_value(ens_user['ens'])
if not dapp_view.element_by_text_part('is owned by you').is_element_displayed(): 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.') self.errors.append('Owned username is not shown in ENS Dapp.')
dapp_view.check_ens_name.click() 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(): if not dapp_view.element_by_text_part('Username added').is_element_displayed():
self.errors.append('No message "Username added" after resolving own username') self.errors.append('No message "Username added" after resolving own username')
dapp_view.element_by_text('Ok, got it').click() 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(): 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') self.errors.append('No ENS name is shown in own "ENS usernames" after adding')
dapp_view.back_button.click() dapp_view.back_button.click()
@ -707,6 +708,7 @@ class TestProfileMultipleDevice(MultipleDeviceTestCase):
home_1 = sign_in_1.recover_access(user_1['passphrase']) home_1 = sign_in_1.recover_access(user_1['passphrase'])
home_2 = sign_in_2.create_user() 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 = sign_in_1.profile_button.click()
profile_1.switch_network('Mainnet with upstream RPC') profile_1.switch_network('Mainnet with upstream RPC')
home_1.profile_button.click() home_1.profile_button.click()
@ -715,11 +717,11 @@ class TestProfileMultipleDevice(MultipleDeviceTestCase):
dapp_view_1.element_by_text('Get started').click() dapp_view_1.element_by_text('Get started').click()
dapp_view_1.ens_name.set_value(ens_user['ens']) 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.check_ens_name.click()
dapp_view_1.element_by_text('Ok, got it').click() dapp_view_1.element_by_text('Ok, got it').click()
dapp_view_1.back_button.click() dapp_view_1.back_button.click()
profile_1.home_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_name = home_1.get_public_chat_name()
chat_2 = home_2.join_public_chat(chat_name) chat_2 = home_2.join_public_chat(chat_name)
chat_1 = home_1.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') self.errors.append('Default username is not shown in public chat')
chat_2.send_message('message from device 2') 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() chat_1.get_back_to_home_view()
home_1.profile_button.click() home_1.profile_button.click()
profile_1.element_by_text('Your ENS name').click() profile_1.element_by_text('Your ENS name').click()

View File

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

View File

@ -349,19 +349,17 @@
"ens-chat-settings": "Chat settings", "ens-chat-settings": "Chat settings",
"ens-custom-domain": "Custom domain", "ens-custom-domain": "Custom domain",
"ens-custom-username-hints": "Type the entire username including the custom domain like username.domain.eth", "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-deposit": "Deposit",
"ens-displayed-with": "Your messages are displayed to others with", "ens-displayed-with": "Your messages are displayed to others with",
"ens-get-name": "Get a universal username", "ens-get-name": "Get a universal username",
"ens-got-it": "Ok, got it", "ens-got-it": "Ok, got it",
"ens-locked": "Username locked. You wont be able to release it until 25.05.2020", "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-no-usernames": "You don't have any username connected",
"ens-powered-by": "Powered by Ethereum Name Services", "ens-powered-by": "Powered by Ethereum Name Services",
"ens-primary-username": "Primary username", "ens-primary-username": "Primary username",
"ens-register": "Register", "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": "To register the username, please try again.",
"ens-registration-failed-title": "Transaction failed", "ens-registration-failed-title": "Transaction failed",
"ens-release-username": "Release username", "ens-release-username": "Release username",
@ -386,12 +384,14 @@
"ens-transaction-pending": "Transaction pending...", "ens-transaction-pending": "Transaction pending...",
"ens-understand": "I understand that my wallet address will be publicly connected to my username.", "ens-understand": "I understand that my wallet address will be publicly connected to my username.",
"ens-username": "ENS 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-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-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-owned": "✓ Username is owned by you. ",
"ens-username-registrable": "✓ Username available!", "ens-username-registration-confirmation": "Nice! You own {{username}} once the transaction is complete.",
"ens-username-unregistrable": "Username already taken:(", "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": "ENS usernames",
"ens-usernames-details": "Register a universal username to be easily recognized by other users", "ens-usernames-details": "Register a universal username to be easily recognized by other users",
"ens-wallet-address": "Wallet address", "ens-wallet-address": "Wallet address",
@ -409,6 +409,11 @@
"ens-your-username": "Your username", "ens-your-username": "Your username",
"ens-your-usernames": "Your usernames", "ens-your-usernames": "Your usernames",
"ens-your-your-name": "Your ENS name", "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-12-words": "Enter the 12 words of your seed phrase, separated by single spaces",
"enter-contact-code": "Enter chat key or username", "enter-contact-code": "Enter chat key or username",
"enter-ens-or-contact-code": "Enter ENS username or chat key", "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-chat-settings": "Paramètres de Chat",
"ens-custom-domain": "Domaine personnalisé", "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-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-deposit": "Dépôt",
"ens-displayed-with": "Vos messages sont affichés aux autres avec", "ens-displayed-with": "Vos messages sont affichés aux autres avec",
"ens-get-name": "Obtenir un nom d'utilisateur universel", "ens-get-name": "Obtenir un nom d'utilisateur universel",
@ -351,8 +351,6 @@
"ens-powered-by": "Propulsé par Ethereum Name Services", "ens-powered-by": "Propulsé par Ethereum Name Services",
"ens-primary-username": "Nom d'utilisateur principal", "ens-primary-username": "Nom d'utilisateur principal",
"ens-register": "S'inscrire", "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": "Pour enregistrer le nom d'utilisateur, veuillez réessayer.",
"ens-registration-failed-title": "La transaction a échoué", "ens-registration-failed-title": "La transaction a échoué",
"ens-release-username": "Nom d'utilisateur libre", "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-hints": "Au moins 4 caractères. Lettres latines, chiffres et minuscules seulement.",
"ens-username-invalid": "Lettres et chiffres uniquement", "ens-username-invalid": "Lettres et chiffres uniquement",
"ens-username-owned": "✓ Le nom d'utilisateur vous appartient.", "ens-username-owned": "✓ Le nom d'utilisateur vous appartient.",
"ens-username-registrable": "✓ Nom d'utilisateur disponible!", "ens-username-available": "✓ Nom d'utilisateur disponible!",
"ens-username-unregistrable": "Nom d'utilisateur déjà pris :(", "ens-username-taken": "Nom d'utilisateur déjà pris :(",
"ens-usernames": "Noms d'utilisateurs ENS", "ens-usernames": "Noms d'utilisateurs ENS",
"ens-usernames-details": "Enregistrer un nom d'utilisateur universel pour être facilement reconnu par les autres utilisateurs", "ens-usernames-details": "Enregistrer un nom d'utilisateur universel pour être facilement reconnu par les autres utilisateurs",
"ens-wallet-address": "Adresse portefeuille", "ens-wallet-address": "Adresse portefeuille",

View File

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

View File

@ -348,7 +348,7 @@
"ens-chat-settings": "채팅 설정", "ens-chat-settings": "채팅 설정",
"ens-custom-domain": "커스텀 도메인", "ens-custom-domain": "커스텀 도메인",
"ens-custom-username-hints": "username.domain.eth와 같은 커스텀 도메인 형식의 사용자 이름을 입력해주세요", "ens-custom-username-hints": "username.domain.eth와 같은 커스텀 도메인 형식의 사용자 이름을 입력해주세요",
"ens-custom-username-unregistrable": "사용자 이름을 등록할 수 없습니다 :(", "ens-custom-username-taken": "사용자 이름을 등록할 수 없습니다 :(",
"ens-deposit": "입금", "ens-deposit": "입금",
"ens-displayed-with": "귀하의 메시지가 다른 사용자에게 다음과 같이 표시됩니다", "ens-displayed-with": "귀하의 메시지가 다른 사용자에게 다음과 같이 표시됩니다",
"ens-get-name": "유니버셜 사용자 이름 등록", "ens-get-name": "유니버셜 사용자 이름 등록",
@ -359,8 +359,6 @@
"ens-powered-by": "Powered by Ethereum Name Services", "ens-powered-by": "Powered by Ethereum Name Services",
"ens-primary-username": "주 사용자 이름", "ens-primary-username": "주 사용자 이름",
"ens-register": "등록하기", "ens-register": "등록하기",
"ens-registered": "지갑의 거래내역 섹션에서 진행 상황을 확인할 수 있습니다.",
"ens-registered-title": "좋습니다! \n거래가 완료되면 이름이 등록됩니다.",
"ens-registration-failed": "사용자 이름을 등록하려면, 다시 시도해주세요", "ens-registration-failed": "사용자 이름을 등록하려면, 다시 시도해주세요",
"ens-registration-failed-title": "거래 실패", "ens-registration-failed-title": "거래 실패",
"ens-release-username": "사용자 이름 계약 해지", "ens-release-username": "사용자 이름 계약 해지",
@ -389,8 +387,8 @@
"ens-username-hints": "최소 4자 이상이어야 하며, 알파벳 소문자 및 숫자만 가능합니다.", "ens-username-hints": "최소 4자 이상이어야 하며, 알파벳 소문자 및 숫자만 가능합니다.",
"ens-username-invalid": "문자와 숫자만 가능", "ens-username-invalid": "문자와 숫자만 가능",
"ens-username-owned": "✓ 이미 소유한 이름입니다.", "ens-username-owned": "✓ 이미 소유한 이름입니다.",
"ens-username-registrable": "✓ 사용자 이름을 사용할 수 있습니다!", "ens-username-available": "✓ 사용자 이름을 사용할 수 있습니다!",
"ens-username-unregistrable": "이미 사용 중인 이름입니다 :(", "ens-username-taken": "이미 사용 중인 이름입니다 :(",
"ens-usernames": "ENS 사용자 이름", "ens-usernames": "ENS 사용자 이름",
"ens-usernames-details": "검색 가능한 아이디 등록", "ens-usernames-details": "검색 가능한 아이디 등록",
"ens-wallet-address": "지갑 주소", "ens-wallet-address": "지갑 주소",

View File

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

View File

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

View File

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