From 1e652accd08d1feb84fa421bb71e55d9c2e84131 Mon Sep 17 00:00:00 2001 From: Andrey Shovkoplyas Date: Wed, 5 Jun 2019 13:11:47 +0200 Subject: [PATCH] [#8026] [Wallet] Sign transaction module --- .../src/status_im/ui/components/react.cljs | 7 - src/status_im/browser/core.cljs | 66 ++- .../chat/commands/impl/transactions.cljs | 62 +-- src/status_im/chat/models/input.cljs | 5 + src/status_im/events.cljs | 22 - .../extensions/capacities/ethereum.cljs | 57 +-- src/status_im/hardwallet/core.cljs | 3 +- src/status_im/signing/core.cljs | 250 ++++++++++ src/status_im/signing/gas.cljs | 100 ++++ src/status_im/stickers/core.cljs | 9 +- src/status_im/subs.cljs | 118 +++-- src/status_im/tribute_to_talk/core.cljs | 50 +- .../ui/components/bottom_sheet/view.cljs | 2 +- .../ui/components/button/styles.cljs | 4 +- src/status_im/ui/components/button/view.cljs | 6 +- .../ui/components/list_item/styles.cljs | 8 +- .../ui/components/list_item/views.cljs | 32 +- .../ui/components/status_bar/view.cljs | 2 - src/status_im/ui/screens/db.cljs | 13 +- src/status_im/ui/screens/events.cljs | 1 - src/status_im/ui/screens/routing/modals.cljs | 10 +- src/status_im/ui/screens/routing/screens.cljs | 13 - .../ui/screens/routing/wallet_stack.cljs | 2 - src/status_im/ui/screens/signing/sheets.cljs | 51 ++ src/status_im/ui/screens/signing/styles.cljs | 40 ++ src/status_im/ui/screens/signing/views.cljs | 197 ++++++++ src/status_im/ui/screens/views.cljs | 7 +- .../screens/wallet/custom_tokens/views.cljs | 4 + .../ui/screens/wallet/navigation.cljs | 26 +- .../ui/screens/wallet/send/animations.cljs | 11 - .../ui/screens/wallet/send/events.cljs | 451 +----------------- .../ui/screens/wallet/send/views.cljs | 221 +-------- .../ui/screens/wallet/sign_message/views.cljs | 121 ----- src/status_im/wallet/core.cljs | 282 +---------- .../test/chat/commands/impl/transactions.cljs | 18 +- test/cljs/status_im/test/models/wallet.cljs | 57 --- test/cljs/status_im/test/runner.cljs | 10 +- test/cljs/status_im/test/signing/core.cljs | 54 +++ test/cljs/status_im/test/signing/gas.cljs | 38 ++ .../status_im/test/tribute_to_talk/core.cljs | 33 -- translations/en.json | 9 +- 41 files changed, 1034 insertions(+), 1438 deletions(-) create mode 100644 src/status_im/signing/core.cljs create mode 100644 src/status_im/signing/gas.cljs create mode 100644 src/status_im/ui/screens/signing/sheets.cljs create mode 100644 src/status_im/ui/screens/signing/styles.cljs create mode 100644 src/status_im/ui/screens/signing/views.cljs delete mode 100644 src/status_im/ui/screens/wallet/send/animations.cljs delete mode 100644 src/status_im/ui/screens/wallet/sign_message/views.cljs delete mode 100644 test/cljs/status_im/test/models/wallet.cljs create mode 100644 test/cljs/status_im/test/signing/core.cljs create mode 100644 test/cljs/status_im/test/signing/gas.cljs diff --git a/components/src/status_im/ui/components/react.cljs b/components/src/status_im/ui/components/react.cljs index 3ede316b0d91..97465a17ca2a 100644 --- a/components/src/status_im/ui/components/react.cljs +++ b/components/src/status_im/ui/components/react.cljs @@ -301,20 +301,13 @@ (case current-view (:wallet :wallet-send-transaction - :wallet-transaction-sent :wallet-request-transaction - :wallet-send-transaction-chat :wallet-send-assets :wallet-request-assets :choose-recipient :recent-recipients - :wallet-send-transaction-modal - :wallet-transaction-sent-modal :wallet-send-transaction-request - :wallet-transaction-fee - :wallet-sign-message-modal :contact-code - :wallet-onboarding-setup :wallet-modal :wallet-onboarding-setup-modal :wallet-settings-hook) diff --git a/src/status_im/browser/core.cljs b/src/status_im/browser/core.cljs index b886aae49ee7..64a86f208301 100644 --- a/src/status_im/browser/core.cljs +++ b/src/status_im/browser/core.cljs @@ -20,7 +20,8 @@ [status-im.utils.random :as random] [status-im.utils.types :as types] [status-im.utils.universal-links.core :as universal-links] - [taoensso.timbre :as log])) + [taoensso.timbre :as log] + [status-im.signing.core :as signing])) (fx/defn initialize-browsers [{:keys [db all-stored-browsers]}] @@ -274,19 +275,58 @@ (navigation/navigate-to-cofx :browser nil) (resolve-url nil)))) +(fx/defn web3-error-callback + {:events [:browser.dapp/transaction-on-error]} + [{{:keys [webview-bridge]} :db} message-id message] + {:browser/send-to-bridge + {:message {:type constants/web3-send-async-callback + :messageId message-id + :error message} + :webview webview-bridge}}) + +(fx/defn dapp-complete-transaction + {:events [:browser.dapp/transaction-on-result]} + [{{:keys [webview-bridge]} :db} message-id id result] + ;;TODO check and test id + {:browser/send-to-bridge + {:message {:type constants/web3-send-async-callback + :messageId message-id + :result {:jsonrpc "2.0" + :id (int id) + :result result}} + :webview webview-bridge}}) + +(defn normalize-sign-message-params + "NOTE (andrey) we need this function, because params may be mixed up, + so we need to figure out which one is address and which message" + [params] + (let [first_param (first params) + second_param (second params) + first-param-address? (ethereum/address? first_param) + second-param-address? (ethereum/address? second_param)] + (when (or first-param-address? second-param-address?) + (if first-param-address? + [first_param second_param] + [second_param first_param])))) + (fx/defn web3-send-async - [{:keys [db]} {:keys [method] :as payload} message-id] - (if (or (= constants/web3-send-transaction method) - (constants/web3-sign-message? method)) - {:db (update-in db [:wallet :transactions-queue] conj {:message-id message-id :payload payload}) - ;;TODO(yenda): refactor check-dapps-transactions-queue to remove this dispatch - :dispatch [:check-dapps-transactions-queue]} - {:browser/call-rpc [payload - #(re-frame/dispatch [:browser.callback/call-rpc - {:type constants/web3-send-async-callback - :messageId message-id - :error %1 - :result %2}])]})) + [cofx {:keys [method params id] :as payload} message-id] + (let [message? (constants/web3-sign-message? method)] + (if (or message? (= constants/web3-send-transaction method)) + (let [[address data] (when message? (normalize-sign-message-params params))] + (when (or (not message?) (and address data)) + (signing/sign cofx (merge + (if message? + {:message {:address address :data data :typed? (not= constants/web3-personal-sign method)}} + {:tx-obj (first params)}) + {:on-result [:browser.dapp/transaction-on-result message-id id] + :on-error [:browser.dapp/transaction-on-error message-id]})))) + {:browser/call-rpc [payload + #(re-frame/dispatch [:browser.callback/call-rpc + {:type constants/web3-send-async-callback + :messageId message-id + :error %1 + :result %2}])]}))) (fx/defn send-to-bridge [cofx message] diff --git a/src/status_im/chat/commands/impl/transactions.cljs b/src/status_im/chat/commands/impl/transactions.cljs index 97e35d3e1f81..8a18e07caec5 100644 --- a/src/status_im/chat/commands/impl/transactions.cljs +++ b/src/status_im/chat/commands/impl/transactions.cljs @@ -6,7 +6,6 @@ :as transactions-styles] [status-im.chat.commands.protocol :as protocol] - [status-im.contact.db :as db.contact] [status-im.data-store.messages :as messages-store] [status-im.ethereum.core :as ethereum] [status-im.ethereum.tokens :as tokens] @@ -18,16 +17,13 @@ [status-im.ui.components.list.views :as list] [status-im.ui.components.react :as react] [status-im.ui.components.svgimage :as svgimage] - [status-im.ui.screens.navigation :as navigation] - [status-im.ui.screens.wallet.choose-recipient.events - :as - choose-recipient.events] [status-im.ui.screens.wallet.utils :as wallet.utils] [status-im.utils.datetime :as datetime] - [status-im.utils.fx :as fx] [status-im.utils.money :as money] [status-im.utils.platform :as platform] - [status-im.wallet.db :as wallet.db]) + [status-im.wallet.db :as wallet.db] + [status-im.signing.core :as signing] + [status-im.ethereum.abi-spec :as abi-spec]) (:require-macros [status-im.utils.views :refer [defview letsubs]])) ;; common `send/request` functionality @@ -287,8 +283,8 @@ (description [_] (i18n/label :t/send-command-payment)) (parameters [_] personal-send-request-params) (validate [_ parameters cofx] - ;; Only superficial/formatting validation, "real validation" will be performed - ;; by the wallet, where we yield control in the next step + ;; Only superficial/formatting validation, "real validation" will be performed + ;; by the wallet, where we yield control in the next step (personal-send-request-validation parameters cofx)) (on-send [_ {:keys [chat-id] :as send-message} {:keys [db]}] (when-let [responding-to (get-in db [:chats chat-id :metadata :responding-to-command])] @@ -304,39 +300,21 @@ (send-preview command-message)) protocol/Yielding (yield-control [_ {{{amount :amount asset :asset} :params} :content} {:keys [db] :as cofx}] - ;; Prefill wallet and navigate there - (let [recipient-contact (or - (get-in db [:contacts/contacts (:current-chat-id db)]) - (db.contact/public-key->new-contact (:current-chat-id db))) - - sender-account (:account/account db) - chain (keyword (:chain db)) - symbol-param (keyword asset) - all-tokens (:wallet/all-tokens db) - {:keys [symbol decimals]} (tokens/asset-for all-tokens chain symbol-param) - {:keys [value error]} (wallet.db/parse-amount amount decimals) - next-view-id (if (:wallet-set-up-passed? sender-account) - :wallet-send-modal-stack - :wallet-send-modal-stack-with-onboarding)] - (fx/merge cofx - {:db (-> db - (assoc-in [:navigation/screen-params :wallet-send-modal-stack :modal?] true) - (update-in [:wallet :send-transaction] - assoc - :amount (money/formatted->internal value symbol decimals) - :amount-text amount - :amount-error error) - (choose-recipient.events/fill-request-details - (transaction-details recipient-contact symbol) false) - (update-in [:wallet :send-transaction] - dissoc :id :password :wrong-password?)) - ;; TODO(janherich) - refactor wallet send events, updating gas price - ;; is generic thing which shouldn't be defined in wallet.send, then - ;; we can include the utility helper without running into circ-dep problem - :wallet/update-gas-price - {:success-event :wallet/update-gas-price-success - :edit? false}} - (navigation/navigate-to-cofx next-view-id {})))) + (let [{:keys [symbol decimals address]} (tokens/asset-for (:wallet/all-tokens db) (keyword (:chain db)) (keyword asset)) + {:keys [value]} (wallet.db/parse-amount amount decimals) + current-chat-id (:current-chat-id db) + amount-hex (str "0x" (abi-spec/number-to-hex (money/formatted->internal value symbol decimals))) + to (ethereum/public-key->address current-chat-id) + to-norm (ethereum/normalized-address (if (= symbol :ETH) to address)) + tx-obj (if (= symbol :ETH) + {:to to-norm + :value amount-hex} + {:to to-norm + :data (abi-spec/encode "transfer(address,uint256)" [to amount-hex])})] + (signing/sign cofx {:tx-obj tx-obj + :on-result [:chat/send-transaction-result current-chat-id {:address to-norm + :asset (name symbol) + :amount amount}]}))) protocol/EnhancedParameters (enhance-send-parameters [_ parameters cofx] (-> parameters diff --git a/src/status_im/chat/models/input.cljs b/src/status_im/chat/models/input.cljs index 11c4ada47746..6deae1bece33 100644 --- a/src/status_im/chat/models/input.cljs +++ b/src/status_im/chat/models/input.cljs @@ -172,6 +172,11 @@ (command-not-complete-fx input-text current-chat-id cofx)) (plain-text-message-fx input-text current-chat-id cofx)))) +(fx/defn send-transaction-result + {:events [:chat/send-transaction-result]} + [cofx chat-id params result] + (commands.sending/send cofx chat-id (get-in cofx [:db :id->command ["send" #{:personal-chats}]]) (assoc params :tx-hash result))) + ;; effects (re-frame/reg-fx diff --git a/src/status_im/events.cljs b/src/status_im/events.cljs index c552dd378a80..5c1db02be12c 100644 --- a/src/status_im/events.cljs +++ b/src/status_im/events.cljs @@ -2102,28 +2102,6 @@ (fn [cofx [_ hash]] (wallet/open-transaction-details cofx hash))) -(handlers/register-handler-fx - :wallet/show-sign-transaction - (fn [cofx [_ {:keys [id method]} from-chat?]] - (wallet/open-send-transaction-modal cofx id method from-chat?))) - -(handlers/register-handler-fx - :wallet/update-gas-price-success - (fn [cofx [_ price edit?]] - (wallet/update-gas-price cofx price edit?))) - -(handlers/register-handler-fx - :TODO.remove/update-estimated-gas - (fn [{:keys [db]} [_ obj]] - {:wallet/update-estimated-gas - {:obj obj - :success-event :wallet/update-estimated-gas-success}})) - -(handlers/register-handler-fx - :wallet/update-estimated-gas-success - (fn [cofx [_ gas]] - (wallet/update-estimated-gas-price cofx gas))) - (handlers/register-handler-fx :wallet.setup.ui/navigate-back-pressed (fn [{:keys [db] :as cofx}] diff --git a/src/status_im/extensions/capacities/ethereum.cljs b/src/status_im/extensions/capacities/ethereum.cljs index f0070b50c04e..b30da1035871 100644 --- a/src/status_im/extensions/capacities/ethereum.cljs +++ b/src/status_im/extensions/capacities/ethereum.cljs @@ -4,22 +4,17 @@ [status-im.ethereum.abi-spec :as abi-spec] [status-im.ethereum.core :as ethereum] [status-im.ethereum.ens :as ens] - [status-im.i18n :as i18n] [status-im.native-module.core :as status] [status-im.ui.screens.navigation :as navigation] [status-im.utils.fx :as fx] [status-im.utils.handlers :as handlers] - [status-im.utils.hex :as hex] - [status-im.utils.money :as money] [status-im.utils.types :as types] - [status-im.wallet.core :as wallet])) + [status-im.signing.core :as signing])) (handlers/register-handler-fx :extensions/wallet-ui-on-success - (fn [cofx [_ on-success _ result _]] - (fx/merge cofx - (when on-success (on-success {:value result})) - (navigation/navigate-back)))) + (fn [_ [_ on-success result]] + (when on-success (on-success {:value result})))) (handlers/register-handler-fx :extensions/wallet-ui-on-failure @@ -42,35 +37,11 @@ #(f db (assoc arguments address-keyword %)))) (f db arguments)))) -(defn prepare-extension-transaction [params contacts on-success on-failure] - (let [{:keys [to value data gas gasPrice nonce]} params - contact (get contacts (hex/normalize-hex to))] - (cond-> {:id "extension-id" - :to-name (or (when (nil? to) - (i18n/label :t/new-contract)) - contact) - :symbol :ETH - :method constants/web3-send-transaction - :to to - :amount (money/bignumber (or value 0)) - :gas (cond - gas - (money/bignumber gas) - (and value (empty? data)) - (money/bignumber 21000)) - :gas-price (when gasPrice - (money/bignumber gasPrice)) - :data data - :on-result [:extensions/wallet-ui-on-success on-success] - :on-error [:extensions/wallet-ui-on-failure on-failure]} - nonce - (assoc :nonce nonce)))) - (defn- execute-send-transaction [db {:keys [method params on-success on-failure] :as arguments}] - (let [tx-object (assoc (select-keys arguments [:to :gas :gas-price :value :nonce]) - :data (when (and method params) (abi-spec/encode method params))) - transaction (prepare-extension-transaction tx-object (:contacts/contacts db) on-success on-failure)] - (wallet/open-modal-wallet-for-transaction db transaction tx-object))) + (signing/sign {:db db} {:tx-obj (assoc (select-keys arguments [:to :gas :gas-price :value :nonce]) + :data (when (and method params) (abi-spec/encode method params))) + :on-result [:extensions/wallet-ui-on-success on-success] + :on-error [:extensions/wallet-ui-on-failure on-failure]})) (handlers/register-handler-fx :extensions/ethereum-send-transaction @@ -381,22 +352,16 @@ (when on-failure (on-failure {:value (str "'" name "' is not a valid name")}))))) -;; EXTENSION SIGN -> SIGN MESSAGE (handlers/register-handler-fx :extensions/ethereum-sign (fn [{db :db :as cofx} [_ _ {:keys [message data id on-success on-failure]}]] (if (and message data) (when on-failure (on-failure {:error "only one of :message and :data can be used"})) - (fx/merge cofx - {:db (assoc-in db [:wallet :send-transaction] - {:id id - :from (ethereum/current-address db) - :data (or data (ethereum/utf8-to-hex message)) - :on-result [:extensions/wallet-ui-on-success on-success] - :on-error [:extensions/wallet-ui-on-failure on-failure] - :method constants/web3-personal-sign})} - (navigation/navigate-to-cofx :wallet-sign-message-modal nil))))) + (signing/sign cofx {:message {:address (ethereum/current-address db) + :data (or data (ethereum/utf8-to-hex message))} + :on-result [:extensions/wallet-ui-on-success on-success] + :on-error [:extensions/wallet-ui-on-failure on-failure]})))) (handlers/register-handler-fx :extensions/ethereum-create-address diff --git a/src/status_im/hardwallet/core.cljs b/src/status_im/hardwallet/core.cljs index fc8509082a30..e3078426159f 100644 --- a/src/status_im/hardwallet/core.cljs +++ b/src/status_im/hardwallet/core.cljs @@ -62,7 +62,8 @@ (if navigate-to-browser? (fx/merge cofx {:db (assoc-in db [:hardwallet :on-card-connected] nil)} - (wallet/discard-transaction) + ;;TODO use new signing flow + ;;(wallet/discard-transaction) (navigation/navigate-to-cofx :browser nil)) (if (= :enter-pin-login (:view-id db)) (navigation/navigate-to-clean cofx :accounts nil) diff --git a/src/status_im/signing/core.cljs b/src/status_im/signing/core.cljs new file mode 100644 index 000000000000..d077e625fb5f --- /dev/null +++ b/src/status_im/signing/core.cljs @@ -0,0 +1,250 @@ +(ns status-im.signing.core + (:require [status-im.utils.fx :as fx] + [status-im.i18n :as i18n] + [status-im.utils.money :as money] + [status-im.utils.hex :as utils.hex] + [status-im.ethereum.tokens :as tokens] + [status-im.ethereum.core :as ethereum] + [clojure.string :as string] + [status-im.ethereum.abi-spec :as abi-spec] + [status-im.utils.security :as security] + [status-im.utils.types :as types] + [status-im.native-module.core :as status] + [re-frame.core :as re-frame] + [status-im.constants :as constants] + [status-im.utils.utils :as utils] + status-im.utils.handlers)) + +(re-frame/reg-fx + :signing/send-transaction-fx + (fn [{:keys [tx-obj password cb]}] + (status/send-transaction (types/clj->json tx-obj) + (security/safe-unmask-data password) + cb))) + +(re-frame/reg-fx + :signing/show-transaction-error + (fn [message] + (utils/show-popup (i18n/label :t/transaction-failed) message))) + +(re-frame/reg-fx + :signing/show-transaction-result + (fn [] + (utils/show-popup (i18n/label :t/transaction-sent) (i18n/label :t/transaction-description)))) + +(re-frame/reg-fx + :signing.fx/sign-message + (fn [{:keys [params on-completed]}] + (status/sign-message (types/clj->json params) + on-completed))) + +(re-frame/reg-fx + :signing.fx/sign-typed-data + (fn [{:keys [data on-completed password]}] + (status/sign-typed-data data (security/safe-unmask-data password) on-completed))) + +(defn get-contact [db to] + (let [to (utils.hex/normalize-hex to)] + (or + (get-in db [:contacts/contacts to]) + {:address (ethereum/normalized-address to)}))) + +(fx/defn change-password + {:events [:signing.ui/password-is-changed]} + [{db :db} password] + (let [unmasked-pass (security/safe-unmask-data password)] + {:db (update db :signing/sign assoc + :password password + :enabled? (and unmasked-pass (> (count unmasked-pass) 5)))})) + +(fx/defn sign-message + [{{:signing/keys [sign tx] :as db} :db}] + (let [{{:keys [data typed?]} :message} tx + {:keys [in-progress? password]} sign + from (ethereum/current-address db)] + (when-not in-progress? + (merge + {:db (update db :signing/sign assoc :error nil :in-progress? true)} + (if typed? + {:signing.fx/sign-typed-data {:data data + :password password + :on-completed #(re-frame/dispatch [:signing/sign-message-completed %])}} + {:signing.fx/sign-message {:params {:data data + :password (security/safe-unmask-data password) + :account from} + :on-completed #(re-frame/dispatch [:signing/sign-message-completed %])}}))))) + +(fx/defn send-transaction + {:events [:signing.ui/sign-is-pressed]} + [{{:signing/keys [sign tx] :as db} :db :as cofx}] + (let [{:keys [in-progress? password]} sign + {:keys [tx-obj gas gasPrice message]} tx] + (if message + (sign-message cofx) + (let [tx-obj-to-send (merge tx-obj + {:from (ethereum/current-address db)} + (when gas + {:gas (str "0x" (abi-spec/number-to-hex gas))}) + (when gasPrice + {:gasPrice (str "0x" (abi-spec/number-to-hex gasPrice))}))] + (when-not in-progress? + {:db (update db :signing/sign assoc :error nil :in-progress? true) + :signing/send-transaction-fx {:tx-obj tx-obj-to-send + :password password + :cb #(re-frame/dispatch [:signing/transaction-completed % tx-obj-to-send])}}))))) + +(fx/defn prepare-unconfirmed-transaction + [{:keys [db now]} hash {:keys [value gasPrice gas data to from]} symbol] + (let [all-tokens (:wallet/all-tokens db) + chain (:chain db) + token (tokens/symbol->token all-tokens (keyword chain) symbol)] + {:db (assoc-in db [:wallet :transactions hash] + {:timestamp (str now) + :to to + :from from + :type :pending + :hash hash + :data data + :token token + :symbol symbol + :value (money/to-fixed (money/bignumber value)) + :gas-price (money/to-fixed (money/bignumber gasPrice)) + :gas-limit (money/to-fixed (money/bignumber gas))})})) + +(defn get-transfer-token [db to data] + (let [{:keys [symbol decimals] :as token} (tokens/address->token (:wallet/all-tokens db) (ethereum/chain-keyword db) to)] + (when (and token data (string? data) (string/starts-with? data (abi-spec/signature->method-id "transfer(address,uint256)"))) + (let [[address value] (abi-spec/decode (str "0x" (subs data 10)) ["address" "uint256"])] + (when (and address value) + {:to address + :contact (get-contact db address) + :contract to + :amount (money/to-fixed (money/token->unit value decimals)) + :token token + :symbol symbol}))))) + +(defn parse-tx-obj [db {:keys [to value data]}] + (if (nil? to) + {:contact {:name (i18n/label :t/new-contract)}} + (let [eth-value (when value (money/bignumber value)) + eth-amount (when eth-value (money/to-number (money/wei->ether eth-value))) + token (get-transfer-token db to data)] + (cond + (and eth-amount (not (zero? eth-amount))) + {:to to + :contact (get-contact db to) + :symbol :ETH + :amount (str eth-amount) + :token (tokens/asset-for (:wallet/all-tokens db) (ethereum/chain-keyword db) :ETH)} + (not (nil? token)) + token + :else + {:to to + :contact {:address (ethereum/normalized-address to)}})))) + +(defn prepare-tx [db {{:keys [data gas gasPrice] :as tx-obj} :tx-obj :as tx}] + (merge + tx + (parse-tx-obj db tx-obj) + {:data data + :gas (when gas (money/bignumber gas)) + :gasPrice (when gasPrice (money/bignumber gasPrice))})) + +(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)] + (if message + {:db (assoc db :signing/in-progress? true + :signing/queue (drop-last queue) + :signing/tx tx + :signing/sign {:type :password + :formatted-data (if typed? (types/json->clj data) (ethereum/hex-to-utf8 data))})} + (fx/merge cofx + {:db (assoc db :signing/in-progress? true + :signing/queue (drop-last queue) + :signing/tx (prepare-tx db tx))} + #(when-not gas + {:signing/update-estimated-gas {:obj tx-obj + :success-event :signing/update-estimated-gas-success}}) + #(when-not gasPrice + {:signing/update-gas-price {:success-event :signing/update-gas-price-success}}))))) + +(fx/defn check-queue [{:keys [db] :as cofx}] + (let [{:signing/keys [in-progress? queue]} db] + (when (and (not in-progress?) (seq queue)) + (show-sign cofx)))) + +(fx/defn transaction-result + [{:keys [db] :as cofx} result tx-obj] + (let [{:keys [on-result symbol]} (get db :signing/tx)] + (fx/merge cofx + {:db (dissoc db :signing/tx :signing/in-progress? :signing/sign) + :signing/show-transaction-result nil} + (prepare-unconfirmed-transaction result tx-obj symbol) + (check-queue) + #(when on-result + {:dispatch (conj on-result result)})))) + +(fx/defn transaction-error + [{:keys [db]} {:keys [code message]}] + (let [on-error (get-in db [:signing/tx :on-error])] + (if (= code constants/send-transaction-err-decrypt) + ;;wrong password + {:db (assoc-in db [:signing/sign :error] (i18n/label :t/wrong-password))} + (merge {:db (dissoc db :signing/tx :signing/in-progress? :signing/sign) + :signing/show-transaction-error message} + (when on-error + {:dispatch (conj on-error message)}))))) + +(fx/defn sign-message-completed + {:events [:signing/sign-message-completed]} + [{:keys [db] :as cofx} result] + (let [{:keys [result error]} (types/json->clj result) + on-result (get-in db [:signing/tx :on-result])] + (if error + {:db (update db :signing/sign assoc :error (i18n/label :t/wrong-password) :in-progress? false)} + (fx/merge cofx + {:db (dissoc db :signing/tx :signing/in-progress? :signing/sign)} + (check-queue) + #(if on-result + {:dispatch (conj on-result result)}))))) + +(fx/defn transaction-completed + {:events [:signing/transaction-completed] + :interceptors [(re-frame/inject-cofx :random-id-generator)]} + [cofx response tx-obj] + (let [cofx-in-progress-false (assoc-in cofx [:db :signing/sign :in-progress?] false) + {:keys [result error]} (types/json->clj response)] + (if error + (transaction-error cofx-in-progress-false error) + (transaction-result cofx-in-progress-false result tx-obj)))) + +(fx/defn discard + "Discrad transactrion signing" + {:events [:signing.ui/cancel-is-pressed]} + [{:keys [db] :as cofx}] + (let [{:keys [on-error]} (get-in db [:signing/tx])] + (fx/merge cofx + {:db (dissoc db :signing/tx :signing/in-progress? :signing/sign)} + (check-queue) + #(when on-error + {:dispatch (conj on-error "transaction was cancelled by user")})))) + +(fx/defn sign [{:keys [db] :as cofx} tx] + "Signing transaction or message, shows signing sheet + tx + {:tx-obj - transaction object to send https://github.com/ethereum/wiki/wiki/JavaScript-API#parameters-25 + :message {:address :data :typed? } - message data to sign + :on-result - re-frame event vector + :on-error - re-frame event vector}" + (fx/merge cofx + {:db (update db :signing/queue conj tx)} + (check-queue))) + +(fx/defn eth-transaction-call + "Prepares tx-obj for contract call and show signing sheet" + [cofx {:keys [contract method params on-result on-error]}] + (sign cofx {:tx-obj {:to contract + :data (abi-spec/encode method params)} + :on-result on-result + :on-error on-error})) \ No newline at end of file diff --git a/src/status_im/signing/gas.cljs b/src/status_im/signing/gas.cljs new file mode 100644 index 000000000000..28933d58db8f --- /dev/null +++ b/src/status_im/signing/gas.cljs @@ -0,0 +1,100 @@ +(ns status-im.signing.gas + (:require [status-im.utils.money :as money] + [status-im.utils.fx :as fx] + [status-im.i18n :as i18n] + [status-im.ui.components.bottom-sheet.core :as bottom-sheet] + [re-frame.core :as re-frame] + [status-im.ethereum.json-rpc :as json-rpc])) + +(def min-gas-price-wei (money/bignumber 1)) + +(defmulti get-error-label-key (fn [type _] type)) + +(defmethod get-error-label-key :gasPrice [_ value] + (cond + (not value) :t/invalid-number + (.lt (money/->wei :gwei value) min-gas-price-wei) :t/wallet-send-min-wei + (-> (money/->wei :gwei value) .decimalPlaces pos?) :t/invalid-number)) + +(defmethod get-error-label-key :default [_ value] + (when (or (not value) + (<= value 0)) + :t/invalid-number)) + +(defn calculate-max-fee + [gas gasPrice] + (if (and gas gasPrice) + (money/to-fixed (money/wei->ether (.times gas gasPrice))) + "0")) + +(defn edit-max-fee [edit] + (let [gasPrice (get-in edit [:gasPrice :value-number]) + gas (get-in edit [:gas :value-number])] + (assoc edit :max-fee (calculate-max-fee gas gasPrice)))) + +(defn build-edit [edit-value key value] + "Takes the previous edit, either :gas or :gas-price and a value as string. + Wei for gas, and gwei for gas price. + Validates them and sets max fee" + (let [bn-value (money/bignumber value) + error-label-key (get-error-label-key key bn-value) + data (if error-label-key + {:value value + :max-fee 0 + :error (i18n/label error-label-key)} + {:value value + :value-number (if (= :gasPrice key) + (money/->wei :gwei bn-value) + bn-value)})] + (-> edit-value + (assoc key data) + edit-max-fee))) + +(fx/defn edit-value + {:events [:signing.edit-fee.ui/edit-value]} + [{:keys [db]} key value] + {:db (update db :signing/edit-fee build-edit key value)}) + +(fx/defn update-estimated-gas-success + {:events [:signing/update-estimated-gas-success]} + [{db :db} gas] + {:db (assoc-in db [:signing/tx :gas] gas)}) + +(fx/defn update-gas-price-success + {:events [:signing/update-gas-price-success]} + [{db :db} price] + {:db (assoc-in db [:signing/tx :gasPrice] price)}) + +(fx/defn open-fee-sheet + {:events [:signing.ui/open-fee-sheet]} + [{{:signing/keys [tx] :as db} :db :as cofx} sheet-opts] + (let [{:keys [gas gasPrice]} tx + edit-fee (-> {} + (build-edit :gas (money/to-fixed gas)) + (build-edit :gasPrice (money/to-fixed (money/wei-> :gwei gasPrice))))] + (fx/merge cofx + {:db (assoc db :signing/edit-fee edit-fee)} + (bottom-sheet/show-bottom-sheet {:view sheet-opts})))) + +(fx/defn submit-fee + {:events [:signing.edit-fee.ui/submit]} + [{{:signing/keys [edit-fee] :as db} :db :as cofx}] + (let [{:keys [gas gasPrice]} edit-fee] + (fx/merge cofx + {:db (update db :signing/tx assoc :gas (:value-number gas) :gasPrice (:value-number gasPrice))} + (bottom-sheet/hide-bottom-sheet)))) + +(re-frame/reg-fx + :signing/update-gas-price + (fn [{:keys [success-event edit?]}] + (json-rpc/call + {:method "eth_gasPrice" + :on-success #(re-frame/dispatch [success-event % edit?])}))) + +(re-frame/reg-fx + :signing/update-estimated-gas + (fn [{:keys [obj success-event]}] + (json-rpc/call + {:method "eth_estimateGas" + :params [obj] + :on-success #(re-frame/dispatch [success-event %])}))) \ No newline at end of file diff --git a/src/status_im/stickers/core.cljs b/src/status_im/stickers/core.cljs index 828cb20ddb6c..ad4b9bf33271 100644 --- a/src/status_im/stickers/core.cljs +++ b/src/status_im/stickers/core.cljs @@ -11,7 +11,7 @@ [status-im.utils.fx :as fx] [status-im.utils.multihash :as multihash] [status-im.utils.utils :as utils] - [status-im.wallet.core :as wallet])) + [status-im.signing.core :as signing])) (defn pack-data-callback [id open?] @@ -142,17 +142,15 @@ (fx/defn approve-pack [{db :db :as cofx} pack-id price] (let [address (ethereum/current-address db) - chain (ethereum/chain-keyword db) stickers-contract (contracts/get-address db :status/stickers) snt-contract (contracts/get-address db :status/snt)] - (wallet/eth-transaction-call + (signing/eth-transaction-call cofx {:contract snt-contract :method "approveAndCall(address,uint256,bytes)" :params [stickers-contract price - (abi-spec/encode "buyToken(uint256,address)" - [pack-id address])] + (abi-spec/encode "buyToken(uint256,address)" [pack-id address])] :on-result [:stickers/pending-pack pack-id]}))) (fx/defn pending-pack @@ -163,7 +161,6 @@ (fx/merge cofx {:db (update db :stickers/packs-pending conj id) :stickers/owned-packs-fx [contract address]} - (navigation/navigate-to-clean :wallet-transaction-sent-modal {}) #(when (zero? (count (:stickers/packs-pending db))) {:stickers/set-pending-timout-fx nil}))))) diff --git a/src/status_im/subs.cljs b/src/status_im/subs.cljs index c49ecd5843e5..0a9dfc7e9f9c 100644 --- a/src/status_im/subs.cljs +++ b/src/status_im/subs.cljs @@ -40,6 +40,7 @@ [status-im.utils.universal-links.core :as links] [status-im.wallet.core :as wallet] [status-im.wallet.db :as wallet.db] + [status-im.signing.gas :as signing.gas] status-im.ui.screens.hardwallet.connect.subs status-im.ui.screens.hardwallet.settings.subs status-im.ui.screens.hardwallet.pin.subs @@ -163,6 +164,11 @@ ;;ethereum (reg-root-key-sub :ethereum/current-block :ethereum/current-block) +;;signing +(reg-root-key-sub :signing/tx :signing/tx) +(reg-root-key-sub :signing/sign :signing/sign) +(reg-root-key-sub :signing/edit-fee :signing/edit-fee) + ;;GENERAL ============================================================================================================== (re-frame/reg-sub @@ -1303,10 +1309,10 @@ (:send-transaction wallet))) (re-frame/reg-sub - :wallet.send/advanced? + :wallet.send/symbol :<- [::send-transaction] (fn [send-transaction] - (:advanced? send-transaction))) + (:symbol send-transaction))) (re-frame/reg-sub :wallet.send/camera-flashlight @@ -1314,41 +1320,6 @@ (fn [send-transaction] (:camera-flashlight send-transaction))) -(re-frame/reg-sub - :wallet.send/wrong-password? - :<- [::send-transaction] - (fn [send-transaction] - (:wrong-password? send-transaction))) - -(re-frame/reg-sub - :wallet.send/sign-password-enabled? - :<- [::send-transaction] - (fn [{:keys [password]}] - (and (not (nil? password)) (not= password "")))) - -(defn edit-or-transaction-data - "Set up edit data structure, defaulting to transaction when not available" - [transaction edit] - (cond-> edit - (not (get-in edit [:gas-price :value])) - (wallet/build-edit - :gas-price - (money/to-fixed (money/wei-> :gwei (:gas-price transaction)))) - - (not (get-in edit [:gas :value])) - (wallet/build-edit - :gas - (money/to-fixed (:gas transaction))))) - -(re-frame/reg-sub - :wallet/edit - :<- [::send-transaction] - :<- [:wallet] - (fn [[send-transaction {:keys [edit]}]] - (edit-or-transaction-data - send-transaction - edit))) - (defn check-sufficient-funds [{:keys [sufficient-funds?] :as transaction} balance symbol amount] (cond-> transaction @@ -1377,17 +1348,9 @@ :<- [:balance] (fn [[{:keys [amount symbol] :as transaction} balance]] (-> transaction - (wallet/add-max-fee) (check-sufficient-funds balance symbol amount) (check-sufficient-gas balance symbol amount)))) -(re-frame/reg-sub - :wallet.send/signing-phrase-with-padding - :<- [:account/account] - (fn [{:keys [signing-phrase]}] - (when signing-phrase - (clojure.string/replace signing-phrase #" " " ")))) - (re-frame/reg-sub :wallet/settings :<- [:wallet] @@ -1868,3 +1831,68 @@ (or (string/blank? screen-snt-amount) (#{"0" "0.0" "0.00"} screen-snt-amount) (string/ends-with? screen-snt-amount "."))))))))) + +;;SIGNING ============================================================================================================= + +(re-frame/reg-sub + :signing/fee + :<- [:signing/tx] + (fn [{:keys [gas gasPrice]}] + (signing.gas/calculate-max-fee gas gasPrice))) + +(re-frame/reg-sub + :signing/phrase + :<- [:account/account] + (fn [{:keys [signing-phrase]}] + signing-phrase)) + +(defn- too-precise-amount? + "Checks if number has any extra digit beyond the allowed number of decimals. + It does so by checking the number against its rounded value." + [amount decimals] + (let [bn (money/bignumber amount)] + (not (.eq bn (.round bn decimals))))) + +(defn get-amount-error [amount decimals] + (when (and (not (empty? amount)) decimals) + (let [normalized-amount (money/normalize amount) + value (money/bignumber normalized-amount)] + (cond + (not (money/valid? value)) + {:amount-error (i18n/label :t/validation-amount-invalid-number)} + + (too-precise-amount? normalized-amount decimals) + {:amount-error (i18n/label :t/validation-amount-is-too-precise {:decimals decimals})} + + :else nil)))) + +(defn get-sufficient-funds-error + [balance symbol amount] + (when-not (money/sufficient-funds? amount (get balance symbol)) + {:amount-error (i18n/label :t/wallet-insufficient-funds)})) + +(defn get-sufficient-gas-error + [balance symbol amount gas gasPrice] + (if (and gas gasPrice) + (let [fee (.times gas gasPrice) + available-ether (money/bignumber (get balance :ETH 0)) + available-for-gas (if (= :ETH symbol) + (.minus available-ether (money/bignumber amount)) + available-ether)] + (when-not (money/sufficient-funds? fee (money/bignumber available-for-gas)) + {:gas-error (i18n/label :t/wallet-insufficient-gas)})) + {:gas-error (i18n/label :t/invalid-number)})) + +(re-frame/reg-sub + :signing/amount-errors + :<- [:signing/tx] + :<- [:balance] + (fn [[{:keys [amount token gas gasPrice]} balance]] + (if (and amount token) + (let [amount-bn (money/formatted->internal (money/bignumber amount) (:symbol token) (:decimals token)) + amount-error (or (get-amount-error amount (:decimals token)) + (get-sufficient-funds-error balance (:symbol token) amount-bn))] + (if amount-error + amount-error + (get-sufficient-gas-error balance (:symbol token) amount-bn gas gasPrice))) + (get-sufficient-gas-error balance nil nil gas gasPrice)))) diff --git a/src/status_im/tribute_to_talk/core.cljs b/src/status_im/tribute_to_talk/core.cljs index 5da77225c3ff..6cc94e1b77d3 100644 --- a/src/status_im/tribute_to_talk/core.cljs +++ b/src/status_im/tribute_to_talk/core.cljs @@ -4,20 +4,17 @@ [re-frame.core :as re-frame] [status-im.accounts.update.core :as accounts.update] [status-im.contact.core :as contact] - [status-im.contact.db :as contact.db] [status-im.ethereum.contracts :as contracts] [status-im.ethereum.core :as ethereum] [status-im.ethereum.json-rpc :as json-rpc] - [status-im.ethereum.tokens :as tokens] [status-im.ethereum.transactions.core :as transactions] [status-im.tribute-to-talk.db :as tribute-to-talk.db] [status-im.tribute-to-talk.whitelist :as whitelist] [status-im.ui.screens.navigation :as navigation] [status-im.utils.fx :as fx] [status-im.utils.money :as money] - [status-im.wallet.core :as wallet] - [status-im.wallet.db :as wallet.db] - [taoensso.timbre :as log])) + [taoensso.timbre :as log] + [status-im.signing.core :as signing])) (defn add-transaction-hash [message db] @@ -88,13 +85,13 @@ (fx/defn set-tribute-signing-flow [{:keys [db] :as cofx} tribute] (if-let [contract (contracts/get-address db :status/tribute-to-talk)] - (wallet/eth-transaction-call + (signing/eth-transaction-call cofx - {:contract contract - :method "setTribute(uint256)" - :params [tribute] + {:contract contract + :method "setTribute(uint256)" + :params [tribute] :on-result [:tribute-to-talk.callback/set-tribute-transaction-sent] - :on-error [:tribute-to-talk.callback/set-tribute-transaction-failed]}) + :on-error [:tribute-to-talk.callback/set-tribute-transaction-failed]}) {:db (assoc-in db [:navigation/screen-params :tribute-to-talk :state] :transaction-failed)})) @@ -263,28 +260,15 @@ (fx/defn pay-tribute {:events [:tribute-to-talk.ui/on-pay-to-chat-pressed]} [{:keys [db] :as cofx} public-key] - (let [{:keys [name address public-key tribute-to-talk] :as recipient-contact} + (let [{:keys [address public-key tribute-to-talk]} (get-in db [:contacts/contacts public-key]) - {:keys [snt-amount]} tribute-to-talk - symbol (ethereum/snt-symbol db) - wallet-balance (get-in db [:wallet :balance symbol] - (money/bignumber 0)) - amount-text (str (tribute-to-talk.db/from-wei snt-amount))] - (wallet/eth-transaction-call + {:keys [snt-amount]} tribute-to-talk] + (signing/eth-transaction-call cofx - {:contract (contracts/get-address db :status/snt) - :method "transfer(address,uint256)" - :params [address snt-amount] - :details {:to-name name - :public-key public-key - :from-chat? true - :asset symbol - :amount-text amount-text - :sufficient-funds? - (money/sufficient-funds? snt-amount wallet-balance) - :send-transaction-message? true} - :on-result [:tribute-to-talk.callback/pay-tribute-transaction-sent - public-key]}))) + {:contract (contracts/get-address db :status/snt) + :method "transfer(address,uint256)" + :params [address snt-amount] + :on-result [:tribute-to-talk.callback/pay-tribute-transaction-sent public-key]}))) (defn tribute-transaction-trigger [db {:keys [block error?]}] @@ -306,12 +290,11 @@ (fx/defn on-pay-tribute-transaction-sent {:events [:tribute-to-talk.callback/pay-tribute-transaction-sent]} - [{:keys [db] :as cofx} public-key id transaction-hash method] + [{:keys [db] :as cofx} public-key transaction-hash] (fx/merge cofx {:db (assoc-in db [:contacts/contacts public-key :tribute-to-talk :transaction-hash] transaction-hash)} - (navigation/navigate-to-clean :wallet-transaction-sent-modal {}) (transactions/watch-transaction transaction-hash {:trigger-fn @@ -340,14 +323,13 @@ (fx/defn on-set-tribute-transaction-sent {:events [:tribute-to-talk.callback/set-tribute-transaction-sent]} - [{:keys [db] :as cofx} id transaction-hash method] + [{:keys [db] :as cofx} transaction-hash] (let [{:keys [snt-amount message]} (get-in db [:navigation/screen-params :tribute-to-talk])] (fx/merge cofx {:db (assoc-in db [:navigation/screen-params :tribute-to-talk :state] :pending)} - (navigation/navigate-to-clean :wallet-transaction-sent-modal {}) (update-settings {:update {:transaction transaction-hash :snt-amount snt-amount :message message}}) diff --git a/src/status_im/ui/components/bottom_sheet/view.cljs b/src/status_im/ui/components/bottom_sheet/view.cljs index 1adb1e4e4332..943eee66c33e 100644 --- a/src/status_im/ui/components/bottom_sheet/view.cljs +++ b/src/status_im/ui/components/bottom_sheet/view.cljs @@ -102,7 +102,7 @@ height content on-cancel] :or {on-cancel #(re-frame/dispatch [:bottom-sheet/hide])} :as opts}] - [react/view + [react/keyboard-avoiding-view (merge (pan-handlers (swipe-pan-responder opts)) {:style styles/container}) diff --git a/src/status_im/ui/components/button/styles.cljs b/src/status_im/ui/components/button/styles.cljs index f4d28f889301..6e59b800557f 100644 --- a/src/status_im/ui/components/button/styles.cljs +++ b/src/status_im/ui/components/button/styles.cljs @@ -53,10 +53,10 @@ ;; See https://github.com/facebook/react-native/issues/13760 :overflow :hidden}}) -(def primary-button +(defn primary-button [disabled?] (merge button-borders - {:background-color colors/blue})) + {:background-color (if disabled? (colors/alpha colors/gray 0.1) colors/blue)})) (def primary-button-text {:color colors/white}) diff --git a/src/status_im/ui/components/button/view.cljs b/src/status_im/ui/components/button/view.cljs index d134354e2ab5..5ad192b35333 100644 --- a/src/status_im/ui/components/button/view.cljs +++ b/src/status_im/ui/components/button/view.cljs @@ -18,13 +18,13 @@ [react/text {:style (merge styles/button-text text-style (when disabled? - {:opacity 0.4}))} + {:color colors/gray}))} label] icon]]) -(defn primary-button [{:keys [style text-style] :as m} label] +(defn primary-button [{:keys [style text-style disabled?] :as m} label] [button (assoc m - :style (merge styles/primary-button style) + :style (merge (styles/primary-button disabled?) style) :text-style (merge styles/primary-button-text text-style)) label]) diff --git a/src/status_im/ui/components/list_item/styles.cljs b/src/status_im/ui/components/list_item/styles.cljs index ef915109805b..26678204b2d2 100644 --- a/src/status_im/ui/components/list_item/styles.cljs +++ b/src/status_im/ui/components/list_item/styles.cljs @@ -19,8 +19,7 @@ :color colors/gray}) (def accessory-text - {:color colors/gray - :margin-right 8}) + {:color colors/gray}) (defn radius [size] (/ size 2)) @@ -28,3 +27,8 @@ {:border-radius (radius size) :width size :height size}) + +(def error + {:bottom-value 0 + :color colors/red-light + :font-size 12}) \ No newline at end of file diff --git a/src/status_im/ui/components/list_item/views.cljs b/src/status_im/ui/components/list_item/views.cljs index 97467a05778d..048ec9972d19 100644 --- a/src/status_im/ui/components/list_item/views.cljs +++ b/src/status_im/ui/components/list_item/views.cljs @@ -3,7 +3,8 @@ [status-im.ui.components.icons.vector-icons :as icons] [status-im.ui.components.react :as react] [status-im.ui.components.list-item.styles :as styles] - [status-im.utils.image :as utils.image])) + [status-im.utils.image :as utils.image] + [status-im.ui.components.tooltip.views :as tooltip])) ; type - optional :default , :small @@ -11,7 +12,7 @@ ; theme - optional :default, :wallet -(defn list-item [{:keys [title subtitle accessories image image-path type theme on-press] :or {type :default theme :default}}] +(defn list-item [{:keys [title subtitle accessories image image-path type theme on-press error] :or {type :default theme :default}}] (let [small? (= :small type)] [react/touchable-highlight {:on-press on-press :disabled (not on-press)} [react/view {:style (styles/container small?)} @@ -26,7 +27,7 @@ [react/image {:source (utils.image/source image-path) :style (styles/photo 40)}]]) ;;Title - [react/view {:style {:margin-left 16 :flex 1}} + [react/view {:style {:margin-left 16 :margin-right 16}} [react/text {:style (styles/title small? subtitle) :number-of-lines 1 :ellipsize-mode :tail} @@ -38,15 +39,26 @@ :ellipsize-mode :tail} subtitle])] ;;Accessories + [react/view {:flex 1}] (for [accessory accessories] (with-meta (cond - (string? accessory) - [react/text {:style styles/accessory-text} - accessory] (= :chevron accessory) - [icons/icon :main-icons/next {:color colors/gray-transparent-40}] + [react/view + [icons/icon :main-icons/next {:color colors/gray-transparent-40}]] (= :check accessory) - [icons/icon :main-icons/check {:color colors/gray}] - :else accessory) - {:key accessory}))]])) + [react/view + [icons/icon :main-icons/check {:color colors/gray}]] + :else + [react/view {:padding-right 8 :flex-shrink 1} + (cond + (string? accessory) + [react/text {:style styles/accessory-text} + accessory] + (vector? accessory) + accessory + :else + [accessory])]) + {:key accessory})) + (when error + [tooltip/tooltip error styles/error])]])) diff --git a/src/status_im/ui/components/status_bar/view.cljs b/src/status_im/ui/components/status_bar/view.cljs index 233a885a6da0..8da4fee8c107 100644 --- a/src/status_im/ui/components/status_bar/view.cljs +++ b/src/status_im/ui/components/status_bar/view.cljs @@ -74,8 +74,6 @@ :wallet-add-custom-token {:type :wallet} :wallet-sign-message-modal {:type :modal-wallet} :wallet-settings-hook {:type :wallet} - :wallet-transaction-sent {:type :transparent} - :wallet-transaction-sent-modal {:type :modal-wallet} :wallet-transactions-filter {:type :modal-main}} view-id)) diff --git a/src/status_im/ui/screens/db.cljs b/src/status_im/ui/screens/db.cljs index be1d02cd5cff..9476c8620d4d 100644 --- a/src/status_im/ui/screens/db.cljs +++ b/src/status_im/ui/screens/db.cljs @@ -204,6 +204,12 @@ (spec/def :extensions/profile (spec/nilable any?)) (spec/def :wallet/custom-token-screen (spec/nilable map?)) +(spec/def :signing/in-progress? (spec/nilable boolean?)) +(spec/def :signing/queue (spec/nilable any?)) +(spec/def :signing/tx (spec/nilable map?)) +(spec/def :signing/sign (spec/nilable map?)) +(spec/def :signing/edit-fee (spec/nilable map?)) + (spec/def ::db (spec/keys :opt [:contacts/contacts :contacts/new-identity :contacts/new-identity-error @@ -279,7 +285,12 @@ :bottom-sheet/view :bottom-sheet/options :extensions/profile - :wallet/custom-token-screen] + :wallet/custom-token-screen + :signing/in-progress? + :signing/queue + :signing/sign + :signing/tx + :signing/edit-fee] :opt-un [::modal ::was-modal? ::rpc-url diff --git a/src/status_im/ui/screens/events.cljs b/src/status_im/ui/screens/events.cljs index 4239d3190d1e..d80afd0569fb 100644 --- a/src/status_im/ui/screens/events.cljs +++ b/src/status_im/ui/screens/events.cljs @@ -232,7 +232,6 @@ :hardwallet-connect-settings (hardwallet/hardwallet-connect-screen-did-load %) :hardwallet-connect-modal (hardwallet/hardwallet-connect-screen-did-load %) :hardwallet-authentication-method (hardwallet/authentication-method-screen-did-load %) - :wallet-send-transaction (wallet/send-transaction-screen-did-load %) :accounts (hardwallet/accounts-screen-did-load %) :chat (mark-messages-seen %) nil)))) diff --git a/src/status_im/ui/screens/routing/modals.cljs b/src/status_im/ui/screens/routing/modals.cljs index b16ed621ca33..3c44a5919cbd 100644 --- a/src/status_im/ui/screens/routing/modals.cljs +++ b/src/status_im/ui/screens/routing/modals.cljs @@ -2,17 +2,11 @@ (def modal-screens [{:name :wallet-send-modal-stack - :screens [:wallet-send-transaction-modal - :wallet-transaction-sent-modal - :wallet-transaction-fee - :hardwallet-connect-modal + :screens [:hardwallet-connect-modal :enter-pin-modal] :config {:initialRouteName :wallet-send-transaction-modal}} {:name :wallet-send-modal-stack-with-onboarding :screens [:wallet-onboarding-setup-modal - :wallet-send-transaction-modal - :wallet-transaction-sent-modal - :wallet-transaction-fee :hardwallet-connect-modal :enter-pin-modal] :config {:initialRouteName :wallet-onboarding-setup-modal}} @@ -20,11 +14,9 @@ :show-extension-modal :stickers-pack-modal :tribute-learn-more - :wallet-sign-message-modal :enter-pin-modal :hardwallet-connect-modal :selection-modal-screen - :wallet-transaction-fee :wallet-transactions-filter :profile-qr-viewer :welcome]) diff --git a/src/status_im/ui/screens/routing/screens.cljs b/src/status_im/ui/screens/routing/screens.cljs index b7cbc69c71b1..355f955c6320 100644 --- a/src/status_im/ui/screens/routing/screens.cljs +++ b/src/status_im/ui/screens/routing/screens.cljs @@ -70,13 +70,6 @@ [status-im.ui.screens.wallet.request.views :as request] [status-im.ui.screens.wallet.send.views :as send] [status-im.ui.screens.wallet.settings.views :as wallet-settings] - [status-im.ui.screens.wallet.sign-message.views :as sign-message] - [status-im.ui.screens.wallet.transaction-fee.views - :as - wallet.transaction-fee] - [status-im.ui.screens.wallet.transaction-sent.views - :as - transaction-sent] [status-im.ui.screens.wallet.transactions.views :as wallet-transactions] [status-im.ui.screens.wallet.custom-tokens.views :as custom-tokens])) @@ -120,19 +113,13 @@ :tribute-learn-more [:modal tr-to-talk/learn-more] :chat-modal [:modal chat/chat-modal] :show-extension-modal [:modal extensions.add/show-extension-modal] - :wallet-send-transaction-modal [:modal send/send-transaction-modal] - :wallet-transaction-sent-modal [:modal transaction-sent/transaction-sent-modal] - :wallet-transaction-fee [:modal wallet.transaction-fee/transaction-fee] :wallet-onboarding-setup-modal [:modal wallet.onboarding/modal] - :wallet-sign-message-modal [:modal sign-message/sign-message-modal] :wallet wallet.main/wallet :collectibles-list collectibles/collectibles-list :wallet-onboarding-setup wallet.onboarding/screen - :wallet-send-transaction-chat send/send-transaction :contact-code wallet.components/contact-code :wallet-send-transaction send/send-transaction :recent-recipients wallet.components/recent-recipients - :wallet-transaction-sent transaction-sent/transaction-sent :recipient-qr-code wallet.components/recipient-qr-code :wallet-send-assets wallet.components/send-assets :wallet-request-transaction request/request-transaction diff --git a/src/status_im/ui/screens/routing/wallet_stack.cljs b/src/status_im/ui/screens/routing/wallet_stack.cljs index 27b7dda31a81..35d86d875e6f 100644 --- a/src/status_im/ui/screens/routing/wallet_stack.cljs +++ b/src/status_im/ui/screens/routing/wallet_stack.cljs @@ -5,12 +5,10 @@ :screens [:wallet :collectibles-list :wallet-onboarding-setup - :wallet-send-transaction-chat :contact-code {:name :send-transaction-stack :screens [:wallet-send-transaction :recent-recipients - :wallet-transaction-sent :enter-pin-sign :hardwallet-connect-sign :recipient-qr-code diff --git a/src/status_im/ui/screens/signing/sheets.cljs b/src/status_im/ui/screens/signing/sheets.cljs new file mode 100644 index 000000000000..93f353e21a76 --- /dev/null +++ b/src/status_im/ui/screens/signing/sheets.cljs @@ -0,0 +1,51 @@ +(ns status-im.ui.screens.signing.sheets + (:require-macros [status-im.utils.views :as views]) + (:require [status-im.ui.components.react :as react] + [re-frame.core :as re-frame] + [status-im.ui.components.text-input.view :as text-input] + [status-im.i18n :as i18n] + [status-im.ui.components.button.view :as button] + [status-im.ui.components.colors :as colors])) + +(views/defview fee-bottom-sheet [fee-display-symbol] + (views/letsubs [{gas-edit :gas gas-price-edit :gasPrice max-fee :max-fee} [:signing/edit-fee]] + [react/view + [react/view {:style {:margin-horizontal 16 :margin-vertical 8}} + [react/text {:style {:typography :title-bold}} (i18n/label :t/network-fee)] + [react/view {:style {:flex-direction :row :margin-top 8}} + [react/view {:flex 1} + [text-input/text-input-with-label + {:on-change-text #(re-frame/dispatch [:signing.edit-fee.ui/edit-value :gas %]) + :label (i18n/label :t/gas-limit) + :error (:error gas-edit) + :default-value (:value gas-edit) + :keyboard-type :numeric + :auto-capitalize :none + :placeholder "0.000" + :auto-focus false}]] + [react/view {:flex 1 :margin-left 33} + [text-input/text-input-with-label + {:label (i18n/label :t/gas-price) + :on-change-text #(re-frame/dispatch [:signing.edit-fee.ui/edit-value :gasPrice %]) + :error (:error gas-price-edit) + :default-value (:value gas-price-edit) + :keyboard-type :numeric + :auto-capitalize :none + :placeholder "0.000" + :auto-focus false}]] + [react/view {:margin-top 58 :margin-left 10} + [react/text (i18n/label :t/gwei)]]] + [react/view {:margin-vertical 28 :align-items :center} + [react/text {:style {:color colors/gray}} (i18n/label :t/wallet-transaction-total-fee)] + [react/view {:height 8}] + [react/nested-text {:style {:font-size 17}} + max-fee " " + [{:style {:color colors/gray}} fee-display-symbol]]]] + [react/view {:height 1 :background-color colors/gray-lighter}] + [react/view {:margin-horizontal 16 :align-items :center :justify-content :space-between :flex-direction :row :margin-top 6} + [button/secondary-button {:on-press #(re-frame/dispatch [:bottom-sheet/hide-sheet]) + :style {:background-color nil}} + (i18n/label :t/cancel)] + [button/secondary-button {:on-press #(re-frame/dispatch [:signing.edit-fee.ui/submit]) + :disabled? (or (:error gas-edit) (:error gas-price-edit))} + (i18n/label :t/update)]]])) \ No newline at end of file diff --git a/src/status_im/ui/screens/signing/styles.cljs b/src/status_im/ui/screens/signing/styles.cljs new file mode 100644 index 000000000000..9bc006572484 --- /dev/null +++ b/src/status_im/ui/screens/signing/styles.cljs @@ -0,0 +1,40 @@ +(ns status-im.ui.screens.signing.styles + (:require [status-im.ui.components.colors :as colors])) + +(def header + {:flex-direction :row + :align-items :center + :justify-content :space-between + :padding-top 16 + :padding-left 16 + :padding-right 24 + :margin-bottom 11}) + +(def message-header + {:flex-direction :row + :align-items :center + :justify-content :space-between + :padding-top 20 + :padding-left 16 + :padding-right 24 + :margin-bottom 19}) + +(def message + {:background-color :white + :border-top-right-radius 16 + :border-top-left-radius 16 + :padding-bottom 40}) + +(def message-border + {:margin-horizontal 24 + :height 96 + :border-radius 8 + :border-color colors/black-transparent + :border-width 1 + :padding 8}) + +(def sheet + {:background-color :white + :border-top-right-radius 16 + :border-top-left-radius 16 + :padding-bottom 40}) \ No newline at end of file diff --git a/src/status_im/ui/screens/signing/views.cljs b/src/status_im/ui/screens/signing/views.cljs new file mode 100644 index 000000000000..e32664e7f38e --- /dev/null +++ b/src/status_im/ui/screens/signing/views.cljs @@ -0,0 +1,197 @@ +(ns status-im.ui.screens.signing.views + (:require-macros [status-im.utils.views :as views]) + (:require [status-im.ui.components.react :as react] + [re-frame.core :as re-frame] + [status-im.ui.components.colors :as colors] + [status-im.ui.components.animation :as anim] + [reagent.core :as reagent] + [status-im.ui.components.list-item.views :as list-item] + [status-im.ui.components.button.view :as button] + [status-im.ui.screens.wallet.utils :as wallet.utils] + [status-im.ui.components.list.views :as list] + [status-im.ui.components.chat-icon.screen :as chat-icon] + [status-im.ui.components.icons.vector-icons :as icons] + [status-im.ui.components.text-input.view :as text-input] + [status-im.i18n :as i18n] + [status-im.utils.security :as security] + [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])) + +(defn hide-panel-anim + [bottom-anim-value alpha-value window-height] + (anim/start + (anim/parallel + [(anim/spring bottom-anim-value {:toValue window-height}) + (anim/timing alpha-value {:toValue 0 + :duration 500})]))) + +(defn show-panel-anim + [bottom-anim-value alpha-value] + (anim/start + (anim/parallel + [(anim/spring bottom-anim-value {:toValue -40}) + (anim/timing alpha-value {:toValue 0.4 + :duration 500})]))) + +(defn separator [] + [react/view {:height 1 :background-color colors/gray-lighter}]) + +(defn acc-text [txt1 txt2] + [react/nested-text nil + txt1 " " + [{:style {:color colors/gray}} txt2]]) + +(defn contact-item [contact] + [list-item/list-item {:type :small + :title (i18n/label :t/to) + :accessories [[react/text {:ellipsize-mode :middle :number-of-lines 1 :style {:flex-wrap :wrap}} + (or (:name contact) (:address contact))]]}]) + +(defn token-item [{:keys [icon color] :as token} display-symbol] + (when token + [react/view + [list-item/list-item + {:type :small :title (i18n/label :t/wallet-asset) + :accessories [[acc-text (:name token) display-symbol] + (if icon + [list/item-image (assoc icon + :style {:background-color colors/gray-lighter + :border-radius 16} + :image-style {:width 32 :height 32})] + [chat-icon/custom-icon-view-list (:name token) color 32])]}] + [separator]])) + +(defn header [{:keys [in-progress?] :as sign} {:keys [contact amount token] :as tx} display-symbol fee fee-display-symbol] + [react/view styles/header + (when sign + [react/touchable-highlight (when-not in-progress? {:on-press #(re-frame/dispatch [:set :signing/sign nil])}) + [react/view {:padding-right 16} + [icons/icon :main-icons/back]]]) + [react/view {:flex 1} + (if amount + [react/text {:style {:typography :title-bold}} (str (i18n/label :t/sending) " " amount " " display-symbol)] + [react/text {:style {:typography :title-bold}} (i18n/label :t/contract-interaction)]) + (if sign + [react/nested-text {:style {:color colors/gray} + :ellipsize-mode :middle + :number-of-lines 1} (i18n/label :t/to) " " + [{:style {:color colors/black}} (or (:name contact) (:address contact))]] + [react/text {:style {:margin-top 6 :color colors/gray}} + (str fee " " fee-display-symbol " " (string/lower-case (i18n/label :t/network-fee)))])] + [react/touchable-highlight (when-not in-progress? {:on-press #(re-frame/dispatch [:signing.ui/cancel-is-pressed])}) + [react/text {:style {:color colors/blue}} (i18n/label :t/cancel)]]]) + +(views/defview password-view [{:keys [type error in-progress? enabled?]}] + (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]] + [text-input/text-input-with-label + {:secure-text-entry true + :placeholder (i18n/label :t/enter-password) + :on-change-text #(re-frame/dispatch [:signing.ui/password-is-changed (security/mask-data %)]) + :accessibility-label :enter-password-input + :auto-capitalize :none + :editable (not in-progress?) + :error error + :container {:margin-top 24 :margin-bottom 32 :margin-horizontal 16}}] + [react/view {:align-items :center :height 44} + (if in-progress? + [react/activity-indicator {:animating true + :size :large}] + [button/primary-button {:on-press #(re-frame/dispatch [:signing.ui/sign-is-pressed]) + :disabled? (not enabled?)} + (i18n/label :t/transactions-sign)])]] + [react/view]))) + +(views/defview message-sheet [] + (views/letsubs [{:keys [formatted-data] :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)] + [react/touchable-highlight {:on-press #(re-frame/dispatch [:signing.ui/cancel-is-pressed])} + [react/text {:style {:color colors/blue}} (i18n/label :t/cancel)]]] + [separator] + [react/view {:padding-top 16} + [react/view styles/message-border + [react/scroll-view + [react/text (or formatted-data "")]]] + [password-view sign]]])) + +(views/defview sheet [{:keys [contact amount token] :as tx}] + (views/letsubs [fee [:signing/fee] + sign [:signing/sign] + chain [:ethereum/chain-keyword] + {:keys [amount-error gas-error]} [:signing/amount-errors]] + (let [display-symbol (wallet.utils/display-symbol token) + fee-display-symbol (wallet.utils/display-symbol (tokens/native-currency chain))] + [react/view styles/sheet + [header sign tx display-symbol fee fee-display-symbol] + [separator] + (if sign + [react/view {:padding-top 20} + [password-view sign]] + [react/view + [contact-item contact] + [separator] + [token-item token display-symbol] + [react/view + [list-item/list-item {:type :small :title (i18n/label :t/send-request-amount) + :error amount-error + :accessories [[acc-text (if amount (str amount) "0") + (or display-symbol fee-display-symbol)]]}] + [separator]] + [list-item/list-item + {:type :small :title (i18n/label :t/network-fee) :error gas-error + :accessories [[acc-text fee fee-display-symbol] :chevron] + :on-press #(re-frame/dispatch [:signing.ui/open-fee-sheet + {:content (fn [] [sheets/fee-bottom-sheet fee-display-symbol]) + :content-height 280}])}] + [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)]]])]))) + +(views/defview signing-view [tx window-height] + (views/letsubs [bottom-anim-value (anim/create-value (- window-height)) + alpha-value (anim/create-value 0) + current-tx (reagent/atom nil) + update? (reagent/atom nil)] + {:component-will-update (fn [_ [_ tx _]] + (cond + @update? + (do (reset! update? false) + (show-panel-anim bottom-anim-value alpha-value)) + + (and @current-tx tx) + (do (reset! update? true) + (js/setTimeout #(reset! current-tx tx) 600) + (hide-panel-anim bottom-anim-value alpha-value (- window-height))) + + tx + (do (reset! current-tx tx) + (show-panel-anim bottom-anim-value alpha-value)) + + :else + (do (js/setTimeout #(reset! current-tx nil) 500) + (hide-panel-anim bottom-anim-value alpha-value (- window-height)))))} + (when @current-tx + [react/view {:position :absolute :top 0 :bottom 0 :left 0 :right 0} + [react/animated-view {:flex 1 :background-color :black :opacity alpha-value}] + [react/animated-view {:position :absolute :height window-height :left 0 :right 0 :bottom bottom-anim-value} + [react/keyboard-avoiding-view {:style {:flex 1}} + [react/view {:flex 1}] + (if (:message @current-tx) + [message-sheet] + [sheet @current-tx])]]]))) + +(views/defview signing [] + (views/letsubs [tx [:signing/tx] + {window-height :height} [:dimensions/window]] + ;;we use select-keys here because we don't want to update view if other keys in map is changed + [signing-view (when tx (select-keys tx [:contact :amount :token :message])) window-height])) \ No newline at end of file diff --git a/src/status_im/ui/screens/views.cljs b/src/status_im/ui/screens/views.cljs index b9d348d179a9..0b7b9c250fc0 100644 --- a/src/status_im/ui/screens/views.cljs +++ b/src/status_im/ui/screens/views.cljs @@ -13,7 +13,8 @@ [status-im.utils.platform :as platform] [status-im.ui.screens.mobile-network-settings.view :as mobile-network-settings] [status-im.ui.screens.home.sheet.views :as home.sheet] - [status-im.ui.screens.routing.core :as routing])) + [status-im.ui.screens.routing.core :as routing] + [status-im.ui.screens.signing.views :as signing])) (defonce rand-label (when js/goog.DEBUG (rand/id))) @@ -97,5 +98,5 @@ (navigation/navigate-to @view-id nil))) ;; see https://reactnavigation.org/docs/en/state-persistence.html#development-mode :persistenceKey (when js/goog.DEBUG rand-label)}] - [bottom-sheet]]))}))) - + [signing/signing] + [bottom-sheet]]))}))) \ No newline at end of file diff --git a/src/status_im/ui/screens/wallet/custom_tokens/views.cljs b/src/status_im/ui/screens/wallet/custom_tokens/views.cljs index 08d08997c73a..d4f789f7443b 100644 --- a/src/status_im/ui/screens/wallet/custom_tokens/views.cljs +++ b/src/status_im/ui/screens/wallet/custom_tokens/views.cljs @@ -52,6 +52,7 @@ :default-value contract :multiline true :height 78 + :auto-focus false :placeholder (i18n/label :t/specify-address)}] [react/view {:height 16}] [text-input/text-input-with-label @@ -59,6 +60,7 @@ :label (i18n/label :t/name) :default-value name :error error-name + :auto-focus false :placeholder (i18n/label :t/name-of-token)}] [react/view {:height 16}] [react/view {:style {:flex-direction :row}} @@ -68,6 +70,7 @@ :label (i18n/label :t/symbol) :error error-symbol :default-value symbol + :auto-focus false :placeholder "ABC"}]] [react/view {:flex 1 :margin-left 33} [text-input/text-input-with-label @@ -76,6 +79,7 @@ :default-value decimals :keyboard-type :number-pad :max-length 2 + :auto-focus false :placeholder "18"}]]] [react/view {:height 16}] [text-input/text-input-with-label diff --git a/src/status_im/ui/screens/wallet/navigation.cljs b/src/status_im/ui/screens/wallet/navigation.cljs index 034722475698..e0419c7f933b 100644 --- a/src/status_im/ui/screens/wallet/navigation.cljs +++ b/src/status_im/ui/screens/wallet/navigation.cljs @@ -1,38 +1,24 @@ (ns status-im.ui.screens.wallet.navigation - (:require [re-frame.core :as re-frame] - [status-im.constants :as constants] - [status-im.ethereum.core :as ethereum] + (:require [status-im.constants :as constants] [status-im.ui.screens.navigation :as navigation])) (def transaction-send-default - (let [symbol :ETH - request (atom nil)] - (fn [] - (or @request - (reset! - request - {:gas (ethereum/estimate-gas symbol) - :method constants/web3-send-transaction - :symbol symbol}))))) - -(def transaction-request-default - {:symbol :ETH}) + {:method constants/web3-send-transaction + :symbol :ETH}) (defmethod navigation/preload-data! :wallet-request-transaction [db [event]] (if (= event :navigate-back) db (-> db - (assoc-in [:wallet :request-transaction] transaction-request-default) - (assoc-in [:wallet :send-transaction] (transaction-send-default))))) + (assoc-in [:wallet :request-transaction] {:symbol :ETH}) + (assoc-in [:wallet :send-transaction] transaction-send-default)))) (defmethod navigation/preload-data! :wallet-send-transaction [db [event]] (if (= event :navigate-back) db - (do - (re-frame/dispatch [:wallet/update-gas-price]) - (assoc-in db [:wallet :send-transaction] (transaction-send-default))))) + (assoc-in db [:wallet :send-transaction] transaction-send-default))) (defmethod navigation/preload-data! :wallet-add-custom-token [db [event]] diff --git a/src/status_im/ui/screens/wallet/send/animations.cljs b/src/status_im/ui/screens/wallet/send/animations.cljs deleted file mode 100644 index ef73a5fe28f1..000000000000 --- a/src/status_im/ui/screens/wallet/send/animations.cljs +++ /dev/null @@ -1,11 +0,0 @@ -(ns status-im.ui.screens.wallet.send.animations - (:require [status-im.ui.components.animation :as animation])) - -(defn animate-sign-panel [opacity-value bottom-value] - (animation/start - (animation/parallel - [(animation/timing opacity-value {:toValue 1 - :duration 500}) - (animation/timing bottom-value {:toValue 53 - :easing (.bezier (animation/easing) 0.685, 0.000, 0.025, 1.185) - :duration 500})]))) \ No newline at end of file diff --git a/src/status_im/ui/screens/wallet/send/events.cljs b/src/status_im/ui/screens/wallet/send/events.cljs index f38229016e19..77af62b27ddf 100644 --- a/src/status_im/ui/screens/wallet/send/events.cljs +++ b/src/status_im/ui/screens/wallet/send/events.cljs @@ -1,258 +1,11 @@ (ns status-im.ui.screens.wallet.send.events - (:require [re-frame.core :as re-frame] - [status-im.chat.commands.sending :as commands-sending] - [status-im.constants :as constants] + (:require [status-im.utils.handlers :as handlers] + [status-im.utils.money :as money] + [status-im.wallet.db :as wallet.db] + [status-im.ethereum.tokens :as tokens] [status-im.ethereum.abi-spec :as abi-spec] [status-im.ethereum.core :as ethereum] - [status-im.ethereum.tokens :as tokens] - [status-im.i18n :as i18n] - [status-im.native-module.core :as status] - [status-im.transport.utils :as transport.utils] - [status-im.ui.screens.navigation :as navigation] - [status-im.utils.fx :as fx] - [status-im.utils.handlers :as handlers] - [status-im.utils.money :as money] - [status-im.utils.security :as security] - [status-im.utils.types :as types] - [status-im.utils.utils :as utils] - [status-im.wallet.core :as wallet] - [status-im.wallet.db :as wallet.db])) - -;;;; FX - -(defn- send-ethers [params on-completed masked-password] - (status/send-transaction (types/clj->json params) - (security/safe-unmask-data masked-password) - on-completed)) - -(defn- send-tokens - [all-tokens symbol chain - {:keys [from to value gas gasPrice]} on-completed masked-password] - (let [contract (:address (tokens/symbol->token all-tokens (keyword chain) symbol))] - (status/send-transaction (types/clj->json - {:to contract - :from from - :data (abi-spec/encode - "transfer(address,uint256)" - [to value]) - :gas gas - :gasPrice gasPrice}) - (security/safe-unmask-data masked-password) - on-completed))) - -(re-frame/reg-fx - ::send-transaction - (fn [[params all-tokens symbol chain on-completed masked-password]] - (if (= symbol :ETH) - (send-ethers params on-completed masked-password) - (send-tokens all-tokens symbol chain params on-completed masked-password)))) - -(re-frame/reg-fx - ::sign-message - (fn [{:keys [params on-completed]}] - (status/sign-message (types/clj->json params) - on-completed))) - -(re-frame/reg-fx - ::sign-typed-data - (fn [{:keys [data on-completed password]}] - (status/sign-typed-data data (security/safe-unmask-data password) on-completed))) - -(re-frame/reg-fx - :wallet/show-transaction-error - (fn [message] - ;; (andrey) we need this timeout because modal window conflicts with alert - (utils/set-timeout #(utils/show-popup (i18n/label :t/transaction-failed) message) 1000))) - -;;;; Handlers - -;; SEND TRANSACTION -(handlers/register-handler-fx - :wallet/send-transaction - (fn [{{:keys [chain] :as db} :db} _] - (let [{:keys [password symbol in-progress?] :as transaction} - (get-in db [:wallet :send-transaction]) - all-tokens (:wallet/all-tokens db) - from (ethereum/current-address db)] - (when-not in-progress? - {:db (-> db - (assoc-in [:wallet :send-transaction :wrong-password?] false) - (assoc-in [:wallet :send-transaction :in-progress?] true)) - ::send-transaction [(wallet/prepare-send-transaction from transaction) - all-tokens - symbol - chain - #(re-frame/dispatch [:wallet.callback/transaction-completed (types/json->clj %)]) - password]})))) - -;; SIGN MESSAGE -(handlers/register-handler-fx - :wallet/sign-message - (fn [_ [_ typed? screen-params password-error-cb]] - (let [{:keys [data from password]} screen-params] - (if typed? - {::sign-typed-data {:data data - :password password - :account from - :on-completed #(re-frame/dispatch [::sign-message-completed - screen-params - (types/json->clj %) - password-error-cb])}} - {::sign-message {:params {:data data - :password (security/safe-unmask-data password) - :account from} - :on-completed #(re-frame/dispatch [::sign-message-completed - screen-params - (types/json->clj %) - password-error-cb])}})))) - -(fx/defn send-transaction-message - "NOTE(goranjovic): we want to send the payment message only when we have a - whisper id for the recipient, we always redirect to `:wallet-transaction-sent` - even when we don't" - [{:keys [db] :as cofx} chat-id params] - (let [send-command? (and chat-id - (get-in db [:id->command ["send" #{:personal-chats}]]))] - (when send-command? - (commands-sending/send cofx chat-id send-command? params)))) - -;; SEND TRANSACTION CALLBACK -(handlers/register-handler-fx - :wallet.callback/transaction-completed - [(re-frame/inject-cofx :random-id-generator)] - (fn [{:keys [db now] :as cofx} [_ {:keys [result error]}]] - (let [{:keys [id method public-key to symbol asset amount-text on-result on-error - send-transaction-message?]} - (get-in db [:wallet :send-transaction]) - db' (assoc-in db [:wallet :send-transaction :in-progress?] false) - modal-screen-was-used? (get-in db [:navigation/screen-params :wallet-send-modal-stack :modal?])] - (if error - ;; ERROR - (wallet/handle-transaction-error (assoc cofx :db db') error) - ;; RESULT - (fx/merge cofx - (merge - {:db (cond-> (assoc-in db' [:wallet :send-transaction] {}) - - (not (constants/web3-sign-message? method)) - (assoc-in [:wallet :transactions result] - (wallet/prepare-unconfirmed-transaction db now result)))} - (when on-result - {:dispatch (conj on-result id result method)})) - #(when (or (not on-result) - send-transaction-message?) - (send-transaction-message - % - public-key - {:address to - :asset (name (or asset symbol)) - :amount amount-text - :tx-hash result})) - #(when-not on-result - (navigation/navigate-to-clean - % - (if modal-screen-was-used? - :wallet-transaction-sent-modal - :wallet-transaction-sent) - {}))))))) - -(re-frame/reg-fx - :show-sign-message-error - (fn [[password-error-cb]] - (password-error-cb))) - -;; SIGN MESSAGE CALLBACK -(handlers/register-handler-fx - ::sign-message-completed - (fn [{:keys [db now] :as cofx} [_ {:keys [on-result id method]} {:keys [result error]} password-error-cb]] - (let [db' (assoc-in db [:wallet :send-transaction :in-progress?] false)] - (if error - ;; ERROR - {:show-sign-message-error [password-error-cb]} - ;; RESULT - (if on-result - {:dispatch (conj on-result id result method)}))))) - -(handlers/register-handler-fx - ::hash-message-completed - (fn [{:keys [db] :as cofx} [_ {:keys [result error]}]] - (let [view-id (:view-id db) - db' (assoc-in db [:wallet :send-transaction :in-progress?] false)] - (if error - ;; ERROR - (wallet/handle-transaction-error (assoc cofx :db db') error) - ;; RESULT - (fx/merge cofx - {:db (-> db - (assoc-in [:hardwallet :pin :enter-step] :sign) - (assoc-in [:hardwallet :hash] result))} - (navigation/navigate-to-cofx - (if (contains? - #{:wallet-sign-message-modal :wallet-send-transaction-modal :wallet-send-modal-stack} - view-id) - :enter-pin-modal - :enter-pin-sign) - nil)))))) - -;; DISCARD TRANSACTION -(handlers/register-handler-fx - :wallet/discard-transaction - (fn [cofx _] - (wallet/discard-transaction cofx))) - -(handlers/register-handler-fx - :wallet.dapp/transaction-on-result - (fn [{db :db} [_ message-id id result method]] - (let [webview (:webview-bridge db) - keycard? (boolean (get-in db [:account/account :keycard-instance-uid]))] - (wallet/dapp-complete-transaction (int id) result method message-id webview keycard?)))) - -(handlers/register-handler-fx - :wallet.dapp/transaction-on-error - (fn [{db :db} [_ message-id message]] - (wallet/web3-error-callback {} db message-id message))) - -;; DAPP TRANSACTIONS QUEUE -;; NOTE(andrey) We need this queue because dapp can send several transactions in a row, this is bad behaviour -;; but we need to support it -(handlers/register-handler-fx - :check-dapps-transactions-queue - (fn [{:keys [db]} _] - (let [{:keys [send-transaction transactions-queue]} (:wallet db) - {:keys [payload message-id] :as queued-transaction} (last transactions-queue) - {:keys [method params id]} payload - keycard? (boolean (get-in db [:account/account :keycard-instance-uid])) - db' (update-in db [:wallet :transactions-queue] drop-last)] - (when (and (not (contains? #{:wallet-transaction-sent - :wallet-transaction-sent-modal} - (:view-id db))) - (not (:id send-transaction)) queued-transaction) - (cond - - ;;SEND TRANSACTION - (= method constants/web3-send-transaction) - (let [transaction (wallet/prepare-dapp-transaction queued-transaction (:contacts/contacts db))] - (wallet/open-modal-wallet-for-transaction db' transaction (first params))) - - ;;SIGN MESSAGE - (constants/web3-sign-message? method) - (let [typed? (not= constants/web3-personal-sign method) - [address data] (wallet/normalize-sign-message-params params)] - (if (and address data) - (let [signing-phrase (-> (get-in db [:account/account :signing-phrase]) - (clojure.string/replace #" " " ")) - screen-params {:id (str (or id message-id)) - :from address - :data data - :typed? typed? - :decoded-data (if typed? (types/json->clj data) (ethereum/hex-to-utf8 data)) - :on-result [:wallet.dapp/transaction-on-result message-id] - :on-error [:wallet.dapp/transaction-on-error message-id] - :method method - :signing-phrase signing-phrase - :keycard? keycard?}] - (navigation/navigate-to-cofx {:db db'} :wallet-sign-message-modal screen-params)) - {:db db'}))))))) + [status-im.signing.core :as signing])) (defn set-and-validate-amount-db [db amount symbol decimals] (let [{:keys [value error]} (wallet.db/parse-amount amount decimals)] @@ -266,191 +19,25 @@ (fn [{:keys [db]} [_ amount symbol decimals]] {:db (set-and-validate-amount-db db amount symbol decimals)})) -(handlers/register-handler-fx - :wallet/discard-transaction-navigate-back - (fn [cofx _] - (fx/merge cofx - (navigation/navigate-back) - (wallet/discard-transaction)))) - -(defn update-gas-price - ([db edit? success-event] - {:wallet/update-gas-price - {:success-event (or success-event :wallet/update-gas-price-success) - :edit? edit?}}) - ([db edit?] (update-gas-price db edit? :wallet/update-gas-price-success)) - ([db] (update-gas-price db false :wallet/update-gas-price-success))) - -(defn recalculate-gas [{:keys [db] :as fx} symbol] - (-> fx - (assoc-in [:db :wallet :send-transaction :gas] (ethereum/estimate-gas symbol)) - (merge (update-gas-price db)))) - -(handlers/register-handler-fx - :wallet/update-gas-price - (fn [{:keys [db]} [_ edit?]] - (update-gas-price db edit?))) - (handlers/register-handler-fx :wallet.send/set-symbol (fn [{:keys [db]} [_ symbol]] - (let [old-symbol (get-in db [:wallet :send-transaction :symbol])] - (cond-> {:db (-> db - (assoc-in [:wallet :send-transaction :symbol] symbol) - (assoc-in [:wallet :send-transaction :amount] nil) - (assoc-in [:wallet :send-transaction :amount-text] nil) - (assoc-in [:wallet :send-transaction :asset-error] nil))} - (not= old-symbol symbol) (recalculate-gas symbol))))) - -(handlers/register-handler-fx - :wallet.send/toggle-advanced - (fn [{:keys [db]} [_ advanced?]] - {:db (assoc-in db [:wallet :send-transaction :advanced?] advanced?)})) - -(handlers/register-handler-fx - :wallet/cancel-entering-password - (fn [{:keys [db]} _] - {:db (update-in db [:wallet :send-transaction] assoc - :show-password-input? false - :wrong-password? false - :password nil)})) - -(handlers/register-handler-fx - :wallet.send/set-password - (fn [{:keys [db]} [_ masked-password]] - {:db (assoc-in db [:wallet :send-transaction :password] masked-password)})) - -(handlers/register-handler-fx - :wallet.send/edit-value - (fn [cofx [_ key value]] - (wallet/edit-value key value cofx))) - -(handlers/register-handler-fx - :wallet.send/set-gas-details - (fn [{:keys [db]} [_ gas gas-price]] {:db (-> db - (assoc-in [:wallet :send-transaction :gas] gas) - (assoc-in [:wallet :send-transaction :gas-price] gas-price))})) - -(handlers/register-handler-fx - :wallet.send/clear-gas - (fn [{:keys [db]}] - {:db (update db :wallet dissoc :edit)})) - -(handlers/register-handler-fx - :wallet.send/reset-gas-default - (fn [{:keys [db] :as cofx}] - (let [gas-default (if-some [original-gas (-> db :wallet :send-transaction :original-gas)] - (money/to-fixed original-gas) - (money/to-fixed - (ethereum/estimate-gas - (-> db :wallet :send-transaction :symbol))))] - (assoc (wallet/edit-value - :gas - gas-default - cofx) - :dispatch [:wallet/update-gas-price true])))) - -(handlers/register-handler-fx - :close-transaction-sent-screen - (fn [cofx [_ chat-id]] - (fx/merge cofx - {:dispatch-later [{:ms 400 :dispatch [:check-dapps-transactions-queue]}]} - (navigation/navigate-back)))) - -(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-keycard-transaction - [transaction from symbol chain all-tokens] - (if (= :ETH symbol) - (wallet/prepare-send-transaction from transaction) - (let [contract (:address (tokens/symbol->token all-tokens (keyword chain) symbol)) - {:keys [gas gasPrice to from value]} (wallet/prepare-send-transaction from transaction)] - (merge (abi-spec/encode "transfer(address,uint256)" [to value]) - {:to contract - :from from - :gas gas - :gasPrice gasPrice})))) - -(defn send-keycard-transaction - [{{:keys [chain] :as db} :db}] - (let [{:keys [symbol] :as transaction} (get-in db [:wallet :send-transaction]) - all-tokens (:wallet/all-tokens db) - from (get-in db [:account/account :address])] - {::hash-transaction {:transaction (prepare-keycard-transaction transaction from symbol chain all-tokens) - :all-tokens all-tokens - :symbol symbol - :chain chain - :on-completed #(re-frame/dispatch [:wallet.callback/hash-transaction-completed %])}})) - -(handlers/register-handler-fx - :wallet.callback/hash-transaction-completed - (fn [{:keys [db] :as cofx} [_ result]] - (let [{:keys [transaction hash]} (:result (types/json->clj result))] - (fx/merge cofx - {:db (-> db - (assoc-in [:hardwallet :pin :enter-step] :sign) - (assoc-in [:hardwallet :transaction] transaction) - (assoc-in [:hardwallet :hash] hash))} - (navigation/navigate-to-clean - (if (contains? - #{:wallet-sign-message-modal :wallet-send-transaction-modal :wallet-send-modal-stack} - (:view-id db)) - :enter-pin-modal - :enter-pin-sign) - nil))))) + (assoc-in [:wallet :send-transaction :symbol] symbol) + (assoc-in [:wallet :send-transaction :amount] nil) + (assoc-in [:wallet :send-transaction :amount-text] nil) + (assoc-in [:wallet :send-transaction :asset-error] nil))})) (handlers/register-handler-fx :wallet.ui/sign-transaction-button-clicked (fn [{:keys [db] :as cofx} _] - (let [keycard-account? (boolean (get-in db [:account/account :keycard-instance-uid]))] - (if keycard-account? - (send-keycard-transaction cofx) - {:db (assoc-in db [:wallet :send-transaction :show-password-input?] true)})))) - -(fx/defn keycard-hash-message - [_ data typed?] - (if typed? - {::hash-typed-data {:data data - :on-completed #(re-frame/dispatch [::hash-message-completed (types/json->clj %)])}} - {::hash-message {:message (ethereum/naked-address data) - :on-completed #(re-frame/dispatch [::hash-message-completed (types/json->clj %)])}})) - -(handlers/register-handler-fx - :wallet.ui/sign-message-button-clicked - (fn [{:keys [db] :as cofx} [_ typed? screen-params password-error-cb]] - (let [{:keys [data from password]} screen-params - keycard-account? (boolean (get-in db [:account/account :keycard-instance-uid])) - modal? (= (:view-id db) :wallet-sign-message-modal)] - (if keycard-account? - (fx/merge cofx - {:db (assoc-in db [:navigation/screen-params :wallet-send-modal-stack :modal?] modal?)} - (keycard-hash-message data typed?)) - (if typed? - {::sign-typed-data {:data data - :password password - :account from - :on-completed #(re-frame/dispatch [::sign-message-completed - screen-params - (types/json->clj %) - password-error-cb])}} - {::sign-message {:params {:data data - :password (security/safe-unmask-data password) - :account from} - :on-completed #(re-frame/dispatch [::sign-message-completed - screen-params - (types/json->clj %) - password-error-cb])}}))))) + (let [{:keys [to symbol amount]} (get-in cofx [:db :wallet :send-transaction]) + {:keys [symbol address]} (tokens/asset-for (:wallet/all-tokens db) (keyword (:chain db)) symbol) + amount-hex (str "0x" (abi-spec/number-to-hex amount)) + to-norm (ethereum/normalized-address to)] + (signing/sign cofx {:tx-obj (if (= symbol :ETH) + {:to to-norm + :value amount-hex} + {:to (ethereum/normalized-address address) + :data (abi-spec/encode "transfer(address,uint256)" [to-norm amount-hex])}) + :on-result [:navigate-back]})))) \ No newline at end of file diff --git a/src/status_im/ui/screens/wallet/send/views.cljs b/src/status_im/ui/screens/wallet/send/views.cljs index 9d6136222d0e..8c9f6ead0b1d 100644 --- a/src/status_im/ui/screens/wallet/send/views.cljs +++ b/src/status_im/ui/screens/wallet/send/views.cljs @@ -1,128 +1,31 @@ (ns status-im.ui.screens.wallet.send.views + (:require-macros [status-im.utils.views :refer [defview letsubs]]) (:require [re-frame.core :as re-frame] [reagent.core :as reagent] - [status-im.ethereum.core :as ethereum] [status-im.ethereum.tokens :as tokens] [status-im.i18n :as i18n] - [status-im.ui.components.animation :as animation] [status-im.ui.components.bottom-buttons.view :as bottom-buttons] [status-im.ui.components.button.view :as button] [status-im.ui.components.colors :as colors] [status-im.ui.components.common.common :as common] [status-im.ui.components.icons.vector-icons :as vector-icons] [status-im.ui.components.react :as react] - [status-im.ui.components.status-bar.view :as status-bar] [status-im.ui.components.styles :as components.styles] - [status-im.ui.components.toolbar.actions :as actions] [status-im.ui.components.toolbar.view :as toolbar] - [status-im.ui.components.tooltip.views :as tooltip] - [status-im.ui.screens.wallet.components.styles - :as - wallet.components.styles] [status-im.ui.screens.wallet.components.views :as wallet.components] [status-im.ui.screens.wallet.main.views :as wallet.main.views] - [status-im.ui.screens.wallet.send.animations :as send.animations] [status-im.ui.screens.wallet.send.styles :as styles] - [status-im.ui.screens.wallet.styles :as wallet.styles] - [status-im.ui.screens.wallet.utils :as wallet.utils] - [status-im.utils.money :as money] - [status-im.utils.security :as security] - [status-im.utils.utils :as utils] - [taoensso.timbre :as log]) - (:require-macros [status-im.utils.views :refer [defview letsubs]])) + [status-im.ui.components.toolbar.actions :as actions])) -(defn- toolbar [modal? title] - (let [action (if modal? actions/close-white actions/back-white)] - [toolbar/toolbar {:transparent? true} - [toolbar/nav-button (action (if modal? - #(re-frame/dispatch [:wallet/discard-transaction-navigate-back]) - #(actions/default-handler)))] - [toolbar/content-title {:color :white} title]])) +(defn- toolbar [title] + [toolbar/toolbar {:transparent? true} + [toolbar/nav-button (actions/back-white #(actions/default-handler))] + [toolbar/content-title {:color :white} title]]) -(defn- advanced-cartouche [native-currency {:keys [max-fee gas gas-price]}] - [react/view - [wallet.components/cartouche {:on-press #(do (re-frame/dispatch [:wallet.send/clear-gas]) - (re-frame/dispatch [:navigate-to :wallet-transaction-fee]))} - (i18n/label :t/wallet-transaction-fee) - [react/view {:style styles/advanced-options-text-wrapper - :accessibility-label :transaction-fee-button} - [react/text {:style styles/advanced-fees-text} - (str max-fee " " (wallet.utils/display-symbol native-currency))] - [react/text {:style styles/advanced-fees-details-text} - (str (money/to-fixed gas) " * " (money/to-fixed (money/wei-> :gwei gas-price)) (i18n/label :t/gwei))]]]]) - -(defn- advanced-options [advanced? native-currency transaction scroll] - [react/view {:style styles/advanced-wrapper} - [react/touchable-highlight {:on-press (fn [] - (re-frame/dispatch [:wallet.send/toggle-advanced (not advanced?)]) - (when (and scroll @scroll) (utils/set-timeout #(.scrollToEnd @scroll) 350)))} - [react/view {:style styles/advanced-button-wrapper} - [react/view {:style styles/advanced-button - :accessibility-label :advanced-button} - [react/i18n-text {:style (merge wallet.components.styles/label - styles/advanced-label) - :key :wallet-advanced}] - [vector-icons/icon (if advanced? :main-icons/dropdown-up :main-icons/dropdown) {:color :white}]]]] - (when advanced? - [advanced-cartouche native-currency transaction])]) - -(defview password-input-panel [message-label spinning?] - (letsubs [account [:account/account] - wrong-password? [:wallet.send/wrong-password?] - signing-phrase (:signing-phrase @account) - bottom-value (animation/create-value -250) - opacity-value (animation/create-value 0)] - {:component-did-mount #(send.animations/animate-sign-panel opacity-value bottom-value)} - [react/animated-view {:style (styles/animated-sign-panel bottom-value)} - (when wrong-password? - [tooltip/tooltip (i18n/label :t/wrong-password) styles/password-error-tooltip]) - [react/animated-view {:style (styles/sign-panel opacity-value)} - [react/view styles/spinner-container - (when spinning? - [react/activity-indicator {:animating true - :size :large}])] - [react/view styles/signing-phrase-container - [react/text {:accessibility-label :signing-phrase-text} - signing-phrase]] - [react/i18n-text {:style styles/signing-phrase-description :key message-label}] - [react/view {:style styles/password-container - :important-for-accessibility :no-hide-descendants} - [react/text-input - {:auto-focus true - :secure-text-entry true - :placeholder (i18n/label :t/enter-password) - :placeholder-text-color colors/gray - :on-change-text #(re-frame/dispatch [:wallet.send/set-password (security/mask-data %)]) - :style styles/password - :accessibility-label :enter-password-input - :auto-capitalize :none}]]]])) - -;; "Cancel" and "Sign Transaction >" or "Sign >" buttons, signing with password -(defview enter-password-buttons [spinning? cancel-handler sign-handler sign-label] - (letsubs [sign-enabled? [:wallet.send/sign-password-enabled?] - network-status [:network-status]] - [bottom-buttons/bottom-buttons - styles/sign-buttons - [button/button {:style components.styles/flex - :on-press cancel-handler - :accessibility-label :cancel-button} - (i18n/label :t/cancel)] - [button/button {:style (wallet.styles/button-container sign-enabled?) - :on-press sign-handler - :disabled? (or spinning? - (not sign-enabled?) - (= :offline network-status)) - :accessibility-label :sign-transaction-button} - (i18n/label sign-label) - [vector-icons/icon :main-icons/next {:color colors/white}]]])) - -;; "Sign Transaction >" button -(defn- sign-transaction-button [amount-error to amount sufficient-funds? sufficient-gas? modal? online?] +(defn- sign-transaction-button [amount-error amount sufficient-funds? online?] (let [sign-enabled? (and (nil? amount-error) - (or modal? (not (empty? to))) ;;NOTE(goranjovic) - contract creation will have empty `to` (not (nil? amount)) sufficient-funds? - sufficient-gas? online?)] [bottom-buttons/bottom-buttons styles/sign-buttons @@ -135,79 +38,40 @@ (i18n/label :t/transactions-sign-transaction) [vector-icons/icon :main-icons/next {:color (if sign-enabled? colors/white colors/white-light-transparent)}]]])) -(defn signing-phrase-view [signing-phrase] - [react/view {:flex-direction :column - :align-items :center - :margin-top 10} - [react/view (assoc styles/signing-phrase-container :width "90%" :height 40) - [react/text {:accessibility-label :signing-phrase-text - :style {:padding-vertical 16 - :text-align :center}} - signing-phrase]] - [react/text {:style {:color :white - :text-align :center - :font-size 12 - :padding-vertical 14}} - (i18n/label :t/signing-phrase-warning)]]) - -(defn- render-send-transaction-view [{:keys [chain modal? transaction scroll advanced? keycard? signing-phrase all-tokens amount-input network-status]}] - (let [{:keys [amount amount-text amount-error asset-error show-password-input? to to-name sufficient-funds? - sufficient-gas? in-progress? from-chat? asset symbol]} transaction - native-currency (tokens/native-currency chain) +(defn- render-send-transaction-view [{:keys [chain transaction scroll all-tokens amount-input network-status]}] + (let [{:keys [amount amount-text amount-error asset-error to to-name sufficient-funds? symbol]} transaction {:keys [decimals] :as token} (tokens/asset-for all-tokens chain symbol) online? (= :online network-status)] - [wallet.components/simple-screen {:avoid-keyboard? (not modal?) - :status-bar-type (if modal? :modal-wallet :wallet)} - [toolbar modal? (i18n/label :t/send-transaction)] + [wallet.components/simple-screen {:avoid-keyboard? true + :status-bar-type :wallet} + [toolbar (i18n/label :t/send-transaction)] [react/view components.styles/flex [common/network-info {:text-color :white}] [react/scroll-view {:keyboard-should-persist-taps :always :ref #(reset! scroll %) - :on-content-size-change #(when (and (not modal?) scroll @scroll) + :on-content-size-change #(when (and scroll @scroll) (.scrollToEnd @scroll))} (when-not online? [wallet.main.views/snackbar :t/error-cant-send-transaction-offline]) [react/view styles/send-transaction-form [wallet.components/recipient-selector - {:disabled? (or from-chat? modal? show-password-input?) - :address to - :name to-name - :modal? modal?}] + {:address to + :name to-name}] [wallet.components/asset-selector - {:disabled? (or from-chat? modal? show-password-input?) - :error asset-error + {:error asset-error :type :send - :symbol (or asset symbol)}] + :symbol symbol}] [wallet.components/amount-selector - {:disabled? (or from-chat? modal? show-password-input?) - :error (or amount-error - (when-not sufficient-funds? (i18n/label :t/wallet-insufficient-funds)) - (when-not sufficient-gas? (i18n/label :t/wallet-insufficient-gas))) + {:error (or amount-error (when-not sufficient-funds? (i18n/label :t/wallet-insufficient-funds))) :amount amount :amount-text amount-text :input-options {:on-change-text #(re-frame/dispatch [:wallet.send/set-and-validate-amount % symbol decimals]) - :ref (partial reset! amount-input)}} token] - [advanced-options advanced? native-currency transaction scroll] - (when keycard? - [signing-phrase-view signing-phrase])]] - (if show-password-input? - [enter-password-buttons in-progress? - #(re-frame/dispatch [:wallet/cancel-entering-password]) - #(re-frame/dispatch [:wallet/send-transaction]) - :t/transactions-sign-transaction] - [sign-transaction-button amount-error to amount sufficient-funds? sufficient-gas? modal? online?]) - (when show-password-input? - [password-input-panel :t/signing-phrase-description in-progress?]) - (when in-progress? [react/view styles/processing-view])]])) + :ref (partial reset! amount-input)}} token]]] + [sign-transaction-button amount-error amount sufficient-funds? online?]]])) -;; MAIN SEND TRANSACTION VIEW -(defn- send-transaction-view [{:keys [scroll] :as opts}] +(defn- send-transaction-view [{:keys [scroll]}] (let [amount-input (atom nil) - handler (fn [_] - (when (and scroll @scroll @amount-input - (.isFocused @amount-input)) - (log/debug "Amount field focused, scrolling down") - (.scrollToEnd @scroll)))] + handler #(when (and scroll @scroll @amount-input (.isFocused @amount-input)) (.scrollToEnd @scroll))] (reagent/create-class {:component-will-mount (fn [_] ;;NOTE(goranjovic): keyboardDidShow is for android and keyboardWillShow for ios @@ -216,49 +80,14 @@ :reagent-render (fn [opts] (render-send-transaction-view (assoc opts :amount-input amount-input)))}))) -;; SEND TRANSACTION FROM WALLET (CHAT) (defview send-transaction [] (letsubs [transaction [:wallet.send/transaction] - advanced? [:wallet.send/advanced?] chain [:ethereum/chain-keyword] scroll (atom nil) network-status [:network-status] - all-tokens [:wallet/all-tokens] - signing-phrase [:wallet.send/signing-phrase-with-padding] - keycard? [:keycard-account?]] - [send-transaction-view {:modal? false - :transaction transaction + all-tokens [:wallet/all-tokens]] + [send-transaction-view {:transaction transaction :scroll scroll - :advanced? advanced? - :keycard? keycard? - :signing-phrase signing-phrase :chain chain :all-tokens all-tokens - :network-status network-status}])) - -;; SEND TRANSACTION FROM DAPP -(defview send-transaction-modal [] - (letsubs [transaction [:wallet.send/transaction] - advanced? [:wallet.send/advanced?] - chain [:ethereum/chain-keyword] - scroll (atom nil) - network-status [:network-status] - all-tokens [:wallet/all-tokens] - signing-phrase [:wallet.send/signing-phrase-with-padding] - keycard? [:keycard-account?]] - (if transaction - [send-transaction-view {:modal? true - :transaction transaction - :scroll scroll - :advanced? advanced? - :keycard? keycard? - :signing-phrase signing-phrase - :chain chain - :all-tokens all-tokens - :network-status network-status}] - [react/view wallet.styles/wallet-modal-container - [react/view components.styles/flex - [status-bar/status-bar {:type :modal-wallet}] - [toolbar true (i18n/label :t/send-transaction)] - [react/i18n-text {:style styles/empty-text - :key :unsigned-transaction-expired}]]]))) + :network-status network-status}])) \ No newline at end of file diff --git a/src/status_im/ui/screens/wallet/sign_message/views.cljs b/src/status_im/ui/screens/wallet/sign_message/views.cljs deleted file mode 100644 index 09d0436e5e7e..000000000000 --- a/src/status_im/ui/screens/wallet/sign_message/views.cljs +++ /dev/null @@ -1,121 +0,0 @@ -(ns status-im.ui.screens.wallet.sign-message.views - (:require-macros [status-im.utils.views :refer [defview letsubs]]) - (:require [re-frame.core :as re-frame] - [status-im.i18n :as i18n] - [status-im.ui.components.react :as react] - [status-im.ui.components.styles :as components.styles] - [status-im.ui.screens.wallet.components.views :as components] - [status-im.ui.screens.wallet.components.views :as wallet.components] - [status-im.ui.screens.wallet.send.styles :as styles] - [status-im.ui.screens.wallet.send.views :as wallet.send.views] - [status-im.ui.screens.wallet.main.views :as wallet.main.views] - [status-im.ui.components.toolbar.actions :as actions] - [status-im.ui.components.toolbar.view :as toolbar] - [status-im.ui.screens.wallet.styles :as wallet.styles] - [status-im.ui.components.bottom-buttons.view :as bottom-buttons] - [status-im.ui.components.button.view :as button] - [status-im.ui.components.icons.vector-icons :as vector-icons] - [status-im.ui.components.colors :as colors] - [status-im.ui.components.animation :as animation] - [status-im.ui.screens.wallet.send.animations :as send.animations] - [status-im.utils.security :as security] - [status-im.ui.components.tooltip.views :as tooltip] - [reagent.core :as reagent])) - -(defn- toolbar [modal? title] - (let [action (if modal? actions/close-white actions/back-white)] - [toolbar/toolbar - {:style {:border-bottom-color colors/white-light-transparent}} - [toolbar/nav-button (action (if modal? - #(re-frame/dispatch [:wallet/discard-transaction-navigate-back]) - #(actions/default-handler)))] - [toolbar/content-title {:color :white} title]])) - -(defview enter-password-buttons [value-atom {:keys [spinning? keycard?]} cancel-handler sign-handler sign-label] - (letsubs [network-status [:network-status]] - (let [password (:password @value-atom) - sign-enabled? (or keycard? - (and (not (nil? password)) (not= password "")))] - [bottom-buttons/bottom-buttons - styles/sign-buttons - [button/button {:style components.styles/flex - :on-press cancel-handler - :accessibility-label :cancel-button} - (i18n/label :t/cancel)] - [button/button {:style (wallet.styles/button-container sign-enabled?) - :on-press sign-handler - :disabled? (or spinning? - (not sign-enabled?) - (= :offline network-status)) - :accessibility-label :sign-transaction-button} - (i18n/label sign-label) - [vector-icons/icon :main-icons/next {:color colors/white}]]]))) - -(defview password-input-panel [value-atom message-label spinning?] - (letsubs [account [:account/account] - signing-phrase (:signing-phrase @account) - bottom-value (animation/create-value -250) - opacity-value (animation/create-value 0)] - {:component-did-mount #(send.animations/animate-sign-panel opacity-value bottom-value)} - [react/animated-view {:style (styles/animated-sign-panel bottom-value)} - (when (:wrong-password? @value-atom) - [tooltip/tooltip (i18n/label :t/wrong-password) styles/password-error-tooltip]) - [react/animated-view {:style (styles/sign-panel opacity-value)} - [react/view styles/spinner-container - (when spinning? - [react/activity-indicator {:animating true - :size :large}])] - [react/view styles/signing-phrase-container - [react/text {:accessibility-label :signing-phrase-text} - signing-phrase]] - [react/i18n-text {:style styles/signing-phrase-description :key message-label}] - [react/view {:style styles/password-container - :important-for-accessibility :no-hide-descendants} - [react/text-input - {:auto-focus true - :secure-text-entry true - :placeholder (i18n/label :t/enter-password) - :placeholder-text-color colors/gray - :on-change-text #(swap! value-atom assoc :password (security/mask-data %)) - :style styles/password - :accessibility-label :enter-password-input - :auto-capitalize :none}]]]])) - -;; SIGN MESSAGE FROM DAPP -(defview sign-message-modal [] - (letsubs [value-atom (reagent/atom nil) - {:keys [decoded-data in-progress? typed? keycard? signing-phrase] :as screen-params} [:get-screen-params :wallet-sign-message-modal] - network-status [:network-status]] - [wallet.components/simple-screen {:status-bar-type :modal-wallet} - [toolbar true (i18n/label :t/sign-message)] - [react/view components.styles/flex - [react/scroll-view - (when (= network-status :offline) - [wallet.main.views/snackbar :t/error-cant-sign-message-offline]) - [react/view styles/send-transaction-form - [wallet.components/cartouche {:disabled? true} - (i18n/label :t/message) - [components/amount-input - {:disabled? true - :input-options {:multiline true - :height (if typed? 300 100)} - :amount-text (if typed? - (str "Domain\n" (:domain decoded-data) "\nMessage\n" (:message decoded-data)) - decoded-data)} - nil]]] - (when keycard? - [wallet.send.views/signing-phrase-view signing-phrase])] - [enter-password-buttons - value-atom - {:spinning? false :keycard? keycard?} - #(re-frame/dispatch [:wallet/discard-transaction-navigate-back]) - #(re-frame/dispatch [:wallet.ui/sign-message-button-clicked - typed? - (merge screen-params @value-atom) - (fn [] - (swap! value-atom assoc :wrong-password? true))]) - :t/transactions-sign] - (when-not keycard? - [password-input-panel value-atom :t/signing-message-phrase-description false]) - (when in-progress? - [react/view styles/processing-view])]])) diff --git a/src/status_im/wallet/core.cljs b/src/status_im/wallet/core.cljs index 4f7fb5dbfeec..ee9f01c4626e 100644 --- a/src/status_im/wallet/core.cljs +++ b/src/status_im/wallet/core.cljs @@ -13,7 +13,6 @@ [status-im.utils.config :as config] [status-im.utils.core :as utils.core] [status-im.utils.fx :as fx] - [status-im.utils.hex :as utils.hex] [status-im.utils.money :as money] [status-im.utils.prices :as prices] [status-im.utils.utils :as utils.utils] @@ -49,14 +48,6 @@ {:db (assoc-in db [:wallet :current-transaction] hash)} (navigation/navigate-to-cofx :wallet-transaction-details nil))) -(fx/defn open-send-transaction-modal - [{:keys [db] :as cofx} id method from-chat?] - (fx/merge cofx - {:db (assoc-in db [:wallet :send-transaction] {:id id - :method method - :from-chat? from-chat?})} - (navigation/navigate-to-clean :wallet-send-transaction-modal nil))) - ;; FX (re-frame/reg-fx @@ -80,23 +71,6 @@ #(re-frame/dispatch [error-event %]) chaos-mode?))) -(re-frame/reg-fx - :wallet/update-gas-price - (fn [{:keys [success-event edit?]}] - (json-rpc/call - {:method "eth_gasPrice" - :on-success - #(re-frame/dispatch [success-event % edit?])}))) - -(re-frame/reg-fx - :wallet/update-estimated-gas - (fn [{:keys [obj success-event]}] - (json-rpc/call - {:method "eth_estimateGas" - :params [obj] - :on-success - #(re-frame/dispatch [success-event %])}))) - (defn- validate-token-name! [{:keys [address symbol name]}] (json-rpc/eth-call @@ -175,183 +149,6 @@ (on-success symbol (money/bignumber balance))) :on-error #(on-error symbol %)})))) -(def min-gas-price-wei (money/bignumber 1)) - -(defmulti invalid-send-parameter? (fn [type _] type)) - -(defmethod invalid-send-parameter? :gas-price [_ value] - (cond - (not value) :invalid-number - (.lt (money/->wei :gwei value) min-gas-price-wei) :not-enough-wei - (-> (money/->wei :gwei value) .decimalPlaces pos?) :invalid-number)) - -(defmethod invalid-send-parameter? :default [_ value] - (when (or (not value) - (<= value 0)) - :invalid-number)) - -(defn- calculate-max-fee - [gas gas-price] - (if (and gas gas-price) - (money/to-fixed (money/wei->ether (.times gas gas-price))) - "0")) - -(defn- edit-max-fee [edit] - (let [gas (get-in edit [:gas-price :value-number]) - gas-price (get-in edit [:gas :value-number])] - (assoc edit :max-fee (calculate-max-fee gas gas-price)))) - -(defn add-max-fee [{:keys [gas gas-price] :as transaction}] - (assoc transaction :max-fee (calculate-max-fee gas gas-price))) - -(defn build-edit [edit-value key value] - "Takes the previous edit, either :gas or :gas-price and a value as string. - Wei for gas, and gwei for gas price. - Validates them and sets max fee" - (let [bn-value (money/bignumber value) - invalid? (invalid-send-parameter? key bn-value) - data (if invalid? - {:value value - :max-fee 0 - :invalid? invalid?} - {:value value - :value-number (if (= :gas-price key) - (money/->wei :gwei bn-value) - bn-value) - :invalid? false})] - (-> edit-value - (assoc key data) - edit-max-fee))) - -(defn edit-value - [key value {:keys [db]}] - {:db (update-in db [:wallet :edit] build-edit key value)}) - -;; DAPP TRANSACTION -> SEND TRANSACTION -(defn prepare-dapp-transaction [{{:keys [id method params]} :payload message-id :message-id} contacts] - (let [{:keys [to value data gas gasPrice nonce]} (first params) - contact (get contacts (utils.hex/normalize-hex to))] - (cond-> {:id (str id) - :to-name (or (when (nil? to) - (i18n/label :t/new-contract)) - contact) - :symbol :ETH - :method method - :to to - :amount (money/bignumber (or value 0)) - :gas (cond - gas - (money/bignumber gas) - (and value (empty? data)) - (money/bignumber 21000)) - :gas-price (when gasPrice - (money/bignumber gasPrice)) - :data data - :on-result [:wallet.dapp/transaction-on-result message-id] - :on-error [:wallet.dapp/transaction-on-error message-id]} - nonce - (assoc :nonce nonce)))) - -;; SEND TRANSACTION -> RPC TRANSACTION -(defn prepare-send-transaction - [from {:keys [amount to gas gas-price data nonce]}] - (cond-> {:from (ethereum/normalized-address from) - :to (ethereum/normalized-address to) - :value (str "0x" (abi-spec/number-to-hex amount)) - :gas (str "0x" (abi-spec/number-to-hex gas)) - :gasPrice (str "0x" (abi-spec/number-to-hex gas-price))} - data - (assoc :data data) - nonce - (assoc :nonce nonce))) - -(defn normalize-sign-message-params - "NOTE (andrey) we need this function, because params may be mixed up, - so we need to figure out which one is address and which message" - [params] - (let [first_param (first params) - second_param (second params) - first-param-address? (ethereum/address? first_param) - second-param-address? (ethereum/address? second_param)] - (when (or first-param-address? second-param-address?) - (if first-param-address? - [first_param second_param] - [second_param first_param])))) - -(defn web3-error-callback - [fx {:keys [webview-bridge]} message-id message] - (assoc fx :browser/send-to-bridge - {:message {:type constants/web3-send-async-callback - :messageId message-id - :error message} - :webview webview-bridge})) - -(defn dapp-complete-transaction - [id result method message-id webview keycard?] - (cond-> {:browser/send-to-bridge - {:message {:type constants/web3-send-async-callback - :messageId message-id - :result {:jsonrpc "2.0" - :id (int id) - :result result}} - :webview webview} - :dispatch [:navigate-back]} - - (constants/web3-sign-message? method) - (assoc :dispatch (if keycard? - [:navigate-to :browser] - [:navigate-back])) - - (= method constants/web3-send-transaction) - (assoc :dispatch [:navigate-to-clean :wallet-transaction-sent-modal]))) - -(fx/defn discard-transaction - [{:keys [db]}] - (let [{:keys [on-error]} (get-in db [:wallet :send-transaction])] - (merge {:db (update db :wallet - assoc - :send-transaction {} - :transactions-queue nil)} - (when on-error - {:dispatch (conj on-error "transaction was cancelled by user")})))) - -(defn prepare-unconfirmed-transaction - [db now hash] - (let [transaction (get-in db [:wallet :send-transaction]) - all-tokens (:wallet/all-tokens db)] - (let [chain (:chain db) - token (tokens/symbol->token all-tokens (keyword chain) (:symbol transaction))] - (-> transaction - (assoc :timestamp (str now) - :type :pending - :hash hash - :value (:amount transaction) - :token token - :gas-limit (str (:gas transaction))) - (update :gas-price str) - (dissoc :message-id :id :gas))))) - -(fx/defn handle-transaction-error - [{:keys [db] :as cofx} {:keys [code message]}] - (let [{:keys [on-error]} (get-in db [:wallet :send-transaction])] - (log/warn :wallet/transaction-error - :code code - :message message) - (case code - ;;WRONG PASSWORD - constants/send-transaction-err-decrypt - {:db (-> db - (assoc-in [:wallet :send-transaction :wrong-password?] true))} - - (fx/merge cofx - (merge {:db (-> db - (assoc-in [:wallet :transactions-queue] nil) - (assoc-in [:wallet :send-transaction] {})) - :wallet/show-transaction-error message} - (when on-error - {:dispatch (conj on-error message)})) - navigation/navigate-back)))) - (defn clear-error-message [db error-type] (update-in db [:wallet :errors] dissoc error-type)) @@ -450,29 +247,6 @@ (clear-error-message :prices-update) (assoc :prices-loading? true))}))) -(defn open-modal-wallet-for-transaction - [db transaction tx-object] - (let [{:keys [gas gas-price]} transaction - {:keys [wallet-set-up-passed?]} (:account/account db)] - {:db (-> db - (assoc-in [:navigation/screen-params :wallet-send-modal-stack :modal?] true) - (assoc-in [:wallet :send-transaction] transaction) - (assoc-in [:wallet :send-transaction :original-gas] gas)) - :dispatch-n [(when-not gas - [:TODO.remove/update-estimated-gas tx-object]) - (when-not gas-price - [:wallet/update-gas-price]) - [:navigate-to - (if wallet-set-up-passed? - :wallet-send-modal-stack - :wallet-send-modal-stack-with-onboarding)]]})) - -(defn send-transaction-screen-did-load - [{:keys [db]}] - {:db (assoc-in db - [:navigation/screen-params :wallet-send-modal-stack :modal?] - false)}) - (defn- set-checked [ids id checked?] (if checked? (conj (or ids #{}) id) @@ -496,29 +270,6 @@ (assoc-in [:wallet :balance symbol] (money/bignumber balance)) (assoc-in [:wallet :balance-loading?] false))}) -(fx/defn update-gas-price - [{:keys [db] :as cofx} price edit?] - (if edit? - (edit-value - :gas-price - (money/to-fixed - (money/wei-> :gwei price)) - cofx) - {:db (assoc-in db [:wallet :send-transaction :gas-price] price)})) - -(fx/defn update-estimated-gas-price - [{:keys [db]} gas] - (when gas - (let [adjusted-gas (money/bignumber (int (* gas 1.2))) - db-with-adjusted-gas (assoc-in db - [:wallet :send-transaction :gas] - adjusted-gas)] - {:db (if (some? (-> db :wallet :send-transaction :original-gas)) - db-with-adjusted-gas - (assoc-in db-with-adjusted-gas - [:wallet :send-transaction :original-gas] - adjusted-gas))}))) - (defn update-toggle-in-settings [{{:account/keys [account] :as db} :db} symbol checked?] (let [chain (ethereum/chain-keyword db) @@ -549,35 +300,4 @@ (fx/merge cofx (toggle-visible-token symbol true) ;;TODO(goranjovic): move `update-token-balance-success` function to wallet models - (update-token-balance symbol balance))) - -(fx/defn eth-transaction-call - [{:keys [db] :as cofx} - {:keys [contract method params on-result on-error details] :as transaction}] - (let [current-address (ethereum/current-address db) - transaction (merge {:to contract - :from current-address - :data (abi-spec/encode method params) - :id "approve" - :symbol :ETH - :method "eth_sendTransaction" - :amount (money/bignumber 0) - :on-result on-result - :on-error on-error} - details) - go-to-view-id (if (get-in db [:account/account :wallet-set-up-passed?]) - :wallet-send-modal-stack - :wallet-send-modal-stack-with-onboarding)] - (fx/merge cofx - {:db (-> db - (assoc-in [:navigation/screen-params :wallet-send-modal-stack :modal?] true) - (assoc-in [:wallet :send-transaction] - transaction)) - :wallet/update-estimated-gas - {:obj (select-keys transaction [:to :from :data]) - :success-event :wallet/update-estimated-gas-success} - - :wallet/update-gas-price - {:success-event :wallet/update-gas-price-success - :edit? false}} - (navigation/navigate-to-cofx go-to-view-id {})))) + (update-token-balance symbol balance))) \ No newline at end of file diff --git a/test/cljs/status_im/test/chat/commands/impl/transactions.cljs b/test/cljs/status_im/test/chat/commands/impl/transactions.cljs index ff80ff247644..43705a3a60e5 100644 --- a/test/cljs/status_im/test/chat/commands/impl/transactions.cljs +++ b/test/cljs/status_im/test/chat/commands/impl/transactions.cljs @@ -40,23 +40,7 @@ {:title (i18n/label :t/send-request-amount) :description (i18n/label :t/send-request-amount-max-decimals {:asset-decimals 18})})) (is (= (protocol/validate personal-send-command {:asset "ETH" :amount "0.01"} cofx) - nil))) - (testing "Yielding control prefills wallet" - (let [fx (protocol/yield-control personal-send-command {:content {:params {:asset "ETH" :amount "0.01"}}} cofx)] - (is (= (get-in fx [:db :wallet :send-transaction :amount-text]) "0.01")) - (is (= (get-in fx [:db :wallet :send-transaction :symbol]) :ETH))))) - -(deftest from-contacts - (testing "the user is in our contacts" - (let [fx (protocol/yield-control personal-send-command {:content {:params {:asset "ETH" :amount "0.01"}}} cofx)] - (is (= (get-in fx [:db :wallet :send-transaction :to]) address)) - (is (= (get-in fx [:db :wallet :send-transaction :to-name] "Recipient"))) - (is (= (get-in fx [:db :wallet :send-transaction :public-key]) public-key))) - (testing "the user is not in our contacts" - (let [fx (protocol/yield-control personal-send-command {:content {:params {:asset "ETH" :amount "0.01"}}} (update-in cofx [:db :contacts/contacts] dissoc public-key))] - (is (= (get-in fx [:db :wallet :send-transaction :to]) address)) - (is (= (get-in fx [:db :wallet :send-transaction :to-name]) "Plump Nippy Blobfish")) - (is (= (get-in fx [:db :wallet :send-transaction :public-key]) public-key)))))) + nil)))) ;; testing the `/request` command diff --git a/test/cljs/status_im/test/models/wallet.cljs b/test/cljs/status_im/test/models/wallet.cljs deleted file mode 100644 index f36d1bb655bd..000000000000 --- a/test/cljs/status_im/test/models/wallet.cljs +++ /dev/null @@ -1,57 +0,0 @@ -(ns status-im.test.models.wallet - (:require [cljs.test :refer-macros [deftest is testing]] - [status-im.utils.money :as money] - [status-im.wallet.core :as wallet])) - -(deftest valid-min-gas-price-test - (testing "not an number" - (is (= :invalid-number (wallet/invalid-send-parameter? :gas-price nil)))) - (testing "a number less than the minimum" - (is (= :not-enough-wei (wallet/invalid-send-parameter? :gas-price (money/bignumber "0.0000000001"))))) - (testing "a number greater than the mininum" - (is (not (wallet/invalid-send-parameter? :gas-price 3)))) - (testing "the minimum" - (is (not (wallet/invalid-send-parameter? :gas-price (money/bignumber "0.000000001")))))) - -(deftest valid-gas - (testing "not an number" - (is (= :invalid-number (wallet/invalid-send-parameter? :gas nil)))) - (testing "0" - (is (= :invalid-number (wallet/invalid-send-parameter? :gas 0)))) - (testing "a number" - (is (not (wallet/invalid-send-parameter? :gas 1))))) - -(deftest build-edit-test - (testing "an invalid edit" - (let [actual (-> {} - (wallet/build-edit :gas "invalid") - (wallet/build-edit :gas-price "0.00000000001"))] - (testing "it marks gas-price as invalid" - (is (get-in actual [:gas-price :invalid?]))) - (testing "it does not change value" - (is (= "0.00000000001" (get-in actual [:gas-price :value])))) - (testing "it marks gas as invalid" - (is (get-in actual [:gas :invalid?]))) - (testing "it does not change gas value" - (is (= "invalid" (get-in actual [:gas :value])))) - (testing "it sets max-fee to 0" - (is (= "0" (:max-fee actual)))))) - (testing "gas price in wei should be round" - (let [actual (-> {} - (wallet/build-edit :gas "21000") - (wallet/build-edit :gas-price "0.0000000023"))] - (is (get-in actual [:gas-price :invalid?])))) - (testing "an valid edit" - (let [actual (-> {} - (wallet/build-edit :gas "21000") - (wallet/build-edit :gas-price "10"))] - (testing "it does not mark gas-price as invalid" - (is (not (get-in actual [:gas-price :invalid?])))) - (testing "it sets the value in number for gas-price, in gwei" - (is (= "10000000000" (str (get-in actual [:gas-price :value-number]))))) - (testing "it does not mark gas as invalid" - (is (not (get-in actual [:gas :invalid?])))) - (testing "it sets the value in number for gasi" - (is (= "21000" (str (get-in actual [:gas :value-number]))))) - (testing "it calculates max-fee" - (is (= "0.00021" (:max-fee actual))))))) diff --git a/test/cljs/status_im/test/runner.cljs b/test/cljs/status_im/test/runner.cljs index 192142125350..547013ed79c3 100644 --- a/test/cljs/status_im/test/runner.cljs +++ b/test/cljs/status_im/test/runner.cljs @@ -37,7 +37,6 @@ [status-im.test.models.bootnode] [status-im.test.models.contact] [status-im.test.models.network] - [status-im.test.models.wallet] [status-im.test.node.core] [status-im.test.pairing.core] [status-im.test.search.core] @@ -67,7 +66,9 @@ [status-im.test.wallet.subs] [status-im.test.wallet.transactions.subs] [status-im.test.wallet.transactions] - [status-im.test.contacts.db])) + [status-im.test.contacts.db] + [status-im.test.signing.core] + [status-im.test.signing.gas])) (enable-console-print!) @@ -116,11 +117,12 @@ 'status-im.test.models.bootnode 'status-im.test.models.contact 'status-im.test.models.network - 'status-im.test.models.wallet 'status-im.test.node.core 'status-im.test.pairing.core 'status-im.test.search.core 'status-im.test.sign-in.flow + 'status-im.test.signing.core + 'status-im.test.signing.gas 'status-im.test.transport.core 'status-im.test.tribute-to-talk.core 'status-im.test.tribute-to-talk.db @@ -145,4 +147,4 @@ 'status-im.test.utils.utils 'status-im.test.wallet.subs 'status-im.test.wallet.transactions - 'status-im.test.wallet.transactions.subs) + 'status-im.test.wallet.transactions.subs) \ No newline at end of file diff --git a/test/cljs/status_im/test/signing/core.cljs b/test/cljs/status_im/test/signing/core.cljs new file mode 100644 index 000000000000..ff249a649d49 --- /dev/null +++ b/test/cljs/status_im/test/signing/core.cljs @@ -0,0 +1,54 @@ +(ns status-im.test.signing.core + (:require [cljs.test :refer-macros [deftest is testing]] + [status-im.signing.core :as signing] + [status-im.ethereum.abi-spec :as abi-spec])) + +(deftest signing-test + (testing "showing sheet" + (let [to "0x2f88d65f3cb52605a54a833ae118fb1363acccd2" + contract "0xc55cf4b03948d7ebc8b9e8bad92643703811d162" + amount1-hex (str "0x" (abi-spec/number-to-hex "10000000000000000000")) + amount2-hex (str "0x" (abi-spec/number-to-hex "100000000000000000000")) + data (abi-spec/encode "transfer(address,uint256)" [to amount2-hex]) + first-tx {:tx-obj {:to to + :value amount1-hex}} + second-tx {:tx-obj {:to contract + :data data}} + sign-first (signing/sign {:db {}} first-tx) + sign-second (signing/sign sign-first second-tx)] + (testing "after fist transaction" + (testing "signing in progress" + (is (get-in sign-first [:db :signing/in-progress?]))) + (testing "qieue is empty" + (is (= (get-in sign-first [:db :signing/queue]) '()))) + (testing "first tx object is parsed" + (is (= (dissoc (get-in sign-first [:db :signing/tx]) :token) + (merge first-tx + {:gas nil + :gasPrice nil + :data nil + :to to + :contact {:address to} + :symbol :ETH + :amount "10"}))))) + (testing "after second transaction" + (testing "signing still in progress" + (is (get-in sign-second [:db :signing/in-progress?]))) + (testing "queue is not empty, second tx in queue" + (is (= (get-in sign-second [:db :signing/queue]) (list second-tx)))) + (testing "check queue does nothing" + (is (not (signing/check-queue sign-second)))) + (let [first-discarded (signing/check-queue (update sign-second :db dissoc :signing/in-progress? :signing/tx))] + (testing "after first transaction discarded" + (testing "signing still in progress" + (is (get-in first-discarded [:db :signing/in-progress?]))) + (testing "qieue is empty" + (is (= (get-in first-discarded [:db :signing/queue]) '()))) + (testing "second tx object is parsed" + (is (= (dissoc (get-in first-discarded [:db :signing/tx]) :token) + (merge second-tx + {:gas nil + :gasPrice nil + :data data + :to contract + :contact {:address contract}})))))))))) \ No newline at end of file diff --git a/test/cljs/status_im/test/signing/gas.cljs b/test/cljs/status_im/test/signing/gas.cljs new file mode 100644 index 000000000000..fa00d5c49d75 --- /dev/null +++ b/test/cljs/status_im/test/signing/gas.cljs @@ -0,0 +1,38 @@ +(ns status-im.test.signing.gas + (:require [cljs.test :refer-macros [deftest is testing]] + [status-im.signing.gas :as signing.gas])) + +(deftest build-edit-test + (testing "an invalid edit" + (let [actual (-> {} + (signing.gas/build-edit :gas "invalid") + (signing.gas/build-edit :gasPrice "0.00000000001"))] + (testing "it marks gasPrice as invalid" + (is (get-in actual [:gasPrice :error]))) + (testing "it does not change value" + (is (= "0.00000000001" (get-in actual [:gasPrice :value])))) + (testing "it marks gas as invalid" + (is (get-in actual [:gas :error]))) + (testing "it does not change gas value" + (is (= "invalid" (get-in actual [:gas :value])))) + (testing "it sets max-fee to 0" + (is (= "0" (:max-fee actual)))))) + (testing "gas price in wei should be round" + (let [actual (-> {} + (signing.gas/build-edit :gas "21000") + (signing.gas/build-edit :gasPrice "0.0000000023"))] + (is (get-in actual [:gasPrice :error])))) + (testing "an valid edit" + (let [actual (-> {} + (signing.gas/build-edit :gas "21000") + (signing.gas/build-edit :gasPrice "10"))] + (testing "it does not mark gas-price as invalid" + (is (not (get-in actual [:gasPrice :error])))) + (testing "it sets the value in number for gas-price, in gwei" + (is (= "10000000000" (str (get-in actual [:gasPrice :value-number]))))) + (testing "it does not mark gas as invalid" + (is (not (get-in actual [:gas :error])))) + (testing "it sets the value in number for gasi" + (is (= "21000" (str (get-in actual [:gas :value-number]))))) + (testing "it calculates max-fee" + (is (= "0.00021" (:max-fee actual))))))) \ No newline at end of file diff --git a/test/cljs/status_im/test/tribute_to_talk/core.cljs b/test/cljs/status_im/test/tribute_to_talk/core.cljs index 7f16d1b471b4..07e4a8a405bc 100644 --- a/test/cljs/status_im/test/tribute_to_talk/core.cljs +++ b/test/cljs/status_im/test/tribute_to_talk/core.cljs @@ -61,39 +61,6 @@ :tribute-to-talk {:snt-amount "1000000000000000000"}}} :wallet {:balance {:STT (money/bignumber "1000000000000000000")}}}}) -(deftest pay-tribute - (testing "transaction with enough funds" - (let [results (tribute-to-talk/pay-tribute - user-cofx - recipient-pk)] - (is (= (dissoc (get-in results [:db :wallet :send-transaction]) :amount) - {:on-result - [:tribute-to-talk.callback/pay-tribute-transaction-sent - "0x04263d74e55775280e75b4a4e9a45ba59fc372793a869c5d9c4fa2100556d9963e3f4fbfa1724ec94a46e6da057540ab248ed1f5eb956e36e3129ecd50fade2c97"], - :to-name "bob", - :amount-text "1", - :method "eth_sendTransaction", - :symbol :ETH, - :send-transaction-message? true, - :from-chat? true, - :from "0x954d4393515747ea75808a0301fb73317ae1e460", - :id "approve", - :sufficient-funds? true, - :on-error nil, - :public-key - "0x04263d74e55775280e75b4a4e9a45ba59fc372793a869c5d9c4fa2100556d9963e3f4fbfa1724ec94a46e6da057540ab248ed1f5eb956e36e3129ecd50fade2c97", - :asset :STT, - :to "0xc55cf4b03948d7ebc8b9e8bad92643703811d162", - :data - "0xa9059cbb000000000000000000000000dff1a5e4e57d9723b3294e0f4413372e3ea9a8ff0000000000000000000000000000000000000000000000000de0b6b3a7640000"})))) - - (testing "insufficient funds" - (is (= (get-in (tribute-to-talk/pay-tribute - (assoc-in user-cofx [:db :wallet] nil) - recipient-pk) - [:db :wallet :send-transaction :sufficient-funds?]) - false)))) - (deftest tribute-transaction-trigger (testing "transaction error" (is (tribute-to-talk/tribute-transaction-trigger diff --git a/translations/en.json b/translations/en.json index c2b575035ebe..5d0d058604bd 100644 --- a/translations/en.json +++ b/translations/en.json @@ -1064,5 +1064,12 @@ "default" : "Default", "token-details" : "Token details", "remove-token" : "Remove token", - "report-bug-email-template": "1. Issue Description\n(Describe the feature you would like, or briefly summarise the bug and what you did, what you expected to happen, and what actually happens. Sections below)\n\n\n2. Steps to reproduce\n(Describe how we can replicate the bug step by step.)\n-Open Status\n-...\n-Step 3, etc.\n\n\n3. Expected behavior\n(Describe what you expected to happen.)\n\n\n4. Actual behavior\n(Describe what actually happened.)\n\n\n5. Attach screenshots that can demo the problem, please\n" + "report-bug-email-template": "1. Issue Description\n(Describe the feature you would like, or briefly summarise the bug and what you did, what you expected to happen, and what actually happens. Sections below)\n\n\n2. Steps to reproduce\n(Describe how we can replicate the bug step by step.)\n-Open Status\n-...\n-Step 3, etc.\n\n\n3. Expected behavior\n(Describe what you expected to happen.)\n\n\n4. Actual behavior\n(Describe what actually happened.)\n\n\n5. Attach screenshots that can demo the problem, please\n", + "update" : "Update", + "sending" : "Sending", + "contract-interaction" : "Contract interaction", + "signing-phrase" : "Signing phrase", + "sign-with-password" : "Sign with password", + "network-fee" : "Network fee", + "signing-a-message" : "Signing a message" }