feat: Saved addresses - Display list, Show options and Remove (#20221)

This commit:
- Adds feature flag for saved addresses
- Displays the list of saved addresses in wallet settings
- Shows address options on tap of any saved address
- Adds the ability to remove saved address
- Refactors saved addresses data structure in app-db

Signed-off-by: Mohamed Javid <19339952+smohamedjavid@users.noreply.github.com>
This commit is contained in:
Mohamed Javid 2024-05-31 00:51:51 +05:30 committed by GitHub
parent e04c7f449f
commit aa159f53ef
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
19 changed files with 674 additions and 82 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -2,7 +2,8 @@
(def title-container
{:padding-horizontal 20
:margin-top 12})
:margin-top 12
:margin-bottom 16})
(defn page-wrapper
[inset-top]

View File

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

View File

@ -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
[]

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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}}."
}