[#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
This commit is contained in:
Ulises Manuel 2024-03-08 12:20:41 -06:00 committed by GitHub
parent 7a6714eeb2
commit 3c6923963d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 179 additions and 122 deletions

View File

@ -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})}]])]})))

View File

@ -8,22 +8,22 @@
[status-im.common.floating-button-page.view :as floating-button-page] [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.add-address-to-watch.style :as style]
[status-im.contexts.wallet.common.validation :as validation] [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.i18n :as i18n]
[utils.re-frame :as rf])) [utils.re-frame :as rf]))
(defn validate-message (defn- validate-address
[addresses] [known-addresses user-input]
(fn [s] (cond
(cond (or (nil? user-input) (= user-input "")) nil
(or (= s nil) (= s "")) nil (contains? known-addresses user-input) (i18n/label :t/address-already-in-use)
(contains? addresses s) (i18n/label :t/address-already-in-use) (not
(not (or (validation/eth-address? s) (or (validation/eth-address? user-input)
(validation/ens-name? s))) (i18n/label :t/invalid-address) (validation/ens-name? user-input))) (i18n/label :t/invalid-address)))
:else nil)))
(defn- address-input (defn- address-input
[{:keys [input-value validation-msg validate [{:keys [input-value validation-msg validate clear-input]}]
clear-input]}]
(let [scanned-address (rf/sub [:wallet/scanned-address]) (let [scanned-address (rf/sub [:wallet/scanned-address])
empty-input? (and (string/blank? @input-value) empty-input? (and (string/blank? @input-value)
(string/blank? scanned-address)) (string/blank? scanned-address))
@ -32,10 +32,11 @@
(reset! input-value new-text) (reset! input-value new-text)
(reagent/flush) (reagent/flush)
(if (and (not-empty new-text) (nil? (validate new-text))) (if (and (not-empty new-text) (nil? (validate new-text)))
(rf/dispatch [:wallet/get-address-details new-text]) (debounce/debounce-and-dispatch [:wallet/get-address-details new-text]
(rf/dispatch [:wallet/clear-address-activity-check])) 500)
(rf/dispatch [:wallet/clear-address-activity]))
(when (and scanned-address (not= scanned-address new-text)) (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]))) (rf/dispatch [:wallet/clean-scanned-address])))
paste-on-input #(clipboard/get-string paste-on-input #(clipboard/get-string
(fn [clipboard-text] (fn [clipboard-text]
@ -44,8 +45,7 @@
(when-not (string/blank? scanned-address) (when-not (string/blank? scanned-address)
(on-change-text scanned-address))) (on-change-text scanned-address)))
[scanned-address]) [scanned-address])
[rn/view [rn/view {:style style/input-container}
{:style style/input-container}
[quo/input [quo/input
{:accessibility-label :add-address-to-watch {:accessibility-label :add-address-to-watch
:placeholder (i18n/label :t/address-placeholder) :placeholder (i18n/label :t/address-placeholder)
@ -72,84 +72,92 @@
:i/scan]])) :i/scan]]))
(defn activity-indicator (defn activity-indicator
[] [activity-state]
(let [activity-state (rf/sub [:wallet/watch-address-activity-state]) (let [{:keys [message]
{:keys [accessibility-label icon type message]} :as props} (case activity-state
(case activity-state :has-activity {:accessibility-label :account-has-activity
:has-activity {:accessibility-label :account-has-activity :icon :i/done
:icon :i/done :type :success
:type :success :message :t/this-address-has-activity}
:message :t/this-address-has-activity} :no-activity {:accessibility-label :account-has-no-activity
:no-activity {:accessibility-label :account-has-no-activity :icon :i/info
:icon :i/info :type :warning
:type :warning :message :t/this-address-has-no-activity}
:message :t/this-address-has-no-activity} :invalid-ens {:accessibility-label :error-message
{:accessibility-label :searching-for-activity :icon :i/info
:icon :i/pending-state :type :error
:type :default :message :t/invalid-address}
:message :t/searching-for-activity})] :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 (when activity-state
[quo/info-message [quo/info-message
{:accessibility-label accessibility-label (assoc props
:size :default :style style/info-message
:icon icon :size :default)
:type type
:style style/info-message}
(i18n/label message)]))) (i18n/label message)])))
(defn view (defn view
[] []
(let [addresses (rf/sub [:wallet/addresses]) (let [addresses (rf/sub [:wallet/addresses])
input-value (reagent/atom nil) input-value (reagent/atom nil)
validate (validate-message (set addresses)) validate #(validate-address (set addresses) %)
validation-msg (reagent/atom (validate validation-msg (reagent/atom nil)
@input-value))
clear-input (fn [] clear-input (fn []
(reset! input-value nil) (reset! input-value nil)
(reset! validation-msg nil) (reset! validation-msg nil)
(rf/dispatch [:wallet/clear-address-activity-check]) (rf/dispatch [:wallet/clear-address-activity])
(rf/dispatch [:wallet/clean-scanned-address])) (rf/dispatch [:wallet/clean-scanned-address]))
customization-color (rf/sub [:profile/customization-color])] customization-color (rf/sub [:profile/customization-color])]
(rf/dispatch [:wallet/clean-scanned-address]) (rf/dispatch [:wallet/clean-scanned-address])
(rf/dispatch [:wallet/clear-address-activity-check]) (rf/dispatch [:wallet/clear-address-activity])
(fn [] (fn []
[rn/view (let [activity-state (rf/sub [:wallet/watch-address-activity-state])
{:style {:flex 1}} validated-address (rf/sub [:wallet/watch-address-validated-address])]
[floating-button-page/view [rn/view {:style {:flex 1}}
{:header [quo/page-nav [floating-button-page/view
{:type :no-title {:header [quo/page-nav
:icon-name :i/close {:type :no-title
:on-press (fn [] :icon-name :i/close
(rf/dispatch [:wallet/clean-scanned-address]) :on-press (fn []
(rf/dispatch [:wallet/clear-address-activity-check]) (rf/dispatch [:wallet/clean-scanned-address])
(rf/dispatch [:navigate-back]))}] (rf/dispatch [:wallet/clear-address-activity])
:footer [quo/button (rf/dispatch [:navigate-back]))}]
{:customization-color customization-color :footer [quo/button
:disabled? (or (string/blank? @input-value) {:customization-color customization-color
(some? (validate @input-value))) :disabled? (or (string/blank? @input-value)
:on-press (fn [] (some? (validate @input-value))
(rf/dispatch [:navigate-to (= activity-state :invalid-ens)
:confirm-address-to-watch (= activity-state :scanning)
{:address @input-value}]) (not validated-address))
(clear-input)) :on-press (fn []
:container-style {:z-index 2}} (rf/dispatch [:navigate-to
(i18n/label :t/continue)]} :confirm-address-to-watch
[quo/page-top {:address validated-address}])
{:container-style style/header-container (clear-input))
:title (i18n/label :t/add-address) :container-style {:z-index 2}}
:description :text (i18n/label :t/continue)]}
:description-text (i18n/label :t/enter-eth)}] [quo/page-top
[:f> address-input {:container-style style/header-container
{:input-value input-value :title (i18n/label :t/add-address)
:validate validate :description :text
:validation-msg validation-msg :description-text (i18n/label :t/enter-eth)}]
:clear-input clear-input}] [:f> address-input
(when @validation-msg {:input-value input-value
[quo/info-message :validate validate
{:accessibility-label :error-message :validation-msg validation-msg
:size :default :clear-input clear-input}]
:icon :i/info (if @validation-msg
:type :error [quo/info-message
:style style/info-message} {:accessibility-label :error-message
@validation-msg]) :size :default
[activity-indicator]]]))) :icon :i/info
:type :error
:style style/info-message}
@validation-msg]
[activity-indicator activity-state])]]))))

View File

@ -4,13 +4,13 @@
[react-native.background-timer :as background-timer] [react-native.background-timer :as background-timer]
[react-native.platform :as platform] [react-native.platform :as platform]
[status-im.constants :as constants] [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.common.utils :as utils]
[status-im.contexts.wallet.data-store :as data-store] [status-im.contexts.wallet.data-store :as data-store]
[status-im.contexts.wallet.events.collectibles] [status-im.contexts.wallet.events.collectibles]
[status-im.contexts.wallet.item-types :as item-types] [status-im.contexts.wallet.item-types :as item-types]
[taoensso.timbre :as log] [taoensso.timbre :as log]
[utils.collection] [utils.collection]
[utils.ethereum.chain :as chain]
[utils.ethereum.eip.eip55 :as eip55] [utils.ethereum.eip.eip55 :as eip55]
[utils.i18n :as i18n] [utils.i18n :as i18n]
[utils.number] [utils.number]
@ -360,27 +360,6 @@
(fn [{:keys [db]}] (fn [{:keys [db]}]
{:db (assoc db :wallet/valid-ens-or-address? false)})) {: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 (rf/reg-event-fx
:wallet/navigate-to-chain-explorer-from-bottom-sheet :wallet/navigate-to-chain-explorer-from-bottom-sheet
(fn [_ [explorer-link address]] (fn [_ [explorer-link address]]

View File

@ -115,16 +115,15 @@
(defn- local-suggestions-list (defn- local-suggestions-list
[] []
(fn [] (let [local-suggestion (rf/sub [:wallet/local-suggestions])]
(let [local-suggestion (rf/sub [:wallet/local-suggestions])] [rn/view {:style {:flex 1}}
[rn/view {:style {:flex 1}} [rn/flat-list
[rn/flat-list {:data local-suggestion
{:data local-suggestion :content-container-style {:flex-grow 1}
:content-container-style {:flex-grow 1} :key-fn :id
:key-fn :id :on-scroll-to-index-failed identity
:on-scroll-to-index-failed identity :keyboard-should-persist-taps :handled
:keyboard-should-persist-taps :handled :render-fn suggestion-component}]]))
:render-fn suggestion-component}]])))
(defn- f-view (defn- f-view
[] []

View File

@ -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)

View File

@ -2,6 +2,7 @@
(:require [clojure.string :as string] (:require [clojure.string :as string]
[re-frame.core :as rf] [re-frame.core :as rf]
[status-im.contexts.wallet.common.utils :as utils] [status-im.contexts.wallet.common.utils :as utils]
[status-im.subs.wallet.add-account.address-to-watch]
[utils.number])) [utils.number]))
(defn- filter-networks (defn- filter-networks
@ -93,11 +94,6 @@
:<- [:wallet/wallet-send] :<- [:wallet/wallet-send]
:-> :bridge-to-chain-id) :-> :bridge-to-chain-id)
(rf/reg-sub
:wallet/watch-address-activity-state
:<- [:wallet/ui]
:-> :watch-address-activity-state)
(rf/reg-sub (rf/reg-sub
:wallet/keypairs :wallet/keypairs
:<- [:wallet] :<- [:wallet]

View File

@ -296,12 +296,16 @@
(is (nil? (rf/sub [sub-name])))) (is (nil? (rf/sub [sub-name]))))
(testing "watch address activity state with no-activity value" (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])))) (is (match? :no-activity (rf/sub [sub-name]))))
(testing "watch address activity state with has-activity value" (testing "watch address activity state with has-activity value"
(swap! rf-db/app-db #(assoc-in % [:wallet :ui :watch-address-activity-state] :has-activity)) (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]))))) (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 (h/deftest-sub :wallet/accounts-without-current-viewing-account
[sub-name] [sub-name]