diff --git a/src/quo/components/drawers/drawer_top/view.cljs b/src/quo/components/drawers/drawer_top/view.cljs index fefb4e3b19..4221be2049 100644 --- a/src/quo/components/drawers/drawer_top/view.cljs +++ b/src/quo/components/drawers/drawer_top/view.cljs @@ -79,8 +79,7 @@ (defn- context-tag-subtitle [{:keys [context-tag-type context icon community-logo community-name account-name emoji - customization-color - full-name profile-picture]}] + customization-color full-name profile-picture blur?]}] (let [tag-type (or context-tag-type :account)] [rn/view {:accessibility-label :context-tag-wrapper @@ -97,7 +96,8 @@ :profile-picture profile-picture :full-name full-name :context context - :icon icon}]])) + :icon icon + :blur? blur?}]])) (defn- description-subtitle [{:keys [theme blur? description]}] @@ -142,7 +142,8 @@ :profile-picture profile-picture :full-name full-name :context context - :icon icon}] + :icon icon + :blur? blur?}] (and (not= :label type) description) [description-subtitle diff --git a/src/quo/components/list_items/saved_address/view.cljs b/src/quo/components/list_items/saved_address/view.cljs index 34c2c967df..47bff50192 100644 --- a/src/quo/components/list_items/saved_address/view.cljs +++ b/src/quo/components/list_items/saved_address/view.cljs @@ -4,10 +4,10 @@ [quo.components.icon :as icon] [quo.components.list-items.saved-address.style :as style] [quo.components.markdown.text :as text] + [quo.components.wallet.address-text.view :as address-text] [quo.foundations.colors :as colors] [quo.theme :as quo.theme] - [react-native.core :as rn] - [utils.address :as address])) + [react-native.core :as rn])) (defn- left-container [{:keys [blur? name ens address customization-color]}] @@ -23,15 +23,19 @@ :size :paragraph-1 :style style/name-text} name] - [text/text {:size :paragraph-2} - [text/text - {:size :paragraph-2 - :weight :monospace - :style (style/account-address blur? theme)} - (or ens (address/get-shortened-key address))]]]])) + (if ens + [text/text + {:size :paragraph-2 + :weight :monospace + :style (style/account-address blur? theme)} ens] + [address-text/view + {:address address + :full-address? true + :format :short + :blur? blur?}])]])) (defn view - [{:keys [blur? user-props active-state? customization-color on-press on-options-press] + [{:keys [blur? user-props active-state? customization-color on-press on-options-press container-style] :or {customization-color :blue blur? false}}] (let [theme (quo.theme/use-theme) @@ -52,8 +56,9 @@ (set-state new-state))) [active-state?])] [rn/pressable - {:style (style/container - {:state state :blur? blur? :customization-color customization-color}) + {:style (merge (style/container + {:state state :blur? blur? :customization-color customization-color}) + container-style) :on-press-in on-press-in :on-press-out on-press-out :on-press on-press diff --git a/src/quo/components/tags/context_tag/schema.cljs b/src/quo/components/tags/context_tag/schema.cljs index 15bf8b7ac0..32adacaf4a 100644 --- a/src/quo/components/tags/context_tag/schema.cljs +++ b/src/quo/components/tags/context_tag/schema.cljs @@ -6,7 +6,7 @@ [:type {:optional true} [:maybe [:enum :default :multiuser :group :channel :community :token :network :multinetwork :account - :collectible :address :icon :audio]]] + :collectible :address :icon :audio :wallet-user]]] [:customization-color {:optional true} [:maybe :schema.common/customization-color]] [:container-style {:optional true} [:maybe :map]] [:blur? {:optional true} [:maybe :boolean]] @@ -83,6 +83,10 @@ [:map [:duration {:optional true} [:maybe :string]]]) +(def ^:private ?wallet-user + [:map + [:full-name {:optional true} [:maybe :string]]]) + (def ?schema [:=> [:catn @@ -101,5 +105,6 @@ [:collectible [:merge ?collectible ?size ?context-base]] [:address [:merge ?address ?size ?context-base]] [:icon [:merge ?icon ?size ?context-base]] - [:audio [:merge ?audio ?context-base]]]]] + [:audio [:merge ?audio ?context-base]] + [:wallet-user [:merge ?wallet-user ?size ?context-base]]]]] :any]) diff --git a/src/quo/components/tags/context_tag/view.cljs b/src/quo/components/tags/context_tag/view.cljs index 68fd5e94dc..74d8e87a75 100644 --- a/src/quo/components/tags/context_tag/view.cljs +++ b/src/quo/components/tags/context_tag/view.cljs @@ -3,6 +3,7 @@ [quo.components.avatars.account-avatar.view :as account-avatar] [quo.components.avatars.group-avatar.view :as group-avatar] [quo.components.avatars.user-avatar.view :as user-avatar] + [quo.components.avatars.wallet-user-avatar.view :as wallet-user-avatar] [quo.components.icon :as icons] [quo.components.list-items.preview-list.view :as preview-list] [quo.components.markdown.text :as text] @@ -162,6 +163,13 @@ :icon [icon-tag props] + :wallet-user + [tag-skeleton {:theme theme :size size :text full-name} + [wallet-user-avatar/wallet-user-avatar + {:full-name full-name + :size (if (= size 24) :size-20 :size-24) + :customization-color customization-color}]] + nil)]])) (def view (schema/instrument #'view-internal component-schema/?schema)) diff --git a/src/status_im/contexts/settings/wallet/saved_addresses/events.cljs b/src/status_im/contexts/settings/wallet/saved_addresses/events.cljs new file mode 100644 index 0000000000..27775f9664 --- /dev/null +++ b/src/status_im/contexts/settings/wallet/saved_addresses/events.cljs @@ -0,0 +1,95 @@ +(ns status-im.contexts.settings.wallet.saved-addresses.events + (:require + [status-im.constants :as constants] + [status-im.contexts.wallet.data-store :as data-store] + [taoensso.timbre :as log] + [utils.re-frame :as rf])) + +(defn save-address + [{:keys [db]} + [{:keys [address name customization-color on-success on-error chain-short-names ens] + :or {on-success (fn []) + on-error (fn []) + name "" + customization-color constants/account-default-customization-color + ens "" + ;; the chain short names should be a string like eth: or eth:arb:oeth: + chain-short-names (str constants/mainnet-short-name ":")}}]] + (let [test-networks-enabled? (boolean (get-in db [:profile/profile :test-networks-enabled?])) + address-to-save {:address address + :name name + :colorId customization-color + :ens ens + :isTest test-networks-enabled? + :chainShortNames chain-short-names}] + {:fx [[:json-rpc/call + [{:method "wakuext_upsertSavedAddress" + :params [address-to-save] + :on-success on-success + :on-error on-error}]]]})) + +(rf/reg-event-fx :wallet/save-address save-address) + +(defn get-saved-addresses-success + [{:keys [db]} [raw-saved-addresses]] + (let [saved-addresses (reduce + (fn [result {:keys [address test?] :as saved-address}] + (assoc-in result [(if test? :test :prod) address] saved-address)) + {:test {} + :prod {}} + (data-store/rpc->saved-addresses raw-saved-addresses))] + {:db (assoc-in db [:wallet :saved-addresses] saved-addresses)})) + +(rf/reg-event-fx :wallet/get-saved-addresses-success get-saved-addresses-success) + +(defn saved-addresses-rpc-error + [_ [action error]] + (log/warn (str "[wallet] [saved-addresses] Failed to " action) + {:error error})) + +(rf/reg-event-fx :wallet/saved-addresses-rpc-error saved-addresses-rpc-error) + +(defn get-saved-addresses + [_] + {:fx [[:json-rpc/call + [{:method "wakuext_getSavedAddresses" + :on-success [:wallet/get-saved-addresses-success] + :on-error [:wallet/saved-addresses-rpc-error :get-saved-addresses]}]]]}) + +(rf/reg-event-fx :wallet/get-saved-addresses get-saved-addresses) + +(defn delete-saved-address-success + [_ [toast-message]] + {:fx [[:dispatch [:wallet/get-saved-addresses]] + [:dispatch [:hide-bottom-sheet]] + [:dispatch-later + {:ms 100 + :dispatch [:toasts/upsert + {:type :positive + :theme :dark + :text toast-message}]}]]}) + +(rf/reg-event-fx :wallet/delete-saved-address-success delete-saved-address-success) + +(defn delete-saved-address-failed + [_ [error]] + {:fx [[:dispatch [:hide-bottom-sheet]] + [:dispatch-later + {:ms 100 + :dispatch [:toasts/upsert + {:type :negative + :theme :dark + :text error}]}]]}) + +(rf/reg-event-fx :wallet/delete-saved-address-failed delete-saved-address-failed) + +(defn delete-saved-address + [{:keys [db]} [{:keys [address toast-message]}]] + (let [test-networks-enabled? (boolean (get-in db [:profile/profile :test-networks-enabled?]))] + {:fx [[:json-rpc/call + [{:method "wakuext_deleteSavedAddress" + :params [address test-networks-enabled?] + :on-success [:wallet/delete-saved-address-success toast-message] + :on-error [:wallet/delete-saved-address-failed]}]]]})) + +(rf/reg-event-fx :wallet/delete-saved-address delete-saved-address) diff --git a/src/status_im/contexts/settings/wallet/saved_addresses/events_test.cljs b/src/status_im/contexts/settings/wallet/saved_addresses/events_test.cljs new file mode 100644 index 0000000000..d9f29ce985 --- /dev/null +++ b/src/status_im/contexts/settings/wallet/saved_addresses/events_test.cljs @@ -0,0 +1,152 @@ +(ns status-im.contexts.settings.wallet.saved-addresses.events-test + (:require + [cljs.test :refer-macros [deftest is testing]] + matcher-combinators.test + [status-im.contexts.settings.wallet.saved-addresses.events :as events])) + +(deftest get-saved-addresses-test + (testing "get saved addresses - dispatches RPC call" + (let [cofx {:db {}} + effects (events/get-saved-addresses cofx) + result-fx (:fx effects) + expected-fx [[:json-rpc/call + [{:method "wakuext_getSavedAddresses" + :on-success [:wallet/get-saved-addresses-success] + :on-error [:wallet/saved-addresses-rpc-error :get-saved-addresses]}]]]] + (is (match? expected-fx result-fx))))) + +(def saved-address-1 + {:isTest false + :address "0x1" + :mixedcaseAddress "0x1" + :chainShortNames "eth:arb1:oeth:" + :name "Amy" + :createdAt 1716826806 + :ens "" + :colorId "purple" + :removed false}) + +(def saved-address-2 + {:isTest true + :address "0x2" + :mixedcaseAddress "0x2" + :chainShortNames "eth:arb1:oeth:" + :name "Bob" + :createdAt 1716826714 + :ens "" + :colorId "blue" + :removed false}) + +(deftest get-saved-addresses-success-test + (testing "no saved addresses" + (let [cofx {:db {}} + effects (events/get-saved-addresses-success cofx nil) + result-db (:db effects) + expected-db {:wallet {:saved-addresses {:test {} + :prod {}}}}] + (is (match? expected-db result-db)))) + + (testing "one test saved address" + (let [cofx {:db {}} + effects (events/get-saved-addresses-success cofx [[saved-address-2]]) + result-db (:db effects) + expected-db {:wallet {:saved-addresses + {:test {"0x2" {:test? true + :address "0x2" + :mixedcase-address "0x2" + :chain-short-names "eth:arb1:oeth:" + :has-ens? false + :network-preferences-names #{:arbitrum + :optimism + :mainnet} + :name "Bob" + :created-at 1716826714 + :ens "" + :customization-color :blue + :removed? false}} + :prod {}}}}] + (is (match? expected-db result-db)))) + + (testing "two saved addresses (test and prod)" + (let [cofx {:db {}} + effects (events/get-saved-addresses-success cofx [[saved-address-1 saved-address-2]]) + result-db (:db effects) + expected-db {:wallet {:saved-addresses + {:test {"0x2" {:test? true + :address "0x2" + :mixedcase-address "0x2" + :chain-short-names "eth:arb1:oeth:" + :network-preferences-names #{:arbitrum + :optimism + :mainnet} + :has-ens? false + :name "Bob" + :created-at 1716826714 + :ens "" + :customization-color :blue + :removed? false}} + :prod {"0x1" {:test? false + :address "0x1" + :mixedcase-address "0x1" + :chain-short-names "eth:arb1:oeth:" + :network-preferences-names #{:arbitrum + :optimism + :mainnet} + :has-ens? false + :name "Amy" + :created-at 1716826806 + :ens "" + :customization-color :purple + :removed? false}}}}}] + (is (match? expected-db result-db))))) + +(deftest save-address-test + (testing "save address - dispatches RPC call" + (let [test-networks-enabled? false + cofx {:db {:profile/profile {:test-networks-enabled? + test-networks-enabled?}}} + on-success [:some-success-event] + on-error [:some-failure-event] + name "Bob" + address "0x3" + ens "bobby.eth" + customization-color :yellow + chain-short-names "eth:arb1:oeth:" + args {:on-success on-success + :on-error on-error + :name name + :address address + :customization-color customization-color + :ens ens + :chain-short-names chain-short-names} + effects (events/save-address cofx [args]) + result-fx (:fx effects) + expected-fx [[:json-rpc/call + [{:method "wakuext_upsertSavedAddress" + :params [{:address address + :name name + :colorId customization-color + :ens ens + :isTest test-networks-enabled? + :chainShortNames chain-short-names}] + :on-success on-success + :on-error on-error}]]]] + (is (match? expected-fx result-fx))))) + +(deftest delete-saved-addresses-test + (testing "delete saved addresses - dispatches RPC call" + (let [test-networks-enabled? true + cofx {:db {:profile/profile {:test-networks-enabled? + test-networks-enabled?}}} + address "0x1f69b0904160bf1ce98dabeaf9c2fe147569498d" + toast-message "Saved addresses deleted successfully" + args {:address address + :toast-message toast-message} + effects (events/delete-saved-address cofx [args]) + result-fx (:fx effects) + expected-fx [[:json-rpc/call + [{:method "wakuext_deleteSavedAddress" + :params [address test-networks-enabled?] + :on-success [:wallet/delete-saved-address-success toast-message] + :on-error [:wallet/delete-saved-address-failed]}]]]] + (is (match? expected-fx result-fx))))) diff --git a/src/status_im/contexts/settings/wallet/saved_addresses/sheets/address_options/view.cljs b/src/status_im/contexts/settings/wallet/saved_addresses/sheets/address_options/view.cljs new file mode 100644 index 0000000000..f6a741df3f --- /dev/null +++ b/src/status_im/contexts/settings/wallet/saved_addresses/sheets/address_options/view.cljs @@ -0,0 +1,107 @@ +(ns status-im.contexts.settings.wallet.saved-addresses.sheets.address-options.view + (:require + [clojure.string :as string] + [quo.core :as quo] + [react-native.core :as rn] + [react-native.platform :as platform] + [status-im.common.not-implemented :as not-implemented] + [status-im.constants :as constants] + [status-im.contexts.settings.wallet.saved-addresses.sheets.remove-address.view :as remove-address] + [utils.i18n :as i18n] + [utils.re-frame :as rf])) + +(defn view + [{:keys [name full-address chain-short-names address] :as opts}] + (let [open-send-flow (rn/use-callback + #(rf/dispatch [:wallet/select-send-address + {:address full-address + :recipient full-address + :stack-id :wallet-select-address + :start-flow? true}]) + [full-address]) + open-eth-chain-explorer (rn/use-callback + #(rf/dispatch [:wallet/navigate-to-chain-explorer + {:address address + :network constants/mainnet-network-name}]) + [address]) + open-arb-chain-explorer (rn/use-callback + #(rf/dispatch [:wallet/navigate-to-chain-explorer + {:address address + :network constants/arbitrum-network-name}]) + [address]) + open-oeth-chain-explorer (rn/use-callback + #(rf/dispatch [:wallet/navigate-to-chain-explorer + {:address address + :network constants/optimism-network-name}]) + [address]) + open-share (rn/use-callback + #(rf/dispatch + [:open-share + {:options (if platform/ios? + {:activityItemSources + [{:placeholderItem {:type :text + :content full-address} + :item {:default {:type :text + :content + full-address}} + :linkMetadata {:title full-address}}]} + {:title full-address + :message full-address + :isNewTask true})}]) + [full-address]) + open-remove-confirmation-sheet (rn/use-callback + #(rf/dispatch + [:show-bottom-sheet + {:theme :dark + :shell? true + :content (fn [] + [remove-address/view opts])}]) + [opts])] + [quo/action-drawer + [[{:icon :i/arrow-up + :label (i18n/label :t/send-to-user {:user name}) + :blur? true + :on-press open-send-flow + :accessibility-label :send-to-user} + {:icon :i/link + :right-icon :i/external + :label (i18n/label :t/view-address-on-etherscan) + :blur? true + :on-press open-eth-chain-explorer + :accessibility-label :view-address-on-etherscan} + (when (string/includes? chain-short-names constants/optimism-short-name) + {:icon :i/link + :right-icon :i/external + :label (i18n/label :t/view-address-on-optimistic) + :blur? true + :on-press open-oeth-chain-explorer + :accessibility-label :view-address-on-optimistic}) + (when (string/includes? chain-short-names constants/arbitrum-short-name) + {:icon :i/link + :right-icon :i/external + :label (i18n/label :t/view-address-on-arbiscan) + :blur? true + :on-press open-arb-chain-explorer + :accessibility-label :view-address-on-arbiscan}) + {:icon :i/share + :on-press open-share + :label (i18n/label :t/share-address) + :blur? true + :accessibility-label :share-saved-address} + {:icon :i/qr-code + :label (i18n/label :t/show-address-qr) + :blur? true + :on-press not-implemented/alert + :accessibility-label :show-address-qr-code} + {:icon :i/edit + :label (i18n/label :t/edit-account) + :blur? true + :on-press not-implemented/alert + :accessibility-label :edit-saved-address} + {:icon :i/delete + :label (i18n/label :t/remove-address) + :blur? true + :on-press open-remove-confirmation-sheet + :danger? true + :accessibility-label :remove-saved-address + :add-divider? true}]]])) diff --git a/src/status_im/contexts/settings/wallet/saved_addresses/sheets/remove_address/view.cljs b/src/status_im/contexts/settings/wallet/saved_addresses/sheets/remove_address/view.cljs new file mode 100644 index 0000000000..327d4f0f73 --- /dev/null +++ b/src/status_im/contexts/settings/wallet/saved_addresses/sheets/remove_address/view.cljs @@ -0,0 +1,39 @@ +(ns status-im.contexts.settings.wallet.saved-addresses.sheets.remove-address.view + (:require + [quo.core :as quo] + [react-native.core :as rn] + [utils.i18n :as i18n] + [utils.re-frame :as rf])) + +(defn- hide-bottom-sheet + [] + (rf/dispatch [:hide-bottom-sheet])) + +(defn view + [{:keys [name address customization-color]}] + (let [on-press-remove (rn/use-callback + #(rf/dispatch [:wallet/delete-saved-address + {:address address + :toast-message (i18n/label :t/saved-address-removed)}]) + [address])] + [:<> + [quo/drawer-top + {:title (i18n/label :t/remove-saved-address) + :customization-color customization-color + :type :context-tag + :blur? true + :context-tag-type :wallet-user + :full-name name}] + [quo/text + {:style {:padding-horizontal 20 + :margin-bottom 8}} + (i18n/label :t/remove-saved-address-description {:name name})] + [quo/bottom-actions + {:actions :two-actions + :blur? true + :button-one-label (i18n/label :t/remove) + :button-one-props {:on-press on-press-remove + :type :danger} + :button-two-label (i18n/label :t/cancel) + :button-two-props {:on-press hide-bottom-sheet + :type :grey}}]])) diff --git a/src/status_im/contexts/settings/wallet/saved_addresses/style.cljs b/src/status_im/contexts/settings/wallet/saved_addresses/style.cljs index ba3c5fff91..1c03054ef3 100644 --- a/src/status_im/contexts/settings/wallet/saved_addresses/style.cljs +++ b/src/status_im/contexts/settings/wallet/saved_addresses/style.cljs @@ -2,7 +2,8 @@ (def title-container {:padding-horizontal 20 - :margin-top 12}) + :margin-top 12 + :margin-bottom 16}) (defn page-wrapper [inset-top] diff --git a/src/status_im/contexts/settings/wallet/saved_addresses/view.cljs b/src/status_im/contexts/settings/wallet/saved_addresses/view.cljs index c5274fa066..83aa2f9c8c 100644 --- a/src/status_im/contexts/settings/wallet/saved_addresses/view.cljs +++ b/src/status_im/contexts/settings/wallet/saved_addresses/view.cljs @@ -1,14 +1,16 @@ (ns status-im.contexts.settings.wallet.saved-addresses.view - (:require [quo.core :as quo] - [quo.theme :as quo.theme] - [react-native.core :as rn] - [react-native.safe-area :as safe-area] - [status-im.common.resources :as resources] - [status-im.contexts.settings.wallet.saved-addresses.style :as style] - [utils.i18n :as i18n] - [utils.re-frame :as rf])) + (:require + [quo.core :as quo] + [quo.theme :as quo.theme] + [react-native.core :as rn] + [react-native.safe-area :as safe-area] + [status-im.common.resources :as resources] + [status-im.contexts.settings.wallet.saved-addresses.sheets.address-options.view :as address-options] + [status-im.contexts.settings.wallet.saved-addresses.style :as style] + [utils.i18n :as i18n] + [utils.re-frame :as rf])) -(defn empty-state +(defn- empty-state [] (let [theme (quo.theme/use-theme)] [quo/empty-state @@ -17,12 +19,51 @@ :image (resources/get-themed-image :sweating-man theme) :container-style style/empty-container-style}])) +(defn- saved-address + [{:keys [name address chain-short-names customization-color has-ens? ens]}] + (let [full-address (str chain-short-names address) + on-press-saved-address (rn/use-callback + #(rf/dispatch + [:show-bottom-sheet + {:theme :dark + :shell? true + :content (fn [] + [address-options/view + {:address address + :chain-short-names chain-short-names + :full-address full-address + :name name + :customization-color customization-color}])}]) + [address chain-short-names full-address name customization-color])] + [quo/saved-address + {:user-props {:name name + :address full-address + :ens (when has-ens? ens) + :customization-color customization-color + :blur? true} + :container-style {:margin-horizontal 8} + :on-press on-press-saved-address}])) + +(defn- header + [{:keys [title]}] + [quo/divider-label + {:tight? true + :blur? true} + title]) + +(defn- footer + [] + [rn/view {:height 8}]) + +(defn- navigate-back + [] + (rf/dispatch [:navigate-back])) + (defn view [] (let [inset-top (safe-area/get-top) customization-color (rf/sub [:profile/customization-color]) - saved-addresses [] - navigate-back (rn/use-callback #(rf/dispatch [:navigate-back]))] + saved-addresses (rf/sub [:wallet/grouped-saved-addresses])] [quo/overlay {:type :shell :container-style (style/page-wrapper inset-top)} @@ -38,5 +79,13 @@ :right :action :customization-color customization-color :icon :i/add}]] - (when-not (seq saved-addresses) - [empty-state])])) + [rn/section-list + {:key-fn :title + :sticky-section-headers-enabled false + :render-section-header-fn header + :render-section-footer-fn footer + :sections saved-addresses + :render-fn saved-address + :separator [rn/view {:style {:height 4}}] + :content-container-style {:flex-grow 1} + :empty-component [empty-state]}]])) diff --git a/src/status_im/contexts/settings/wallet/wallet_options/view.cljs b/src/status_im/contexts/settings/wallet/wallet_options/view.cljs index 9c0792806e..d5573ae8fe 100644 --- a/src/status_im/contexts/settings/wallet/wallet_options/view.cljs +++ b/src/status_im/contexts/settings/wallet/wallet_options/view.cljs @@ -21,10 +21,11 @@ :blur? true :on-press open-keypairs-and-accounts-settings-modal :action :arrow}) - {:title (i18n/label :t/saved-addresses) - :blur? true - :on-press open-saved-addresses-settings-modal - :action :arrow}]) + (when (ff/enabled? ::ff/settings.saved-addresses) + {:title (i18n/label :t/saved-addresses) + :blur? true + :on-press open-saved-addresses-settings-modal + :action :arrow})]) (defn basic-settings [] diff --git a/src/status_im/contexts/wallet/data_store.cljs b/src/status_im/contexts/wallet/data_store.cljs index f52c5264d8..8cecb3348c 100644 --- a/src/status_im/contexts/wallet/data_store.cljs +++ b/src/status_im/contexts/wallet/data_store.cljs @@ -5,6 +5,7 @@ [clojure.set :as set] [clojure.string :as string] [status-im.constants :as constants] + [status-im.contexts.wallet.common.utils.networks :as network-utils] [utils.money :as money] [utils.number :as utils.number] [utils.transforms :as transforms])) @@ -136,3 +137,33 @@ [keypairs] (let [renamed-data (sort-and-rename-keypairs keypairs)] (cske/transform-keys csk/->kebab-case-keyword renamed-data))) + +(defn- network-short-names->full-names + [short-names-string] + (->> (string/split short-names-string constants/chain-id-separator) + (map network-utils/short-name->network) + (remove nil?) + set)) + +(defn- add-keys-to-saved-address + [saved-address] + (-> saved-address + (assoc :network-preferences-names + (network-short-names->full-names (:chain-short-names saved-address))) + (assoc :has-ens? (not (string/blank? (:ens saved-address)))))) + +(defn rpc->saved-address + [saved-address] + (-> saved-address + (set/rename-keys {:chainShortNames :chain-short-names + :isTest :test? + :createdAt :created-at + :colorId :customization-color + :mixedcaseAddress :mixedcase-address + :removed :removed?}) + (update :customization-color keyword) + add-keys-to-saved-address)) + +(defn rpc->saved-addresses + [saved-addresses] + (map rpc->saved-address saved-addresses)) diff --git a/src/status_im/contexts/wallet/events.cljs b/src/status_im/contexts/wallet/events.cljs index 5ca07040eb..cb72a38070 100644 --- a/src/status_im/contexts/wallet/events.cljs +++ b/src/status_im/contexts/wallet/events.cljs @@ -6,6 +6,7 @@ [status-im.constants :as constants] [status-im.contexts.settings.wallet.effects] [status-im.contexts.settings.wallet.events] + [status-im.contexts.wallet.common.utils.external-links :as external-links] [status-im.contexts.wallet.common.utils.networks :as network-utils] [status-im.contexts.wallet.data-store :as data-store] [status-im.contexts.wallet.db :as db] @@ -343,6 +344,14 @@ {:fx [[:dispatch [:hide-bottom-sheet]] [:dispatch [:browser.ui/open-url (str explorer-link "/" address)]]]})) +(rf/reg-event-fx + :wallet/navigate-to-chain-explorer + (fn [{:keys [db]} [{:keys [network chain-id address]}]] + (let [chain-id (or chain-id (network-utils/network->chain-id db network)) + explorer-link (external-links/get-explorer-url-by-chain-id chain-id)] + {:fx [[:dispatch [:hide-bottom-sheet]] + [:dispatch [:browser.ui/open-url (str explorer-link "/" address)]]]}))) + (rf/reg-event-fx :wallet/reload (fn [_] {:fx [[:dispatch-n [[:wallet/get-wallet-token]]]]})) @@ -374,7 +383,8 @@ {:fx [[:dispatch [:wallet/start-wallet]] [:dispatch [:wallet/get-ethereum-chains]] [:dispatch [:wallet/get-accounts]] - [:dispatch [:wallet/get-keypairs]]]})) + [:dispatch [:wallet/get-keypairs]] + [:dispatch [:wallet/get-saved-addresses]]]})) (rf/reg-event-fx :wallet/share-account (fn [_ [{:keys [content title]}]] diff --git a/src/status_im/contexts/wallet/save_address/events.cljs b/src/status_im/contexts/wallet/save_address/events.cljs deleted file mode 100644 index 54ec902959..0000000000 --- a/src/status_im/contexts/wallet/save_address/events.cljs +++ /dev/null @@ -1,45 +0,0 @@ -(ns status-im.contexts.wallet.save-address.events - (:require - [status-im.constants :as constants] - [utils.re-frame :as rf])) - -(rf/reg-event-fx - :wallet/save-address - (fn [_ - [{:keys [address name customization-color on-success on-error chain-short-names ens test?] - :or {on-success (fn []) - on-error (fn []) - name "" - ens "" - test? false - ;; the chain short names should be a string like eth: or eth:arb:oeth: - chain-short-names (str constants/mainnet-short-name ":")}}]] - (let [address-to-save {:address address - :name name - :color-id customization-color - :ens ens - :is-test test? - :chain-short-names chain-short-names}] - {:json-rpc/call - [{:method "wakuext_upsertSavedAddress" - :params [address-to-save] - :on-success on-success - :on-error on-error}]}))) - -(rf/reg-event-fx - :wallet/get-saved-addresses-success - (fn [{:keys [db]} [saved-addresses]] - {:db (assoc-in db [:wallet :saved-addresses] saved-addresses)})) - -(rf/reg-event-fx - :wallet/get-saved-addresses-error - (fn [{:keys [db]} [err]] - {:db (assoc db [:wallet :get-saved-addresses-error] err)})) - -(rf/reg-event-fx - :wallet/get-saved-addresses - (fn [_ _] - {:json-rpc/call - [{:method "wakuext_getSavedAddresses" - :on-success [:wallet/get-saved-addresses-success] - :on-error [:wallet/get-saved-addresses-error]}]})) diff --git a/src/status_im/events.cljs b/src/status_im/events.cljs index b66ec418ed..1140b2f44f 100644 --- a/src/status_im/events.cljs +++ b/src/status_im/events.cljs @@ -28,6 +28,7 @@ status-im.contexts.onboarding.events status-im.contexts.profile.events status-im.contexts.profile.settings.events + status-im.contexts.settings.wallet.saved-addresses.events status-im.contexts.shell.qr-reader.events status-im.contexts.shell.share.events status-im.contexts.syncing.events diff --git a/src/status_im/feature_flags.cljs b/src/status_im/feature_flags.cljs index d5642e42c4..71d293caa9 100644 --- a/src/status_im/feature_flags.cljs +++ b/src/status_im/feature_flags.cljs @@ -16,6 +16,8 @@ :FLAG_WALLET_SETTINGS_KEYPAIRS_AND_ACCOUNTS_ENABLED) ::settings.network-settings (enabled-in-env? :FLAG_WALLET_SETTINGS_NETWORK_SETTINGS_ENABLED) + ::settings.saved-addresses (enabled-in-env? + :FLAG_WALLET_SETTINGS_SAVED_ADDRESSES_ENABLED) ::shell.jump-to (enabled-in-env? :ENABLE_JUMP_TO) ::wallet.assets-modal-hide (enabled-in-env? :FLAG_ASSETS_MODAL_HIDE) ::wallet.assets-modal-manage-tokens (enabled-in-env? :FLAG_ASSETS_MODAL_MANAGE_TOKENS) diff --git a/src/status_im/subs/wallet/saved_addresses.cljs b/src/status_im/subs/wallet/saved_addresses.cljs index 8b48bf730d..660065cde9 100644 --- a/src/status_im/subs/wallet/saved_addresses.cljs +++ b/src/status_im/subs/wallet/saved_addresses.cljs @@ -1,5 +1,6 @@ (ns status-im.subs.wallet.saved-addresses (:require + [clojure.string :as string] [re-frame.core :as rf])) (rf/reg-sub @@ -13,3 +14,21 @@ (fn [wallet [address]] (some #(= address (:address %)) (:saved-addresses wallet)))) + +(rf/reg-sub + :wallet/saved-addresses-by-network-mode + :<- [:wallet/saved-addresses] + :<- [:profile/test-networks-enabled?] + (fn [[saved-addresses test-networks-enabled?]] + (vals (get saved-addresses (if test-networks-enabled? :test :prod))))) + +(rf/reg-sub + :wallet/grouped-saved-addresses + :<- [:wallet/saved-addresses-by-network-mode] + (fn [saved-addresses] + (->> saved-addresses + (sort-by :name) + (group-by #(string/upper-case (first (:name %)))) + (map (fn [[k v]] + {:title k + :data v}))))) diff --git a/src/status_im/subs/wallet/saved_addresses_test.cljs b/src/status_im/subs/wallet/saved_addresses_test.cljs new file mode 100644 index 0000000000..200c66bc25 --- /dev/null +++ b/src/status_im/subs/wallet/saved_addresses_test.cljs @@ -0,0 +1,103 @@ +(ns status-im.subs.wallet.saved-addresses-test + (:require [clojure.test :refer [is testing]] + [re-frame.db :as rf-db] + [test-helpers.unit :as h] + [utils.re-frame :as rf])) + +(def saved-addresses-db + {:test {"0x1" {:test? true + :address "0x1" + :mixedcase-address "0x1" + :chain-short-names "eth:arb1:oeth:" + :has-ens? false + :network-preferences-names #{:arbitrum + :optimism + :mainnet} + :name "Alice" + :created-at 1716826714 + :ens "" + :customization-color :blue + :removed? false} + "0x2" {:test? true + :address "0x2" + :mixedcase-address "0x2" + :chain-short-names "eth:" + :has-ens? false + :network-preferences-names #{:mainnet} + :name "Bob" + :created-at 1716828825 + :ens "" + :customization-color :purple + :removed? false}} + :prod {"0x1" {:test? false + :address "0x1" + :mixedcase-address "0x1" + :chain-short-names "eth:" + :has-ens? false + :network-preferences-names #{:mainnet} + :name "Alice" + :created-at 1716826745 + :ens "" + :customization-color :blue + :removed? false} + "0x2" {:test? false + :address "0x2" + :mixedcase-address "0x2" + :chain-short-names "eth:arb1:oeth:" + :has-ens? false + :network-preferences-names #{:arbitrum + :optimism + :mainnet} + :name "Bob" + :created-at 1716828561 + :ens "" + :customization-color :purple + :removed? false}}}) + +(def grouped-saved-addresses-data + [{:title "A" + :data + [{:test? false + :address "0x1" + :mixedcase-address "0x1" + :chain-short-names "eth:" + :has-ens? false + :network-preferences-names #{:mainnet} + :name "Alice" + :created-at 1716826745 + :ens "" + :customization-color :blue + :removed? false}]} + {:title "B" + :data + [{:test? false + :address "0x2" + :mixedcase-address "0x2" + :chain-short-names "eth:arb1:oeth:" + :has-ens? false + :network-preferences-names #{:arbitrum + :optimism + :mainnet} + :name "Bob" + :created-at 1716828561 + :ens "" + :customization-color :purple + :removed? false}]}]) + +(h/deftest-sub :wallet/saved-addresses-by-network-mode + [sub-name] + (testing "returns saved addresses by network mode" + (swap! rf-db/app-db + #(-> % + (assoc-in [:wallet :saved-addresses] saved-addresses-db) + (assoc-in [:profile/profile :test-networks-enabled?] true))) + (is (= (vals (:test saved-addresses-db)) (rf/sub [sub-name]))))) + +(h/deftest-sub :wallet/grouped-saved-addresses + [sub-name] + (testing "returns saved addresses grouped by name" + (swap! rf-db/app-db + #(-> % + (assoc-in [:wallet :saved-addresses] saved-addresses-db) + (assoc-in [:profile/profile :test-networks-enabled?] false))) + (is (= grouped-saved-addresses-data (rf/sub [sub-name]))))) diff --git a/translations/en.json b/translations/en.json index 560f6c0c0c..4bb23a9146 100644 --- a/translations/en.json +++ b/translations/en.json @@ -2658,5 +2658,13 @@ "proceed-anyway": "Proceed anyway", "sending-to-unpreferred-networks": "Sending to unpreferred networks", "review-send": "Review send", - "review-bridge": "Review bridge" + "review-bridge": "Review bridge", + "send-to-user": "Send to {{user}}", + "view-address-on-etherscan": "View address on Etherscan", + "view-address-on-optimistic": "View address on Optimistic", + "view-address-on-arbiscan": "View address on Arbiscan", + "saved-address-removed": "Saved address removed", + "remove-address": "Remove address", + "remove-saved-address": "Remove saved address", + "remove-saved-address-description": "Transaction history relating to this address will no longer be labelled ‘{{name}}’." }