Feature: Implement import of key-pair with recovery phrase inside wallet settings (#20181)
* fix: ensure missing key pairs scroll with list * chore: add translations for labels * chore: promisify validate-mnemonic native module function * chore: add events for validating seed-phrases and making key-pairs fully operable with seed-phrase * chore: define re-usable effects for validate-mnemonic and make-seed-phrase-keypair-fully-operable and import-keypair-by-seedphrase * tweak: refactor error handling logic when importing key-pair by seed-phrase * tweak: handle vectors and functions between events and effects * tweak: refactor recover-phrase form to support custom title, children, and navigation icon * tweak: add support for accessing input-ref in recovery-phrase form * tweak: always mask seed-phrase when passing to render-controls * feature: add initial screen for importing key-pair with seed phrase * tweak: conditionally render keypair context tag inside recovery-phrase form * tidy: rename on-input-ref to ref * tidy: remove unused property * tidy: refactor to use case expression * tidy: format
This commit is contained in:
parent
0d6bd9c15d
commit
d7530dfbee
|
@ -527,9 +527,11 @@
|
|||
|
||||
(defn validate-mnemonic
|
||||
"Validate that a mnemonic conforms to BIP39 dictionary/checksum standards"
|
||||
[mnemonic callback]
|
||||
(log/debug "[native-module] validate-mnemonic")
|
||||
(.validateMnemonic ^js (utils) mnemonic callback))
|
||||
([mnemonic]
|
||||
(native-utils/promisify-native-module-call validate-mnemonic mnemonic))
|
||||
([mnemonic callback]
|
||||
(log/debug "[native-module] validate-mnemonic")
|
||||
(.validateMnemonic ^js (utils) mnemonic callback)))
|
||||
|
||||
(defn delete-multiaccount
|
||||
"Delete multiaccount from database, deletes multiaccount's database and
|
||||
|
|
|
@ -40,7 +40,7 @@
|
|||
(defn recovery-phrase-input
|
||||
[{:keys [customization-color blur? on-focus on-blur mark-errors?
|
||||
error-pred-current-word error-pred-written-words word-limit
|
||||
container-style placeholder-text-color]
|
||||
container-style placeholder-text-color on-input-ref]
|
||||
:or {customization-color :blue
|
||||
word-limit ##Inf
|
||||
error-pred-current-word (constantly false)
|
||||
|
@ -60,7 +60,8 @@
|
|||
extra-props (apply dissoc props custom-props)]
|
||||
[rn/view {:style (style/container container-style)}
|
||||
[rn/text-input
|
||||
(merge {:accessibility-label :recovery-phrase-input
|
||||
(merge {:ref on-input-ref
|
||||
:accessibility-label :recovery-phrase-input
|
||||
:style (style/input theme)
|
||||
:placeholder-text-color (or placeholder-text-color
|
||||
(style/placeholder-color state theme blur?))
|
||||
|
|
|
@ -31,10 +31,10 @@
|
|||
(comp not word-in-dictionary?))
|
||||
|
||||
(defn- header
|
||||
[seed-phrase-count]
|
||||
[text seed-phrase-count]
|
||||
[rn/view {:style style/header-container}
|
||||
[quo/text {:weight :semi-bold :size :heading-1}
|
||||
(i18n/label :t/use-recovery-phrase)]
|
||||
text]
|
||||
[rn/view {:style style/word-count-container}
|
||||
[quo/text
|
||||
{:style {:color colors/white-opa-40}
|
||||
|
@ -51,34 +51,42 @@
|
|||
(string/replace #"\s+" " ")
|
||||
(string/trim)))
|
||||
|
||||
(defn- recovery-form
|
||||
[{:keys [seed-phrase word-count error-state? all-words-valid? on-change-seed-phrase
|
||||
keyboard-shown? on-submit]}]
|
||||
(let [button-disabled? (or error-state?
|
||||
(not (constants/seed-phrase-valid-length word-count))
|
||||
(not all-words-valid?))]
|
||||
[rn/view {:style style/form-container}
|
||||
[header word-count]
|
||||
[rn/view {:style style/input-container}
|
||||
[quo/recovery-phrase-input
|
||||
{:accessibility-label :passphrase-input
|
||||
:placeholder (i18n/label :t/seed-phrase-placeholder)
|
||||
:placeholder-text-color colors/white-opa-30
|
||||
:auto-capitalize :none
|
||||
:auto-correct false
|
||||
:auto-focus true
|
||||
:mark-errors? true
|
||||
:word-limit max-seed-phrase-length
|
||||
:error-pred-current-word partial-word-not-in-dictionary?
|
||||
:error-pred-written-words word-not-in-dictionary?
|
||||
:on-change-text on-change-seed-phrase}
|
||||
seed-phrase]]
|
||||
[quo/button
|
||||
{:container-style (style/continue-button keyboard-shown?)
|
||||
:type :primary
|
||||
:disabled? button-disabled?
|
||||
:on-press on-submit}
|
||||
(i18n/label :t/continue)]]))
|
||||
(defn- secure-clean-seed-phrase
|
||||
[seed-phrase]
|
||||
(-> seed-phrase
|
||||
security/safe-unmask-data
|
||||
clean-seed-phrase
|
||||
security/mask-data))
|
||||
|
||||
(defn- recovery-phrase-form
|
||||
[{:keys [keypair title seed-phrase word-count on-change-seed-phrase ref]} & children]
|
||||
(->> children
|
||||
(into
|
||||
[rn/view {:style style/form-container}
|
||||
[header title word-count]
|
||||
(when keypair
|
||||
[quo/context-tag
|
||||
{:type :icon
|
||||
:container-style {:padding-top 8}
|
||||
:icon :i/seed-phrase
|
||||
:size 24
|
||||
:blur? true
|
||||
:context (:name keypair)}])
|
||||
[rn/view {:style style/input-container}
|
||||
[quo/recovery-phrase-input
|
||||
{:accessibility-label :passphrase-input
|
||||
:ref ref
|
||||
:placeholder (i18n/label :t/seed-phrase-placeholder)
|
||||
:placeholder-text-color colors/white-opa-30
|
||||
:auto-capitalize :none
|
||||
:auto-correct false
|
||||
:auto-focus true
|
||||
:mark-errors? true
|
||||
:word-limit max-seed-phrase-length
|
||||
:error-pred-current-word partial-word-not-in-dictionary?
|
||||
:error-pred-written-words word-not-in-dictionary?
|
||||
:on-change-text on-change-seed-phrase}
|
||||
seed-phrase]]])))
|
||||
|
||||
(defn keyboard-suggestions
|
||||
[current-word]
|
||||
|
@ -86,8 +94,8 @@
|
|||
(filter #(string/starts-with? % current-word))
|
||||
(take 7)))
|
||||
|
||||
(defn screen
|
||||
[recovering-keypair?]
|
||||
(defn recovery-phrase-screen
|
||||
[{:keys [keypair title recovering-keypair? render-controls]}]
|
||||
(reagent/with-let [keyboard-shown? (reagent/atom false)
|
||||
keyboard-show-listener (.addListener rn/keyboard
|
||||
"keyboardDidShow"
|
||||
|
@ -96,6 +104,11 @@
|
|||
"keyboardDidHide"
|
||||
#(reset! keyboard-shown? false))
|
||||
invalid-seed-phrase? (reagent/atom false)
|
||||
input-ref (reagent/atom nil)
|
||||
focus-input (fn []
|
||||
(let [ref @input-ref]
|
||||
(when ref
|
||||
(.focus ref))))
|
||||
set-invalid-seed-phrase #(reset! invalid-seed-phrase? true)
|
||||
seed-phrase (reagent/atom "")
|
||||
on-change-seed-phrase (fn [new-phrase]
|
||||
|
@ -139,16 +152,33 @@
|
|||
words-exceeded? (i18n/label :t/seed-phrase-words-exceeded)
|
||||
error-in-words? (i18n/label :t/seed-phrase-error)
|
||||
@invalid-seed-phrase? (i18n/label :t/seed-phrase-invalid)
|
||||
:else (i18n/label :t/seed-phrase-info))]
|
||||
:else (i18n/label :t/seed-phrase-info))
|
||||
error-state? (= suggestions-state :error)
|
||||
button-disabled? (or error-state?
|
||||
(not (constants/seed-phrase-valid-length word-count))
|
||||
(not all-words-valid?))]
|
||||
[:<>
|
||||
[recovery-form
|
||||
{:seed-phrase @seed-phrase
|
||||
:error-state? (= suggestions-state :error)
|
||||
:all-words-valid? all-words-valid?
|
||||
[recovery-phrase-form
|
||||
{:title title
|
||||
:keypair keypair
|
||||
:seed-phrase @seed-phrase
|
||||
:on-change-seed-phrase on-change-seed-phrase
|
||||
:word-count word-count
|
||||
:on-submit on-submit
|
||||
:keyboard-shown? @keyboard-shown?}]
|
||||
:ref #(reset! input-ref %)}
|
||||
(if (fn? render-controls)
|
||||
(render-controls {:submit-disabled? button-disabled?
|
||||
:keyboard-shown? @keyboard-shown?
|
||||
:container-style (style/continue-button @keyboard-shown?)
|
||||
:prepare-seed-phrase secure-clean-seed-phrase
|
||||
:focus-input focus-input
|
||||
:seed-phrase (security/mask-data @seed-phrase)
|
||||
:set-invalid-seed-phrase set-invalid-seed-phrase})
|
||||
[quo/button
|
||||
{:container-style (style/continue-button @keyboard-shown?)
|
||||
:type :primary
|
||||
:disabled? button-disabled?
|
||||
:on-press on-submit}
|
||||
(i18n/label :t/continue)])]
|
||||
(when @keyboard-shown?
|
||||
[rn/view {:style style/keyboard-container}
|
||||
[quo/predictive-keyboard
|
||||
|
@ -161,16 +191,29 @@
|
|||
(.remove keyboard-show-listener)
|
||||
(.remove keyboard-hide-listener))))
|
||||
|
||||
(defn screen
|
||||
[{:keys [initial-insets title keypair navigation-icon recovering-keypair? render-controls]}]
|
||||
(let [{navigation-bar-top :top} initial-insets]
|
||||
[rn/view {:style style/full-layout}
|
||||
[rn/keyboard-avoiding-view {:style style/page-container}
|
||||
[quo/page-nav
|
||||
{:margin-top navigation-bar-top
|
||||
:background :blur
|
||||
:icon-name (or navigation-icon
|
||||
(if recovering-keypair? :i/close :i/arrow-left))
|
||||
:on-press #(rf/dispatch [:navigate-back])}]
|
||||
[recovery-phrase-screen
|
||||
{:title title
|
||||
:keypair keypair
|
||||
:render-controls render-controls
|
||||
:recovering-keypair? recovering-keypair?}]]]))
|
||||
|
||||
(defn view
|
||||
[]
|
||||
(let [{navigation-bar-top :top} (safe-area/get-insets)]
|
||||
(let [insets (safe-area/get-insets)]
|
||||
(fn []
|
||||
(let [{:keys [recovering-keypair?]} (rf/sub [:get-screen-params])]
|
||||
[rn/view {:style style/full-layout}
|
||||
[rn/keyboard-avoiding-view {:style style/page-container}
|
||||
[quo/page-nav
|
||||
{:margin-top navigation-bar-top
|
||||
:background :blur
|
||||
:icon-name (if recovering-keypair? :i/close :i/arrow-left)
|
||||
:on-press #(rf/dispatch [:navigate-back])}]
|
||||
[screen recovering-keypair?]]]))))
|
||||
[screen
|
||||
{:title (i18n/label :t/use-recovery-phrase)
|
||||
:initial-insets insets
|
||||
:recovering-keypair? recovering-keypair?}]))))
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
(ns status-im.contexts.onboarding.events
|
||||
(:require
|
||||
[native-module.core :as native-module]
|
||||
[re-frame.core :as re-frame]
|
||||
status-im.common.biometric.events
|
||||
[status-im.constants :as constants]
|
||||
|
@ -9,19 +8,7 @@
|
|||
[taoensso.timbre :as log]
|
||||
[utils.i18n :as i18n]
|
||||
[utils.re-frame :as rf]
|
||||
[utils.security.core :as security]
|
||||
[utils.transforms :as transforms]))
|
||||
|
||||
(re-frame/reg-fx
|
||||
:multiaccount/validate-mnemonic
|
||||
(fn [[mnemonic on-success on-error]]
|
||||
(native-module/validate-mnemonic
|
||||
(security/safe-unmask-data mnemonic)
|
||||
(fn [result]
|
||||
(let [{:keys [error keyUID]} (transforms/json->clj result)]
|
||||
(if (seq error)
|
||||
(when on-error (on-error error))
|
||||
(on-success mnemonic keyUID)))))))
|
||||
[utils.security.core :as security]))
|
||||
|
||||
(rf/defn profile-data-set
|
||||
{:events [:onboarding/profile-data-set]}
|
||||
|
|
|
@ -1,9 +1,11 @@
|
|||
(ns status-im.contexts.settings.wallet.events
|
||||
(:require
|
||||
[native-module.core :as native-module]
|
||||
[status-im.contexts.settings.wallet.data-store :as data-store]
|
||||
[taoensso.timbre :as log]
|
||||
[utils.i18n :as i18n]
|
||||
[utils.re-frame :as rf]))
|
||||
[utils.re-frame :as rf]
|
||||
[utils.security.core :as security]))
|
||||
|
||||
(rf/reg-event-fx
|
||||
:wallet/rename-keypair-success
|
||||
|
@ -117,3 +119,54 @@
|
|||
:sha3-pwd password}]))}]]]})
|
||||
|
||||
(rf/reg-event-fx :wallet/success-keypair-qr-scan success-keypair-qr-scan)
|
||||
|
||||
(defn wallet-validate-seed-phrase
|
||||
[_ [seed-phrase on-success on-error]]
|
||||
{:fx [[:multiaccount/validate-mnemonic [seed-phrase on-success on-error]]]})
|
||||
|
||||
(rf/reg-event-fx :wallet/validate-seed-phrase wallet-validate-seed-phrase)
|
||||
|
||||
(defn make-seed-phrase-keypair-fully-operable
|
||||
[_ [mnemonic password on-success on-error]]
|
||||
{:fx [[:json-rpc/call
|
||||
[{:method "accounts_makeSeedPhraseKeypairFullyOperable"
|
||||
:params [(security/safe-unmask-data mnemonic)
|
||||
(-> password security/safe-unmask-data native-module/sha3)]
|
||||
:on-success on-success
|
||||
:on-error on-error}]]]})
|
||||
|
||||
(rf/reg-event-fx :wallet/make-seed-phrase-keypair-fully-operable make-seed-phrase-keypair-fully-operable)
|
||||
|
||||
(defn import-keypair-by-seed-phrase
|
||||
[_ [{:keys [keypair-key-uid seed-phrase password on-success on-error]}]]
|
||||
{:fx [[:import-keypair-by-seed-phrase
|
||||
{:keypair-key-uid keypair-key-uid
|
||||
:seed-phrase seed-phrase
|
||||
:password password
|
||||
:on-success (fn []
|
||||
(rf/dispatch [:wallet/make-keypairs-accounts-fully-operable
|
||||
#{keypair-key-uid}])
|
||||
(cond
|
||||
(vector? on-success) (rf/dispatch (conj on-success))
|
||||
(fn? on-success) (on-success)))
|
||||
:on-error (fn [error]
|
||||
(rf/dispatch [:wallet/import-keypair-by-seed-phrase-failed error])
|
||||
(cond
|
||||
(vector? on-error) (rf/dispatch (conj on-error error))
|
||||
(fn? on-error) (on-error error)))}]]})
|
||||
|
||||
(rf/reg-event-fx :wallet/import-keypair-by-seed-phrase import-keypair-by-seed-phrase)
|
||||
|
||||
(defn import-keypair-by-seed-phrase-failed
|
||||
[_ [error]]
|
||||
(let [error-type (-> error ex-message keyword)
|
||||
error-data (ex-data error)]
|
||||
(when-not (and (= error-type :import-keypair-by-seed-phrase/import-error)
|
||||
(= (:hint error-data) :incorrect-seed-phrase-for-keypair))
|
||||
{:fx [[:dispatch
|
||||
[:toasts/upsert
|
||||
{:type :negative
|
||||
:theme :dark
|
||||
:text (:error error-data)}]]]})))
|
||||
|
||||
(rf/reg-event-fx :wallet/import-keypair-by-seed-phrase-failed import-keypair-by-seed-phrase-failed)
|
||||
|
|
|
@ -2,7 +2,9 @@
|
|||
(:require
|
||||
[cljs.test :refer-macros [deftest is testing]]
|
||||
matcher-combinators.test
|
||||
[status-im.contexts.settings.wallet.events :as sut]))
|
||||
[native-module.core :as native-module]
|
||||
[status-im.contexts.settings.wallet.events :as sut]
|
||||
[utils.security.core :as security]))
|
||||
|
||||
(def mock-key-uid "key-1")
|
||||
(defn mock-db
|
||||
|
@ -95,3 +97,33 @@
|
|||
(let [effects (sut/success-keypair-qr-scan nil [connection-string keypairs-key-uids])
|
||||
fx (:fx effects)]
|
||||
(is (some? fx))))))
|
||||
|
||||
(deftest wallet-validate-seed-phrase-test
|
||||
(let [cofx {:db {}}
|
||||
seed-phrase-masked (security/mask-data "seed phrase")
|
||||
on-success #(prn "success")
|
||||
on-error #(prn "error")
|
||||
expected {:fx [[:multiaccount/validate-mnemonic
|
||||
[seed-phrase-masked on-success on-error]]]}]
|
||||
(is (= expected
|
||||
(sut/wallet-validate-seed-phrase
|
||||
cofx
|
||||
[seed-phrase-masked on-success on-error])))))
|
||||
|
||||
(deftest make-seed-phrase-keypair-fully-operable-test
|
||||
(let [cofx {:db {}}
|
||||
mnemonic "seed phrase"
|
||||
password "password"
|
||||
mnemonic-masked (security/mask-data mnemonic)
|
||||
password-masked (security/mask-data password)
|
||||
on-success #(prn "success")
|
||||
on-error #(prn "error")
|
||||
expected {:fx [[:json-rpc/call
|
||||
[{:method "accounts_makeSeedPhraseKeypairFullyOperable"
|
||||
:params [mnemonic (native-module/sha3 password)]
|
||||
:on-success fn?
|
||||
:on-error fn?}]]]}]
|
||||
(is (match? expected
|
||||
(sut/make-seed-phrase-keypair-fully-operable
|
||||
cofx
|
||||
[mnemonic-masked password-masked on-success on-error])))))
|
||||
|
|
|
@ -6,27 +6,31 @@
|
|||
[utils.re-frame :as rf]))
|
||||
|
||||
(defn view
|
||||
[props keypair]
|
||||
(let [has-paired-device (rf/sub [:pairing/has-paired-devices])
|
||||
missing-keypair? (= (:stored props) :missing)
|
||||
on-scan-qr (rn/use-callback #(rf/dispatch [:open-modal :screen/settings.scan-keypair-qr
|
||||
[(:key-uid keypair)]])
|
||||
[keypair])
|
||||
on-show-qr (rn/use-callback #(rf/dispatch [:open-modal
|
||||
:screen/settings.encrypted-key-pair-qr
|
||||
keypair])
|
||||
[keypair])
|
||||
on-remove-keypair (rn/use-callback #(rf/dispatch
|
||||
[:show-bottom-sheet
|
||||
{:theme :dark
|
||||
:content (fn []
|
||||
[remove-key-pair/view keypair])}])
|
||||
[keypair])
|
||||
on-rename-keypair (rn/use-callback #(rf/dispatch [:open-modal :screen/settings.rename-keypair
|
||||
keypair])
|
||||
[keypair])]
|
||||
[{:keys [drawer-props keypair]}]
|
||||
(let [has-paired-device (rf/sub [:pairing/has-paired-devices])
|
||||
missing-keypair? (= (:stored drawer-props) :missing)
|
||||
on-scan-qr (rn/use-callback #(rf/dispatch [:open-modal
|
||||
:screen/settings.scan-keypair-qr
|
||||
[(:key-uid keypair)]])
|
||||
[keypair])
|
||||
on-show-qr (rn/use-callback #(rf/dispatch [:open-modal
|
||||
:screen/settings.encrypted-key-pair-qr
|
||||
keypair])
|
||||
[keypair])
|
||||
on-remove-keypair (rn/use-callback #(rf/dispatch
|
||||
[:show-bottom-sheet
|
||||
{:theme :dark
|
||||
:content (fn []
|
||||
[remove-key-pair/view keypair])}])
|
||||
[keypair])
|
||||
on-rename-keypair (rn/use-callback #(rf/dispatch [:open-modal :screen/settings.rename-keypair
|
||||
keypair])
|
||||
[keypair])
|
||||
on-import-seed-phrase (rn/use-callback
|
||||
#(rf/dispatch [:open-modal :screen/settings.import-seed-phrase keypair])
|
||||
[keypair])]
|
||||
[:<>
|
||||
[quo/drawer-top props]
|
||||
[quo/drawer-top drawer-props]
|
||||
[quo/action-drawer
|
||||
[(when has-paired-device
|
||||
(if-not missing-keypair?
|
||||
|
@ -38,8 +42,15 @@
|
|||
:accessibility-label :import-by-scan-qr
|
||||
:label (i18n/label :t/import-by-scanning-encrypted-qr)
|
||||
:on-press on-scan-qr}]))
|
||||
(when (= (:type props) :keypair)
|
||||
[{:icon :i/edit
|
||||
(when (= (:type drawer-props) :keypair)
|
||||
[(when missing-keypair?
|
||||
(case (:type keypair)
|
||||
:seed {:icon :i/seed
|
||||
:accessibility-label :import-seed-phrase
|
||||
:label (i18n/label :t/import-by-entering-recovery-phrase)
|
||||
:on-press #(on-import-seed-phrase keypair)}
|
||||
nil))
|
||||
{:icon :i/edit
|
||||
:accessibility-label :rename-key-pair
|
||||
:label (i18n/label :t/rename-key-pair)
|
||||
:on-press on-rename-keypair}
|
||||
|
|
|
@ -0,0 +1,65 @@
|
|||
(ns status-im.contexts.settings.wallet.keypairs-and-accounts.import-seed-phrase.view
|
||||
(:require
|
||||
[quo.core :as quo]
|
||||
[react-native.core :as rn]
|
||||
[react-native.safe-area :as safe-area]
|
||||
[reagent.core :as reagent]
|
||||
[status-im.common.enter-seed-phrase.view :as enter-seed-phrase]
|
||||
[status-im.common.standard-authentication.core :as standard-auth]
|
||||
[utils.i18n :as i18n]
|
||||
[utils.re-frame :as rf]))
|
||||
|
||||
(defn import-seed-phrase-controls
|
||||
[{:keys [submit-disabled?
|
||||
container-style
|
||||
prepare-seed-phrase
|
||||
seed-phrase
|
||||
set-invalid-seed-phrase
|
||||
focus-input]}]
|
||||
(let [keypair (rf/sub [:get-screen-params])
|
||||
customization-color (rf/sub [:profile/customization-color])
|
||||
show-errors (rn/use-callback
|
||||
#(js/setTimeout
|
||||
(fn []
|
||||
(focus-input)
|
||||
(reagent/next-tick set-invalid-seed-phrase))
|
||||
600))
|
||||
on-import-error (rn/use-callback
|
||||
(fn [_error]
|
||||
(rf/dispatch [:hide-bottom-sheet])
|
||||
(show-errors)))
|
||||
on-import-success (rn/use-callback
|
||||
(fn []
|
||||
(rf/dispatch [:hide-bottom-sheet])
|
||||
(rf/dispatch [:navigate-back])))
|
||||
on-auth-success (rn/use-callback
|
||||
(fn [password]
|
||||
(rf/dispatch [:wallet/import-keypair-by-seed-phrase
|
||||
{:keypair-key-uid (:key-uid keypair)
|
||||
:seed-phrase (prepare-seed-phrase seed-phrase)
|
||||
:password password
|
||||
:on-success on-import-success
|
||||
:on-error on-import-error}]))
|
||||
[keypair seed-phrase on-import-success on-import-error])]
|
||||
[standard-auth/slide-button
|
||||
{:blur? true
|
||||
:size :size-48
|
||||
:customization-color customization-color
|
||||
:track-text (i18n/label :t/slide-to-import)
|
||||
:on-auth-success on-auth-success
|
||||
:auth-button-label (i18n/label :t/import-key-pair)
|
||||
:auth-button-icon-left :i/seed
|
||||
:container-style container-style
|
||||
:disabled? submit-disabled?
|
||||
:dependencies [on-auth-success]}]))
|
||||
|
||||
(defn view
|
||||
[]
|
||||
(let [keypair (rf/sub [:get-screen-params])]
|
||||
[quo/overlay {:type :shell}
|
||||
[enter-seed-phrase/screen
|
||||
{:keypair keypair
|
||||
:navigation-icon :i/close
|
||||
:render-controls import-seed-phrase-controls
|
||||
:title (i18n/label :t/enter-recovery-phrase)
|
||||
:initial-insets (safe-area/get-insets)}]]))
|
|
@ -14,11 +14,12 @@
|
|||
(rf/dispatch [:navigate-back]))
|
||||
|
||||
(defn on-options-press
|
||||
[{:keys [theme]
|
||||
:as props} keypair]
|
||||
[{:keys [drawer-props keypair]}]
|
||||
(rf/dispatch [:show-bottom-sheet
|
||||
{:content (fn [] [actions/view props keypair])
|
||||
:theme theme}]))
|
||||
{:content (fn [] [actions/view
|
||||
{:drawer-props drawer-props
|
||||
:keypair keypair}])
|
||||
:theme (:theme drawer-props)}]))
|
||||
|
||||
(defn options-drawer-props
|
||||
[{{:keys [name]} :keypair
|
||||
|
@ -48,15 +49,17 @@
|
|||
on-press (rn/use-callback
|
||||
(fn []
|
||||
(on-options-press
|
||||
(options-drawer-props
|
||||
{:theme theme
|
||||
:keypair item
|
||||
:type (if default-keypair? :default-keypair :keypair)
|
||||
:stored :on-device
|
||||
:shortened-key shortened-key
|
||||
:customization-color customization-color
|
||||
:profile-picture profile-picture})
|
||||
item))
|
||||
{:keypair item
|
||||
:drawer-props (options-drawer-props
|
||||
{:theme theme
|
||||
:keypair item
|
||||
:type (if default-keypair?
|
||||
:default-keypair
|
||||
:keypair)
|
||||
:stored :on-device
|
||||
:shortened-key shortened-key
|
||||
:customization-color customization-color
|
||||
:profile-picture profile-picture})}))
|
||||
[customization-color default-keypair? item
|
||||
profile-picture shortened-key theme])]
|
||||
[quo/keypair
|
||||
|
@ -78,13 +81,13 @@
|
|||
(rf/dispatch [:show-bottom-sheet
|
||||
{:theme :dark
|
||||
:content (fn [] [actions/view
|
||||
(options-drawer-props
|
||||
{:theme :dark
|
||||
:type :keypair
|
||||
:stored :missing
|
||||
:blur? true
|
||||
:keypair keypair-data})
|
||||
keypair-data])}]))
|
||||
{:keypair keypair-data
|
||||
:drawer-props (options-drawer-props
|
||||
{:theme :dark
|
||||
:type :keypair
|
||||
:stored :missing
|
||||
:blur? true
|
||||
:keypair keypair-data})}])}]))
|
||||
|
||||
(defn view
|
||||
[]
|
||||
|
@ -112,16 +115,16 @@
|
|||
:accessibility-label :keypairs-and-accounts-header
|
||||
:customization-color customization-color}]
|
||||
[rn/view {:style style/settings-keypairs-container}
|
||||
(when (seq missing-keypairs)
|
||||
[quo/missing-keypairs
|
||||
{:blur? true
|
||||
:keypairs missing-keypairs
|
||||
:on-import-press on-import-press
|
||||
:container-style style/missing-keypairs-container-style
|
||||
:on-options-press on-missing-keypair-options-press}])
|
||||
[rn/flat-list
|
||||
{:data operable-keypairs
|
||||
:render-fn keypair
|
||||
:header (when (seq missing-keypairs)
|
||||
[quo/missing-keypairs
|
||||
{:blur? true
|
||||
:keypairs missing-keypairs
|
||||
:on-import-press on-import-press
|
||||
:container-style style/missing-keypairs-container-style
|
||||
:on-options-press on-missing-keypair-options-press}])
|
||||
:render-data {:profile-picture profile-picture
|
||||
:compressed-key compressed-key
|
||||
:customization-color customization-color}
|
||||
|
|
|
@ -2,8 +2,16 @@
|
|||
(:require
|
||||
[clojure.string :as string]
|
||||
[native-module.core :as native-module]
|
||||
[promesa.core :as promesa]
|
||||
[re-frame.core :as rf]
|
||||
[taoensso.timbre :as log]))
|
||||
[status-im.common.json-rpc.events :as json-rpc]
|
||||
[taoensso.timbre :as log]
|
||||
[utils.security.core :as security]
|
||||
[utils.transforms :as transforms]))
|
||||
|
||||
(defn- error-message
|
||||
[kw]
|
||||
(-> kw symbol str))
|
||||
|
||||
(rf/reg-fx
|
||||
:effects.wallet/create-account-from-mnemonic
|
||||
|
@ -17,3 +25,66 @@
|
|||
{:MnemonicPhrase phrase
|
||||
:paths paths}
|
||||
on-success))))
|
||||
|
||||
(defn validate-mnemonic
|
||||
[mnemonic]
|
||||
(-> mnemonic
|
||||
(security/safe-unmask-data)
|
||||
(native-module/validate-mnemonic)
|
||||
(promesa/then (fn [result]
|
||||
(let [{:keys [keyUID]} (transforms/json->clj result)]
|
||||
{:key-uid keyUID})))))
|
||||
|
||||
(rf/reg-fx
|
||||
:multiaccount/validate-mnemonic
|
||||
(fn [[mnemonic on-success on-error]]
|
||||
(-> (validate-mnemonic mnemonic)
|
||||
(promesa/then (fn [{:keys [key-uid]}]
|
||||
(when (fn? on-success)
|
||||
(on-success mnemonic key-uid))))
|
||||
(promesa/catch (fn [error]
|
||||
(when (and error (fn? on-error))
|
||||
(on-error error)))))))
|
||||
|
||||
(defn make-seed-phrase-fully-operable
|
||||
[mnemonic password]
|
||||
(promesa/create
|
||||
(fn [resolver rejecter]
|
||||
(json-rpc/call {:method "accounts_makeSeedPhraseKeypairFullyOperable"
|
||||
:params [(security/safe-unmask-data mnemonic)
|
||||
(-> password security/safe-unmask-data native-module/sha3)]
|
||||
:on-error (fn [error]
|
||||
(rejecter (ex-info (str error) {:error error})))
|
||||
:on-success (fn [value]
|
||||
(resolver {:value value}))}))))
|
||||
|
||||
(defn import-keypair-by-seed-phrase
|
||||
[keypair-key-uid seed-phrase password]
|
||||
(-> (validate-mnemonic seed-phrase)
|
||||
(promesa/then
|
||||
(fn [{:keys [key-uid]}]
|
||||
(if (not= keypair-key-uid key-uid)
|
||||
(promesa/rejected
|
||||
(ex-info
|
||||
(error-message :import-keypair-by-seed-phrase/import-error)
|
||||
{:hint :incorrect-seed-phrase-for-keypair}))
|
||||
(make-seed-phrase-fully-operable seed-phrase password))))
|
||||
(promesa/catch
|
||||
(fn [error]
|
||||
(promesa/rejected
|
||||
(ex-info
|
||||
(error-message :import-keypair-by-seed-phrase/import-error)
|
||||
(ex-data error)))))))
|
||||
|
||||
(rf/reg-fx
|
||||
:import-keypair-by-seed-phrase
|
||||
(fn [{:keys [keypair-key-uid seed-phrase password on-success on-error]}]
|
||||
(-> (import-keypair-by-seed-phrase keypair-key-uid seed-phrase password)
|
||||
(promesa/then (fn [_result]
|
||||
(cond
|
||||
(vector? on-success) (rf/dispatch on-success)
|
||||
(fn? on-success) (on-success))))
|
||||
(promesa/catch (fn [error]
|
||||
(cond
|
||||
(vector? on-error) (rf/dispatch (conj on-error error))
|
||||
(fn? on-error) (on-error error)))))))
|
||||
|
|
|
@ -59,6 +59,8 @@
|
|||
[status-im.contexts.profile.settings.view :as settings]
|
||||
[status-im.contexts.settings.wallet.keypairs-and-accounts.encrypted-qr.view :as
|
||||
encrypted-key-pair-qr]
|
||||
[status-im.contexts.settings.wallet.keypairs-and-accounts.import-seed-phrase.view :as
|
||||
import-seed-phrase]
|
||||
[status-im.contexts.settings.wallet.keypairs-and-accounts.rename.view :as keypair-rename]
|
||||
[status-im.contexts.settings.wallet.keypairs-and-accounts.scan-qr.view :as scan-keypair-qr]
|
||||
[status-im.contexts.settings.wallet.keypairs-and-accounts.view :as keypairs-and-accounts]
|
||||
|
@ -547,6 +549,10 @@
|
|||
:options options/transparent-modal-screen-options
|
||||
:component scan-keypair-qr/view}
|
||||
|
||||
{:name :screen/settings.import-seed-phrase
|
||||
:options options/transparent-screen-options
|
||||
:component import-seed-phrase/view}
|
||||
|
||||
{:name :screen/settings.network-settings
|
||||
:options options/transparent-modal-screen-options
|
||||
:component network-settings/view}
|
||||
|
|
|
@ -1018,6 +1018,9 @@
|
|||
"generate-keys": "Generate keys",
|
||||
"generate-keys-subtitle": "Create your new self-sovereign identity",
|
||||
"experienced-web3": "Experienced in Web3?",
|
||||
"enter-recovery-phrase": "Enter recovery phrase",
|
||||
"import-key-pair": "Import key pair",
|
||||
"import-by-entering-recovery-phrase": "Import by entering recovery phrase",
|
||||
"use-recovery-phrase": "Use recovery phrase",
|
||||
"use-recovery-phrase-subtitle": "If you already have an Ethereum address",
|
||||
"use-keycard": "Use Keycard",
|
||||
|
@ -1980,6 +1983,7 @@
|
|||
"select-token-to-swap": "Select token to Swap",
|
||||
"select-token-to-receive": "Select token to receive",
|
||||
"slide-to-request-to-join": "Slide to request to join",
|
||||
"slide-to-import": "Slide to import",
|
||||
"slide-to-reveal-code": "Slide to reveal code",
|
||||
"slide-to-create-account": "Slide to create account",
|
||||
"slide-to-remove-key-pair": "Slide to remove key pair",
|
||||
|
|
Loading…
Reference in New Issue