feat: enable biometrics for standard-auth (#17992)

This commit is contained in:
Jamie Caprani 2023-12-07 16:44:16 +00:00 committed by GitHub
parent 6093d56c90
commit 55a99da2f5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 143 additions and 149 deletions

View File

@ -46,6 +46,7 @@
[status-im.wallet.core :as wallet] [status-im.wallet.core :as wallet]
status-im.wallet.custom-tokens.core status-im.wallet.custom-tokens.core
[status-im2.common.biometric.events :as biometric] [status-im2.common.biometric.events :as biometric]
status-im2.common.standard-authentication.events
[status-im2.common.theme.core :as theme] [status-im2.common.theme.core :as theme]
[status-im2.common.universal-links :as universal-links] [status-im2.common.universal-links :as universal-links]
[status-im2.constants :as constants] [status-im2.constants :as constants]

View File

@ -1,7 +1,7 @@
(ns status-im2.common.standard-authentication.core (ns status-im2.common.standard-authentication.core
(:require (:require
status-im2.common.standard-authentication.standard-auth.button.view status-im2.common.standard-authentication.password-input.view
status-im2.common.standard-authentication.standard-auth.slide-button.view)) status-im2.common.standard-authentication.standard-auth.slide-button.view))
(def button status-im2.common.standard-authentication.standard-auth.button.view/view) (def password-input status-im2.common.standard-authentication.password-input.view/view)
(def slide-button status-im2.common.standard-authentication.standard-auth.slide-button.view/view) (def slide-button status-im2.common.standard-authentication.standard-auth.slide-button.view/view)

View File

@ -9,7 +9,7 @@
[utils.re-frame :as rf])) [utils.re-frame :as rf]))
(defn view (defn view
[{:keys [on-enter-password button-label button-icon-left]}] [{:keys [on-enter-password on-press-biometrics button-label button-icon-left]}]
(let [{:keys [key-uid customization-color] :as profile} (rf/sub [:profile/profile-with-image]) (let [{:keys [key-uid customization-color] :as profile} (rf/sub [:profile/profile-with-image])
{:keys [error processing password]} (rf/sub [:profile/login]) {:keys [error processing password]} (rf/sub [:profile/login])
sign-in-enabled? (rf/sub [:sign-in-enabled?])] sign-in-enabled? (rf/sub [:sign-in-enabled?])]
@ -32,20 +32,21 @@
:customization-color customization-color :customization-color customization-color
:size 24}]] :size 24}]]
[password-input/view [password-input/view
{:processing processing {:on-press-biometrics on-press-biometrics
:error error :processing processing
:default-password password :error error
:sign-in-enabled? sign-in-enabled?}] :default-password password
[rn/view {:style style/enter-password-button} :sign-in-enabled? sign-in-enabled?}]
[quo/button [quo/button
{:size 40 {:size 40
:type :primary :container-style style/enter-password-button
:customization-color (or customization-color :primary) :type :primary
:accessibility-label :login-button :customization-color (or customization-color :primary)
:icon-left button-icon-left :accessibility-label :login-button
:disabled? (or (not sign-in-enabled?) processing) :icon-left button-icon-left
:on-press (fn [] :disabled? (or (not sign-in-enabled?) processing)
(rf/dispatch [:set-in [:profile/login :key-uid] key-uid]) :on-press (fn []
(rf/dispatch [:profile.login/verify-database-password password (rf/dispatch [:set-in [:profile/login :key-uid] key-uid])
#(on-enter-password password)]))} (rf/dispatch [:profile.login/verify-database-password password
button-label]]]]])) #(on-enter-password password)]))}
button-label]]]]))

View File

@ -0,0 +1,8 @@
(ns status-im2.common.standard-authentication.events
(:require
[utils.re-frame :as rf]))
(rf/reg-event-fx :standard-auth/on-biometric-success
(fn [{:keys [db]} [callback]]
(let [key-uid (get-in db [:profile/profile :key-uid])]
{:fx [[:keychain/get-user-password [key-uid callback]]]})))

View File

@ -4,3 +4,7 @@
{:margin-top 8 {:margin-top 8
:flex-direction :row :flex-direction :row
:align-items :center}) :align-items :center})
(def auth-button
{:align-self :flex-end
:margin-left 8})

View File

