feature #3233 #4095 and #3234 - send and request tokens in chat and wallet

This commit is contained in:
Goran Jovic 2018-06-15 17:20:50 +02:00 committed by Roman Volosovskyi
parent caba84e439
commit 32bbf4e533
No known key found for this signature in database
GPG Key ID: 0238A4B5ECEE70DE
22 changed files with 300 additions and 118 deletions

View File

@ -1,30 +1,17 @@
// Send command/response // Send command/response
var assetSendParam = {
function amountParameterBox(params, context) { name: "asset",
type: status.types.TEXT,
suggestions: function (params) {
return { return {
title: I18n.t('send_title'), markup: status.components.chooseAsset("asset", 0)
showBack: true,
markup: status.components.view({
flex: 1
}, [
status.components.text({
style: {
fontSize: 14,
color: "rgb(147, 155, 161)",
paddingTop: 12,
paddingLeft: 16,
paddingRight: 16,
paddingBottom: 20
}
},
I18n.t('send_specify_amount')
)
])
}; };
} },
placeholder: I18n.t('currency_placeholder')
};
var recipientSendParam = { var recipientSendParam = {
name: "recipient", name: "recipient",
@ -41,15 +28,26 @@ function amountSendParam() {
return { return {
name: "amount", name: "amount",
type: status.types.NUMBER, type: status.types.NUMBER,
suggestions: amountParameterBox.bind(this) placeholder: I18n.t('amount_placeholder')
}; };
} }
var paramsPersonalSend = [amountSendParam()]; var paramsPersonalSend = [assetSendParam, amountSendParam()];
var paramsGroupSend = [recipientSendParam, amountSendParam()]; var paramsGroupSend = [recipientSendParam, amountSendParam()];
function validateSend(validateRecipient, params, context) { function validateSend(validateRecipient, params, context) {
var allowedAssets = context["allowed-assets"];
var asset = params["asset"];
if(!allowedAssets.hasOwnProperty(asset)){
return {
markup: status.components.validationMessage(
"Invalid asset",
"Unknown token - " + asset
)
};
}
if (!params["amount"]) { if (!params["amount"]) {
return { return {
@ -62,11 +60,12 @@ function validateSend(validateRecipient, params, context) {
var amount = params["amount"].replace(",", "."); var amount = params["amount"].replace(",", ".");
var amountSplitted = amount.split("."); var amountSplitted = amount.split(".");
if (amountSplitted.length === 2 && amountSplitted[1].length > 18) { var decimals = allowedAssets[asset];
if (amountSplitted.length === 2 && amountSplitted[1].length > decimals) {
return { return {
markup: status.components.validationMessage( markup: status.components.validationMessage(
I18n.t('validation_title'), I18n.t('validation_title'),
I18n.t('validation_amount_is_too_small') I18n.t('validation_amount_is_too_small') + decimals
) )
}; };
} }
@ -135,6 +134,17 @@ status.response(groupSend);
// Request command // Request command
var assetRequestParam = {
name: "asset",
type: status.types.TEXT,
suggestions: function (params) {
return {
markup: status.components.chooseAsset("asset", 0)
};
},
placeholder: I18n.t('currency_placeholder')
};
var recipientRequestParam = { var recipientRequestParam = {
name: "recipient", name: "recipient",
type: status.types.TEXT, type: status.types.TEXT,
@ -148,15 +158,17 @@ var recipientRequestParam = {
var amountRequestParam = { var amountRequestParam = {
name: "amount", name: "amount",
type: status.types.NUMBER type: status.types.NUMBER,
placeholder: I18n.t('amount_placeholder')
}; };
var paramsPersonalRequest = [amountRequestParam]; var paramsPersonalRequest = [assetRequestParam, amountRequestParam];
var paramsGroupRequest = [recipientRequestParam, amountRequestParam]; var paramsGroupRequest = [recipientRequestParam, amountRequestParam];
function handlePersonalRequest(params, context) { function handlePersonalRequest(params, context) {
var val = params["amount"].replace(",", "."); var val = params["amount"].replace(",", ".");
var network = context["network"]; var network = context["network"];
var asset = params["asset"];
return { return {
event: "request", event: "request",
@ -165,8 +177,9 @@ function handlePersonalRequest(params, context) {
params: { params: {
network: network, network: network,
amount: val, amount: val,
asset: asset
}, },
prefill: [val] prefill: [asset, val]
} }
}; };
} }
@ -194,7 +207,7 @@ function handleGroupRequest(params, context) {
}; };
} }
function validateRequest(validateRecipient, params) { function validateRequest(validateRecipient, params, context) {
if (!params["bot-db"]) { if (!params["bot-db"]) {
params["bot-db"] = {}; params["bot-db"] = {};
} }
@ -210,6 +223,18 @@ function validateRequest(validateRecipient, params) {
} }
} }
var allowedAssets = context["allowed-assets"];
var asset = params["asset"];
if(!allowedAssets.hasOwnProperty(asset)){
return {
markup: status.components.validationMessage(
"Invalid asset",
"Unknown token - " + asset
)
};
}
if (!params["amount"]) { if (!params["amount"]) {
return { return {
markup: status.components.validationMessage( markup: status.components.validationMessage(
@ -221,11 +246,12 @@ function validateRequest(validateRecipient, params) {
var amount = params.amount.replace(",", "."); var amount = params.amount.replace(",", ".");
var amountSplitted = amount.split("."); var amountSplitted = amount.split(".");
if (amountSplitted.length === 2 && amountSplitted[1].length > 18) { var decimals = allowedAssets[asset];
if (amountSplitted.length === 2 && amountSplitted[1].length > decimals) {
return { return {
markup: status.components.validationMessage( markup: status.components.validationMessage(
I18n.t('validation_title'), I18n.t('validation_title'),
I18n.t('validation_amount_is_too_small') I18n.t('validation_amount_is_too_small') + decimals
) )
}; };
} }

View File

@ -6,6 +6,9 @@ I18n.translations = {
send_specify_amount: 'Specify amount', send_specify_amount: 'Specify amount',
send_sending_to: 'to ', send_sending_to: 'to ',
currency_placeholder: 'Currency',
amount_placeholder: 'Amount',
eth: 'ETH', eth: 'ETH',
request_title: 'Request ETH', request_title: 'Request ETH',
@ -20,7 +23,7 @@ I18n.translations = {
validation_tx_failed: 'Transaction failed', validation_tx_failed: 'Transaction failed',
validation_amount_specified: 'Amount must be specified', validation_amount_specified: 'Amount must be specified',
validation_invalid_number: 'Amount is not valid number', validation_invalid_number: 'Amount is not valid number',
validation_amount_is_too_small: 'Amount is too precise. Max number of decimals is 18.', validation_amount_is_too_small: 'Max number of decimals is ',
validation_insufficient_amount: 'Insufficient funds for gas * price + value (balance ' validation_insufficient_amount: 'Insufficient funds for gas * price + value (balance '
} }

View File

@ -215,6 +215,14 @@ function chooseContact(titleText, botDbKey, argumentIndex) {
}]; }];
} }
function chooseAsset(botDbKey, argumentIndex) {
return ['choose-asset', {
"bot-db-key": botDbKey,
index: argumentIndex
}];
}
function separator() { function separator() {
return ['separator']; return ['separator'];
} }
@ -276,6 +284,7 @@ var status = {
webView: webView, webView: webView,
validationMessage: validationMessage, validationMessage: validationMessage,
bridgedWebView: bridgedWebView, bridgedWebView: bridgedWebView,
chooseAsset: chooseAsset,
chooseContact: chooseContact, chooseContact: chooseContact,
subscribe: subscribe, subscribe: subscribe,
dispatch: dispatch, dispatch: dispatch,

View File

@ -6,24 +6,39 @@
[status-im.utils.handlers :as handlers] [status-im.utils.handlers :as handlers]
[status-im.i18n :as i18n] [status-im.i18n :as i18n]
[status-im.utils.platform :as platform] [status-im.utils.platform :as platform]
[status-im.chat.events.shortcuts :as shortcuts])) [status-im.chat.events.shortcuts :as shortcuts]
[status-im.utils.ethereum.tokens :as tokens]))
;;;; Helper fns ;;;; Helper fns
;;TODO(goranjovic): currently we only allow tokens which are enabled in Manage assets here
;; because balances are only fetched for them. Revisit this decision with regard to battery/network consequences
;; if we were to update all balances.
(defn- allowed-assets [network account]
(let [chain (keyword (ethereum/network-names network))
visible-token-symbols (get-in account [:settings :wallet :visible-tokens chain])]
(->> (tokens/tokens-for chain)
(filter #(not (:nft? %)))
(filter #(contains? visible-token-symbols (:symbol %)))
(map #(vector (-> % :symbol clojure.core/name)
(:decimals %)))
(into {"ETH" 18}))))
(defn- generate-context (defn- generate-context
"Generates context for jail call" "Generates context for jail call"
[current-account-id chat-id group-chat? to network] [account current-account-id chat-id group-chat? to network]
(merge {:platform platform/os (merge {:platform platform/os
:network (ethereum/network-names network) :network (ethereum/network-names network)
:from current-account-id :from current-account-id
:to to :to to
:allowed-assets (clj->js (allowed-assets network account))
:chat {:chat-id chat-id :chat {:chat-id chat-id
:group-chat (boolean group-chat?)}} :group-chat (boolean group-chat?)}}
i18n/delimeters)) i18n/delimeters))
(defn request-command-message-data (defn request-command-message-data
"Requests command message data from jail" "Requests command message data from jail"
[{:contacts/keys [contacts] :keys [network] :as db} [{:contacts/keys [contacts] :account/keys [account] :keys [network] :as db}
{{:keys [command command-scope-bitmask bot params type]} :content {{:keys [command command-scope-bitmask bot params type]} :content
:keys [chat-id group-id] :as message} :keys [chat-id group-id] :as message}
{:keys [data-type] :as opts}] {:keys [data-type] :as opts}]
@ -36,7 +51,7 @@
to (get-in contacts [chat-id :address]) to (get-in contacts [chat-id :address])
address (get-in db [:account/account :address]) address (get-in db [:account/account :address])
jail-params {:parameters params jail-params {:parameters params
:context (generate-context address chat-id (models.message/group-message? message) to network)}] :context (generate-context account address chat-id (models.message/group-message? message) to network)}]
{:db db {:db db
:call-jail [{:jail-id bot :call-jail [{:jail-id bot
:path path :path path

View File

@ -208,6 +208,7 @@
(not prevent-auto-focus?) (not prevent-auto-focus?)
(merge fx' (chat-input-focus (:db fx') :seq-input-ref))))))) (merge fx' (chat-input-focus (:db fx') :seq-input-ref)))))))
;; TODO(goranjovic) - generalize setting something as a command argument
(defn set-contact-as-command-argument (defn set-contact-as-command-argument
"Sets contact as command argument for active chat" "Sets contact as command argument for active chat"
[db {:keys [bot-db-key contact arg-index]}] [db {:keys [bot-db-key contact arg-index]}]
@ -233,6 +234,31 @@
(min (count input-text)))] (min (count input-text)))]
(merge fx (update-text-selection new-db new-selection))))))) (merge fx (update-text-selection new-db new-selection)))))))
;; TODO(goranjovic) - generalize setting something as a command argument
(defn set-asset-as-command-argument
"Sets asset as command argument for active chat"
[db {:keys [bot-db-key asset arg-index]}]
(let [name (string/replace (name (:symbol asset)) (re-pattern constants/arg-wrapping-char) "")
command-owner (get-in (input-model/selected-chat-command db) [:command :owner-id])]
(-> db
(set-command-argument arg-index name true)
(bots-events/set-in-bot-db {:bot command-owner
:path [:public (keyword bot-db-key)]
:value asset})
(as-> fx
(let [{:keys [current-chat-id]
:as new-db} (:db fx)
arg-position (input-model/argument-position new-db)
input-text (get-in new-db [:chats current-chat-id :input-text])
command-args (cond-> (input-model/split-command-args input-text)
(input-model/text-ends-with-space? input-text) (conj ""))
new-selection (->> command-args
(take (+ 3 arg-position))
(input-model/join-command-args)
count
(min (count input-text)))]
(merge fx (update-text-selection new-db new-selection)))))))
;; function creating "message shaped" data from command, because that's what `request-command-message-data` expects ;; function creating "message shaped" data from command, because that's what `request-command-message-data` expects
(defn- command->message (defn- command->message
[{:keys [bot-db current-chat-id chats]} {:keys [command] :as command-params}] [{:keys [bot-db current-chat-id chats]} {:keys [command] :as command-params}]
@ -468,6 +494,12 @@
(fn [{:keys [db]} [params]] (fn [{:keys [db]} [params]]
(set-contact-as-command-argument db params))) (set-contact-as-command-argument db params)))
(handlers/register-handler-fx
:set-asset-as-command-argument
[re-frame/trim-v]
(fn [{:keys [db]} [params]]
(set-asset-as-command-argument db params)))
(handlers/register-handler-db (handlers/register-handler-db
:show-suggestions :show-suggestions
(fn [db _] (fn [db _]

View File

@ -2,26 +2,31 @@
(:require [status-im.ui.screens.wallet.send.events :as send.events] (:require [status-im.ui.screens.wallet.send.events :as send.events]
[status-im.ui.screens.wallet.choose-recipient.events :as choose-recipient.events] [status-im.ui.screens.wallet.choose-recipient.events :as choose-recipient.events]
[status-im.ui.screens.navigation :as navigation] [status-im.ui.screens.navigation :as navigation]
[status-im.utils.ethereum.core :as ethereum])) [status-im.utils.ethereum.core :as ethereum]
[status-im.utils.ethereum.tokens :as tokens]
[taoensso.timbre :as log]))
;; TODO(goranjovic) - update to include tokens in https://github.com/status-im/status-react/issues/3233 ;; TODO(goranjovic) - update to include tokens in https://github.com/status-im/status-react/issues/3233
(defn- transaction-details [contact] (defn- transaction-details [contact symbol]
(-> contact (-> contact
(select-keys [:name :address :whisper-identity]) (select-keys [:name :address :whisper-identity])
(assoc :symbol :ETH (assoc :symbol symbol
:gas (ethereum/estimate-gas :ETH) :gas (ethereum/estimate-gas symbol)
:from-chat? true))) :from-chat? true)))
(defn send-shortcut-fx [{:account/keys [account] :as db} contact params] (defn send-shortcut-fx [{:account/keys [account] :as db} contact params]
(let [chain (keyword (ethereum/network-names (:network db)))
symbol (-> params :asset keyword)
{:keys [decimals]} (tokens/asset-for chain symbol)]
(merge {:db (-> db (merge {:db (-> db
(send.events/set-and-validate-amount-db (:amount params) :ETH 18) (send.events/set-and-validate-amount-db (:amount params) symbol decimals)
(choose-recipient.events/fill-request-details (transaction-details contact)) (choose-recipient.events/fill-request-details (transaction-details contact symbol))
(update-in [:wallet :send-transaction] dissoc :id :password :wrong-password?) (update-in [:wallet :send-transaction] dissoc :id :password :wrong-password?)
(navigation/navigate-to (navigation/navigate-to
(if (:wallet-set-up-passed? account) (if (:wallet-set-up-passed? account)
:wallet-send-transaction-chat :wallet-send-transaction-chat
:wallet-onboarding-setup)))} :wallet-onboarding-setup)))}
(send.events/update-gas-price db false))) (send.events/update-gas-price db false))))
(def shortcuts (def shortcuts
{"send" send-shortcut-fx}) {"send" send-shortcut-fx})

View File

@ -378,7 +378,7 @@
{:params (cond-> params {:params (cond-> params
(= (:name command) constants/command-send) (= (:name command) constants/command-send)
(assoc :network (ethereum/network-names network) (assoc :network (ethereum/network-names network)
:fiat-amount (money/usd-amount (:amount params) prices) :fiat-amount (money/usd-amount (:amount params) (-> params :asset keyword) prices)
:tx-hash tx-hash))}) :tx-hash tx-hash))})
content' (assoc content content' (assoc content
:command (:name command) :command (:name command)

View File

@ -75,7 +75,7 @@
:text-align-vertical :center :text-align-vertical :center
:flex 1 :flex 1
:android {:top -1} :android {:top -1}
:ios {:line-height min-input-height}}) :ios {:line-height 43}})
(defnstyle seq-input-text [left container-width] (defnstyle seq-input-text [left container-width]
{:min-width (- container-width left) {:min-width (- container-width left)

View File

@ -288,15 +288,13 @@
(fn [[{:keys [input-text]} command]] (fn [[{:keys [input-text]} command]]
(when (and (string/ends-with? (or input-text "") chat-constants/spacing-char) (when (and (string/ends-with? (or input-text "") chat-constants/spacing-char)
(not (get-in command [:command :sequential-params]))) (not (get-in command [:command :sequential-params])))
(let [input (string/trim (or input-text "")) (let [real-args (remove string/blank? (:args command))]
real-args (remove string/blank? (:args command))]
(cond (cond
(and command (empty? real-args)) (and command (empty? real-args))
(get-in command [:command :params 0 :placeholder]) (get-in command [:command :params 0 :placeholder])
(and command (and command
(= (count real-args) 1) (= (count real-args) 1))
(input-model/text-ends-with-space? input))
(get-in command [:command :params 1 :placeholder])))))) (get-in command [:command :params 1 :placeholder]))))))
(reg-sub (reg-sub

View File

@ -0,0 +1,39 @@
(ns status-im.chat.views.api.choose-asset
(:require-macros [status-im.utils.views :refer [defview letsubs]])
(:require [re-frame.core :as re-frame]
[status-im.ui.components.list.views :as list]
[status-im.ui.components.react :as react]
[status-im.utils.money :as money]
[status-im.chat.views.api.styles :as styles]))
(defn clean-asset [asset]
(select-keys asset [:name :symbol :decimals :address]))
(defn- render-asset [arg-index bot-db-key]
(fn [{:keys [name symbol amount decimals] :as asset}]
[react/touchable-highlight {:on-press #(re-frame/dispatch
[:set-asset-as-command-argument {:arg-index arg-index
:bot-db-key bot-db-key
:asset (clean-asset asset)}])}
[react/view styles/asset-container
[react/view styles/asset-main
[react/image {:source (-> asset :icon :source)
:style styles/asset-icon}]
[react/text {:style styles/asset-symbol} symbol]
[react/text {:style styles/asset-name} name]]
[react/text {:style styles/asset-balance}
(str (money/internal->formatted amount symbol decimals))]]]))
(def assets-separator [react/view styles/asset-separator])
(defview choose-asset-view [{arg-index :index
bot-db-key :bot-db-key}]
(letsubs [assets [:wallet/visible-assets-with-amount]]
[react/view
[list/flat-list {:data (filter #(not (:nft? %)) assets)
:key-fn (comp name :symbol)
:render-fn (render-asset arg-index bot-db-key)
:enableEmptySections true
:separator assets-separator
:keyboardShouldPersistTaps :always
:bounces false}]]))

View File

@ -0,0 +1,35 @@
(ns status-im.chat.views.api.styles
(:require [status-im.ui.components.colors :as colors]))
(def asset-container
{:flex-direction :row
:align-items :center
:justify-content :space-between
:padding-vertical 11})
(def asset-main
{:flex 1
:flex-direction :row
:align-items :center})
(def asset-icon
{:width 30
:height 30
:margin-left 14
:margin-right 12})
(def asset-symbol
{:color colors/black})
(def asset-name
{:color colors/gray
:padding-left 4})
(def asset-balance
{:color colors/gray
:padding-right 14})
(def asset-separator
{:height 1
:background-color colors/gray-light
:margin-left 56})

View File

@ -77,7 +77,7 @@
(defview message-content-command-send (defview message-content-command-send
[{:keys [content timestamp-str outgoing group-chat]}] [{:keys [content timestamp-str outgoing group-chat]}]
(letsubs [network [:network-name]] (letsubs [network [:network-name]]
(let [{{:keys [amount fiat-amount tx-hash] send-network :network} :params} content (let [{{:keys [amount fiat-amount tx-hash asset] send-network :network} :params} content
recipient-name (get-in content [:params :bot-db :public :recipient]) recipient-name (get-in content [:params :bot-db :public :recipient])
network-mismatch? (and (seq send-network) (not= network send-network))] network-mismatch? (and (seq send-network) (not= network send-network))]
[react/view style/command-send-message-view [react/view style/command-send-message-view
@ -91,7 +91,7 @@
"."] "."]
[react/text {:style (style/command-send-currency-text outgoing) [react/text {:style (style/command-send-currency-text outgoing)
:font :default} :font :default}
(i18n/label :eth)]]]] asset]]]]
(when fiat-amount (when fiat-amount
[react/view style/command-send-fiat-amount [react/view style/command-send-fiat-amount
[react/text {:style style/command-send-fiat-amount-text} [react/text {:style style/command-send-fiat-amount-text}

View File

@ -89,9 +89,9 @@
(merge command {:prefill prefill (merge command {:prefill prefill
:prefill-bot-db (or prefill-bot-db prefillBotDb)}) :prefill-bot-db (or prefill-bot-db prefillBotDb)})
command) command)
{:keys [amount] request-network :network} params {:keys [amount asset] request-network :network} params
recipient-name (get-in params [:bot-db :public :recipient]) recipient-name (get-in params [:bot-db :public :recipient])
usd-amount (money/usd-amount amount prices) usd-amount (money/usd-amount amount (keyword asset) prices)
network-mismatch? (and request-network (not= request-network network)) network-mismatch? (and request-network (not= request-network network))
on-press-handler (cond on-press-handler (cond
network-mismatch? nil network-mismatch? nil
@ -117,7 +117,7 @@
"."] "."]
[text {:style (st/command-request-currency-text outgoing) [text {:style (st/command-request-currency-text outgoing)
:font :default} :font :default}
(i18n/label :eth)]]] asset]]]
[view st/command-request-fiat-amount-row [view st/command-request-fiat-amount-row
[text {:style st/command-request-fiat-amount-text} [text {:style st/command-request-fiat-amount-text}
(str "~ " usd-amount " " (i18n/label :usd-currency))]] (str "~ " usd-amount " " (i18n/label :usd-currency))]]

View File

@ -5,6 +5,7 @@
[status-im.ui.components.react :as components] [status-im.ui.components.react :as components]
[status-im.chat.views.input.validation-messages :as chat-validation-messages] [status-im.chat.views.input.validation-messages :as chat-validation-messages]
[status-im.chat.views.api.choose-contact :as choose-contact] [status-im.chat.views.api.choose-contact :as choose-contact]
[status-im.chat.views.api.choose-asset :as choose-asset]
[status-im.ui.components.qr-code-viewer.views :as qr-code-viewer] [status-im.ui.components.qr-code-viewer.views :as qr-code-viewer]
[status-im.ui.components.chat-preview :as chat-preview] [status-im.ui.components.chat-preview :as chat-preview]
[status-im.utils.handlers :refer [register-handler]] [status-im.utils.handlers :refer [register-handler]]
@ -32,6 +33,7 @@
:activity-indicator components/activity-indicator :activity-indicator components/activity-indicator
:validation-message chat-validation-messages/validation-message :validation-message chat-validation-messages/validation-message
:choose-contact choose-contact/choose-contact-view :choose-contact choose-contact/choose-contact-view
:choose-asset choose-asset/choose-asset-view
:separator parameter-box-separator}) :separator parameter-box-separator})
(defn get-element [n] (defn get-element [n]

View File

@ -19,7 +19,7 @@
[status-im.ui.components.common.common :as components.common])) [status-im.ui.components.common.common :as components.common]))
(defn command-short-preview (defn command-short-preview
[{:keys [command] {:keys [amount]} :params}] [{:keys [command] {:keys [amount asset]} :params}]
[chat-preview/text {} [chat-preview/text {}
(str (str
(i18n/label (if (= command constants/command-request) (i18n/label (if (= command constants/command-request)
@ -27,7 +27,7 @@
:command-sending)) :command-sending))
(i18n/label-number amount) (i18n/label-number amount)
" " " "
(i18n/label :eth))]) asset)])
(defn message-content-text [{:keys [content] :as message}] (defn message-content-text [{:keys [content] :as message}]
[react/view styles/last-message-container [react/view styles/last-message-container

View File

@ -19,12 +19,15 @@
{:gas (ethereum/estimate-gas symbol) {:gas (ethereum/estimate-gas symbol)
:symbol symbol})) :symbol symbol}))
(def transaction-request-default
{:symbol :ETH})
(defmethod navigation/preload-data! :wallet-request-transaction (defmethod navigation/preload-data! :wallet-request-transaction
[db [event]] [db [event]]
(if (= event :navigate-back) (if (= event :navigate-back)
db db
(-> db (-> db
(update :wallet dissoc :request-transaction) (assoc-in [:wallet :request-transaction] transaction-request-default)
(assoc-in [:wallet :send-transaction] transaction-send-default)))) (assoc-in [:wallet :send-transaction] transaction-send-default))))
(defmethod navigation/preload-data! :wallet-send-transaction (defmethod navigation/preload-data! :wallet-send-transaction

View File

@ -10,21 +10,21 @@
(handlers/register-handler-fx (handlers/register-handler-fx
::wallet-send-chat-request ::wallet-send-chat-request
[re-frame/trim-v] [re-frame/trim-v]
(fn [{{:contacts/keys [contacts]} :db :as cofx} [amount]] (fn [{{:contacts/keys [contacts]} :db :as cofx} [asset amount]]
(handlers-macro/merge-fx cofx (handlers-macro/merge-fx cofx
{:dispatch [:send-current-message]} {:dispatch [:send-current-message]}
(input-events/select-chat-input-command (input-events/select-chat-input-command
(assoc (get-in contacts chat-const/request-command-ref) :prefill [amount]) nil true)))) (assoc (get-in contacts chat-const/request-command-ref) :prefill [asset amount]) nil true))))
(handlers/register-handler-fx (handlers/register-handler-fx
:wallet-send-request :wallet-send-request
[re-frame/trim-v] [re-frame/trim-v]
(fn [_ [whisper-identity amount]] (fn [_ [whisper-identity amount symbol decimals]]
(assert whisper-identity) (assert whisper-identity)
{:dispatch-n [[:navigate-back] {:dispatch-n [[:navigate-back]
[:navigate-to-clean :home] [:navigate-to-clean :home]
[:add-chat-loaded-event whisper-identity [:add-chat-loaded-event whisper-identity
[::wallet-send-chat-request (str (money/wei->ether amount))]] [::wallet-send-chat-request (name symbol) (str (money/internal->formatted amount symbol decimals))]]
[:start-chat whisper-identity]]})) [:start-chat whisper-identity]]}))
(handlers/register-handler-fx (handlers/register-handler-fx
@ -34,9 +34,15 @@
(handlers/register-handler-fx (handlers/register-handler-fx
:wallet.request/set-and-validate-amount :wallet.request/set-and-validate-amount
(fn [{:keys [db]} [_ amount]] (fn [{:keys [db]} [_ amount symbol decimals]]
(let [{:keys [value error]} (wallet-db/parse-amount amount :ETH)] (let [{:keys [value error]} (wallet-db/parse-amount amount symbol)]
{:db (-> db {:db (-> db
(assoc-in [:wallet :request-transaction :amount] (money/ether->wei value)) (assoc-in [:wallet :request-transaction :amount] (money/formatted->internal value symbol decimals))
(assoc-in [:wallet :request-transaction :amount-text] amount) (assoc-in [:wallet :request-transaction :amount-text] amount)
(assoc-in [:wallet :request-transaction :amount-error] error))}))) (assoc-in [:wallet :request-transaction :amount-error] error))})))
(handlers/register-handler-fx
:wallet.request/set-symbol
(fn [{:keys [db]} [_ symbol]]
{:db (-> db
(assoc-in [:wallet :request-transaction :symbol] symbol))}))

View File

@ -19,15 +19,18 @@
[status-im.ui.screens.wallet.components.views :as components] [status-im.ui.screens.wallet.components.views :as components]
[status-im.utils.ethereum.core :as ethereum] [status-im.utils.ethereum.core :as ethereum]
[status-im.utils.ethereum.eip681 :as eip681] [status-im.utils.ethereum.eip681 :as eip681]
[status-im.utils.utils :as utils])) [status-im.utils.utils :as utils]
[status-im.utils.ethereum.tokens :as tokens]))
;; Request screen ;; Request screen
(views/defview send-transaction-request [] (views/defview send-transaction-request []
;; TODO(jeluard) both send and request flows should be merged ;; TODO(jeluard) both send and request flows should be merged
(views/letsubs [{:keys [to to-name whisper-identity]} [:wallet.send/transaction] (views/letsubs [network [:get-current-account-network]
{:keys [amount amount-error amount-text]} [:wallet.request/transaction] {:keys [to to-name whisper-identity]} [:wallet.send/transaction]
{:keys [amount amount-error amount-text symbol]} [:wallet.request/transaction]
scroll (atom nil)] scroll (atom nil)]
(let [{:keys [decimals] :as token} (tokens/asset-for (ethereum/network->chain-keyword network) symbol)]
[comp/simple-screen {:avoid-keyboard? true} [comp/simple-screen {:avoid-keyboard? true}
[comp/toolbar (i18n/label :t/new-request)] [comp/toolbar (i18n/label :t/new-request)]
[react/view components.styles/flex [react/view components.styles/flex
@ -38,24 +41,24 @@
:address to :address to
:name to-name :name to-name
:request? true}] :request? true}]
[components/asset-selector {:disabled? true [components/asset-selector {:disabled? false
:symbol :ETH}] :type :request
:symbol symbol}]
[components/amount-selector {:error amount-error [components/amount-selector {:error amount-error
:amount amount :amount amount
:amount-text amount-text :amount-text amount-text
:input-options {:max-length 21 :input-options {:max-length 21
:on-focus (fn [] (when @scroll (utils/set-timeout #(.scrollToEnd @scroll) 100))) :on-focus (fn [] (when @scroll (utils/set-timeout #(.scrollToEnd @scroll) 100)))
:on-change-text #(re-frame/dispatch [:wallet.request/set-and-validate-amount %])}} :on-change-text #(re-frame/dispatch [:wallet.request/set-and-validate-amount % symbol decimals])}}
{:decimals 18 token]]]
:symbol :ETH}]]]
[bottom-buttons/bottom-buttons styles/bottom-buttons [bottom-buttons/bottom-buttons styles/bottom-buttons
nil ;; Force a phantom button to ensure consistency with other transaction screens which define 2 buttons nil ;; Force a phantom button to ensure consistency with other transaction screens which define 2 buttons
[button/button {:disabled? (not (and to amount)) [button/button {:disabled? (not (and to amount))
:on-press #(re-frame/dispatch [:wallet-send-request whisper-identity amount]) :on-press #(re-frame/dispatch [:wallet-send-request whisper-identity amount symbol decimals])
:text-style {:padding-horizontal 0} :text-style {:padding-horizontal 0}
:accessibility-label :sent-request-button} :accessibility-label :sent-request-button}
(i18n/label :t/send-request) (i18n/label :t/send-request)
[vector-icons/icon :icons/forward {:color :white}]]]]])) [vector-icons/icon :icons/forward {:color :white}]]]]])))
;; Main screen ;; Main screen

