diff --git a/src/status_im2/contexts/profile/login/events.cljs b/src/status_im2/contexts/profile/login/events.cljs index d65470eb71..a4b0158da8 100644 --- a/src/status_im2/contexts/profile/login/events.cljs +++ b/src/status_im2/contexts/profile/login/events.cljs @@ -76,6 +76,7 @@ {:db (assoc db :chats/loading? true :networks/current-network current-network + :wallet/tokens-loading? true :networks/networks (merge networks config/default-networks-by-id) :profile/profile (merge profile settings))} (notifications/load-preferences) diff --git a/src/status_im2/contexts/wallet/account/style.cljs b/src/status_im2/contexts/wallet/account/style.cljs index d7e4993f4c..fd44270f1f 100644 --- a/src/status_im2/contexts/wallet/account/style.cljs +++ b/src/status_im2/contexts/wallet/account/style.cljs @@ -1,5 +1,8 @@ (ns status-im2.contexts.wallet.account.style) +(def container + {:flex 1}) + (def tabs {:padding-left 20 :padding-vertical 12}) diff --git a/src/status_im2/contexts/wallet/account/view.cljs b/src/status_im2/contexts/wallet/account/view.cljs index a45bc9492f..725273fa06 100644 --- a/src/status_im2/contexts/wallet/account/view.cljs +++ b/src/status_im2/contexts/wallet/account/view.cljs @@ -2,11 +2,11 @@ (:require [quo.core :as quo] [react-native.core :as rn] - [react-native.safe-area :as safe-area] [reagent.core :as reagent] [status-im2.contexts.wallet.account.style :as style] [status-im2.contexts.wallet.account.tabs.view :as tabs] [status-im2.contexts.wallet.common.temp :as temp] + [status-im2.contexts.wallet.common.utils :as utils] [utils.i18n :as i18n] [utils.re-frame :as rf])) @@ -57,14 +57,13 @@ {:id :about :label (i18n/label :t/about) :accessibility-label :about}]) (defn view - [] - (let [top (safe-area/get-top) - selected-tab (reagent/atom (:id (first tabs-data)))] + [account-address] + (let [selected-tab (reagent/atom (:id (first tabs-data)))] (fn [] - (let [networks (rf/sub [:wallet/network-details])] - [rn/view - {:style {:flex 1 - :margin-top top}} + (let [account-address (or account-address (rf/sub [:get-screen-params :wallet-accounts])) + account (rf/sub [:wallet/account account-address]) + networks (rf/sub [:wallet/network-details])] + [rn/view {:style style/container} [quo/page-nav {:type :wallet-networks :background :blur @@ -79,7 +78,11 @@ :gradient-cover? true :customization-color :purple}]) :emoji "🍑"}}] - [quo/account-overview temp/account-overview-state] + [quo/account-overview + {:current-value (utils/prettify-balance (:balance account)) + :account-name (:name account) + :account :default + :customization-color :blue}] [quo/wallet-graph {:time-frame :empty}] [quo/wallet-ctas {:send-action #(rf/dispatch [:open-modal :wallet-select-address]) diff --git a/src/status_im2/contexts/wallet/common/utils.cljs b/src/status_im2/contexts/wallet/common/utils.cljs index 5d5e20b2b2..a3e97a057c 100644 --- a/src/status_im2/contexts/wallet/common/utils.cljs +++ b/src/status_im2/contexts/wallet/common/utils.cljs @@ -6,6 +6,21 @@ [full-name] (first (string/split full-name #" "))) +(defn get-balance-by-address + [balances address] + (->> balances + (filter #(= (:address %) address)) + first + :balance)) + +(defn get-account-by-address + [accounts address] + (some #(when (= (:address %) address) %) accounts)) + +(defn prettify-balance + [balance] + (str "$" (.toFixed (if (number? balance) balance 0) 2))) + (defn get-derivation-path [number-of-accounts] (str constants/path-wallet-root "/" number-of-accounts)) diff --git a/src/status_im2/contexts/wallet/events.cljs b/src/status_im2/contexts/wallet/events.cljs index a73a6086d6..2209f5c553 100644 --- a/src/status_im2/contexts/wallet/events.cljs +++ b/src/status_im2/contexts/wallet/events.cljs @@ -6,6 +6,26 @@ [utils.re-frame :as rf] [utils.security.core :as security])) +(rf/defn get-wallet-token + {:events [:wallet/get-wallet-token]} + [{:keys [db]}] + (let [params (map :address (:profile/wallet-accounts db))] + {:json-rpc/call [{:method "wallet_getWalletToken" + :params [params] + :on-success #(rf/dispatch [:wallet/get-wallet-token-success %]) + :on-error (fn [error] + (log/info "failed to get wallet token" + {:event :wallet/get-wallet-token + :error error + :params params}))}]})) + +(rf/defn get-wallet-token-success + {:events [:wallet/get-wallet-token-success]} + [{:keys [db]} data] + {:db (assoc db + :wallet/tokens data + :wallet/tokens-loading? false)}) + (rf/defn scan-address-success {:events [:wallet/scan-address-success]} [{:keys [db]} address] diff --git a/src/status_im2/contexts/wallet/home/view.cljs b/src/status_im2/contexts/wallet/home/view.cljs index f9e4bbcb1d..09d968bc03 100644 --- a/src/status_im2/contexts/wallet/home/view.cljs +++ b/src/status_im2/contexts/wallet/home/view.cljs @@ -8,7 +8,7 @@ [status-im2.contexts.wallet.common.activity-tab.view :as activity] [status-im2.contexts.wallet.common.collectibles-tab.view :as collectibles] [status-im2.contexts.wallet.common.temp :as temp] - [status-im2.contexts.wallet.common.token-value.view :as token-value] + [status-im2.contexts.wallet.common.utils :as utils] [status-im2.contexts.wallet.home.style :as style] [utils.i18n :as i18n] [utils.re-frame :as rf])) @@ -28,58 +28,72 @@ :on-press #(rf/dispatch [:navigate-to :wallet-address-watch]) :add-divider? true}]]]) -(def account-cards - [{:name "Account 1" - :balance "€0.00" - :percentage-value "€0.00" - :customization-color :blue - :type :empty - :emoji "🍑" - :on-press #(rf/dispatch [:navigate-to :wallet-accounts])} - {:customization-color :blue - :on-press #(rf/dispatch - [:show-bottom-sheet {:content new-account}]) - :type :add-account}]) +(defn- add-account-placeholder + [color] + {:customization-color color + :on-press #(rf/dispatch [:show-bottom-sheet {:content new-account}]) + :type :add-account}) (def tabs-data [{:id :assets :label (i18n/label :t/assets) :accessibility-label :assets-tab} {:id :collectibles :label (i18n/label :t/collectibles) :accessibility-label :collectibles-tab} {:id :activity :label (i18n/label :t/activity) :accessibility-label :activity-tab}]) +(defn account-cards + [{:keys [accounts loading? balances profile]}] + (let [accounts-with-balances + (mapv + (fn [account] + (assoc account + :type :empty + :customization-color (:customization-color profile) + :on-press #(rf/dispatch [:navigate-to :wallet-accounts (:address account)]) + :loading? loading? + :balance (utils/prettify-balance + (utils/get-balance-by-address balances (:address account))))) + accounts)] + (conj accounts-with-balances (add-account-placeholder (:customization-color profile))))) + (defn view [] - (let [top (safe-area/get-top) - selected-tab (reagent/atom (:id (first tabs-data)))] - (fn [] - (let [networks (rf/sub [:wallet/network-details])] - [rn/view - {:style {:margin-top top - :flex 1}} - [common.top-nav/view] - [rn/view {:style style/overview-container} - [quo/wallet-overview (temp/wallet-overview-state networks)]] - [rn/pressable - {:on-long-press #(rf/dispatch [:show-bottom-sheet - {:content temp/wallet-temporary-navigation}])} - [quo/wallet-graph {:time-frame :empty}]] - [rn/view {:style style/accounts-container} - [rn/flat-list - {:style style/accounts-list - :data account-cards - :horizontal true - :separator [rn/view {:style {:width 12}}] - :render-fn quo/account-card}]] - [quo/tabs - {:style style/tabs - :size 32 - :default-active @selected-tab - :data tabs-data - :on-change #(reset! selected-tab %)}] - (case @selected-tab - :assets [rn/flat-list - {:render-fn token-value/view - :data temp/tokens - :key :assets-list - :content-container-style {:padding-horizontal 8}}] - :collectibles [collectibles/view] - [activity/view])])))) + (rf/dispatch [:wallet/get-wallet-token]) + (fn [] + (let [accounts (rf/sub [:profile/wallet-accounts]) + top (safe-area/get-top) + selected-tab (reagent/atom (:id (first tabs-data))) + loading? (rf/sub [:wallet/tokens-loading?]) + balances (rf/sub [:wallet/balances]) + profile (rf/sub [:profile/profile]) + networks (rf/sub [:wallet/network-details])] + [rn/view + {:style {:margin-top top + :flex 1}} + [common.top-nav/view] + [rn/view {:style style/overview-container} + [quo/wallet-overview (temp/wallet-overview-state networks)]] + [rn/pressable + {:on-long-press #(rf/dispatch [:show-bottom-sheet {:content temp/wallet-temporary-navigation}])} + [quo/wallet-graph {:time-frame :empty}]] + [rn/flat-list + {:style style/accounts-list + :data (account-cards {:accounts accounts + :loading? loading? + :balances balances + :profile profile}) + :horizontal true + :separator [rn/view {:style {:width 12}}] + :render-fn quo/account-card}] + [quo/tabs + {:style style/tabs + :size 32 + :default-active @selected-tab + :data tabs-data + :on-change #(reset! selected-tab %)}] + (case @selected-tab + :assets [rn/flat-list + {:render-fn quo/token-value + :data temp/tokens + :key :assets-list + :content-container-style {:padding-horizontal 8}}] + :collectibles [collectibles/view] + [activity/view])]))) diff --git a/src/status_im2/navigation/screens.cljs b/src/status_im2/navigation/screens.cljs index dd41448132..91a929b247 100644 --- a/src/status_im2/navigation/screens.cljs +++ b/src/status_im2/navigation/screens.cljs @@ -243,6 +243,7 @@ :component emoji-picker/view} {:name :wallet-accounts + :options {:insets {:top? true}} :component wallet-accounts/view} {:name :wallet-edit-account diff --git a/src/status_im2/subs/root.cljs b/src/status_im2/subs/root.cljs index b0bf402a54..8575967ca3 100644 --- a/src/status_im2/subs/root.cljs +++ b/src/status_im2/subs/root.cljs @@ -11,7 +11,8 @@ status-im2.subs.pairing status-im2.subs.profile status-im2.subs.shell - status-im2.subs.wallet.networks)) + status-im2.subs.wallet.networks + status-im2.subs.wallet.wallet)) (defn reg-root-key-sub [sub-name db-key] @@ -142,6 +143,10 @@ (reg-root-key-sub :communities/selected-tab :communities/selected-tab) (reg-root-key-sub :contract-communities :contract-communities) +;;wallet +(reg-root-key-sub :wallet/tokens :wallet/tokens) +(reg-root-key-sub :wallet/tokens-loading? :wallet/tokens-loading?) + ;;activity center (reg-root-key-sub :activity-center :activity-center) diff --git a/src/status_im2/subs/wallet/wallet.cljs b/src/status_im2/subs/wallet/wallet.cljs new file mode 100644 index 0000000000..dd7cf97c24 --- /dev/null +++ b/src/status_im2/subs/wallet/wallet.cljs @@ -0,0 +1,51 @@ +(ns status-im2.subs.wallet.wallet + (:require [clojure.string :as string] + [re-frame.core :as re-frame] + [status-im2.contexts.wallet.common.utils :as utils] + [utils.number])) + +(defn- calculate-raw-balance + [raw-balance decimals] + (if-let [n (utils.number/parse-int raw-balance nil)] + (/ n (Math/pow 10 (utils.number/parse-int decimals))) + 0)) + +(defn- total-per-token + [item] + (reduce (fn [ac balances] + (+ (calculate-raw-balance (:rawBalance balances) + (:decimals item)) + ac)) + 0 + (vals (:balancesPerChain item)))) + +(defn- calculate-balance + [address tokens] + (let [token (get tokens (keyword (string/lower-case address))) + result (reduce + (fn [acc item] + (let [total-values (* (total-per-token item) + (get-in item [:marketValuesPerCurrency :USD :price]))] + (+ acc total-values))) + 0 + token)] + result)) + +(re-frame/reg-sub + :wallet/balances + :<- [:profile/wallet-accounts] + :<- [:wallet/tokens] + (fn [[accounts tokens]] + (for [{:keys [address]} accounts] + {:address address + :balance (calculate-balance address tokens)}))) + +(re-frame/reg-sub + :wallet/account + :<- [:profile/wallet-accounts] + :<- [:wallet/balances] + (fn [[accounts balances] [_ account-address]] + (assoc + (utils/get-account-by-address accounts account-address) + :balance + (utils/get-balance-by-address balances account-address)))) diff --git a/src/status_im2/subs/wallet/wallet_test.cljs b/src/status_im2/subs/wallet/wallet_test.cljs new file mode 100644 index 0000000000..96442ff169 --- /dev/null +++ b/src/status_im2/subs/wallet/wallet_test.cljs @@ -0,0 +1,83 @@ +(ns status-im2.subs.wallet.wallet-test + (:require [cljs.test :refer [is testing]] + [re-frame.db :as rf-db] + status-im2.subs.root + [test-helpers.unit :as h] + [utils.re-frame :as rf])) + +(def tokens + {:0x1 [{:decimals 1 + :symbol "ETH" + :name "Ether" + :balancesPerChain {:1 {:rawBalance "20" + :hasError false} + :2 {:rawBalance "10" + :hasError false}} + :marketValuesPerCurrency {:USD {:price 1000}}} ;; total should be 3000 + {:decimals 2 + :symbol "DAI" + :name "Dai Stablecoin" + :balancesPerChain {:1 {:rawBalance "100" + :hasError false} + :2 {:rawBalance "150" + :hasError false}} + :marketValuesPerCurrency {:USD {:price 100}}}] ;; total should be 250 + :0x2 [{:decimals 3 + :symbol "ETH" + :name "Ether" + :balancesPerChain {:1 {:rawBalance "2500" + :hasError false} + :2 {:rawBalance "3000" + :hasError false} + :3 {:rawBalance "" + :hasError false}} + :marketValuesPerCurrency {:USD {:price 200}}} ;; total should be 1100 + {:decimals 10 + :symbol "DAI" + :name "Dai Stablecoin" + :balancesPerChain {:1 {:rawBalance "10000000000" + :hasError false} + :2 {:rawBalance "0" + :hasError false} + :3 {:rawBalance "" + :hasError false}} + :marketValuesPerCurrency {:USD {:price 1000}}}]}) ;; total should be 1000 + +(def accounts + [{:address "0x1" + :name "Main account" + :hidden false + :removed false} + {:address "0x2" + :name "Secondary account" + :hidden false + :removed false}]) + +(h/deftest-sub :wallet/balances + [sub-name] + (testing "returns vector of maps containing :address and :balance" + (swap! rf-db/app-db assoc + :profile/wallet-accounts accounts + :wallet/tokens tokens) + (is (= [{:address "0x1" + :balance 3250} + {:address "0x2" + :balance 2100}] + (rf/sub [sub-name]))))) + +(h/deftest-sub :wallet/account + [sub-name] + (testing "returns current account with balance base on the account-address" + (swap! rf-db/app-db assoc + :profile/wallet-accounts accounts + :wallet/tokens tokens + :wallet/balances [{:address "0x1" + :balance 3250} + {:address "0x2" + :balance 2100}]) + (is (= {:address "0x1" + :name "Main account" + :hidden false + :removed false + :balance 3250} + (rf/sub [sub-name "0x1"])))))