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:
Sean Hagstrom 2024-06-06 10:33:34 +01:00 committed by GitHub
parent 0d6bd9c15d
commit d7530dfbee
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
12 changed files with 397 additions and 119 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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