diff --git a/resources/images/ui/keycard-logo.png b/resources/images/ui/keycard-logo.png new file mode 100644 index 0000000000..8cd8acd049 Binary files /dev/null and b/resources/images/ui/keycard-logo.png differ diff --git a/src/status_im/events.cljs b/src/status_im/events.cljs index 9365693a39..e52b5f6513 100644 --- a/src/status_im/events.cljs +++ b/src/status_im/events.cljs @@ -28,6 +28,7 @@ [status-im.fleet.core :as fleet] [status-im.group-chats.core :as group-chats] [status-im.hardwallet.core :as hardwallet] + [status-im.signing.keycard :as signing.keycard] [status-im.i18n :as i18n] [status-im.init.core :as init] [status-im.log-level.core :as log-level] @@ -1056,11 +1057,6 @@ (fn [cofx [_ data]] (hardwallet/on-get-keys-success cofx data))) -(handlers/register-handler-fx - :hardwallet.callback/on-sign-success - (fn [cofx [_ data]] - (hardwallet/on-sign-success cofx data))) - (handlers/register-handler-fx :hardwallet/auto-login (fn [cofx _] @@ -1343,16 +1339,6 @@ (fn [cofx _] (hardwallet/navigate-to-reset-card-screen cofx))) -(handlers/register-handler-fx - :hardwallet/sign - (fn [cofx _] - (hardwallet/sign cofx))) - -(handlers/register-handler-fx - :hardwallet/prepare-to-sign - (fn [cofx _] - (hardwallet/prepare-to-sign cofx))) - (handlers/register-handler-fx :hardwallet/unblock-pin (fn [cofx _] diff --git a/src/status_im/hardwallet/core.cljs b/src/status_im/hardwallet/core.cljs index 9590a3d1f4..4f6688dd6a 100644 --- a/src/status_im/hardwallet/core.cljs +++ b/src/status_im/hardwallet/core.cljs @@ -1123,6 +1123,7 @@ nil))) (fx/defn sign + {:events [:hardwallet/sign]} [{:keys [db] :as cofx}] (let [card-connected? (get-in db [:hardwallet :card-connected?]) pairing (get-pairing db) @@ -1140,24 +1141,25 @@ :pairing pairing :pin pin}} (fx/merge cofx - {:db (assoc-in db [:hardwallet :on-card-connected] :hardwallet/sign)} + {:db (-> db + (assoc-in [:signing/sign :keycard-step] :signing) + (assoc-in [:hardwallet :on-card-connected] :hardwallet/sign))} (when-not keycard-match? - (show-wrong-keycard-alert card-connected?)) - (navigate-to-connect-screen :hardwallet-connect-sign))))) + (show-wrong-keycard-alert card-connected?)))))) (fx/defn prepare-to-sign + {:events [:hardwallet/prepare-to-sign]} [{:keys [db] :as cofx}] (let [card-connected? (get-in db [:hardwallet :card-connected?]) pairing (get-pairing db)] (if card-connected? - (get-application-info cofx pairing :hardwallet/sign) (fx/merge cofx - {:db (assoc-in db [:hardwallet :on-card-connected] :hardwallet/prepare-to-sign)} - (navigation/navigate-to-cofx - (if (= (:view-id db) :enter-pin-modal) - :hardwallet-connect-modal - :hardwallet-connect-sign) - nil))))) + {:db (assoc-in db [:signing/sign :keycard-step] :signing)} + (get-application-info pairing :hardwallet/sign)) + (fx/merge cofx + {:db (-> db + (assoc-in [:signing/sign :keycard-step] :connect) + (assoc-in [:hardwallet :on-card-connected] :hardwallet/prepare-to-sign))})))) (fx/defn import-account [{:keys [db] :as cofx}] @@ -1590,23 +1592,22 @@ {:send-transaction-with-signature data}) (fx/defn sign-message-completed - [{:keys [db]} signature] - (let [screen-params (get-in db [:navigation/screen-params :wallet-sign-message-modal]) - signature' (-> signature + [_ signature] + (let [signature' (-> signature ; add 27 to last byte ; https://github.com/ethereum/go-ethereum/blob/master/internal/ethapi/api.go#L431 (clojure.string/replace-first #"00$", "1b") (clojure.string/replace-first #"01$", "1c") (ethereum/normalized-address))] {:dispatch - [:status-im.ui.screens.wallet.send.events/sign-message-completed - screen-params - {:result signature'}]})) + [:signing/sign-message-completed (types/clj->json {:result signature'})]})) (fx/defn on-sign-success + {:events [:hardwallet.callback/on-sign-success]} [{:keys [db] :as cofx} signature] (log/debug "[hardwallet] sign success: " signature) - (let [transaction (get-in db [:hardwallet :transaction])] + (let [transaction (get-in db [:hardwallet :transaction]) + tx-obj (select-keys transaction [:from :to :value :gas :gasPrice])] (fx/merge cofx {:db (-> db (assoc-in [:hardwallet :pin :sign] []) @@ -1618,30 +1619,28 @@ (if transaction (send-transaction-with-signature {:transaction (types/clj->json transaction) :signature signature - :on-completed #(re-frame/dispatch [:wallet.callback/transaction-completed (types/json->clj %)])}) + :on-completed #(re-frame/dispatch [:signing/transaction-completed % tx-obj])}) (sign-message-completed signature))))) (fx/defn on-sign-error [{:keys [db] :as cofx} error] (log/debug "[hardwallet] sign error: " error) - (let [tag-was-lost? (= "Tag was lost." (:error error)) - modal? (get-in db [:navigation/screen-params :wallet-send-modal-stack :modal?])] + (let [tag-was-lost? (= "Tag was lost." (:error error))] (fx/merge cofx (if tag-was-lost? (fx/merge cofx - {:db (-> db - (assoc-in [:hardwallet :on-card-connected] :hardwallet/prepare-to-sign) - (assoc-in [:hardwallet :pin :status] nil)) + {:db (-> db + (assoc-in [:hardwallet :on-card-connected] :hardwallet/prepare-to-sign) + (assoc-in [:hardwallet :pin :status] nil) + (assoc-in [:signing/sign :keycard-step] :connect)) :utils/show-popup {:title (i18n/label :t/error) - :content (i18n/label :t/cannot-read-card)}} - (navigate-to-connect-screen :hardwallet-connect-sign))) + :content (i18n/label :t/cannot-read-card)}})) (if (re-matches pin-mismatch-error (:error error)) (fx/merge cofx - {:db (update-in db [:hardwallet :pin] merge {:status :error - :sign [] - :error-label :t/pin-mismatch})} - (navigation/navigate-to-cofx (if modal? - :enter-pin-modal - :enter-pin-sign) nil) + {:db (-> db + (update-in [:hardwallet :pin] merge {:status :error + :sign [] + :error-label :t/pin-mismatch}) + (assoc-in [:signing/sign :keycard-step] :pin))} (get-application-info (get-pairing db) nil)) (show-wrong-keycard-alert cofx true))))) diff --git a/src/status_im/react_native/resources.cljs b/src/status_im/react_native/resources.cljs index 23c2b51b44..bd435de676 100644 --- a/src/status_im/react_native/resources.cljs +++ b/src/status_im/react_native/resources.cljs @@ -13,6 +13,7 @@ :secret-keys (js-require/js-require "./resources/images/ui/secret-keys.png") :keycard-lock (js-require/js-require "./resources/images/ui/keycard-lock.png") :keycard (js-require/js-require "./resources/images/ui/keycard.png") + :keycard-logo (js-require/js-require "./resources/images/ui/keycard-logo.png") :keycard-phone (js-require/js-require "./resources/images/ui/keycard-phone.png") :keycard-connection (js-require/js-require "./resources/images/ui/keycard-connection.png") :keycard-nfc-on (js-require/js-require "./resources/images/ui/keycard-nfc-on.png") diff --git a/src/status_im/signing/core.cljs b/src/status_im/signing/core.cljs index 7ac401c1ae..0e594493f9 100644 --- a/src/status_im/signing/core.cljs +++ b/src/status_im/signing/core.cljs @@ -166,13 +166,14 @@ (fx/defn show-sign [{:keys [db] :as cofx}] (let [{:signing/keys [queue]} db - {{:keys [gas gasPrice] :as tx-obj} :tx-obj {:keys [data typed?] :as message} :message :as tx} (last queue)] + {{:keys [gas gasPrice] :as tx-obj} :tx-obj {:keys [data typed?] :as message} :message :as tx} (last queue) + keycard-account? (boolean (get-in db [:account/account :keycard-instance-uid]))] (if message {:db (assoc db :signing/in-progress? true :signing/queue (drop-last queue) :signing/tx tx - :signing/sign {:type :password + :signing/sign {:type (if keycard-account? :keycard :password) :formatted-data (if typed? (types/json->clj data) (ethereum/hex-to-utf8 data))})} (fx/merge cofx {:db diff --git a/src/status_im/signing/keycard.cljs b/src/status_im/signing/keycard.cljs new file mode 100644 index 0000000000..a0341c847f --- /dev/null +++ b/src/status_im/signing/keycard.cljs @@ -0,0 +1,76 @@ +(ns status-im.signing.keycard + (:require [re-frame.core :as re-frame] + [status-im.utils.fx :as fx] + [status-im.native-module.core :as status] + [status-im.utils.types :as types] + [status-im.ethereum.abi-spec :as abi-spec] + [status-im.ethereum.core :as ethereum])) + +(re-frame/reg-fx + ::hash-transaction + (fn [{:keys [transaction on-completed]}] + (status/hash-transaction (types/clj->json transaction) on-completed))) + +(re-frame/reg-fx + ::hash-message + (fn [{:keys [message on-completed]}] + (status/hash-message message on-completed))) + +(re-frame/reg-fx + ::hash-typed-data + (fn [{:keys [data on-completed]}] + (status/hash-typed-data data on-completed))) + +(defn prepare-transaction + [{:keys [gas gasPrice data nonce tx-obj]}] + (let [{:keys [from to value]} tx-obj] + (cond-> {:from from + :to to + :value value + :gas (str "0x" (abi-spec/number-to-hex gas)) + :gasPrice (str "0x" (abi-spec/number-to-hex gasPrice))} + data + (assoc :data data) + nonce + (assoc :nonce nonce)))) + +(fx/defn hash-message + [_ {:keys [data typed?]}] + (if typed? + {::hash-typed-data {:data data + :on-completed #(re-frame/dispatch [:signing.keycard.callback/hash-message-completed %])}} + {::hash-message {:message (ethereum/naked-address data) + :on-completed #(re-frame/dispatch [:signing.keycard.callback/hash-message-completed %])}})) + +(fx/defn hash-message-completed + {:events [:signing.keycard.callback/hash-message-completed]} + [{:keys [db]} result] + (let [{:keys [result error]} (types/json->clj result)] + {:db (-> db + (assoc-in [:hardwallet :hash] result))})) + +(fx/defn hash-transaction + [{:keys [db]}] + {::hash-transaction {:transaction (prepare-transaction (:signing/tx db)) + :on-completed #(re-frame/dispatch [:signing.keycard.callback/hash-transaction-completed %])}}) + +(fx/defn hash-transaction-completed + {:events [:signing.keycard.callback/hash-transaction-completed]} + [{:keys [db]} result] + (let [{:keys [transaction hash]} (:result (types/json->clj result))] + {:db (-> db + (assoc-in [:hardwallet :transaction] transaction) + (assoc-in [:hardwallet :hash] hash))})) + +(fx/defn sign-with-keycard + {:events [:signing.ui/sign-with-keycard-pressed]} + [{:keys [db] :as cofx}] + (let [message (get-in db [:signing/tx :message])] + (fx/merge cofx + {:db (-> db + (assoc-in [:hardwallet :pin :enter-step] :sign) + (assoc-in [:signing/sign :keycard-step] :pin) + (assoc-in [:signing/sign :type] :keycard))} + (if message + (hash-message message) + (hash-transaction))))) \ No newline at end of file diff --git a/src/status_im/subs.cljs b/src/status_im/subs.cljs index 68e94d196c..c87fd97485 100644 --- a/src/status_im/subs.cljs +++ b/src/status_im/subs.cljs @@ -226,6 +226,11 @@ :<- [:dimensions/window] :width) +(re-frame/reg-sub + :dimensions/window-height + :<- [:dimensions/window] + :height) + (re-frame/reg-sub :get-screen-params :<- [:screen-params] diff --git a/src/status_im/ui/components/colors.cljs b/src/status_im/ui/components/colors.cljs index 572a21113c..1a71b450e9 100644 --- a/src/status_im/ui/components/colors.cljs +++ b/src/status_im/ui/components/colors.cljs @@ -24,6 +24,7 @@ (def black-transparent (alpha black 0.1)) ;; Used as background color for rounded button on dark background and as background color for containers like "Backup seed phrase" (def black-transparent-40 (alpha black 0.4)) (def gray-light black-transparent) ;; Used as divider color +(def black-light "#2d2d2d") ;; DARK GREY (def gray "#939ba1") ;; Dark grey, used as a background for a light foreground and as section header and secondary text color diff --git a/src/status_im/ui/screens/hardwallet/pin/styles.cljs b/src/status_im/ui/screens/hardwallet/pin/styles.cljs index 48ec12d3e7..0fb205cd6b 100644 --- a/src/status_im/ui/screens/hardwallet/pin/styles.cljs +++ b/src/status_im/ui/screens/hardwallet/pin/styles.cljs @@ -21,10 +21,10 @@ {:color colors/red :text-align :center}) -(def center-container +(defn center-container [title] {:flex-direction :column :align-items :center - :margin-top 28}) + :margin-top (if title 28 5)}) (def center-title-text {:typography :header}) diff --git a/src/status_im/ui/screens/hardwallet/pin/views.cljs b/src/status_im/ui/screens/hardwallet/pin/views.cljs index 75121a7df5..346a05c2fb 100644 --- a/src/status_im/ui/screens/hardwallet/pin/views.cljs +++ b/src/status_im/ui/screens/hardwallet/pin/views.cljs @@ -75,12 +75,14 @@ (let [enabled? (not= status :verifying)] [react/scroll-view [react/view styles/pin-container - [react/view styles/center-container - [react/text {:style styles/center-title-text} - (i18n/label title-label)] - [react/text {:style styles/create-pin-text - :number-of-lines 2} - (i18n/label description-label)] + [react/view (styles/center-container title-label) + (when title-label + [react/text {:style styles/center-title-text} + (i18n/label title-label)]) + (when description-label + [react/text {:style styles/create-pin-text + :number-of-lines 2} + (i18n/label description-label)]) (when retry-counter [react/text {:style {:font-weight "700" :padding-top 10 diff --git a/src/status_im/ui/screens/signing/views.cljs b/src/status_im/ui/screens/signing/views.cljs index 3a5cdafa90..b4c2dac773 100644 --- a/src/status_im/ui/screens/signing/views.cljs +++ b/src/status_im/ui/screens/signing/views.cljs @@ -18,7 +18,9 @@ [status-im.ui.screens.signing.sheets :as sheets] [status-im.ethereum.tokens :as tokens] [clojure.string :as string] - [status-im.ui.screens.signing.styles :as styles])) + [status-im.ui.screens.signing.styles :as styles] + [status-im.react-native.resources :as resources] + [status-im.ui.screens.hardwallet.pin.views :as pin.views])) (defn hide-panel-anim [bottom-anim-value alpha-value window-height] @@ -95,14 +97,79 @@ [react/view {:padding 6} [react/text {:style {:color colors/blue}} (i18n/label :t/cancel)]]]]) -(views/defview password-view [{:keys [type error in-progress? enabled?]}] +(views/defview keycard-pin-view [] + (views/letsubs [pin [:hardwallet/pin]] + [react/view + [pin.views/pin-view + {:pin pin + :retry-counter nil + :step :sign + :status nil + :error-label nil}]])) + +(defn- keycard-connect-view [] + [react/view {:padding-vertical 20 + :flex 1 + :align-items :center + :justify-content :center} + [react/image {:source (resources/get-image :keycard-phone) + :resize-mode :center + :style {:width 160 + :height 170}}] + [react/view {:margin-top 10} + [react/text {:style {:text-align :center + :color colors/gray}} + (i18n/label :t/hold-card)]]]) + +(defn- keycard-processing-view [] + [react/view {:flex-direction :column + :flex 1 + :justify-content :center + :align-items :center} + [react/activity-indicator {:size :large + :animating true}] + [react/text {:style {:margin-top 16 + :color colors/gray}} + (i18n/label :t/processing)]]) + +(defn- sign-with-keycard-button + [amount-error gas-error] + [button/button {:on-press #(re-frame/dispatch [:signing.ui/sign-with-keycard-pressed]) + :text-style {:padding-right 2 + :padding-left 16} + :style {:background-color colors/black-light + :padding-top 2 + :border-radius 8} + :disabled? (or amount-error gas-error)} + (i18n/label :t/sign-with) + [react/view {:padding-right 16} + [react/image {:source (resources/get-image :keycard-logo) + :style {:width 64 + :margin-bottom 7 + :height 26}}]]]) + +(defn- signing-phrase-view [phrase] + [react/view {:align-items :center} + [react/text {:style {:color colors/gray :padding-bottom 8}} (i18n/label :t/signing-phrase)] + [react/text phrase]]) + +(defn- keycard-view + [{:keys [keycard-step]} phrase] + [react/view {:height 450} + [signing-phrase-view phrase] + (case keycard-step + :pin [keycard-pin-view] + :connect [keycard-connect-view] + :signing [keycard-processing-view] + [react/view {:align-items :center :margin-top 16 :margin-bottom 40} + [sign-with-keycard-button nil nil]])]) + +(views/defview password-view [{:keys [type error in-progress? enabled?] :as sign}] (views/letsubs [phrase [:signing/phrase]] (case type :password [react/view {:padding-top 16 :padding-bottom 16} - [react/view {:align-items :center} - [react/text {:style {:color colors/gray :padding-bottom 8}} (i18n/label :t/signing-phrase)] - [react/text phrase]] + [signing-phrase-view phrase] [text-input/text-input-with-label {:secure-text-entry true :placeholder (i18n/label :t/enter-password) @@ -119,10 +186,12 @@ [button/primary-button {:on-press #(re-frame/dispatch [:signing.ui/sign-is-pressed]) :disabled? (not enabled?)} (i18n/label :t/transactions-sign)])]] + :keycard + [keycard-view sign phrase] [react/view]))) (views/defview message-sheet [] - (views/letsubs [{:keys [formatted-data] :as sign} [:signing/sign]] + (views/letsubs [{:keys [formatted-data type] :as sign} [:signing/sign]] [react/view styles/message [react/view styles/message-header [react/text {:style {:typography :title-bold}} (i18n/label :t/signing-a-message)] @@ -140,7 +209,8 @@ (views/letsubs [fee [:signing/fee] sign [:signing/sign] chain [:ethereum/chain-keyword] - {:keys [amount-error gas-error]} [:signing/amount-errors]] + {:keys [amount-error gas-error]} [:signing/amount-errors] + keycard-account? [:keycard-account?]] (let [display-symbol (wallet.utils/display-symbol token) fee-display-symbol (wallet.utils/display-symbol (tokens/native-currency chain))] [react/view styles/sheet @@ -167,9 +237,11 @@ {:content (fn [] [sheets/fee-bottom-sheet fee-display-symbol]) :content-height 270}])}] [react/view {:align-items :center :margin-top 16 :margin-bottom 40} - [button/primary-button {:on-press #(re-frame/dispatch [:set :signing/sign {:type :password}]) - :disabled? (or amount-error gas-error)} - (i18n/label :t/sign-with-password)]]])]))) + (if keycard-account? + [sign-with-keycard-button amount-error gas-error] + [button/primary-button {:on-press #(re-frame/dispatch [:set :signing/sign {:type :password}]) + :disabled? (or amount-error gas-error)} + (i18n/label :t/sign-with-password)])]])]))) (defn signing-view [tx window-height] (let [bottom-anim-value (anim/create-value window-height) diff --git a/translations/en.json b/translations/en.json index 662521d3fd..6a9f071c26 100644 --- a/translations/en.json +++ b/translations/en.json @@ -1076,6 +1076,7 @@ "signing-phrase" : "Signing phrase", "network-fee" : "Network fee", "sign-with-password" : "Sign with password", + "sign-with" : "Sign with", "signing-a-message" : "Signing a message", "select-chat" : "Select chat to start messaging", "etherscan-lookup": "Look up on Etherscan",