[#19232] - Fix derivation path generation and keypair creation (#19531)

* Add more default dependencies to slide button

* Fix wallet account creation: derivation paths and keypairs
This commit is contained in:
Ulises Manuel 2024-05-24 10:03:02 -06:00 committed by GitHub
parent f65c10502b
commit 49a41f4787
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
12 changed files with 446 additions and 233 deletions

View File

@ -24,7 +24,7 @@
:on-auth-success on-auth-success :on-auth-success on-auth-success
:on-auth-fail on-auth-fail :on-auth-fail on-auth-fail
:auth-button-label auth-button-label}])) :auth-button-label auth-button-label}]))
(into [] (concat [theme] dependencies)))] (vec (conj dependencies on-auth-success on-auth-fail)))]
[quo/slide-button [quo/slide-button
{:container-style container-style {:container-style container-style
:size size :size size

View File

@ -38,8 +38,7 @@
:disabled? (string/blank? @account-name) :disabled? (string/blank? @account-name)
:accessibility-label :confirm-button-label :accessibility-label :confirm-button-label
:on-press #(rf/dispatch [:wallet/add-account :on-press #(rf/dispatch [:wallet/add-account
{: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

@ -1,6 +1,10 @@
(ns status-im.contexts.wallet.add-account.create-account.events (ns status-im.contexts.wallet.add-account.create-account.events
(:require [camel-snake-kebab.extras :as cske] (:require [camel-snake-kebab.extras :as cske]
[clojure.string :as string]
[status-im.constants :as constants]
[status-im.contexts.wallet.add-account.create-account.utils :as create-account.utils]
[status-im.contexts.wallet.data-store :as data-store] [status-im.contexts.wallet.data-store :as data-store]
[taoensso.timbre :as log]
[utils.re-frame :as rf] [utils.re-frame :as rf]
[utils.security.core :as security] [utils.security.core :as security]
[utils.transforms :as transforms])) [utils.transforms :as transforms]))
@ -22,14 +26,18 @@
(rf/reg-event-fx :wallet/confirm-account-origin confirm-account-origin) (rf/reg-event-fx :wallet/confirm-account-origin confirm-account-origin)
(defn store-seed-phrase (defn store-new-seed-phrase
[{:keys [db]} [{:keys [seed-phrase random-phrase]}]] [{:keys [db]} [{:keys [seed-phrase random-phrase]}]]
{:db (-> db {:db (update-in db
(assoc-in [:wallet :ui :create-account :seed-phrase] seed-phrase) [:wallet :ui :create-account :new-keypair]
(assoc-in [:wallet :ui :create-account :random-phrase] random-phrase)) assoc
:fx [[:dispatch-later [{:ms 20 :dispatch [:navigate-to :screen/wallet.confirm-backup]}]]]}) :seed-phrase seed-phrase
:random-phrase random-phrase)
:fx [[:dispatch-later
[{:ms 20
:dispatch [:navigate-to :screen/wallet.confirm-backup]}]]]})
(rf/reg-event-fx :wallet/store-seed-phrase store-seed-phrase) (rf/reg-event-fx :wallet/store-new-seed-phrase store-new-seed-phrase)
(defn seed-phrase-validated (defn seed-phrase-validated
[{:keys [db]} [seed-phrase]] [{:keys [db]} [seed-phrase]]
@ -43,33 +51,45 @@
{:fx [[:multiaccount/validate-mnemonic {:fx [[:multiaccount/validate-mnemonic
[seed-phrase [seed-phrase
(fn [mnemonic key-uid] (fn [mnemonic key-uid]
(rf/dispatch [:wallet/seed-phrase-validated (rf/dispatch [:wallet/seed-phrase-validated mnemonic key-uid]))
mnemonic key-uid]))
on-error]]]}) on-error]]]})
(rf/reg-event-fx :wallet/seed-phrase-entered seed-phrase-entered) (rf/reg-event-fx :wallet/seed-phrase-entered seed-phrase-entered)
(defn new-keypair-created (defn store-account-generated
[{:keys [db]} [{:keys [new-keypair]}]] [{:keys [db]} [{:keys [new-account-data keypair-name]}]]
{:db (assoc-in db [:wallet :ui :create-account :new-keypair] new-keypair) (let [new-account (update new-account-data :mnemonic security/mask-data)]
:fx [[:dispatch [:navigate-back-to :screen/wallet.create-account]]]}) {:db (-> db
(update-in [:wallet :ui :create-account :new-keypair]
assoc
:new-account-data new-account
:keypair-name keypair-name)
(update-in [:wallet :ui :create-account :new-keypair]
dissoc
:seed-phrase
:random-phrase))
:fx [[:dispatch [:navigate-back-to :screen/wallet.create-account]]]}))
(rf/reg-event-fx :wallet/new-keypair-created new-keypair-created) (rf/reg-event-fx :wallet/store-account-generated store-account-generated)
(defn new-keypair-continue (defn generate-account-for-keypair
[{:keys [db]} [{:keys [keypair-name]}]] [{:keys [db]} [{:keys [keypair-name]}]]
(let [seed-phrase (get-in db [:wallet :ui :create-account :seed-phrase])] (let [seed-phrase (-> db :wallet :ui :create-account :new-keypair :seed-phrase)]
{:fx [[:effects.wallet/create-account-from-mnemonic {:fx [[:effects.wallet/create-account-from-mnemonic
{:seed-phrase (security/safe-unmask-data seed-phrase) {:mnemonic-phrase (security/safe-unmask-data seed-phrase)
:keypair-name keypair-name}]]})) :paths [constants/path-default-wallet]
:on-success (fn [new-account-data]
(rf/dispatch [:wallet/store-account-generated
{:new-account-data new-account-data
:keypair-name keypair-name}]))}]]}))
(rf/reg-event-fx :wallet/new-keypair-continue new-keypair-continue) (rf/reg-event-fx :wallet/generate-account-for-keypair generate-account-for-keypair)
(defn clear-new-keypair (defn clear-create-account-data
[{:keys [db]}] [{:keys [db]}]
{:db (update-in db [:wallet :ui :create-account] dissoc :new-keypair)}) {:db (update-in db [:wallet :ui :create-account] dissoc :new-keypair)})
(rf/reg-event-fx :wallet/clear-new-keypair clear-new-keypair) (rf/reg-event-fx :wallet/clear-create-account clear-create-account-data)
(defn get-derived-addresses (defn get-derived-addresses
[{:keys [db]} [{:keys [password derived-from paths]}]] [{:keys [db]} [{:keys [password derived-from paths]}]]
@ -80,6 +100,21 @@
(rf/reg-event-fx :wallet/get-derived-addresses get-derived-addresses) (rf/reg-event-fx :wallet/get-derived-addresses get-derived-addresses)
(rf/reg-event-fx
:wallet/next-derivation-path
(fn [_ [{:keys [on-success keypair-uid]}]]
{:fx [[:json-rpc/call
[{:method "accounts_resolveSuggestedPathForKeypair"
:params [keypair-uid]
:on-success on-success
:on-error (fn [error]
(log/error
"Failed to resolve next path derivation path"
{:event :wallet/next-derivation-path
:method "accounts_resolveSuggestedPathForKeypair"
:error error
:params keypair-uid}))}]]]}))
(defn get-derived-addresses-success (defn get-derived-addresses-success
[{:keys [db]} [response]] [{:keys [db]} [response]]
(let [derived-address (first response)] (let [derived-address (first response)]
@ -105,3 +140,53 @@
:wallet/clear-private-key-data :wallet/clear-private-key-data
(fn [{:keys [db]} _] (fn [{:keys [db]} _]
{:db (update-in db [:wallet :ui :create-account] dissoc :private-key :public-address)})) {:db (update-in db [:wallet :ui :create-account] dissoc :private-key :public-address)}))
(rf/reg-event-fx
:wallet/create-keypair-with-account
(fn [{db :db} [password account-preferences]]
(let [{:keys [keypair-name
new-account-data]} (-> db :wallet :ui :create-account :new-keypair)
keypair-with-account (create-account.utils/prepare-new-account
{:keypair-name keypair-name
:account-data new-account-data
:account-preferences account-preferences})
new-address (some-> new-account-data
(create-account.utils/first-derived-account)
(:address)
(string/lower-case))
unmasked-password (security/safe-unmask-data password)]
{:fx [[:json-rpc/call
[{:method "accounts_addKeypair"
:params [unmasked-password keypair-with-account]
:on-success [:wallet/add-account-success new-address]
:on-error #(log/error "Failed to add Keypair and create account" %)}]]]})))
(defn import-and-create-keypair-with-account
[{db :db} [{:keys [password account-preferences]}]]
(let [account-data (-> db :wallet :ui :create-account :new-keypair :new-account-data)
unmasked-mnemonic (security/safe-unmask-data (:mnemonic account-data))
unmasked-password (security/safe-unmask-data password)]
{:fx [[:json-rpc/call
[{:method "accounts_importMnemonic"
:params [unmasked-mnemonic unmasked-password]
:on-success #(rf/dispatch
[:wallet/create-keypair-with-account password account-preferences])}]]]}))
(rf/reg-event-fx :wallet/import-and-create-keypair-with-account import-and-create-keypair-with-account)
(rf/reg-event-fx
:wallet/derive-address-and-add-account
(fn [_ [{:keys [password derived-from-address derivation-path account-preferences]}]]
{:fx [[:json-rpc/call
[{:method "wallet_getDerivedAddresses"
:params [(security/safe-unmask-data password)
derived-from-address
[derivation-path]]
:on-success (fn [[derived-account]]
(rf/dispatch [:wallet/add-account
(assoc account-preferences :password password)
derived-account]))
:on-error #(log/info "Failed to get derived addresses"
derived-from-address
%)}]]]}))

View File

@ -1,8 +1,10 @@
(ns status-im.contexts.wallet.add-account.create-account.events-test (ns status-im.contexts.wallet.add-account.create-account.events-test
(:require (:require
[cljs.test :refer-macros [deftest is]] [cljs.test :refer-macros [deftest is]]
matcher-combinators.test [matcher-combinators.test]
[status-im.contexts.wallet.add-account.create-account.events :as events])) [status-im.constants :as constants]
[status-im.contexts.wallet.add-account.create-account.events :as events]
[utils.security.core :as security]))
(deftest confirm-account-origin (deftest confirm-account-origin
(let [db {:wallet {:ui {:create-account {}}}} (let [db {:wallet {:ui {:create-account {}}}}
@ -15,32 +17,60 @@
(deftest store-seed-phrase (deftest store-seed-phrase
(let [db {} (let [db {}
props [{:seed-phrase "test-secret" :random-phrase "random-test"}] props [{:seed-phrase "test-secret" :random-phrase "random-test"}]
expected-db {:wallet {:ui {:create-account {:seed-phrase "test-secret" expected-db {:wallet {:ui {:create-account {:new-keypair {:seed-phrase "test-secret"
:random-phrase "random-test"}}}} :random-phrase "random-test"}}}}}
effects (events/store-seed-phrase {:db db} props) effects (events/store-new-seed-phrase {:db db} props)
result-db (:db effects)] result-db (:db effects)]
(is (match? result-db expected-db)))) (is (match? result-db expected-db))))
(deftest new-keypair-created (deftest store-account-generated
(let [db {} (let [db {:wallet {:ui {:create-account
props [{:new-keypair "test-keypair"}] {:new-keypair {:seed-phrase "test-secret"
expected-db {:wallet {:ui {:create-account {:new-keypair "test-keypair"}}}} :random-phrase "random-test"}}}}}
effects (events/new-keypair-created {:db db} props) mnemonic "my mnemonic"
result-db (:db effects)] masked-mnemonic (security/mask-data mnemonic)
(is (match? result-db expected-db)))) props [{:new-account-data {"test" "data"
:mnemonic mnemonic}
:keypair-name "new-keypair-name"}]
expected-db {:wallet {:ui {:create-account
{:new-keypair
{:new-account-data {"test" "data"
:mnemonic masked-mnemonic}
:keypair-name "new-keypair-name"}}}}}
effects (events/store-account-generated {:db db} props)
result-db (:db effects)
remove-mnemonic #(update-in %
[:wallet :ui :create-account :new-keypair :new-account-data]
dissoc
:mnemonic)
unmask-mnemonic #(-> %
:wallet
:ui
:create-account
:new-keypair
:new-account-data
:mnemonic
security/safe-unmask-data)]
(is (= (remove-mnemonic result-db) (remove-mnemonic expected-db)))
(is (= (unmask-mnemonic result-db) (unmask-mnemonic expected-db)))))
(deftest new-keypair-continue
(let [db {:wallet {:ui {:create-account {:seed-phrase "test-secret"}}}} (deftest generate-account-for-keypair
(let [db {:wallet {:ui {:create-account {:new-keypair {:seed-phrase "test-secret"}}}}}
props [{:keypair-name "test-keypair"}] props [{:keypair-name "test-keypair"}]
expected-effects [[:effects.wallet/create-account-from-mnemonic expected-effects [[:effects.wallet/create-account-from-mnemonic
{:seed-phrase "test-secret" :keypair-name "test-keypair"}]] {:mnemonic-phrase "test-secret"
effects (events/new-keypair-continue {:db db} props)] :paths [constants/path-default-wallet]}]]
(is (match? effects {:fx expected-effects})))) effects (events/generate-account-for-keypair {:db db} props)]
(is (match?
(update-in effects [:fx 0 1] dissoc :on-success)
{:fx expected-effects}))
(is (some? (get-in effects [:fx 0 1 :on-success])))))
(deftest clear-new-keypair (deftest clear-create-account-data
(let [db {:wallet {:ui {:create-account {:new-keypair "test-keypair"}}}} (let [db {:wallet {:ui {:create-account {:new-keypair "test-keypair"}}}}
expected-db {:wallet {:ui {:create-account {}}}} expected-db {:wallet {:ui {:create-account {}}}}
effects (events/clear-new-keypair {:db db})] effects (events/clear-create-account-data {:db db})]
(is (match? (:db effects) expected-db)))) (is (match? (:db effects) expected-db))))
(deftest get-derived-addresses-test (deftest get-derived-addresses-test

View File

@ -52,8 +52,8 @@
:import-private-key :import-private-key
(not-implemented/alert) (not-implemented/alert)
:new-key-pair :new-keypair
(rf/dispatch [:wallet/new-keypair-continue (rf/dispatch [:wallet/generate-account-for-keypair
{:keypair-name key-pair-name}]) {:keypair-name key-pair-name}])
(js/alert "Unknown workflow"))) (js/alert "Unknown workflow")))
[workflow key-pair-name]) [workflow key-pair-name])

View File

@ -108,7 +108,7 @@
:button-one-label (i18n/label :t/i-have-written) :button-one-label (i18n/label :t/i-have-written)
:button-one-props {:disabled? (some false? (vals @checked?)) :button-one-props {:disabled? (some false? (vals @checked?))
:customization-color customization-color :customization-color customization-color
:on-press #(rf/dispatch [:wallet/store-seed-phrase :on-press #(rf/dispatch [:wallet/store-new-seed-phrase
{:seed-phrase (security/mask-data {:seed-phrase (security/mask-data
@seed-phrase) @seed-phrase)
:random-phrase @random-phrase}])}}] :random-phrase @random-phrase}])}}]

View File

@ -65,7 +65,7 @@
quiz-index (reagent/atom 0) quiz-index (reagent/atom 0)
incorrect-count (reagent/atom 0) incorrect-count (reagent/atom 0)
show-error? (reagent/atom false) show-error? (reagent/atom false)
{:keys [seed-phrase random-phrase]} (rf/sub [:wallet/create-account]) {:keys [seed-phrase random-phrase]} (rf/sub [:wallet/create-account-new-keypair])
unmasked-seed-phrase (security/safe-unmask-data seed-phrase)] unmasked-seed-phrase (security/safe-unmask-data seed-phrase)]
(fn [] (fn []
(let [current-word-index (get random-indices (let [current-word-index (get random-indices
@ -82,7 +82,7 @@
(when (= @quiz-index questions-count) (when (= @quiz-index questions-count)
(rf/dispatch [:navigate-to (rf/dispatch [:navigate-to
:screen/wallet.keypair-name :screen/wallet.keypair-name
{:workflow :new-key-pair}]))) {:workflow :new-keypair}])))
(do (do
(when (> @incorrect-count 0) (when (> @incorrect-count 0)
(rf/dispatch [:show-bottom-sheet (rf/dispatch [:show-bottom-sheet

View File

@ -1,19 +1,29 @@
(ns status-im.contexts.wallet.add-account.create-account.utils) (ns status-im.contexts.wallet.add-account.create-account.utils
(:require [status-im.constants :as constants]))
(defn prepare-new-keypair (defn first-derived-account
[{:keys [new-keypair address account-name account-color emoji derivation-path]}] [account-data]
(assoc new-keypair (-> account-data :derived first val))
:name (:keypair-name new-keypair)
:key-uid (:keyUid new-keypair) (defn prepare-new-account
:type :seed [{keypair-name :keypair-name
:derived-from address {:keys [keyUid address] :as account} :account-data
:accounts [{:keypair-name (:keypair-name new-keypair) {:keys [account-name color emoji]} :account-preferences}]
:key-uid (:keyUid new-keypair) (let [account-to-create (first-derived-account account)
:seed-phrase (:mnemonic new-keypair) account-config {:address (:address account-to-create)
:public-key (:publicKey new-keypair) :key-uid keyUid
:name account-name :wallet false
:type :seed :chat false
:emoji emoji :type :seed
:colorID account-color :path constants/path-default-wallet
:path derivation-path :public-key (:publicKey account-to-create)
:address (:address new-keypair)}])) :name account-name
:emoji emoji
:colorID color
:hidden false}]
{:key-uid keyUid
:name keypair-name
:type :seed
:derived-from address
:last-used-derivation-index 0
:accounts [account-config]}))

View File

@ -12,14 +12,11 @@
[status-im.common.standard-authentication.core :as standard-auth] [status-im.common.standard-authentication.core :as standard-auth]
[status-im.constants :as constants] [status-im.constants :as constants]
[status-im.contexts.wallet.add-account.create-account.style :as style] [status-im.contexts.wallet.add-account.create-account.style :as style]
[status-im.contexts.wallet.add-account.create-account.utils :as create-account.utils]
[status-im.contexts.wallet.common.utils :as utils]
[status-im.contexts.wallet.sheets.account-origin.view :as account-origin] [status-im.contexts.wallet.sheets.account-origin.view :as account-origin]
[status-im.feature-flags :as ff] [status-im.feature-flags :as ff]
[utils.i18n :as i18n] [utils.i18n :as i18n]
[utils.re-frame :as rf] [utils.re-frame :as rf]
[utils.responsiveness :as responsiveness] [utils.responsiveness :as responsiveness]
[utils.security.core :as security]
[utils.string])) [utils.string]))
(defn- get-keypair-data (defn- get-keypair-data
@ -57,107 +54,47 @@
:description :text :description :text
:description-props {:text formatted-path}}])) :description-props {:text formatted-path}}]))
(defn view (defn- avatar
[{:keys [account-color emoji on-select-emoji]}]
[rn/view {:style style/account-avatar-container}
[quo/account-avatar
{:customization-color account-color
:size 80
:emoji emoji
:type :default}]
[quo/button
{:size 32
:type :grey
:background :photo
:icon-only? true
:on-press #(rf/dispatch [:emoji-picker/open {:on-select on-select-emoji}])
:container-style style/reaction-button-container}
:i/reaction]])
(defn- input
[_] [_]
(let [top (safe-area/get-top) (let [placeholder (i18n/label :t/default-account-placeholder)]
bottom (safe-area/get-bottom) (fn [{:keys [account-color account-name on-change-text]}]
{window-width :width} (rn/get-window) [quo/title-input
account-color (reagent/atom (rand-nth colors/account-colors)) {:customization-color account-color
emoji (reagent/atom (emoji-picker.utils/random-emoji)) :placeholder placeholder
account-name (reagent/atom "") :on-change-text on-change-text
on-change-text #(reset! account-name %) :max-length constants/wallet-account-name-max-length
show-account-origin #(rf/dispatch [:show-bottom-sheet :blur? true
{:content account-origin/view}])] :disabled? false
(fn [] :default-value account-name
(let [theme (quo.theme/use-theme) :container-style style/title-input-container}])))
number-of-accounts (count (rf/sub
[:wallet/accounts-without-watched-accounts])) (defn- color-picker
{:keys [address customization-color]} (rf/sub [:profile/profile]) [_]
{:keys [new-keypair]} (rf/sub [:wallet/create-account]) (let [{window-width :width} (rn/get-window)
keypairs (rf/sub [:wallet/keypairs]) color-picker-style {:padding-vertical 12
selected-keypair-uid (rf/sub [:wallet/selected-keypair-uid]) :padding-left (responsiveness/iphone-11-Pro-20-pixel-from-width
placeholder (i18n/label :t/default-account-placeholder) window-width)}]
derivation-path (utils/get-derivation-path (fn [{:keys [account-color set-account-color]}]
number-of-accounts) [:<>
keypair (some #(when (= (:key-uid %) selected-keypair-uid) [quo/divider-line]
%) (let [theme (quo.theme/use-theme)]
keypairs)
primary-keypair? (= selected-keypair-uid (:key-uid (first keypairs)))
create-new-keypair-account #(rf/dispatch
[:wallet/add-keypair-and-create-account
{:sha3-pwd (security/safe-unmask-data %)
:new-keypair
(create-account.utils/prepare-new-keypair
{:new-keypair new-keypair
:address address
:account-name @account-name
:account-color @account-color
:emoji @emoji
:derivation-path
derivation-path})}])
create-existing-keypair-account #(rf/dispatch [:wallet/derive-address-and-add-account
{:sha3-pwd (security/safe-unmask-data %)
:emoji @emoji
:color @account-color
:path derivation-path
:account-name @account-name}])
keypair-title (or (:keypair-name new-keypair)
(if primary-keypair?
(i18n/label :t/keypair-title
{:name (:name keypair)})
(:name keypair)))]
(rn/use-unmount #(rf/dispatch [:wallet/clear-new-keypair]))
[floating-button-page/view
{:gradient-cover? true
:footer-container-padding 0
:header-container-style {:padding-top top}
:customization-color @account-color
:header [quo/page-nav
{:type :no-title
:background :blur
:right-side [{:icon-name :i/info
:on-press show-account-origin}]
:icon-name :i/close
:on-press #(rf/dispatch [:navigate-back])}]
:footer [standard-auth/slide-button
{:size :size-48
:track-text (i18n/label :t/slide-to-create-account)
:customization-color @account-color
:on-auth-success (fn [password]
(if new-keypair
(create-new-keypair-account password)
(create-existing-keypair-account
password)))
:auth-button-label (i18n/label :t/confirm)
:disabled? (empty? @account-name)
:container-style (style/slide-button-container bottom)
:dependencies [new-keypair]}]}
[rn/view {:style style/account-avatar-container}
[quo/account-avatar
{:customization-color @account-color
:size 80
:emoji @emoji
:type :default}]
[quo/button
{:size 32
:type :grey
:background :photo
:icon-only? true
:on-press #(rf/dispatch [:emoji-picker/open
{:on-select (fn [selected-emoji]
(reset! emoji selected-emoji))}])
:container-style style/reaction-button-container}
:i/reaction]]
[quo/title-input
{:customization-color @account-color
:placeholder placeholder
:on-change-text on-change-text
:max-length constants/wallet-account-name-max-length
:blur? true
:disabled? false
:default-value @account-name
:container-style style/title-input-container}]
[quo/divider-line]
[rn/view {:style style/color-picker-container} [rn/view {:style style/color-picker-container}
[quo/text [quo/text
{:size :paragraph-2 {:size :paragraph-2
@ -165,17 +102,168 @@
:style (style/color-label theme)} :style (style/color-label theme)}
(i18n/label :t/colour)] (i18n/label :t/colour)]
[quo/color-picker [quo/color-picker
{:default-selected @account-color {:default-selected account-color
:on-change #(reset! account-color %) :on-change set-account-color
:container-style {:padding-vertical 12 :container-style color-picker-style}]])])))
:padding-left (responsiveness/iphone-11-Pro-20-pixel-from-width
window-width)}}]] (defn- new-account-origin
[quo/divider-line] [{:keys [keypair-title derivation-path customization-color]}]
[quo/category (let [{keypair-name :name} (rf/sub [:wallet/selected-keypair])
{:list-type :settings primary? (rf/sub [:wallet/selected-primary-keypair?])
:label (i18n/label :t/origin) keypair-name (or keypair-title
:data (get-keypair-data {:title keypair-title (if primary?
:primary-keypair? primary-keypair? (i18n/label :t/keypair-title {:name keypair-name})
:new-keypair? (boolean new-keypair) keypair-name))]
:derivation-path derivation-path [:<>
:customization-color customization-color})}]])))) [quo/divider-line]
[quo/category
{:list-type :settings
:label (i18n/label :t/origin)
:data (get-keypair-data {:primary-keypair? primary?
:title keypair-name
:derivation-path derivation-path
:customization-color customization-color})}]]))
(defn- floating-button
[_ & _]
(let [top (safe-area/get-top)
bottom (safe-area/get-bottom)
header [quo/page-nav
{:type :no-title
:background :blur
:right-side [{:icon-name :i/info
:on-press #(rf/dispatch [:show-bottom-sheet
{:content account-origin/view}])}]
:icon-name :i/close
:on-press #(rf/dispatch [:navigate-back])}]]
(fn [{:keys [slide-button-props account-color]} & children]
(into
[floating-button-page/view
{:gradient-cover? true
:footer-container-padding 0
:header-container-style {:padding-top top}
:customization-color account-color
:header header
:footer [standard-auth/slide-button
(assoc slide-button-props
:size :size-48
:track-text (i18n/label :t/slide-to-create-account)
:customization-color account-color
:auth-button-label (i18n/label :t/confirm)
:container-style (style/slide-button-container bottom))]}]
children))))
(defn add-new-keypair-variant
[{:keys [on-change-text set-account-color set-emoji]
{:keys [account-name account-color emoji]}
:state}]
(let [on-auth-success (fn [password]
(rf/dispatch
[:wallet/import-and-create-keypair-with-account
{:password password
:account-preferences {:account-name @account-name
:color @account-color
:emoji @emoji}}]))]
(fn [{:keys [customization-color keypair-name]}]
(let [{:keys [new-account-data]} (rf/sub [:wallet/create-account-new-keypair])]
[floating-button
{:account-color @account-color
:slide-button-props {:on-auth-success on-auth-success
:disabled? (empty? @account-name)
:dependencies [new-account-data]}}
[avatar
{:account-color @account-color
:emoji @emoji
:on-select-emoji set-emoji}]
[input
{:account-color @account-color
:account-name @account-name
:on-change-text on-change-text}]
[color-picker
{:account-color @account-color
:set-account-color set-account-color}]
[new-account-origin
{:derivation-path constants/path-default-wallet
:customization-color customization-color
:keypair-title keypair-name}]]))))
(defn derive-account-variant
[{:keys [on-change-text set-account-color set-emoji]
{:keys [account-name account-color emoji]}
:state}]
(let [derivation-path (reagent/atom "")
set-derivation-path #(reset! derivation-path %)]
(fn [{:keys [customization-color]}]
(let [{:keys [derived-from
key-uid]} (rf/sub [:wallet/selected-keypair])
on-auth-success (rn/use-callback
(fn [password]
(let [preferences {:account-name @account-name
:color @account-color
:emoji @emoji}]
(rf/dispatch
[:wallet/derive-address-and-add-account
{:password password
:derived-from-address derived-from
:derivation-path @derivation-path
:account-preferences preferences}])))
[derived-from])]
(rn/use-effect
#(rf/dispatch
[:wallet/next-derivation-path
{:on-success set-derivation-path
:keypair-uid key-uid}])
[key-uid])
[floating-button
{:account-color @account-color
:slide-button-props {:on-auth-success on-auth-success
:disabled? (or (empty? @account-name)
(= "" @derivation-path))}}
[avatar
{:account-color @account-color
:emoji @emoji
:on-select-emoji set-emoji}]
[input
{:account-color @account-color
:account-name @account-name
:on-change-text on-change-text}]
[color-picker
{:account-color @account-color
:set-account-color set-account-color}]
[new-account-origin
{:derivation-path @derivation-path
:customization-color customization-color}]]))))
(defn view
[_]
(let [account-name (reagent/atom "")
account-color (reagent/atom (rand-nth colors/account-colors))
emoji (reagent/atom (emoji-picker.utils/random-emoji))
on-change-text #(reset! account-name %)
set-account-color #(reset! account-color %)
set-emoji #(reset! emoji %)
state {:account-name account-name
:account-color account-color
:emoji emoji}]
(fn []
(let [customization-color (rf/sub [:profile/customization-color])
;; Having a keypair means the user is importing it or creating it.
{:keys [keypair-name]} (rf/sub [:wallet/create-account-new-keypair])]
(rn/use-unmount #(rf/dispatch [:wallet/clear-create-account]))
(if keypair-name
[add-new-keypair-variant
{:customization-color customization-color
:on-change-text on-change-text
:set-account-color set-account-color
:set-emoji set-emoji
:state state
:keypair-name keypair-name}]
[derive-account-variant
{:customization-color customization-color
:on-change-text on-change-text
:set-account-color set-account-color
:set-emoji set-emoji
:state state}])))))

View File

@ -2,15 +2,18 @@
(:require (:require
[clojure.string :as string] [clojure.string :as string]
[native-module.core :as native-module] [native-module.core :as native-module]
[re-frame.core :as rf])) [re-frame.core :as rf]
[taoensso.timbre :as log]))
(rf/reg-fx (rf/reg-fx
:effects.wallet/create-account-from-mnemonic :effects.wallet/create-account-from-mnemonic
(fn [{:keys [seed-phrase keypair-name]}] (fn [{:keys [mnemonic-phrase paths on-success]
(native-module/create-account-from-mnemonic :or {paths []}}]
{:MnemonicPhrase (if (string? seed-phrase) (let [phrase (condp #(%1 %2) mnemonic-phrase
seed-phrase string? mnemonic-phrase
(string/join " " seed-phrase))} coll? (string/join " " mnemonic-phrase)
(fn [new-keypair] (log/error "Unexpected value " mnemonic-phrase))]
(rf/dispatch [:wallet/new-keypair-created (native-module/create-account-from-mnemonic
{:new-keypair (assoc new-keypair :keypair-name keypair-name)}]))))) {:MnemonicPhrase phrase
:paths paths}
on-success))))

View File

@ -16,17 +16,18 @@
[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 transforms])) [utils.transforms :as transforms]))
(rf/reg-event-fx :wallet/show-account-created-toast (rf/reg-event-fx :wallet/show-account-created-toast
(fn [{:keys [db]} [address]] (fn [{:keys [db]} [address]]
(let [account (get-in db [:wallet :accounts address])] (let [account-name (get-in db [:wallet :accounts address :name])]
{:db (update db :wallet dissoc :navigate-to-account :new-account?) {:db (update db :wallet dissoc :navigate-to-account :new-account?)
:fx [[:dispatch :fx [[:dispatch
[:toasts/upsert [:toasts/upsert
{:id :new-wallet-account-created {:id :new-wallet-account-created
:type :positive :type :positive
:text (i18n/label :t/account-created {:name (:name account)})}]]]}))) :text (i18n/label :t/account-created {:name account-name})}]]]})))
(rf/reg-event-fx :wallet/navigate-to-account (rf/reg-event-fx :wallet/navigate-to-account
(fn [{:keys [db]} [address]] (fn [{:keys [db]} [address]]
@ -173,30 +174,22 @@
[{:keys [db]}] [{:keys [db]}]
{:db (update-in db [:wallet :ui] dissoc :scanned-address)}) {:db (update-in db [:wallet :ui] dissoc :scanned-address)})
(rf/reg-event-fx :wallet/create-derived-addresses
(fn [{:keys [db]} [{:keys [sha3-pwd path]} on-success]]
(let [{:keys [address]} (:profile/profile db)]
{:fx [[:json-rpc/call
[{:method "wallet_getDerivedAddresses"
:params [sha3-pwd address [path]]
:on-success on-success
:on-error #(log/info "failed to derive address " %)}]]]})))
(rf/reg-event-fx :wallet/add-account-success (rf/reg-event-fx :wallet/add-account-success
(fn [{:keys [db]} [address]] (fn [{:keys [db]} [address]]
{:db (update db {:db (-> db
:wallet assoc (assoc-in [:wallet :navigate-to-account] address)
:navigate-to-account address (assoc-in [:wallet :new-account?] true))
:new-account? true)
:fx [[:dispatch [:wallet/get-accounts]] :fx [[:dispatch [:wallet/get-accounts]]
[:dispatch [:wallet/get-keypairs]] [:dispatch [:wallet/get-keypairs]]
[:dispatch [:wallet/clear-new-keypair]]]})) [:dispatch [:wallet/clear-create-account]]]}))
(rf/reg-event-fx :wallet/add-account (rf/reg-event-fx :wallet/add-account
(fn [{:keys [db]} (fn [{:keys [db]}
[{:keys [sha3-pwd emoji account-name color type] :or {type :generated}} [{:keys [password account-name emoji color type]
{:keys [public-key address path]}]] :or {type :generated}}
(let [lowercase-address (if address (string/lower-case address) address) {:keys [public-key address path] :as _derived-account}]]
(let [lowercase-address (some-> address
(string/lower-case))
key-uid (get-in db [:wallet :ui :create-account :selected-keypair-uid]) key-uid (get-in db [:wallet :ui :create-account :selected-keypair-uid])
account-config {:key-uid (when (= type :generated) key-uid) account-config {:key-uid (when (= type :generated) key-uid)
:wallet false :wallet false
@ -210,30 +203,11 @@
:colorID color}] :colorID color}]
{:fx [[:json-rpc/call {:fx [[:json-rpc/call
[{:method "accounts_addAccount" [{:method "accounts_addAccount"
:params [(when (= type :generated) sha3-pwd) account-config] :params [(when (= type :generated)
(security/safe-unmask-data password))
account-config]
:on-success [:wallet/add-account-success lowercase-address] :on-success [:wallet/add-account-success lowercase-address]
:on-error #(log/info "failed to create account " %)}]]]}))) :on-error #(log/info "failed to create account " % account-config)}]]]})))
(rf/reg-event-fx
:wallet/derive-address-and-add-account
(fn [_ [account-details]]
(let [on-success (fn [derived-address-details]
(rf/dispatch [:wallet/add-account account-details
(first derived-address-details)]))]
{:fx [[:dispatch [:wallet/create-derived-addresses account-details on-success]]]})))
(defn add-keypair-and-create-account
[_ [{:keys [sha3-pwd new-keypair]}]]
(let [lowercase-address (if (:address new-keypair)
(string/lower-case (:address new-keypair))
(:address new-keypair))]
{:fx [[:json-rpc/call
[{:method "accounts_addKeypair"
:params [sha3-pwd new-keypair]
:on-success [:wallet/add-account-success lowercase-address]
:on-error #(log/info "failed to create keypair " %)}]]]}))
(rf/reg-event-fx :wallet/add-keypair-and-create-account add-keypair-and-create-account)
(defn get-keypairs (defn get-keypairs
[_] [_]

View File

@ -47,6 +47,11 @@
:<- [:wallet/ui] :<- [:wallet/ui]
:-> :create-account) :-> :create-account)
(rf/reg-sub
:wallet/create-account-new-keypair
:<- [:wallet/create-account]
:-> :new-keypair)
(rf/reg-sub (rf/reg-sub
:wallet/network-filter :wallet/network-filter
:<- [:wallet/ui] :<- [:wallet/ui]
@ -165,6 +170,25 @@
:<- [:wallet/create-account] :<- [:wallet/create-account]
:-> :selected-keypair-uid) :-> :selected-keypair-uid)
(rf/reg-sub
:wallet/selected-keypair
:<- [:wallet/keypairs]
:<- [:wallet/selected-keypair-uid]
(fn [[keypairs selected-keypair-uid]]
(some #(when (= (:key-uid %) selected-keypair-uid)
%)
keypairs)))
(rf/reg-sub
:wallet/selected-primary-keypair?
:<- [:wallet/keypairs]
:<- [:wallet/selected-keypair-uid]
(fn [[keypairs selected-keypair-uid]]
(let [primary-keypair-uid (->> keypairs
(some #(when (= (:type %) "profile") %))
(:key-uid))]
(= selected-keypair-uid primary-keypair-uid))))
(rf/reg-sub (rf/reg-sub
:wallet/selected-networks->chain-ids :wallet/selected-networks->chain-ids
:<- [:wallet/selected-networks] :<- [:wallet/selected-networks]