[#8026] [Wallet] Sign transaction module

This commit is contained in:
Andrey Shovkoplyas 2019-06-05 13:11:47 +02:00
parent eb4ba4c50e
commit 7de2941f26
No known key found for this signature in database
GPG Key ID: EAAB7C8622D860A4
43 changed files with 1073 additions and 1439 deletions

View File

@ -301,20 +301,13 @@
(case current-view (case current-view
(:wallet (:wallet
:wallet-send-transaction :wallet-send-transaction
:wallet-transaction-sent
:wallet-request-transaction :wallet-request-transaction
:wallet-send-transaction-chat
:wallet-send-assets :wallet-send-assets
:wallet-request-assets :wallet-request-assets
:choose-recipient :choose-recipient
:recent-recipients :recent-recipients
:wallet-send-transaction-modal
:wallet-transaction-sent-modal
:wallet-send-transaction-request :wallet-send-transaction-request
:wallet-transaction-fee
:wallet-sign-message-modal
:contact-code :contact-code
:wallet-onboarding-setup
:wallet-modal :wallet-modal
:wallet-onboarding-setup-modal :wallet-onboarding-setup-modal
:wallet-settings-hook) :wallet-settings-hook)

View File

@ -20,7 +20,8 @@
[status-im.utils.random :as random] [status-im.utils.random :as random]
[status-im.utils.types :as types] [status-im.utils.types :as types]
[status-im.utils.universal-links.core :as universal-links] [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 (fx/defn initialize-browsers
[{:keys [db all-stored-browsers]}] [{:keys [db all-stored-browsers]}]
@ -274,19 +275,55 @@
(navigation/navigate-to-cofx :browser nil) (navigation/navigate-to-cofx :browser nil)
(resolve-url 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 (fx/defn web3-send-async
[{:keys [db]} {:keys [method] :as payload} message-id] [cofx {:keys [method params id] :as payload} message-id]
(if (or (= constants/web3-send-transaction method) (let [message? (constants/web3-sign-message? method)]
(constants/web3-sign-message? method)) (if (or message? (= constants/web3-send-transaction method))
{:db (update-in db [:wallet :transactions-queue] conj {:message-id message-id :payload payload}) (let [[address data] (when message? (normalize-sign-message-params params))]
;;TODO(yenda): refactor check-dapps-transactions-queue to remove this dispatch (when (or (not message?) (and address data))
:dispatch [:check-dapps-transactions-queue]} (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 {:browser/call-rpc [payload
#(re-frame/dispatch [:browser.callback/call-rpc #(re-frame/dispatch [:browser.callback/call-rpc
{:type constants/web3-send-async-callback {:type constants/web3-send-async-callback
:messageId message-id :messageId message-id
:error %1 :error %1
:result %2}])]})) :result %2}])]})))
(fx/defn send-to-bridge (fx/defn send-to-bridge
[cofx message] [cofx message]

View File

@ -6,7 +6,6 @@
:as :as
transactions-styles] transactions-styles]
[status-im.chat.commands.protocol :as protocol] [status-im.chat.commands.protocol :as protocol]
[status-im.contact.db :as db.contact]
[status-im.data-store.messages :as messages-store] [status-im.data-store.messages :as messages-store]
[status-im.ethereum.core :as ethereum] [status-im.ethereum.core :as ethereum]
[status-im.ethereum.tokens :as tokens] [status-im.ethereum.tokens :as tokens]
@ -18,16 +17,13 @@
[status-im.ui.components.list.views :as list] [status-im.ui.components.list.views :as list]
[status-im.ui.components.react :as react] [status-im.ui.components.react :as react]
[status-im.ui.components.svgimage :as svgimage] [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.ui.screens.wallet.utils :as wallet.utils]
[status-im.utils.datetime :as datetime] [status-im.utils.datetime :as datetime]
[status-im.utils.fx :as fx]
[status-im.utils.money :as money] [status-im.utils.money :as money]
[status-im.utils.platform :as platform] [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]])) (:require-macros [status-im.utils.views :refer [defview letsubs]]))
;; common `send/request` functionality ;; common `send/request` functionality
@ -304,39 +300,21 @@
(send-preview command-message)) (send-preview command-message))
protocol/Yielding protocol/Yielding
(yield-control [_ {{{amount :amount asset :asset} :params} :content} {:keys [db] :as cofx}] (yield-control [_ {{{amount :amount asset :asset} :params} :content} {:keys [db] :as cofx}]
;; Prefill wallet and navigate there (let [{:keys [symbol decimals address]} (tokens/asset-for (:wallet/all-tokens db) (keyword (:chain db)) (keyword asset))
(let [recipient-contact (or {:keys [value]} (wallet.db/parse-amount amount decimals)
(get-in db [:contacts/contacts (:current-chat-id db)]) current-chat-id (:current-chat-id db)
(db.contact/public-key->new-contact (: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)
sender-account (:account/account db) to-norm (ethereum/normalized-address (if (= symbol :ETH) to address))
chain (keyword (:chain db)) tx-obj (if (= symbol :ETH)
symbol-param (keyword asset) {:to to-norm
all-tokens (:wallet/all-tokens db) :value amount-hex}
{:keys [symbol decimals]} (tokens/asset-for all-tokens chain symbol-param) {:to to-norm
{:keys [value error]} (wallet.db/parse-amount amount decimals) :data (abi-spec/encode "transfer(address,uint256)" [to amount-hex])})]
next-view-id (if (:wallet-set-up-passed? sender-account) (signing/sign cofx {:tx-obj tx-obj
:wallet-send-modal-stack :on-result [:chat/send-transaction-result current-chat-id {:address to-norm
:wallet-send-modal-stack-with-onboarding)] :asset (name symbol)
(fx/merge cofx :amount amount}]})))
{: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 {}))))
protocol/EnhancedParameters protocol/EnhancedParameters
(enhance-send-parameters [_ parameters cofx] (enhance-send-parameters [_ parameters cofx]
(-> parameters (-> parameters

View File

@ -172,6 +172,11 @@
(command-not-complete-fx input-text current-chat-id cofx)) (command-not-complete-fx input-text current-chat-id cofx))
(plain-text-message-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 ;; effects
(re-frame/reg-fx (re-frame/reg-fx

View File

@ -238,6 +238,10 @@
;; (ethereum/sha3 "Transfer(address,address,uint256)") ;; (ethereum/sha3 "Transfer(address,address,uint256)")
(def ^:const event-transfer-hash "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef") (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-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-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,}") (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,}")

View File

@ -13,7 +13,7 @@
"eth_getBalance" "eth_getBalance"
{:on-result money/bignumber} {:on-result money/bignumber}
"eth_estimateGas" "eth_estimateGas"
{:on-result money/bignumber} {:on-result #(money/bignumber (int (* % 1.2)))}
"eth_gasPrice" "eth_gasPrice"
{:on-result money/bignumber} {:on-result money/bignumber}
"eth_getBlockByHash" "eth_getBlockByHash"

View File

@ -2102,28 +2102,6 @@
(fn [cofx [_ hash]] (fn [cofx [_ hash]]
(wallet/open-transaction-details 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 (handlers/register-handler-fx
:wallet.setup.ui/navigate-back-pressed :wallet.setup.ui/navigate-back-pressed
(fn [{:keys [db] :as cofx}] (fn [{:keys [db] :as cofx}]
@ -2135,3 +2113,8 @@
:shake-event :shake-event
(fn [cofx _] (fn [cofx _]
(logging/show-logs-dialog cofx))) (logging/show-logs-dialog cofx)))
(re-frame/reg-fx
:dismiss-keyboard
(fn []
(react/dismiss-keyboard!)))

View File

@ -4,22 +4,17 @@
[status-im.ethereum.abi-spec :as abi-spec] [status-im.ethereum.abi-spec :as abi-spec]
[status-im.ethereum.core :as ethereum] [status-im.ethereum.core :as ethereum]
[status-im.ethereum.ens :as ens] [status-im.ethereum.ens :as ens]
[status-im.i18n :as i18n]
[status-im.native-module.core :as status] [status-im.native-module.core :as status]
[status-im.ui.screens.navigation :as navigation] [status-im.ui.screens.navigation :as navigation]
[status-im.utils.fx :as fx] [status-im.utils.fx :as fx]
[status-im.utils.handlers :as handlers] [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.utils.types :as types]
[status-im.wallet.core :as wallet])) [status-im.signing.core :as signing]))
(handlers/register-handler-fx (handlers/register-handler-fx
:extensions/wallet-ui-on-success :extensions/wallet-ui-on-success
(fn [cofx [_ on-success _ result _]] (fn [_ [_ on-success result]]
(fx/merge cofx (when on-success (on-success {:value result}))))
(when on-success (on-success {:value result}))
(navigation/navigate-back))))
(handlers/register-handler-fx (handlers/register-handler-fx
:extensions/wallet-ui-on-failure :extensions/wallet-ui-on-failure
@ -42,35 +37,11 @@
#(f db (assoc arguments address-keyword %)))) #(f db (assoc arguments address-keyword %))))
(f db arguments)))) (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}] (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]) (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))) :data (when (and method params) (abi-spec/encode method params)))
transaction (prepare-extension-transaction tx-object (:contacts/contacts db) on-success on-failure)] :on-result [:extensions/wallet-ui-on-success on-success]
(wallet/open-modal-wallet-for-transaction db transaction tx-object))) :on-error [:extensions/wallet-ui-on-failure on-failure]}))
(handlers/register-handler-fx (handlers/register-handler-fx
:extensions/ethereum-send-transaction :extensions/ethereum-send-transaction
@ -381,22 +352,16 @@
(when on-failure (when on-failure
(on-failure {:value (str "'" name "' is not a valid name")}))))) (on-failure {:value (str "'" name "' is not a valid name")})))))
;; EXTENSION SIGN -> SIGN MESSAGE
(handlers/register-handler-fx (handlers/register-handler-fx
:extensions/ethereum-sign :extensions/ethereum-sign
(fn [{db :db :as cofx} [_ _ {:keys [message data id on-success on-failure]}]] (fn [{db :db :as cofx} [_ _ {:keys [message data id on-success on-failure]}]]
(if (and message data) (if (and message data)
(when on-failure (when on-failure
(on-failure {:error "only one of :message and :data can be used"})) (on-failure {:error "only one of :message and :data can be used"}))
(fx/merge cofx (signing/sign cofx {:message {:address (ethereum/current-address db)
{:db (assoc-in db [:wallet :send-transaction] :data (or data (ethereum/utf8-to-hex message))}
{: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-result [:extensions/wallet-ui-on-success on-success]
:on-error [:extensions/wallet-ui-on-failure on-failure] :on-error [:extensions/wallet-ui-on-failure on-failure]}))))
:method constants/web3-personal-sign})}
(navigation/navigate-to-cofx :wallet-sign-message-modal nil)))))
(handlers/register-handler-fx (handlers/register-handler-fx
:extensions/ethereum-create-address :extensions/ethereum-create-address

View File

@ -62,7 +62,8 @@
(if navigate-to-browser? (if navigate-to-browser?
(fx/merge cofx (fx/merge cofx
{:db (assoc-in db [:hardwallet :on-card-connected] nil)} {: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)) (navigation/navigate-to-cofx :browser nil))
(if (= :enter-pin-login (:view-id db)) (if (= :enter-pin-login (:view-id db))
(navigation/navigate-to-clean cofx :accounts nil) (navigation/navigate-to-clean cofx :accounts nil)

View File

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

View File

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

View File

@ -11,7 +11,7 @@
[status-im.utils.fx :as fx] [status-im.utils.fx :as fx]
[status-im.utils.multihash :as multihash] [status-im.utils.multihash :as multihash]
[status-im.utils.utils :as utils] [status-im.utils.utils :as utils]
[status-im.wallet.core :as wallet])) [status-im.signing.core :as signing]))
(defn pack-data-callback (defn pack-data-callback
[id open?] [id open?]
@ -142,17 +142,15 @@
(fx/defn approve-pack (fx/defn approve-pack
[{db :db :as cofx} pack-id price] [{db :db :as cofx} pack-id price]
(let [address (ethereum/current-address db) (let [address (ethereum/current-address db)
chain (ethereum/chain-keyword db)
stickers-contract (contracts/get-address db :status/stickers) stickers-contract (contracts/get-address db :status/stickers)
snt-contract (contracts/get-address db :status/snt)] snt-contract (contracts/get-address db :status/snt)]
(wallet/eth-transaction-call (signing/eth-transaction-call
cofx cofx
{:contract snt-contract {:contract snt-contract
:method "approveAndCall(address,uint256,bytes)" :method "approveAndCall(address,uint256,bytes)"
:params [stickers-contract :params [stickers-contract
price price
(abi-spec/encode "buyToken(uint256,address)" (abi-spec/encode "buyToken(uint256,address)" [pack-id address])]
[pack-id address])]
:on-result [:stickers/pending-pack pack-id]}))) :on-result [:stickers/pending-pack pack-id]})))
(fx/defn pending-pack (fx/defn pending-pack
@ -163,7 +161,6 @@
(fx/merge cofx (fx/merge cofx
{:db (update db :stickers/packs-pending conj id) {:db (update db :stickers/packs-pending conj id)
:stickers/owned-packs-fx [contract address]} :stickers/owned-packs-fx [contract address]}
(navigation/navigate-to-clean :wallet-transaction-sent-modal {})
#(when (zero? (count (:stickers/packs-pending db))) #(when (zero? (count (:stickers/packs-pending db)))
{:stickers/set-pending-timout-fx nil}))))) {:stickers/set-pending-timout-fx nil})))))