@ -27,21 +27,30 @@
(rf/dispatch [:set-in [:profile/login :error] ""])) (rf/dispatch [:set-in [:profile/login :error] ""]))
(defn- view-internal (defn- view-internal
[{:keys [default-password theme shell?]}] [{:keys [default-password theme shell? on-press-biometrics]}]
(let [{:keys [error processing]} (rf/sub [:profile/login]) (let [{:keys [error processing]} (rf/sub [:profile/login])
error-message (get-error-message error) error-message (get-error-message error)
error? (boolean (seq error-message))] error? (boolean (seq error-message))]
[:<> [:<>
[quo/input [rn/view {:style {:flex-direction :row}}
{:type :password [quo/input
:blur? true {:container-style {:flex 1}
:disabled? processing :type :password
:placeholder (i18n/label :t/type-your-password) :blur? true
:auto-focus true :disabled? processing
:error? error? :placeholder (i18n/label :t/type-your-password)
:label (i18n/label :t/profile-password) :auto-focus true
:on-change-text on-change-password :error? error?
:default-value (security/safe-unmask-data default-password)}] :label (i18n/label :t/profile-password)
:on-change-text on-change-password
:default-value (security/safe-unmask-data default-password)}]
(when on-press-biometrics
[quo/button
{:container-style style/auth-button
:on-press on-press-biometrics
:icon-only? true
:type :outline}
:i/face-id])]
(when error? (when error?
[rn/view {:style style/error-message} [rn/view {:style style/error-message}
[quo/info-message [quo/info-message
@ -60,10 +69,7 @@
:shell? shell?}]))} :shell? shell?}]))}
[rn/text [rn/text
{:style {:text-decoration-line :underline {:style {:text-decoration-line :underline
:color (colors/theme-colors :color (colors/resolve-color :danger theme)}
(colors/custom-color :danger 50)
(colors/custom-color :danger 60)
theme)}
:size :paragraph-2 :size :paragraph-2
:suppress-highlighting true} :suppress-highlighting true}
(i18n/label :t/forgot-password)]]])])) (i18n/label :t/forgot-password)]]])]))

View File

@ -1,10 +1,12 @@
(ns status-im2.common.standard-authentication.standard-auth.authorize (ns status-im2.common.standard-authentication.standard-auth.authorize
(:require (:require
[native-module.core :as native-module]
[react-native.touch-id :as biometric] [react-native.touch-id :as biometric]
[status-im2.common.standard-authentication.enter-password.view :as enter-password] [status-im2.common.standard-authentication.enter-password.view :as enter-password]
[taoensso.timbre :as log] [taoensso.timbre :as log]
[utils.i18n :as i18n] [utils.i18n :as i18n]
[utils.re-frame :as rf])) [utils.re-frame :as rf]
[utils.security.core :as security]))
(defn reset-password (defn reset-password
[] []
@ -12,33 +14,49 @@
(rf/dispatch [:set-in [:profile/login :error] ""])) (rf/dispatch [:set-in [:profile/login :error] ""]))
(defn authorize (defn authorize
[{:keys [on-enter-password biometric-auth? on-auth-success on-auth-fail on-close [{:keys [biometric-auth? on-auth-success on-auth-fail on-close
auth-button-label theme blur? auth-button-icon-left]}] auth-button-label theme blur? auth-button-icon-left]}]
(biometric/get-supported-type (let [handle-auth-success (fn [biometric?]
(fn [biometric-type] (fn [entered-password]
(if (and biometric-auth? biometric-type) (let [sha3-pwd (if biometric?
(biometric/authenticate (str (security/safe-unmask-data entered-password))
{:reason (i18n/label :t/biometric-auth-confirm-message) (native-module/sha3 (str (security/safe-unmask-data
:on-success (fn [response] entered-password))))]
(when on-auth-success (on-auth-success response)) (on-auth-success sha3-pwd))))
(log/info "response" response)) password-login (fn [{:keys [on-press-biometrics]}]
:on-fail (fn [error] (rf/dispatch [:show-bottom-sheet
(log/error "Authentication Failed. Error:" error) {:on-close on-close
(when on-auth-fail (on-auth-fail error)) :theme theme
(rf/dispatch [:show-bottom-sheet :shell? blur?
{:theme theme :content (fn []
:shell? blur? [enter-password/view
:content (fn [] {:on-enter-password (handle-auth-success
[enter-password/view false)
{:on-enter-password on-enter-password}])}]))}) :on-press-biometrics on-press-biometrics
(do :button-icon-left auth-button-icon-left
(reset-password) :button-label auth-button-label}])}]))
(rf/dispatch [:show-bottom-sheet ; biometrics-login recursively passes itself as a parameter because if the user
{:on-close on-close ; fails biometric auth they will be shown the password bottom sheet with an option
:theme theme ; to retrigger biometric auth, so they can endlessly repeat this cycle.
:shell? blur? biometrics-login (fn [on-press-biometrics]
:content (fn [] (rf/dispatch [:dismiss-keyboard])
[enter-password/view (biometric/authenticate
{:on-enter-password on-enter-password {:reason (i18n/label :t/biometric-auth-confirm-message)
:button-icon-left auth-button-icon-left :on-success (fn [_response]
:button-label auth-button-label}])}])))))) (on-close)
(rf/dispatch [:standard-auth/on-biometric-success
(handle-auth-success true)]))
:on-fail (fn [error]
(on-close)
(log/error "Authentication Failed. Error:" error)
(when on-auth-fail (on-auth-fail error))
(password-login {:on-press-biometrics
#(on-press-biometrics
on-press-biometrics)}))}))]
(biometric/get-supported-type
(fn [biometric-type]
(if (and biometric-auth? biometric-type)
(biometrics-login biometrics-login)
(do
(reset-password)
(password-login {})))))))

