send command and remove old wallet
This commit is contained in:
parent
91bb69c6f7
commit
54d68081b4
|
@ -101,26 +101,6 @@
|
|||
status)
|
||||
:on-press #(rf/dispatch [:my-profile.drawer/edit-status])}])])))
|
||||
|
||||
(defview transaction-list-item [{:keys [to value timestamp] :as transaction}]
|
||||
[recipient [:contact-by-address to]]
|
||||
(let [eth-value (str (money/wei->ether value))
|
||||
value (i18n/label-number eth-value)
|
||||
recipient-name (or (:name recipient) to)]
|
||||
[touchable-highlight {:on-press #(rf/dispatch [:navigate-to-modal :transaction-details transaction])}
|
||||
[view {:style st/transaction}
|
||||
[vi/icon :icons/arrow-right {:container-style st/transaction-icon}]
|
||||
[view {:style st/transaction-info}
|
||||
[view {:style st/transaction-value-container}
|
||||
[text {:style st/transaction-value :font :medium} value]
|
||||
[text {:style st/transaction-unit} "ETH"]]
|
||||
[view {:style st/transaction-details-container}
|
||||
[text {:style st/transaction-to} (i18n/label :t/to)]
|
||||
[text {:style st/transaction-recipient :number-of-lines 1} recipient-name]
|
||||
[text {:style st/transaction-time} (time/format-date "dd MMM HH:mm" (time/to-date timestamp))]]]
|
||||
[view {:style st/transaction-picture}
|
||||
(when recipient
|
||||
[ci/chat-icon (:photo-path recipient) {:size 40}])]]]))
|
||||
|
||||
(defn render-separator-fn [transactions-count]
|
||||
(fn [_ row-id _]
|
||||
(when (< row-id (dec transactions-count))
|
||||
|
@ -128,26 +108,6 @@
|
|||
^{:key row-id}
|
||||
[common/separator {} st/transactions-list-separator]))))
|
||||
|
||||
(defview unsigned-transactions []
|
||||
[all-transactions [:transactions]]
|
||||
(let [transactions (take 2 (sort-by :timestamp > all-transactions))]
|
||||
(if (empty? transactions)
|
||||
[view {:style st/empty-transactions-title-container}
|
||||
[text {:style st/transactions-title} (i18n/label :t/no-unsigned-transactions)]]
|
||||
|
||||
[view
|
||||
[view {:style st/transactions-title-container}
|
||||
[text {:style st/transactions-title} (i18n/label :t/unsigned-transactions)]]
|
||||
[list-view {:dataSource (lw/to-datasource transactions)
|
||||
:renderSeparator (render-separator-fn (count transactions))
|
||||
:renderRow (fn [row _ _] (list-item [transaction-list-item row]))}]
|
||||
[touchable-opacity {:style st/view-all-transactions-button
|
||||
:on-press #(rf/dispatch [:navigate-to-modal :unsigned-transactions])}
|
||||
[text {:style st/view-all-transactions-text
|
||||
:font (if platform/android? :medium :default)
|
||||
:uppercase? platform/android?}
|
||||
(i18n/label :t/view-all)]]])))
|
||||
|
||||
(defview current-network []
|
||||
(letsubs [network [:get-current-account-network]]
|
||||
[view {:style st/network-label-container}
|
||||
|
@ -182,7 +142,6 @@
|
|||
[options-btn]]
|
||||
[current-network]]
|
||||
[view
|
||||
[unsigned-transactions]
|
||||
[switch-account]]]]))
|
||||
|
||||
(defn drawer-view [items]
|
||||
|
|
|
@ -70,3 +70,9 @@
|
|||
:DataDir "/ethereum/mainnet_rpc"
|
||||
:UpstreamConfig {:Enabled true
|
||||
:URL "https://mainnet.infura.io/z6GCTmjdP3FETEJmMBI4 "}}}}))
|
||||
|
||||
(def ^:const send-transaction-no-error-code "0")
|
||||
(def ^:const send-transaction-default-error-code "1")
|
||||
(def ^:const send-transaction-password-error-code "2")
|
||||
(def ^:const send-transaction-timeout-error-code "3")
|
||||
(def ^:const send-transaction-discarded-error-code "4")
|
||||
|
|
|
@ -1,233 +0,0 @@
|
|||
(ns status-im.transactions.handlers
|
||||
(:require [re-frame.core :refer [after dispatch debug enrich]]
|
||||
[status-im.utils.handlers :refer [register-handler]]
|
||||
[status-im.ui.screens.navigation :as nav]
|
||||
[status-im.utils.datetime :as time]
|
||||
[status-im.utils.handlers :as u]
|
||||
[status-im.utils.types :as t]
|
||||
[status-im.utils.hex :refer [valid-hex? normalize-hex]]
|
||||
[status-im.native-module.core :as status]
|
||||
[clojure.string :as s]
|
||||
[taoensso.timbre :as log]))
|
||||
|
||||
;; flow:
|
||||
;; :accept-transactions
|
||||
;; ↓
|
||||
;; :transaction-completed
|
||||
;; ↓
|
||||
;; ::remove-transaction && [:set :wrong-password? false] <- on error
|
||||
;; ::remove-transaction <- when transaction is
|
||||
;; not from the jail
|
||||
;; ::add-transactions-hash
|
||||
;; && ::check-completed-transaction!
|
||||
;; && :navigation-replace <- on success
|
||||
|
||||
|
||||
(defmethod nav/preload-data! :unsigned-transactions
|
||||
[{:keys [transactions-queue] :as db} _]
|
||||
(-> db
|
||||
(assoc :transactions transactions-queue
|
||||
:wrong-password-counter 0
|
||||
:wrong-password? false)
|
||||
(assoc-in [:confirm-transactions :password] "")))
|
||||
|
||||
(defmethod nav/preload-data! :transaction-details
|
||||
[db [_ _ transaction]]
|
||||
(-> db
|
||||
(assoc :selected-transaction transaction
|
||||
:wrong-password-counter 0
|
||||
:wrong-password? false)
|
||||
(assoc-in [:confirm-transactions :password] "")))
|
||||
|
||||
(defn on-transactions-completed [raw-results]
|
||||
(let [results (:results (t/json->clj raw-results))]
|
||||
(doseq [result results]
|
||||
(dispatch [:transaction-completed {:id (name (key result)) :response (second result)}]))))
|
||||
|
||||
(register-handler :accept-transactions
|
||||
(u/side-effect!
|
||||
(fn [{:keys [transactions]} [_ password]]
|
||||
(dispatch [:set :wrong-password? false])
|
||||
(status/complete-transactions (keys transactions) password on-transactions-completed))))
|
||||
|
||||
(register-handler :accept-transaction
|
||||
(u/side-effect!
|
||||
(fn [_ [_ password id]]
|
||||
(dispatch [:set :wrong-password? false])
|
||||
(status/complete-transactions (list id) password on-transactions-completed))))
|
||||
|
||||
(register-handler :deny-transactions
|
||||
(u/side-effect!
|
||||
(fn [{:keys [transactions]}]
|
||||
(let [transactions' (vals transactions)
|
||||
messages-ids (map :message-id transactions')
|
||||
ids (map :id transactions')]
|
||||
(dispatch [::remove-pending-messages messages-ids])
|
||||
(dispatch [::remove-transactions ids])
|
||||
(doseq [id ids]
|
||||
(dispatch [::discard-transaction id]))))))
|
||||
|
||||
(register-handler :deny-transaction
|
||||
(u/side-effect!
|
||||
(fn [{:keys [transactions]} [_ id]]
|
||||
(let [{:keys [message-id] :as transaction} (get transactions id)]
|
||||
(when transaction
|
||||
(dispatch [::remove-pending-message message-id])
|
||||
(dispatch [::remove-transaction id])
|
||||
(dispatch [::discard-transaction id]))))))
|
||||
|
||||
(register-handler ::discard-transaction
|
||||
(u/side-effect!
|
||||
(fn [_ [_ id]]
|
||||
(status/discard-transaction id))))
|
||||
|
||||
(register-handler ::remove-transactions
|
||||
(fn [db [_ hashes]]
|
||||
(-> db
|
||||
(dissoc :transactions)
|
||||
(update :transactions-queue #(apply dissoc % hashes)))))
|
||||
|
||||
(register-handler ::remove-transaction
|
||||
(fn [db [_ hash]]
|
||||
(-> db
|
||||
(update :transactions dissoc hash)
|
||||
(update :transactions-queue dissoc hash))))
|
||||
|
||||
(register-handler :wait-for-transaction
|
||||
(after (fn [_ [_ message-id]]
|
||||
(dispatch [::check-completed-transaction!
|
||||
{:message-id message-id}])))
|
||||
(fn [db [_ message-id params]]
|
||||
(assoc-in db [:transaction-subscribers message-id] params)))
|
||||
|
||||
(defn remove-pending-message
|
||||
[{:keys [command->chat] :as db} message-id]
|
||||
(let [chat-id (get command->chat message-id)]
|
||||
(if chat-id
|
||||
(update db :transaction-subscribers dissoc message-id)
|
||||
db)))
|
||||
|
||||
(register-handler ::remove-pending-messages
|
||||
(fn [db [_ ids]]
|
||||
(log/debug :message-ids ids)
|
||||
(reduce remove-pending-message db ids)))
|
||||
|
||||
(register-handler ::remove-pending-message
|
||||
(fn [db [_ message-id]]
|
||||
(remove-pending-message db message-id)))
|
||||
|
||||
(defn transaction-valid? [{{:keys [to data]} :args}]
|
||||
(or (and to (valid-hex? to)) (and data (not= data "0x"))))
|
||||
|
||||
(register-handler :transaction-queued
|
||||
(u/side-effect!
|
||||
(fn [_ [_ {:keys [id] :as transaction}]]
|
||||
(if (transaction-valid? transaction)
|
||||
(dispatch [::transaction-queued transaction])
|
||||
(status/discard-transaction id)))))
|
||||
|
||||
(register-handler ::transaction-queued
|
||||
(fn [{:wallet/keys [send-transaction] :as db} [_ {:keys [id message_id args] :as transaction}]]
|
||||
(let [{:keys [from to value data gas gasPrice]} args]
|
||||
(if (transaction-valid? transaction)
|
||||
(let [transaction {:id id
|
||||
:from from
|
||||
:to to
|
||||
:value (.toDecimal js/Web3.prototype value)
|
||||
:data data
|
||||
:gas (.toDecimal js/Web3.prototype gas)
|
||||
:gas-price (.toDecimal js/Web3.prototype gasPrice)
|
||||
:timestamp (time/now-ms)
|
||||
:message-id message_id}]
|
||||
(if (:waiting-signal? send-transaction)
|
||||
(dispatch [:wallet/transaction-queued id])
|
||||
(dispatch [:navigate-to-modal :unsigned-transactions]))
|
||||
(assoc-in db [:transactions-queue id] transaction))
|
||||
db))))
|
||||
|
||||
(register-handler :transaction-completed
|
||||
(u/side-effect!
|
||||
(fn [{:keys [transactions modal]} [_ {:keys [id response]}]]
|
||||
(let [{:keys [hash error]} response
|
||||
{:keys [message-id]} (get transactions id)]
|
||||
(log/debug :parsed-response response)
|
||||
(when-not (and error (string? error) (not (s/blank? error)))
|
||||
(if (and message-id (not (s/blank? message-id)))
|
||||
(do (dispatch [::add-transactions-hash {:id id
|
||||
:hash hash
|
||||
:message-id message-id}])
|
||||
(dispatch [::check-completed-transaction! {:message-id message-id}]))
|
||||
(dispatch [::remove-transaction id]))
|
||||
(when (#{:unsigned-transactions :transaction-details} modal)
|
||||
(dispatch [:navigate-to-modal :confirmation-success])))))))
|
||||
|
||||
(register-handler ::add-transactions-hash
|
||||
(fn [db [_ {:keys [id hash message-id]}]]
|
||||
(-> db
|
||||
(assoc-in [:transactions id :hash] hash)
|
||||
(assoc-in [:message-id->transaction-id message-id] id))))
|
||||
|
||||
(register-handler ::send-pending-message
|
||||
(u/side-effect!
|
||||
(fn [{:keys [transaction-subscribers]} [_ message-id hash]]
|
||||
(when-let [{:keys [chat-id] :as params} (transaction-subscribers message-id)]
|
||||
(let [params' (assoc-in params [:handler-data :transaction-hash] hash)]
|
||||
(dispatch [:prepare-command! chat-id params']))
|
||||
(dispatch [::remove-transaction-subscriber message-id])))))
|
||||
|
||||
(register-handler ::remove-transaction-subscriber
|
||||
(fn [db [_ old-hash]]
|
||||
(update db :transaction-subscribers dissoc old-hash)))
|
||||
|
||||
(register-handler ::check-completed-transaction!
|
||||
(u/side-effect!
|
||||
(fn [{:keys [message-id->transaction-id transactions transaction-subscribers]}
|
||||
[_ {:keys [message-id]}]]
|
||||
(let [id (get message-id->transaction-id message-id)
|
||||
{:keys [hash]} (get transactions id)
|
||||
pending-message (get transaction-subscribers message-id)]
|
||||
(when (and pending-message id hash)
|
||||
(dispatch [::send-pending-message message-id hash]))
|
||||
;; todo revisit this
|
||||
(dispatch [::remove-transaction id])))))
|
||||
|
||||
(def wrong-password-code "2")
|
||||
(def discard-code "4")
|
||||
|
||||
(register-handler :transaction-failed
|
||||
(u/side-effect!
|
||||
(fn [{:accounts/keys [accounts current-account-id]} [_ {:keys [id args message_id error_code error_message] :as event}]]
|
||||
(let [current-account-address (:address (get accounts current-account-id))
|
||||
transaction-initiator-address (normalize-hex (:from args))]
|
||||
(cond
|
||||
|
||||
(= error_code wrong-password-code)
|
||||
(dispatch [:set-wrong-password!])
|
||||
|
||||
(not= discard-code error_code)
|
||||
(do (when message_id
|
||||
(dispatch [::remove-pending-message message_id]))
|
||||
(dispatch [:clear-selected-transaction])
|
||||
(dispatch [::remove-transaction id])
|
||||
(when (= current-account-address transaction-initiator-address)
|
||||
(dispatch [:set-chat-ui-props {:validation-messages error_message}])))
|
||||
|
||||
:else
|
||||
(dispatch [:set-chat-ui-props {:validation-messages nil}]))))))
|
||||
|
||||
(register-handler :clear-selected-transaction
|
||||
(fn [db _]
|
||||
(dissoc db :selected-transaction)))
|
||||
|
||||
(def attempts-limit 3)
|
||||
|
||||
(register-handler :set-wrong-password!
|
||||
(after (fn [{:keys [wrong-password-counter]}]
|
||||
(when (>= wrong-password-counter attempts-limit)
|
||||
(dispatch [:set :wrong-password? false])
|
||||
(dispatch [:set :wrong-password-counter 0])
|
||||
(dispatch [:set-in [:confirm-transactions :password] ""]))))
|
||||
(fn [db]
|
||||
(-> db
|
||||
(assoc :wrong-password? true)
|
||||
(update :wrong-password-counter (fnil inc 0)))))
|
|
@ -1,25 +0,0 @@
|
|||
(ns status-im.transactions.screens.confirmation-success
|
||||
(:require-macros [status-im.utils.views :refer [defview]])
|
||||
(:require [re-frame.core :as rf]
|
||||
[status-im.components.react :as rn]
|
||||
[status-im.components.sticky-button :as sticky-button]
|
||||
[status-im.components.status-bar :as status-bar]
|
||||
[status-im.transactions.views.list-item :as transactions-list-item]
|
||||
[status-im.transactions.styles.screens :as st]
|
||||
[status-im.i18n :as i18n]))
|
||||
|
||||
(defview confirmation-success []
|
||||
[quantity [:get :confirmed-transactions-count]]
|
||||
[rn/view {:style st/success-screen}
|
||||
[status-bar/status-bar {:type :transaction}]
|
||||
[rn/view {:style st/success-screen-content-container}
|
||||
[rn/view {:style st/success-icon-container}
|
||||
[rn/image {:source {:uri :icon_ok_white}
|
||||
:style st/success-icon}]]
|
||||
[rn/view
|
||||
[rn/text {:style st/success-text}
|
||||
(i18n/label-pluralize quantity :t/transactions-confirmed)]]]
|
||||
[sticky-button/sticky-button
|
||||
(i18n/label :t/got-it)
|
||||
#(do (rf/dispatch [:navigate-back])
|
||||
(rf/dispatch [:set :confirmed-transactions-count 0]))]])
|
|
@ -1,80 +0,0 @@
|
|||
(ns status-im.transactions.screens.transaction-details
|
||||
(:require-macros [status-im.utils.views :refer [defview]])
|
||||
(:require [re-frame.core :as rf]
|
||||
[status-im.components.react :as rn]
|
||||
[status-im.components.common.common :as common]
|
||||
[status-im.components.sticky-button :as sticky-button]
|
||||
[status-im.components.status-bar :as status-bar]
|
||||
[status-im.components.sync-state.offline :as offline-view]
|
||||
[status-im.components.toolbar-new.actions :as act]
|
||||
[status-im.components.toolbar-new.view :as toolbar]
|
||||
[status-im.i18n :as i18n]
|
||||
[status-im.transactions.styles.screens :as st]
|
||||
[status-im.transactions.views.list-item :as transactions-list-item]
|
||||
[status-im.transactions.views.password-form :as password-form]
|
||||
[status-im.utils.platform :as platform]
|
||||
[status-im.utils.money :as money]))
|
||||
|
||||
(defn toolbar-view []
|
||||
[toolbar/toolbar2
|
||||
{:background-color st/transactions-toolbar-background
|
||||
:border-style st/toolbar-border}
|
||||
[toolbar/nav-button (act/back-white #(rf/dispatch [:navigate-to-modal :unsigned-transactions]))]
|
||||
[rn/view {:style st/toolbar-title-container}
|
||||
[rn/text {:style st/toolbar-title-text
|
||||
:font :toolbar-title}
|
||||
(i18n/label :t/transaction)]]])
|
||||
|
||||
(defn detail-item [title content name?]
|
||||
[rn/view {:style st/details-item}
|
||||
[rn/text {:style st/details-item-title} title]
|
||||
[rn/text {:style (st/details-item-content name?)
|
||||
:number-of-lines 1}
|
||||
content]])
|
||||
|
||||
(defn detail-data [content]
|
||||
[rn/view {:style st/details-data}
|
||||
[rn/text {:style st/details-data-title} (i18n/label :t/data)]
|
||||
[rn/text {:style st/details-data-content} content]])
|
||||
|
||||
(defview details [{:keys [to data gas gas-price] :as transaction}]
|
||||
[current-account [:get-current-account]
|
||||
recipient [:contact-by-address to]]
|
||||
(let [recipient-name (or (:name recipient) to (i18n/label :t/contract-creation))
|
||||
gas-price' (money/wei->ether gas-price)
|
||||
fee-value (money/fee-value gas gas-price')
|
||||
estimated-fee (str fee-value " ETH")]
|
||||
[rn/view st/details-container
|
||||
[detail-item (i18n/label :t/to) recipient-name true]
|
||||
[detail-item (i18n/label :t/from) (:name current-account) true]
|
||||
[detail-item (i18n/label :t/estimated-fee) estimated-fee]
|
||||
[detail-data data]]))
|
||||
|
||||
(defview transaction-details []
|
||||
[{:keys [id] :as transaction} [:get :selected-transaction]
|
||||
{:keys [password]} [:get :confirm-transactions]
|
||||
confirmed? [:get-in [:transaction-details-ui-props :confirmed?]]
|
||||
sync-state [:sync-state]
|
||||
network-status [:get :network-status]]
|
||||
{:component-did-update #(when-not transaction (rf/dispatch [:navigate-to-modal :unsigned-transactions]))
|
||||
:component-will-unmount #(rf/dispatch [:set-in [:transaction-details-ui-props :confirmed?] false])}
|
||||
(let [offline? (or (= network-status :offline) (= sync-state :offline))]
|
||||
[rn/keyboard-avoiding-view {:style st/transactions-screen}
|
||||
[status-bar/status-bar {:type :transaction}]
|
||||
[toolbar-view]
|
||||
[rn/scroll-view st/details-screen-content-container
|
||||
[transactions-list-item/view transaction #(rf/dispatch [:navigate-to-modal :unsigned-transactions])]
|
||||
[common/separator st/details-separator st/details-separator-wrapper]
|
||||
[details transaction]]
|
||||
(when (and confirmed? (not offline?))
|
||||
[password-form/view 1])
|
||||
(when-not offline?
|
||||
(let [confirm-text (if confirmed?
|
||||
(i18n/label :t/confirm)
|
||||
(i18n/label-pluralize 1 :t/confirm-transactions))
|
||||
confirm-fn (if confirmed?
|
||||
#(do (rf/dispatch [:accept-transaction password id])
|
||||
(rf/dispatch [:set :confirmed-transactions-count 1]))
|
||||
#(rf/dispatch [:set-in [:transaction-details-ui-props :confirmed?] true]))]
|
||||
[sticky-button/sticky-button confirm-text confirm-fn true]))
|
||||
[offline-view/offline-view {:top (if platform/ios? 21 0)}]]))
|
|
@ -1,72 +0,0 @@
|
|||
(ns status-im.transactions.screens.unsigned-transactions
|
||||
(:require-macros [status-im.utils.views :refer [defview]])
|
||||
(:require [re-frame.core :as rf]
|
||||
[status-im.components.common.common :as common]
|
||||
[status-im.components.react :as rn]
|
||||
[status-im.components.sticky-button :as sticky-button]
|
||||
[status-im.components.status-bar :as status-bar]
|
||||
[status-im.components.sync-state.offline :as offline-view]
|
||||
[status-im.components.toolbar-new.actions :as act]
|
||||
[status-im.components.toolbar-new.view :as toolbar]
|
||||
[status-im.transactions.views.list-item :as transactions-list-item]
|
||||
[status-im.transactions.views.password-form :as password-form]
|
||||
[status-im.transactions.styles.screens :as st]
|
||||
[status-im.utils.listview :as lw]
|
||||
[status-im.utils.platform :as platform]
|
||||
[status-im.i18n :as i18n]))
|
||||
|
||||
(defn toolbar-view [transactions]
|
||||
[toolbar/toolbar
|
||||
{:background-color st/transactions-toolbar-background
|
||||
:nav-action (act/close-white #(rf/dispatch [:navigate-back]))
|
||||
:border-style st/toolbar-border
|
||||
:custom-content [rn/view {:style st/toolbar-title-container}
|
||||
[rn/text {:style st/toolbar-title-text
|
||||
:font :toolbar-title}
|
||||
(i18n/label :t/unsigned-transactions)]
|
||||
[rn/text {:style st/toolbar-title-count
|
||||
:font :toolbar-title}
|
||||
(count transactions)]]}])
|
||||
|
||||
(defn render-separator-fn [transactions-count]
|
||||
(fn [_ row-id _]
|
||||
(when (< row-id (dec transactions-count))
|
||||
(rn/list-item
|
||||
^{:key row-id}
|
||||
[common/separator {} st/transactions-list-separator]))))
|
||||
|
||||
(defn render-row-fn [row _ _]
|
||||
(rn/list-item
|
||||
[rn/touchable-highlight {:on-press #(rf/dispatch [:navigate-to-modal :transaction-details row])}
|
||||
[rn/view
|
||||
[transactions-list-item/view row]]]))
|
||||
|
||||
(defview unsigned-transactions []
|
||||
[transactions [:transactions]
|
||||
{:keys [password]} [:get :confirm-transactions]
|
||||
confirmed? [:get-in [:transactions-list-ui-props :confirmed?]]
|
||||
sync-state [:sync-state]
|
||||
network-status [:get :network-status]]
|
||||
{:component-did-update #(when-not (seq transactions) (rf/dispatch [:navigate-back]))
|
||||
:component-will-unmount #(rf/dispatch [:set-in [:transactions-list-ui-props :confirmed?] false])}
|
||||
(let [offline? (or (= network-status :offline) (= sync-state :offline))]
|
||||
[rn/keyboard-avoiding-view st/transactions-screen
|
||||
[status-bar/status-bar {:type :transaction}]
|
||||
[toolbar-view transactions]
|
||||
[rn/view {:style st/transactions-screen-content-container}
|
||||
[rn/list-view {:style st/transactions-list
|
||||
:dataSource (lw/to-datasource transactions)
|
||||
:renderSeparator (render-separator-fn (count transactions))
|
||||
:renderRow render-row-fn}]
|
||||
(when (and confirmed? (not offline?))
|
||||
[password-form/view (count transactions)])]
|
||||
(when-not offline?
|
||||
(let [confirm-text (if confirmed?
|
||||
(i18n/label :t/confirm)
|
||||
(i18n/label-pluralize (count transactions) :t/confirm-transactions))
|
||||
confirm-fn (if confirmed?
|
||||
#(do (rf/dispatch [:accept-transactions password])
|
||||
(rf/dispatch [:set :confirmed-transactions-count (count transactions)]))
|
||||
#(rf/dispatch [:set-in [:transactions-list-ui-props :confirmed?] true]))]
|
||||
[sticky-button/sticky-button confirm-text confirm-fn true]))
|
||||
[offline-view/offline-view {:top (if platform/ios? 21 0)}]]))
|
|
@ -1,12 +0,0 @@
|
|||
(ns status-im.transactions.specs
|
||||
(:require [cljs.spec.alpha :as s]))
|
||||
|
||||
(s/def :transactions/transactions (s/nilable map?)) ;; {id (string) transaction (map)}
|
||||
(s/def :transactions/transactions-queue (s/nilable map?)) ;; {id (string) transaction (map)}
|
||||
(s/def :transactions/selected-transaction (s/nilable map?))
|
||||
(s/def :transactions/confirm-transactions (s/nilable map?))
|
||||
(s/def :transactions/confirmed-transactions-count (s/nilable int?))
|
||||
(s/def :transactions/transactions-list-ui-props (s/nilable map?))
|
||||
(s/def :transactions/transaction-details-ui-props (s/nilable map?))
|
||||
(s/def :transactions/wrong-password-counter (s/nilable int?))
|
||||
(s/def :transactions/wrong-password? (s/nilable boolean?))
|
|
@ -1,52 +0,0 @@
|
|||
(ns status-im.transactions.styles.list-item
|
||||
(:require-macros [status-im.utils.styles :refer [defstyle]])
|
||||
(:require [status-im.components.styles :as st]))
|
||||
|
||||
(def item
|
||||
{:padding-vertical 19
|
||||
:padding-horizontal 16
|
||||
:flex 1
|
||||
:flex-direction :row
|
||||
:align-items :center})
|
||||
|
||||
(def item-photo
|
||||
{:width 86
|
||||
:height 48
|
||||
:border-radius 100
|
||||
:background-color st/color-dark-blue-3
|
||||
:flex-direction :row
|
||||
:align-items :center})
|
||||
|
||||
(def item-info
|
||||
{:margin-left 16
|
||||
:flex 1})
|
||||
|
||||
(defstyle item-info-recipient
|
||||
{:color st/color-light-blue
|
||||
:font-size 15
|
||||
:flex-shrink 1
|
||||
:android {:margin-bottom 5}
|
||||
:ios {:margin-bottom 4}})
|
||||
|
||||
(defstyle item-info-amount
|
||||
{:color st/color-white
|
||||
:android {:font-size 19}
|
||||
:ios {:font-size 20}})
|
||||
|
||||
(def item-deny-btn
|
||||
{:margin-left 16})
|
||||
|
||||
(def item-deny-btn-icon
|
||||
{:width 24
|
||||
:height 24})
|
||||
|
||||
(def photo-size 48)
|
||||
|
||||
(def photo-placeholder
|
||||
{:width 48
|
||||
:height 48})
|
||||
|
||||
(def item-photo-icon
|
||||
{:margin-left 4
|
||||
:width 24
|
||||
:height 24})
|
|
@ -1,9 +0,0 @@
|
|||
(ns status-im.transactions.styles.password-form
|
||||
(:require-macros [status-im.utils.styles :refer [defstyle]])
|
||||
(:require [status-im.components.styles :as common]))
|
||||
|
||||
(defstyle password-container
|
||||
{:android {:margin-bottom 27}
|
||||
:ios {:margin-bottom 20
|
||||
:border-top-width 1
|
||||
:border-top-color common/color-white-transparent-2}})
|
|
@ -1,134 +0,0 @@
|
|||
(ns status-im.transactions.styles.screens
|
||||
(:require-macros [status-im.utils.styles :refer [defstyle]])
|
||||
(:require [status-im.components.styles :as common]
|
||||
[status-im.utils.platform :as platform]))
|
||||
|
||||
;; common
|
||||
|
||||
(def transactions-toolbar-background (if platform/ios?
|
||||
common/color-dark-blue-2
|
||||
common/color-dark-blue-1))
|
||||
|
||||
(defstyle toolbar-title-container
|
||||
{:flex 1
|
||||
:flex-direction :row
|
||||
:padding-left 18
|
||||
:ios {:align-items :center
|
||||
:justify-content :center}})
|
||||
|
||||
(def toolbar-title-text
|
||||
{:color common/color-white
|
||||
:font-size 17})
|
||||
|
||||
(defstyle toolbar-title-count
|
||||
{:color common/color-white
|
||||
:font-size 17
|
||||
:margin-left 8
|
||||
:android {:opacity 0.2}
|
||||
:ios {:opacity 0.6}})
|
||||
|
||||
(defstyle toolbar-border
|
||||
{:ios {:background-color common/color-white
|
||||
:opacity 0.1}})
|
||||
|
||||
;; unsigned-transactions
|
||||
|
||||
(def transactions-screen
|
||||
{:flex 1
|
||||
:background-color common/color-dark-blue-2})
|
||||
|
||||
(def transactions-screen-content-container
|
||||
{:flex 1
|
||||
:justify-content :space-between})
|
||||
|
||||
(defstyle transactions-list
|
||||
{:flex 1
|
||||
:android {:padding-vertical 8}})
|
||||
|
||||
(def transactions-list-separator
|
||||
{:margin-left 16
|
||||
:opacity 0.1})
|
||||
|
||||
;; transaction-details
|
||||
|
||||
(defstyle details-screen-content-container
|
||||
{:flex 1
|
||||
:android {:padding-top 8}})
|
||||
|
||||
(def details-separator-wrapper
|
||||
{:background-color common/color-dark-blue-2})
|
||||
|
||||
(def details-separator
|
||||
{:margin-left 16
|
||||
:background-color common/color-white
|
||||
:opacity 0.1})
|
||||
|
||||
(defstyle details-container
|
||||
{:ios {:margin-top 10}
|
||||
:android {:margin-top 0}})
|
||||
|
||||
(def details-item
|
||||
{:margin-top 10
|
||||
:padding-left 16
|
||||
:padding-right 16
|
||||
:flex-direction :row})
|
||||
|
||||
(defstyle details-item-title
|
||||
{:width 80
|
||||
:font-size 15
|
||||
:color common/color-white
|
||||
:android {:opacity 0.2
|
||||
:margin-right 24}
|
||||
:ios {:opacity 0.5
|
||||
:margin-right 8
|
||||
:text-align :right}})
|
||||
|
||||
(defn details-item-content [name?]
|
||||
{:font-size 15
|
||||
:flex-shrink 1
|
||||
:color (if name? common/color-light-blue common/color-white)})
|
||||
|
||||
(defstyle details-data
|
||||
{:padding 16
|
||||
:margin-top 16
|
||||
:background-color common/color-dark-blue-3
|
||||
:ios {:margin-horizontal 16}})
|
||||
|
||||
(defstyle details-data-title
|
||||
{:font-size 15
|
||||
:color common/color-white
|
||||
:android {:opacity 0.2}
|
||||
:ios {:opacity 0.5}})
|
||||
|
||||
(def details-data-content
|
||||
{:font-size 15
|
||||
:color common/color-white
|
||||
:margin-top 8})
|
||||
|
||||
;; confirmation-success
|
||||
|
||||
(def success-screen
|
||||
{:flex 1
|
||||
:background-color common/color-dark-blue-2})
|
||||
|
||||
(def success-screen-content-container
|
||||
{:flex 1
|
||||
:align-items :center
|
||||
:justify-content :center})
|
||||
|
||||
(def success-icon-container
|
||||
{:background-color common/color-light-blue
|
||||
:border-radius 100
|
||||
:height 133
|
||||
:width 133
|
||||
:justify-content :center
|
||||
:align-items :center})
|
||||
|
||||
(def success-icon
|
||||
{:height 40
|
||||
:width 54})
|
||||
|
||||
(def success-text
|
||||
{:font-size 17
|
||||
:color common/color-light-blue3
|
||||
:margin-top 26})
|
|
@ -1,24 +0,0 @@
|
|||
(ns status-im.transactions.subs
|
||||
(:require [re-frame.core :refer [reg-sub subscribe]]
|
||||
[status-im.utils.hex :as i]))
|
||||
|
||||
(reg-sub :transactions
|
||||
(fn [db]
|
||||
(vals (:transactions db))))
|
||||
|
||||
(reg-sub :contacts-by-address
|
||||
(fn [db]
|
||||
(into {} (map (fn [[_ {:keys [address] :as contact}]]
|
||||
(when address
|
||||
[address contact]))
|
||||
(:contacts/contacts db)))))
|
||||
|
||||
(reg-sub :contact-by-address
|
||||
:<- [:contacts-by-address]
|
||||
(fn [contacts [_ address]]
|
||||
(let [address' (when address
|
||||
(i/normalize-hex address))]
|
||||
(contacts address'))))
|
||||
|
||||
(reg-sub :wrong-password?
|
||||
(fn [db] (:wrong-password? db)))
|
|
@ -1,40 +0,0 @@
|
|||
(ns status-im.transactions.views.list-item
|
||||
(:require-macros [status-im.utils.views :refer [defview]])
|
||||
(:require [re-frame.core :as rf]
|
||||
[status-im.components.chat-icon.screen :as chat-icon]
|
||||
[status-im.components.react :as rn]
|
||||
[status-im.i18n :as i18n]
|
||||
[status-im.transactions.styles.list-item :as st]
|
||||
[status-im.utils.money :as money]))
|
||||
|
||||
(defview item-image [contact]
|
||||
[rn/view {:style st/item-photo}
|
||||
(if-not (empty? contact)
|
||||
[chat-icon/chat-icon (:photo-path contact) {:size st/photo-size}]
|
||||
[rn/view {:style st/photo-placeholder}])
|
||||
[rn/image {:source {:uri :icon_arrow_left_white}
|
||||
:style st/item-photo-icon}]])
|
||||
|
||||
(defn item-info [recipient-name value]
|
||||
[rn/view {:style st/item-info}
|
||||
[rn/text {:style st/item-info-recipient
|
||||
:number-of-lines 1}
|
||||
recipient-name]
|
||||
[rn/text {:style st/item-info-amount} value]])
|
||||
|
||||
(defn deny-btn [transaction-id on-deny]
|
||||
[rn/touchable-highlight {:on-press #(do (rf/dispatch [:deny-transaction transaction-id])
|
||||
(when on-deny (on-deny)))}
|
||||
[rn/view {:style st/item-deny-btn}
|
||||
[rn/image {:source {:uri :icon_close_white}
|
||||
:style st/item-deny-btn-icon}]]])
|
||||
|
||||
(defview view [{:keys [to value id] :as transaction} on-deny]
|
||||
[recipient [:contact-by-address to]]
|
||||
(let [eth-value (str (money/wei->ether value))
|
||||
value-str (str (i18n/label-number eth-value) " ETH")
|
||||
recipient-name (or (:name recipient) to (i18n/label :t/contract-creation))]
|
||||
[rn/view {:style st/item}
|
||||
[item-image recipient]
|
||||
[item-info recipient-name value-str]
|
||||
[deny-btn id on-deny]]))
|
|
@ -1,21 +0,0 @@
|
|||
(ns status-im.transactions.views.password-form
|
||||
(:require-macros [status-im.utils.views :refer [defview]])
|
||||
(:require [re-frame.core :as rf]
|
||||
[status-im.components.react :as rn]
|
||||
[status-im.components.text-input-with-label.view :refer [text-input-with-label]]
|
||||
[status-im.transactions.styles.password-form :as st]
|
||||
[status-im.i18n :as i18n]))
|
||||
|
||||
(defview view [transaction-quantity]
|
||||
[wrong-password? [:wrong-password?]]
|
||||
(let [error? wrong-password?]
|
||||
[rn/view st/password-container
|
||||
[text-input-with-label
|
||||
{:label (i18n/label :t/password)
|
||||
:description (i18n/label-pluralize transaction-quantity :t/enter-password-transactions)
|
||||
:on-change-text #(rf/dispatch [:set-in [:confirm-transactions :password] %])
|
||||
:style {:color :white}
|
||||
:auto-focus true
|
||||
:secure-text-entry true
|
||||
:error (when error? (i18n/label :t/wrong-password))}]]))
|
||||
|
|
@ -10,7 +10,6 @@
|
|||
status-im.chat.specs
|
||||
status-im.chat.new-public-chat.db
|
||||
status-im.ui.screens.profile.db
|
||||
status-im.transactions.specs
|
||||
status-im.ui.screens.discover.db
|
||||
status-im.ui.screens.network-settings.db))
|
||||
|
||||
|
@ -172,15 +171,6 @@
|
|||
:chat/raw-unviewed-messages
|
||||
:chat/bot-db
|
||||
:chat/geolocation
|
||||
:transactions/transactions
|
||||
:transactions/transactions-queue
|
||||
:transactions/selected-transaction
|
||||
:transactions/confirm-transactions
|
||||
:transactions/confirmed-transactions-count
|
||||
:transactions/transactions-list-ui-props
|
||||
:transactions/transaction-details-ui-props
|
||||
:transactions/wrong-password-counter
|
||||
:transactions/wrong-password?
|
||||
:discoveries/discoveries
|
||||
:discoveries/discover-search-tags
|
||||
:discoveries/tags
|
||||
|
|
|
@ -6,7 +6,6 @@
|
|||
status-im.debug.handlers
|
||||
status-im.network.handlers
|
||||
status-im.protocol.handlers
|
||||
status-im.transactions.handlers
|
||||
status-im.ui.screens.accounts.events
|
||||
status-im.ui.screens.contacts.events
|
||||
status-im.ui.screens.discover.events
|
||||
|
@ -18,6 +17,8 @@
|
|||
status-im.ui.screens.qr-scanner.events
|
||||
status-im.ui.screens.wallet.events
|
||||
status-im.ui.screens.wallet.send.events
|
||||
status-im.ui.screens.wallet.choose-recipient.events
|
||||
status-im.ui.screens.wallet.transactions.events
|
||||
[re-frame.core :refer [dispatch reg-fx reg-cofx] :as re-frame]
|
||||
[status-im.native-module.core :as status]
|
||||
[status-im.components.permissions :as permissions]
|
||||
|
@ -348,10 +349,10 @@
|
|||
|
||||
(register-handler-fx
|
||||
:app-state-change
|
||||
(fn [_ [_ state]]
|
||||
(fn [_ [_ state]]))
|
||||
;; TODO(rasom): let's not remove this handler, it will be used for
|
||||
;; pausing node on entering background on android
|
||||
))
|
||||
|
||||
|
||||
(register-handler-fx
|
||||
:request-permissions
|
||||
|
|
|
@ -13,7 +13,6 @@
|
|||
status-im.ui.screens.wallet.send.subs
|
||||
status-im.ui.screens.wallet.request.subs
|
||||
status-im.ui.screens.network-settings.subs
|
||||
status-im.transactions.subs
|
||||
status-im.bots.subs))
|
||||
|
||||
(reg-sub :get
|
||||
|
|
|
@ -21,11 +21,6 @@
|
|||
|
||||
[status-im.ui.screens.qr-scanner.views :refer [qr-scanner]]
|
||||
|
||||
[status-im.transactions.screens.confirmation-success :refer [confirmation-success]]
|
||||
[status-im.transactions.screens.unsigned-transactions :refer [unsigned-transactions]]
|
||||
[status-im.transactions.screens.transaction-details :refer [transaction-details]]
|
||||
|
||||
|
||||
[status-im.ui.screens.group.views :refer [new-group edit-contact-group]]
|
||||
[status-im.ui.screens.group.chat-settings.views :refer [chat-group-settings]]
|
||||
[status-im.ui.screens.group.edit-contacts.views :refer [edit-contact-group-contact-list
|
||||
|
@ -40,7 +35,7 @@
|
|||
[status-im.ui.screens.profile.photo-capture.views :refer [profile-photo-capture]]
|
||||
[status-im.ui.screens.profile.qr-code.views :refer [qr-code-view]]
|
||||
|
||||
[status-im.ui.screens.wallet.send.views :refer [send-transaction]]
|
||||
[status-im.ui.screens.wallet.send.views :refer [send-transaction send-transaction-modal]]
|
||||
[status-im.ui.screens.wallet.choose-recipient.views :refer [choose-recipient]]
|
||||
[status-im.ui.screens.wallet.request.views :refer [request-transaction]]
|
||||
[status-im.ui.screens.wallet.wallet-list.views :refer [wallet-list-screen]]
|
||||
|
@ -109,8 +104,6 @@
|
|||
:add-rpc-url add-rpc-url
|
||||
:network-details network-details
|
||||
(throw (str "Unknown view: " current-view)))]
|
||||
|
||||
|
||||
[(if android? menu-context view) common-styles/flex
|
||||
[view common-styles/flex
|
||||
[component]
|
||||
|
@ -122,11 +115,8 @@
|
|||
(let [component (case modal-view
|
||||
:qr-scanner qr-scanner
|
||||
:qr-code-view qr-code-view
|
||||
:unsigned-transactions unsigned-transactions
|
||||
:transaction-details transaction-details
|
||||
:confirmation-success confirmation-success
|
||||
:contact-list-modal contact-list-modal
|
||||
:wallet-transactions-filter wallet-transactions/filter-history
|
||||
:wallet-transactions-sign-all wallet-transactions/sign-all
|
||||
:wallet-send-transaction-modal send-transaction-modal
|
||||
(throw (str "Unknown modal view: " modal-view)))]
|
||||
[component])]])]])))))
|
||||
|
|
|
@ -0,0 +1,26 @@
|
|||
(ns status-im.ui.screens.wallet.choose-recipient.events
|
||||
(:require [status-im.utils.handlers :as handlers]))
|
||||
|
||||
(handlers/register-handler-db
|
||||
:wallet/toggle-flashlight
|
||||
(fn [db]
|
||||
(let [flashlight-state (get-in db [:wallet/send-transaction :camera-flashlight])
|
||||
toggled-state (if (= :on flashlight-state) :off :on)]
|
||||
(assoc-in db [:wallet/send-transaction :camera-flashlight] toggled-state))))
|
||||
|
||||
(defn choose-address-and-name [db address name]
|
||||
(update db :wallet/send-transaction assoc :to-address address :to-name name))
|
||||
|
||||
(handlers/register-handler-fx
|
||||
:choose-recipient
|
||||
(fn [{:keys [db]} [_ address name]]
|
||||
(let [{:keys [view-id]} db]
|
||||
(cond-> {:db (choose-address-and-name db address name)}
|
||||
(= :choose-recipient view-id) (assoc :dispatch [:navigate-back])))))
|
||||
|
||||
(handlers/register-handler-fx
|
||||
:wallet-open-send-transaction
|
||||
(fn [{db :db} [_ address name]]
|
||||
{:db (choose-address-and-name db address name)
|
||||
:dispatch-n [[:navigate-back]
|
||||
[:navigate-back]]}))
|
|
@ -21,7 +21,7 @@
|
|||
:line-height 12}})
|
||||
|
||||
(def amount-text-input-container
|
||||
{:margin-top 8})
|
||||
{:margin-top 8})
|
||||
|
||||
(def label-transparent
|
||||
(merge label
|
||||
|
@ -61,6 +61,14 @@
|
|||
:ios {:border-radius 8}
|
||||
:android {:border-radius 4}})
|
||||
|
||||
(defstyle container-disabled
|
||||
{:border-width 1
|
||||
:border-color styles/color-white-transparent-4
|
||||
:background-color nil
|
||||
:ios {:border-radius 8}
|
||||
:android {:border-radius 4}})
|
||||
|
||||
|
||||
(defstyle recipient-container
|
||||
{:flex-direction :row
|
||||
:flex 1
|
||||
|
|
|
@ -21,6 +21,17 @@
|
|||
[react/text {:style styles/tooltip-text} label]]
|
||||
[vector-icons/icon :icons/tooltip-triangle {:color :white :style styles/tooltip-triangle}]]]))
|
||||
|
||||
;;TODO (andrey) temporary, should be removed later
|
||||
(defn amount-input-disabled [amount]
|
||||
[react/view components.styles/flex
|
||||
[react/text {:style styles/label} (i18n/label :t/amount)]
|
||||
[react/view styles/amount-text-input-container
|
||||
[react/view (merge (styles/amount-container false) styles/container-disabled)
|
||||
[react/text-input
|
||||
{:editable false
|
||||
:default-value amount
|
||||
:style styles/text-input}]]]])
|
||||
|
||||
(defn amount-input []
|
||||
(let [active? (reagent/atom false)]
|
||||
(fn [& [{:keys [input-options style error]}]]
|
||||
|
@ -53,6 +64,22 @@
|
|||
style)
|
||||
[react/text {:style styles/wallet-name} "ETH"]]])
|
||||
|
||||
(defn choose-recipient-disabled [{:keys [address name]}]
|
||||
[react/view
|
||||
[react/text {:style styles/label} (i18n/label :t/recipient)]
|
||||
[react/view (merge styles/recipient-container
|
||||
styles/container-disabled)
|
||||
(when name
|
||||
[react/view styles/recipient-name-container
|
||||
[react/text {:style (styles/participant true)
|
||||
:number-of-lines 1}
|
||||
name]])
|
||||
[react/view components.styles/flex
|
||||
[react/text {:style (styles/participant (not name))
|
||||
:number-of-lines 1
|
||||
:ellipsizeMode :middle}
|
||||
address]]]])
|
||||
|
||||
(defn choose-recipient [{:keys [address name on-press style]}]
|
||||
(let [address? (and (not (nil? address)) (not= address ""))]
|
||||
[react/touchable-highlight {:on-press on-press}
|
||||
|
|
|
@ -136,3 +136,8 @@
|
|||
(fn [{:keys [db]} [_ hash]]
|
||||
{:db (assoc-in db [:wallet :current-transaction] hash)
|
||||
:dispatch [:navigate-to :wallet-transaction-details]}))
|
||||
|
||||
(handlers/register-handler-fx
|
||||
:wallet/discard-unsigned-transaction
|
||||
(fn [_ [_ transaction-id]]
|
||||
{:discard-transaction transaction-id}))
|
||||
|
|
|
@ -20,4 +20,8 @@
|
|||
[db [event]]
|
||||
(if (= event :navigate-back)
|
||||
db
|
||||
(dissoc db :wallet/send-transaction)))
|
||||
(dissoc db :wallet/send-transaction)))
|
||||
|
||||
(defmethod navigation/preload-data! :wallet-send-transaction-modal
|
||||
[db [_ _ value]]
|
||||
(assoc db :wallet/send-transaction value))
|
|
@ -7,6 +7,7 @@
|
|||
(spec/def ::to-name (spec/nilable string?))
|
||||
(spec/def ::amount-error (spec/nilable string?))
|
||||
(spec/def ::password (spec/nilable string?))
|
||||
(spec/def ::wrong-password? (spec/nilable boolean?))
|
||||
(spec/def ::transaction-id (spec/nilable string?))
|
||||
(spec/def ::waiting-signal? (spec/nilable boolean?))
|
||||
(spec/def ::signing? (spec/nilable boolean?))
|
||||
|
@ -17,5 +18,5 @@
|
|||
(spec/def ::camera-flashlight #{:on :off})
|
||||
|
||||
(spec/def :wallet/send-transaction (allowed-keys
|
||||
:opt-un [::amount ::to-address ::to-name ::amount-error ::password
|
||||
:opt-un [::amount ::to-address ::to-name ::amount-error ::password ::wrong-password?
|
||||
::waiting-signal? ::signing? ::transaction-id ::later? ::camera-dimensions ::camera-flashlight]))
|
||||
|
|
|
@ -30,66 +30,58 @@
|
|||
(fn []
|
||||
(utils/show-popup (i18n/label :t/transaction-moved-title) (i18n/label :t/transaction-moved-text))))
|
||||
|
||||
(re-frame/reg-fx
|
||||
::discard-transaction
|
||||
(fn [id]
|
||||
(status/discard-transaction id)))
|
||||
|
||||
;;;; Handlers
|
||||
|
||||
(defn choose-address-and-name [db address name]
|
||||
(update db :wallet/send-transaction assoc :to-address address :to-name name))
|
||||
|
||||
(handlers/register-handler-fx
|
||||
:choose-recipient
|
||||
(fn [{:keys [db]} [_ address name]]
|
||||
(let [{:keys [view-id]} db]
|
||||
(cond-> {:db (choose-address-and-name db address name)}
|
||||
(= :choose-recipient view-id) (assoc :dispatch [:navigate-back])))))
|
||||
|
||||
(handlers/register-handler-fx
|
||||
:wallet-open-send-transaction
|
||||
(fn [{db :db} [_ address name]]
|
||||
{:db (choose-address-and-name db address name)
|
||||
:dispatch-n [[:navigate-back]
|
||||
[:navigate-back]]}))
|
||||
|
||||
(handlers/register-handler-fx
|
||||
:wallet-validate-amount
|
||||
(fn [{{:keys [web3] :wallet/keys [send-transaction] :as db} :db} _]
|
||||
(let [amount (:amount send-transaction)
|
||||
error (wallet.db/get-amount-validation-error amount web3)]
|
||||
{:db (assoc-in db [:wallet/send-transaction :amount-error] error)})))
|
||||
:wallet/set-and-validate-amount
|
||||
(fn [{{:keys [web3] :wallet/keys [send-transaction] :as db} :db} [_ amount]]
|
||||
(let [error (wallet.db/get-amount-validation-error amount web3)]
|
||||
{:db (-> db
|
||||
(assoc-in [:wallet/send-transaction :amount] amount)
|
||||
(assoc-in [:wallet/send-transaction :amount-error] error))})))
|
||||
|
||||
(handlers/register-handler-fx
|
||||
::transaction-completed
|
||||
(fn [{db :db} [_ {:keys [id response]}]]
|
||||
(let [{:keys [hash error]} response]
|
||||
;; error is handling in TRANSACTION FAILED signal from status-go
|
||||
(when-not (and error (string? error) (not (string/blank? error)))
|
||||
{:db (-> db
|
||||
(update-in [:wallet :transactions-unsigned] dissoc id)
|
||||
(assoc-in [:wallet/send-transaction :transaction-id] nil)
|
||||
(assoc :wrong-password? false))
|
||||
(assoc-in [:wallet/send-transaction :wrong-password?] false))
|
||||
:dispatch [:navigate-to :wallet-transaction-sent]}))))
|
||||
|
||||
(defn on-transactions-completed [raw-results]
|
||||
(let [results (:results (types/json->clj raw-results))]
|
||||
(doseq [result results]
|
||||
;;TODO (andrey) legacy, should be removed with old transactions screens
|
||||
(re-frame/dispatch [:transaction-completed {:id (name (key result)) :response (second result)}])
|
||||
(re-frame/dispatch [::transaction-completed {:id (name (key result)) :response (second result)}]))))
|
||||
|
||||
(handlers/register-handler-fx
|
||||
:wallet/transaction-queued
|
||||
(fn [{{:wallet/keys [send-transaction] :as db} :db} [_ transaction-id]]
|
||||
(let [{:keys [later? password]} send-transaction]
|
||||
::transaction-modal-completed
|
||||
(fn [{db :db} [_ {:keys [id response]}]]
|
||||
(let [{:keys [hash error]} response]
|
||||
(when-not (and error (string? error) (not (string/blank? error)))
|
||||
{:db (-> db
|
||||
(update-in [:wallet :transactions-unsigned] dissoc id)
|
||||
(assoc-in [:wallet/send-transaction :transaction-id] nil)
|
||||
(assoc-in [:wallet/send-transaction :wrong-password?] false))
|
||||
:dispatch [:navigate-back]}))))
|
||||
|
||||
(defn on-transactions-modal-completed [raw-results]
|
||||
(let [results (:results (types/json->clj raw-results))]
|
||||
(doseq [result results]
|
||||
(re-frame/dispatch [::transaction-modal-completed {:id (name (key result)) :response (second result)}]))))
|
||||
|
||||
(handlers/register-handler-fx
|
||||
:wallet.send-transaction/transaction-queued
|
||||
(fn [{{:wallet/keys [send-transaction] :as db} :db} _]
|
||||
(let [{:keys [later? password transaction-id]} send-transaction]
|
||||
(if later?
|
||||
{:db (-> db
|
||||
(assoc-in [:wallet/send-transaction :waiting-signal?] false)
|
||||
(assoc :transactions (:transactions-queue db)))
|
||||
{:db (assoc-in db [:wallet/send-transaction :waiting-signal?] false)
|
||||
:dispatch [:navigate-back]
|
||||
::show-transaction-moved nil}
|
||||
{:db (assoc-in db [:wallet/send-transaction :transaction-id] transaction-id)
|
||||
::accept-transaction {:id transaction-id
|
||||
{::accept-transaction {:id transaction-id
|
||||
:password password
|
||||
:on-completed on-transactions-completed}}))))
|
||||
|
||||
|
@ -112,22 +104,30 @@
|
|||
:to (:to-address send-transaction)
|
||||
:value amount-in-wei}}))))
|
||||
|
||||
(handlers/register-handler-fx
|
||||
:wallet/sign-transaction-modal
|
||||
(fn [{{:keys [web3]
|
||||
:wallet/keys [send-transaction]
|
||||
:accounts/keys [accounts current-account-id] :as db} :db} [_ later?]]
|
||||
(let [{:keys [amount transaction-id password]} send-transaction
|
||||
amount' (money/to-wei (string/replace amount #"," "."))]
|
||||
{::accept-transaction {:id transaction-id
|
||||
:password password
|
||||
:on-completed on-transactions-modal-completed}})))
|
||||
|
||||
(handlers/register-handler-fx
|
||||
:wallet/discard-transaction
|
||||
(fn [{{:wallet/keys [send-transaction] :as db} :db} _]
|
||||
(let [{:keys [transaction-id]} send-transaction]
|
||||
(merge {:db (-> db
|
||||
(update-in [:wallet/send-transaction]
|
||||
#(assoc % :signing? false :transaction-id nil))
|
||||
(assoc :wrong-password? false))}
|
||||
#(assoc % :signing? false :transaction-id nil :wrong-password? false)))}
|
||||
(when transaction-id
|
||||
;;TODO (andrey) use ::discard-transaction fx instead
|
||||
{:dispatch-n [[:deny-transaction transaction-id]
|
||||
[:status-im.transactions.handlers/remove-transaction transaction-id]]})))))
|
||||
{:discard-transaction transaction-id})))))
|
||||
|
||||
(handlers/register-handler-db
|
||||
:wallet/toggle-flashlight
|
||||
(fn [db]
|
||||
(let [flashlight-state (get-in db [:wallet/send-transaction :camera-flashlight])
|
||||
toggled-state (if (= :on flashlight-state) :off :on)]
|
||||
(assoc-in db [:wallet/send-transaction :camera-flashlight] toggled-state))))
|
||||
(handlers/register-handler-fx
|
||||
:wallet/cancel-signing-modal
|
||||
(fn [{{:wallet/keys [send-transaction] :as db} :db} _]
|
||||
(let [{:keys [transaction-id]} send-transaction]
|
||||
{:db (update-in db [:wallet/send-transaction]
|
||||
assoc :signing? false :wrong-password? false)})))
|
||||
|
|
|
@ -36,8 +36,7 @@
|
|||
|
||||
(defview sign-panel []
|
||||
(letsubs [account [:get-current-account]
|
||||
;;TODO (andrey) use send-transaction map after we remove old transactions ui
|
||||
wrong-password? [:get :wrong-password?];[:get-in [:wallet/send-transaction :wrong-password?]]
|
||||
wrong-password? [:get-in [:wallet/send-transaction :wrong-password?]]
|
||||
signing-phrase (:signing-phrase @account)
|
||||
bottom-value (animation/create-value -250)
|
||||
opacity-value (animation/create-value 0)]
|
||||
|
@ -58,14 +57,16 @@
|
|||
(when wrong-password?
|
||||
[components/tooltip (i18n/label :t/wrong-password)])]))
|
||||
|
||||
(defview signing-buttons []
|
||||
|
||||
;; "Cancel" and "Sign Transaction >" buttons, signing with password
|
||||
(defview signing-buttons [cancel-handler sign-handler]
|
||||
(letsubs [sign-enabled? [:wallet.send/sign-password-enabled?]]
|
||||
[react/view wallet.styles/buttons-container
|
||||
[react/touchable-highlight {:on-press #(re-frame/dispatch [:wallet/discard-transaction])}
|
||||
[react/touchable-highlight {:on-press cancel-handler}
|
||||
[react/view (wallet.styles/button-container true)
|
||||
[components/button-text (i18n/label :t/cancel)]]]
|
||||
[react/view components.styles/flex]
|
||||
[react/touchable-highlight {:on-press #(re-frame/dispatch [:wallet/sign-transaction])}
|
||||
[react/touchable-highlight {:on-press sign-handler}
|
||||
[react/view (wallet.styles/button-container sign-enabled?)
|
||||
[components/button-text (i18n/label :t/transactions-sign-transaction)]
|
||||
[vector-icons/icon :icons/forward {:color :white :container-style wallet.styles/forward-icon-container}]]]]))
|
||||
|
@ -76,12 +77,13 @@
|
|||
(not (nil? to-address)) (not= to-address "")
|
||||
(not (nil? amount)) (not= amount "")))
|
||||
|
||||
(defn- sign-buttons [amount-error to-address amount sufficient-funds?]
|
||||
;; "Sign Later" and "Sign Transaction >" buttons
|
||||
(defn- sign-buttons [amount-error to-address amount sufficient-funds? sign-later-handler]
|
||||
(let [sign-enabled? (sign-enabled? amount-error to-address amount)
|
||||
immediate-sign-enabled? (and sign-enabled? sufficient-funds?)]
|
||||
[react/view wallet.styles/buttons-container
|
||||
(when sign-enabled?
|
||||
[react/touchable-highlight {:on-press sign-later}
|
||||
[react/touchable-highlight {:on-press sign-later-handler}
|
||||
[react/view (wallet.styles/button-container sign-enabled?)
|
||||
[components/button-text (i18n/label :t/transactions-sign-later)]]])
|
||||
[react/view components.styles/flex]
|
||||
|
@ -119,13 +121,50 @@
|
|||
:input-options {:auto-focus true
|
||||
:default-value amount
|
||||
:on-change-text #(let [value (string/trim %)]
|
||||
(re-frame/dispatch [:set-in [:wallet/send-transaction :amount] value])
|
||||
(re-frame/dispatch [:wallet-validate-amount]))}}]
|
||||
(re-frame/dispatch [:wallet/set-and-validate-amount value]))}}]
|
||||
[react/view wallet.styles/choose-currency-container
|
||||
[components/choose-currency wallet.styles/choose-currency]]]]]
|
||||
[components/separator]
|
||||
(if signing?
|
||||
[signing-buttons]
|
||||
[sign-buttons amount-error to-address amount sufficient-funds?])
|
||||
[signing-buttons
|
||||
#(re-frame/dispatch [:wallet/discard-transaction])
|
||||
#(re-frame/dispatch [:wallet/sign-transaction])]
|
||||
[sign-buttons amount-error to-address amount sufficient-funds? sign-later])
|
||||
(when signing?
|
||||
[sign-panel])]])))
|
||||
[sign-panel])]])))
|
||||
|
||||
(defn toolbar-modal []
|
||||
[toolbar/toolbar2 {:style wallet.styles/toolbar}
|
||||
[toolbar/nav-button (act/close-white act/default-handler)]
|
||||
[toolbar/content-title {:color :white} (i18n/label :t/send-transaction)]])
|
||||
|
||||
(defview send-transaction-modal []
|
||||
(letsubs [amount [:get-in [:wallet/send-transaction :amount]]
|
||||
amount-error [:get-in [:wallet/send-transaction :amount-error]]
|
||||
signing? [:get-in [:wallet/send-transaction :signing?]]
|
||||
to-address [:get-in [:wallet/send-transaction :to-address]]
|
||||
to-name [:get-in [:wallet/send-transaction :to-name]]
|
||||
recipient [:contact-by-address @to-name]]
|
||||
[react/keyboard-avoiding-view wallet.styles/wallet-modal-container
|
||||
[react/view components.styles/flex
|
||||
[status-bar/status-bar {:type :wallet}]
|
||||
[toolbar-modal]
|
||||
[react/scroll-view {:keyboardShouldPersistTaps :always}
|
||||
[react/view components.styles/flex
|
||||
[react/view wallet.styles/choose-participant-container
|
||||
[components/choose-recipient-disabled {:address to-address
|
||||
:name (:name recipient)}]]
|
||||
[react/view wallet.styles/choose-wallet-container
|
||||
[components/choose-wallet]]
|
||||
[react/view wallet.styles/amount-container
|
||||
[components/amount-input-disabled amount]
|
||||
[react/view wallet.styles/choose-currency-container
|
||||
[components/choose-currency wallet.styles/choose-currency]]]]]
|
||||
[components/separator]
|
||||
(if signing?
|
||||
[signing-buttons
|
||||
#(re-frame/dispatch [:wallet/cancel-signing-modal])
|
||||
#(re-frame/dispatch [:wallet/sign-transaction-modal])]
|
||||
[sign-buttons amount-error to-address amount true #(re-frame/dispatch [:navigate-back])])
|
||||
(when signing?
|
||||
[sign-panel])]]))
|
|
@ -0,0 +1,77 @@
|
|||
(ns status-im.ui.screens.wallet.transactions.events
|
||||
(:require [status-im.utils.handlers :as handlers]
|
||||
[status-im.constants :as constants]
|
||||
[status-im.utils.money :as money]
|
||||
[status-im.native-module.core :as status]
|
||||
[re-frame.core :as re-frame]
|
||||
[status-im.utils.hex :as utils.hex]
|
||||
[clojure.string :as string]))
|
||||
|
||||
;;FX
|
||||
|
||||
(re-frame/reg-fx
|
||||
:discard-transaction
|
||||
(fn [id]
|
||||
(status/discard-transaction id)))
|
||||
|
||||
;;Helper functions
|
||||
|
||||
(defn transaction-valid? [{{:keys [to data]} :args}]
|
||||
(or (and to (utils.hex/valid-hex? to)) (and data (not= data "0x"))))
|
||||
|
||||
;;Handlers
|
||||
|
||||
;TRANSACTION QUEUED signal from status-go
|
||||
(handlers/register-handler-fx
|
||||
:transaction-queued
|
||||
[(re-frame/inject-cofx :now)]
|
||||
(fn [{{:wallet/keys [send-transaction] :as db} :db now :now} [_ {:keys [id message_id args] :as transaction}]]
|
||||
(if (transaction-valid? transaction)
|
||||
(let [{:keys [from to value data gas gasPrice]} args]
|
||||
(let [;;TODO (andrey) revisit this map later (this map from old transactions, idk if we need all these fields)
|
||||
transaction {:id id
|
||||
:from from
|
||||
:to to
|
||||
:value (money/to-decimal value)
|
||||
:data data
|
||||
:gas (money/to-decimal gas)
|
||||
:gas-price (money/to-decimal gasPrice)
|
||||
:timestamp now
|
||||
:message-id message_id}]
|
||||
(merge
|
||||
{:db (-> db
|
||||
(assoc-in [:wallet :transactions-unsigned id] transaction)
|
||||
(assoc-in [:wallet/send-transaction :transaction-id] id))}
|
||||
(if (:waiting-signal? send-transaction)
|
||||
;;sending from wallet
|
||||
{:dispatch [:wallet.send-transaction/transaction-queued id]}
|
||||
;;sending from chat
|
||||
{:dispatch [:navigate-to-modal :wallet-send-transaction-modal {:amount (str (money/wei->ether value))
|
||||
:transaction-id id
|
||||
:to-address to
|
||||
:to-name to}]}))))
|
||||
{:discard-transaction id})))
|
||||
|
||||
;TRANSACTION FAILED signal from status-go
|
||||
(handlers/register-handler-fx
|
||||
:transaction-failed
|
||||
(fn [{{:accounts/keys [accounts current-account-id] :as db} :db} [_ {:keys [id args message_id error_code error_message] :as event}]]
|
||||
(let [current-account-address (:address (get accounts current-account-id))
|
||||
transaction-initiator-address (utils.hex/normalize-hex (:from args))]
|
||||
(case error_code
|
||||
|
||||
;;WRONG PASSWORD
|
||||
constants/send-transaction-password-error-code
|
||||
{:db (assoc-in db [:wallet/send-transaction :wrong-password?] true)}
|
||||
|
||||
;;TODO (andrey) something weird here below, revisit
|
||||
;;DISCARDED
|
||||
constants/send-transaction-discarded-error-code
|
||||
{:db (update-in db [:wallet :transactions-unsigned] dissoc id)
|
||||
:dispatch [:set-chat-ui-props {:validation-messages nil}]}
|
||||
|
||||
;;NO ERROR, TIMEOUT or DEFAULT ERROR
|
||||
(merge
|
||||
{:db (update-in db [:wallet :transactions-unsigned] dissoc id)}
|
||||
(when (and message_id (= current-account-address transaction-initiator-address))
|
||||
{:dispatch [:set-chat-ui-props {:validation-messages error_message}]}))))))
|
|
@ -3,7 +3,8 @@
|
|||
[status-im.i18n :as i18n]
|
||||
[status-im.utils.datetime :as datetime]
|
||||
[status-im.utils.money :as money]
|
||||
[status-im.utils.transactions :as transactions]))
|
||||
[status-im.utils.transactions :as transactions]
|
||||
[status-im.utils.hex :as utils.hex]))
|
||||
|
||||
(reg-sub :wallet.transactions/transactions-loading?
|
||||
:<- [:wallet]
|
||||
|
@ -25,18 +26,23 @@
|
|||
(fn [transactions]
|
||||
(group-by :type (vals transactions))))
|
||||
|
||||
(defn format-unsigned-transaction [{:keys [message-id gas-price] :as transaction}]
|
||||
(defn format-unsigned-transaction [{:keys [id] :as transaction}]
|
||||
(assoc transaction
|
||||
:type :unsigned
|
||||
:confirmations 0
|
||||
:symbol "ETH"
|
||||
:hash message-id))
|
||||
;; TODO (andrey) revisit this, we shouldn't set not hash value to the hash field
|
||||
:hash id))
|
||||
|
||||
(reg-sub :wallet/unsigned-transactions
|
||||
(fn [db]
|
||||
(vals (get-in db [:wallet :transactions-unsigned]))))
|
||||
|
||||
(reg-sub :wallet.transactions/unsigned-transactions
|
||||
:<- [:transactions]
|
||||
:<- [:wallet/unsigned-transactions]
|
||||
(fn [transactions]
|
||||
(reduce (fn [acc {:keys [message-id] :as transaction}]
|
||||
(assoc acc message-id (format-unsigned-transaction transaction)))
|
||||
(reduce (fn [acc {:keys [id] :as transaction}]
|
||||
(assoc acc id (format-unsigned-transaction transaction)))
|
||||
{}
|
||||
transactions)))
|
||||
|
||||
|
@ -134,3 +140,19 @@
|
|||
(if (>= confirmations max-confirmations)
|
||||
100
|
||||
(* 100 (/ confirmations max-confirmations))))))
|
||||
|
||||
(reg-sub
|
||||
:contacts-by-address
|
||||
(fn [db]
|
||||
(into {} (map (fn [[_ {:keys [address] :as contact}]]
|
||||
(when address
|
||||
[address contact]))
|
||||
(:contacts/contacts db)))))
|
||||
|
||||
(reg-sub
|
||||
:contact-by-address
|
||||
:<- [:contacts-by-address]
|
||||
(fn [contacts [_ address]]
|
||||
(let [address' (when address
|
||||
(utils.hex/normalize-hex address))]
|
||||
(contacts address'))))
|
||||
|
|
|
@ -26,10 +26,11 @@
|
|||
(re-frame/dispatch [:accept-transactions password]))
|
||||
|
||||
(defn on-delete-transaction
|
||||
[m]
|
||||
;; TODO(yenda) implement
|
||||
(utils/show-popup "TODO" "Delete Transaction"))
|
||||
[{:keys [id]}]
|
||||
;; TODO(andrey) implement alert
|
||||
(re-frame/dispatch [:wallet/discard-unsigned-transaction id]))
|
||||
|
||||
;; TODO (andrey) implement
|
||||
(defn unsigned-action [unsigned-transactions-count]
|
||||
[toolbar/text-action {:disabled? (zero? unsigned-transactions-count)
|
||||
:handler #(re-frame/dispatch [:navigate-to-modal :wallet-transactions-sign-all])}
|
||||
|
@ -45,37 +46,24 @@
|
|||
[toolbar/content-title (i18n/label :t/transactions)]
|
||||
(case @view-id
|
||||
:wallet-transactions-unsigned
|
||||
[unsigned-action unsigned-transactions-count]
|
||||
nil ;; TODO (andrey) implement [unsigned-action unsigned-transactions-count]
|
||||
|
||||
:wallet-transactions-history
|
||||
[toolbar/actions
|
||||
[history-action]])])
|
||||
|
||||
;; Sign all
|
||||
|
||||
(defview sign-all []
|
||||
[]
|
||||
[react/keyboard-avoiding-view {:style transactions.styles/sign-all-view}
|
||||
[react/view {:style transactions.styles/sign-all-done}
|
||||
[button/primary-button {:style transactions.styles/sign-all-done-button
|
||||
:text (i18n/label :t/done)
|
||||
:on-press #(re-frame/dispatch [:navigate-back])}]]
|
||||
[react/view {:style transactions.styles/sign-all-popup}
|
||||
[react/text {:style transactions.styles/sign-all-popup-sign-phrase} "one two three"] ;; TODO hook
|
||||
[react/text {:style transactions.styles/sign-all-popup-text} (i18n/label :t/transactions-sign-all-text)]
|
||||
[react/view {:style transactions.styles/sign-all-actions}
|
||||
[react/text-input {:style transactions.styles/sign-all-input
|
||||
:secure-text-entry true
|
||||
:placeholder (i18n/label :t/transactions-sign-input-placeholder)}]
|
||||
[button/primary-button {:text (i18n/label :t/transactions-sign-all) :on-press #(println %)}]]]])
|
||||
|
||||
|
||||
(defn action-buttons [m]
|
||||
(defn action-buttons [{:keys [id to value] :as transaction}]
|
||||
[react/view {:style transactions.styles/action-buttons}
|
||||
[button/primary-button {:text (i18n/label :t/transactions-sign)
|
||||
:style {:margin-right 12}
|
||||
:on-press #(re-frame/dispatch [:navigate-to-modal :wallet-transactions-sign-all])}]
|
||||
[button/secondary-button {:text (i18n/label :t/delete) :on-press #(on-delete-transaction m)}]])
|
||||
[button/primary-button {:style {:margin-right 12}
|
||||
:on-press #(re-frame/dispatch [:navigate-to-modal
|
||||
:wallet-send-transaction-modal
|
||||
{:amount (str (money/wei->ether value))
|
||||
:transaction-id id
|
||||
:to-address to
|
||||
:to-name to}])}
|
||||
(i18n/label :t/transactions-sign)]
|
||||
[button/secondary-button {:on-press #(on-delete-transaction transaction)}
|
||||
(i18n/label :t/delete)]])
|
||||
|
||||
(defn- inbound? [type] (= "inbound" type))
|
||||
(defn- unsigned? [type] (= "unsigned" type))
|
||||
|
@ -93,7 +81,7 @@
|
|||
(:postponed :pending) (transaction-icon :icons/arrow-right styles/color-gray4-transparent styles/color-gray7)
|
||||
(throw (str "Unknown transaction type: " k))))
|
||||
|
||||
(defn render-transaction [{:keys [hash to from type value symbol] :as m}]
|
||||
(defn render-transaction [{:keys [hash to from type value symbol] :as transaction}]
|
||||
[list/touchable-item #(re-frame/dispatch [:show-transaction-details hash])
|
||||
[react/view
|
||||
[list/item
|
||||
|
@ -104,7 +92,7 @@
|
|||
(str (i18n/label :t/from) " " from)
|
||||
(str (i18n/label :t/to) " " to))
|
||||
(when (unsigned? type)
|
||||
[action-buttons m])]
|
||||
[action-buttons transaction])]
|
||||
[list/item-icon {:icon :icons/forward
|
||||
:style {:margin-top 10}
|
||||
:icon-opts transactions.styles/forward}]]]])
|
||||
|
@ -259,14 +247,14 @@
|
|||
{:text (i18n/label :t/open-on-etherscan) :value #(.openURL react/linking url)}])])
|
||||
|
||||
(defview transaction-details []
|
||||
(letsubs [{:keys [hash url type] :as transactions} [:wallet.transactions/details]
|
||||
(letsubs [{:keys [hash url type] :as transactions} [:wallet.transactions/transaction-details]
|
||||
confirmations [:wallet.transactions.details/confirmations]
|
||||
confirmations-progress [:wallet.transactions.details/confirmations-progress]]
|
||||
[react/view {:style styles/flex}
|
||||
[status-bar/status-bar]
|
||||
[toolbar/toolbar2 {}
|
||||
toolbar/default-nav-back
|
||||
[toolbar/content-title (i18n/label :t/details)]
|
||||
[toolbar/content-title (i18n/label :t/transaction-details)]
|
||||
[toolbar/actions (details-action hash url)]]
|
||||
[react/scroll-view {:style transactions.styles/main-section}
|
||||
[details-header transactions]
|
||||
|
|
|
@ -26,6 +26,9 @@
|
|||
(defn to-wei [str]
|
||||
(dependencies/Web3.prototype.toWei str "ether"))
|
||||
|
||||
(defn to-decimal [value]
|
||||
(dependencies/Web3.prototype.toDecimal value))
|
||||
|
||||
(def eth-units
|
||||
{:wei (bignumber "1")
|
||||
:kwei (bignumber "1000")
|
||||
|
|
Loading…
Reference in New Issue