feat: new keypair: mnemonic, address, KP name screen (#18790)

feat: new keypair: mnemonic, address, KP name screen (#18790)
This commit is contained in:
Omar Basem 2024-02-21 15:53:14 +04:00 committed by GitHub
parent cfaed80066
commit 28f43acb83
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
15 changed files with 337 additions and 135 deletions

View File

@ -342,4 +342,14 @@ public class AccountManager extends ReactContextBaseJavaModule {
final String keyStoreDir = this.utils.getKeyStorePath(keyUID);
this.utils.executeRunnableStatusGoMethod(() -> Statusgo.deleteMultiaccount(keyUID, keyStoreDir), callback);
}
@ReactMethod
public void getRandomMnemonic(final Callback callback) throws JSONException {
this.utils.executeRunnableStatusGoMethod(() -> Statusgo.getRandomMnemonic(), callback);
}
@ReactMethod
public void createAccountFromMnemonicAndDeriveAccountsForPaths(final String mnemonic, final Callback callback) throws JSONException {
this.utils.executeRunnableStatusGoMethod(() -> Statusgo.createAccountFromMnemonicAndDeriveAccountsForPaths(mnemonic), callback);
}
}

View File

@ -214,4 +214,20 @@ RCT_EXPORT_METHOD(logout) {
NSLog(@"%@", result);
}
RCT_EXPORT_METHOD(getRandomMnemonic:(RCTResponseSenderBlock)callback) {
#if DEBUG
NSLog(@"GetRandomMnemonic() method called");
#endif
NSString *result = StatusgoGetRandomMnemonic();
callback(@[result]);
}
RCT_EXPORT_METHOD(createAccountFromMnemonicAndDeriveAccountsForPaths:(NSString *)mnemonic callback:(RCTResponseSenderBlock)callback) {
#if DEBUG
NSLog(@"createAccountFromMnemonicAndDeriveAccountsForPaths() method called");
#endif
NSString *result = StatusgoCreateAccountFromMnemonicAndDeriveAccountsForPaths(mnemonic);
callback(@[result]);
}
@end

View File

@ -573,3 +573,13 @@
(defn init-status-go-logging
[{:keys [enable? mobile-system? log-level callback]}]
(.initLogging ^js (log-manager) enable? mobile-system? log-level callback))
(defn get-random-mnemonic
[callback]
(.getRandomMnemonic ^js (account-manager) #(callback (types/json->clj %))))
(defn create-account-from-mnemonic
[mnemonic callback]
(.createAccountFromMnemonicAndDeriveAccountsForPaths ^js (account-manager)
(types/clj->json mnemonic)
#(callback (types/json->clj %))))

View File

@ -1,11 +1,12 @@
(ns status-im.contexts.wallet.create-account.new-keypair.backup-recovery-phrase.view
(:require
[clojure.string :as string]
[native-module.core :as native-module]
[quo.core :as quo]
[quo.theme :as quo.theme]
[react-native.blur :as blur]
[react-native.core :as rn]
[reagent.core :as reagent]
[status-im.contexts.wallet.common.temp :as temp]
[status-im.contexts.wallet.create-account.new-keypair.backup-recovery-phrase.style :as style]
[utils.i18n :as i18n]
[utils.re-frame :as rf]))
@ -17,7 +18,7 @@
[quo/text {:style {:margin-left 4}} item]])
(defn- words-column
[words first-half?]
[{:keys [words first-half?]}]
[rn/flat-list
{:style {:padding-vertical 8}
:data (if first-half? (subvec words 0 6) (subvec words 6))
@ -34,18 +35,24 @@
:on-change #(swap! checked? assoc (keyword (str index)) %)}]
[quo/text {:style {:margin-left 12}} (i18n/label item)]])
(defn- view-internal
(defn- f-view
[{:keys [theme]}]
(let [step-labels [:t/backup-step-1 :t/backup-step-2 :t/backup-step-3
:t/backup-step-4]
checked? (reagent/atom
{:0 false
:1 false
:2 false
:3 false})
revealed? (reagent/atom false)
{:keys [customization-color]} (rf/sub [:profile/profile])]
(let [step-labels [:t/backup-step-1 :t/backup-step-2 :t/backup-step-3
:t/backup-step-4]
checked? (reagent/atom
{:0 false
:1 false
:2 false
:3 false})
revealed? (reagent/atom false)
customization-color (rf/sub [:profile/customization-color])
secret-phrase (reagent/atom [])
random-phrase (reagent/atom [])]
(fn []
(rn/use-effect
(fn []
(native-module/get-random-mnemonic #(reset! secret-phrase (string/split % #"\s")))
(native-module/get-random-mnemonic #(reset! random-phrase (string/split % #"\s")))))
[rn/view {:style {:flex 1}}
[quo/page-nav
{:icon-name :i/close
@ -56,9 +63,15 @@
:description :text
:description-text (i18n/label :t/backup-recovery-phrase-description)}]
[rn/view {:style (style/seed-phrase-container theme)}
[words-column temp/secret-phrase true]
[rn/view {:style (style/separator theme)}]
[words-column temp/secret-phrase false]
(when (pos? (count @secret-phrase))
[:<>
[words-column
{:words @secret-phrase
:first-half? true}]
[rn/view {:style (style/separator theme)}]
[words-column
{:words @secret-phrase
:first-half? false}]])
(when-not @revealed?
[rn/view {:style style/blur-container}
[blur/view (style/blur theme)]])]
@ -82,8 +95,9 @@
:button-one-label (i18n/label :t/i-have-written)
:button-one-props {:disabled? (some false? (vals @checked?))
:customization-color customization-color
:on-press #(rf/dispatch [:navigate-to
:wallet-check-your-backup])}}]
:on-press #(rf/dispatch [:wallet/store-secret-phrase
{:secret-phrase @secret-phrase
:random-phrase @random-phrase}])}}]
[quo/text
{:size :paragraph-2
:style (style/description-text theme)}
@ -96,4 +110,8 @@
:on-press #(reset! revealed? true)}
:container-style style/slide-button}])])))
(defn view-internal
[params]
[:f> f-view params])
(def view (quo.theme/with-theme view-internal))