View File

@ -40,6 +40,7 @@
[status-im.utils.universal-links.core :as links] [status-im.utils.universal-links.core :as links]
[status-im.wallet.core :as wallet] [status-im.wallet.core :as wallet]
[status-im.wallet.db :as wallet.db] [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.connect.subs
status-im.ui.screens.hardwallet.settings.subs status-im.ui.screens.hardwallet.settings.subs
status-im.ui.screens.hardwallet.pin.subs status-im.ui.screens.hardwallet.pin.subs
@ -163,6 +164,11 @@
;;ethereum ;;ethereum
(reg-root-key-sub :ethereum/current-block :ethereum/current-block) (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 ============================================================================================================== ;;GENERAL ==============================================================================================================
(re-frame/reg-sub (re-frame/reg-sub
@ -1303,10 +1309,10 @@
(:send-transaction wallet))) (:send-transaction wallet)))
(re-frame/reg-sub (re-frame/reg-sub
:wallet.send/advanced? :wallet.send/symbol
:<- [::send-transaction] :<- [::send-transaction]
(fn [send-transaction] (fn [send-transaction]
(:advanced? send-transaction))) (:symbol send-transaction)))
(re-frame/reg-sub (re-frame/reg-sub
:wallet.send/camera-flashlight :wallet.send/camera-flashlight
@ -1314,41 +1320,6 @@
(fn [send-transaction] (fn [send-transaction]
(:camera-flashlight 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 (defn check-sufficient-funds
[{:keys [sufficient-funds?] :as transaction} balance symbol amount] [{:keys [sufficient-funds?] :as transaction} balance symbol amount]
(cond-> transaction (cond-> transaction
@ -1377,17 +1348,9 @@
:<- [:balance] :<- [:balance]
(fn [[{:keys [amount symbol] :as transaction} balance]] (fn [[{:keys [amount symbol] :as transaction} balance]]
(-> transaction (-> transaction
(wallet/add-max-fee)
(check-sufficient-funds balance symbol amount) (check-sufficient-funds balance symbol amount)
(check-sufficient-gas 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 (re-frame/reg-sub
:wallet/settings :wallet/settings
:<- [:wallet] :<- [:wallet]
@ -1868,3 +1831,66 @@
(or (string/blank? screen-snt-amount) (or (string/blank? screen-snt-amount)
(#{"0" "0.0" "0.00"} screen-snt-amount) (#{"0" "0.0" "0.00"} screen-snt-amount)
(string/ends-with? 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))))

View File

@ -4,20 +4,17 @@
[re-frame.core :as re-frame] [re-frame.core :as re-frame]
[status-im.accounts.update.core :as accounts.update] [status-im.accounts.update.core :as accounts.update]
[status-im.contact.core :as contact] [status-im.contact.core :as contact]
[status-im.contact.db :as contact.db]
[status-im.ethereum.contracts :as contracts] [status-im.ethereum.contracts :as contracts]
[status-im.ethereum.core :as ethereum] [status-im.ethereum.core :as ethereum]
[status-im.ethereum.json-rpc :as json-rpc] [status-im.ethereum.json-rpc :as json-rpc]
[status-im.ethereum.tokens :as tokens]
[status-im.ethereum.transactions.core :as transactions] [status-im.ethereum.transactions.core :as transactions]
[status-im.tribute-to-talk.db :as tribute-to-talk.db] [status-im.tribute-to-talk.db :as tribute-to-talk.db]
[status-im.tribute-to-talk.whitelist :as whitelist] [status-im.tribute-to-talk.whitelist :as whitelist]
[status-im.ui.screens.navigation :as navigation] [status-im.ui.screens.navigation :as navigation]
[status-im.utils.fx :as fx] [status-im.utils.fx :as fx]
[status-im.utils.money :as money] [status-im.utils.money :as money]
[status-im.wallet.core :as wallet] [taoensso.timbre :as log]
[status-im.wallet.db :as wallet.db] [status-im.signing.core :as signing]))
[taoensso.timbre :as log]))
(defn add-transaction-hash (defn add-transaction-hash
[message db] [message db]
@ -88,7 +85,7 @@
(fx/defn set-tribute-signing-flow (fx/defn set-tribute-signing-flow
[{:keys [db] :as cofx} tribute] [{:keys [db] :as cofx} tribute]
(if-let [contract (contracts/get-address db :status/tribute-to-talk)] (if-let [contract (contracts/get-address db :status/tribute-to-talk)]
(wallet/eth-transaction-call (signing/eth-transaction-call
cofx cofx
{:contract contract {:contract contract
:method "setTribute(uint256)" :method "setTribute(uint256)"
@ -263,28 +260,15 @@
(fx/defn pay-tribute (fx/defn pay-tribute
{:events [:tribute-to-talk.ui/on-pay-to-chat-pressed]} {:events [:tribute-to-talk.ui/on-pay-to-chat-pressed]}
[{:keys [db] :as cofx} public-key] [{: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]) (get-in db [:contacts/contacts public-key])
{:keys [snt-amount]} tribute-to-talk {:keys [snt-amount]} tribute-to-talk]
symbol (ethereum/snt-symbol db) (signing/eth-transaction-call
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
cofx cofx
{:contract (contracts/get-address db :status/snt) {:contract (contracts/get-address db :status/snt)
:method "transfer(address,uint256)" :method "transfer(address,uint256)"
:params [address snt-amount] :params [address snt-amount]
:details {:to-name name :on-result [:tribute-to-talk.callback/pay-tribute-transaction-sent public-key]})))
: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]})))
(defn tribute-transaction-trigger (defn tribute-transaction-trigger
[db {:keys [block error?]}] [db {:keys [block error?]}]
@ -306,12 +290,11 @@
(fx/defn on-pay-tribute-transaction-sent (fx/defn on-pay-tribute-transaction-sent
{:events [:tribute-to-talk.callback/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 (fx/merge cofx
{:db (assoc-in db [:contacts/contacts public-key {:db (assoc-in db [:contacts/contacts public-key
:tribute-to-talk :transaction-hash] :tribute-to-talk :transaction-hash]
transaction-hash)} transaction-hash)}
(navigation/navigate-to-clean :wallet-transaction-sent-modal {})
(transactions/watch-transaction (transactions/watch-transaction
transaction-hash transaction-hash
{:trigger-fn {:trigger-fn
@ -340,14 +323,13 @@
(fx/defn on-set-tribute-transaction-sent (fx/defn on-set-tribute-transaction-sent
{:events [:tribute-to-talk.callback/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 (let [{:keys [snt-amount message]} (get-in db [:navigation/screen-params
:tribute-to-talk])] :tribute-to-talk])]
(fx/merge cofx (fx/merge cofx
{:db (assoc-in db [:navigation/screen-params {:db (assoc-in db [:navigation/screen-params
:tribute-to-talk :state] :tribute-to-talk :state]
:pending)} :pending)}
(navigation/navigate-to-clean :wallet-transaction-sent-modal {})
(update-settings {:update {:transaction transaction-hash (update-settings {:update {:transaction transaction-hash
:snt-amount snt-amount :snt-amount snt-amount
:message message}}) :message message}})

View File

@ -102,7 +102,7 @@
height content on-cancel] height content on-cancel]
:or {on-cancel #(re-frame/dispatch [:bottom-sheet/hide])} :or {on-cancel #(re-frame/dispatch [:bottom-sheet/hide])}
:as opts}] :as opts}]
[react/view [react/keyboard-avoiding-view
(merge (merge
(pan-handlers (swipe-pan-responder opts)) (pan-handlers (swipe-pan-responder opts))
{:style styles/container}) {:style styles/container})

View File

@ -53,10 +53,10 @@
;; See https://github.com/facebook/react-native/issues/13760 ;; See https://github.com/facebook/react-native/issues/13760
:overflow :hidden}}) :overflow :hidden}})
(def primary-button (defn primary-button [disabled?]
(merge (merge
button-borders 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}) (def primary-button-text {:color colors/white})

View File

@ -18,13 +18,13 @@
[react/text {:style (merge styles/button-text [react/text {:style (merge styles/button-text
text-style text-style
(when disabled? (when disabled?
{:opacity 0.4}))} {:color colors/gray}))}
label] label]
icon]]) 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 [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)) :text-style (merge styles/primary-button-text text-style))
label]) label])

View File

@ -19,8 +19,7 @@
:color colors/gray}) :color colors/gray})
(def accessory-text (def accessory-text
{:color colors/gray {:color colors/gray})
:margin-right 8})
(defn radius [size] (/ size 2)) (defn radius [size] (/ size 2))
@ -28,3 +27,8 @@
{:border-radius (radius size) {:border-radius (radius size)
:width size :width size
:height size}) :height size})
(def error
{:bottom-value 0
:color colors/red-light
:font-size 12})

View File

@ -3,7 +3,8 @@
[status-im.ui.components.icons.vector-icons :as icons] [status-im.ui.components.icons.vector-icons :as icons]
[status-im.ui.components.react :as react] [status-im.ui.components.react :as react]
[status-im.ui.components.list-item.styles :as styles] [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 ; type - optional :default , :small
@ -11,7 +12,7 @@
; theme - optional :default, :wallet ; 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)] (let [small? (= :small type)]
[react/touchable-highlight {:on-press on-press :disabled (not on-press)} [react/touchable-highlight {:on-press on-press :disabled (not on-press)}
[react/view {:style (styles/container small?)} [react/view {:style (styles/container small?)}
@ -26,7 +27,7 @@
[react/image {:source (utils.image/source image-path) [react/image {:source (utils.image/source image-path)
:style (styles/photo 40)}]]) :style (styles/photo 40)}]])
;;Title ;;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) [react/text {:style (styles/title small? subtitle)
:number-of-lines 1 :number-of-lines 1
:ellipsize-mode :tail} :ellipsize-mode :tail}
@ -38,15 +39,26 @@
:ellipsize-mode :tail} :ellipsize-mode :tail}
subtitle])] subtitle])]
;;Accessories ;;Accessories
[react/view {:flex 1}]
(for [accessory accessories] (for [accessory accessories]
(with-meta (with-meta
(cond
(= :chevron accessory)
[react/view
[icons/icon :main-icons/next {:color colors/gray-transparent-40}]]
(= :check accessory)
[react/view
[icons/icon :main-icons/check {:color colors/gray}]]
:else
[react/view {:padding-right 8 :flex-shrink 1}
(cond (cond
(string? accessory) (string? accessory)
[react/text {:style styles/accessory-text} [react/text {:style styles/accessory-text}
accessory] accessory]
(= :chevron accessory) (vector? accessory)
[icons/icon :main-icons/next {:color colors/gray-transparent-40}] accessory
(= :check accessory) :else
[icons/icon :main-icons/check {:color colors/gray}] [accessory])])
:else accessory) {:key accessory}))
{:key accessory}))]])) (when error
[tooltip/tooltip error styles/error])]]))