View File

@ -7,12 +7,7 @@
(re-frame/reg-sub ::send-transaction (re-frame/reg-sub ::send-transaction
:<- [:wallet] :<- [:wallet]
(fn [wallet] (fn [wallet]
(let [transaction (:send-transaction wallet)] (:send-transaction wallet)))
;NOTE(goranjovic): the transactions started from chat using /send command
; are only in ether, so this parameter defaults to ETH
(if (:symbol transaction)
transaction
(assoc transaction :symbol :ETH)))))
(re-frame/reg-sub :wallet.send/symbol (re-frame/reg-sub :wallet.send/symbol
:<- [::send-transaction] :<- [::send-transaction]

View File

@ -139,10 +139,10 @@
(when (and amount balance) (when (and amount balance)
(.greaterThanOrEqualTo balance amount))) (.greaterThanOrEqualTo balance amount)))
(defn usd-amount [amount-str prices] (defn usd-amount [amount-str from prices]
(-> amount-str (-> amount-str
(js/parseFloat) (js/parseFloat)
bignumber bignumber
(crypto->fiat (get-in prices [:ETH :USD :price])) (crypto->fiat (get-in prices [from :USD :price] (bignumber 0)))
(with-precision 2) (with-precision 2)
str)) str))

View File

@ -50,6 +50,7 @@ class TestTransaction(SingleDeviceTestCase):
chat_view = home_view.get_chat_with_user(recipient['username']).click() chat_view = home_view.get_chat_with_user(recipient['username']).click()
chat_view.commands_button.click() chat_view.commands_button.click()
chat_view.send_command.click() chat_view.send_command.click()
chat_view.eth_asset.click()
chat_view.send_as_keyevent(transaction_amount) chat_view.send_as_keyevent(transaction_amount)
wallet_view = chat_view.get_wallet_view() wallet_view = chat_view.get_wallet_view()
chat_view.send_message_button.click_until_presence_of_element(wallet_view.sign_in_phrase) chat_view.send_message_button.click_until_presence_of_element(wallet_view.sign_in_phrase)
@ -253,6 +254,7 @@ class TestTransactions(MultipleDeviceTestCase):
one_to_one_chat_device_2.click_until_presence_of_element(device_2_chat.commands_button) one_to_one_chat_device_2.click_until_presence_of_element(device_2_chat.commands_button)
device_1_chat.commands_button.click_until_presence_of_element(device_1_chat.request_command) device_1_chat.commands_button.click_until_presence_of_element(device_1_chat.request_command)
device_1_chat.request_command.click() device_1_chat.request_command.click()
device_1_chat.eth_asset.click()
device_1_chat.send_as_keyevent(amount) device_1_chat.send_as_keyevent(amount)
device_1_chat.send_message_button.click() device_1_chat.send_message_button.click()
device_2_chat.send_eth_to_request(amount, sender['password'], wallet_set_up=True) device_2_chat.send_eth_to_request(amount, sender['password'], wallet_set_up=True)