View File

@ -4,7 +4,6 @@
[quo.theme :as quo.theme]
[react-native.core :as rn]
[reagent.core :as reagent]
[status-im.contexts.wallet.common.temp :as temp]
[status-im.contexts.wallet.create-account.new-keypair.check-your-backup.style :as style]
[utils.i18n :as i18n]
[utils.re-frame :as rf]))
@ -31,7 +30,7 @@
(defn- cheat-warning
[]
(let [{:keys [customization-color]} (rf/sub [:profile/profile])]
(let [customization-color (rf/sub [:profile/customization-color])]
[:<>
[quo/drawer-top {:title (i18n/label :t/do-not-cheat)}]
[quo/text
@ -62,26 +61,32 @@
(defn- view-internal
[]
(let [random-indices (random-selection)
quiz-index (reagent/atom 0)
incorrect-count (reagent/atom 0)
show-error? (reagent/atom false)]
(let [random-indices (random-selection)
quiz-index (reagent/atom 0)
incorrect-count (reagent/atom 0)
show-error? (reagent/atom false)
{:keys [secret-phrase random-phrase]} (rf/sub [:wallet/create-account])]
(fn []
(let [current-word-index (get random-indices (min @quiz-index (dec questions-count)))
current-word (get temp/secret-phrase current-word-index)
[options-r-0 options-r-1] (random-words-with-string temp/random-words current-word)
on-button-press (fn [word]
(if (= word current-word)
(do
(reset! quiz-index (inc @quiz-index))
(reset! incorrect-count 0)
(reset! show-error? false))
(do
(when (> @incorrect-count 0)
(rf/dispatch [:show-bottom-sheet
{:content cheat-warning}]))
(reset! incorrect-count (inc @incorrect-count))
(reset! show-error? true))))]
(let [current-word-index (get random-indices
(min @quiz-index (dec questions-count)))
current-word (get secret-phrase current-word-index)
[options-row-0 options-row-1] (random-words-with-string random-phrase current-word)
on-button-press (fn [word]
(if (= word current-word)
(do
(when (< @quiz-index questions-count)
(reset! quiz-index (inc @quiz-index)))
(reset! incorrect-count 0)
(reset! show-error? false)
(when (= @quiz-index questions-count)
(rf/dispatch [:navigate-to
:wallet-keypair-name])))
(do
(when (> @incorrect-count 0)
(rf/dispatch [:show-bottom-sheet
{:content cheat-warning}]))
(reset! incorrect-count (inc @incorrect-count))
(reset! show-error? true))))]
[rn/view {:style {:flex 1}}
[quo/page-nav
{:icon-name :i/arrow-left
@ -109,7 +114,7 @@
:else
:disabled)
:word (get temp/secret-phrase num)
:word (get secret-phrase num)
:number (inc num)
:on-press #(when (= @quiz-index index)
(reset! show-error? false))}])
@ -119,9 +124,9 @@
[buttons-row
{:on-press on-button-press
:margin-bottom 12
:options options-r-0}]
:options options-row-0}]
[buttons-row
{:on-press on-button-press
:options options-r-1}]]]))))
:options options-row-1}]]]))))
(def view (quo.theme/with-theme view-internal))

