From 4ff5e4da8be7001d17b787eb2a4dc9c5830d0689 Mon Sep 17 00:00:00 2001 From: Ulises Manuel <90291778+ulisesmac@users.noreply.github.com> Date: Wed, 3 Jan 2024 18:41:12 -0600 Subject: [PATCH] [#17938] Collectibles per account (#18277) * Update wallet events and subs to handle collectibles per account Additionally, - Move collectibles related events to a new events namespace (`status-im.contexts.wallet.events.collectibles`). - Update tests to consider collectibles per account. * Update collectibles tab to handle a current viewing account --- .../wallet/common/collectibles_tab/view.cljs | 5 +- src/status_im/contexts/wallet/events.cljs | 124 +---------------- .../contexts/wallet/events/collectibles.cljs | 125 ++++++++++++++++++ .../contexts/wallet/events_test.cljs | 46 ++++--- src/status_im/subs/wallet/collectibles.cljs | 20 ++- src/status_im/subs/wallet/wallet.cljs | 1 - 6 files changed, 178 insertions(+), 143 deletions(-) create mode 100644 src/status_im/contexts/wallet/events/collectibles.cljs diff --git a/src/status_im/contexts/wallet/common/collectibles_tab/view.cljs b/src/status_im/contexts/wallet/common/collectibles_tab/view.cljs index e514fabd99..d1363de92f 100644 --- a/src/status_im/contexts/wallet/common/collectibles_tab/view.cljs +++ b/src/status_im/contexts/wallet/common/collectibles_tab/view.cljs @@ -10,7 +10,10 @@ (defn- view-internal [{:keys [theme]}] - (let [collectible-list (rf/sub [:wallet/collectibles])] + (let [specific-address (rf/sub [:wallet/current-viewing-account-address]) + collectible-list (if specific-address + (rf/sub [:wallet/collectibles-per-account specific-address]) + (rf/sub [:wallet/all-collectibles]))] (if (empty? collectible-list) [empty-tab/view {:title (i18n/label :t/no-collectibles) diff --git a/src/status_im/contexts/wallet/events.cljs b/src/status_im/contexts/wallet/events.cljs index 696d205d13..9d5e4f7840 100644 --- a/src/status_im/contexts/wallet/events.cljs +++ b/src/status_im/contexts/wallet/events.cljs @@ -5,6 +5,7 @@ [clojure.string :as string] [react-native.background-timer :as background-timer] [status-im.common.data-store.wallet :as data-store] + [status-im.contexts.wallet.events.collectibles] [status-im.contexts.wallet.item-types :as item-types] [status-im.contexts.wallet.temp :as temp] [taoensso.timbre :as log] @@ -13,8 +14,7 @@ [utils.i18n :as i18n] [utils.money :as money] [utils.number] - [utils.re-frame :as rf] - [utils.transforms :as types])) + [utils.re-frame :as rf])) (rf/reg-event-fx :wallet/show-account-created-toast (fn [{:keys [db]} [address]] @@ -65,10 +65,7 @@ db (data-store/rpc->accounts wallet-accounts)) :fx [[:dispatch [:wallet/get-wallet-token]] - [:dispatch - [:wallet/request-collectibles - {:start-at-index 0 - :new-request? true}]] + [:dispatch [:wallet/request-collectibles {:start-at-index 0 :new-request? true}]] (when new-account? [:dispatch [:wallet/navigate-to-new-account navigate-to-account]])]}))) @@ -224,121 +221,6 @@ data)}] {:db (assoc db :wallet/networks network-data)}))) -(def collectibles-request-batch-size 1000) - -(defn displayable-collectible? - [collectible] - (let [{:keys [image-url animation-url]} (:collectible-data collectible)] - (or (not (string/blank? animation-url)) - (not (string/blank? image-url))))) - -(defn store-collectibles - [{:keys [db]} [collectibles]] - (let [stored-collectibles (get-in db [:wallet :collectibles]) - displayable-collectibles (filter displayable-collectible? collectibles)] - {:db (assoc-in db - [:wallet :collectibles] - (reduce conj displayable-collectibles stored-collectibles))})) - -(rf/reg-event-fx :wallet/store-collectibles store-collectibles) - -(defn clear-stored-collectibles - [{:keys [db]}] - {:db (update db :wallet dissoc :collectibles)}) - -(rf/reg-event-fx :wallet/clear-stored-collectibles clear-stored-collectibles) - -(defn store-last-collectible-details - [{:keys [db]} [collectible]] - {:db (assoc-in db - [:wallet :last-collectible-details] - collectible)}) - -(rf/reg-event-fx :wallet/store-last-collectible-details store-last-collectible-details) - -(def collectible-data-types - {:unique-id 0 - :header 1 - :details 2 - :community-header 3}) - -(def fetch-type - {:never-fetch 0 - :always-fetch 1 - :fetch-if-not-cached 2 - :fetch-if-cache-old 3}) - -(def max-cache-age-seconds 3600) - -(rf/reg-event-fx - :wallet/request-collectibles - (fn [{:keys [db]} [{:keys [start-at-index new-request?]}]] - (let [request-id 0 - collectibles-filter nil - data-type (collectible-data-types :header) - fetch-criteria {:fetch-type (fetch-type :fetch-if-not-cached) - :max-cache-age-seconds max-cache-age-seconds} - request-params [request-id - [(chain/chain-id db)] - (keys (get-in db [:wallet :accounts])) - collectibles-filter - start-at-index - collectibles-request-batch-size - data-type - fetch-criteria]] - {:fx [[:json-rpc/call - [{:method "wallet_getOwnedCollectiblesAsync" - :params request-params - :on-success #() - :on-error (fn [error] - (log/error "failed to request collectibles" - {:event :wallet/request-collectibles - :error error - :params request-params}))}]] - (when new-request? - [:dispatch [:wallet/clear-stored-collectibles]])]}))) - -(rf/reg-event-fx :wallet/owned-collectibles-filtering-done - (fn [_ [{:keys [message]}]] - (let [response (cske/transform-keys csk/->kebab-case-keyword - (types/json->clj message)) - {:keys [collectibles has-more offset]} response - start-at-index (+ offset (count collectibles))] - {:fx - [[:dispatch [:wallet/store-collectibles collectibles]] - (when has-more - [:dispatch - [:wallet/request-collectibles - {:start-at-index start-at-index}]])]}))) - -(rf/reg-event-fx :wallet/get-collectible-details - (fn [_ [collectible-id]] - (let [request-id 0 - collectible-id-converted (cske/transform-keys csk/->PascalCaseKeyword collectible-id) - data-type (collectible-data-types :details) - request-params [request-id [collectible-id-converted] data-type]] - {:fx [[:json-rpc/call - [{:method "wallet_getCollectiblesByUniqueIDAsync" - :params request-params - :on-error (fn [error] - (log/error "failed to request collectible" - {:event :wallet/get-collectible-details - :error error - :params request-params}))}]]]}))) - -(rf/reg-event-fx :wallet/get-collectible-details-done - (fn [_ [{:keys [message]}]] - (let [response (cske/transform-keys csk/->kebab-case-keyword - (types/json->clj message)) - {:keys [collectibles]} response - collectible (first collectibles)] - (if collectible - {:fx - [[:dispatch [:wallet/store-last-collectible-details collectible]]]} - (log/error "failed to get collectible details" - {:event :wallet/get-collectible-details-done - :response response}))))) - (rf/reg-event-fx :wallet/find-ens (fn [{:keys [db]} [input contacts chain-id cb]] (let [result (if (empty? input) diff --git a/src/status_im/contexts/wallet/events/collectibles.cljs b/src/status_im/contexts/wallet/events/collectibles.cljs new file mode 100644 index 0000000000..477f25dcc3 --- /dev/null +++ b/src/status_im/contexts/wallet/events/collectibles.cljs @@ -0,0 +1,125 @@ +(ns status-im.contexts.wallet.events.collectibles + (:require [camel-snake-kebab.core :as csk] + [camel-snake-kebab.extras :as cske] + [clojure.string :as string] + [re-frame.core :as rf] + [taoensso.timbre :as log] + [utils.ethereum.chain :as chain] + [utils.transforms :as types])) + +(def collectible-data-types + {:unique-id 0 + :header 1 + :details 2 + :community-header 3}) + +(def fetch-type + {:never-fetch 0 + :always-fetch 1 + :fetch-if-not-cached 2 + :fetch-if-cache-old 3}) + +(def max-cache-age-seconds 3600) +(def collectibles-request-batch-size 1000) + +(defn displayable-collectible? + [collectible] + (let [{:keys [image-url animation-url]} (:collectible-data collectible)] + (or (not (string/blank? animation-url)) + (not (string/blank? image-url))))) + +(defn- add-collectibles-to-accounts + [accounts collectibles] + (reduce (fn [acc {:keys [ownership] :as collectible}] + (->> ownership + (map :address) ; In ERC1155 tokens a collectible can be owned by multiple addresses. + (reduce (fn add-collectible-to-address [acc address] + (update-in acc [address :collectibles] conj collectible)) + acc))) + accounts + collectibles)) + +(defn store-collectibles + [{:keys [db]} [collectibles]] + (let [displayable-collectibles (filter displayable-collectible? collectibles)] + {:db (update-in db [:wallet :accounts] add-collectibles-to-accounts displayable-collectibles)})) + +(rf/reg-event-fx :wallet/store-collectibles store-collectibles) + +(defn clear-stored-collectibles + [{:keys [db]}] + {:db (update-in db [:wallet :accounts] update-vals #(dissoc % :collectibles))}) + +(rf/reg-event-fx :wallet/clear-stored-collectibles clear-stored-collectibles) + +(defn store-last-collectible-details + [{:keys [db]} [collectible]] + {:db (assoc-in db [:wallet :last-collectible-details] collectible)}) + +(rf/reg-event-fx :wallet/store-last-collectible-details store-last-collectible-details) + +(rf/reg-event-fx + :wallet/request-collectibles + (fn [{:keys [db]} [{:keys [start-at-index new-request?]}]] + (let [request-id 0 + collectibles-filter nil + data-type (collectible-data-types :header) + fetch-criteria {:fetch-type (fetch-type :fetch-if-not-cached) + :max-cache-age-seconds max-cache-age-seconds} + request-params [request-id + [(chain/chain-id db)] + (keys (get-in db [:wallet :accounts])) + collectibles-filter + start-at-index + collectibles-request-batch-size + data-type + fetch-criteria]] + {:fx [[:json-rpc/call + [{:method "wallet_getOwnedCollectiblesAsync" + :params request-params + :on-error (fn [error] + (log/error "failed to request collectibles" + {:event :wallet/request-collectibles + :error error + :params request-params}))}]] + (when new-request? + [:dispatch [:wallet/clear-stored-collectibles]])]}))) + +(rf/reg-event-fx + :wallet/owned-collectibles-filtering-done + (fn [_ [{:keys [message]}]] + (let [{:keys [has-more offset + collectibles]} (cske/transform-keys csk/->kebab-case-keyword (types/json->clj message)) + start-at-index (+ offset (count collectibles))] + {:fx [[:dispatch [:wallet/store-collectibles collectibles]] + (when has-more + [:dispatch [:wallet/request-collectibles {:start-at-index start-at-index}]])]}))) + +(rf/reg-event-fx + :wallet/get-collectible-details + (fn [_ [collectible-id]] + (let [request-id 0 + collectible-id-converted (cske/transform-keys csk/->PascalCaseKeyword collectible-id) + data-type (collectible-data-types :details) + request-params [request-id [collectible-id-converted] data-type]] + {:fx [[:json-rpc/call + [{:method "wallet_getCollectiblesByUniqueIDAsync" + :params request-params + :on-error (fn [error] + (log/error "failed to request collectible" + {:event :wallet/get-collectible-details + :error error + :params request-params}))}]]]}))) + +(rf/reg-event-fx + :wallet/get-collectible-details-done + (fn [_ [{:keys [message]}]] + (let [response (cske/transform-keys csk/->kebab-case-keyword + (types/json->clj message)) + {:keys [collectibles]} response + collectible (first collectibles)] + (if collectible + {:fx [[:dispatch [:wallet/store-last-collectible-details collectible]]]} + (log/error "failed to get collectible details" + {:event :wallet/get-collectible-details-done + :response response}))))) diff --git a/src/status_im/contexts/wallet/events_test.cljs b/src/status_im/contexts/wallet/events_test.cljs index cbdd2caf56..32e2b7edd2 100644 --- a/src/status_im/contexts/wallet/events_test.cljs +++ b/src/status_im/contexts/wallet/events_test.cljs @@ -2,7 +2,8 @@ (:require [cljs.test :refer-macros [deftest is testing]] matcher-combinators.test - [status-im.contexts.wallet.events :as events])) + [status-im.contexts.wallet.events :as events] + [status-im.contexts.wallet.events.collectibles :as collectibles])) (def address "0x2f88d65f3cb52605a54a833ae118fb1363acccd2") @@ -35,27 +36,40 @@ [false {:collectible-data {:image-url nil :animation-url ""}}] [false {:collectible-data {:image-url "" :animation-url ""}}]]] (doseq [[result collection] expected-results] - (is (match? result (events/displayable-collectible? collection)))))) + (is (match? result (collectibles/displayable-collectible? collection)))))) (testing "save-collectibles-request-details" - (let [db {:wallet {}} - collectibles [{:collectible-data {:image-url "https://..." :animation-url "https://..."}} - {:collectible-data {:image-url "" :animation-url "https://..."}} - {:collectible-data {:image-url "" :animation-url nil}}] - expected-db {:wallet {:collectibles [{:collectible-data - {:image-url "https://..." :animation-url "https://..."}} - {:collectible-data - {:image-url "" :animation-url "https://..."}}]}} - effects (events/store-collectibles {:db db} [collectibles]) - result-db (:db effects)] + (let [db {:wallet {:accounts {"0x1" {} + "0x3" {}}}} + collectible-1 {:collectible-data {:image-url "https://..." :animation-url "https://..."} + :ownership [{:address "0x1" + :balance "1"}]} + collectible-2 {:collectible-data {:image-url "" :animation-url "https://..."} + :ownership [{:address "0x1" + :balance "1"}]} + collectible-3 {:collectible-data {:image-url "" :animation-url nil} + :ownership [{:address "0x2" + :balance "1"}]} + collectibles [collectible-1 collectible-2 collectible-3] + expected-db {:wallet {:accounts {"0x1" {:collectibles (list collectible-2 collectible-1)} + "0x2" {:collectibles (list collectible-3)} + "0x3" {}}}} + effects (collectibles/store-collectibles {:db db} [collectibles]) + result-db (:db effects)] (is (match? result-db expected-db))))) (deftest clear-stored-collectibles - (let [db {:wallet {:collectibles [{:id 1} {:id 2}]}}] + (let [db {:wallet {:accounts {"0x1" {:collectibles [{:id 1} {:id 2}]} + "0x2" {"some other stuff" "with any value" + :collectibles [{:id 3}]} + "0x3" {}}}}] (testing "clear-stored-collectibles" - (let [expected-db {:wallet {}} - effects (events/clear-stored-collectibles {:db db}) + (let [expected-db {:wallet {:accounts {"0x1" {} + "0x2" {"some other stuff" "with any value"} + "0x3" {}}}} + effects (collectibles/clear-stored-collectibles {:db db}) result-db (:db effects)] + (is (match? result-db expected-db)))))) (deftest store-last-collectible-details @@ -65,6 +79,6 @@ :image-url "https://..."} expected-db {:wallet {:last-collectible-details {:description "Pandaria" :image-url "https://..."}}} - effects (events/store-last-collectible-details {:db db} [last-collectible]) + effects (collectibles/store-last-collectible-details {:db db} [last-collectible]) result-db (:db effects)] (is (match? result-db expected-db))))) diff --git a/src/status_im/subs/wallet/collectibles.cljs b/src/status_im/subs/wallet/collectibles.cljs index 642996b6b7..ec1aceec7e 100644 --- a/src/status_im/subs/wallet/collectibles.cljs +++ b/src/status_im/subs/wallet/collectibles.cljs @@ -11,12 +11,24 @@ image-url)) (re-frame/reg-sub - :wallet/collectibles + :wallet/collectibles-per-account + :<- [:wallet] + (fn [wallet [_ address]] + (as-> wallet $ + (get-in $ [:accounts address :collectibles]) + (map (fn [{:keys [collectible-data] :as collectible}] + (assoc collectible :preview-url (preview-url collectible-data))) + $)))) + +(re-frame/reg-sub + :wallet/all-collectibles :<- [:wallet] (fn [wallet] - (map (fn [collectible] - (assoc collectible :preview-url (preview-url (:collectible-data collectible)))) - (:collectibles wallet)))) + (->> wallet + :accounts + (mapcat (comp :collectibles val)) + (map (fn [{:keys [collectible-data] :as collectible}] + (assoc collectible :preview-url (preview-url collectible-data))))))) (re-frame/reg-sub :wallet/last-collectible-details diff --git a/src/status_im/subs/wallet/wallet.cljs b/src/status_im/subs/wallet/wallet.cljs index a9384fce66..e44eff87dc 100644 --- a/src/status_im/subs/wallet/wallet.cljs +++ b/src/status_im/subs/wallet/wallet.cljs @@ -39,7 +39,6 @@ :<- [:wallet/ui] :-> :tokens-loading?) - (rf/reg-sub :wallet/current-viewing-account-address :<- [:wallet]