View File

@ -43,6 +43,12 @@ class RequestCommand(BaseButton):
self.locator = self.Locator.accessibility_id('request-payment-button') self.locator = self.Locator.accessibility_id('request-payment-button')
class EthAsset(BaseButton):
def __init__(self, driver):
super(EthAsset, self).__init__(driver)
self.locator = self.Locator.text_selector('ETH')
class FaucetCommand(BaseButton): class FaucetCommand(BaseButton):
def __init__(self, driver): def __init__(self, driver):
super(FaucetCommand, self).__init__(driver) super(FaucetCommand, self).__init__(driver)
@ -200,6 +206,7 @@ class ChatView(BaseView):
self.commands_button = CommandsButton(self.driver) self.commands_button = CommandsButton(self.driver)
self.send_command = SendCommand(self.driver) self.send_command = SendCommand(self.driver)
self.request_command = RequestCommand(self.driver) self.request_command = RequestCommand(self.driver)
self.eth_asset = EthAsset(self.driver)
self.faucet_command = FaucetCommand(self.driver) self.faucet_command = FaucetCommand(self.driver)
self.faucet_send_command = FaucetSendCommand(self.driver) self.faucet_send_command = FaucetSendCommand(self.driver)
@ -292,6 +299,7 @@ class ChatView(BaseView):
def send_transaction_in_1_1_chat(self, amount, password, wallet_set_up=False): def send_transaction_in_1_1_chat(self, amount, password, wallet_set_up=False):
self.commands_button.click() self.commands_button.click()
self.send_command.click() self.send_command.click()
self.eth_asset.click()
self.send_as_keyevent(amount) self.send_as_keyevent(amount)
send_transaction_view = self.get_send_transaction_view() send_transaction_view = self.get_send_transaction_view()
if wallet_set_up: if wallet_set_up:
@ -327,6 +335,7 @@ class ChatView(BaseView):
def request_transaction_in_1_1_chat(self, amount): def request_transaction_in_1_1_chat(self, amount):
self.commands_button.click() self.commands_button.click()
self.request_command.click() self.request_command.click()
self.eth_asset.click()
self.send_as_keyevent(amount) self.send_as_keyevent(amount)
self.send_message_button.click() self.send_message_button.click()