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>
This commit is contained in:
Mohamed Javid 2024-01-06 00:36:54 +05:30 committed by GitHub
parent b4640dabb9
commit a00e44d056
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 146 additions and 119 deletions

View File

@ -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}]

View File

@ -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

View File

@ -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}}))

View File

@ -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

View File

@ -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

View File

@ -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)

View File

@ -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}])))

View File

@ -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

View File

@ -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

View File

@ -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)))))