[#9749] Support importing private key and seed

This commit is contained in:
Andrey Shovkoplyas 2020-02-28 12:37:16 +01:00
parent c9486dc634
commit a79a72fccb
No known key found for this signature in database
GPG Key ID: EAAB7C8622D860A4
15 changed files with 434 additions and 307 deletions

View File

@ -620,5 +620,6 @@ var TopLevel = {
"multiAccountLoadAccount" : function () {},
"multiAccountStoreAccount" : function () {},
"multiAccountImportMnemonic" : function () {},
"multiAccountImportPrivateKey" : function () {},
"validateMnemonic" : function () {}
}

View File

@ -776,6 +776,23 @@ class StatusModule extends ReactContextBaseJavaModule implements LifecycleEventL
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");

View File

@ -266,6 +266,16 @@ RCT_EXPORT_METHOD(multiAccountStoreDerived:(NSString *)json
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) {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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)}])))))))))))))
(derive-and-store-account path hashed-password :generated)))))
(fx/defn set-symbol-request
{:events [:wallet.accounts/share]}
[_ address]
{:list.selection/open-share {:message (eip55/address->checksum address)}})
(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)}})))
{: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 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)})})
(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)}})

View File

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