View File

@ -1,44 +0,0 @@
(ns status-im2.common.standard-authentication.standard-auth.button.view
(:require
[quo.core :as quo]
[quo.theme :as quo.theme]
[reagent.core :as reagent]
[status-im2.common.standard-authentication.standard-auth.authorize :as authorize]))
(defn- view-internal
[_]
(let [reset-slider? (reagent/atom false)
on-close #(reset! reset-slider? true)]
(fn [{:keys [biometric-auth?
customization-color
auth-button-label
on-enter-password
on-auth-success
on-press
on-auth-fail
auth-button-icon-left
size
button-label
theme
blur?
container-style
icon-left]}]
[quo/button
{:size size
:container-style container-style
:customization-color customization-color
:icon-left icon-left
:on-press (if on-press
on-press
#(authorize/authorize {:on-close on-close
:auth-button-icon-left auth-button-icon-left
:theme theme
:blur? blur?
:on-enter-password on-enter-password
:biometric-auth? biometric-auth?
:on-auth-success on-auth-success
:on-auth-fail on-auth-fail
:auth-button-label auth-button-label}))}
button-label])))
(def view (quo.theme/with-theme view-internal))

View File

@ -4,17 +4,21 @@
[quo.theme :as quo.theme] [quo.theme :as quo.theme]
[react-native.core :as rn] [react-native.core :as rn]
[reagent.core :as reagent] [reagent.core :as reagent]
[status-im2.common.standard-authentication.standard-auth.authorize :as authorize])) [status-im2.common.standard-authentication.standard-auth.authorize :as authorize]
[utils.re-frame :as rf]))
(defn- view-internal (defn- view-internal
[_] [_]
(let [reset-slider? (reagent/atom false) (let [reset-slider? (reagent/atom false)
on-close #(reset! reset-slider? true)] on-close (fn []
(fn [{:keys [biometric-auth? (js/setTimeout
track-text #(reset! reset-slider? true)
200))
auth-method (rf/sub [:auth-method])
biometric-auth? (= auth-method "biometric")]
(fn [{:keys [track-text
customization-color customization-color
auth-button-label auth-button-label
on-enter-password
on-auth-success on-auth-success
on-auth-fail on-auth-fail
auth-button-icon-left auth-button-icon-left
@ -32,7 +36,6 @@
:auth-button-icon-left auth-button-icon-left :auth-button-icon-left auth-button-icon-left
:theme theme :theme theme
:blur? blur? :blur? blur?
:on-enter-password on-enter-password
:biometric-auth? biometric-auth? :biometric-auth? biometric-auth?
:on-auth-success on-auth-success :on-auth-success on-auth-success
:on-auth-fail on-auth-fail :on-auth-fail on-auth-fail

View File

@ -7,7 +7,6 @@
[status-im2.common.parallax.view :as parallax] [status-im2.common.parallax.view :as parallax]
[status-im2.common.parallax.whitelist :as whitelist] [status-im2.common.parallax.whitelist :as whitelist]
[status-im2.common.resources :as resources] [status-im2.common.resources :as resources]
[status-im2.common.standard-authentication.core :as standard-auth]
[status-im2.contexts.onboarding.enable-biometrics.style :as style] [status-im2.contexts.onboarding.enable-biometrics.style :as style]
[status-im2.navigation.state :as state] [status-im2.navigation.state :as state]
[utils.i18n :as i18n] [utils.i18n :as i18n]
@ -31,13 +30,13 @@
(rf/sub [:profile/customization-color])) (rf/sub [:profile/customization-color]))
syncing-results? (= :syncing-results @state/root-id)] syncing-results? (= :syncing-results @state/root-id)]
[rn/view {:style (style/buttons insets)} [rn/view {:style (style/buttons insets)}
[standard-auth/button [quo/button
{:size 40 {:size 40
:accessibility-label :enable-biometrics-button :accessibility-label :enable-biometrics-button
:icon-left :i/face-id :icon-left :i/face-id
:customization-color profile-color :customization-color profile-color
:on-press #(rf/dispatch [:onboarding/enable-biometrics]) :on-press #(rf/dispatch [:onboarding/enable-biometrics])}
:button-label (i18n/label :t/biometric-enable-button {:bio-type-label bio-type-label})}] (i18n/label :t/biometric-enable-button {:bio-type-label bio-type-label})]
[quo/button [quo/button
{:accessibility-label :maybe-later-button {:accessibility-label :maybe-later-button
:background :blur :background :blur

View File

@ -133,13 +133,18 @@
{:db (dissoc db :onboarding/profile) {:db (dissoc db :onboarding/profile)
:dispatch [:navigate-to-within-stack [:create-profile :new-to-status]]}) :dispatch [:navigate-to-within-stack [:create-profile :new-to-status]]})
(rf/reg-event-fx :onboarding/set-auth-method
(fn [{:keys [db]} [auth-method]]
{:db (assoc db :auth-method auth-method)}))
(rf/defn onboarding-new-account-finalize-setup (rf/defn onboarding-new-account-finalize-setup
{:events [:onboarding/finalize-setup]} {:events [:onboarding/finalize-setup]}
[{:keys [db]}] [{:keys [db]}]
(let [masked-password (get-in db [:onboarding/profile :password]) (let [masked-password (get-in db [:onboarding/profile :password])
key-uid (get-in db [:profile/profile :key-uid]) key-uid (get-in db [:profile/profile :key-uid])
syncing? (get-in db [:onboarding/profile :syncing?]) syncing? (get-in db [:onboarding/profile :syncing?])
biometric-enabled? (= (get-in db [:onboarding/profile :auth-method]) auth-method (get-in db [:onboarding/profile :auth-method])
biometric-enabled? (= auth-method
constants/auth-method-biometric)] constants/auth-method-biometric)]
(cond-> {:db (assoc db :onboarding/generated-keys? true)} (cond-> {:db (assoc db :onboarding/generated-keys? true)}
biometric-enabled? biometric-enabled?
@ -149,9 +154,9 @@
masked-password masked-password
(security/hash-masked-password masked-password)) (security/hash-masked-password masked-password))
:on-success (fn [] :on-success (fn []
(if syncing? (rf/dispatch [:onboarding/set-auth-method auth-method])
(rf/dispatch [:onboarding/navigate-to-enable-notifications]) (when syncing?
(log/error "successfully saved biometrics"))) (rf/dispatch [:onboarding/navigate-to-enable-notifications])))
:on-error #(log/error "failed to save biometrics" :on-error #(log/error "failed to save biometrics"
{:key-uid key-uid {:key-uid key-uid
:error %})})))) :error %})}))))

View File

@ -7,7 +7,7 @@
[react-native.safe-area :as safe-area] [react-native.safe-area :as safe-area]
[reagent.core :as reagent] [reagent.core :as reagent]
[status-im2.common.confirmation-drawer.view :as confirmation-drawer] [status-im2.common.confirmation-drawer.view :as confirmation-drawer]
[status-im2.common.standard-authentication.password-input.view :as password-input] [status-im2.common.standard-authentication.core :as standard-authentication]
[status-im2.config :as config] [status-im2.config :as config]
[status-im2.constants :as constants] [status-im2.constants :as constants]
[status-im2.contexts.onboarding.common.background.view :as background] [status-im2.contexts.onboarding.common.background.view :as background]
@ -205,7 +205,7 @@
:customization-color (or customization-color :primary) :customization-color (or customization-color :primary)
:profile-picture profile-picture :profile-picture profile-picture
:card-style style/login-profile-card}] :card-style style/login-profile-card}]
[password-input/view [standard-authentication/password-input
{:shell? true {:shell? true
:default-password password}]] :default-password password}]]
[quo/button [quo/button

View File

@ -12,7 +12,6 @@
[status-im2.contexts.syncing.utils :as sync-utils] [status-im2.contexts.syncing.utils :as sync-utils]
[taoensso.timbre :as log] [taoensso.timbre :as log]
[utils.re-frame :as rf] [utils.re-frame :as rf]
[utils.security.core :as security]
[utils.transforms :as transforms])) [utils.transforms :as transforms]))
(rf/defn local-pairing-update-role (rf/defn local-pairing-update-role
@ -97,7 +96,7 @@
(rf/defn preparations-for-connection-string (rf/defn preparations-for-connection-string
{:events [:syncing/get-connection-string]} {:events [:syncing/get-connection-string]}
[{:keys [db] :as cofx} entered-password on-valid-connection-string] [{:keys [db] :as cofx} sha3-pwd on-valid-connection-string]
(let [error (get-in db [:profile/login :error]) (let [error (get-in db [:profile/login :error])
handle-connection (fn [response] handle-connection (fn [response]
(when (sync-utils/valid-connection-string? response) (when (sync-utils/valid-connection-string? response)
@ -105,8 +104,7 @@
(rf/dispatch [:syncing/update-role constants/local-pairing-role-sender]) (rf/dispatch [:syncing/update-role constants/local-pairing-role-sender])
(rf/dispatch [:hide-bottom-sheet])))] (rf/dispatch [:hide-bottom-sheet])))]
(when-not (and error (string/blank? error)) (when-not (and error (string/blank? error))
(let [key-uid (get-in db [:profile/login :key-uid]) (let [key-uid (get-in db [:profile/profile :key-uid])
sha3-pwd (native-module/sha3 (str (security/safe-unmask-data entered-password)))
config-map (.stringify js/JSON config-map (.stringify js/JSON
(clj->js {:senderConfig {:keyUID key-uid (clj->js {:senderConfig {:keyUID key-uid
:keystorePath "" :keystorePath ""

View File

@ -53,7 +53,7 @@
(reset! code nil) (reset! code nil)
(reset! timestamp nil) (reset! timestamp nil)
(reset! valid-for-ms code-valid-for-ms)) (reset! valid-for-ms code-valid-for-ms))
on-enter-password (fn [entered-password] on-auth-success (fn [entered-password]
(rf/dispatch [:syncing/get-connection-string entered-password (rf/dispatch [:syncing/get-connection-string entered-password
set-code]))] set-code]))]
(fn [] (fn []
@ -124,8 +124,7 @@
:size :size-40 :size :size-40
:track-text (i18n/label :t/slide-to-reveal-code) :track-text (i18n/label :t/slide-to-reveal-code)
:customization-color customization-color :customization-color customization-color
:on-enter-password on-enter-password :on-auth-success on-auth-success
:biometric-auth? false
:auth-button-label (i18n/label :t/reveal-sync-code) :auth-button-label (i18n/label :t/reveal-sync-code)
:auth-button-icon-left :i/reveal}]])]] :auth-button-icon-left :i/reveal}]])]]
[rn/view {:style style/sync-code} [rn/view {:style style/sync-code}

View File

@ -43,8 +43,8 @@
:bottom-action-props {:customization-color @account-color :bottom-action-props {:customization-color @account-color
:disabled? (string/blank? @account-name) :disabled? (string/blank? @account-name)
:on-press #(rf/dispatch [:wallet/add-account :on-press #(rf/dispatch [:wallet/add-account
nil {:sha3-pwd nil
{:type :watch :type :watch
:account-name @account-name :account-name @account-name
:emoji @account-emoji :emoji @account-emoji
:color @account-color} :color @account-color}

View File

@ -118,14 +118,14 @@
{:size :size-48 {:size :size-48
:track-text (i18n/label :t/slide-to-create-account) :track-text (i18n/label :t/slide-to-create-account)
:customization-color @account-color :customization-color @account-color
:on-enter-password (fn [entered-password] :on-auth-success (fn [entered-password]
(prn entered-password)
(rf/dispatch [:wallet/derive-address-and-add-account (rf/dispatch [:wallet/derive-address-and-add-account
entered-password {:sha3-pwd entered-password
{:emoji @emoji :emoji @emoji
:color @account-color :color @account-color
:path @derivation-path :path @derivation-path
:account-name @account-name}])) :account-name @account-name}]))
:biometric-auth? false
:auth-button-label (i18n/label :t/confirm) :auth-button-label (i18n/label :t/confirm)
:container-style (style/slide-button-container bottom)}]]))) :container-style (style/slide-button-container bottom)}]])))