View File

@ -0,0 +1,11 @@
(ns status-im.contexts.wallet.create-account.new-keypair.keypair-name.style)
(def header-container
{:margin-horizontal 20
:margin-vertical 12})
(def bottom-action
{:position :absolute
:bottom 12
:left 0
:right 0})

View File

@ -0,0 +1,40 @@
(ns status-im.contexts.wallet.create-account.new-keypair.keypair-name.view
(:require
[quo.core :as quo]
[react-native.core :as rn]
[reagent.core :as reagent]
[status-im.contexts.wallet.create-account.new-keypair.keypair-name.style :as style]
[utils.i18n :as i18n]
[utils.re-frame :as rf]))
(def keypair-name-max-length 15)
(defn view
[]
(let [keypair-name (reagent/atom "")]
(fn []
(let [customization-color (rf/sub [:profile/customization-color])]
[rn/view {:style {:flex 1}}
[quo/page-nav
{:icon-name :i/arrow-left
:on-press #(rf/dispatch [:navigate-back])
:accessibility-label :top-bar}]
[quo/text-combinations
{:container-style style/header-container
:title (i18n/label :t/keypair-name)
:description (i18n/label :t/keypair-name-description)}]
[quo/input
{:container-style {:margin-horizontal 20}
:placeholder (i18n/label :t/keypair-name-input-placeholder)
:label (i18n/label :t/keypair-name)
:char-limit keypair-name-max-length
:on-change-text #(reset! keypair-name %)}]
[quo/bottom-actions
{:actions :one-action
:button-one-label (i18n/label :t/continue)
:button-one-props {:disabled? (or (zero? (count @keypair-name))
(> (count @keypair-name) keypair-name-max-length))
:customization-color customization-color
:on-press #(rf/dispatch [:wallet/new-keypair-continue
{:keypair-name @keypair-name}])}
:container-style style/bottom-action}]]))))

View File

