[#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
(:wallet
:wallet-send-transaction
:wallet-transaction-sent
:wallet-request-transaction
:wallet-send-transaction-chat
:wallet-send-assets
:wallet-request-assets
:choose-recipient
:recent-recipients
:wallet-send-transaction-modal
:wallet-transaction-sent-modal
:wallet-send-transaction-request
:wallet-transaction-fee
:wallet-sign-message-modal
:contact-code
:wallet-onboarding-setup
:wallet-modal
:wallet-onboarding-setup-modal
:wallet-settings-hook)

View File

@ -20,7 +20,8 @@
[status-im.utils.random :as random]
[status-im.utils.types :as types]
[status-im.utils.universal-links.core :as universal-links]
[taoensso.timbre :as log]))
[taoensso.timbre :as log]
[status-im.signing.core :as signing]))
(fx/defn initialize-browsers
[{:keys [db all-stored-browsers]}]
@ -274,19 +275,55 @@
(navigation/navigate-to-cofx :browser nil)
(resolve-url nil))))
(fx/defn web3-error-callback
{:events [:browser.dapp/transaction-on-error]}
[{{:keys [webview-bridge]} :db} message-id message]
{:browser/send-to-bridge
{:message {:type constants/web3-send-async-callback
:messageId message-id
:error message}
:webview webview-bridge}})
(fx/defn dapp-complete-transaction
{:events [:browser.dapp/transaction-on-result]}
[{{:keys [webview-bridge]} :db} message-id id result]
;;TODO check and test id
{:browser/send-to-bridge
{:message {:type constants/web3-send-async-callback
:messageId message-id
:result {:jsonrpc "2.0"
:id (int id)
:result result}}
:webview webview-bridge}})
(defn normalize-sign-message-params
"NOTE (andrey) we need this function, because params may be mixed up"
[params]
(let [[first-param second-param] params]
(cond
(ethereum/address? first-param)
[first-param second-param]
(ethereum/address? second-param)
[second-param first-param])))
(fx/defn web3-send-async
[{:keys [db]} {:keys [method] :as payload} message-id]
(if (or (= constants/web3-send-transaction method)
(constants/web3-sign-message? method))
{:db (update-in db [:wallet :transactions-queue] conj {:message-id message-id :payload payload})
;;TODO(yenda): refactor check-dapps-transactions-queue to remove this dispatch
:dispatch [:check-dapps-transactions-queue]}
{:browser/call-rpc [payload
#(re-frame/dispatch [:browser.callback/call-rpc
{:type constants/web3-send-async-callback
:messageId message-id
:error %1
:result %2}])]}))
[cofx {:keys [method params id] :as payload} message-id]
(let [message? (constants/web3-sign-message? method)]
(if (or message? (= constants/web3-send-transaction method))
(let [[address data] (when message? (normalize-sign-message-params params))]
(when (or (not message?) (and address data))
(signing/sign cofx (merge
(if message?
{:message {:address address :data data :typed? (not= constants/web3-personal-sign method)}}
{:tx-obj (first params)})
{:on-result [:browser.dapp/transaction-on-result message-id id]
:on-error [:browser.dapp/transaction-on-error message-id]}))))
{:browser/call-rpc [payload
#(re-frame/dispatch [:browser.callback/call-rpc
{:type constants/web3-send-async-callback
:messageId message-id
:error %1
:result %2}])]})))
(fx/defn send-to-bridge
[cofx message]

View File

@ -6,7 +6,6 @@
:as
transactions-styles]
[status-im.chat.commands.protocol :as protocol]
[status-im.contact.db :as db.contact]
[status-im.data-store.messages :as messages-store]
[status-im.ethereum.core :as ethereum]
[status-im.ethereum.tokens :as tokens]
@ -18,16 +17,13 @@
[status-im.ui.components.list.views :as list]
[status-im.ui.components.react :as react]
[status-im.ui.components.svgimage :as svgimage]
[status-im.ui.screens.navigation :as navigation]
[status-im.ui.screens.wallet.choose-recipient.events
:as
choose-recipient.events]
[status-im.ui.screens.wallet.utils :as wallet.utils]
[status-im.utils.datetime :as datetime]
[status-im.utils.fx :as fx]
[status-im.utils.money :as money]
[status-im.utils.platform :as platform]
[status-im.wallet.db :as wallet.db])
[status-im.wallet.db :as wallet.db]
[status-im.signing.core :as signing]
[status-im.ethereum.abi-spec :as abi-spec])
(:require-macros [status-im.utils.views :refer [defview letsubs]]))
;; common `send/request` functionality
@ -287,8 +283,8 @@
(description [_] (i18n/label :t/send-command-payment))
(parameters [_] personal-send-request-params)
(validate [_ parameters cofx]
;; Only superficial/formatting validation, "real validation" will be performed
;; by the wallet, where we yield control in the next step
;; Only superficial/formatting validation, "real validation" will be performed
;; by the wallet, where we yield control in the next step
(personal-send-request-validation parameters cofx))
(on-send [_ {:keys [chat-id] :as send-message} {:keys [db]}]
(when-let [responding-to (get-in db [:chats chat-id :metadata :responding-to-command])]
@ -304,39 +300,21 @@
(send-preview command-message))
protocol/Yielding
(yield-control [_ {{{amount :amount asset :asset} :params} :content} {:keys [db] :as cofx}]
;; Prefill wallet and navigate there
(let [recipient-contact (or
(get-in db [:contacts/contacts (:current-chat-id db)])
(db.contact/public-key->new-contact (:current-chat-id db)))
sender-account (:account/account db)
chain (keyword (:chain db))
symbol-param (keyword asset)
all-tokens (:wallet/all-tokens db)
{:keys [symbol decimals]} (tokens/asset-for all-tokens chain symbol-param)
{:keys [value error]} (wallet.db/parse-amount amount decimals)
next-view-id (if (:wallet-set-up-passed? sender-account)
:wallet-send-modal-stack
:wallet-send-modal-stack-with-onboarding)]
(fx/merge cofx
{:db (-> db
(assoc-in [:navigation/screen-params :wallet-send-modal-stack :modal?] true)
(update-in [:wallet :send-transaction]
assoc
:amount (money/formatted->internal value symbol decimals)
:amount-text amount
:amount-error error)
(choose-recipient.events/fill-request-details
(transaction-details recipient-contact symbol) false)
(update-in [:wallet :send-transaction]
dissoc :id :password :wrong-password?))
;; TODO(janherich) - refactor wallet send events, updating gas price
;; is generic thing which shouldn't be defined in wallet.send, then
;; we can include the utility helper without running into circ-dep problem
:wallet/update-gas-price
{:success-event :wallet/update-gas-price-success
:edit? false}}
(navigation/navigate-to-cofx next-view-id {}))))
(let [{:keys [symbol decimals address]} (tokens/asset-for (:wallet/all-tokens db) (keyword (:chain db)) (keyword asset))
{:keys [value]} (wallet.db/parse-amount amount decimals)
current-chat-id (:current-chat-id db)
amount-hex (str "0x" (abi-spec/number-to-hex (money/formatted->internal value symbol decimals)))
to (ethereum/public-key->address current-chat-id)
to-norm (ethereum/normalized-address (if (= symbol :ETH) to address))
tx-obj (if (= symbol :ETH)
{:to to-norm
:value amount-hex}
{:to to-norm
:data (abi-spec/encode "transfer(address,uint256)" [to amount-hex])})]
(signing/sign cofx {:tx-obj tx-obj
:on-result [:chat/send-transaction-result current-chat-id {:address to-norm
:asset (name symbol)
:amount amount}]})))
protocol/EnhancedParameters
(enhance-send-parameters [_ parameters cofx]
(-> parameters

View File

@ -172,6 +172,11 @@
(command-not-complete-fx input-text current-chat-id cofx))
(plain-text-message-fx input-text current-chat-id cofx))))
(fx/defn send-transaction-result
{:events [:chat/send-transaction-result]}
[cofx chat-id params result]
(commands.sending/send cofx chat-id (get-in cofx [:db :id->command ["send" #{:personal-chats}]]) (assoc params :tx-hash result)))
;; effects
(re-frame/reg-fx

View File

@ -238,6 +238,10 @@
;; (ethereum/sha3 "Transfer(address,address,uint256)")
(def ^:const event-transfer-hash "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef")
(def ^:const method-id-transfer "0xa9059cbb")
(def ^:const method-id-approve "0x095ea7b3")
(def ^:const method-id-approve-and-call "0xcae9ca51")
(def regx-emoji #"^((?:[\u261D\u26F9\u270A-\u270D]|\uD83C[\uDF85\uDFC2-\uDFC4\uDFC7\uDFCA-\uDFCC]|\uD83D[\uDC42\uDC43\uDC46-\uDC50\uDC66-\uDC69\uDC6E\uDC70-\uDC78\uDC7C\uDC81-\uDC83\uDC85-\uDC87\uDCAA\uDD74\uDD75\uDD7A\uDD90\uDD95\uDD96\uDE45-\uDE47\uDE4B-\uDE4F\uDEA3\uDEB4-\uDEB6\uDEC0\uDECC]|\uD83E[\uDD18-\uDD1C\uDD1E\uDD1F\uDD26\uDD30-\uDD39\uDD3D\uDD3E\uDDD1-\uDDDD])(?:\uD83C[\uDFFB-\uDFFF])?|(?:[\u231A\u231B\u23E9-\u23EC\u23F0\u23F3\u25FD\u25FE\u2614\u2615\u2648-\u2653\u267F\u2693\u26A1\u26AA\u26AB\u26BD\u26BE\u26C4\u26C5\u26CE\u26D4\u26EA\u26F2\u26F3\u26F5\u26FA\u26FD\u2705\u270A\u270B\u2728\u274C\u274E\u2753-\u2755\u2757\u2795-\u2797\u27B0\u27BF\u2B1B\u2B1C\u2B50\u2B55]|\uD83C[\uDC04\uDCCF\uDD8E\uDD91-\uDD9A\uDDE6-\uDDFF\uDE01\uDE1A\uDE2F\uDE32-\uDE36\uDE38-\uDE3A\uDE50\uDE51\uDF00-\uDF20\uDF2D-\uDF35\uDF37-\uDF7C\uDF7E-\uDF93\uDFA0-\uDFCA\uDFCF-\uDFD3\uDFE0-\uDFF0\uDFF4\uDFF8-\uDFFF]|\uD83D[\uDC00-\uDC3E\uDC40\uDC42-\uDCFC\uDCFF-\uDD3D\uDD4B-\uDD4E\uDD50-\uDD67\uDD7A\uDD95\uDD96\uDDA4\uDDFB-\uDE4F\uDE80-\uDEC5\uDECC\uDED0-\uDED2\uDEEB\uDEEC\uDEF4-\uDEF8]|\uD83E[\uDD10-\uDD3A\uDD3C-\uDD3E\uDD40-\uDD45\uDD47-\uDD4C\uDD50-\uDD6B\uDD80-\uDD97\uDDC0\uDDD0-\uDDE6])|(?:[#\*0-9\xA9\xAE\u203C\u2049\u2122\u2139\u2194-\u2199\u21A9\u21AA\u231A\u231B\u2328\u23CF\u23E9-\u23F3\u23F8-\u23FA\u24C2\u25AA\u25AB\u25B6\u25C0\u25FB-\u25FE\u2600-\u2604\u260E\u2611\u2614\u2615\u2618\u261D\u2620\u2622\u2623\u2626\u262A\u262E\u262F\u2638-\u263A\u2640\u2642\u2648-\u2653\u2660\u2663\u2665\u2666\u2668\u267B\u267F\u2692-\u2697\u2699\u269B\u269C\u26A0\u26A1\u26AA\u26AB\u26B0\u26B1\u26BD\u26BE\u26C4\u26C5\u26C8\u26CE\u26CF\u26D1\u26D3\u26D4\u26E9\u26EA\u26F0-\u26F5\u26F7-\u26FA\u26FD\u2702\u2705\u2708-\u270D\u270F\u2712\u2714\u2716\u271D\u2721\u2728\u2733\u2734\u2744\u2747\u274C\u274E\u2753-\u2755\u2757\u2763\u2764\u2795-\u2797\u27A1\u27B0\u27BF\u2934\u2935\u2B05-\u2B07\u2B1B\u2B1C\u2B50\u2B55\u3030\u303D\u3297\u3299]|\uD83C[\uDC04\uDCCF\uDD70\uDD71\uDD7E\uDD7F\uDD8E\uDD91-\uDD9A\uDDE6-\uDDFF\uDE01\uDE02\uDE1A\uDE2F\uDE32-\uDE3A\uDE50\uDE51\uDF00-\uDF21\uDF24-\uDF93\uDF96\uDF97\uDF99-\uDF9B\uDF9E-\uDFF0\uDFF3-\uDFF5\uDFF7-\uDFFF]|\uD83D[\uDC00-\uDCFD\uDCFF-\uDD3D\uDD49-\uDD4E\uDD50-\uDD67\uDD6F\uDD70\uDD73-\uDD7A\uDD87\uDD8A-\uDD8D\uDD90\uDD95\uDD96\uDDA4\uDDA5\uDDA8\uDDB1\uDDB2\uDDBC\uDDC2-\uDDC4\uDDD1-\uDDD3\uDDDC-\uDDDE\uDDE1\uDDE3\uDDE8\uDDEF\uDDF3\uDDFA-\uDE4F\uDE80-\uDEC5\uDECB-\uDED2\uDEE0-\uDEE5\uDEE9\uDEEB\uDEEC\uDEF0\uDEF3-\uDEF8]|\uD83E[\uDD10-\uDD3A\uDD3C-\uDD3E\uDD40-\uDD45\uDD47-\uDD4C\uDD50-\uDD6B\uDD80-\uDD97\uDDC0\uDDD0-\uDDE6])\uFE0F|[\t-\r \xA0\u1680\u2000-\u200A\u2028\u2029\u202F\u205F\u3000\uFEFF])+$")
(def regx-rtl-characters #"[^\u0591-\u06EF\u06FA-\u07FF\u200F\u202B\u202E\uFB1D-\uFDFD\uFE70-\uFEFC]*?[\u0591-\u06EF\u06FA-\u07FF\u200F\u202B\u202E\uFB1D-\uFDFD\uFE70-\uFEFC]")
(def regx-url #"(?i)(?:[a-z][\w-]+:(?:/{1,3}|[a-z0-9%])|www\d{0,3}[.]|[a-z0-9\-]+[.][a-z]{1,4}/?)(?:[^\s()<>]+|\(([^\s()<>]+|(\([^\s()<>]+\)))*\))+(?:\(([^\s()<>]+|(\([^\s()<>]+\)))*\)|[^\s`!()\[\]{};:\'\".,<>?«»“”‘’]){0,}")

View File

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

View File

@ -2102,28 +2102,6 @@
(fn [cofx [_ hash]]
(wallet/open-transaction-details cofx hash)))
(handlers/register-handler-fx
:wallet/show-sign-transaction
(fn [cofx [_ {:keys [id method]} from-chat?]]
(wallet/open-send-transaction-modal cofx id method from-chat?)))
(handlers/register-handler-fx
:wallet/update-gas-price-success
(fn [cofx [_ price edit?]]
(wallet/update-gas-price cofx price edit?)))
(handlers/register-handler-fx
:TODO.remove/update-estimated-gas
(fn [{:keys [db]} [_ obj]]
{:wallet/update-estimated-gas
{:obj obj
:success-event :wallet/update-estimated-gas-success}}))
(handlers/register-handler-fx
:wallet/update-estimated-gas-success
(fn [cofx [_ gas]]
(wallet/update-estimated-gas-price cofx gas)))
(handlers/register-handler-fx
:wallet.setup.ui/navigate-back-pressed
(fn [{:keys [db] :as cofx}]
@ -2135,3 +2113,8 @@
:shake-event
(fn [cofx _]
(logging/show-logs-dialog cofx)))
(re-frame/reg-fx
:dismiss-keyboard
(fn []
(react/dismiss-keyboard!)))

View File

@ -4,22 +4,17 @@
[status-im.ethereum.abi-spec :as abi-spec]
[status-im.ethereum.core :as ethereum]
[status-im.ethereum.ens :as ens]
[status-im.i18n :as i18n]
[status-im.native-module.core :as status]
[status-im.ui.screens.navigation :as navigation]
[status-im.utils.fx :as fx]
[status-im.utils.handlers :as handlers]
[status-im.utils.hex :as hex]
[status-im.utils.money :as money]
[status-im.utils.types :as types]
[status-im.wallet.core :as wallet]))
[status-im.signing.core :as signing]))
(handlers/register-handler-fx
:extensions/wallet-ui-on-success
(fn [cofx [_ on-success _ result _]]
(fx/merge cofx
(when on-success (on-success {:value result}))
(navigation/navigate-back))))
(fn [_ [_ on-success result]]
(when on-success (on-success {:value result}))))
(handlers/register-handler-fx
:extensions/wallet-ui-on-failure
@ -42,35 +37,11 @@
#(f db (assoc arguments address-keyword %))))
(f db arguments))))
(defn prepare-extension-transaction [params contacts on-success on-failure]
(let [{:keys [to value data gas gasPrice nonce]} params
contact (get contacts (hex/normalize-hex to))]
(cond-> {:id "extension-id"
:to-name (or (when (nil? to)
(i18n/label :t/new-contract))
contact)
:symbol :ETH
:method constants/web3-send-transaction
:to to
:amount (money/bignumber (or value 0))
:gas (cond
gas
(money/bignumber gas)
(and value (empty? data))
(money/bignumber 21000))
:gas-price (when gasPrice
(money/bignumber gasPrice))
:data data
:on-result [:extensions/wallet-ui-on-success on-success]
:on-error [:extensions/wallet-ui-on-failure on-failure]}
nonce
(assoc :nonce nonce))))
(defn- execute-send-transaction [db {:keys [method params on-success on-failure] :as arguments}]
(let [tx-object (assoc (select-keys arguments [:to :gas :gas-price :value :nonce])
:data (when (and method params) (abi-spec/encode method params)))
transaction (prepare-extension-transaction tx-object (:contacts/contacts db) on-success on-failure)]
(wallet/open-modal-wallet-for-transaction db transaction tx-object)))
(signing/sign {:db db} {:tx-obj (assoc (select-keys arguments [:to :gas :gas-price :value :nonce])
:data (when (and method params) (abi-spec/encode method params)))
:on-result [:extensions/wallet-ui-on-success on-success]
:on-error [:extensions/wallet-ui-on-failure on-failure]}))
(handlers/register-handler-fx
:extensions/ethereum-send-transaction
@ -381,22 +352,16 @@
(when on-failure
(on-failure {:value (str "'" name "' is not a valid name")})))))
;; EXTENSION SIGN -> SIGN MESSAGE
(handlers/register-handler-fx
:extensions/ethereum-sign
(fn [{db :db :as cofx} [_ _ {:keys [message data id on-success on-failure]}]]
(if (and message data)
(when on-failure
(on-failure {:error "only one of :message and :data can be used"}))
(fx/merge cofx
{:db (assoc-in db [:wallet :send-transaction]
{:id id
:from (ethereum/current-address db)
:data (or data (ethereum/utf8-to-hex message))
:on-result [:extensions/wallet-ui-on-success on-success]
:on-error [:extensions/wallet-ui-on-failure on-failure]
:method constants/web3-personal-sign})}
(navigation/navigate-to-cofx :wallet-sign-message-modal nil)))))
(signing/sign cofx {:message {:address (ethereum/current-address db)
:data (or data (ethereum/utf8-to-hex message))}
:on-result [:extensions/wallet-ui-on-success on-success]
:on-error [:extensions/wallet-ui-on-failure on-failure]}))))
(handlers/register-handler-fx
:extensions/ethereum-create-address

View File

@ -62,7 +62,8 @@
(if navigate-to-browser?
(fx/merge cofx
{:db (assoc-in db [:hardwallet :on-card-connected] nil)}
(wallet/discard-transaction)
;;TODO use new signing flow
;;(wallet/discard-transaction)
(navigation/navigate-to-cofx :browser nil))
(if (= :enter-pin-login (:view-id db))
(navigation/navigate-to-clean cofx :accounts nil)

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

View File

@ -40,6 +40,7 @@
[status-im.utils.universal-links.core :as links]
[status-im.wallet.core :as wallet]
[status-im.wallet.db :as wallet.db]
[status-im.signing.gas :as signing.gas]
status-im.ui.screens.hardwallet.connect.subs
status-im.ui.screens.hardwallet.settings.subs
status-im.ui.screens.hardwallet.pin.subs
@ -163,6 +164,11 @@
;;ethereum
(reg-root-key-sub :ethereum/current-block :ethereum/current-block)
;;signing
(reg-root-key-sub :signing/tx :signing/tx)
(reg-root-key-sub :signing/sign :signing/sign)
(reg-root-key-sub :signing/edit-fee :signing/edit-fee)
;;GENERAL ==============================================================================================================
(re-frame/reg-sub
@ -1303,10 +1309,10 @@
(:send-transaction wallet)))
(re-frame/reg-sub
:wallet.send/advanced?
:wallet.send/symbol
:<- [::send-transaction]
(fn [send-transaction]
(:advanced? send-transaction)))
(:symbol send-transaction)))
(re-frame/reg-sub
:wallet.send/camera-flashlight
@ -1314,41 +1320,6 @@
(fn [send-transaction]
(:camera-flashlight send-transaction)))
(re-frame/reg-sub
:wallet.send/wrong-password?
:<- [::send-transaction]
(fn [send-transaction]
(:wrong-password? send-transaction)))
(re-frame/reg-sub
:wallet.send/sign-password-enabled?
:<- [::send-transaction]
(fn [{:keys [password]}]
(and (not (nil? password)) (not= password ""))))
(defn edit-or-transaction-data
"Set up edit data structure, defaulting to transaction when not available"
[transaction edit]
(cond-> edit
(not (get-in edit [:gas-price :value]))
(wallet/build-edit
:gas-price
(money/to-fixed (money/wei-> :gwei (:gas-price transaction))))
(not (get-in edit [:gas :value]))
(wallet/build-edit
:gas
(money/to-fixed (:gas transaction)))))
(re-frame/reg-sub
:wallet/edit
:<- [::send-transaction]
:<- [:wallet]
(fn [[send-transaction {:keys [edit]}]]
(edit-or-transaction-data
send-transaction
edit)))
(defn check-sufficient-funds
[{:keys [sufficient-funds?] :as transaction} balance symbol amount]
(cond-> transaction
@ -1377,17 +1348,9 @@
:<- [:balance]
(fn [[{:keys [amount symbol] :as transaction} balance]]
(-> transaction
(wallet/add-max-fee)
(check-sufficient-funds balance symbol amount)
(check-sufficient-gas balance symbol amount))))
(re-frame/reg-sub
:wallet.send/signing-phrase-with-padding
:<- [:account/account]
(fn [{:keys [signing-phrase]}]
(when signing-phrase
(clojure.string/replace signing-phrase #" " " "))))
(re-frame/reg-sub
:wallet/settings
:<- [:wallet]
@ -1868,3 +1831,66 @@
(or (string/blank? screen-snt-amount)
(#{"0" "0.0" "0.00"} screen-snt-amount)
(string/ends-with? screen-snt-amount ".")))))))))
;;SIGNING =============================================================================================================
(re-frame/reg-sub
:signing/fee
:<- [:signing/tx]
(fn [{:keys [gas gasPrice]}]
(signing.gas/calculate-max-fee gas gasPrice)))
(re-frame/reg-sub
:signing/phrase
:<- [:account/account]
(fn [{:keys [signing-phrase]}]
signing-phrase))
(defn- too-precise-amount?
"Checks if number has any extra digit beyond the allowed number of decimals.
It does so by checking the number against its rounded value."
[amount decimals]
(let [bn (money/bignumber amount)]
(not (.eq bn (.round bn decimals)))))
(defn get-amount-error [amount decimals]
(when (and (not (empty? amount)) decimals)
(let [normalized-amount (money/normalize amount)
value (money/bignumber normalized-amount)]
(cond
(not (money/valid? value))
{:amount-error (i18n/label :t/validation-amount-invalid-number)}
(too-precise-amount? normalized-amount decimals)
{:amount-error (i18n/label :t/validation-amount-is-too-precise {:decimals decimals})}
:else nil))))
(defn get-sufficient-funds-error
[balance symbol amount]
(when-not (money/sufficient-funds? amount (get balance symbol))
{:amount-error (i18n/label :t/wallet-insufficient-funds)}))
(defn get-sufficient-gas-error
[balance symbol amount gas gasPrice]
(if (and gas gasPrice)
(let [fee (.times gas gasPrice)
available-ether (money/bignumber (get balance :ETH 0))
available-for-gas (if (= :ETH symbol)
(.minus available-ether (money/bignumber amount))
available-ether)]
(when-not (money/sufficient-funds? fee (money/bignumber available-for-gas))
{:gas-error (i18n/label :t/wallet-insufficient-gas)}))
{:gas-error (i18n/label :t/invalid-number)}))
(re-frame/reg-sub
:signing/amount-errors
:<- [:signing/tx]
:<- [:balance]
(fn [[{:keys [amount token gas gasPrice approve?]} balance]]
(if (and amount token (not approve?))
(let [amount-bn (money/formatted->internal (money/bignumber amount) (:symbol token) (:decimals token))
amount-error (or (get-amount-error amount (:decimals token))
(get-sufficient-funds-error balance (:symbol token) amount-bn))]
(or amount-error (get-sufficient-gas-error balance (:symbol token) amount-bn gas gasPrice)))
(get-sufficient-gas-error balance nil nil gas gasPrice))))

