Compare commits

...
Author SHA1 Message Date
Cristian Lungu f248265182 ref: using changes in :wallet/tokens-filtered 2024-10-25 14:37:53 +03:00
Cristian Lungu 42a2b86723 feat: refactor 2024-10-25 12:50:40 +03:00
Cristian Lungu a3962654e4 ref: changed the way tokens are fetched 2024-10-24 17:44:50 +03:00
6 changed files with 215 additions and 212 deletions
+90 -39
View File
@@ -1,41 +1,92 @@
(ns status-im.contexts.wallet.tokens.data)
(ns status-im.contexts.wallet.tokens.data
(:require [camel-snake-kebab.extras :as cske]
[clojure.set :as set]
[utils.transforms :as transforms]))
(defn- tokens-by-key
[{:keys [key-fn added-tokens source-name tokens chain-ids]}]
(reduce
(fn [acc {:keys [address chainId decimals name image verified] :as token}]
(if (some #{chainId} chain-ids)
(let [k (key-fn token)]
(assoc acc
k
{:key k
:name name
:symbol (:symbol token)
:sources (if-let [added-token (get added-tokens k)]
(conj (:sources added-token) source-name)
[source-name])
:chain-id chainId
:address address
:decimals decimals
:image image
:type (if (= name "native") :native :erc20)
:community-id (get-in token [:communityData :id])
:verified? verified}))
acc))
(defn- normalize-by-symbol
[token-source]
(->> token-source
:tokens
(reduce (fn [acc token-data]
(let [chain-id (:chainId token-data)
token-symbol (-> token-data :symbol keyword)]
(->> token-data
(cske/transform-keys transforms/->kebab-case-keyword)
(update acc chain-id assoc token-symbol))))
{})))
(defn- flat-symbol-list
[token-source]
(->> token-source
:tokens
(reduce (fn [acc token-data]
(let [chain-id (:chainId token-data)
token-symbol (-> token-data :symbol keyword)]
(update acc chain-id set/union #{token-symbol})))
{})))
(defn- sort-symbol-list
[symbol-map]
(reduce (fn [acc [chain-id symbol-list]]
(assoc acc chain-id (-> symbol-list sort vec)))
{}
symbol-map))
(defn normalize-tokens
[{:keys [data]}]
(-> (reduce
(fn [{:keys [sources tokens-by-symbol symbol-lists]} token-source]
(let [{:keys [source version name]} token-source]
{:sources (conj sources
{:name name
:source source
:version version})
:tokens-by-symbol (merge-with merge tokens-by-symbol (normalize-by-symbol token-source))
:symbol-lists (merge-with set/union symbol-lists (flat-symbol-list token-source))}))
{}
data)
(update :symbol-lists sort-symbol-list)))
(defn all-token-symbols
[symbol-lists]
(->> symbol-lists
vals
(map set)
(apply set/union)
vec))
(defn normalize-market-values
[market-values]
(reduce-kv (fn [acc token-symbol data]
(assoc acc
token-symbol
{:market-cap (:MKTCAP data)
:high-day (:HIGHDAY data)
:low-day (:LOWDAY data)
:change-pct-hour (:CHANGEPCTHOUR data)
:change-pct-day (:CHANGEPCTDAY data)
:change-pct-24h (:CHANGEPCT24HOUR data)
:change-24h (:CHANGE24HOUR data)}))
{}
market-values))
(defn normalize-token-details
[token-details]
(reduce-kv (fn [acc token-symbol data]
(->> data
(cske/transform-keys transforms/->kebab-case-keyword)
(assoc acc token-symbol)))
{}
token-details))
(defn normalize-prices
[token-prices]
(reduce-kv
(fn [acc k v]
(->> v
(map (fn [[currency price]]
[currency {:price price}]))
(into {})
(assoc acc k)))
{}
tokens))
(defn tokens-by-address
[props]
(tokens-by-key (assoc props
:key-fn
(fn [{:keys [chainId address]}]
(str chainId "-" address)))))
(defn tokens-by-symbol
[props]
(tokens-by-key
(assoc props
:key-fn
(fn [{:keys [chainId address] :as token}]
(str chainId "-" (if (get-in token [:communityData :id]) address (:symbol token)))))))
token-prices))
@@ -4,22 +4,15 @@
[utils.re-frame :as rf]))
(rf/reg-fx
:effects.wallet.tokens/fetch-market-values
:effects.wallet.tokens/fetch-additional-token-data
(fn [{:keys [symbols currency on-success on-error]}]
(-> (rpc/fetch-market-values symbols currency)
(-> (rpc/fetch-additional-token-data symbols currency)
(promesa/then (partial rf/call-continuation on-success))
(promesa/catch (partial rf/call-continuation on-error)))))
(rf/reg-fx
:effects.wallet.tokens/fetch-details
(fn [{:keys [symbols on-success on-error]}]
(-> (rpc/fetch-details symbols)
(promesa/then (partial rf/call-continuation on-success))
(promesa/catch (partial rf/call-continuation on-error)))))
(rf/reg-fx
:effects.wallet.tokens/fetch-prices
(fn [{:keys [symbols currencies on-success on-error]}]
(-> (rpc/fetch-prices symbols currencies)
:effects.wallet.tokens/fetch-token-list
(fn [{:keys [on-success on-error]}]
(-> (rpc/fetch-token-list)
(promesa/then (partial rf/call-continuation on-success))
(promesa/catch (partial rf/call-continuation on-error)))))
+40 -133
View File
@@ -4,144 +4,51 @@
[status-im.contexts.wallet.tokens.data :as tokens-data]
[status-im.contexts.wallet.tokens.effects]
[taoensso.timbre :as log]
[utils.address]
[utils.ethereum.chain :as chain]))
[utils.address]))
(rf/reg-event-fx
:wallet.tokens/get-token-list
;; NOTE: fetching the token list and the rest of the data (market-values, token-details, token-prices)
;; separately, since getting the token list is about 10x quicker.
(rf/reg-event-fx :wallet.tokens/get-token-list
(fn [{:keys [db]}]
(when-not (get-in db [:wallet :tokens])
{:db (assoc-in db [:wallet :ui :loading :token-list] true)
:fx [[:json-rpc/call
[{:method "wallet_getTokenList"
:params []
:on-success [:wallet.tokens/store-token-list]
:on-error [:wallet.tokens/get-token-list-failed]}]]]})))
(when-not (-> db :wallet :tokens)
{:fx [[:effects.wallet.tokens/fetch-token-list
{:on-success [:wallet.tokens/store-token-list]
:on-error [:wallet.tokens/get-token-list-failed]}]]})))
(defn store-token-list
[{:keys [db]} [{:keys [data]}]]
(let [chain-ids (chain/chain-ids db)
tokens (reduce (fn [{:keys [by-address by-symbol] :as data}
{:keys [name source version tokens]}]
(-> data
(update :sources
conj
{:name name
:source source
:version version
:tokens-count (count tokens)})
(update :by-address
merge
(tokens-data/tokens-by-address
{:added-tokens by-address
:source-name name
:tokens tokens
:chain-ids chain-ids}))
(update :by-symbol
merge
(tokens-data/tokens-by-symbol
{:added-tokens by-symbol
:source-name name
:tokens tokens
:chain-ids chain-ids}))))
{:sources []
:by-address {}
:by-symbol {}}
data)
symbols (->> tokens
:by-symbol
vals
(map :symbol)
set
vec)]
{:fx [[:effects.wallet.tokens/fetch-market-values
{:symbols symbols
:currency constants/profile-default-currency
:on-success [:wallet.tokens/store-market-values]
:on-error [:wallet.tokens/fetch-market-values-failed]}]
[:effects.wallet.tokens/fetch-details
{:symbols symbols
:on-success [:wallet.tokens/store-details]
:on-error [:wallet.tokens/fetch-details-failed]}]
[:effects.wallet.tokens/fetch-prices
{:symbols symbols
:currencies [constants/profile-default-currency]
:on-success [:wallet.tokens/store-prices]
:on-error [:wallet.tokens/fetch-prices-failed]}]]
:db (-> db
(assoc-in [:wallet :tokens]
{:sources (:sources tokens)
:by-address (-> tokens :by-address vals)
:by-symbol (-> tokens :by-symbol vals)})
(assoc-in [:wallet :ui :loading]
{:token-list false
:market-values true
:details true
:prices true}))}))
(rf/reg-event-fx :wallet.tokens/store-token-list
(fn [{:keys [db]} [{:keys [sources tokens-by-symbol symbol-lists]}]]
(let [all-symbols (tokens-data/all-token-symbols symbol-lists)
currency (or (-> db :profile/profile :currency)
constants/profile-default-currency)]
{:db (assoc-in db
[:wallet :tokens]
{:sources sources
:symbol-lists symbol-lists
:tokens-by-symbol tokens-by-symbol})
:fx [[:effects.wallet.tokens/fetch-additional-token-data
{:currency currency
:symbols all-symbols
:on-success [:wallet.tokens/store-additional-token-data]
:on-error [:wallet.tokens/get-token-list-failed]}]]})))
(rf/reg-event-fx :wallet.tokens/store-token-list store-token-list)
(rf/reg-event-fx :wallet.tokens/store-additional-token-data
(fn [{:keys [db]} [{:keys [market-values prices details]}]]
{:db (update-in db
[:wallet :tokens]
assoc
:market-values-by-symbol market-values
:prices-by-symbol prices
:details-by-symbol details)}))
(rf/reg-event-fx
:wallet.tokens/get-token-list-failed
(fn [{:keys [db]} [error]]
(log/info "failed to get wallet tokens "
(rf/reg-event-fx :wallet.tokens/get-token-list-failed
(fn [_ [error]]
(log/info "failed to get wallet tokens"
{:error error
:event :wallet.tokens/get-token-list})
{:db (assoc-in db [:wallet :ui :loading :token-list] false)}))
:event :wallet.tokens/get-token-list-failed})))
(rf/reg-event-fx
:wallet.tokens/store-market-values
(fn [{:keys [db]} [raw-data]]
(let [market-values (reduce (fn [acc [token data]]
(assoc acc
token
{:market-cap (:MKTCAP data)
:high-day (:HIGHDAY data)
:low-day (:LOWDAY data)
:change-pct-hour (:CHANGEPCTHOUR data)
:change-pct-day (:CHANGEPCTDAY data)
:change-pct-24h (:CHANGEPCT24HOUR data)
:change-24h (:CHANGE24HOUR data)}))
{}
raw-data)]
{:db (-> db
(assoc-in [:wallet :tokens :market-values-per-token] market-values)
(assoc-in [:wallet :ui :loading :market-values] false))})))
(rf/reg-event-fx
:wallet.tokens/fetch-market-values-failed
(fn [{:keys [db]} [error]]
(log/info "failed to get wallet market values "
(rf/reg-event-fx :wallet.tokens/get-additional-token-data-failed
(fn [_ [error]]
(log/info "failed to get wallet token data"
{:error error
:event :wallet.tokens/fetch-market-values})
{:db (assoc-in db [:wallet :ui :loading :market-values] false)}))
(rf/reg-event-fx
:wallet.tokens/store-details
(fn [{:keys [db]} [details]]
{:db (-> db
(assoc-in [:wallet :tokens :details-per-token] details)
(assoc-in [:wallet :ui :loading :details] false))}))
(rf/reg-event-fx
:wallet.tokens/fetch-details-failed
(fn [{:keys [db]} [error]]
(log/info "failed to get wallet details "
{:error error
:event :wallet.tokens/fetch-details})
{:db (assoc-in db [:wallet :ui :loading :details] false)}))
(rf/reg-event-fx
:wallet.tokens/store-prices
(fn [{:keys [db]} [prices]]
{:db (-> db
(assoc-in [:wallet :tokens :prices-per-token] prices)
(assoc-in [:wallet :ui :loading :prices] false))}))
(rf/reg-event-fx
:wallet.tokens/fetch-prices-failed
(fn [{:keys [db]} [error]]
(log/info "failed to get wallet prices "
{:error error
:event :wallet.tokens/fetch-prices})
{:db (assoc-in db [:wallet :ui :loading :prices] false)}))
:event :wallet.tokens/get-additional-token-data-failed})))
+23 -3
View File
@@ -1,19 +1,39 @@
(ns status-im.contexts.wallet.tokens.rpc
(:require [promesa.core :as promesa]
[status-im.common.json-rpc.events :as rpc-events]
[status-im.contexts.wallet.tokens.data :as tokens-data]
[utils.transforms :as transforms]))
(defn fetch-token-list
[]
(-> (rpc-events/call-async "wallet_getTokenList" true)
(promesa/then transforms/js->clj)
(promesa/then tokens-data/normalize-tokens)))
(defn fetch-market-values
[symbols currency]
(-> (rpc-events/call-async "wallet_fetchMarketValues" true symbols currency)
(promesa/then transforms/js->clj)))
(promesa/then transforms/js->clj)
(promesa/then tokens-data/normalize-market-values)))
(defn fetch-details
[symbols]
(-> (rpc-events/call-async "wallet_fetchTokenDetails" true symbols)
(promesa/then transforms/js->clj)))
(promesa/then transforms/js->clj)
(promesa/then tokens-data/normalize-token-details)))
(defn fetch-prices
[symbols currencies]
(-> (rpc-events/call-async "wallet_fetchPrices" true symbols currencies)
(promesa/then transforms/js->clj)))
(promesa/then transforms/js->clj)
(promesa/then tokens-data/normalize-prices)))
(defn fetch-additional-token-data
[symbols currency]
(-> (promesa/all [(fetch-market-values symbols currency)
(fetch-details symbols)
(fetch-prices symbols [currency])])
(promesa/then (fn [[market-values details prices]]
{:market-values market-values
:details details
:prices prices}))))
+57 -25
View File
@@ -474,33 +474,65 @@
sorted-tokens))
sorted-tokens))))
;; NOTE: ideally we just pass the symbols list and individual components get the details themselves
(rf/reg-sub
:wallet.swap/popular-token-symbols
:<- [:wallet/tokens]
(fn [{:keys [symbol-lists tokens-by-symbol]} [_ chain-id query]]
(when symbol-lists
(let [tokens (->> (get symbol-lists chain-id)
(mapv (fn [token-symbol]
{:type constants/swap-tokens-popular
:name (get-in tokens-by-symbol [chain-id (keyword token-symbol) :name])
:symbol token-symbol})))]
(if query
(let [query-string (string/lower-case query)]
(filter #(or (string/starts-with? (string/lower-case (:name %)) query-string)
(string/starts-with? (string/lower-case (str (:symbol %))) query-string))
tokens))
tokens)))))
(rf/reg-sub
:wallet.swap/get-popular-token-data
:<- [:wallet/tokens]
(fn [{:keys [tokens-by-symbol market-values-by-symbol prices-by-symbol details-by-symbol]}
[_ chain-id token-symbol]]
(let [token-symbol (keyword token-symbol)]
(-> tokens-by-symbol
(get-in [chain-id token-symbol])
(assoc :market-values (get market-values-by-symbol token-symbol)
:details (get details-by-symbol token-symbol)
:market-values-per-currency (get prices-by-symbol token-symbol))))))
(-> (rf/subscribe [:wallet.swap/popular-token-symbols 1 "ETH"])
deref)
;; NOTE: even with this structure, we don't have to filter out the chain-ids, as they are already
;; split by chain-ids
(rf/reg-sub
:wallet/all-tokens-by-chain-id
:<- [:wallet/tokens]
(fn [{:keys [symbol-lists tokens-by-symbol market-values-by-symbol prices-by-symbol details-by-symbol]}
[_ chain-id]]
(->> (get symbol-lists chain-id)
(map (fn [token-symbol]
(-> tokens-by-symbol
(get-in [chain-id (keyword token-symbol)])
(assoc :market-values (get market-values-by-symbol token-symbol)
:details (get details-by-symbol token-symbol)
:market-values-per-currency (get prices-by-symbol token-symbol))))))))
(rf/reg-sub
:wallet/tokens-filtered
:<- [:wallet/tokens]
:<- [:profile/currency]
(fn [[{:keys [by-symbol market-values-per-token details-per-token prices-per-token]} currency]
[_ {:keys [query chain-ids hide-token-fn]}]]
(let [tokens (->> by-symbol
(map (fn [token]
(let [token-symbol (keyword (:symbol token))
price (get-in prices-per-token [token-symbol currency])]
(-> token
(assoc :market-values
(get market-values-per-token token-symbol))
(assoc :details (get details-per-token token-symbol))
(assoc-in [:market-values-per-currency currency :price]
price)))))
(filter (fn [{:keys [chain-id]}]
(some #{chain-id} chain-ids)))
(remove #(when hide-token-fn
(hide-token-fn constants/swap-tokens-popular %))))
sorted-tokens (utils/sort-tokens-by-name tokens)]
(if query
(let [query-string (string/lower-case query)]
(filter #(or (string/starts-with? (string/lower-case (:name %)) query-string)
(string/starts-with? (string/lower-case (:symbol %)) query-string))
sorted-tokens))
sorted-tokens))))
(fn [[_ {:keys [chain-ids]}]]
(rf/subscribe [:wallet/all-tokens-by-chain-id (first chain-ids)]))
(fn [tokens [_ {:keys [query]}]]
(if query
(let [query-string (string/lower-case query)]
(filter #(or (string/starts-with? (string/lower-case (:name %)) query-string)
(string/starts-with? (string/lower-case (:symbol %)) query-string))
tokens))
tokens)))
(rf/reg-sub
:wallet/token-by-symbol