From 7de2941f26bbfaa4f5948f32dd2ffa4409e1bdcc 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 | 63 ++- .../chat/commands/impl/transactions.cljs | 62 +-- src/status_im/chat/models/input.cljs | 5 + src/status_im/constants.cljs | 4 + src/status_im/ethereum/json_rpc.cljs | 2 +- src/status_im/events.cljs | 27 +- .../extensions/capacities/ethereum.cljs | 57 +-- src/status_im/hardwallet/core.cljs | 3 +- src/status_im/signing/core.cljs | 274 +++++++++++ src/status_im/signing/gas.cljs | 100 ++++ src/status_im/stickers/core.cljs | 9 +- src/status_im/subs.cljs | 116 +++-- 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 | 204 ++++++++ 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 | 56 +++ test/cljs/status_im/test/signing/gas.cljs | 38 ++ .../status_im/test/tribute_to_talk/core.cljs | 33 -- translations/en.json | 10 +- 43 files changed, 1073 insertions(+), 1439 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 3ede316b0d..97465a17ca 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 b886aae49e..a33cb40741 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,55 @@ (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" + [params] + (let [[first-param second-param] params] + (cond + (ethereum/address? first-param) + [first-param second-param] + (ethereum/address? 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 97e35d3e1f..8a18e07cae 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 11c4ada477..6deae1bece 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/constants.cljs b/src/status_im/constants.cljs index db3c846b93..141ab36bbe 100644 --- a/src/status_im/constants.cljs +++ b/src/status_im/constants.cljs @@ -238,6 +238,10 @@ ;; (ethereum/sha3 "Transfer(address,address,uint256)") (def ^:const event-transfer-hash "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef") +(def ^:const method-id-transfer "0xa9059cbb") +(def ^:const method-id-approve "0x095ea7b3") +(def ^:const method-id-approve-and-call "0xcae9ca51") + (def regx-emoji #"^((?:[\u261D\u26F9\u270A-\u270D]|\uD83C[\uDF85\uDFC2-\uDFC4\uDFC7\uDFCA-\uDFCC]|\uD83D[\uDC42\uDC43\uDC46-\uDC50\uDC66-\uDC69\uDC6E\uDC70-\uDC78\uDC7C\uDC81-\uDC83\uDC85-\uDC87\uDCAA\uDD74\uDD75\uDD7A\uDD90\uDD95\uDD96\uDE45-\uDE47\uDE4B-\uDE4F\uDEA3\uDEB4-\uDEB6\uDEC0\uDECC]|\uD83E[\uDD18-\uDD1C\uDD1E\uDD1F\uDD26\uDD30-\uDD39\uDD3D\uDD3E\uDDD1-\uDDDD])(?:\uD83C[\uDFFB-\uDFFF])?|(?:[\u231A\u231B\u23E9-\u23EC\u23F0\u23F3\u25FD\u25FE\u2614\u2615\u2648-\u2653\u267F\u2693\u26A1\u26AA\u26AB\u26BD\u26BE\u26C4\u26C5\u26CE\u26D4\u26EA\u26F2\u26F3\u26F5\u26FA\u26FD\u2705\u270A\u270B\u2728\u274C\u274E\u2753-\u2755\u2757\u2795-\u2797\u27B0\u27BF\u2B1B\u2B1C\u2B50\u2B55]|\uD83C[\uDC04\uDCCF\uDD8E\uDD91-\uDD9A\uDDE6-\uDDFF\uDE01\uDE1A\uDE2F\uDE32-\uDE36\uDE38-\uDE3A\uDE50\uDE51\uDF00-\uDF20\uDF2D-\uDF35\uDF37-\uDF7C\uDF7E-\uDF93\uDFA0-\uDFCA\uDFCF-\uDFD3\uDFE0-\uDFF0\uDFF4\uDFF8-\uDFFF]|\uD83D[\uDC00-\uDC3E\uDC40\uDC42-\uDCFC\uDCFF-\uDD3D\uDD4B-\uDD4E\uDD50-\uDD67\uDD7A\uDD95\uDD96\uDDA4\uDDFB-\uDE4F\uDE80-\uDEC5\uDECC\uDED0-\uDED2\uDEEB\uDEEC\uDEF4-\uDEF8]|\uD83E[\uDD10-\uDD3A\uDD3C-\uDD3E\uDD40-\uDD45\uDD47-\uDD4C\uDD50-\uDD6B\uDD80-\uDD97\uDDC0\uDDD0-\uDDE6])|(?:[#\*0-9\xA9\xAE\u203C\u2049\u2122\u2139\u2194-\u2199\u21A9\u21AA\u231A\u231B\u2328\u23CF\u23E9-\u23F3\u23F8-\u23FA\u24C2\u25AA\u25AB\u25B6\u25C0\u25FB-\u25FE\u2600-\u2604\u260E\u2611\u2614\u2615\u2618\u261D\u2620\u2622\u2623\u2626\u262A\u262E\u262F\u2638-\u263A\u2640\u2642\u2648-\u2653\u2660\u2663\u2665\u2666\u2668\u267B\u267F\u2692-\u2697\u2699\u269B\u269C\u26A0\u26A1\u26AA\u26AB\u26B0\u26B1\u26BD\u26BE\u26C4\u26C5\u26C8\u26CE\u26CF\u26D1\u26D3\u26D4\u26E9\u26EA\u26F0-\u26F5\u26F7-\u26FA\u26FD\u2702\u2705\u2708-\u270D\u270F\u2712\u2714\u2716\u271D\u2721\u2728\u2733\u2734\u2744\u2747\u274C\u274E\u2753-\u2755\u2757\u2763\u2764\u2795-\u2797\u27A1\u27B0\u27BF\u2934\u2935\u2B05-\u2B07\u2B1B\u2B1C\u2B50\u2B55\u3030\u303D\u3297\u3299]|\uD83C[\uDC04\uDCCF\uDD70\uDD71\uDD7E\uDD7F\uDD8E\uDD91-\uDD9A\uDDE6-\uDDFF\uDE01\uDE02\uDE1A\uDE2F\uDE32-\uDE3A\uDE50\uDE51\uDF00-\uDF21\uDF24-\uDF93\uDF96\uDF97\uDF99-\uDF9B\uDF9E-\uDFF0\uDFF3-\uDFF5\uDFF7-\uDFFF]|\uD83D[\uDC00-\uDCFD\uDCFF-\uDD3D\uDD49-\uDD4E\uDD50-\uDD67\uDD6F\uDD70\uDD73-\uDD7A\uDD87\uDD8A-\uDD8D\uDD90\uDD95\uDD96\uDDA4\uDDA5\uDDA8\uDDB1\uDDB2\uDDBC\uDDC2-\uDDC4\uDDD1-\uDDD3\uDDDC-\uDDDE\uDDE1\uDDE3\uDDE8\uDDEF\uDDF3\uDDFA-\uDE4F\uDE80-\uDEC5\uDECB-\uDED2\uDEE0-\uDEE5\uDEE9\uDEEB\uDEEC\uDEF0\uDEF3-\uDEF8]|\uD83E[\uDD10-\uDD3A\uDD3C-\uDD3E\uDD40-\uDD45\uDD47-\uDD4C\uDD50-\uDD6B\uDD80-\uDD97\uDDC0\uDDD0-\uDDE6])\uFE0F|[\t-\r \xA0\u1680\u2000-\u200A\u2028\u2029\u202F\u205F\u3000\uFEFF])+$") (def regx-rtl-characters #"[^\u0591-\u06EF\u06FA-\u07FF\u200F\u202B\u202E\uFB1D-\uFDFD\uFE70-\uFEFC]*?[\u0591-\u06EF\u06FA-\u07FF\u200F\u202B\u202E\uFB1D-\uFDFD\uFE70-\uFEFC]") (def regx-url #"(?i)(?:[a-z][\w-]+:(?:/{1,3}|[a-z0-9%])|www\d{0,3}[.]|[a-z0-9\-]+[.][a-z]{1,4}/?)(?:[^\s()<>]+|\(([^\s()<>]+|(\([^\s()<>]+\)))*\))+(?:\(([^\s()<>]+|(\([^\s()<>]+\)))*\)|[^\s`!()\[\]{};:\'\".,<>?«»“”‘’]){0,}") diff --git a/src/status_im/ethereum/json_rpc.cljs b/src/status_im/ethereum/json_rpc.cljs index cd54094ac3..137c25eb56 100644 --- a/src/status_im/ethereum/json_rpc.cljs +++ b/src/status_im/ethereum/json_rpc.cljs @@ -13,7 +13,7 @@ "eth_getBalance" {:on-result money/bignumber} "eth_estimateGas" - {:on-result money/bignumber} + {:on-result #(money/bignumber (int (* % 1.2)))} "eth_gasPrice" {:on-result money/bignumber} "eth_getBlockByHash" diff --git a/src/status_im/events.cljs b/src/status_im/events.cljs index c552dd378a..8ebc70c7da 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}] @@ -2135,3 +2113,8 @@ :shake-event (fn [cofx _] (logging/show-logs-dialog cofx))) + +(re-frame/reg-fx + :dismiss-keyboard + (fn [] + (react/dismiss-keyboard!))) \ No newline at end of file diff --git a/src/status_im/extensions/capacities/ethereum.cljs b/src/status_im/extensions/capacities/ethereum.cljs index f0070b50c0..b30da10358 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 fc8509082a..e307842615 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 0000000000..7ac401c1ae --- /dev/null +++ b/src/status_im/signing/core.cljs @@ -0,0 +1,274 @@ +(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 + :error nil + :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-method-type [data] + (cond + (string/starts-with? data constants/method-id-transfer) + :transfer + (string/starts-with? data constants/method-id-approve) + :approve + (string/starts-with? data constants/method-id-approve-and-call) + :approve-and-call)) + +(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)) + (when-let [type (get-method-type data)] + (let [[address value _] (abi-spec/decode + (str "0x" (subs data 10)) + (if (= type :approve-and-call) ["address" "uint256" "bytes"] ["address" "uint256"]))] + (when (and address value) + {:to address + :contact (get-contact db address) + :contract to + :approve? (not= type :transfer) + :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 (or (not (zero? eth-amount)) (nil? data))) + {: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)) + :dismiss-keyboard + nil} + #(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 transaction 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")})))) + +(defn normalize-tx-obj [db tx] + (if (get-in tx [:tx-obj :from]) + tx + (assoc-in tx [:tx-obj :from] (ethereum/current-address db)))) + +(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 (normalize-tx-obj db 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 0000000000..28933d58db --- /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 828cb20ddb..ad4b9bf332 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 c49ecd5843..acc52fc80b 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,66 @@ (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 approve?]} balance]] + (if (and amount token (not approve?)) + (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))] + (or 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 5da77225c3..6cc94e1b77 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 1adb1e4e43..943eee66c3 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 f4d28f8893..6e59b80055 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 d134354e2a..5ad192b353 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 ef91510980..26678204b2 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 97467a0577..048ec9972d 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 233a885a6d..8da4fee8c1 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 be1d02cd5c..9476c8620d 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 4239d3190d..d80afd0569 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 b16ed621ca..3c44a5919c 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 b7cbc69c71..355f955c63 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 27b7dda31a..35d86d875e 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 0000000000..93f353e21a --- /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 0000000000..9bc0065724 --- /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 0000000000..9eb3ccca2a --- /dev/null +++ b/src/status_im/ui/screens/signing/views.cljs @@ -0,0 +1,204 @@ +(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 approve?] :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 (if approve? (i18n/label :t/authorize) (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/view {:padding 6} + [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/view {:padding 6} + [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 approve?] :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] + (when-not approve? + [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 270}])}] + [react/view {:align-items :center :margin-top 16 :margin-bottom 40} + [button/primary-button {:on-press #(re-frame/dispatch [:set :signing/sign {:type :password}]) + :disabled? (or amount-error gas-error)} + (i18n/label :t/sign-with-password)]]])]))) + +(defn signing-view [tx window-height] + (let [bottom-anim-value (anim/create-value (- window-height)) + alpha-value (anim/create-value 0) + current-tx (reagent/atom nil) + update? (reagent/atom nil)] + (reagent/create-class + {: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))))) + :reagent-render (fn [] + (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 :approve? :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 b9d348d179..0b7b9c250f 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 08d08997c7..d4f789f744 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 0347224756..e0419c7f93 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 ef73a5fe28..0000000000 --- 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 f38229016e..77af62b27d 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 9d6136222d..8c9f6ead0b 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 09d0436e5e..0000000000 --- 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 4f7fb5dbfe..ee9f01c462 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 ff80ff2476..43705a3a60 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 f36d1bb655..0000000000 --- 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 1921421253..547013ed79 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 0000000000..31eb286382 --- /dev/null +++ b/test/cljs/status_im/test/signing/core.cljs @@ -0,0 +1,56 @@ +(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 + :from nil + :value amount1-hex}} + second-tx {:tx-obj {:to contract + :from nil + :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 0000000000..fa00d5c49d --- /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 7f16d1b471..07e4a8a405 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 c2b575035e..61fc812432 100644 --- a/translations/en.json +++ b/translations/en.json @@ -1064,5 +1064,13 @@ "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", + "authorize" : "Authorize", + "contract-interaction" : "Contract interaction", + "signing-phrase" : "Signing phrase", + "network-fee" : "Network fee", + "sign-with-password" : "Sign with password", + "signing-a-message" : "Signing a message" }