View File

@ -74,8 +74,6 @@
:wallet-add-custom-token {:type :wallet} :wallet-add-custom-token {:type :wallet}
:wallet-sign-message-modal {:type :modal-wallet} :wallet-sign-message-modal {:type :modal-wallet}
:wallet-settings-hook {:type :wallet} :wallet-settings-hook {:type :wallet}
:wallet-transaction-sent {:type :transparent}
:wallet-transaction-sent-modal {:type :modal-wallet}
:wallet-transactions-filter {:type :modal-main}} :wallet-transactions-filter {:type :modal-main}}
view-id)) view-id))

View File

@ -204,6 +204,12 @@
(spec/def :extensions/profile (spec/nilable any?)) (spec/def :extensions/profile (spec/nilable any?))
(spec/def :wallet/custom-token-screen (spec/nilable map?)) (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 (spec/def ::db (spec/keys :opt [:contacts/contacts
:contacts/new-identity :contacts/new-identity
:contacts/new-identity-error :contacts/new-identity-error
@ -279,7 +285,12 @@
:bottom-sheet/view :bottom-sheet/view
:bottom-sheet/options :bottom-sheet/options
:extensions/profile :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 :opt-un [::modal
::was-modal? ::was-modal?
::rpc-url ::rpc-url

View File

@ -232,7 +232,6 @@
:hardwallet-connect-settings (hardwallet/hardwallet-connect-screen-did-load %) :hardwallet-connect-settings (hardwallet/hardwallet-connect-screen-did-load %)
:hardwallet-connect-modal (hardwallet/hardwallet-connect-screen-did-load %) :hardwallet-connect-modal (hardwallet/hardwallet-connect-screen-did-load %)
:hardwallet-authentication-method (hardwallet/authentication-method-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 %) :accounts (hardwallet/accounts-screen-did-load %)
:chat (mark-messages-seen %) :chat (mark-messages-seen %)
nil)))) nil))))

