From 3c6923963d04d2643f74db8344927b1aa587d8d1 Mon Sep 17 00:00:00 2001 From: Ulises Manuel <90291778+ulisesmac@users.noreply.github.com> Date: Fri, 8 Mar 2024 12:20:41 -0600 Subject: [PATCH] [#18736] add address to watch using an ENS (#19043) * Add capability to register an address based on an ENS * update tests * Check for existing addresses using an ENS --- .../add_account/address_to_watch/events.cljs | 54 ++++++ .../wallet/add_address_to_watch/view.cljs | 172 +++++++++--------- src/status_im/contexts/wallet/events.cljs | 23 +-- .../wallet/send/select_address/view.cljs | 19 +- .../wallet/add_account/address_to_watch.cljs | 17 ++ src/status_im/subs/wallet/wallet.cljs | 6 +- src/status_im/subs/wallet/wallet_test.cljs | 10 +- 7 files changed, 179 insertions(+), 122 deletions(-) create mode 100644 src/status_im/contexts/wallet/accounts/add_account/address_to_watch/events.cljs create mode 100644 src/status_im/subs/wallet/add_account/address_to_watch.cljs diff --git a/src/status_im/contexts/wallet/accounts/add_account/address_to_watch/events.cljs b/src/status_im/contexts/wallet/accounts/add_account/address_to_watch/events.cljs new file mode 100644 index 0000000000..c2d7c9b077 --- /dev/null +++ b/src/status_im/contexts/wallet/accounts/add_account/address_to_watch/events.cljs @@ -0,0 +1,54 @@ +(ns status-im.contexts.wallet.accounts.add-account.address-to-watch.events + (:require [clojure.string :as string] + [status-im.constants :as constants] + [taoensso.timbre :as log] + [utils.re-frame :as rf])) + +(rf/reg-event-fx + :wallet/ens-not-found + (fn [{:keys [db]} _] + {:db (-> db + (assoc-in [:wallet :ui :add-address-to-watch :activity-state] :invalid-ens) + (assoc-in [:wallet :ui :add-address-to-watch :validated-address] nil))})) + +(rf/reg-event-fx + :wallet/store-address-activity + (fn [{:keys [db]} [address {:keys [hasActivity]}]] + (let [registered-addresses (-> db :wallet :accounts keys set) + address-already-registered? (registered-addresses address)] + (if address-already-registered? + {:db (-> db + (assoc-in [:wallet :ui :add-address-to-watch :activity-state] + :address-already-registered) + (assoc-in [:wallet :ui :add-address-to-watch :validated-address] nil))} + (let [state (if hasActivity :has-activity :no-activity)] + {:db (-> db + (assoc-in [:wallet :ui :add-address-to-watch :activity-state] state) + (assoc-in [:wallet :ui :add-address-to-watch :validated-address] address))}))))) + +(rf/reg-event-fx + :wallet/clear-address-activity + (fn [{:keys [db]}] + {:db (update-in db [:wallet :ui] dissoc :add-address-to-watch)})) + +(rf/reg-event-fx + :wallet/get-address-details + (fn [{:keys [db]} [address-or-ens]] + (let [request-params [constants/ethereum-mainnet-chain-id address-or-ens] + ens? (string/includes? address-or-ens ".")] + {:db (-> db + (assoc-in [:wallet :ui :add-address-to-watch :activity-state] :scanning) + (assoc-in [:wallet :ui :add-address-to-watch :validated-address] nil)) + :fx [(if ens? + [:json-rpc/call + [{:method "ens_addressOf" + :params request-params + :on-success [:wallet/get-address-details] + :on-error [:wallet/ens-not-found]}]] + [:json-rpc/call + [{:method "wallet_getAddressDetails" + :params request-params + :on-success [:wallet/store-address-activity address-or-ens] + :on-error #(log/info "failed to get address details" + {:error % + :event :wallet/get-address-details})}]])]}))) diff --git a/src/status_im/contexts/wallet/add_address_to_watch/view.cljs b/src/status_im/contexts/wallet/add_address_to_watch/view.cljs index cc5477e655..5b2d4f0484 100644 --- a/src/status_im/contexts/wallet/add_address_to_watch/view.cljs +++ b/src/status_im/contexts/wallet/add_address_to_watch/view.cljs @@ -8,22 +8,22 @@ [status-im.common.floating-button-page.view :as floating-button-page] [status-im.contexts.wallet.add-address-to-watch.style :as style] [status-im.contexts.wallet.common.validation :as validation] + [status-im.subs.wallet.add-account.address-to-watch] + [utils.debounce :as debounce] [utils.i18n :as i18n] [utils.re-frame :as rf])) -(defn validate-message - [addresses] - (fn [s] - (cond - (or (= s nil) (= s "")) nil - (contains? addresses s) (i18n/label :t/address-already-in-use) - (not (or (validation/eth-address? s) - (validation/ens-name? s))) (i18n/label :t/invalid-address) - :else nil))) +(defn- validate-address + [known-addresses user-input] + (cond + (or (nil? user-input) (= user-input "")) nil + (contains? known-addresses user-input) (i18n/label :t/address-already-in-use) + (not + (or (validation/eth-address? user-input) + (validation/ens-name? user-input))) (i18n/label :t/invalid-address))) (defn- address-input - [{:keys [input-value validation-msg validate - clear-input]}] + [{:keys [input-value validation-msg validate clear-input]}] (let [scanned-address (rf/sub [:wallet/scanned-address]) empty-input? (and (string/blank? @input-value) (string/blank? scanned-address)) @@ -32,10 +32,11 @@ (reset! input-value new-text) (reagent/flush) (if (and (not-empty new-text) (nil? (validate new-text))) - (rf/dispatch [:wallet/get-address-details new-text]) - (rf/dispatch [:wallet/clear-address-activity-check])) + (debounce/debounce-and-dispatch [:wallet/get-address-details new-text] + 500) + (rf/dispatch [:wallet/clear-address-activity])) (when (and scanned-address (not= scanned-address new-text)) - (rf/dispatch [:wallet/clear-address-activity-check]) + (rf/dispatch [:wallet/clear-address-activity]) (rf/dispatch [:wallet/clean-scanned-address]))) paste-on-input #(clipboard/get-string (fn [clipboard-text] @@ -44,8 +45,7 @@ (when-not (string/blank? scanned-address) (on-change-text scanned-address))) [scanned-address]) - [rn/view - {:style style/input-container} + [rn/view {:style style/input-container} [quo/input {:accessibility-label :add-address-to-watch :placeholder (i18n/label :t/address-placeholder) @@ -72,84 +72,92 @@ :i/scan]])) (defn activity-indicator - [] - (let [activity-state (rf/sub [:wallet/watch-address-activity-state]) - {:keys [accessibility-label icon type message]} - (case activity-state - :has-activity {:accessibility-label :account-has-activity - :icon :i/done - :type :success - :message :t/this-address-has-activity} - :no-activity {:accessibility-label :account-has-no-activity - :icon :i/info - :type :warning - :message :t/this-address-has-no-activity} - {:accessibility-label :searching-for-activity - :icon :i/pending-state - :type :default - :message :t/searching-for-activity})] + [activity-state] + (let [{:keys [message] + :as props} (case activity-state + :has-activity {:accessibility-label :account-has-activity + :icon :i/done + :type :success + :message :t/this-address-has-activity} + :no-activity {:accessibility-label :account-has-no-activity + :icon :i/info + :type :warning + :message :t/this-address-has-no-activity} + :invalid-ens {:accessibility-label :error-message + :icon :i/info + :type :error + :message :t/invalid-address} + :address-already-registered {:accessibility-label :error-message + :icon :i/info + :type :error + :message :t/address-already-in-use} + {:accessibility-label :searching-for-activity + :icon :i/pending-state + :type :default + :message :t/searching-for-activity})] (when activity-state [quo/info-message - {:accessibility-label accessibility-label - :size :default - :icon icon - :type type - :style style/info-message} + (assoc props + :style style/info-message + :size :default) (i18n/label message)]))) (defn view [] (let [addresses (rf/sub [:wallet/addresses]) input-value (reagent/atom nil) - validate (validate-message (set addresses)) - validation-msg (reagent/atom (validate - @input-value)) + validate #(validate-address (set addresses) %) + validation-msg (reagent/atom nil) clear-input (fn [] (reset! input-value nil) (reset! validation-msg nil) - (rf/dispatch [:wallet/clear-address-activity-check]) + (rf/dispatch [:wallet/clear-address-activity]) (rf/dispatch [:wallet/clean-scanned-address])) customization-color (rf/sub [:profile/customization-color])] (rf/dispatch [:wallet/clean-scanned-address]) - (rf/dispatch [:wallet/clear-address-activity-check]) + (rf/dispatch [:wallet/clear-address-activity]) (fn [] - [rn/view - {:style {:flex 1}} - [floating-button-page/view - {:header [quo/page-nav - {:type :no-title - :icon-name :i/close - :on-press (fn [] - (rf/dispatch [:wallet/clean-scanned-address]) - (rf/dispatch [:wallet/clear-address-activity-check]) - (rf/dispatch [:navigate-back]))}] - :footer [quo/button - {:customization-color customization-color - :disabled? (or (string/blank? @input-value) - (some? (validate @input-value))) - :on-press (fn [] - (rf/dispatch [:navigate-to - :confirm-address-to-watch - {:address @input-value}]) - (clear-input)) - :container-style {:z-index 2}} - (i18n/label :t/continue)]} - [quo/page-top - {:container-style style/header-container - :title (i18n/label :t/add-address) - :description :text - :description-text (i18n/label :t/enter-eth)}] - [:f> address-input - {:input-value input-value - :validate validate - :validation-msg validation-msg - :clear-input clear-input}] - (when @validation-msg - [quo/info-message - {:accessibility-label :error-message - :size :default - :icon :i/info - :type :error - :style style/info-message} - @validation-msg]) - [activity-indicator]]]))) + (let [activity-state (rf/sub [:wallet/watch-address-activity-state]) + validated-address (rf/sub [:wallet/watch-address-validated-address])] + [rn/view {:style {:flex 1}} + [floating-button-page/view + {:header [quo/page-nav + {:type :no-title + :icon-name :i/close + :on-press (fn [] + (rf/dispatch [:wallet/clean-scanned-address]) + (rf/dispatch [:wallet/clear-address-activity]) + (rf/dispatch [:navigate-back]))}] + :footer [quo/button + {:customization-color customization-color + :disabled? (or (string/blank? @input-value) + (some? (validate @input-value)) + (= activity-state :invalid-ens) + (= activity-state :scanning) + (not validated-address)) + :on-press (fn [] + (rf/dispatch [:navigate-to + :confirm-address-to-watch + {:address validated-address}]) + (clear-input)) + :container-style {:z-index 2}} + (i18n/label :t/continue)]} + [quo/page-top + {:container-style style/header-container + :title (i18n/label :t/add-address) + :description :text + :description-text (i18n/label :t/enter-eth)}] + [:f> address-input + {:input-value input-value + :validate validate + :validation-msg validation-msg + :clear-input clear-input}] + (if @validation-msg + [quo/info-message + {:accessibility-label :error-message + :size :default + :icon :i/info + :type :error + :style style/info-message} + @validation-msg] + [activity-indicator activity-state])]])))) diff --git a/src/status_im/contexts/wallet/events.cljs b/src/status_im/contexts/wallet/events.cljs index 91cc479535..60d7da0c9c 100644 --- a/src/status_im/contexts/wallet/events.cljs +++ b/src/status_im/contexts/wallet/events.cljs @@ -4,13 +4,13 @@ [react-native.background-timer :as background-timer] [react-native.platform :as platform] [status-im.constants :as constants] + [status-im.contexts.wallet.accounts.add-account.address-to-watch.events] [status-im.contexts.wallet.common.utils :as utils] [status-im.contexts.wallet.data-store :as data-store] [status-im.contexts.wallet.events.collectibles] [status-im.contexts.wallet.item-types :as item-types] [taoensso.timbre :as log] [utils.collection] - [utils.ethereum.chain :as chain] [utils.ethereum.eip.eip55 :as eip55] [utils.i18n :as i18n] [utils.number] @@ -360,27 +360,6 @@ (fn [{:keys [db]}] {:db (assoc db :wallet/valid-ens-or-address? false)})) -(rf/reg-event-fx :wallet/get-address-details-success - (fn [{:keys [db]} [{:keys [hasActivity]}]] - {:db (assoc-in db - [:wallet :ui :watch-address-activity-state] - (if hasActivity :has-activity :no-activity))})) - -(rf/reg-event-fx :wallet/clear-address-activity-check - (fn [{:keys [db]}] - {:db (update-in db [:wallet :ui] dissoc :watch-address-activity-state)})) - -(rf/reg-event-fx :wallet/get-address-details - (fn [{:keys [db]} [address]] - {:db (assoc-in db [:wallet :ui :watch-address-activity-state] :scanning) - :fx [[:json-rpc/call - [{:method "wallet_getAddressDetails" - :params [(chain/chain-id db) address] - :on-success [:wallet/get-address-details-success] - :on-error #(log/info "failed to get address details" - {:error % - :event :wallet/get-address-details})}]]]})) - (rf/reg-event-fx :wallet/navigate-to-chain-explorer-from-bottom-sheet (fn [_ [explorer-link address]] diff --git a/src/status_im/contexts/wallet/send/select_address/view.cljs b/src/status_im/contexts/wallet/send/select_address/view.cljs index da218485d1..18173db63f 100644 --- a/src/status_im/contexts/wallet/send/select_address/view.cljs +++ b/src/status_im/contexts/wallet/send/select_address/view.cljs @@ -115,16 +115,15 @@ (defn- local-suggestions-list [] - (fn [] - (let [local-suggestion (rf/sub [:wallet/local-suggestions])] - [rn/view {:style {:flex 1}} - [rn/flat-list - {:data local-suggestion - :content-container-style {:flex-grow 1} - :key-fn :id - :on-scroll-to-index-failed identity - :keyboard-should-persist-taps :handled - :render-fn suggestion-component}]]))) + (let [local-suggestion (rf/sub [:wallet/local-suggestions])] + [rn/view {:style {:flex 1}} + [rn/flat-list + {:data local-suggestion + :content-container-style {:flex-grow 1} + :key-fn :id + :on-scroll-to-index-failed identity + :keyboard-should-persist-taps :handled + :render-fn suggestion-component}]])) (defn- f-view [] diff --git a/src/status_im/subs/wallet/add_account/address_to_watch.cljs b/src/status_im/subs/wallet/add_account/address_to_watch.cljs new file mode 100644 index 0000000000..7c4b747741 --- /dev/null +++ b/src/status_im/subs/wallet/add_account/address_to_watch.cljs @@ -0,0 +1,17 @@ +(ns status-im.subs.wallet.add-account.address-to-watch + (:require [re-frame.core :as rf])) + +(rf/reg-sub + :wallet/add-address-to-watch + :<- [:wallet/ui] + :-> :add-address-to-watch) + +(rf/reg-sub + :wallet/watch-address-activity-state + :<- [:wallet/add-address-to-watch] + :-> :activity-state) + +(rf/reg-sub + :wallet/watch-address-validated-address + :<- [:wallet/add-address-to-watch] + :-> :validated-address) diff --git a/src/status_im/subs/wallet/wallet.cljs b/src/status_im/subs/wallet/wallet.cljs index 32d8623978..30d845891a 100644 --- a/src/status_im/subs/wallet/wallet.cljs +++ b/src/status_im/subs/wallet/wallet.cljs @@ -2,6 +2,7 @@ (:require [clojure.string :as string] [re-frame.core :as rf] [status-im.contexts.wallet.common.utils :as utils] + [status-im.subs.wallet.add-account.address-to-watch] [utils.number])) (defn- filter-networks @@ -93,11 +94,6 @@ :<- [:wallet/wallet-send] :-> :bridge-to-chain-id) -(rf/reg-sub - :wallet/watch-address-activity-state - :<- [:wallet/ui] - :-> :watch-address-activity-state) - (rf/reg-sub :wallet/keypairs :<- [:wallet] diff --git a/src/status_im/subs/wallet/wallet_test.cljs b/src/status_im/subs/wallet/wallet_test.cljs index 45e79edd20..f0ff32bd73 100644 --- a/src/status_im/subs/wallet/wallet_test.cljs +++ b/src/status_im/subs/wallet/wallet_test.cljs @@ -296,12 +296,16 @@ (is (nil? (rf/sub [sub-name])))) (testing "watch address activity state with no-activity value" - (swap! rf-db/app-db #(assoc-in % [:wallet :ui :watch-address-activity-state] :no-activity)) + (swap! rf-db/app-db #(assoc-in % [:wallet :ui :add-address-to-watch :activity-state] :no-activity)) (is (match? :no-activity (rf/sub [sub-name])))) (testing "watch address activity state with has-activity value" - (swap! rf-db/app-db #(assoc-in % [:wallet :ui :watch-address-activity-state] :has-activity)) - (is (match? :has-activity (rf/sub [sub-name]))))) + (swap! rf-db/app-db #(assoc-in % [:wallet :ui :add-address-to-watch :activity-state] :has-activity)) + (is (match? :has-activity (rf/sub [sub-name])))) + + (testing "watch address activity state with invalid-ens value" + (swap! rf-db/app-db #(assoc-in % [:wallet :ui :add-address-to-watch :activity-state] :invalid-ens)) + (is (match? :invalid-ens (rf/sub [sub-name]))))) (h/deftest-sub :wallet/accounts-without-current-viewing-account [sub-name]