Skip to content

[#18736] add address to watch using an ENS #19043

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 13 commits into from
Mar 8, 2024
Original file line number Diff line number Diff line change
@@ -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})}]])]})))
172 changes: 90 additions & 82 deletions src/status_im/contexts/wallet/add_address_to_watch/view.cljs
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand All @@ -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]
Expand All @@ -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)
Expand All @@ -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])]]))))
Comment on lines +155 to +163
Copy link
Contributor Author

@ulisesmac ulisesmac Feb 28, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This if was a when, while using when, two messages could be displayed under certain circumstances, because we have two components and the back-end response comes some time later.

The message is the following:
image

I think we should only have a single component, having two makes the code more error prone. A bigger refactor on this component might be needed, but I didn't do it here because I wanted to keep the diff focused.

23 changes: 1 addition & 22 deletions src/status_im/contexts/wallet/events.cljs
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down Expand Up @@ -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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hmm, so this file is really starting to bloat.
@OmarBasem, @briansztamfater & I discussed this week of further splitting the wallet into sub-contexts
Mostly based off the figma keys, but we can have others that better fit.
Screenshot 2024-02-29 at 18 30 20

e.g something like

src/status_im/contexts/wallet/
     |-/accounts
         |-/events.cljs
         |-/view.cljs
         |-/style.cljs
      |-/add-account
         |-/events.cljs
         |-/view.cljs
         |-/style.cljs
         |-/create-account
         |-/add-account-to-watch

etc..
We have this for Send & Collectibles already too.

Anyway, I'm not saying to do this refactor here, but perhaps we can at least create the appropriate ns for these events added and move them to that file and the others can be moved in a refactor.

Also maybe these events are common enough to wallet that they should be in this ns. 🤷‍♂️

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@J-Son89
Totally agree!

Actually I noticed this while adding these events, but noticed it's only happening on Collectibles and wasn't sure.
But yeah, I'll move them. mirrorring figma structure is a nice idea 👍

:wallet/navigate-to-chain-explorer-from-bottom-sheet
(fn [_ [explorer-link address]]
Expand Down
19 changes: 9 additions & 10 deletions src/status_im/contexts/wallet/send/select_address/view.cljs
Original file line number Diff line number Diff line change
Expand Up @@ -115,16 +115,15 @@

(defn- local-suggestions-list
[]
(fn []
Copy link
Contributor Author

@ulisesmac ulisesmac Feb 28, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just removing an extra fn [] I found, not related to this PR

(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
[]
Expand Down
17 changes: 17 additions & 0 deletions src/status_im/subs/wallet/add_account/address_to_watch.cljs
Original file line number Diff line number Diff line change
@@ -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)
6 changes: 1 addition & 5 deletions src/status_im/subs/wallet/wallet.cljs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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]
Expand Down
10 changes: 7 additions & 3 deletions src/status_im/subs/wallet/wallet_test.cljs
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down