View File

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

View File

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

View File

@ -53,10 +53,10 @@
;; See https://github.com/facebook/react-native/issues/13760
:overflow :hidden}})
(def primary-button
(defn primary-button [disabled?]
(merge
button-borders
{:background-color colors/blue}))
{:background-color (if disabled? (colors/alpha colors/gray 0.1) colors/blue)}))
(def primary-button-text {:color colors/white})

View File

@ -18,13 +18,13 @@
[react/text {:style (merge styles/button-text
text-style
(when disabled?
{:opacity 0.4}))}
{:color colors/gray}))}
label]
icon]])
(defn primary-button [{:keys [style text-style] :as m} label]
(defn primary-button [{:keys [style text-style disabled?] :as m} label]
[button (assoc m
:style (merge styles/primary-button style)
:style (merge (styles/primary-button disabled?) style)
:text-style (merge styles/primary-button-text text-style))
label])

View File

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

View File

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

View File

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

View File

@ -204,6 +204,12 @@
(spec/def :extensions/profile (spec/nilable any?))
(spec/def :wallet/custom-token-screen (spec/nilable map?))
(spec/def :signing/in-progress? (spec/nilable boolean?))
(spec/def :signing/queue (spec/nilable any?))
(spec/def :signing/tx (spec/nilable map?))
(spec/def :signing/sign (spec/nilable map?))
(spec/def :signing/edit-fee (spec/nilable map?))
(spec/def ::db (spec/keys :opt [:contacts/contacts
:contacts/new-identity
:contacts/new-identity-error
@ -279,7 +285,12 @@
:bottom-sheet/view
:bottom-sheet/options
:extensions/profile
:wallet/custom-token-screen]
:wallet/custom-token-screen
:signing/in-progress?
:signing/queue
:signing/sign
:signing/tx
:signing/edit-fee]
:opt-un [::modal
::was-modal?
::rpc-url

View File

@ -232,7 +232,6 @@
:hardwallet-connect-settings (hardwallet/hardwallet-connect-screen-did-load %)
:hardwallet-connect-modal (hardwallet/hardwallet-connect-screen-did-load %)
:hardwallet-authentication-method (hardwallet/authentication-method-screen-did-load %)
:wallet-send-transaction (wallet/send-transaction-screen-did-load %)
:accounts (hardwallet/accounts-screen-did-load %)
:chat (mark-messages-seen %)
nil))))

