From a00e44d0569c68f5eb7557fbc3f44ce1233c54ce Mon Sep 17 00:00:00 2001 From: Mohamed Javid <19339952+smohamedjavid@users.noreply.github.com> Date: Sat, 6 Jan 2024 00:36:54 +0530 Subject: [PATCH] Show aggregated tokens and balance in Wallet home (#18275) This commit adds the feature to show aggregated tokens and balances in the Wallet home. --------- Signed-off-by: Mohamed Javid <19339952+smohamedjavid@users.noreply.github.com> --- .../contexts/wallet/account/view.cljs | 7 +- .../contexts/wallet/common/temp.cljs | 29 ------- .../contexts/wallet/common/utils.cljs | 76 +++++++++++++++---- .../contexts/wallet/common/utils_test.cljs | 17 ----- src/status_im/contexts/wallet/data_store.cljs | 16 ++++ src/status_im/contexts/wallet/events.cljs | 14 +--- .../wallet/home/tabs/assets/view.cljs | 7 +- src/status_im/contexts/wallet/home/view.cljs | 11 +-- src/status_im/subs/wallet/wallet.cljs | 65 +++++++++------- src/status_im/subs/wallet/wallet_test.cljs | 23 +++++- 10 files changed, 146 insertions(+), 119 deletions(-) diff --git a/src/status_im/contexts/wallet/account/view.cljs b/src/status_im/contexts/wallet/account/view.cljs index 5f4dc180a8..b5faf353cf 100644 --- a/src/status_im/contexts/wallet/account/view.cljs +++ b/src/status_im/contexts/wallet/account/view.cljs @@ -7,7 +7,6 @@ [status-im.contexts.wallet.account.tabs.view :as tabs] [status-im.contexts.wallet.common.account-switcher.view :as account-switcher] [status-im.contexts.wallet.common.temp :as temp] - [status-im.contexts.wallet.common.utils :as utils] [utils.i18n :as i18n] [utils.re-frame :as rf])) @@ -35,12 +34,12 @@ [] (let [selected-tab (reagent/atom first-tab-id)] (fn [] - (let [{:keys [name color balance watch-only?]} (rf/sub [:wallet/current-viewing-account]) - currency-symbol (rf/sub [:profile/currency-symbol])] + (let [{:keys [name color formatted-balance + watch-only?]} (rf/sub [:wallet/current-viewing-account])] [rn/view {:style {:flex 1}} [account-switcher/view {:on-press #(rf/dispatch [:wallet/close-account-page])}] [quo/account-overview - {:current-value (utils/prettify-balance currency-symbol balance) + {:current-value formatted-balance :account-name name :account (if watch-only? :watched-address :default) :customization-color color}] diff --git a/src/status_im/contexts/wallet/common/temp.cljs b/src/status_im/contexts/wallet/common/temp.cljs index 424688d7bd..06ba3d1ce4 100644 --- a/src/status_im/contexts/wallet/common/temp.cljs +++ b/src/status_im/contexts/wallet/common/temp.cljs @@ -4,35 +4,6 @@ [status-im.common.resources :as status.resources] [utils.i18n :as i18n])) -(def tokens - [{:token :snt - :token-name "Status" - :state :default - :status :empty - :customization-color :blue - :values {:crypto-value "0.00" - :fiat-value "€0.00" - :percentage-change "0.00" - :fiat-change "€0.00"}} - {:token :eth - :token-name "Ether" - :state :default - :status :empty - :customization-color :blue - :values {:crypto-value "0.00" - :fiat-value "€0.00" - :percentage-change "0.00" - :fiat-change "€0.00"}} - {:token :dai - :token-name "Dai Stablecoin" - :state :default - :status :empty - :customization-color :blue - :values {:crypto-value "0.00" - :fiat-value "€0.00" - :percentage-change "0.00" - :fiat-change "€0.00"}}]) - (def address "0x39cf6E0Ba4C4530735616e1Ee7ff5FbCB726fBd4") (def buy-tokens-list diff --git a/src/status_im/contexts/wallet/common/utils.cljs b/src/status_im/contexts/wallet/common/utils.cljs index 44a12d42d4..c7005537bf 100644 --- a/src/status_im/contexts/wallet/common/utils.cljs +++ b/src/status_im/contexts/wallet/common/utils.cljs @@ -87,18 +87,6 @@ [accounts address] (some #(when (= (:address %) address) %) accounts)) -(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 token-value-in-chain - [{:keys [balances-per-chain decimals]} chain-id] - (let [balance-in-chain (get balances-per-chain chain-id)] - (when balance-in-chain - (calculate-raw-balance (:raw-balance balance-in-chain) decimals)))) - (defn total-token-fiat-value "Returns the total token fiat value taking into account all token's chains." [currency {:keys [market-values-per-currency] :as token}] @@ -127,6 +115,41 @@ (map #(calculate-balance-for-token %)) (reduce +))) +(defn calculate-balance-from-tokens + [{:keys [currency tokens]}] + (->> tokens + (map #(total-token-fiat-value currency %)) + (reduce money/add))) + +(defn- add-balances-per-chain + [b1 b2] + {:raw-balance (money/add (:raw-balance b1) (:raw-balance b2)) + :chain-id (:chain-id b2)}) + +(defn- merge-token + [existing-token token] + (assoc token + :balances-per-chain + (merge-with add-balances-per-chain + (:balances-per-chain existing-token) + (:balances-per-chain token)))) + +(defn aggregate-tokens-for-all-accounts + "Receives accounts (seq) and returns aggregated tokens in all accounts + NOTE: We use double reduce for faster performance (faster than mapcat and flatten)" + [accounts] + (->> accounts + (map :tokens) + (reduce + (fn [result-map tokens-per-account] + (reduce + (fn [acc token] + (update acc (:symbol token) merge-token token)) + result-map + tokens-per-account)) + {}) + vals)) + (defn network-list [{:keys [balances-per-chain]} networks] (into #{} @@ -136,10 +159,6 @@ networks))) (keys balances-per-chain)))) -(defn calculate-fiat-change - [fiat-value change-pct-24hour] - (money/bignumber (* fiat-value (/ change-pct-24hour (+ 100 change-pct-24hour))))) - (defn get-wallet-qr [{:keys [wallet-type selected-networks address]}] (if (= wallet-type :wallet-multichain) @@ -153,3 +172,28 @@ {constants/mainnet-chain-id :ethereum constants/optimism-chain-id :optimism constants/arbitrum-chain-id :arbitrum}) + +(defn calculate-token-value + "This function returns token values in the props of token-value (quo) component" + [{:keys [token color currency currency-symbol]}] + (let [token-units (total-token-units-in-all-chains token) + fiat-value (total-token-fiat-value currency token) + market-values (or (get-in token [:market-values-per-currency currency]) + (get-in token + [:market-values-per-currency + constants/profile-default-currency])) + {:keys [change-pct-24hour]} market-values + crypto-value (get-standard-crypto-format token token-units) + fiat-value (if (string/includes? crypto-value "<") + "<$0.01" + (prettify-balance currency-symbol fiat-value))] + {:token (:symbol token) + :token-name (:name token) + :state :default + :status (cond + (pos? change-pct-24hour) :positive + (neg? change-pct-24hour) :negative + :else :empty) + :customization-color color + :values {:crypto-value crypto-value + :fiat-value fiat-value}})) diff --git a/src/status_im/contexts/wallet/common/utils_test.cljs b/src/status_im/contexts/wallet/common/utils_test.cljs index 6fa47f3c66..8eac92a613 100644 --- a/src/status_im/contexts/wallet/common/utils_test.cljs +++ b/src/status_im/contexts/wallet/common/utils_test.cljs @@ -3,7 +3,6 @@ [status-im.contexts.wallet.common.utils :as utils] [utils.money :as money])) - (deftest test-get-first-name (testing "get-first-name function" (is (= (utils/get-first-name "John Doe") "John")) @@ -93,22 +92,6 @@ address-to-find "0x999"] (is (= (utils/get-account-by-address accounts address-to-find) nil))))) -(deftest test-calculate-raw-balance - (testing "calculate-raw-balance function" - (is (= (utils/calculate-raw-balance "100000000" "8") 1.0)) - (is (= (utils/calculate-raw-balance "50000000" "8") 0.5)) - (is (= (utils/calculate-raw-balance "123456789" "2") 1234567.89)) - (is (= (utils/calculate-raw-balance "0" "4") 0.0)))) - -(deftest test-token-value-in-chain - (testing "token-value-in-chain function" - (let [token {:balances-per-chain {1 {:raw-balance (money/bignumber 100000000)} - 2 {:raw-balance (money/bignumber 50000000)} - 3 {:raw-balance (money/bignumber 123456789)}} - :decimals 8}] - (is (= (utils/token-value-in-chain token 1) 1.0))))) - - (deftest test-get-wallet-qr (testing "Test get-wallet-qr function" (let [wallet-multichain {:wallet-type :wallet-multichain diff --git a/src/status_im/contexts/wallet/data_store.cljs b/src/status_im/contexts/wallet/data_store.cljs index 6ff91148ba..e027eca043 100644 --- a/src/status_im/contexts/wallet/data_store.cljs +++ b/src/status_im/contexts/wallet/data_store.cljs @@ -1,8 +1,11 @@ (ns status-im.contexts.wallet.data-store (:require + [camel-snake-kebab.core :as csk] + [camel-snake-kebab.extras :as cske] [clojure.set :as set] [clojure.string :as string] [status-im.constants :as constants] + [utils.money :as money] [utils.number :as utils.number])) (defn chain-ids-string->set @@ -48,6 +51,19 @@ (update :testPreferredChainIds chain-ids-set->string) (dissoc :watch-only?))) +(defn- rpc->balances-per-chain + [token] + (-> token + (update :balances-per-chain update-vals #(update % :raw-balance money/bignumber)) + (update :balances-per-chain update-keys (comp utils.number/parse-int name)))) + +(defn rpc->tokens + [tokens] + (-> tokens + (update-keys name) + (update-vals #(cske/transform-keys csk/->kebab-case %)) + (update-vals #(mapv rpc->balances-per-chain %)))) + (defn <-rpc [network] (-> network diff --git a/src/status_im/contexts/wallet/events.cljs b/src/status_im/contexts/wallet/events.cljs index aeb1162833..d6d8f83e58 100644 --- a/src/status_im/contexts/wallet/events.cljs +++ b/src/status_im/contexts/wallet/events.cljs @@ -1,7 +1,5 @@ (ns status-im.contexts.wallet.events (:require - [camel-snake-kebab.core :as csk] - [camel-snake-kebab.extras :as cske] [clojure.string :as string] [react-native.background-timer :as background-timer] [status-im.contexts.wallet.data-store :as data-store] @@ -12,7 +10,6 @@ [utils.ethereum.chain :as chain] [utils.ethereum.eip.eip55 :as eip55] [utils.i18n :as i18n] - [utils.money :as money] [utils.number] [utils.re-frame :as rf])) @@ -109,19 +106,10 @@ :params params}) {:db (assoc-in db [:wallet :ui :tokens-loading?] false)})) -(defn- fix-balances-per-chain - [token] - (-> token - (update :balances-per-chain update-vals #(update % :raw-balance money/bignumber)) - (update :balances-per-chain update-keys (comp utils.number/parse-int name)))) - (rf/reg-event-fx :wallet/store-wallet-token (fn [{:keys [db]} [raw-tokens-data]] - (let [tokens (-> raw-tokens-data - (update-keys name) - (update-vals #(cske/transform-keys csk/->kebab-case %)) - (update-vals #(mapv fix-balances-per-chain %))) + (let [tokens (data-store/rpc->tokens raw-tokens-data) add-tokens (fn [stored-accounts tokens-per-account] (reduce-kv (fn [accounts address tokens-data] (if (accounts address) diff --git a/src/status_im/contexts/wallet/home/tabs/assets/view.cljs b/src/status_im/contexts/wallet/home/tabs/assets/view.cljs index cd2ab787c9..4bf05892ca 100644 --- a/src/status_im/contexts/wallet/home/tabs/assets/view.cljs +++ b/src/status_im/contexts/wallet/home/tabs/assets/view.cljs @@ -2,13 +2,13 @@ (:require [quo.core :as quo] [react-native.core :as rn] - [status-im.contexts.wallet.common.temp :as temp] [status-im.contexts.wallet.home.tabs.assets.style :as style] [utils.re-frame :as rf])) (defn view [] - (let [tokens-loading? (rf/sub [:wallet/tokens-loading?])] + (let [tokens-loading? (rf/sub [:wallet/tokens-loading?]) + {:keys [tokens]} (rf/sub [:wallet/aggregated-tokens-and-balance])] (if tokens-loading? [quo/skeleton-list {:content :assets @@ -16,6 +16,5 @@ :animated? false}] [rn/flat-list {:render-fn quo/token-value - :data temp/tokens - :key :assets-list + :data tokens :content-container-style style/list-container}]))) diff --git a/src/status_im/contexts/wallet/home/view.cljs b/src/status_im/contexts/wallet/home/view.cljs index 8e4022c0e2..17da7b6e4c 100644 --- a/src/status_im/contexts/wallet/home/view.cljs +++ b/src/status_im/contexts/wallet/home/view.cljs @@ -39,10 +39,11 @@ [] (let [selected-tab (reagent/atom (:id (first tabs-data)))] (fn [] - (let [tokens-loading? (rf/sub [:wallet/tokens-loading?]) - networks (rf/sub [:wallet/network-details]) - account-cards-data (rf/sub [:wallet/account-cards-data]) - cards (conj account-cards-data (new-account-card-data))] + (let [tokens-loading? (rf/sub [:wallet/tokens-loading?]) + networks (rf/sub [:wallet/network-details]) + account-cards-data (rf/sub [:wallet/account-cards-data]) + cards (conj account-cards-data (new-account-card-data)) + {:keys [formatted-balance]} (rf/sub [:wallet/aggregated-tokens-and-balance])] [rn/view {:style (style/home-container)} [common.top-nav/view] [rn/view {:style style/overview-container} @@ -50,7 +51,7 @@ {:state (if tokens-loading? :loading :default) :time-frame :none :metrics :none - :balance "€0.00" + :balance formatted-balance :networks networks}]] [quo/wallet-graph {:time-frame :empty}] [rn/flat-list diff --git a/src/status_im/subs/wallet/wallet.cljs b/src/status_im/subs/wallet/wallet.cljs index 1feb8684b0..73de9be920 100644 --- a/src/status_im/subs/wallet/wallet.cljs +++ b/src/status_im/subs/wallet/wallet.cljs @@ -1,7 +1,6 @@ (ns status-im.subs.wallet.wallet (:require [clojure.string :as string] [re-frame.core :as rf] - [status-im.constants :as constants] [status-im.contexts.wallet.common.utils :as utils] [utils.number])) @@ -120,10 +119,14 @@ :<- [:wallet/accounts] :<- [:wallet/current-viewing-account-address] :<- [:wallet/balances] - (fn [[accounts current-viewing-account-address balances]] - (let [current-viewing-account (utils/get-account-by-address accounts current-viewing-account-address)] + :<- [:profile/currency-symbol] + (fn [[accounts current-viewing-account-address balances currency-symbol]] + (let [current-viewing-account (utils/get-account-by-address accounts current-viewing-account-address) + balance (get balances current-viewing-account-address) + formatted-balance (utils/prettify-balance currency-symbol balance)] (-> current-viewing-account - (assoc :balance (get balances current-viewing-account-address)))))) + (assoc :balance balance + :formatted-balance formatted-balance))))) (rf/reg-sub :wallet/tokens-filtered @@ -157,37 +160,41 @@ (fn [accounts] (remove #(:watch-only? %) accounts))) -(defn- calc-token-value - [{:keys [market-values-per-currency] :as token} color currency currency-symbol] - (let [token-units (utils/total-token-units-in-all-chains token) - fiat-value (utils/total-token-fiat-value currency token) - market-values (get market-values-per-currency - currency - (get market-values-per-currency - constants/profile-default-currency)) - {:keys [change-pct-24hour]} market-values - crypto-value (utils/get-standard-crypto-format token token-units) - fiat-value (if (string/includes? crypto-value "<") - "<$0.01" - (utils/prettify-balance currency-symbol fiat-value))] - {:token (:symbol token) - :token-name (:name token) - :state :default - :status (cond - (pos? change-pct-24hour) :positive - (neg? change-pct-24hour) :negative - :else :empty) - :customization-color color - :values {:crypto-value crypto-value - :fiat-value fiat-value}})) - (rf/reg-sub :wallet/account-token-values :<- [:wallet/current-viewing-account] :<- [:profile/currency] :<- [:profile/currency-symbol] (fn [[{:keys [tokens color]} currency currency-symbol]] - (mapv #(calc-token-value % color currency currency-symbol) tokens))) + (mapv #(utils/calculate-token-value {:token % + :color color + :currency currency + :currency-symbol currency-symbol}) + tokens))) + +(rf/reg-sub + :wallet/aggregated-tokens + :<- [:wallet/accounts] + (fn [accounts] + (utils/aggregate-tokens-for-all-accounts accounts))) + +(rf/reg-sub + :wallet/aggregated-tokens-and-balance + :<- [:wallet/aggregated-tokens] + :<- [:profile/customization-color] + :<- [:profile/currency] + :<- [:profile/currency-symbol] + (fn [[aggregated-tokens color currency currency-symbol]] + (let [balance (utils/calculate-balance-from-tokens {:currency currency + :tokens aggregated-tokens}) + formatted-balance (utils/prettify-balance currency-symbol balance)] + {:balance balance + :formatted-balance formatted-balance + :tokens (mapv #(utils/calculate-token-value {:token % + :color color + :currency currency + :currency-symbol currency-symbol}) + aggregated-tokens)}))) (rf/reg-sub :wallet/network-preference-details diff --git a/src/status_im/subs/wallet/wallet_test.cljs b/src/status_im/subs/wallet/wallet_test.cljs index fbf5e78a49..7c65517dbd 100644 --- a/src/status_im/subs/wallet/wallet_test.cljs +++ b/src/status_im/subs/wallet/wallet_test.cljs @@ -278,9 +278,10 @@ :public-key "0x04371e2d9d66b82f056bc128064" :removed false :tokens tokens-0x1} - (dissoc result :balance))) + (dissoc result :balance :formatted-balance))) - (is (money/equal-to (:balance result) (money/bignumber 3250)))))) + (is (money/equal-to (:balance result) (money/bignumber 3250))) + (is (match? (:formatted-balance result) "$3250.00"))))) (h/deftest-sub :wallet/addresses [sub-name] @@ -440,3 +441,21 @@ (->> (rf/sub [sub-name]) ;; Removed `#js source` property for correct compare (map #(dissoc % :source))))))) + +(h/deftest-sub :wallet/aggregated-tokens + [sub-name] + (testing "returns aggregated tokens from all accounts" + (swap! rf-db/app-db #(assoc-in % [:wallet :accounts] accounts)) + (let [result (rf/sub [sub-name]) + eth-token (some #(when (= (:symbol %) "ETH") %) result) + eth-mainnet-raw-balance (get-in eth-token [:balances-per-chain 1 :raw-balance])] + (is (match? 2 (count result))) + (is (money/equal-to (money/bignumber 7520) eth-mainnet-raw-balance))))) + +(h/deftest-sub :wallet/aggregated-tokens-and-balance + [sub-name] + (testing "returns aggregated tokens (in quo/token-value props) and balances from all accounts" + (swap! rf-db/app-db #(assoc-in % [:wallet :accounts] accounts)) + (let [{:keys [formatted-balance tokens]} (rf/sub [sub-name])] + (is (match? 2 (count tokens))) + (is (match? "$4506.00" formatted-balance)))))