diff --git a/.env.jenkins b/.env.jenkins index 175debf567..367b5f01ca 100644 --- a/.env.jenkins +++ b/.env.jenkins @@ -1 +1,2 @@ TESTFAIRY_ENABLED=1 +WALLET_TAB_ENABLED=1 \ No newline at end of file diff --git a/src/status_im/ui/screens/db.cljs b/src/status_im/ui/screens/db.cljs index 8bf90d8e01..cfd1160a89 100644 --- a/src/status_im/ui/screens/db.cljs +++ b/src/status_im/ui/screens/db.cljs @@ -36,6 +36,8 @@ :discover-search-tags '() :tags [] :sync-state :done + :wallet {} + :prices {} :network "testnet"}) ;;;;GLOBAL @@ -172,4 +174,6 @@ :discoveries/tags :discoveries/current-tag :discoveries/request-discoveries-timer - :discoveries/new-discover])) + :discoveries/new-discover + :wallet/wallet + :prices/prices])) diff --git a/src/status_im/ui/screens/events.cljs b/src/status_im/ui/screens/events.cljs index 57120ab4aa..275694e4b3 100644 --- a/src/status_im/ui/screens/events.cljs +++ b/src/status_im/ui/screens/events.cljs @@ -15,7 +15,7 @@ status-im.ui.screens.navigation status-im.ui.screens.profile.events status-im.ui.screens.qr-scanner.events - + status-im.ui.screens.wallet.events [re-frame.core :refer [dispatch reg-fx]] [status-im.components.status :as status] [status-im.components.permissions :as permissions] @@ -163,7 +163,8 @@ [:send-account-update-if-needed] [:start-requesting-discoveries] [:remove-old-discoveries!] - [:set :accounts/creating-account? false]]})) + [:set :accounts/creating-account? false] + [:init-wallet]]})) (register-handler-fx :check-console-chat diff --git a/src/status_im/ui/screens/wallet/db.cljs b/src/status_im/ui/screens/wallet/db.cljs new file mode 100644 index 0000000000..1beea72b41 --- /dev/null +++ b/src/status_im/ui/screens/wallet/db.cljs @@ -0,0 +1,7 @@ +(ns status-im.ui.screens.wallet.db) + +;; Placeholder namespace for wallet specs, which are a WIP depending on data +;; model we decide on for balances, prices, etc. + +;; TODO(oskarth): spec for balance as BigNumber +;; TODO(oskarth): Spec for prices as as: {:from ETH, :to USD, :price 290.11, :last-day 304.17} diff --git a/src/status_im/ui/screens/wallet/events.cljs b/src/status_im/ui/screens/wallet/events.cljs new file mode 100644 index 0000000000..16dd888910 --- /dev/null +++ b/src/status_im/ui/screens/wallet/events.cljs @@ -0,0 +1,75 @@ +(ns status-im.ui.screens.wallet.events + (:require [re-frame.core :as re-frame :refer [dispatch reg-fx]] + [status-im.utils.handlers :as handlers] + [status-im.utils.prices :as prices])) + +(defn get-balance [{:keys [web3 account-id on-success on-error]}] + (if (and web3 account-id) + (.getBalance + (.-eth web3) + account-id + (fn [err resp] + (if-not err + (on-success resp) + (on-error err)))) + (on-error "web3 or account-id not available"))) + +;; FX + +(reg-fx + :get-balance + (fn [{:keys [web3 account-id success-event error-event]}] + (get-balance + {:web3 web3 + :account-id account-id + :on-success #(dispatch [success-event %]) + :on-error #(dispatch [error-event %])}))) + +(reg-fx + :get-prices + (fn [{:keys [from to success-event error-event]}] + (prices/get-prices + from + to + #(dispatch [success-event %]) + #(dispatch [error-event %])))) + +;; Handlers + +;; TODO(oskarth): At some point we want to get list of relevant assets to get prices for +(handlers/register-handler-fx + :load-prices + (fn [{{:keys [wallet] :as db} :db} [_ a]] + {:get-prices {:from "ETH" + :to "USD" + :success-event :update-prices + :error-event :update-prices-fail}})) + +(handlers/register-handler-fx + :init-wallet + (fn [{{:keys [web3 accounts/current-account-id] :as db} :db} [_ a]] + {:get-balance {:web3 web3 + :account-id current-account-id + :success-event :update-balance + :error-event :update-balance-fail} + :dispatch [:load-prices]})) + +(handlers/register-handler-db + :update-balance + (fn [db [_ balance]] + (assoc db :wallet {:balance balance}))) + +(handlers/register-handler-db + :update-prices + (fn [db [_ prices]] + (assoc db :prices prices))) + +(handlers/register-handler-fx + :update-balance-fail + (fn [_ [_ err]] + (.log js/console "Unable to get balance: " err))) + +(handlers/register-handler-fx + :update-prices-fail + (fn [_ [_ err]] + (.log js/console "Unable to get prices: " err))) diff --git a/src/status_im/ui/screens/wallet/main/views.cljs b/src/status_im/ui/screens/wallet/main/views.cljs index 0f2488ce17..7b91a5abb2 100644 --- a/src/status_im/ui/screens/wallet/main/views.cljs +++ b/src/status_im/ui/screens/wallet/main/views.cljs @@ -1,5 +1,5 @@ (ns status-im.ui.screens.wallet.main.views - (:require-macros [status-im.utils.views :refer [defview]]) + (:require-macros [status-im.utils.views :refer [defview letsubs]]) (:require [clojure.string :as string] [re-frame.core :as rf] [status-im.components.common.common :as common] @@ -11,7 +11,8 @@ [status-im.i18n :as i18n] [status-im.utils.listview :as lw] [status-im.utils.platform :as platform] - [status-im.ui.screens.wallet.main.styles :as st])) + [status-im.ui.screens.wallet.main.styles :as st] + [status-im.utils.money :as money])) (defn toolbar-title [] [rn/view {:style st/toolbar-title-container} @@ -31,16 +32,17 @@ :custom-content [toolbar-title] :custom-action [toolbar-buttons]}]) -(defn main-section [] +;; TODO(oskarth): Whatever triggers the "in progress" animation should also trigger wallet-init or load-prices event. +(defn main-section [usd-value change] [rn/view {:style st/main-section} [rn/view {:style st/total-balance-container} [rn/view {:style st/total-balance} - [rn/text {:style st/total-balance-value} "12.43"] + [rn/text {:style st/total-balance-value} usd-value] [rn/text {:style st/total-balance-currency} "USD"]] [rn/view {:style st/value-variation} [rn/text {:style st/value-variation-title} "Total value"] [rn/view {:style st/today-variation-container} - [rn/text {:style st/today-variation} "+5.43%"]]] + [rn/text {:style st/today-variation} change]]] [btn/buttons st/buttons [{:text "Send" :on-press #(rf/dispatch [:navigate-to :wallet-send-transaction])} @@ -72,20 +74,46 @@ [rn/view [asset-list-item row]]])) -(defn asset-section [] - (let [assets {"eth" {:currency :eth :amount 0.445} - "snt" {:currency :snt :amount 1} - "gno" {:currency :gno :amount 0.024794}}] +(def assets-example-map + {"eth" {:currency :eth :amount 0.445} + "snt" {:currency :snt :amount 1} + "gno" {:currency :gno :amount 0.024794}}) + +;; NOTE(oskarth): In development, replace assets with assets-example-map +;; to check multiple assets being rendered +(defn asset-section [eth] + (let [assets {"eth" {:currency :eth :amount eth}}] [rn/view {:style st/asset-section} [rn/text {:style st/asset-section-title} "Assets"] [rn/list-view {:dataSource (lw/to-datasource assets) :renderSeparator (when platform/ios? (render-separator-fn (count assets))) :renderRow render-row-fn}]])) +(defn eth-balance [{:keys [balance]}] + (when balance + (money/wei->ether balance))) + +(defn portfolio-value [{:keys [balance]} {:keys [price]}] + (when (and balance price) + (-> (money/wei->ether balance) + (money/eth->usd price) + (money/with-precision 2) + str))) + +(defn portfolio-change [{:keys [price last-day]}] + (when (and price last-day) + (-> (money/percent-change price last-day) + (money/with-precision 2) + (str "%")))) + (defview wallet [] - [] - [rn/view {:style st/wallet-container} - [toolbar-view] - [rn/scroll-view - [main-section] - [asset-section]]]) + (letsubs [wallet [:get :wallet] + prices [:get :prices]] + (let [eth (or (eth-balance wallet) "...") + usd (or (portfolio-value wallet prices) "...") + change (or (portfolio-change prices) "-%")] + [rn/view {:style st/wallet-container} + [toolbar-view] + [rn/scroll-view + [main-section usd change] + [asset-section eth]]]))) diff --git a/src/status_im/utils/money.cljs b/src/status_im/utils/money.cljs index 9ccff7e643..71845ea9e7 100644 --- a/src/status_im/utils/money.cljs +++ b/src/status_im/utils/money.cljs @@ -29,3 +29,14 @@ (defn fee-value [gas gas-price] (.times (bignumber gas) (bignumber gas-price))) + +(defn eth->usd [eth usd-price] + (.times (bignumber eth) (bignumber usd-price))) + +(defn percent-change [from to] + (-> (.dividedBy (bignumber from) (bignumber to)) + (.minus 1) + (.times 100))) + +(defn with-precision [n decimals] + (.round (bignumber n) decimals)) diff --git a/src/status_im/utils/prices.cljs b/src/status_im/utils/prices.cljs new file mode 100644 index 0000000000..03593d19ec --- /dev/null +++ b/src/status_im/utils/prices.cljs @@ -0,0 +1,30 @@ +(ns status-im.utils.prices + (:require [status-im.utils.utils :as utils] + [status-im.utils.types :as types])) + +;; Responsible for interacting with Cryptocompare API to get current prices for +;; currencies and tokens. +;; +;; No tests since fetch API (via http-get) relies on `window` being available. +;; +;; Example usage: +;; (get-prices "ETH" "USD" println print) + +(def api-url "https://min-api.cryptocompare.com/data") + +(defn- gen-price-url [fsyms tsyms] + (str api-url "/pricemultifull?fsyms=" fsyms "&tsyms=" tsyms)) + +(defn- format-price-resp [from to resp] + (let [raw (:RAW (types/json->clj resp)) + entry (get-in raw [(keyword from) (keyword to)])] + {:from from + :to to + :price (:PRICE entry) + :last-day (:OPEN24HOUR entry)})) + +(defn get-prices [from to on-success on-error] + (utils/http-get + (gen-price-url from to) + (fn [resp] (on-success (format-price-resp from to resp))) + on-error))