View File

@ -3,7 +3,6 @@
[camel-snake-kebab.core :as csk] [camel-snake-kebab.core :as csk]
[camel-snake-kebab.extras :as cske] [camel-snake-kebab.extras :as cske]
[clojure.string :as string] [clojure.string :as string]
[native-module.core :as native-module]
[quo.foundations.colors :as colors] [quo.foundations.colors :as colors]
[react-native.background-timer :as background-timer] [react-native.background-timer :as background-timer]
[status-im2.common.data-store.wallet :as data-store] [status-im2.common.data-store.wallet :as data-store]
@ -15,7 +14,6 @@
[utils.i18n :as i18n] [utils.i18n :as i18n]
[utils.number] [utils.number]
[utils.re-frame :as rf] [utils.re-frame :as rf]
[utils.security.core :as security]
[utils.transforms :as types])) [utils.transforms :as types]))
(rf/reg-event-fx :wallet/show-account-created-toast (rf/reg-event-fx :wallet/show-account-created-toast
@ -143,9 +141,8 @@
{:db (dissoc db :wallet/scanned-address :wallet/send-address)}) {:db (dissoc db :wallet/scanned-address :wallet/send-address)})
(rf/reg-event-fx :wallet/create-derived-addresses (rf/reg-event-fx :wallet/create-derived-addresses
(fn [{:keys [db]} [password {:keys [path]} on-success]] (fn [{:keys [db]} [{:keys [sha3-pwd path]} on-success]]
(let [{:keys [wallet-root-address]} (:profile/profile db) (let [{:keys [wallet-root-address]} (:profile/profile db)]
sha3-pwd (native-module/sha3 (str (security/safe-unmask-data password)))]
{:fx [[:json-rpc/call {:fx [[:json-rpc/call
[{:method "wallet_getDerivedAddresses" [{:method "wallet_getDerivedAddresses"
:params [sha3-pwd wallet-root-address [path]] :params [sha3-pwd wallet-root-address [path]]
@ -162,11 +159,10 @@
(rf/reg-event-fx :wallet/add-account (rf/reg-event-fx :wallet/add-account
(fn [{:keys [db]} (fn [{:keys [db]}
[password {:keys [emoji account-name color type] :or {type :generated}} [{:keys [sha3-pwd emoji account-name color type] :or {type :generated}}
{:keys [public-key address path]}]] {:keys [public-key address path]}]]
(let [lowercase-address (if address (string/lower-case address) address) (let [lowercase-address (if address (string/lower-case address) address)
key-uid (get-in db [:profile/profile :key-uid]) key-uid (get-in db [:profile/profile :key-uid])
sha3-pwd (native-module/sha3 (security/safe-unmask-data password))
account-config {:key-uid (when (= type :generated) key-uid) account-config {:key-uid (when (= type :generated) key-uid)
:wallet false :wallet false
:chat false :chat false
@ -185,11 +181,11 @@
(rf/reg-event-fx (rf/reg-event-fx
:wallet/derive-address-and-add-account :wallet/derive-address-and-add-account
(fn [_ [password account-details]] (fn [_ [account-details]]
(let [on-success (fn [derived-address-details] (let [on-success (fn [derived-address-details]
(rf/dispatch [:wallet/add-account password account-details (rf/dispatch [:wallet/add-account account-details
(first derived-address-details)]))] (first derived-address-details)]))]
{:fx [[:dispatch [:wallet/create-derived-addresses password account-details on-success]]]}))) {:fx [[:dispatch [:wallet/create-derived-addresses account-details on-success]]]})))
(rf/defn get-ethereum-chains (rf/defn get-ethereum-chains
{:events [:wallet/get-ethereum-chains]} {:events [:wallet/get-ethereum-chains]}