@ -10,7 +10,6 @@
[status-im.common.emoji-picker.utils :as emoji-picker.utils]
[status-im.common.standard-authentication.core :as standard-auth]
[status-im.constants :as constants]
[status-im.contexts.wallet.common.sheets.account-origin.view :as account-origin]
[status-im.contexts.wallet.common.utils :as utils]
[status-im.contexts.wallet.create-account.style :as style]
[status-im.feature-flags :as ff]
@ -20,19 +19,16 @@
[utils.security.core :as security]
[utils.string]))
(defn keypair-string
[full-name]
(let [first-name (utils/get-first-name full-name)]
(i18n/label :t/keypair-title {:name first-name})))
(defn get-keypair-data
[name derivation-path account-color]
[{:title (keypair-string name)
:image :avatar
:image-props {:full-name (utils.string/get-initials name 1)
:size :xxs
:customization-color account-color}
:action :button
(defn- get-keypair-data
[primary-name derivation-path account-color {:keys [keypair-name]}]
[{:title (or keypair-name (i18n/label :t/keypair-title {:name primary-name}))
:image (if keypair-name :icon :avatar)
:image-props (if keypair-name
:i/seed
{:full-name (utils.string/get-initials primary-name 1)
:size :xxs
:customization-color account-color})
:action (when-not keypair-name :button)
:action-props {:on-press #(ff/alert ::ff/wallet.edit-default-keypair
(fn []
(rf/dispatch [:navigate-to :wallet-select-keypair])))
@ -51,7 +47,7 @@
:description :text
:description-props {:text (string/replace derivation-path #"/" " / ")}}])
(defn- view-internal
(defn- f-view
[]
(let [top (safe-area/get-top)
bottom (safe-area/get-bottom)
@ -64,80 +60,86 @@
derivation-path (reagent/atom (utils/get-derivation-path number-of-accounts))
{:keys [public-key]} (rf/sub [:profile/profile])
on-change-text #(reset! account-name %)
[primary-name _] (first (rf/sub [:contacts/contact-two-names-by-identity public-key]))
primary-name (first (rf/sub [:contacts/contact-two-names-by-identity public-key]))
{window-width :width} (rn/get-window)]
(fn [{:keys [theme]}]
[rn/view {:style {:flex 1}}
[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])}]
[quo/gradient-cover
{:customization-color @account-color
:container-style (style/gradient-cover-container top)}]
[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
:auto-focus true
:default-value @account-name
:container-style style/title-input-container}]
[quo/divider-line]
[rn/view
{:style style/color-picker-container}
[quo/text
{:size :paragraph-2
:weight :medium
:style (style/color-label theme)}
(i18n/label :t/colour)]
[quo/color-picker
{:default-selected @account-color
:on-change #(reset! account-color %)
:container-style {:padding-vertical 12
:padding-left (iphone-11-Pro-20-pixel-from-width window-width)}}]]
[quo/divider-line]
[quo/category
{:list-type :settings
:label (i18n/label :t/origin)
:data (get-keypair-data primary-name @derivation-path @account-color)}]
[standard-auth/slide-button
{:size :size-48
:track-text (i18n/label :t/slide-to-create-account)
:customization-color @account-color
:on-auth-success (fn [entered-password]
(rf/dispatch [:wallet/derive-address-and-add-account
{:sha3-pwd (security/safe-unmask-data entered-password)
:emoji @emoji
:color @account-color
:path @derivation-path
:account-name @account-name}]))
:auth-button-label (i18n/label :t/confirm)
;; TODO (@rende11) Add this property when sliding button issue will fixed
;; https://github.com/status-im/status-mobile/pull/18683#issuecomment-1941564785
;; :disabled? (empty? @account-name)
:container-style (style/slide-button-container bottom)}]])))
(let [{:keys [new-keypair]} (rf/sub [:wallet/create-account])]
(rn/use-effect (fn [] #(rf/dispatch [:wallet/clear-new-keypair])))
[rn/view {:style {:flex 1}}
[quo/page-nav
{:type :no-title
:background :blur
:right-side [{:icon-name :i/info}]
:icon-name :i/close
:on-press #(rf/dispatch [:navigate-back])}]
[quo/gradient-cover
{:customization-color @account-color
:container-style (style/gradient-cover-container top)}]
[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}
[quo/text
{:size :paragraph-2
:weight :medium
:style (style/color-label theme)}
(i18n/label :t/colour)]
[quo/color-picker
{:default-selected @account-color
:on-change #(reset! account-color %)
:container-style {:padding-vertical 12
:padding-left (iphone-11-Pro-20-pixel-from-width window-width)}}]]
[quo/divider-line]
[quo/category
{:list-type :settings
:label (i18n/label :t/origin)
:data (get-keypair-data primary-name @derivation-path @account-color new-keypair)}]
[standard-auth/slide-button
{:size :size-48
:track-text (i18n/label :t/slide-to-create-account)
:customization-color @account-color
:on-auth-success (fn [entered-password]
(if new-keypair
(js/alert "Feature under development")
(rf/dispatch [:wallet/derive-address-and-add-account
{:sha3-pwd (security/safe-unmask-data
entered-password)
:emoji @emoji
:color @account-color
:path @derivation-path
:account-name @account-name}])))
:auth-button-label (i18n/label :t/confirm)
;; TODO (@rende11) Add this property when sliding button issue will fixed
;; https://github.com/status-im/status-mobile/pull/18683#issuecomment-1941564785
;; :disabled? (empty? @account-name)
:container-style (style/slide-button-container bottom)}]]))))
(defn- view-internal
[]
[:f> f-view])
(def view (quo.theme/with-theme view-internal))

View File

@ -1,7 +1,19 @@
(ns status-im.contexts.wallet.effects
(:require [re-frame.core :as rf]
[react-native.share :as share]))
(:require
[clojure.string :as string]
[native-module.core :as native-module]
[re-frame.core :as rf]
[react-native.share :as share]))
(rf/reg-fx :effects.share/open
(fn [content]
(share/open content)))
(rf/reg-fx
:effects.wallet/create-account-from-mnemonic
(fn [{:keys [secret-phrase keypair-name]}]
(native-module/create-account-from-mnemonic
{:MnemonicPhrase (string/join " " secret-phrase)}
(fn [new-keypair]
(rf/dispatch [:wallet/new-keypair-created
{:new-keypair (assoc new-keypair :keypair-name keypair-name)}])))))

View File

@ -386,3 +386,35 @@
{:title title
:subject title
:message content})]]}))
(defn store-secret-phrase
[{:keys [db]} [{:keys [secret-phrase random-phrase]}]]
{:db (-> db
(assoc-in [:wallet :ui :create-account :secret-phrase] secret-phrase)
(assoc-in [:wallet :ui :create-account :random-phrase] random-phrase))
:fx [[:dispatch-later [{:ms 20 :dispatch [:navigate-to :wallet-check-your-backup]}]]]})
(rf/reg-event-fx :wallet/store-secret-phrase store-secret-phrase)
(defn new-keypair-created
[{:keys [db]} [{:keys [new-keypair]}]]
{:db (assoc-in db [:wallet :ui :create-account :new-keypair] new-keypair)
:fx [[:dispatch [:navigate-back-to :wallet-create-account]]]})
(rf/reg-event-fx :wallet/new-keypair-created new-keypair-created)
(defn new-keypair-continue
[{:keys [db]} [{:keys [keypair-name]}]]
(let [secret-phrase (get-in db [:wallet :ui :create-account :secret-phrase])]
{:fx [[:effects.wallet/create-account-from-mnemonic
{:secret-phrase secret-phrase
:keypair-name keypair-name}]]}))
(rf/reg-event-fx :wallet/new-keypair-continue new-keypair-continue)
(defn clear-new-keypair
[{:keys [db]}]
{:db (update-in db [:wallet :ui :create-account] dissoc :new-keypair)})
(rf/reg-event-fx :wallet/clear-new-keypair clear-new-keypair)

