From a79a72fccb27911b0caf96534a52215ba162d92b Mon Sep 17 00:00:00 2001 From: Andrey Shovkoplyas Date: Fri, 28 Feb 2020 12:37:16 +0100 Subject: [PATCH 1/3] [#9749] Support importing private key and seed --- externs.js | 1 + .../status/ethereum/module/StatusModule.java | 17 + .../ios/RCTStatus/RCTStatus.m | 10 + src/status_im/hardwallet/wallet.cljs | 8 +- src/status_im/native_module/core.cljs | 8 +- src/status_im/subs.cljs | 22 +- .../ui/screens/routing/back_actions.cljs | 2 - src/status_im/ui/screens/routing/screens.cljs | 3 - .../ui/screens/routing/wallet_stack.cljs | 3 - .../wallet/account_settings/views.cljs | 42 +-- .../ui/screens/wallet/accounts/sheets.cljs | 62 ++-- .../ui/screens/wallet/accounts/views.cljs | 2 +- .../ui/screens/wallet/add_new/views.cljs | 225 ++++++------ src/status_im/wallet/accounts/core.cljs | 331 ++++++++++++------ translations/en.json | 9 +- 15 files changed, 436 insertions(+), 309 deletions(-) diff --git a/externs.js b/externs.js index 8a3f8b7c22b..d49c0b0f23f 100644 --- a/externs.js +++ b/externs.js @@ -620,5 +620,6 @@ var TopLevel = { "multiAccountLoadAccount" : function () {}, "multiAccountStoreAccount" : function () {}, "multiAccountImportMnemonic" : function () {}, + "multiAccountImportPrivateKey" : function () {}, "validateMnemonic" : function () {} } diff --git a/modules/react-native-status/android/src/main/java/im/status/ethereum/module/StatusModule.java b/modules/react-native-status/android/src/main/java/im/status/ethereum/module/StatusModule.java index 9b7d3d8e07b..78657ef9348 100644 --- a/modules/react-native-status/android/src/main/java/im/status/ethereum/module/StatusModule.java +++ b/modules/react-native-status/android/src/main/java/im/status/ethereum/module/StatusModule.java @@ -776,6 +776,23 @@ public void run() { StatusThreadPoolExecutor.getInstance().execute(r); } + @ReactMethod + public void multiAccountImportPrivateKey(final String json, final Callback callback) { + Log.d(TAG, "multiAccountImportPrivateKey"); + if (!checkAvailability()) { + callback.invoke(false); + return; + } + Runnable r = new Runnable() { + @Override + public void run() { + String res = Statusgo.multiAccountImportPrivateKey(json); + callback.invoke(res); + } + }; + StatusThreadPoolExecutor.getInstance().execute(r); + } + @ReactMethod public void hashTransaction(final String txArgsJSON, final Callback callback) { Log.d(TAG, "hashTransaction"); diff --git a/modules/react-native-status/ios/RCTStatus/RCTStatus.m b/modules/react-native-status/ios/RCTStatus/RCTStatus.m index 8071ce20c83..c17d4f74cf5 100644 --- a/modules/react-native-status/ios/RCTStatus/RCTStatus.m +++ b/modules/react-native-status/ios/RCTStatus/RCTStatus.m @@ -266,6 +266,16 @@ - (void)handleSignal:(NSString *)signal callback(@[result]); } +//////////////////////////////////////////////////////////////////// multiAccountImportPrivateKey +RCT_EXPORT_METHOD(multiAccountImportPrivateKey:(NSString *)json + callback:(RCTResponseSenderBlock)callback) { +#if DEBUG + NSLog(@"MultiAccountImportPrivateKey() method called"); +#endif + NSString *result = StatusgoMultiAccountImportPrivateKey(json); + callback(@[result]); +} + //////////////////////////////////////////////////////////////////// multiAccountImportMnemonic RCT_EXPORT_METHOD(multiAccountImportMnemonic:(NSString *)json callback:(RCTResponseSenderBlock)callback) { diff --git a/src/status_im/hardwallet/wallet.cljs b/src/status_im/hardwallet/wallet.cljs index 662f6d13255..3e729be837e 100644 --- a/src/status_im/hardwallet/wallet.cljs +++ b/src/status_im/hardwallet/wallet.cljs @@ -18,13 +18,11 @@ (if card-connected? (fx/merge cofx {:db (assoc-in db [:hardwallet :on-export-success] - #(vector :wallet.accounts/account-generated - {:name (str "Account " path-num) - ;; Strip leading 04 prefix denoting uncompressed key format + #(vector :wallet.accounts/account-stored + {;; Strip leading 04 prefix denoting uncompressed key format :address (eip55/address->checksum (str "0x" (ethereum/public-key->address (subs % 2)))) :public-key (str "0x" %) - :path path - :color (rand-nth colors/account-colors)})) + :path path})) :hardwallet/export-key {:pin pin :pairing pairing :path path}} (navigation/navigate-to-cofx :keycard-processing nil) (common/set-on-card-connected :wallet.accounts/generate-new-keycard-account)) diff --git a/src/status_im/native_module/core.cljs b/src/status_im/native_module/core.cljs index 87bb3dc5c8a..2f98845bb36 100644 --- a/src/status_im/native_module/core.cljs +++ b/src/status_im/native_module/core.cljs @@ -157,9 +157,15 @@ (types/clj->json {:mnemonicPhrase mnemonic ;;NOTE this is not the multiaccount password :Bip39Passphrase password}) - callback)) +(defn multiaccount-import-private-key + [private-key callback] + (log/debug "[native-module] multiaccount-import-private-key") + (.multiAccountImportPrivateKey (status) + (types/clj->json {:privateKey private-key}) + callback)) + (defn verify "NOTE: beware, the password has to be sha3 hashed" [address hashed-password callback] diff --git a/src/status_im/subs.cljs b/src/status_im/subs.cljs index 498b40f0306..78bec37afd4 100644 --- a/src/status_im/subs.cljs +++ b/src/status_im/subs.cljs @@ -538,15 +538,19 @@ :add-account-disabled? :<- [:multiaccount/accounts] :<- [:add-account] - (fn [[accounts {:keys [address]}]] - (or (not (ethereum/address? address)) - (some #(when (= (:address %) address) %) accounts)))) - -(re-frame/reg-sub - :add-account-scanned-address - :<- [:add-account] - (fn [add-account] - (get add-account :scanned-address))) + (fn [[accounts {:keys [address type account seed private-key]}]] + (or (string/blank? (:name account)) + (case type + :generate + false + :watch + (or (not (ethereum/address? address)) + (some #(when (= (:address %) address) %) accounts)) + :key + (string/blank? (security/safe-unmask-data private-key)) + :seed + (string/blank? (security/safe-unmask-data seed)) + false)))) ;;CHAT ============================================================================================================== diff --git a/src/status_im/ui/screens/routing/back_actions.cljs b/src/status_im/ui/screens/routing/back_actions.cljs index f8264e3e64d..7f2e00e981e 100644 --- a/src/status_im/ui/screens/routing/back_actions.cljs +++ b/src/status_im/ui/screens/routing/back_actions.cljs @@ -8,8 +8,6 @@ :new-public-chat :default :wallet-account :default :add-new-account :default - :add-watch-account :default - :add-new-account-password :default :add-new-account-pin :default :about-app :default :help-center :default diff --git a/src/status_im/ui/screens/routing/screens.cljs b/src/status_im/ui/screens/routing/screens.cljs index 9dadd52eaf8..e3128d43b2a 100644 --- a/src/status_im/ui/screens/routing/screens.cljs +++ b/src/status_im/ui/screens/routing/screens.cljs @@ -184,10 +184,7 @@ :welcome [:modal home/welcome] :keycard-welcome keycard/welcome :add-new-account add-account/add-account - :add-watch-account add-account/add-watch-account - :add-new-account-password add-account/password :add-new-account-pin add-account/pin - :account-added account-settings/account-added :account-settings account-settings/account-settings}) (defn get-screen [screen] diff --git a/src/status_im/ui/screens/routing/wallet_stack.cljs b/src/status_im/ui/screens/routing/wallet_stack.cljs index f746aa4bc54..edde9e5e852 100644 --- a/src/status_im/ui/screens/routing/wallet_stack.cljs +++ b/src/status_im/ui/screens/routing/wallet_stack.cljs @@ -6,10 +6,7 @@ :screens (cond-> [:wallet :wallet-account :add-new-account - :add-watch-account - :add-new-account-password :add-new-account-pin - :account-added :account-settings :collectibles-list :wallet-onboarding-setup diff --git a/src/status_im/ui/screens/wallet/account_settings/views.cljs b/src/status_im/ui/screens/wallet/account_settings/views.cljs index 318899522b7..1f0736ba4e7 100644 --- a/src/status_im/ui/screens/wallet/account_settings/views.cljs +++ b/src/status_im/ui/screens/wallet/account_settings/views.cljs @@ -6,8 +6,6 @@ [status-im.i18n :as i18n] [status-im.ui.components.icons.vector-icons :as icons] [status-im.ui.components.colors :as colors] - [status-im.ui.components.button :as button] - [clojure.string :as string] [status-im.ui.components.toolbar :as toolbar] [status-im.ui.components.copyable-text :as copyable-text] [reagent.core :as reagent] @@ -33,45 +31,6 @@ :label (i18n/label :t/cancel) :type :secondary}}]])) -(defview account-added [] - (letsubs [{:keys [account]} [:add-account]] - [react/keyboard-avoiding-view {:flex 1} - [react/scroll-view {:keyboard-should-persist-taps :handled - :style {:margin-top 70 :flex 1}} - [react/view {:align-items :center :padding-horizontal 40} - [react/view {:height 40 :width 40 :border-radius 20 :align-items :center :justify-content :center - :background-color (:color account)} - [icons/icon :main-icons/check {:color colors/white}]] - [react/text {:style {:typography :header :margin-top 16}} - (i18n/label :t/account-added)] - [react/text {:style {:color colors/gray :text-align :center :margin-top 16 :line-height 22}} - (i18n/label :t/you-can-change-account)]] - [react/view {:height 52}] - [react/view {:margin-horizontal 16} - [text-input/text-input-with-label - {:label (i18n/label :t/account-name) - :auto-focus false - :default-value (:name account) - :placeholder (i18n/label :t/account-name) - :on-change-text #(re-frame/dispatch [:set-in [:add-account :account :name] %])}] - [react/text {:style {:margin-top 30}} (i18n/label :t/account-color)] - [react/touchable-highlight - {:on-press #(re-frame/dispatch [:show-popover - {:view [colors-popover (:color account) - (fn [new-color] - (re-frame/dispatch [:set-in [:add-account :account :color] new-color]) - (re-frame/dispatch [:hide-popover]))] - :style {:max-height "60%"}}])} - [react/view {:height 52 :margin-top 12 :background-color (:color account) :border-radius 8 - :align-items :flex-end :justify-content :center :padding-right 12} - [icons/icon :main-icons/dropdown {:color colors/white}]]]]] - [toolbar/toolbar - {:show-border? true - :right {:type :next - :label (i18n/label :t/finish) - :on-press #(re-frame/dispatch [:wallet.accounts/save-generated-account]) - :disabled? (string/blank? (:name account))}}]])) - (defn property [label value] [react/view {:margin-top 28} [react/text {:style {:color colors/gray}} label] @@ -118,6 +77,7 @@ [property (i18n/label :t/type) (case type :watch (i18n/label :t/watch-only) + (:key :seed) (i18n/label :t/off-status-tree) (i18n/label :t/on-status-tree))] [property (i18n/label :t/wallet-address) [copyable-text/copyable-text-view diff --git a/src/status_im/ui/screens/wallet/accounts/sheets.cljs b/src/status_im/ui/screens/wallet/accounts/sheets.cljs index c7ca31efabc..130fa0a8bd8 100644 --- a/src/status_im/ui/screens/wallet/accounts/sheets.cljs +++ b/src/status_im/ui/screens/wallet/accounts/sheets.cljs @@ -15,25 +15,29 @@ :title :t/wallet-manage-assets :icon :main-icons/token :accessibility-label :wallet-manage-assets - :on-press #(hide-sheet-and-dispatch [:navigate-to :wallet-settings-assets])}] + :on-press #(hide-sheet-and-dispatch + [:navigate-to :wallet-settings-assets])}] [list-item/list-item {:theme :action :title :t/set-currency :icon :main-icons/language :accessibility-label :wallet-set-currency - :on-press #(hide-sheet-and-dispatch [:navigate-to :currency-settings])}] + :on-press #(hide-sheet-and-dispatch + [:navigate-to :currency-settings])}] [list-item/list-item {:theme :action :title :t/view-signing :icon :main-icons/info - :on-press #(hide-sheet-and-dispatch [:show-popover {:view :signing-phrase}])}] + :on-press #(hide-sheet-and-dispatch + [:show-popover {:view :signing-phrase}])}] (when mnemonic [list-item/list-item {:theme :action-destructive :title :t/wallet-backup-recovery-title :icon :main-icons/security :accessibility-label :wallet-backup-recovery-title - :on-press #(hide-sheet-and-dispatch [:navigate-to :backup-seed])}])])) + :on-press #(hide-sheet-and-dispatch + [:navigate-to :backup-seed])}])])) (defn send-receive [account type] [react/view @@ -43,7 +47,8 @@ :title :t/wallet-send :icon :main-icons/send :accessibility-label :send-transaction-button - :on-press #(hide-sheet-and-dispatch [:wallet/prepare-transaction-from-wallet account])}]) + :on-press #(hide-sheet-and-dispatch + [:wallet/prepare-transaction-from-wallet account])}]) [list-item/list-item {:theme :action :title :t/receive @@ -56,27 +61,40 @@ (defn add-account [] [react/view [list-item/list-item - {:theme :action - :title :t/add-an-account + {:title :t/generate-a-new-account + :theme :action :icon :main-icons/add - :on-press #(hide-sheet-and-dispatch [:navigate-to :add-new-account])}] + :on-press #(hide-sheet-and-dispatch + [:wallet.accounts/start-adding-new-account + {:type :generate}])}] + [list-item/list-item + {:theme :action + :title :t/add-a-watch-account + :icon :main-icons/watch + :on-press #(hide-sheet-and-dispatch + [:wallet.accounts/start-adding-new-account + {:type :watch}])}] [list-item/list-item - {:theme :action - :title :t/add-a-watch-account - :icon :main-icons/watch - :on-press #(hide-sheet-and-dispatch [:wallet.accounts/start-adding-new-account {:type :watch}])}]]) + {:title :t/enter-a-seed-phrase + :theme :action + :icon :main-icons/text + :on-press #(hide-sheet-and-dispatch + [:wallet.accounts/start-adding-new-account + {:type :seed}])}] + [list-item/list-item + {:title :t/enter-a-private-key + :theme :action + :icon :main-icons/address + :on-press #(hide-sheet-and-dispatch + [:wallet.accounts/start-adding-new-account + {:type :key}])}]]) (defn account-settings [] [react/view [list-item/list-item - {:theme :action - :title :t/account-settings + {:theme :action + :title :t/account-settings :accessibility-label :account-settings-bottom-sheet - :icon :main-icons/info - :on-press #(hide-sheet-and-dispatch [:navigate-to :account-settings])}] - ;; Commented out for v1 - #_[list-item/list-item - {:theme :action - :title :t/export-account - :icon :main-icons/copy - :disabled? true}]]) + :icon :main-icons/info + :on-press #(hide-sheet-and-dispatch + [:navigate-to :account-settings])}]]) \ No newline at end of file diff --git a/src/status_im/ui/screens/wallet/accounts/views.cljs b/src/status_im/ui/screens/wallet/accounts/views.cljs index c2f4c4e9d4c..2a3c21af279 100644 --- a/src/status_im/ui/screens/wallet/accounts/views.cljs +++ b/src/status_im/ui/screens/wallet/accounts/views.cljs @@ -45,7 +45,7 @@ (defn add-card [] [react/touchable-highlight {:on-press #(re-frame/dispatch [:bottom-sheet/show-sheet {:content sheets/add-account - :content-height 130}])} + :content-height 260}])} [react/view {:style styles/add-card} [react/view {:width 40 :height 40 :justify-content :center :border-radius 20 :align-items :center :background-color colors/blue-transparent-10 :margin-bottom 8} diff --git a/src/status_im/ui/screens/wallet/add_new/views.cljs b/src/status_im/ui/screens/wallet/add_new/views.cljs index 11edc42327d..910678c5050 100644 --- a/src/status_im/ui/screens/wallet/add_new/views.cljs +++ b/src/status_im/ui/screens/wallet/add_new/views.cljs @@ -5,62 +5,19 @@ [status-im.i18n :as i18n] [re-frame.core :as re-frame] [status-im.ui.components.colors :as colors] - [status-im.ui.components.list-item.views :as list-item] [reagent.core :as reagent] [cljs.spec.alpha :as spec] [status-im.multiaccounts.db :as multiaccounts.db] [status-im.ui.components.toolbar :as toolbar] - [status-im.ui.components.styles :as components.styles] [status-im.ui.components.topbar :as topbar] - [status-im.utils.utils :as utils.utils])) - -(defn add-account [] - [react/view {:flex 1} - [topbar/topbar] - [react/scroll-view {:keyboard-should-persist-taps :handled - :style {:flex 1}} - [react/view {:align-items :center :padding-horizontal 40 :margin-bottom 52} - [react/text {:style {:typography :header :margin-top 16}} - (i18n/label :t/add-an-account)] - [react/text {:style {:color colors/gray :text-align :center :margin-top 16 :line-height 22}} - (i18n/label :t/add-account-description)]] - [list-item/list-item - {:type :section-header - :title :t/default}] - [list-item/list-item - {:title :t/generate-a-new-account - :theme :action - :icon :main-icons/add - :accessories [:chevron] - :on-press #(re-frame/dispatch [:wallet.accounts/start-adding-new-account {:type :generate}])}] - ;;TODO: implement adding account by seedphrase and private key - #_[list-item/list-item - {:type :section-header - :container-margin-top 24 - :title (i18n/label :t/advanced)}] - #_[list-item/list-item - {:title (i18n/label :t/enter-a-seed-phrase) - :theme :action - :icon :main-icons/add - :accessories [:chevron] - :disabled? true - :on-press #(re-frame/dispatch [:wallet.accounts/start-adding-new-account {:type :seed}])}] - #_[list-item/list-item - {:title (i18n/label :t/enter-a-private-key) - :theme :action - :icon :main-icons/add - :accessories [:chevron] - :disabled? true - :on-press #(re-frame/dispatch [:wallet.accounts/start-adding-new-account {:type :key}])}]]]) - -(def input-container - {:flex-direction :row - :align-items :center - :border-radius components.styles/border-radius - :height 52 - :margin 16 - :padding-horizontal 16 - :background-color colors/gray-lighter}) + [status-im.utils.utils :as utils.utils] + [status-im.ui.components.text-input.view :as text-input] + [status-im.ui.components.icons.vector-icons :as icons] + [status-im.ui.screens.wallet.account-settings.views :as account-settings] + [status-im.ethereum.core :as ethereum] + [status-im.utils.security :as security] + [clojure.string :as string] + [status-im.utils.platform :as platform])) (defn- request-camera-permissions [] (let [options {:handler :wallet.add-new/qr-scanner-result}] @@ -76,37 +33,119 @@ (i18n/label :t/camera-access-error))) 50)}]))) -(defview add-watch-account [] - (letsubs [add-account-disabled? [:add-account-disabled?] - add-account-scanned-address [:add-account-scanned-address]] - [react/keyboard-avoiding-view {:flex 1} - [topbar/topbar {:accessories [{:icon :qr :handler #(request-camera-permissions)}]}] - [react/view {:flex 1 - :justify-content :space-between - :align-items :center :margin-horizontal 16} - [react/view - [react/text {:style {:typography :header :margin-top 16}} - (i18n/label :t/add-a-watch-account)] - [react/text {:style {:color colors/gray :text-align :center :margin-vertical 16}} - (i18n/label :t/enter-watch-account-address)]] - [react/view {:align-items :center :flex 1 :flex-direction :row} - [react/text-input {:auto-focus true - :multiline true - :text-align :center - :default-value add-account-scanned-address - :placeholder (i18n/label :t/enter-address) - :style {:typography :header :flex 1} - :on-change-text #(re-frame/dispatch [:set-in [:add-account :address] %])}]]] +(defn add-account-topbar [type] + (let [title (case type + :generate :t/generate-an-account + :watch :t/add-watch-account + :seed :t/add-seed-account + :key :t/add-private-key-account + "")] + [topbar/topbar + (merge {:title title} + (when (= type :watch) + {:accessories [{:icon :qr + :handler #(request-camera-permissions)}]}))])) + +(defn common-settings [account] + [react/view {:margin-horizontal 16 :margin-top 30} + [text-input/text-input-with-label + {:label (i18n/label :t/account-name) + :auto-focus false + :default-value (:name account) + :placeholder (i18n/label :t/account-name) + :on-change-text #(re-frame/dispatch [:set-in [:add-account :account :name] %])}] + [react/text {:style {:margin-top 30}} (i18n/label :t/account-color)] + [react/touchable-highlight + {:on-press #(re-frame/dispatch + [:show-popover + {:view [account-settings/colors-popover (:color account) + (fn [new-color] + (re-frame/dispatch [:set-in [:add-account :account :color] new-color]) + (re-frame/dispatch [:hide-popover]))] + :style {:max-height "60%"}}])} + [react/view {:height 52 :margin-top 12 :background-color (:color account) :border-radius 8 + :align-items :flex-end :justify-content :center :padding-right 12} + [icons/icon :main-icons/dropdown {:color colors/white}]]]]) + +(defn settings [{:keys [type scanned-address password-error account-error]} + entered-password] + [react/view {:margin-horizontal 16} + (if (= type :watch) + [text-input/text-input-with-label + {:label (i18n/label :t/wallet-key-title) + :auto-focus false + :default-value scanned-address + :placeholder (i18n/label :t/enter-address) + :on-change-text #(re-frame/dispatch [:set-in [:add-account :address] %])}] + [text-input/text-input-with-label + {:label (i18n/label :t/password) + :parent-container {:margin-top 30} + :auto-focus false + :placeholder (i18n/label :t/enter-your-password) + :secure-text-entry true + :text-content-type :none + :error (when password-error (i18n/label :t/add-account-incorrect-password)) + :on-change-text #(do + (re-frame/dispatch [:set-in [:add-account :password-error] nil]) + (reset! entered-password %))}]) + (when (= type :seed) + [text-input/text-input-with-label + {:parent-container {:margin-top 30} + :label (i18n/label :t/recovery-phrase) + :auto-focus false + :placeholder (i18n/label :t/multiaccounts-recover-enter-phrase-title) + :auto-correct false + :keyboard-type "visible-password" + :multiline true + :style (when platform/android? + {:flex 1}) + :height 95 + :error account-error + :on-change-text + #(do + (re-frame/dispatch [:set-in [:add-account :account-error] nil]) + (re-frame/dispatch [:set-in [:add-account :seed] (security/mask-data (string/lower-case %))]))}]) + (when (= type :key) + [text-input/text-input-with-label + {:parent-container {:margin-top 30} + :label (i18n/label :t/private-key) + :auto-focus false + :placeholder (i18n/label :t/enter-a-private-key) + :auto-correct false + :keyboard-type "visible-password" + :error account-error + :secure-text-entry true + :text-content-type :none + :on-change-text + #(do + (re-frame/dispatch [:set-in [:add-account :account-error] nil]) + (re-frame/dispatch [:set-in [:add-account :private-key] (security/mask-data %)]))}])]) + +(defview add-account [] + (letsubs [{:keys [type account] :as add-account} [:add-account] + add-account-disabled? [:add-account-disabled?] + entered-password (reagent/atom "")] + [react/keyboard-avoiding-view {:style {:flex 1}} + [add-account-topbar type] + [react/scroll-view {:keyboard-should-persist-taps :handled + :style {:flex 1}} + [settings add-account entered-password] + [common-settings account]] [toolbar/toolbar {:show-border? true - :right {:type :next - :label (i18n/label :t/next) - :on-press #(re-frame/dispatch [:wallet.accounts/add-watch-account]) - :disabled? add-account-disabled?}}]])) + :right + {:type :next + :label :t/add-account + :on-press #(re-frame/dispatch [:wallet.accounts/add-new-account + (ethereum/sha3 @entered-password)]) + :disabled? (or add-account-disabled? + (and + (not (= type :watch)) + (not (spec/valid? ::multiaccounts.db/password @entered-password))))}}]])) (defview pin [] - (letsubs [pin [:hardwallet/pin] - status [:hardwallet/pin-status] + (letsubs [pin [:hardwallet/pin] + status [:hardwallet/pin-status] error-label [:hardwallet/pin-error-label]] [react/keyboard-avoiding-view {:style {:flex 1}} [topbar/topbar] @@ -116,32 +155,4 @@ :title-label :t/current-pin :description-label :t/current-pin-description :error-label error-label - :step :export-key}]])) - -(defview password [] - (letsubs [{:keys [error]} [:add-account] - entered-password (reagent/atom "")] - [react/keyboard-avoiding-view {:style {:flex 1}} - [topbar/topbar] - [react/view {:flex 1 - :justify-content :space-between - :align-items :center :margin-horizontal 16} - [react/text {:style {:typography :header :margin-top 16}} (i18n/label :t/enter-your-password)] - [react/view {:justify-content :center :flex 1} - [react/text-input {:secure-text-entry true - :auto-focus true - :auto-capitalize :none - :text-align :center - :placeholder "" - :style {:typography :header} - :on-change-text #(reset! entered-password %)}] - (when error - [react/text {:style {:text-align :center :color colors/red :margin-top 76}} error])] - [react/text {:style {:color colors/gray :text-align :center :margin-bottom 16}} - (i18n/label :t/to-encrypt-enter-password)]] - [toolbar/toolbar - {:show-border? true - :right {:type :next - :label :t/generate-account - :on-press #(re-frame/dispatch [:wallet.accounts/generate-new-account @entered-password]) - :disabled? (not (spec/valid? ::multiaccounts.db/password @entered-password))}}]])) + :step :export-key}]])) \ No newline at end of file diff --git a/src/status_im/wallet/accounts/core.cljs b/src/status_im/wallet/accounts/core.cljs index 0258808993e..b44bc7988f7 100644 --- a/src/status_im/wallet/accounts/core.cljs +++ b/src/status_im/wallet/accounts/core.cljs @@ -13,78 +13,228 @@ [status-im.ui.screens.navigation :as navigation] [status-im.utils.fx :as fx] [status-im.utils.types :as types] - [status-im.wallet.core :as wallet])) + [status-im.wallet.core :as wallet] + [clojure.string :as string] + [status-im.utils.security :as security] + [status-im.multiaccounts.recover.core :as recover] + [status-im.ethereum.mnemonic :as mnemonic])) + +(fx/defn start-adding-new-account + {:events [:wallet.accounts/start-adding-new-account]} + [{:keys [db] :as cofx} {:keys [type] :as add-account}] + (let [{:keys [latest-derived-path]} (:multiaccount db) + path-num (inc latest-derived-path) + account (merge + {:color (rand-nth colors/account-colors)} + (when (= type :generate) + {:name (str "Account " path-num)}))] + (fx/merge cofx + {:db (assoc db :add-account (assoc add-account :account account))} + (navigation/navigate-to-cofx :add-new-account nil)))) + +(fx/defn new-account-error + {:events [::new-account-error]} + [{:keys [db]} error-key error] + {:db (update db :add-account merge {error-key error + :step nil})}) + +(defn account-stored [path type] + (fn [result] + (let [{:keys [error publicKey address]} (types/json->clj result)] + (if error + (re-frame/dispatch [::new-account-error :account-error error]) + (re-frame/dispatch [:wallet.accounts/account-stored + {:address address + :public-key publicKey + :type type + :path path}]))))) + +(def dec-pass-error "could not decrypt key with given password") + +(defn normalize-path [path] + (if (string/starts-with? path "m/") + (str constants/path-wallet-root + "/" (last (string/split path "/"))) + path)) + +(defn derive-and-store-account [path hashed-password type] + (fn [value] + (let [{:keys [id error]} (types/json->clj value)] + (if error + (re-frame/dispatch [::new-account-error :password-error error]) + (status/multiaccount-derive-addresses + id + [path] + (fn [_] + (status/multiaccount-store-derived + id + [path] + hashed-password + (fn [result] + (let [{:keys [error] :as result} (types/json->clj result) + {:keys [publicKey address]} (get result (keyword path))] + (if error + (re-frame/dispatch [::new-account-error :account-error error]) + (re-frame/dispatch + [:wallet.accounts/account-stored + {:address address + :public-key publicKey + :type type + :path (normalize-path path)}]))))))))))) + +(def pass-error "cannot retrieve a valid key for a given account: could not decrypt key with given password") + +(defn store-account [path hashed-password type] + (fn [value] + (let [{:keys [id error]} (types/json->clj value)] + (if error + (re-frame/dispatch [::new-account-error + (if (= error pass-error) :password-error :account-error) + error]) + (status/multiaccount-store-account + id + hashed-password + (account-stored path type)))))) (re-frame/reg-fx - :list.selection/open-share - (fn [obj] - (list-selection/open-share obj))) + ::verify-password + (fn [{:keys [address hashed-password]}] + (status/verify + address hashed-password + #(re-frame/dispatch [:wallet.accounts/add-new-account-password-verifyied % hashed-password])))) (re-frame/reg-fx ::generate-account - (fn [{:keys [derivation-info hashed-password path-num]}] + (fn [{:keys [derivation-info hashed-password]}] (let [{:keys [address path]} derivation-info] (status/multiaccount-load-account address hashed-password - (fn [value] - (let [{:keys [id error]} (types/json->clj value)] - (if error - (re-frame/dispatch [::generate-new-account-error]) - (status/multiaccount-derive-addresses - id - [path] - (fn [result] - (status/multiaccount-store-derived - id - [path] - hashed-password - (fn [result] - (let [{:keys [publicKey address]} - (get (types/json->clj result) (keyword path))] - (re-frame/dispatch [:wallet.accounts/account-generated - {:name (str "Account " path-num) - :address address - :public-key publicKey - :path (str constants/path-wallet-root "/" path-num) - :color (rand-nth colors/account-colors)}]))))))))))))) - -(fx/defn set-symbol-request - {:events [:wallet.accounts/share]} - [_ address] - {:list.selection/open-share {:message (eip55/address->checksum address)}}) + (derive-and-store-account path hashed-password :generated))))) + +(re-frame/reg-fx + ::import-account-seed + (fn [{:keys [passphrase hashed-password]}] + (status/multiaccount-import-mnemonic + (mnemonic/sanitize-passphrase (security/unmask passphrase)) + "" + (derive-and-store-account constants/path-default-wallet hashed-password :seed)))) + +(re-frame/reg-fx + ::import-account-private-key + (fn [{:keys [private-key hashed-password]}] + (status/multiaccount-import-private-key + (string/trim (security/unmask private-key)) + (store-account constants/path-default-wallet hashed-password :key)))) (fx/defn generate-new-account - {:events [:wallet.accounts/generate-new-account]} - [{:keys [db]} password] + [{:keys [db]} hashed-password] (let [wallet-root-address (get-in db [:multiaccount :wallet-root-address]) path-num (inc (get-in db [:multiaccount :latest-derived-path]))] - (when-not (get-in db [:add-account :step]) - {:db (assoc-in db [:add-account :step] :generating) - ::generate-account {:derivation-info (if wallet-root-address - ;; Use the walllet-root-address for stored on disk keys - ;; This needs to be the RELATIVE path to the key used to derive - {:path (str "m/" path-num) - :address wallet-root-address} - ;; Fallback on the master account for keycards, use the absolute path - {:path (str constants/path-wallet-root "/" path-num) - :address (get-in db [:multiaccount :address])}) - :path-num path-num - :hashed-password (ethereum/sha3 password)}}))) - -(fx/defn generate-new-account-error - {:events [::generate-new-account-error]} - [{:keys [db]} password] - {:db (assoc db - :add-account - {:error (i18n/label :t/add-account-incorrect-password)})}) + {:db (assoc-in db [:add-account :step] :generating) + ::generate-account {:derivation-info (if wallet-root-address + ;; Use the walllet-root-address for stored on disk keys + ;; This needs to be the RELATIVE path to the key used to derive + {:path (str "m/" path-num) + :address wallet-root-address} + ;; Fallback on the master account for keycards, use the absolute path + {:path (str constants/path-wallet-root "/" path-num) + :address (get-in db [:multiaccount :address])}) + :hashed-password hashed-password}})) + +(fx/defn import-new-account-seed + [{:keys [db]} passphrase hashed-password] + {:db (assoc-in db [:add-account :step] :generating) + ::recover/validate-mnemonic [(security/safe-unmask-data passphrase) + #(re-frame/dispatch [:wallet.accounts/seed-validated + % passphrase hashed-password])]}) + +(fx/defn new-account-seed-validated + {:events [:wallet.accounts/seed-validated]} + [cofx phrase-warnings passphrase hashed-password] + (let [error (:error (types/json->clj phrase-warnings))] + (if-not (string/blank? error) + (new-account-error cofx :account-error error) + {::import-account-seed {:passphrase passphrase + :hashed-password hashed-password}}))) + +(fx/defn import-new-account-private-key + [{:keys [db]} private-key hashed-password] + {:db (assoc-in db [:add-account :step] :generating) + ::import-account-private-key {:private-key private-key + :hashed-password hashed-password}}) + +(fx/defn save-new-account + [{:keys [db] :as cofx}] + (let [{:keys [latest-derived-path]} (:multiaccount db) + {:keys [account type]} (:add-account db) + accounts (:multiaccount/accounts db) + new-accounts (conj accounts account)] + (when account + (fx/merge cofx + {::json-rpc/call [{:method "accounts_saveAccounts" + :params [[account]] + :on-success #()}] + :db (-> db + (assoc :multiaccount/accounts new-accounts) + (dissoc :add-account))} + (when (= type :generate) + (multiaccounts.update/multiaccount-update + :latest-derived-path (inc latest-derived-path) + {})))))) (fx/defn account-generated - {:events [:wallet.accounts/account-generated]} - [{:keys [db] :as cofx} account] - (fx/merge cofx - {:db (update db :add-account assoc :account account :step :generated)} - (navigation/navigate-to-cofx :account-added nil))) + {:events [:wallet.accounts/account-stored]} + [{:keys [db] :as cofx} {:keys [address] :as account}] + (let [accounts (:multiaccount/accounts db)] + (if (some #(when (= (:address %) address) %) accounts) + (new-account-error cofx :account-error (i18n/label :t/account-exists-title)) + (fx/merge cofx + {:db (update-in db [:add-account :account] merge account)} + (save-new-account) + (wallet/update-balances nil) + (wallet/update-prices) + (navigation/navigate-back))))) + +(fx/defn add-watch-account + [{:keys [db] :as cofx}] + (let [address (get-in db [:add-account :address])] + (account-generated cofx {:address (eip55/address->checksum (ethereum/normalized-hex address)) + :type :watch}))) + +(fx/defn add-new-account-password-verifyied + {:events [:wallet.accounts/add-new-account-password-verifyied]} + [{:keys [db] :as cofx} result hashed-password] + (let [{:keys [error]} (types/json->clj result)] + (if (not (string/blank? error)) + (new-account-error cofx :password-error error) + (let [{:keys [type step seed private-key]} (:add-account db)] + (case type + :seed + (import-new-account-seed cofx seed hashed-password) + :key + (import-new-account-private-key cofx private-key hashed-password) + nil))))) + +(fx/defn add-new-account-verify-password + [{:keys [db]} hashed-password] + {:db (assoc-in db [:add-account :step] :generating) + ::verify-password {:address (get-in db [:multiaccount :wallet-root-address]) + :hashed-password hashed-password}}) + +(fx/defn add-new-account + {:events [:wallet.accounts/add-new-account]} + [{:keys [db] :as cofx} hashed-password] + (let [{:keys [type step]} (:add-account db)] + (when-not step + (case type + :watch + (add-watch-account cofx) + :generate + (generate-new-account cofx hashed-password) + (:seed :key) + (add-new-account-verify-password cofx hashed-password) + nil)))) (fx/defn save-account {:events [:wallet.accounts/save-account]} @@ -114,63 +264,6 @@ (assoc-in [:wallet :accounts deleted-address] nil))} (navigation/navigate-to-cofx :wallet nil)))) -(fx/defn save-generated-account - {:events [:wallet.accounts/save-generated-account]} - [{:keys [db] :as cofx}] - (let [{:keys [latest-derived-path]} (:multiaccount db) - {:keys [account path type]} (:add-account db) - accounts (:multiaccount/accounts db) - new-accounts (conj accounts account)] - (when account - (fx/merge cofx - {::json-rpc/call [{:method "accounts_saveAccounts" - :params [[account]] - :on-success #()}] - :db (-> db - (assoc :multiaccount/accounts new-accounts) - (dissoc :add-account))} - (when (= type :generate) - (multiaccounts.update/multiaccount-update - :latest-derived-path (inc latest-derived-path) - {})) - (wallet/update-balances nil) - (navigation/navigate-to-cofx :wallet nil))))) - -(fx/defn start-adding-new-account - {:events [:wallet.accounts/start-adding-new-account]} - [{:keys [db] :as cofx} {:keys [type] :as add-account}] - (let [{:keys [keycard-pairing]} (:multiaccount db) - screen (case type - :generate (if keycard-pairing :add-new-account-pin - :add-new-account-password) - :watch :add-watch-account)] - (fx/merge cofx - {:db (cond-> (assoc db :add-account add-account) - keycard-pairing - (assoc-in [:hardwallet :pin :enter-step] :export-key))} - (navigation/navigate-to-cofx screen nil)))) - -(fx/defn enter-phrase-next-pressed - {:events [:wallet.accounts/enter-phrase-next-pressed]} - [{:keys [db] :as cofx}] - (fx/merge cofx - {:db (-> db - (dissoc :intro-wizard) - (assoc-in [:add-account :seed] (get-in db [:intro-wizard :passphrase])))} - (navigation/navigate-to-cofx :add-new-account-password nil))) - -(fx/defn add-watch-account - {:events [:wallet.accounts/add-watch-account]} - [{:keys [db] :as cofx}] - (let [address (get-in db [:add-account :address])] - (fx/merge cofx - {:db (assoc-in db [:add-account :account] - {:name "" - :address (eip55/address->checksum (ethereum/normalized-hex address)) - :type :watch - :color (rand-nth colors/account-colors)})} - (navigation/navigate-to-cofx :account-added nil)))) - (fx/defn view-only-qr-scanner-result {:events [:wallet.add-new/qr-scanner-result]} [{db :db :as cofx} data _] @@ -184,3 +277,13 @@ {:utils/show-popup {:title (i18n/label :t/error) :content (i18n/label :t/invalid-address-qr-code)}})) (navigation/navigate-back)))) + +(re-frame/reg-fx + :list.selection/open-share + (fn [obj] + (list-selection/open-share obj))) + +(fx/defn wallet-accounts-share + {:events [:wallet.accounts/share]} + [_ address] + {:list.selection/open-share {:message (eip55/address->checksum address)}}) \ No newline at end of file diff --git a/translations/en.json b/translations/en.json index 7a2e60d5881..a225efe084f 100644 --- a/translations/en.json +++ b/translations/en.json @@ -443,7 +443,7 @@ "gas-price": "Gas price", "gas-used": "Gas used", "generate-a-key": "Generate keys", - "generate-a-new-account": "Generate keys", + "generate-a-new-account": "Generate an account", "generate-a-new-key": "Generate a new key", "generate-account": "Generate keys", "generate-new-key": "Generate keys", @@ -1079,6 +1079,7 @@ "select-account-dapp": "Select the account you wish to use with Dapps", "apply": "Apply", "on-status-tree": "On Status tree", + "off-status-tree": "Off Status tree", "derivation-path": "Derivation path", "storage": "Storage", "keycard-free-pairing-slots": "Keycard has {{n}} free pairing slots", @@ -1089,5 +1090,11 @@ "mail-should-be-configured": "Mail client should be configured", "check-on-etherscan": "Check on etherscan", "transactions-load-more": "Load more", + "private-key": "Private key", + "generate-an-account": "Generate an account", + "add-watch-account": "Add a watch-only account", + "add-seed-account": "Add account with a seed phrase", + "account-exists-title": "Account already exists", + "add-private-key-account": "Add account from private key", "user-not-found": "User not found" } From f645d3fbb5974f0d20d43034407feb112767239c Mon Sep 17 00:00:00 2001 From: Andrey Shovkoplyas Date: Mon, 2 Mar 2020 12:31:23 +0100 Subject: [PATCH 2/3] accessibility-labels --- .../ui/screens/wallet/accounts/sheets.cljs | 52 ++++----- .../ui/screens/wallet/add_new/views.cljs | 102 +++++++++--------- 2 files changed, 82 insertions(+), 72 deletions(-) diff --git a/src/status_im/ui/screens/wallet/accounts/sheets.cljs b/src/status_im/ui/screens/wallet/accounts/sheets.cljs index 130fa0a8bd8..baf50ba83be 100644 --- a/src/status_im/ui/screens/wallet/accounts/sheets.cljs +++ b/src/status_im/ui/screens/wallet/accounts/sheets.cljs @@ -61,33 +61,37 @@ (defn add-account [] [react/view [list-item/list-item - {:title :t/generate-a-new-account - :theme :action - :icon :main-icons/add - :on-press #(hide-sheet-and-dispatch - [:wallet.accounts/start-adding-new-account - {:type :generate}])}] + {:title :t/generate-a-new-account + :theme :action + :icon :main-icons/add + :accessibility-label :add-account-sheet-generate + :on-press #(hide-sheet-and-dispatch + [:wallet.accounts/start-adding-new-account + {:type :generate}])}] [list-item/list-item - {:theme :action - :title :t/add-a-watch-account - :icon :main-icons/watch - :on-press #(hide-sheet-and-dispatch - [:wallet.accounts/start-adding-new-account - {:type :watch}])}] + {:theme :action + :title :t/add-a-watch-account + :icon :main-icons/watch + :accessibility-label :add-account-sheet-watch + :on-press #(hide-sheet-and-dispatch + [:wallet.accounts/start-adding-new-account + {:type :watch}])}] [list-item/list-item - {:title :t/enter-a-seed-phrase - :theme :action - :icon :main-icons/text - :on-press #(hide-sheet-and-dispatch - [:wallet.accounts/start-adding-new-account - {:type :seed}])}] + {:title :t/enter-a-seed-phrase + :theme :action + :icon :main-icons/text + :accessibility-label :add-account-sheet-seed + :on-press #(hide-sheet-and-dispatch + [:wallet.accounts/start-adding-new-account + {:type :seed}])}] [list-item/list-item - {:title :t/enter-a-private-key - :theme :action - :icon :main-icons/address - :on-press #(hide-sheet-and-dispatch - [:wallet.accounts/start-adding-new-account - {:type :key}])}]]) + {:title :t/enter-a-private-key + :theme :action + :icon :main-icons/address + :accessibility-label :add-account-sheet-private-key + :on-press #(hide-sheet-and-dispatch + [:wallet.accounts/start-adding-new-account + {:type :key}])}]]) (defn account-settings [] [react/view diff --git a/src/status_im/ui/screens/wallet/add_new/views.cljs b/src/status_im/ui/screens/wallet/add_new/views.cljs index 910678c5050..9295b1bee08 100644 --- a/src/status_im/ui/screens/wallet/add_new/views.cljs +++ b/src/status_im/ui/screens/wallet/add_new/views.cljs @@ -49,11 +49,12 @@ (defn common-settings [account] [react/view {:margin-horizontal 16 :margin-top 30} [text-input/text-input-with-label - {:label (i18n/label :t/account-name) - :auto-focus false - :default-value (:name account) - :placeholder (i18n/label :t/account-name) - :on-change-text #(re-frame/dispatch [:set-in [:add-account :account :name] %])}] + {:label (i18n/label :t/account-name) + :auto-focus false + :default-value (:name account) + :accessibility-label :add-account-enter-account-name + :placeholder (i18n/label :t/account-name) + :on-change-text #(re-frame/dispatch [:set-in [:add-account :account :name] %])}] [react/text {:style {:margin-top 30}} (i18n/label :t/account-color)] [react/touchable-highlight {:on-press #(re-frame/dispatch @@ -72,50 +73,54 @@ [react/view {:margin-horizontal 16} (if (= type :watch) [text-input/text-input-with-label - {:label (i18n/label :t/wallet-key-title) - :auto-focus false - :default-value scanned-address - :placeholder (i18n/label :t/enter-address) - :on-change-text #(re-frame/dispatch [:set-in [:add-account :address] %])}] + {:label (i18n/label :t/wallet-key-title) + :auto-focus false + :default-value scanned-address + :placeholder (i18n/label :t/enter-address) + :accessibility-label :add-account-enter-watch-address + :on-change-text #(re-frame/dispatch [:set-in [:add-account :address] %])}] [text-input/text-input-with-label - {:label (i18n/label :t/password) - :parent-container {:margin-top 30} - :auto-focus false - :placeholder (i18n/label :t/enter-your-password) - :secure-text-entry true - :text-content-type :none - :error (when password-error (i18n/label :t/add-account-incorrect-password)) - :on-change-text #(do - (re-frame/dispatch [:set-in [:add-account :password-error] nil]) - (reset! entered-password %))}]) + {:label (i18n/label :t/password) + :parent-container {:margin-top 30} + :auto-focus false + :placeholder (i18n/label :t/enter-your-password) + :secure-text-entry true + :text-content-type :none + :accessibility-label :add-account-enter-password + :error (when password-error (i18n/label :t/add-account-incorrect-password)) + :on-change-text #(do + (re-frame/dispatch [:set-in [:add-account :password-error] nil]) + (reset! entered-password %))}]) (when (= type :seed) [text-input/text-input-with-label - {:parent-container {:margin-top 30} - :label (i18n/label :t/recovery-phrase) - :auto-focus false - :placeholder (i18n/label :t/multiaccounts-recover-enter-phrase-title) - :auto-correct false - :keyboard-type "visible-password" - :multiline true - :style (when platform/android? - {:flex 1}) - :height 95 - :error account-error + {:parent-container {:margin-top 30} + :label (i18n/label :t/recovery-phrase) + :auto-focus false + :placeholder (i18n/label :t/multiaccounts-recover-enter-phrase-title) + :auto-correct false + :keyboard-type "visible-password" + :multiline true + :style (when platform/android? + {:flex 1}) + :height 95 + :error account-error + :accessibility-label :add-account-enter-seed :on-change-text #(do (re-frame/dispatch [:set-in [:add-account :account-error] nil]) (re-frame/dispatch [:set-in [:add-account :seed] (security/mask-data (string/lower-case %))]))}]) (when (= type :key) [text-input/text-input-with-label - {:parent-container {:margin-top 30} - :label (i18n/label :t/private-key) - :auto-focus false - :placeholder (i18n/label :t/enter-a-private-key) - :auto-correct false - :keyboard-type "visible-password" - :error account-error - :secure-text-entry true - :text-content-type :none + {:parent-container {:margin-top 30} + :label (i18n/label :t/private-key) + :auto-focus false + :placeholder (i18n/label :t/enter-a-private-key) + :auto-correct false + :keyboard-type "visible-password" + :error account-error + :secure-text-entry true + :accessibility-label :add-account-enter-private-key + :text-content-type :none :on-change-text #(do (re-frame/dispatch [:set-in [:add-account :account-error] nil]) @@ -134,14 +139,15 @@ [toolbar/toolbar {:show-border? true :right - {:type :next - :label :t/add-account - :on-press #(re-frame/dispatch [:wallet.accounts/add-new-account - (ethereum/sha3 @entered-password)]) - :disabled? (or add-account-disabled? - (and - (not (= type :watch)) - (not (spec/valid? ::multiaccounts.db/password @entered-password))))}}]])) + {:type :next + :label :t/add-account + :accessibility-label :add-account-add-account-button + :on-press #(re-frame/dispatch [:wallet.accounts/add-new-account + (ethereum/sha3 @entered-password)]) + :disabled? (or add-account-disabled? + (and + (not (= type :watch)) + (not (spec/valid? ::multiaccounts.db/password @entered-password))))}}]])) (defview pin [] (letsubs [pin [:hardwallet/pin] From 7b7f567361b2ef63a2a181810de632814d39c382 Mon Sep 17 00:00:00 2001 From: Churikova Tetiana Date: Mon, 2 Mar 2020 17:07:46 +0100 Subject: [PATCH 3/3] e2e for importing accounts Signed-off-by: Andrey Shovkoplyas --- .../atomic/account_management/test_profile.py | 1 + .../test_wallet_management.py | 108 +++++++++++++++--- test/appium/tests/users.py | 3 +- test/appium/views/wallet_view.py | 70 +++++++----- 4 files changed, 135 insertions(+), 47 deletions(-) diff --git a/test/appium/tests/atomic/account_management/test_profile.py b/test/appium/tests/atomic/account_management/test_profile.py index 0b8b2b20724..4f528906181 100644 --- a/test/appium/tests/atomic/account_management/test_profile.py +++ b/test/appium/tests/atomic/account_management/test_profile.py @@ -78,6 +78,7 @@ def test_mobile_data_usage_popup_stop_syncing(self): sign_in_view.just_fyi("Start syncing in offline popup") sign_in_view.element_by_text("Start syncing").click() + sign_in_view.element_by_text_part(offline_banner_text).wait_for_invisibility_of_element(10) if sign_in_view.element_by_text_part(offline_banner_text).is_element_displayed(): self.driver.fail("Popup about offline history is shown") diff --git a/test/appium/tests/atomic/account_management/test_wallet_management.py b/test/appium/tests/atomic/account_management/test_wallet_management.py index 022e3a9febd..134917c497e 100644 --- a/test/appium/tests/atomic/account_management/test_wallet_management.py +++ b/test/appium/tests/atomic/account_management/test_wallet_management.py @@ -278,28 +278,27 @@ def test_add_custom_token(self): @marks.testrail_id(6224) @marks.critical - def test_add_account_to_multiaccount_instance(self): + def test_add_account_to_multiaccount_instance_generate_new(self): sign_in_view = SignInView(self.driver) sign_in_view.create_user() wallet_view = sign_in_view.wallet_button.click() wallet_view.set_up_wallet() wallet_view.add_account_button.click() - wallet_view.add_an_account_button.click() - wallet_view.generate_new_account_button.click() - wallet_view.generate_account_button.click() - if wallet_view.element_by_text('Account added').is_element_displayed(): + wallet_view.generate_an_account_button.click() + wallet_view.add_account_generate_account_button.click() + account_name = ''.join(random.choice(string.ascii_uppercase + string.digits) for _ in range(10)) + wallet_view.account_name_input.send_keys(account_name) + wallet_view.account_color_button.select_color_by_position(1) + if wallet_view.get_account_options_by_name(account_name).is_element_displayed(): self.driver.fail('Account is added without password') wallet_view.enter_your_password_input.send_keys('000000') - wallet_view.generate_account_button.click() - if not wallet_view.element_by_text_part('Password seems to be incorrect').is_element_displayed(): - self.driver.fail("Incorrect password validation is not performed") + wallet_view.add_account_generate_account_button.click() + # TODO: blocked due to #8567 + # if not wallet_view.element_by_text_part('Password seems to be incorrect').is_element_displayed(): + # self.driver.fail("Incorrect password validation is not performed") wallet_view.enter_your_password_input.clear() wallet_view.enter_your_password_input.send_keys(common_password) - wallet_view.generate_account_button.click() - account_name = ''.join(random.choice(string.ascii_uppercase + string.digits) for _ in range(10)) - wallet_view.account_name_input.send_keys(account_name) - wallet_view.account_color_button.select_color_by_position(1) - wallet_view.finish_button.click() + wallet_view.add_account_generate_account_button.click() account_button = wallet_view.get_account_by_name(account_name) if not account_button.is_element_displayed(): self.driver.fail('Account was not added') @@ -318,10 +317,9 @@ def test_add_and_delete_watch_only_account_to_multiaccount_instance(self): wallet_view.add_account_button.click() wallet_view.add_watch_only_address_button.click() wallet_view.enter_address_input.send_keys(basic_user['address']) - wallet_view.next_button.click() account_name = ''.join(random.choice(string.ascii_uppercase + string.digits) for _ in range(10)) wallet_view.account_name_input.send_keys(account_name) - wallet_view.finish_button.click() + wallet_view.add_account_generate_account_button.click() account_button = wallet_view.get_account_by_name(account_name) if not account_button.is_element_displayed(): self.driver.fail('Account was not added') @@ -357,6 +355,86 @@ def test_add_and_delete_watch_only_account_to_multiaccount_instance(self): self.errors.verify_no_errors() + @marks.testrail_id(6271) + @marks.high + def test_add_account_to_multiaccount_instance_seed_phrase(self): + sign_in_view = SignInView(self.driver) + sign_in_view.create_user() + wallet_view = sign_in_view.wallet_button.click() + wallet_view.set_up_wallet() + + wallet_view.just_fyi('Add account from seed phrase') + wallet_view.add_account_button.click() + wallet_view.enter_a_seed_phrase_button.click() + wallet_view.enter_your_password_input.send_keys(common_password) + + wallet_view.enter_seed_phrase_input.set_value('') + account_name = ''.join(random.choice(string.ascii_uppercase + string.digits) for _ in range(10)) + wallet_view.account_name_input.send_keys(account_name) + wallet_view.add_account_generate_account_button.click() + if wallet_view.get_account_options_by_name(account_name).is_element_displayed(): + self.driver.fail('Account is added without seed phrase') + wallet_view.enter_seed_phrase_input.set_value(str(wallet_users['C']['passphrase']).upper()) + wallet_view.add_account_generate_account_button.click() + + account_button = wallet_view.get_account_by_name(account_name) + if not account_button.is_element_displayed(): + self.driver.fail('Account was not added') + + wallet_view.just_fyi('Check that overall balance is changed after adding account') + for asset in ('ETHro', 'ADI'): + wallet_view.wait_balance_is_changed(asset) + + wallet_view.just_fyi('Check account view and send option') + wallet_view.get_account_by_name(account_name).click() + if not wallet_view.send_transaction_button.is_element_displayed(): + self.errors.append('Send button is not shown on account added with seed phrase') + wallet_view.receive_transaction_button.click() + if wallet_view.address_text.text[2:] != wallet_users['C']['address']: + self.errors.append( + 'Wrong address %s is shown in "Receive" popup ' % wallet_view.address_text.text) + self.errors.verify_no_errors() + + @marks.testrail_id(6272) + @marks.high + def test_add_account_to_multiaccount_instance_private_key(self): + sign_in_view = SignInView(self.driver) + sign_in_view.create_user() + wallet_view = sign_in_view.wallet_button.click() + wallet_view.set_up_wallet() + + wallet_view.just_fyi('Add account from private key') + wallet_view.add_account_button.click() + wallet_view.enter_a_private_key_button.click() + wallet_view.enter_your_password_input.send_keys(common_password) + + wallet_view.enter_a_private_key_input.set_value(wallet_users['C']['private_key'][0:9]) + account_name = ''.join(random.choice(string.ascii_uppercase + string.digits) for _ in range(10)) + wallet_view.account_name_input.send_keys(account_name) + wallet_view.add_account_generate_account_button.click() + if wallet_view.get_account_options_by_name(account_name).is_element_displayed(): + self.driver.fail('Account is added with wrong private key') + wallet_view.enter_a_private_key_input.set_value(wallet_users['C']['private_key']) + wallet_view.add_account_generate_account_button.click() + + account_button = wallet_view.get_account_by_name(account_name) + if not account_button.is_element_displayed(): + self.driver.fail('Account was not added') + + wallet_view.just_fyi('Check that overall balance is changed after adding account') + for asset in ('ETHro', 'ADI'): + wallet_view.wait_balance_is_changed(asset) + + wallet_view.just_fyi('Check individual account view, receive option') + wallet_view.get_account_by_name(account_name).click() + if not wallet_view.send_transaction_button.is_element_displayed(): + self.errors.append('Send button is not shown on account added with private key') + wallet_view.receive_transaction_button.click() + if wallet_view.address_text.text[2:] != wallet_users['C']['address']: + self.errors.append( + 'Wrong address %s is shown in "Receive" popup account ' % wallet_view.address_text.text) + self.errors.verify_no_errors() + @marks.testrail_id(5406) @marks.critical diff --git a/test/appium/tests/users.py b/test/appium/tests/users.py index 459b9d5b366..0d719151714 100644 --- a/test/appium/tests/users.py +++ b/test/appium/tests/users.py @@ -44,9 +44,10 @@ wallet_users['C'] = dict() wallet_users['C']['passphrase'] = "purchase ensure mistake crystal person similar shaft family shield clog risk market" wallet_users['C']['username'] = "Mellow Virtual Nubiangoat" -wallet_users['C']['address'] = "8dce052ccda2f6f6b555759cee6957e04a6ddf5b" +wallet_users['C']['address'] = "8DcE052cCda2F6F6B555759cEe6957e04A6dDf5B" wallet_users['C']['public_key'] = "0x040e562b69362e7e57492bca50b6095acfa636c48b85eef2bc0e4180b6e99fc5e73f45c3" \ "40837da01728d4585695fda7f1de2ed193a1dd4080291d90812e1cae77" +wallet_users['C']['private_key'] = '7800C28310576645BBF6BF6355F7AA4CEC659B1713AF7E7713E1A33097A3DDF6' wallet_users['D'] = dict() wallet_users['D']['passphrase'] = "hen mango since lottery laundry flag report whisper cycle rate festival carry" diff --git a/test/appium/views/wallet_view.py b/test/appium/views/wallet_view.py index 447b98ae17e..403541589c8 100644 --- a/test/appium/views/wallet_view.py +++ b/test/appium/views/wallet_view.py @@ -268,50 +268,59 @@ def __init__(self, driver): self.locator = self.Locator.text_selector('Add account') -class AddAnAccountButton(BaseButton): +class GenerateAnAccountButton(BaseButton): def __init__(self, driver): - super(AddAnAccountButton, self).__init__(driver) - self.locator = self.Locator.text_selector('Add an account') + super(GenerateAnAccountButton, self).__init__(driver) + self.locator = self.Locator.accessibility_id('add-account-sheet-generate') class AddAWatchOnlyAddressButton(BaseButton): def __init__(self, driver): super(AddAWatchOnlyAddressButton, self).__init__(driver) - self.locator = self.Locator.text_selector('Add a watch-only address') + self.locator = self.Locator.accessibility_id('add-account-sheet-watch') + +class EnterASeedPhraseButton(BaseButton): + def __init__(self, driver): + super(EnterASeedPhraseButton, self).__init__(driver) + self.locator = self.Locator.accessibility_id('add-account-sheet-seed') + +class EnterAPrivateKeyButton(BaseButton): + def __init__(self, driver): + super(EnterAPrivateKeyButton, self).__init__(driver) + self.locator = self.Locator.accessibility_id('add-account-sheet-private-key') class EnterAddressInput(BaseEditBox): def __init__(self, driver): super(EnterAddressInput, self).__init__(driver) - self.locator = self.Locator.text_selector('Enter address') + self.locator = self.Locator.accessibility_id('add-account-enter-watch-address') + +class EnterSeedPhraseInput(BaseEditBox): + def __init__(self, driver): + super(EnterSeedPhraseInput, self).__init__(driver) + self.locator = self.Locator.accessibility_id('add-account-enter-seed') + +class EnterPrivateKeyInput(BaseEditBox): + def __init__(self, driver): + super(EnterPrivateKeyInput, self).__init__(driver) + self.locator = self.Locator.accessibility_id('add-account-enter-private-key') class DeleteAccountButton(BaseButton): def __init__(self, driver): super(DeleteAccountButton, self).__init__(driver) self.locator = self.Locator.text_selector('Delete account') -class GenerateNewAccountButton(BaseButton): - def __init__(self, driver): - super(GenerateNewAccountButton, self).__init__(driver) - self.locator = self.Locator.text_selector('Generate keys') class EnterYourPasswordInput(BaseEditBox): def __init__(self, driver): super(EnterYourPasswordInput, self).__init__(driver) - self.locator = self.Locator.xpath_selector( - "//android.widget.TextView[@text='Enter your password']/following-sibling::android.widget.EditText") + self.locator = self.Locator.accessibility_id('add-account-enter-password') -class GenerateAccountButton(BaseButton): - def __init__(self, driver): - super(GenerateAccountButton, self).__init__(driver) - self.locator = self.Locator.text_selector('Generate keys') - class AccountNameInput(BaseEditBox): def __init__(self, driver): super(AccountNameInput, self).__init__(driver) - self.locator = self.Locator.xpath_selector("//android.widget.TextView[@text='Account name']" - "/following-sibling::android.view.ViewGroup/android.widget.EditText") + self.locator = self.Locator.accessibility_id('add-account-enter-account-name') class AccountColorButton(BaseButton): @@ -325,11 +334,11 @@ def select_color_by_position(self, position: int): self.driver.find_element_by_xpath( "//*[@text='Cancel']/../preceding-sibling::android.widget.ScrollView/*/*[%s]" % position).click() - -class FinishButton(BaseButton): +# Add account on Generate An Account screen +class AddAccountGenerateAnAccountButton(BaseButton): def __init__(self, driver): - super(FinishButton, self).__init__(driver) - self.locator = self.Locator.text_selector('Finish') + super(AddAccountGenerateAnAccountButton, self).__init__(driver) + self.locator = self.Locator.accessibility_id('add-account-add-account-button') class AccountSettingsButton(BaseButton): def __init__(self, driver): @@ -380,16 +389,18 @@ def __init__(self, driver): self.view_in_cryptokitties_button = ViewInCryptoKittiesButton(self.driver) self.set_currency_button = SetCurrencyButton(self.driver) self.add_account_button = AddAccountButton(self.driver) - self.add_an_account_button = AddAnAccountButton(self.driver) + self.generate_an_account_button = GenerateAnAccountButton(self.driver) self.add_watch_only_address_button = AddAWatchOnlyAddressButton(self.driver) + self.enter_a_seed_phrase_button = EnterASeedPhraseButton(self.driver) + self.enter_a_private_key_button = EnterAPrivateKeyButton(self.driver) self.enter_address_input = EnterAddressInput(self.driver) + self.enter_seed_phrase_input = EnterSeedPhraseInput(self.driver) + self.enter_a_private_key_input = EnterPrivateKeyInput(self.driver) self.delete_account_button = DeleteAccountButton(self.driver) - self.generate_new_account_button = GenerateNewAccountButton(self.driver) self.enter_your_password_input = EnterYourPasswordInput(self.driver) - self.generate_account_button = GenerateAccountButton(self.driver) self.account_name_input = AccountNameInput(self.driver) self.account_color_button = AccountColorButton(self.driver) - self.finish_button = FinishButton(self.driver) + self.add_account_generate_account_button = AddAccountGenerateAnAccountButton(self.driver) # individual account settings self.account_settings_button = AccountSettingsButton(self.driver) @@ -561,10 +572,7 @@ def get_account_by_name(self, account_name: str): def add_account(self, account_name: str, password: str = common_password): self.add_account_button.click() - self.add_an_account_button.click() - self.generate_new_account_button.click() - self.generate_account_button.click() + self.generate_an_account_button.click() self.enter_your_password_input.send_keys(password) - self.generate_account_button.click() self.account_name_input.send_keys(account_name) - self.finish_button.click() + self.add_account_generate_account_button.click()