View File

@ -2,17 +2,11 @@
(def modal-screens (def modal-screens
[{:name :wallet-send-modal-stack [{:name :wallet-send-modal-stack
:screens [:wallet-send-transaction-modal :screens [:hardwallet-connect-modal
:wallet-transaction-sent-modal
:wallet-transaction-fee
:hardwallet-connect-modal
:enter-pin-modal] :enter-pin-modal]
:config {:initialRouteName :wallet-send-transaction-modal}} :config {:initialRouteName :wallet-send-transaction-modal}}
{:name :wallet-send-modal-stack-with-onboarding {:name :wallet-send-modal-stack-with-onboarding
:screens [:wallet-onboarding-setup-modal :screens [:wallet-onboarding-setup-modal
:wallet-send-transaction-modal
:wallet-transaction-sent-modal
:wallet-transaction-fee
:hardwallet-connect-modal :hardwallet-connect-modal
:enter-pin-modal] :enter-pin-modal]
:config {:initialRouteName :wallet-onboarding-setup-modal}} :config {:initialRouteName :wallet-onboarding-setup-modal}}
@ -20,11 +14,9 @@
:show-extension-modal :show-extension-modal
:stickers-pack-modal :stickers-pack-modal
:tribute-learn-more :tribute-learn-more
:wallet-sign-message-modal
:enter-pin-modal :enter-pin-modal
:hardwallet-connect-modal :hardwallet-connect-modal
:selection-modal-screen :selection-modal-screen
:wallet-transaction-fee
:wallet-transactions-filter :wallet-transactions-filter
:profile-qr-viewer :profile-qr-viewer
:welcome]) :welcome])