View File

@ -24,6 +24,38 @@
result-db (:db effects)]
(is (match? result-db expected-db))))))
(deftest store-secret-phrase
(let [db {}
props [{:secret-phrase "test-secret" :random-phrase "random-test"}]
expected-db {:wallet {:ui {:create-account {:secret-phrase "test-secret"
:random-phrase "random-test"}}}}
effects (events/store-secret-phrase {:db db} props)
result-db (:db effects)]
(is (match? result-db expected-db))))
(deftest new-keypair-created
(let [db {}
props [{:new-keypair "test-keypair"}]
expected-db {:wallet {:ui {:create-account {:new-keypair "test-keypair"}}}}
effects (events/new-keypair-created {:db db} props)
result-db (:db effects)]
(is (match? result-db expected-db))))
(deftest new-keypair-continue
(let [db {:wallet {:ui {:create-account {:secret-phrase "test-secret"}}}}
props [{:keypair-name "test-keypair"}]
expected-effects [[:effects.wallet/create-account-from-mnemonic
{:secret-phrase "test-secret" :keypair-name "test-keypair"}]]
effects (events/new-keypair-continue {:db db} props)]
(is (match? effects {:fx expected-effects}))))
(deftest clear-new-keypair
(let [db {:wallet {:ui {:create-account {:new-keypair "test-keypair"}}}}
expected-db {:wallet {:ui {:create-account {}}}}
effects (events/clear-new-keypair {:db db})]
(is (match? (:db effects) expected-db))))
(deftest store-collectibles
(testing "(displayable-collectible?) helper function"
(let [expected-results [[true

View File

@ -63,6 +63,7 @@
wallet-backup-recovery-phrase]
[status-im.contexts.wallet.create-account.new-keypair.check-your-backup.view :as
wallet-check-your-backup]
[status-im.contexts.wallet.create-account.new-keypair.keypair-name.view :as wallet-keypair-name]
[status-im.contexts.wallet.create-account.select-keypair.view :as wallet-select-keypair]
[status-im.contexts.wallet.create-account.view :as wallet-create-account]
[status-im.contexts.wallet.edit-account.view :as wallet-edit-account]
@ -372,6 +373,10 @@
:options {:insets {:top? true :bottom? true}}
:component wallet-check-your-backup/view}
{:name :wallet-keypair-name
:options {:insets {:top? true :bottom? true}}
:component wallet-keypair-name/view}
{:name :wallet-share-address
:options options/transparent-screen-options
:component wallet-share-address/view}

View File

@ -38,6 +38,11 @@
:<- [:wallet/ui]
:-> :tokens-loading?)
(rf/reg-sub
:wallet/create-account
:<- [:wallet/ui]
:-> :create-account)
(rf/reg-sub
:wallet/current-viewing-account-address
:<- [:wallet]

View File

@ -3,7 +3,7 @@
"_comment": "Instead use: scripts/update-status-go.sh <rev>",
"owner": "status-im",
"repo": "status-go",
"version": "v0.174.8",
"commit-sha1": "8a3e71378f7208f75bd688c02b0ae5c43ca600f2",
"src-sha256": "10wn93xn6xnkg2d8slyygy9rfrwiargm49738bdjj1g4b81220bq"
"version": "v0.175.1",
"commit-sha1": "cba3ac570337404e71da0db424d658b6c29f7ad9",
"src-sha256": "186hpv6lcj4189aa67jvg62gij1z5q42j1qxijijvf3kqnvd4lmq"
}

View File

@ -2512,5 +2512,9 @@
"origin-desc": "Origin is where your key pair (your private and public key) comes from. You can generate a new key pair or import an existing private key.",
"derivation-path-header": "Derivation path",
"derivation-path-desc": "Derivation paths are the routes your Status Wallet uses to generate addresses from your private key.",
"select-networks": "Select networks"
"select-networks": "Select networks",
"generating-keypair": "Generating keypair...",
"keypair-name": "Keypair name",
"keypair-name-description": "Name keypair for your own personal reference",
"keypair-name-input-placeholder": "Collectibles account, Old vault...."
}