View File

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

View File

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

View File

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

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

View File

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

View File

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

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
(:require [re-frame.core :as re-frame]
[status-im.chat.commands.sending :as commands-sending]
[status-im.constants :as constants]
(:require [status-im.utils.handlers :as handlers]
[status-im.utils.money :as money]
[status-im.wallet.db :as wallet.db]
[status-im.ethereum.tokens :as tokens]
[status-im.ethereum.abi-spec :as abi-spec]
[status-im.ethereum.core :as ethereum]
[status-im.ethereum.tokens :as tokens]
[status-im.i18n :as i18n]
[status-im.native-module.core :as status]
[status-im.transport.utils :as transport.utils]
[status-im.ui.screens.navigation :as navigation]
[status-im.utils.fx :as fx]
[status-im.utils.handlers :as handlers]
[status-im.utils.money :as money]
[status-im.utils.security :as security]
[status-im.utils.types :as types]
[status-im.utils.utils :as utils]
[status-im.wallet.core :as wallet]
[status-im.wallet.db :as wallet.db]))
;;;; FX
(defn- send-ethers [params on-completed masked-password]
(status/send-transaction (types/clj->json params)
(security/safe-unmask-data masked-password)
on-completed))
(defn- send-tokens
[all-tokens symbol chain
{:keys [from to value gas gasPrice]} on-completed masked-password]
(let [contract (:address (tokens/symbol->token all-tokens (keyword chain) symbol))]
(status/send-transaction (types/clj->json
{:to contract
:from from
:data (abi-spec/encode
"transfer(address,uint256)"
[to value])
:gas gas
:gasPrice gasPrice})
(security/safe-unmask-data masked-password)
on-completed)))
(re-frame/reg-fx
::send-transaction
(fn [[params all-tokens symbol chain on-completed masked-password]]
(if (= symbol :ETH)
(send-ethers params on-completed masked-password)
(send-tokens all-tokens symbol chain params on-completed masked-password))))
(re-frame/reg-fx
::sign-message
(fn [{:keys [params on-completed]}]
(status/sign-message (types/clj->json params)
on-completed)))
(re-frame/reg-fx
::sign-typed-data
(fn [{:keys [data on-completed password]}]
(status/sign-typed-data data (security/safe-unmask-data password) on-completed)))
(re-frame/reg-fx
:wallet/show-transaction-error
(fn [message]
;; (andrey) we need this timeout because modal window conflicts with alert
(utils/set-timeout #(utils/show-popup (i18n/label :t/transaction-failed) message) 1000)))
;;;; Handlers
;; SEND TRANSACTION
(handlers/register-handler-fx
:wallet/send-transaction
(fn [{{:keys [chain] :as db} :db} _]
(let [{:keys [password symbol in-progress?] :as transaction}
(get-in db [:wallet :send-transaction])
all-tokens (:wallet/all-tokens db)
from (ethereum/current-address db)]
(when-not in-progress?
{:db (-> db
(assoc-in [:wallet :send-transaction :wrong-password?] false)
(assoc-in [:wallet :send-transaction :in-progress?] true))
::send-transaction [(wallet/prepare-send-transaction from transaction)
all-tokens
symbol
chain
#(re-frame/dispatch [:wallet.callback/transaction-completed (types/json->clj %)])
password]}))))
;; SIGN MESSAGE
(handlers/register-handler-fx
:wallet/sign-message
(fn [_ [_ typed? screen-params password-error-cb]]
(let [{:keys [data from password]} screen-params]
(if typed?
{::sign-typed-data {:data data
:password password
:account from
:on-completed #(re-frame/dispatch [::sign-message-completed
screen-params
(types/json->clj %)
password-error-cb])}}
{::sign-message {:params {:data data
:password (security/safe-unmask-data password)
:account from}
:on-completed #(re-frame/dispatch [::sign-message-completed
screen-params
(types/json->clj %)
password-error-cb])}}))))
(fx/defn send-transaction-message
"NOTE(goranjovic): we want to send the payment message only when we have a
whisper id for the recipient, we always redirect to `:wallet-transaction-sent`
even when we don't"
[{:keys [db] :as cofx} chat-id params]
(let [send-command? (and chat-id
(get-in db [:id->command ["send" #{:personal-chats}]]))]
(when send-command?
(commands-sending/send cofx chat-id send-command? params))))
;; SEND TRANSACTION CALLBACK
(handlers/register-handler-fx
:wallet.callback/transaction-completed
[(re-frame/inject-cofx :random-id-generator)]
(fn [{:keys [db now] :as cofx} [_ {:keys [result error]}]]
(let [{:keys [id method public-key to symbol asset amount-text on-result on-error
send-transaction-message?]}
(get-in db [:wallet :send-transaction])
db' (assoc-in db [:wallet :send-transaction :in-progress?] false)
modal-screen-was-used? (get-in db [:navigation/screen-params :wallet-send-modal-stack :modal?])]
(if error
;; ERROR
(wallet/handle-transaction-error (assoc cofx :db db') error)
;; RESULT
(fx/merge cofx
(merge
{:db (cond-> (assoc-in db' [:wallet :send-transaction] {})
(not (constants/web3-sign-message? method))
(assoc-in [:wallet :transactions result]
(wallet/prepare-unconfirmed-transaction db now result)))}
(when on-result
{:dispatch (conj on-result id result method)}))
#(when (or (not on-result)
send-transaction-message?)
(send-transaction-message
%
public-key
{:address to
:asset (name (or asset symbol))
:amount amount-text
:tx-hash result}))
#(when-not on-result
(navigation/navigate-to-clean
%
(if modal-screen-was-used?
:wallet-transaction-sent-modal
:wallet-transaction-sent)
{})))))))
(re-frame/reg-fx
:show-sign-message-error
(fn [[password-error-cb]]
(password-error-cb)))
;; SIGN MESSAGE CALLBACK
(handlers/register-handler-fx
::sign-message-completed
(fn [{:keys [db now] :as cofx} [_ {:keys [on-result id method]} {:keys [result error]} password-error-cb]]
(let [db' (assoc-in db [:wallet :send-transaction :in-progress?] false)]
(if error
;; ERROR
{:show-sign-message-error [password-error-cb]}
;; RESULT
(if on-result
{:dispatch (conj on-result id result method)})))))
(handlers/register-handler-fx
::hash-message-completed
(fn [{:keys [db] :as cofx} [_ {:keys [result error]}]]
(let [view-id (:view-id db)
db' (assoc-in db [:wallet :send-transaction :in-progress?] false)]
(if error
;; ERROR
(wallet/handle-transaction-error (assoc cofx :db db') error)
;; RESULT
(fx/merge cofx
{:db (-> db
(assoc-in [:hardwallet :pin :enter-step] :sign)
(assoc-in [:hardwallet :hash] result))}
(navigation/navigate-to-cofx
(if (contains?
#{:wallet-sign-message-modal :wallet-send-transaction-modal :wallet-send-modal-stack}
view-id)
:enter-pin-modal
:enter-pin-sign)
nil))))))
;; DISCARD TRANSACTION
(handlers/register-handler-fx
:wallet/discard-transaction
(fn [cofx _]
(wallet/discard-transaction cofx)))
(handlers/register-handler-fx
:wallet.dapp/transaction-on-result
(fn [{db :db} [_ message-id id result method]]
(let [webview (:webview-bridge db)
keycard? (boolean (get-in db [:account/account :keycard-instance-uid]))]
(wallet/dapp-complete-transaction (int id) result method message-id webview keycard?))))
(handlers/register-handler-fx
:wallet.dapp/transaction-on-error
(fn [{db :db} [_ message-id message]]
(wallet/web3-error-callback {} db message-id message)))
;; DAPP TRANSACTIONS QUEUE
;; NOTE(andrey) We need this queue because dapp can send several transactions in a row, this is bad behaviour
;; but we need to support it
(handlers/register-handler-fx
:check-dapps-transactions-queue
(fn [{:keys [db]} _]
(let [{:keys [send-transaction transactions-queue]} (:wallet db)
{:keys [payload message-id] :as queued-transaction} (last transactions-queue)
{:keys [method params id]} payload
keycard? (boolean (get-in db [:account/account :keycard-instance-uid]))
db' (update-in db [:wallet :transactions-queue] drop-last)]
(when (and (not (contains? #{:wallet-transaction-sent
:wallet-transaction-sent-modal}
(:view-id db)))
(not (:id send-transaction)) queued-transaction)
(cond
;;SEND TRANSACTION
(= method constants/web3-send-transaction)
(let [transaction (wallet/prepare-dapp-transaction queued-transaction (:contacts/contacts db))]
(wallet/open-modal-wallet-for-transaction db' transaction (first params)))
;;SIGN MESSAGE
(constants/web3-sign-message? method)
(let [typed? (not= constants/web3-personal-sign method)
[address data] (wallet/normalize-sign-message-params params)]
(if (and address data)
(let [signing-phrase (-> (get-in db [:account/account :signing-phrase])
(clojure.string/replace #" " " "))
screen-params {:id (str (or id message-id))
:from address
:data data
:typed? typed?
:decoded-data (if typed? (types/json->clj data) (ethereum/hex-to-utf8 data))
:on-result [:wallet.dapp/transaction-on-result message-id]
:on-error [:wallet.dapp/transaction-on-error message-id]
:method method
:signing-phrase signing-phrase
:keycard? keycard?}]
(navigation/navigate-to-cofx {:db db'} :wallet-sign-message-modal screen-params))
{:db db'})))))))
[status-im.signing.core :as signing]))
(defn set-and-validate-amount-db [db amount symbol decimals]
(let [{:keys [value error]} (wallet.db/parse-amount amount decimals)]
@ -266,191 +19,25 @@
(fn [{:keys [db]} [_ amount symbol decimals]]
{:db (set-and-validate-amount-db db amount symbol decimals)}))
(handlers/register-handler-fx
:wallet/discard-transaction-navigate-back
(fn [cofx _]
(fx/merge cofx
(navigation/navigate-back)
(wallet/discard-transaction))))
(defn update-gas-price
([db edit? success-event]
{:wallet/update-gas-price
{:success-event (or success-event :wallet/update-gas-price-success)
:edit? edit?}})
([db edit?] (update-gas-price db edit? :wallet/update-gas-price-success))
([db] (update-gas-price db false :wallet/update-gas-price-success)))
(defn recalculate-gas [{:keys [db] :as fx} symbol]
(-> fx
(assoc-in [:db :wallet :send-transaction :gas] (ethereum/estimate-gas symbol))
(merge (update-gas-price db))))
(handlers/register-handler-fx
:wallet/update-gas-price
(fn [{:keys [db]} [_ edit?]]
(update-gas-price db edit?)))
(handlers/register-handler-fx
:wallet.send/set-symbol
(fn [{:keys [db]} [_ symbol]]
(let [old-symbol (get-in db [:wallet :send-transaction :symbol])]
(cond-> {:db (-> db
(assoc-in [:wallet :send-transaction :symbol] symbol)
(assoc-in [:wallet :send-transaction :amount] nil)
(assoc-in [:wallet :send-transaction :amount-text] nil)
(assoc-in [:wallet :send-transaction :asset-error] nil))}
(not= old-symbol symbol) (recalculate-gas symbol)))))
(handlers/register-handler-fx
:wallet.send/toggle-advanced
(fn [{:keys [db]} [_ advanced?]]
{:db (assoc-in db [:wallet :send-transaction :advanced?] advanced?)}))
(handlers/register-handler-fx
:wallet/cancel-entering-password
(fn [{:keys [db]} _]
{:db (update-in db [:wallet :send-transaction] assoc
:show-password-input? false
:wrong-password? false
:password nil)}))
(handlers/register-handler-fx
:wallet.send/set-password
(fn [{:keys [db]} [_ masked-password]]
{:db (assoc-in db [:wallet :send-transaction :password] masked-password)}))
(handlers/register-handler-fx
:wallet.send/edit-value
(fn [cofx [_ key value]]
(wallet/edit-value key value cofx)))
(handlers/register-handler-fx
:wallet.send/set-gas-details
(fn [{:keys [db]} [_ gas gas-price]]
{:db (-> db
(assoc-in [:wallet :send-transaction :gas] gas)
(assoc-in [:wallet :send-transaction :gas-price] gas-price))}))
(handlers/register-handler-fx
:wallet.send/clear-gas
(fn [{:keys [db]}]
{:db (update db :wallet dissoc :edit)}))
(handlers/register-handler-fx
:wallet.send/reset-gas-default
(fn [{:keys [db] :as cofx}]
(let [gas-default (if-some [original-gas (-> db :wallet :send-transaction :original-gas)]
(money/to-fixed original-gas)
(money/to-fixed
(ethereum/estimate-gas
(-> db :wallet :send-transaction :symbol))))]
(assoc (wallet/edit-value
:gas
gas-default
cofx)
:dispatch [:wallet/update-gas-price true]))))
(handlers/register-handler-fx
:close-transaction-sent-screen
(fn [cofx [_ chat-id]]
(fx/merge cofx
{:dispatch-later [{:ms 400 :dispatch [:check-dapps-transactions-queue]}]}
(navigation/navigate-back))))
(re-frame/reg-fx
::hash-transaction
(fn [{:keys [transaction on-completed]}]
(status/hash-transaction (types/clj->json transaction) on-completed)))
(re-frame/reg-fx
::hash-message
(fn [{:keys [message on-completed]}]
(status/hash-message message on-completed)))
(re-frame/reg-fx
::hash-typed-data
(fn [{:keys [data on-completed]}]
(status/hash-typed-data data on-completed)))
(defn- prepare-keycard-transaction
[transaction from symbol chain all-tokens]
(if (= :ETH symbol)
(wallet/prepare-send-transaction from transaction)
(let [contract (:address (tokens/symbol->token all-tokens (keyword chain) symbol))
{:keys [gas gasPrice to from value]} (wallet/prepare-send-transaction from transaction)]
(merge (abi-spec/encode "transfer(address,uint256)" [to value])
{:to contract
:from from
:gas gas
:gasPrice gasPrice}))))
(defn send-keycard-transaction
[{{:keys [chain] :as db} :db}]
(let [{:keys [symbol] :as transaction} (get-in db [:wallet :send-transaction])
all-tokens (:wallet/all-tokens db)
from (get-in db [:account/account :address])]
{::hash-transaction {:transaction (prepare-keycard-transaction transaction from symbol chain all-tokens)
:all-tokens all-tokens
:symbol symbol
:chain chain
:on-completed #(re-frame/dispatch [:wallet.callback/hash-transaction-completed %])}}))
(handlers/register-handler-fx
:wallet.callback/hash-transaction-completed
(fn [{:keys [db] :as cofx} [_ result]]
(let [{:keys [transaction hash]} (:result (types/json->clj result))]
(fx/merge cofx
{:db (-> db
(assoc-in [:hardwallet :pin :enter-step] :sign)
(assoc-in [:hardwallet :transaction] transaction)
(assoc-in [:hardwallet :hash] hash))}
(navigation/navigate-to-clean
(if (contains?
#{:wallet-sign-message-modal :wallet-send-transaction-modal :wallet-send-modal-stack}
(:view-id db))
:enter-pin-modal
:enter-pin-sign)
nil)))))
(assoc-in [:wallet :send-transaction :symbol] symbol)
(assoc-in [:wallet :send-transaction :amount] nil)
(assoc-in [:wallet :send-transaction :amount-text] nil)
(assoc-in [:wallet :send-transaction :asset-error] nil))}))
(handlers/register-handler-fx
:wallet.ui/sign-transaction-button-clicked
(fn [{:keys [db] :as cofx} _]
(let [keycard-account? (boolean (get-in db [:account/account :keycard-instance-uid]))]
(if keycard-account?
(send-keycard-transaction cofx)
{:db (assoc-in db [:wallet :send-transaction :show-password-input?] true)}))))
(fx/defn keycard-hash-message
[_ data typed?]
(if typed?
{::hash-typed-data {:data data
:on-completed #(re-frame/dispatch [::hash-message-completed (types/json->clj %)])}}
{::hash-message {:message (ethereum/naked-address data)
:on-completed #(re-frame/dispatch [::hash-message-completed (types/json->clj %)])}}))
(handlers/register-handler-fx
:wallet.ui/sign-message-button-clicked
(fn [{:keys [db] :as cofx} [_ typed? screen-params password-error-cb]]
(let [{:keys [data from password]} screen-params
keycard-account? (boolean (get-in db [:account/account :keycard-instance-uid]))
modal? (= (:view-id db) :wallet-sign-message-modal)]
(if keycard-account?
(fx/merge cofx
{:db (assoc-in db [:navigation/screen-params :wallet-send-modal-stack :modal?] modal?)}
(keycard-hash-message data typed?))
(if typed?
{::sign-typed-data {:data data
:password password
:account from
:on-completed #(re-frame/dispatch [::sign-message-completed
screen-params
(types/json->clj %)
password-error-cb])}}
{::sign-message {:params {:data data
:password (security/safe-unmask-data password)
:account from}
:on-completed #(re-frame/dispatch [::sign-message-completed
screen-params
(types/json->clj %)
password-error-cb])}})))))
(let [{:keys [to symbol amount]} (get-in cofx [:db :wallet :send-transaction])
{:keys [symbol address]} (tokens/asset-for (:wallet/all-tokens db) (keyword (:chain db)) symbol)
amount-hex (str "0x" (abi-spec/number-to-hex amount))
to-norm (ethereum/normalized-address to)]
(signing/sign cofx {:tx-obj (if (= symbol :ETH)
{:to to-norm
:value amount-hex}
{:to (ethereum/normalized-address address)
:data (abi-spec/encode "transfer(address,uint256)" [to-norm amount-hex])})
:on-result [:navigate-back]}))))

View File

@ -1,128 +1,31 @@
(ns status-im.ui.screens.wallet.send.views
(:require-macros [status-im.utils.views :refer [defview letsubs]])
(:require [re-frame.core :as re-frame]
[reagent.core :as reagent]
[status-im.ethereum.core :as ethereum]
[status-im.ethereum.tokens :as tokens]
[status-im.i18n :as i18n]
[status-im.ui.components.animation :as animation]
[status-im.ui.components.bottom-buttons.view :as bottom-buttons]
[status-im.ui.components.button.view :as button]
[status-im.ui.components.colors :as colors]
[status-im.ui.components.common.common :as common]
[status-im.ui.components.icons.vector-icons :as vector-icons]
[status-im.ui.components.react :as react]
[status-im.ui.components.status-bar.view :as status-bar]
[status-im.ui.components.styles :as components.styles]
[status-im.ui.components.toolbar.actions :as actions]
[status-im.ui.components.toolbar.view :as toolbar]
[status-im.ui.components.tooltip.views :as tooltip]
[status-im.ui.screens.wallet.components.styles
:as
wallet.components.styles]
[status-im.ui.screens.wallet.components.views :as wallet.components]
[status-im.ui.screens.wallet.main.views :as wallet.main.views]
[status-im.ui.screens.wallet.send.animations :as send.animations]
[status-im.ui.screens.wallet.send.styles :as styles]
[status-im.ui.screens.wallet.styles :as wallet.styles]
[status-im.ui.screens.wallet.utils :as wallet.utils]
[status-im.utils.money :as money]
[status-im.utils.security :as security]
[status-im.utils.utils :as utils]
[taoensso.timbre :as log])
(:require-macros [status-im.utils.views :refer [defview letsubs]]))
[status-im.ui.components.toolbar.actions :as actions]))
(defn- toolbar [modal? title]
(let [action (if modal? actions/close-white actions/back-white)]
[toolbar/toolbar {:transparent? true}
[toolbar/nav-button (action (if modal?
#(re-frame/dispatch [:wallet/discard-transaction-navigate-back])
#(actions/default-handler)))]
[toolbar/content-title {:color :white} title]]))
(defn- toolbar [title]
[toolbar/toolbar {:transparent? true}
[toolbar/nav-button (actions/back-white #(actions/default-handler))]
[toolbar/content-title {:color :white} title]])
(defn- advanced-cartouche [native-currency {:keys [max-fee gas gas-price]}]
[react/view
[wallet.components/cartouche {:on-press #(do (re-frame/dispatch [:wallet.send/clear-gas])
(re-frame/dispatch [:navigate-to :wallet-transaction-fee]))}
(i18n/label :t/wallet-transaction-fee)
[react/view {:style styles/advanced-options-text-wrapper
:accessibility-label :transaction-fee-button}
[react/text {:style styles/advanced-fees-text}
(str max-fee " " (wallet.utils/display-symbol native-currency))]
[react/text {:style styles/advanced-fees-details-text}
(str (money/to-fixed gas) " * " (money/to-fixed (money/wei-> :gwei gas-price)) (i18n/label :t/gwei))]]]])
(defn- advanced-options [advanced? native-currency transaction scroll]
[react/view {:style styles/advanced-wrapper}
[react/touchable-highlight {:on-press (fn []
(re-frame/dispatch [:wallet.send/toggle-advanced (not advanced?)])
(when (and scroll @scroll) (utils/set-timeout #(.scrollToEnd @scroll) 350)))}
[react/view {:style styles/advanced-button-wrapper}
[react/view {:style styles/advanced-button
:accessibility-label :advanced-button}
[react/i18n-text {:style (merge wallet.components.styles/label
styles/advanced-label)
:key :wallet-advanced}]
[vector-icons/icon (if advanced? :main-icons/dropdown-up :main-icons/dropdown) {:color :white}]]]]
(when advanced?
[advanced-cartouche native-currency transaction])])
(defview password-input-panel [message-label spinning?]
(letsubs [account [:account/account]
wrong-password? [:wallet.send/wrong-password?]
signing-phrase (:signing-phrase @account)
bottom-value (animation/create-value -250)
opacity-value (animation/create-value 0)]
{:component-did-mount #(send.animations/animate-sign-panel opacity-value bottom-value)}
[react/animated-view {:style (styles/animated-sign-panel bottom-value)}
(when wrong-password?
[tooltip/tooltip (i18n/label :t/wrong-password) styles/password-error-tooltip])
[react/animated-view {:style (styles/sign-panel opacity-value)}
[react/view styles/spinner-container
(when spinning?
[react/activity-indicator {:animating true
:size :large}])]
[react/view styles/signing-phrase-container
[react/text {:accessibility-label :signing-phrase-text}
signing-phrase]]
[react/i18n-text {:style styles/signing-phrase-description :key message-label}]
[react/view {:style styles/password-container
:important-for-accessibility :no-hide-descendants}
[react/text-input
{:auto-focus true
:secure-text-entry true
:placeholder (i18n/label :t/enter-password)
:placeholder-text-color colors/gray
:on-change-text #(re-frame/dispatch [:wallet.send/set-password (security/mask-data %)])
:style styles/password
:accessibility-label :enter-password-input
:auto-capitalize :none}]]]]))
;; "Cancel" and "Sign Transaction >" or "Sign >" buttons, signing with password
(defview enter-password-buttons [spinning? cancel-handler sign-handler sign-label]
(letsubs [sign-enabled? [:wallet.send/sign-password-enabled?]
network-status [:network-status]]
[bottom-buttons/bottom-buttons
styles/sign-buttons
[button/button {:style components.styles/flex
:on-press cancel-handler
:accessibility-label :cancel-button}
(i18n/label :t/cancel)]
[button/button {:style (wallet.styles/button-container sign-enabled?)
:on-press sign-handler
:disabled? (or spinning?
(not sign-enabled?)
(= :offline network-status))
:accessibility-label :sign-transaction-button}
(i18n/label sign-label)
[vector-icons/icon :main-icons/next {:color colors/white}]]]))
;; "Sign Transaction >" button
(defn- sign-transaction-button [amount-error to amount sufficient-funds? sufficient-gas? modal? online?]
(defn- sign-transaction-button [amount-error amount sufficient-funds? online?]
(let [sign-enabled? (and (nil? amount-error)
(or modal? (not (empty? to))) ;;NOTE(goranjovic) - contract creation will have empty `to`
(not (nil? amount))
sufficient-funds?
sufficient-gas?
online?)]
[bottom-buttons/bottom-buttons
styles/sign-buttons
@ -135,79 +38,40 @@
(i18n/label :t/transactions-sign-transaction)
[vector-icons/icon :main-icons/next {:color (if sign-enabled? colors/white colors/white-light-transparent)}]]]))
(defn signing-phrase-view [signing-phrase]
[react/view {:flex-direction :column
:align-items :center
:margin-top 10}
[react/view (assoc styles/signing-phrase-container :width "90%" :height 40)
[react/text {:accessibility-label :signing-phrase-text
:style {:padding-vertical 16
:text-align :center}}
signing-phrase]]
[react/text {:style {:color :white
:text-align :center
:font-size 12
:padding-vertical 14}}
(i18n/label :t/signing-phrase-warning)]])
(defn- render-send-transaction-view [{:keys [chain modal? transaction scroll advanced? keycard? signing-phrase all-tokens amount-input network-status]}]
(let [{:keys [amount amount-text amount-error asset-error show-password-input? to to-name sufficient-funds?
sufficient-gas? in-progress? from-chat? asset symbol]} transaction
native-currency (tokens/native-currency chain)
(defn- render-send-transaction-view [{:keys [chain transaction scroll all-tokens amount-input network-status]}]
(let [{:keys [amount amount-text amount-error asset-error to to-name sufficient-funds? symbol]} transaction
{:keys [decimals] :as token} (tokens/asset-for all-tokens chain symbol)
online? (= :online network-status)]
[wallet.components/simple-screen {:avoid-keyboard? (not modal?)
:status-bar-type (if modal? :modal-wallet :wallet)}
[toolbar modal? (i18n/label :t/send-transaction)]
[wallet.components/simple-screen {:avoid-keyboard? true
:status-bar-type :wallet}
[toolbar (i18n/label :t/send-transaction)]
[react/view components.styles/flex
[common/network-info {:text-color :white}]
[react/scroll-view {:keyboard-should-persist-taps :always
:ref #(reset! scroll %)
:on-content-size-change #(when (and (not modal?) scroll @scroll)
:on-content-size-change #(when (and scroll @scroll)
(.scrollToEnd @scroll))}
(when-not online?
[wallet.main.views/snackbar :t/error-cant-send-transaction-offline])
[react/view styles/send-transaction-form
[wallet.components/recipient-selector
{:disabled? (or from-chat? modal? show-password-input?)
:address to
:name to-name
:modal? modal?}]
{:address to
:name to-name}]
[wallet.components/asset-selector
{:disabled? (or from-chat? modal? show-password-input?)
:error asset-error
{:error asset-error
:type :send
:symbol (or asset symbol)}]
:symbol symbol}]
[wallet.components/amount-selector
{:disabled? (or from-chat? modal? show-password-input?)
:error (or amount-error
(when-not sufficient-funds? (i18n/label :t/wallet-insufficient-funds))
(when-not sufficient-gas? (i18n/label :t/wallet-insufficient-gas)))
{:error (or amount-error (when-not sufficient-funds? (i18n/label :t/wallet-insufficient-funds)))
:amount amount
:amount-text amount-text
:input-options {:on-change-text #(re-frame/dispatch [:wallet.send/set-and-validate-amount % symbol decimals])
:ref (partial reset! amount-input)}} token]
[advanced-options advanced? native-currency transaction scroll]
(when keycard?
[signing-phrase-view signing-phrase])]]
(if show-password-input?
[enter-password-buttons in-progress?
#(re-frame/dispatch [:wallet/cancel-entering-password])
#(re-frame/dispatch [:wallet/send-transaction])
:t/transactions-sign-transaction]
[sign-transaction-button amount-error to amount sufficient-funds? sufficient-gas? modal? online?])
(when show-password-input?
[password-input-panel :t/signing-phrase-description in-progress?])
(when in-progress? [react/view styles/processing-view])]]))
:ref (partial reset! amount-input)}} token]]]
[sign-transaction-button amount-error amount sufficient-funds? online?]]]))
;; MAIN SEND TRANSACTION VIEW
(defn- send-transaction-view [{:keys [scroll] :as opts}]
(defn- send-transaction-view [{:keys [scroll]}]
(let [amount-input (atom nil)
handler (fn [_]
(when (and scroll @scroll @amount-input
(.isFocused @amount-input))
(log/debug "Amount field focused, scrolling down")
(.scrollToEnd @scroll)))]
handler #(when (and scroll @scroll @amount-input (.isFocused @amount-input)) (.scrollToEnd @scroll))]
(reagent/create-class
{:component-will-mount (fn [_]
;;NOTE(goranjovic): keyboardDidShow is for android and keyboardWillShow for ios
@ -216,49 +80,14 @@
:reagent-render (fn [opts] (render-send-transaction-view
(assoc opts :amount-input amount-input)))})))
;; SEND TRANSACTION FROM WALLET (CHAT)
(defview send-transaction []
(letsubs [transaction [:wallet.send/transaction]
advanced? [:wallet.send/advanced?]
chain [:ethereum/chain-keyword]
scroll (atom nil)
network-status [:network-status]
all-tokens [:wallet/all-tokens]
signing-phrase [:wallet.send/signing-phrase-with-padding]
keycard? [:keycard-account?]]
[send-transaction-view {:modal? false
:transaction transaction
all-tokens [:wallet/all-tokens]]
[send-transaction-view {:transaction transaction
:scroll scroll
:advanced? advanced?
:keycard? keycard?
:signing-phrase signing-phrase
:chain chain
:all-tokens all-tokens
:network-status network-status}]))
;; SEND TRANSACTION FROM DAPP
(defview send-transaction-modal []
(letsubs [transaction [:wallet.send/transaction]
advanced? [:wallet.send/advanced?]
chain [:ethereum/chain-keyword]
scroll (atom nil)
network-status [:network-status]
all-tokens [:wallet/all-tokens]
signing-phrase [:wallet.send/signing-phrase-with-padding]
keycard? [:keycard-account?]]
(if transaction
[send-transaction-view {:modal? true
:transaction transaction
:scroll scroll
:advanced? advanced?
:keycard? keycard?
:signing-phrase signing-phrase
:chain chain
:all-tokens all-tokens
:network-status network-status}]
[react/view wallet.styles/wallet-modal-container
[react/view components.styles/flex
[status-bar/status-bar {:type :modal-wallet}]
[toolbar true (i18n/label :t/send-transaction)]
[react/i18n-text {:style styles/empty-text
:key :unsigned-transaction-expired}]]])))
:network-status network-status}]))

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.core :as utils.core]
[status-im.utils.fx :as fx]
[status-im.utils.hex :as utils.hex]
[status-im.utils.money :as money]
[status-im.utils.prices :as prices]
[status-im.utils.utils :as utils.utils]
@ -49,14 +48,6 @@
{:db (assoc-in db [:wallet :current-transaction] hash)}
(navigation/navigate-to-cofx :wallet-transaction-details nil)))
(fx/defn open-send-transaction-modal
[{:keys [db] :as cofx} id method from-chat?]
(fx/merge cofx
{:db (assoc-in db [:wallet :send-transaction] {:id id
:method method
:from-chat? from-chat?})}
(navigation/navigate-to-clean :wallet-send-transaction-modal nil)))
;; FX
(re-frame/reg-fx
@ -80,23 +71,6 @@
#(re-frame/dispatch [error-event %])
chaos-mode?)))
(re-frame/reg-fx
:wallet/update-gas-price
(fn [{:keys [success-event edit?]}]
(json-rpc/call
{:method "eth_gasPrice"
:on-success
#(re-frame/dispatch [success-event % edit?])})))
(re-frame/reg-fx
:wallet/update-estimated-gas
(fn [{:keys [obj success-event]}]
(json-rpc/call
{:method "eth_estimateGas"
:params [obj]
:on-success
#(re-frame/dispatch [success-event %])})))
(defn- validate-token-name!
[{:keys [address symbol name]}]
(json-rpc/eth-call
@ -175,183 +149,6 @@
(on-success symbol (money/bignumber balance)))
:on-error #(on-error symbol %)}))))
(def min-gas-price-wei (money/bignumber 1))
(defmulti invalid-send-parameter? (fn [type _] type))
(defmethod invalid-send-parameter? :gas-price [_ value]
(cond
(not value) :invalid-number
(.lt (money/->wei :gwei value) min-gas-price-wei) :not-enough-wei
(-> (money/->wei :gwei value) .decimalPlaces pos?) :invalid-number))
(defmethod invalid-send-parameter? :default [_ value]
(when (or (not value)
(<= value 0))
:invalid-number))
(defn- calculate-max-fee
[gas gas-price]
(if (and gas gas-price)
(money/to-fixed (money/wei->ether (.times gas gas-price)))
"0"))
(defn- edit-max-fee [edit]
(let [gas (get-in edit [:gas-price :value-number])
gas-price (get-in edit [:gas :value-number])]
(assoc edit :max-fee (calculate-max-fee gas gas-price))))
(defn add-max-fee [{:keys [gas gas-price] :as transaction}]
(assoc transaction :max-fee (calculate-max-fee gas gas-price)))
(defn build-edit [edit-value key value]
"Takes the previous edit, either :gas or :gas-price and a value as string.
Wei for gas, and gwei for gas price.
Validates them and sets max fee"
(let [bn-value (money/bignumber value)
invalid? (invalid-send-parameter? key bn-value)
data (if invalid?
{:value value
:max-fee 0
:invalid? invalid?}
{:value value
:value-number (if (= :gas-price key)
(money/->wei :gwei bn-value)
bn-value)
:invalid? false})]
(-> edit-value
(assoc key data)
edit-max-fee)))
(defn edit-value
[key value {:keys [db]}]
{:db (update-in db [:wallet :edit] build-edit key value)})
;; DAPP TRANSACTION -> SEND TRANSACTION
(defn prepare-dapp-transaction [{{:keys [id method params]} :payload message-id :message-id} contacts]
(let [{:keys [to value data gas gasPrice nonce]} (first params)
contact (get contacts (utils.hex/normalize-hex to))]
(cond-> {:id (str id)
:to-name (or (when (nil? to)
(i18n/label :t/new-contract))
contact)
:symbol :ETH
:method method
:to to
:amount (money/bignumber (or value 0))
:gas (cond
gas
(money/bignumber gas)
(and value (empty? data))
(money/bignumber 21000))
:gas-price (when gasPrice
(money/bignumber gasPrice))
:data data
:on-result [:wallet.dapp/transaction-on-result message-id]
:on-error [:wallet.dapp/transaction-on-error message-id]}
nonce
(assoc :nonce nonce))))
;; SEND TRANSACTION -> RPC TRANSACTION
(defn prepare-send-transaction
[from {:keys [amount to gas gas-price data nonce]}]
(cond-> {:from (ethereum/normalized-address from)
:to (ethereum/normalized-address to)
:value (str "0x" (abi-spec/number-to-hex amount))
:gas (str "0x" (abi-spec/number-to-hex gas))
:gasPrice (str "0x" (abi-spec/number-to-hex gas-price))}
data
(assoc :data data)
nonce
(assoc :nonce nonce)))
(defn normalize-sign-message-params
"NOTE (andrey) we need this function, because params may be mixed up,
so we need to figure out which one is address and which message"
[params]
(let [first_param (first params)
second_param (second params)
first-param-address? (ethereum/address? first_param)
second-param-address? (ethereum/address? second_param)]
(when (or first-param-address? second-param-address?)
(if first-param-address?
[first_param second_param]
[second_param first_param]))))
(defn web3-error-callback
[fx {:keys [webview-bridge]} message-id message]
(assoc fx :browser/send-to-bridge
{:message {:type constants/web3-send-async-callback
:messageId message-id
:error message}
:webview webview-bridge}))
(defn dapp-complete-transaction
[id result method message-id webview keycard?]
(cond-> {:browser/send-to-bridge
{:message {:type constants/web3-send-async-callback
:messageId message-id
:result {:jsonrpc "2.0"
:id (int id)
:result result}}
:webview webview}
:dispatch [:navigate-back]}
(constants/web3-sign-message? method)
(assoc :dispatch (if keycard?
[:navigate-to :browser]
[:navigate-back]))
(= method constants/web3-send-transaction)
(assoc :dispatch [:navigate-to-clean :wallet-transaction-sent-modal])))
(fx/defn discard-transaction
[{:keys [db]}]
(let [{:keys [on-error]} (get-in db [:wallet :send-transaction])]
(merge {:db (update db :wallet
assoc
:send-transaction {}
:transactions-queue nil)}
(when on-error
{:dispatch (conj on-error "transaction was cancelled by user")}))))
(defn prepare-unconfirmed-transaction
[db now hash]
(let [transaction (get-in db [:wallet :send-transaction])
all-tokens (:wallet/all-tokens db)]
(let [chain (:chain db)
token (tokens/symbol->token all-tokens (keyword chain) (:symbol transaction))]
(-> transaction
(assoc :timestamp (str now)
:type :pending
:hash hash
:value (:amount transaction)
:token token
:gas-limit (str (:gas transaction)))
(update :gas-price str)
(dissoc :message-id :id :gas)))))
(fx/defn handle-transaction-error
[{:keys [db] :as cofx} {:keys [code message]}]
(let [{:keys [on-error]} (get-in db [:wallet :send-transaction])]
(log/warn :wallet/transaction-error
:code code
:message message)
(case code
;;WRONG PASSWORD
constants/send-transaction-err-decrypt
{:db (-> db
(assoc-in [:wallet :send-transaction :wrong-password?] true))}
(fx/merge cofx
(merge {:db (-> db
(assoc-in [:wallet :transactions-queue] nil)
(assoc-in [:wallet :send-transaction] {}))
:wallet/show-transaction-error message}
(when on-error
{:dispatch (conj on-error message)}))
navigation/navigate-back))))
(defn clear-error-message [db error-type]
(update-in db [:wallet :errors] dissoc error-type))
@ -450,29 +247,6 @@
(clear-error-message :prices-update)
(assoc :prices-loading? true))})))
(defn open-modal-wallet-for-transaction
[db transaction tx-object]
(let [{:keys [gas gas-price]} transaction
{:keys [wallet-set-up-passed?]} (:account/account db)]
{:db (-> db
(assoc-in [:navigation/screen-params :wallet-send-modal-stack :modal?] true)
(assoc-in [:wallet :send-transaction] transaction)
(assoc-in [:wallet :send-transaction :original-gas] gas))
:dispatch-n [(when-not gas
[:TODO.remove/update-estimated-gas tx-object])
(when-not gas-price
[:wallet/update-gas-price])
[:navigate-to
(if wallet-set-up-passed?
:wallet-send-modal-stack
:wallet-send-modal-stack-with-onboarding)]]}))
(defn send-transaction-screen-did-load
[{:keys [db]}]
{:db (assoc-in db
[:navigation/screen-params :wallet-send-modal-stack :modal?]
false)})
(defn- set-checked [ids id checked?]
(if checked?
(conj (or ids #{}) id)
@ -496,29 +270,6 @@
(assoc-in [:wallet :balance symbol] (money/bignumber balance))
(assoc-in [:wallet :balance-loading?] false))})
(fx/defn update-gas-price
[{:keys [db] :as cofx} price edit?]
(if edit?
(edit-value
:gas-price
(money/to-fixed
(money/wei-> :gwei price))
cofx)
{:db (assoc-in db [:wallet :send-transaction :gas-price] price)}))
(fx/defn update-estimated-gas-price
[{:keys [db]} gas]
(when gas
(let [adjusted-gas (money/bignumber (int (* gas 1.2)))
db-with-adjusted-gas (assoc-in db
[:wallet :send-transaction :gas]
adjusted-gas)]
{:db (if (some? (-> db :wallet :send-transaction :original-gas))
db-with-adjusted-gas
(assoc-in db-with-adjusted-gas
[:wallet :send-transaction :original-gas]
adjusted-gas))})))
(defn update-toggle-in-settings
[{{:account/keys [account] :as db} :db} symbol checked?]
(let [chain (ethereum/chain-keyword db)
@ -549,35 +300,4 @@
(fx/merge cofx
(toggle-visible-token symbol true)
;;TODO(goranjovic): move `update-token-balance-success` function to wallet models
(update-token-balance symbol balance)))
(fx/defn eth-transaction-call
[{:keys [db] :as cofx}
{:keys [contract method params on-result on-error details] :as transaction}]
(let [current-address (ethereum/current-address db)
transaction (merge {:to contract
:from current-address
:data (abi-spec/encode method params)
:id "approve"
:symbol :ETH
:method "eth_sendTransaction"
:amount (money/bignumber 0)
:on-result on-result
:on-error on-error}
details)
go-to-view-id (if (get-in db [:account/account :wallet-set-up-passed?])
:wallet-send-modal-stack
:wallet-send-modal-stack-with-onboarding)]
(fx/merge cofx
{:db (-> db
(assoc-in [:navigation/screen-params :wallet-send-modal-stack :modal?] true)
(assoc-in [:wallet :send-transaction]
transaction))
:wallet/update-estimated-gas
{:obj (select-keys transaction [:to :from :data])
:success-event :wallet/update-estimated-gas-success}
:wallet/update-gas-price
{:success-event :wallet/update-gas-price-success
:edit? false}}
(navigation/navigate-to-cofx go-to-view-id {}))))
(update-token-balance symbol balance)))

View File

@ -40,23 +40,7 @@
{:title (i18n/label :t/send-request-amount)
:description (i18n/label :t/send-request-amount-max-decimals {:asset-decimals 18})}))
(is (= (protocol/validate personal-send-command {:asset "ETH" :amount "0.01"} cofx)
nil)))
(testing "Yielding control prefills wallet"
(let [fx (protocol/yield-control personal-send-command {:content {:params {:asset "ETH" :amount "0.01"}}} cofx)]
(is (= (get-in fx [:db :wallet :send-transaction :amount-text]) "0.01"))
(is (= (get-in fx [:db :wallet :send-transaction :symbol]) :ETH)))))
(deftest from-contacts
(testing "the user is in our contacts"
(let [fx (protocol/yield-control personal-send-command {:content {:params {:asset "ETH" :amount "0.01"}}} cofx)]
(is (= (get-in fx [:db :wallet :send-transaction :to]) address))
(is (= (get-in fx [:db :wallet :send-transaction :to-name] "Recipient")))
(is (= (get-in fx [:db :wallet :send-transaction :public-key]) public-key)))
(testing "the user is not in our contacts"
(let [fx (protocol/yield-control personal-send-command {:content {:params {:asset "ETH" :amount "0.01"}}} (update-in cofx [:db :contacts/contacts] dissoc public-key))]
(is (= (get-in fx [:db :wallet :send-transaction :to]) address))
(is (= (get-in fx [:db :wallet :send-transaction :to-name]) "Plump Nippy Blobfish"))
(is (= (get-in fx [:db :wallet :send-transaction :public-key]) public-key))))))
nil))))
;; testing the `/request` command

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

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"}}}
:wallet {:balance {:STT (money/bignumber "1000000000000000000")}}}})
(deftest pay-tribute
(testing "transaction with enough funds"
(let [results (tribute-to-talk/pay-tribute
user-cofx
recipient-pk)]
(is (= (dissoc (get-in results [:db :wallet :send-transaction]) :amount)
{:on-result
[:tribute-to-talk.callback/pay-tribute-transaction-sent
"0x04263d74e55775280e75b4a4e9a45ba59fc372793a869c5d9c4fa2100556d9963e3f4fbfa1724ec94a46e6da057540ab248ed1f5eb956e36e3129ecd50fade2c97"],
:to-name "bob",
:amount-text "1",
:method "eth_sendTransaction",
:symbol :ETH,
:send-transaction-message? true,
:from-chat? true,
:from "0x954d4393515747ea75808a0301fb73317ae1e460",
:id "approve",
:sufficient-funds? true,
:on-error nil,
:public-key
"0x04263d74e55775280e75b4a4e9a45ba59fc372793a869c5d9c4fa2100556d9963e3f4fbfa1724ec94a46e6da057540ab248ed1f5eb956e36e3129ecd50fade2c97",
:asset :STT,
:to "0xc55cf4b03948d7ebc8b9e8bad92643703811d162",
:data
"0xa9059cbb000000000000000000000000dff1a5e4e57d9723b3294e0f4413372e3ea9a8ff0000000000000000000000000000000000000000000000000de0b6b3a7640000"}))))
(testing "insufficient funds"
(is (= (get-in (tribute-to-talk/pay-tribute
(assoc-in user-cofx [:db :wallet] nil)
recipient-pk)
[:db :wallet :send-transaction :sufficient-funds?])
false))))
(deftest tribute-transaction-trigger
(testing "transaction error"
(is (tribute-to-talk/tribute-transaction-trigger

View File

@ -1064,5 +1064,13 @@
"default" : "Default",
"token-details" : "Token details",
"remove-token" : "Remove token",
"report-bug-email-template": "1. Issue Description\n(Describe the feature you would like, or briefly summarise the bug and what you did, what you expected to happen, and what actually happens. Sections below)\n\n\n2. Steps to reproduce\n(Describe how we can replicate the bug step by step.)\n-Open Status\n-...\n-Step 3, etc.\n\n\n3. Expected behavior\n(Describe what you expected to happen.)\n\n\n4. Actual behavior\n(Describe what actually happened.)\n\n\n5. Attach screenshots that can demo the problem, please\n"
"report-bug-email-template": "1. Issue Description\n(Describe the feature you would like, or briefly summarise the bug and what you did, what you expected to happen, and what actually happens. Sections below)\n\n\n2. Steps to reproduce\n(Describe how we can replicate the bug step by step.)\n-Open Status\n-...\n-Step 3, etc.\n\n\n3. Expected behavior\n(Describe what you expected to happen.)\n\n\n4. Actual behavior\n(Describe what actually happened.)\n\n\n5. Attach screenshots that can demo the problem, please\n",
"update" : "Update",
"sending" : "Sending",
"authorize" : "Authorize",
"contract-interaction" : "Contract interaction",
"signing-phrase" : "Signing phrase",
"network-fee" : "Network fee",
"sign-with-password" : "Sign with password",
"signing-a-message" : "Signing a message"
}