View File

@ -70,13 +70,6 @@
[status-im.ui.screens.wallet.request.views :as request] [status-im.ui.screens.wallet.request.views :as request]
[status-im.ui.screens.wallet.send.views :as send] [status-im.ui.screens.wallet.send.views :as send]
[status-im.ui.screens.wallet.settings.views :as wallet-settings] [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.transactions.views :as wallet-transactions]
[status-im.ui.screens.wallet.custom-tokens.views :as custom-tokens])) [status-im.ui.screens.wallet.custom-tokens.views :as custom-tokens]))
@ -120,19 +113,13 @@
:tribute-learn-more [:modal tr-to-talk/learn-more] :tribute-learn-more [:modal tr-to-talk/learn-more]
:chat-modal [:modal chat/chat-modal] :chat-modal [:modal chat/chat-modal]
:show-extension-modal [:modal extensions.add/show-extension-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-onboarding-setup-modal [:modal wallet.onboarding/modal]
:wallet-sign-message-modal [:modal sign-message/sign-message-modal]
:wallet wallet.main/wallet :wallet wallet.main/wallet
:collectibles-list collectibles/collectibles-list :collectibles-list collectibles/collectibles-list
:wallet-onboarding-setup wallet.onboarding/screen :wallet-onboarding-setup wallet.onboarding/screen
:wallet-send-transaction-chat send/send-transaction
:contact-code wallet.components/contact-code :contact-code wallet.components/contact-code
:wallet-send-transaction send/send-transaction :wallet-send-transaction send/send-transaction
:recent-recipients wallet.components/recent-recipients :recent-recipients wallet.components/recent-recipients
:wallet-transaction-sent transaction-sent/transaction-sent
:recipient-qr-code wallet.components/recipient-qr-code :recipient-qr-code wallet.components/recipient-qr-code
:wallet-send-assets wallet.components/send-assets :wallet-send-assets wallet.components/send-assets
:wallet-request-transaction request/request-transaction :wallet-request-transaction request/request-transaction

View File

@ -5,12 +5,10 @@
:screens [:wallet :screens [:wallet
:collectibles-list :collectibles-list
:wallet-onboarding-setup :wallet-onboarding-setup
:wallet-send-transaction-chat
:contact-code :contact-code
{:name :send-transaction-stack {:name :send-transaction-stack
:screens [:wallet-send-transaction :screens [:wallet-send-transaction
:recent-recipients :recent-recipients
:wallet-transaction-sent
:enter-pin-sign :enter-pin-sign
:hardwallet-connect-sign :hardwallet-connect-sign
:recipient-qr-code :recipient-qr-code

View File

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

View File

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

View File

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

View File

@ -13,7 +13,8 @@
[status-im.utils.platform :as platform] [status-im.utils.platform :as platform]
[status-im.ui.screens.mobile-network-settings.view :as mobile-network-settings] [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.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))) (defonce rand-label (when js/goog.DEBUG (rand/id)))
@ -97,5 +98,5 @@
(navigation/navigate-to @view-id nil))) (navigation/navigate-to @view-id nil)))
;; see https://reactnavigation.org/docs/en/state-persistence.html#development-mode ;; see https://reactnavigation.org/docs/en/state-persistence.html#development-mode
:persistenceKey (when js/goog.DEBUG rand-label)}] :persistenceKey (when js/goog.DEBUG rand-label)}]
[signing/signing]
[bottom-sheet]]))}))) [bottom-sheet]]))})))

View File

@ -52,6 +52,7 @@
:default-value contract :default-value contract
:multiline true :multiline true
:height 78 :height 78
:auto-focus false
:placeholder (i18n/label :t/specify-address)}] :placeholder (i18n/label :t/specify-address)}]
[react/view {:height 16}] [react/view {:height 16}]
[text-input/text-input-with-label [text-input/text-input-with-label
@ -59,6 +60,7 @@
:label (i18n/label :t/name) :label (i18n/label :t/name)
:default-value name :default-value name
:error error-name :error error-name
:auto-focus false
:placeholder (i18n/label :t/name-of-token)}] :placeholder (i18n/label :t/name-of-token)}]
[react/view {:height 16}] [react/view {:height 16}]
[react/view {:style {:flex-direction :row}} [react/view {:style {:flex-direction :row}}
@ -68,6 +70,7 @@
:label (i18n/label :t/symbol) :label (i18n/label :t/symbol)
:error error-symbol :error error-symbol
:default-value symbol :default-value symbol
:auto-focus false
:placeholder "ABC"}]] :placeholder "ABC"}]]
[react/view {:flex 1 :margin-left 33} [react/view {:flex 1 :margin-left 33}
[text-input/text-input-with-label [text-input/text-input-with-label
@ -76,6 +79,7 @@
:default-value decimals :default-value decimals
:keyboard-type :number-pad :keyboard-type :number-pad
:max-length 2 :max-length 2
:auto-focus false
:placeholder "18"}]]] :placeholder "18"}]]]
[react/view {:height 16}] [react/view {:height 16}]
[text-input/text-input-with-label [text-input/text-input-with-label

View File

@ -1,38 +1,24 @@
(ns status-im.ui.screens.wallet.navigation (ns status-im.ui.screens.wallet.navigation
(:require [re-frame.core :as re-frame] (:require [status-im.constants :as constants]
[status-im.constants :as constants]
[status-im.ethereum.core :as ethereum]
[status-im.ui.screens.navigation :as navigation])) [status-im.ui.screens.navigation :as navigation]))
(def transaction-send-default (def transaction-send-default
(let [symbol :ETH {:method constants/web3-send-transaction
request (atom nil)] :symbol :ETH})
(fn []
(or @request
(reset!
request
{:gas (ethereum/estimate-gas symbol)
:method constants/web3-send-transaction
:symbol symbol})))))
(def transaction-request-default
{:symbol :ETH})
(defmethod navigation/preload-data! :wallet-request-transaction (defmethod navigation/preload-data! :wallet-request-transaction
[db [event]] [db [event]]
(if (= event :navigate-back) (if (= event :navigate-back)
db db
(-> db (-> db
(assoc-in [:wallet :request-transaction] transaction-request-default) (assoc-in [:wallet :request-transaction] {:symbol :ETH})
(assoc-in [:wallet :send-transaction] (transaction-send-default))))) (assoc-in [:wallet :send-transaction] transaction-send-default))))
(defmethod navigation/preload-data! :wallet-send-transaction (defmethod navigation/preload-data! :wallet-send-transaction
[db [event]] [db [event]]
(if (= event :navigate-back) (if (= event :navigate-back)
db db
(do (assoc-in db [:wallet :send-transaction] transaction-send-default)))
(re-frame/dispatch [:wallet/update-gas-price])
(assoc-in db [:wallet :send-transaction] (transaction-send-default)))))
(defmethod navigation/preload-data! :wallet-add-custom-token (defmethod navigation/preload-data! :wallet-add-custom-token
[db [event]] [db [event]]

View File

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

View File

@ -1,258 +1,11 @@
(ns status-im.ui.screens.wallet.send.events (ns status-im.ui.screens.wallet.send.events
(:require [re-frame.core :as re-frame] (:require [status-im.utils.handlers :as handlers]
[status-im.chat.commands.sending :as commands-sending] [status-im.utils.money :as money]
[status-im.constants :as constants] [status-im.wallet.db :as wallet.db]
[status-im.ethereum.tokens :as tokens]
[status-im.ethereum.abi-spec :as abi-spec] [status-im.ethereum.abi-spec :as abi-spec]
[status-im.ethereum.core :as ethereum] [status-im.ethereum.core :as ethereum]
[status-im.ethereum.tokens :as tokens] [status-im.signing.core :as signing]))
[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'})))))))
(defn set-and-validate-amount-db [db amount symbol decimals] (defn set-and-validate-amount-db [db amount symbol decimals]
(let [{:keys [value error]} (wallet.db/parse-amount amount decimals)] (let [{:keys [value error]} (wallet.db/parse-amount amount decimals)]
@ -266,191 +19,25 @@
(fn [{:keys [db]} [_ amount symbol decimals]] (fn [{:keys [db]} [_ amount symbol decimals]]
{:db (set-and-validate-amount-db 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 (handlers/register-handler-fx
:wallet.send/set-symbol :wallet.send/set-symbol
(fn [{:keys [db]} [_ symbol]] (fn [{:keys [db]} [_ symbol]]
(let [old-symbol (get-in db [:wallet :send-transaction :symbol])] {:db (-> db
(cond-> {:db (-> db
(assoc-in [:wallet :send-transaction :symbol] symbol) (assoc-in [:wallet :send-transaction :symbol] symbol)
(assoc-in [:wallet :send-transaction :amount] nil) (assoc-in [:wallet :send-transaction :amount] nil)
(assoc-in [:wallet :send-transaction :amount-text] nil) (assoc-in [:wallet :send-transaction :amount-text] nil)
(assoc-in [:wallet :send-transaction :asset-error] 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)))))
(handlers/register-handler-fx (handlers/register-handler-fx
:wallet.ui/sign-transaction-button-clicked :wallet.ui/sign-transaction-button-clicked
(fn [{:keys [db] :as cofx} _] (fn [{:keys [db] :as cofx} _]
(let [keycard-account? (boolean (get-in db [:account/account :keycard-instance-uid]))] (let [{:keys [to symbol amount]} (get-in cofx [:db :wallet :send-transaction])
(if keycard-account? {:keys [symbol address]} (tokens/asset-for (:wallet/all-tokens db) (keyword (:chain db)) symbol)
(send-keycard-transaction cofx) amount-hex (str "0x" (abi-spec/number-to-hex amount))
{:db (assoc-in db [:wallet :send-transaction :show-password-input?] true)})))) to-norm (ethereum/normalized-address to)]
(signing/sign cofx {:tx-obj (if (= symbol :ETH)
(fx/defn keycard-hash-message {:to to-norm
[_ data typed?] :value amount-hex}
(if typed? {:to (ethereum/normalized-address address)
{::hash-typed-data {:data data :data (abi-spec/encode "transfer(address,uint256)" [to-norm amount-hex])})
:on-completed #(re-frame/dispatch [::hash-message-completed (types/json->clj %)])}} :on-result [:navigate-back]}))))
{::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])}})))))

View File

@ -1,128 +1,31 @@
(ns status-im.ui.screens.wallet.send.views (ns status-im.ui.screens.wallet.send.views
(:require-macros [status-im.utils.views :refer [defview letsubs]])
(:require [re-frame.core :as re-frame] (:require [re-frame.core :as re-frame]
[reagent.core :as reagent] [reagent.core :as reagent]
[status-im.ethereum.core :as ethereum]
[status-im.ethereum.tokens :as tokens] [status-im.ethereum.tokens :as tokens]
[status-im.i18n :as i18n] [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.bottom-buttons.view :as bottom-buttons]
[status-im.ui.components.button.view :as button] [status-im.ui.components.button.view :as button]
[status-im.ui.components.colors :as colors] [status-im.ui.components.colors :as colors]
[status-im.ui.components.common.common :as common] [status-im.ui.components.common.common :as common]
[status-im.ui.components.icons.vector-icons :as vector-icons] [status-im.ui.components.icons.vector-icons :as vector-icons]
[status-im.ui.components.react :as react] [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.styles :as components.styles]
[status-im.ui.components.toolbar.actions :as actions]
[status-im.ui.components.toolbar.view :as toolbar] [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.components.views :as wallet.components]
[status-im.ui.screens.wallet.main.views :as wallet.main.views] [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.send.styles :as styles]
[status-im.ui.screens.wallet.styles :as wallet.styles] [status-im.ui.components.toolbar.actions :as actions]))
[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]]))
(defn- toolbar [modal? title] (defn- toolbar [title]
(let [action (if modal? actions/close-white actions/back-white)]
[toolbar/toolbar {:transparent? true} [toolbar/toolbar {:transparent? true}
[toolbar/nav-button (action (if modal? [toolbar/nav-button (actions/back-white #(actions/default-handler))]
#(re-frame/dispatch [:wallet/discard-transaction-navigate-back]) [toolbar/content-title {:color :white} title]])
#(actions/default-handler)))]
[toolbar/content-title {:color :white} title]]))
(defn- advanced-cartouche [native-currency {:keys [max-fee gas gas-price]}] (defn- sign-transaction-button [amount-error amount sufficient-funds? online?]
[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?]
(let [sign-enabled? (and (nil? amount-error) (let [sign-enabled? (and (nil? amount-error)
(or modal? (not (empty? to))) ;;NOTE(goranjovic) - contract creation will have empty `to`
(not (nil? amount)) (not (nil? amount))
sufficient-funds? sufficient-funds?
sufficient-gas?
online?)] online?)]
[bottom-buttons/bottom-buttons [bottom-buttons/bottom-buttons
styles/sign-buttons styles/sign-buttons
@ -135,79 +38,40 @@
(i18n/label :t/transactions-sign-transaction) (i18n/label :t/transactions-sign-transaction)
[vector-icons/icon :main-icons/next {:color (if sign-enabled? colors/white colors/white-light-transparent)}]]])) [vector-icons/icon :main-icons/next {:color (if sign-enabled? colors/white colors/white-light-transparent)}]]]))
(defn signing-phrase-view [signing-phrase] (defn- render-send-transaction-view [{:keys [chain transaction scroll all-tokens amount-input network-status]}]
[react/view {:flex-direction :column (let [{:keys [amount amount-text amount-error asset-error to to-name sufficient-funds? symbol]} transaction
: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)
{:keys [decimals] :as token} (tokens/asset-for all-tokens chain symbol) {:keys [decimals] :as token} (tokens/asset-for all-tokens chain symbol)
online? (= :online network-status)] online? (= :online network-status)]
[wallet.components/simple-screen {:avoid-keyboard? (not modal?) [wallet.components/simple-screen {:avoid-keyboard? true
:status-bar-type (if modal? :modal-wallet :wallet)} :status-bar-type :wallet}
[toolbar modal? (i18n/label :t/send-transaction)] [toolbar (i18n/label :t/send-transaction)]
[react/view components.styles/flex [react/view components.styles/flex
[common/network-info {:text-color :white}] [common/network-info {:text-color :white}]
[react/scroll-view {:keyboard-should-persist-taps :always [react/scroll-view {:keyboard-should-persist-taps :always
:ref #(reset! scroll %) :ref #(reset! scroll %)
:on-content-size-change #(when (and (not modal?) scroll @scroll) :on-content-size-change #(when (and scroll @scroll)
(.scrollToEnd @scroll))} (.scrollToEnd @scroll))}
(when-not online? (when-not online?
[wallet.main.views/snackbar :t/error-cant-send-transaction-offline]) [wallet.main.views/snackbar :t/error-cant-send-transaction-offline])
[react/view styles/send-transaction-form [react/view styles/send-transaction-form
[wallet.components/recipient-selector [wallet.components/recipient-selector
{:disabled? (or from-chat? modal? show-password-input?) {:address to
:address to :name to-name}]
:name to-name
:modal? modal?}]
[wallet.components/asset-selector [wallet.components/asset-selector
{:disabled? (or from-chat? modal? show-password-input?) {:error asset-error
:error asset-error
:type :send :type :send
:symbol (or asset symbol)}] :symbol symbol}]
[wallet.components/amount-selector [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)))
:error (or amount-error
(when-not sufficient-funds? (i18n/label :t/wallet-insufficient-funds))
(when-not sufficient-gas? (i18n/label :t/wallet-insufficient-gas)))
:amount amount :amount amount
:amount-text amount-text :amount-text amount-text
:input-options {:on-change-text #(re-frame/dispatch [:wallet.send/set-and-validate-amount % symbol decimals]) :input-options {:on-change-text #(re-frame/dispatch [:wallet.send/set-and-validate-amount % symbol decimals])
:ref (partial reset! amount-input)}} token] :ref (partial reset! amount-input)}} token]]]
[advanced-options advanced? native-currency transaction scroll] [sign-transaction-button amount-error amount sufficient-funds? online?]]]))
(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])]]))
;; MAIN SEND TRANSACTION VIEW (defn- send-transaction-view [{:keys [scroll]}]
(defn- send-transaction-view [{:keys [scroll] :as opts}]
(let [amount-input (atom nil) (let [amount-input (atom nil)
handler (fn [_] handler #(when (and scroll @scroll @amount-input (.isFocused @amount-input)) (.scrollToEnd @scroll))]
(when (and scroll @scroll @amount-input
(.isFocused @amount-input))
(log/debug "Amount field focused, scrolling down")
(.scrollToEnd @scroll)))]
(reagent/create-class (reagent/create-class
{:component-will-mount (fn [_] {:component-will-mount (fn [_]
;;NOTE(goranjovic): keyboardDidShow is for android and keyboardWillShow for ios ;;NOTE(goranjovic): keyboardDidShow is for android and keyboardWillShow for ios
@ -216,49 +80,14 @@
:reagent-render (fn [opts] (render-send-transaction-view :reagent-render (fn [opts] (render-send-transaction-view
(assoc opts :amount-input amount-input)))}))) (assoc opts :amount-input amount-input)))})))
;; SEND TRANSACTION FROM WALLET (CHAT)
(defview send-transaction [] (defview send-transaction []
(letsubs [transaction [:wallet.send/transaction] (letsubs [transaction [:wallet.send/transaction]
advanced? [:wallet.send/advanced?]
chain [:ethereum/chain-keyword] chain [:ethereum/chain-keyword]
scroll (atom nil) scroll (atom nil)
network-status [:network-status] network-status [:network-status]
all-tokens [:wallet/all-tokens] all-tokens [:wallet/all-tokens]]
signing-phrase [:wallet.send/signing-phrase-with-padding] [send-transaction-view {:transaction transaction
keycard? [:keycard-account?]]
[send-transaction-view {:modal? false
:transaction transaction
:scroll scroll :scroll scroll
:advanced? advanced?
:keycard? keycard?
:signing-phrase signing-phrase
:chain chain :chain chain
:all-tokens all-tokens :all-tokens all-tokens
:network-status network-status}])) :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}]]])))

View File

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

View File

@ -13,7 +13,6 @@
[status-im.utils.config :as config] [status-im.utils.config :as config]
[status-im.utils.core :as utils.core] [status-im.utils.core :as utils.core]
[status-im.utils.fx :as fx] [status-im.utils.fx :as fx]
[status-im.utils.hex :as utils.hex]
[status-im.utils.money :as money] [status-im.utils.money :as money]
[status-im.utils.prices :as prices] [status-im.utils.prices :as prices]
[status-im.utils.utils :as utils.utils] [status-im.utils.utils :as utils.utils]
@ -49,14 +48,6 @@
{:db (assoc-in db [:wallet :current-transaction] hash)} {:db (assoc-in db [:wallet :current-transaction] hash)}
(navigation/navigate-to-cofx :wallet-transaction-details nil))) (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 ;; FX
(re-frame/reg-fx (re-frame/reg-fx
@ -80,23 +71,6 @@
#(re-frame/dispatch [error-event %]) #(re-frame/dispatch [error-event %])
chaos-mode?))) 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! (defn- validate-token-name!
[{:keys [address symbol name]}] [{:keys [address symbol name]}]
(json-rpc/eth-call (json-rpc/eth-call
@ -175,183 +149,6 @@
(on-success symbol (money/bignumber balance))) (on-success symbol (money/bignumber balance)))
:on-error #(on-error symbol %)})))) :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] (defn clear-error-message [db error-type]
(update-in db [:wallet :errors] dissoc error-type)) (update-in db [:wallet :errors] dissoc error-type))
@ -450,29 +247,6 @@
(clear-error-message :prices-update) (clear-error-message :prices-update)
(assoc :prices-loading? true))}))) (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?] (defn- set-checked [ids id checked?]
(if checked? (if checked?
(conj (or ids #{}) id) (conj (or ids #{}) id)
@ -496,29 +270,6 @@
(assoc-in [:wallet :balance symbol] (money/bignumber balance)) (assoc-in [:wallet :balance symbol] (money/bignumber balance))
(assoc-in [:wallet :balance-loading?] false))}) (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 (defn update-toggle-in-settings
[{{:account/keys [account] :as db} :db} symbol checked?] [{{:account/keys [account] :as db} :db} symbol checked?]
(let [chain (ethereum/chain-keyword db) (let [chain (ethereum/chain-keyword db)
@ -550,34 +301,3 @@
(toggle-visible-token symbol true) (toggle-visible-token symbol true)
;;TODO(goranjovic): move `update-token-balance-success` function to wallet models ;;TODO(goranjovic): move `update-token-balance-success` function to wallet models
(update-token-balance symbol balance))) (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 {}))))

View File

@ -40,23 +40,7 @@
{:title (i18n/label :t/send-request-amount) {:title (i18n/label :t/send-request-amount)
:description (i18n/label :t/send-request-amount-max-decimals {:asset-decimals 18})})) :description (i18n/label :t/send-request-amount-max-decimals {:asset-decimals 18})}))
(is (= (protocol/validate personal-send-command {:asset "ETH" :amount "0.01"} cofx) (is (= (protocol/validate personal-send-command {:asset "ETH" :amount "0.01"} cofx)
nil))) 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))))))
;; testing the `/request` command ;; testing the `/request` command

View File

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

View File

@ -37,7 +37,6 @@
[status-im.test.models.bootnode] [status-im.test.models.bootnode]
[status-im.test.models.contact] [status-im.test.models.contact]
[status-im.test.models.network] [status-im.test.models.network]
[status-im.test.models.wallet]
[status-im.test.node.core] [status-im.test.node.core]
[status-im.test.pairing.core] [status-im.test.pairing.core]
[status-im.test.search.core] [status-im.test.search.core]
@ -67,7 +66,9 @@
[status-im.test.wallet.subs] [status-im.test.wallet.subs]
[status-im.test.wallet.transactions.subs] [status-im.test.wallet.transactions.subs]
[status-im.test.wallet.transactions] [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!) (enable-console-print!)
@ -116,11 +117,12 @@
'status-im.test.models.bootnode 'status-im.test.models.bootnode
'status-im.test.models.contact 'status-im.test.models.contact
'status-im.test.models.network 'status-im.test.models.network
'status-im.test.models.wallet
'status-im.test.node.core 'status-im.test.node.core
'status-im.test.pairing.core 'status-im.test.pairing.core
'status-im.test.search.core 'status-im.test.search.core
'status-im.test.sign-in.flow 'status-im.test.sign-in.flow
'status-im.test.signing.core
'status-im.test.signing.gas
'status-im.test.transport.core 'status-im.test.transport.core
'status-im.test.tribute-to-talk.core 'status-im.test.tribute-to-talk.core
'status-im.test.tribute-to-talk.db 'status-im.test.tribute-to-talk.db

View File

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

View File

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

View File

@ -61,39 +61,6 @@
:tribute-to-talk {:snt-amount "1000000000000000000"}}} :tribute-to-talk {:snt-amount "1000000000000000000"}}}
:wallet {:balance {:STT (money/bignumber "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 (deftest tribute-transaction-trigger
(testing "transaction error" (testing "transaction error"
(is (tribute-to-talk/tribute-transaction-trigger (is (tribute-to-talk/tribute-transaction-trigger

View File

@ -1064,5 +1064,13 @@
"default" : "Default", "default" : "Default",
"token-details" : "Token details", "token-details" : "Token details",
"remove-token" : "Remove token", "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"
} }