diff --git a/.env b/.env
index 0f41fc4bb0..3ed6d3c318 100644
--- a/.env
+++ b/.env
@@ -17,4 +17,4 @@ DEBUG_WEBVIEW=1
INSTABUG_SURVEYS=1
GROUP_CHATS_ENABLED=0
USE_SYM_KEY=0
-SPAM_BUTTON_DETECTION_ENABLED=1
+SPAM_BUTTON_DETECTION_ENABLED=1
\ No newline at end of file
diff --git a/.env.e2e b/.env.e2e
index 65433625fd..b0a4481eac 100644
--- a/.env.e2e
+++ b/.env.e2e
@@ -16,4 +16,4 @@ INSTABUG_TOKEN=758630ed52864cbad9c5eeeac596c60c
DEBUG_WEBVIEW=1
INSTABUG_SURVEYS=0
GROUP_CHATS_ENABLED=1
-USE_SYM_KEY=0
+USE_SYM_KEY=0
\ No newline at end of file
diff --git a/.env.jenkins b/.env.jenkins
index 339f042fe7..95c63759a9 100644
--- a/.env.jenkins
+++ b/.env.jenkins
@@ -18,4 +18,4 @@ INSTABUG_SURVEYS=1
GROUP_CHATS_ENABLED=0
USE_SYM_KEY=0
SPAM_BUTTON_DETECTION_ENABLED=1
-MAINNET_WARNING_ENABLED=1
+MAINNET_WARNING_ENABLED=1
\ No newline at end of file
diff --git a/.env.nightly b/.env.nightly
index 74243ae611..c9be838cf8 100644
--- a/.env.nightly
+++ b/.env.nightly
@@ -17,4 +17,4 @@ DEBUG_WEBVIEW=1
INSTABUG_SURVEYS=1
GROUP_CHATS_ENABLED=0
SPAM_BUTTON_DETECTION_ENABLED=1
-MAINNET_WARNING_ENABLED=1
+MAINNET_WARNING_ENABLED=1
\ No newline at end of file
diff --git a/.env.prod b/.env.prod
index 5800678c76..03acd57be3 100644
--- a/.env.prod
+++ b/.env.prod
@@ -20,4 +20,4 @@ GROUP_CHATS_ENABLED=0
USE_SYM_KEY=0
MAINNET_WARNING_ENABLED=1
SPAM_BUTTON_DETECTION_ENABLED=1
-UNIVERSAL_LINKS_ENABLED=0
+UNIVERSAL_LINKS_ENABLED=0
\ No newline at end of file
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 2211aa1090..be636eaaf3 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -8,6 +8,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
### Added
- Added Farsi public #status channel
- Spam moderation
+- Collectibles support (CryptoKitties, CryptoStrikers and Etheremon)
## [0.9.21] - 2018-06-25
### Added
diff --git a/src/status_im/ui/components/svgimage.cljs b/src/status_im/ui/components/svgimage.cljs
index 82b44ff62d..4f1eb0e600 100644
--- a/src/status_im/ui/components/svgimage.cljs
+++ b/src/status_im/ui/components/svgimage.cljs
@@ -1,7 +1,8 @@
(ns status-im.ui.components.svgimage
(:require [status-im.ui.components.react :as react]
[reagent.core :as reagent]
- [status-im.utils.platform :as platform]))
+ [status-im.utils.platform :as platform]
+ [status-im.utils.http :as http]))
(defn html [uri width height]
(str
@@ -38,7 +39,7 @@
(defn svgimage [{:keys [style source]}]
(let [width (reagent/atom nil)
{:keys [uri k] :or {k 1}} source]
- (when (re-find #"^(https:)([/|.|\w|\s|-])*\.(?:jpg|svg|png)$" uri)
+ (when (http/url-sanitized? uri)
(fn []
[react/view {:style style
:on-layout #(reset! width (-> % .-nativeEvent .-layout .-width))}
diff --git a/src/status_im/ui/screens/db.cljs b/src/status_im/ui/screens/db.cljs
index 90f2a7f55e..5fed9f4f52 100644
--- a/src/status_im/ui/screens/db.cljs
+++ b/src/status_im/ui/screens/db.cljs
@@ -114,7 +114,7 @@
(spec/def :navigation.screen-params/usage-data vector?)
-(spec/def :navigation.screen-params/display-collectible map?)
+(spec/def :navigation.screen-params/collectibles-list map?)
(spec/def :navigation/screen-params (spec/nilable (allowed-keys :opt-un [:navigation.screen-params/network-details
:navigation.screen-params/browser
@@ -124,7 +124,7 @@
:navigation.screen-params/edit-contact-group
:navigation.screen-params/dapp-description
:navigation.screen-params/usage-data
- :navigation.screen-params/display-collectible])))
+ :navigation.screen-params/collectibles-list])))
(spec/def :desktop/desktop (spec/nilable any?))
diff --git a/src/status_im/ui/screens/events.cljs b/src/status_im/ui/screens/events.cljs
index 6c20e32cbd..9d3fa32f1a 100644
--- a/src/status_im/ui/screens/events.cljs
+++ b/src/status_im/ui/screens/events.cljs
@@ -26,6 +26,9 @@
status-im.ui.screens.wallet.settings.events
status-im.ui.screens.wallet.transactions.events
status-im.ui.screens.wallet.choose-recipient.events
+ status-im.ui.screens.wallet.collectibles.cryptokitties.events
+ status-im.ui.screens.wallet.collectibles.cryptostrikers.events
+ status-im.ui.screens.wallet.collectibles.etheremon.events
status-im.ui.screens.browser.events
status-im.ui.screens.offline-messaging-settings.events
status-im.ui.screens.bootnodes-settings.events
diff --git a/src/status_im/ui/screens/views.cljs b/src/status_im/ui/screens/views.cljs
index e353037589..5e293d9b09 100644
--- a/src/status_im/ui/screens/views.cljs
+++ b/src/status_im/ui/screens/views.cljs
@@ -25,7 +25,7 @@
[status-im.ui.screens.profile.contact.views :as profile.contact]
[status-im.ui.screens.profile.group-chat.views :as profile.group-chat]
[status-im.ui.screens.profile.photo-capture.views :refer [profile-photo-capture]]
- [status-im.ui.screens.wallet.collectibles.views :as collectibles]
+ [status-im.ui.screens.wallet.collectibles.views :refer [collectibles-list]]
[status-im.ui.screens.wallet.send.views :refer [send-transaction send-transaction-modal sign-message-modal]]
[status-im.ui.screens.wallet.choose-recipient.views :refer [choose-recipient]]
[status-im.ui.screens.wallet.request.views :refer [request-transaction send-transaction-request]]
@@ -54,7 +54,7 @@
(defn get-main-component [view-id]
(case view-id
- :display-collectible collectibles/display-collectible
+ :collectibles-list collectibles-list
:intro intro
:create-account create-account
:usage-data usage-data
diff --git a/src/status_im/ui/screens/wallet/collectibles/cryptokitties.cljs b/src/status_im/ui/screens/wallet/collectibles/cryptokitties.cljs
deleted file mode 100644
index b57a7e9738..0000000000
--- a/src/status_im/ui/screens/wallet/collectibles/cryptokitties.cljs
+++ /dev/null
@@ -1,62 +0,0 @@
-(ns status-im.ui.screens.wallet.collectibles.cryptokitties
- (:require [re-frame.core :as re-frame]
- [status-im.i18n :as i18n]
- [status-im.ui.components.action-button.action-button :as action-button]
- [status-im.ui.components.colors :as colors]
- [status-im.ui.components.react :as react]
- [status-im.ui.screens.wallet.collectibles.views :as collectibles]
- [status-im.ui.screens.wallet.collectibles.styles :as styles]
- [status-im.utils.handlers :as handlers]
- [status-im.utils.http :as http]
- [status-im.ui.components.svgimage :as svgimage]))
-
-(def ck :CK)
-
-(handlers/register-handler-fx
- :load-kitties
- (fn [{db :db} [_ ids]]
- {:db db
- :http-get-n (mapv (fn [id]
- {:url (str "https://api.cryptokitties.co/kitties/" id)
- :success-event-creator (fn [o]
- [:load-collectible-success ck {id (http/parse-payload o)}])
- :failure-event-creator (fn [o]
- [:load-collectible-failure ck {id (http/parse-payload o)}])})
- ids)}))
-
-(defn kitties-url [address]
- (str "https://api.cryptokitties.co/kitties?offset=0&limit=20&owner_wallet_address=" address "&parents=false"))
-
-(handlers/register-handler-fx
- :load-kitties-success
- (fn [{db :db} [_ ids]]
- {:db db
- :dispatch [:load-kitties ids]}))
-
-;; TODO(julien) Each HTTP call will return up to 20 kitties. Make sure all extra kitties are fetched
-(defmethod collectibles/load-collectibles-fx ck [_ _ _ address]
- {:http-get {:url (kitties-url address)
- :success-event-creator (fn [o]
- [:load-kitties-success (map :id (:kitties (http/parse-payload o)))])
- :failure-event-creator (fn [o]
- [:load-collectibles-failure (http/parse-payload o)])
- :timeout-ms 10000}})
-
-(def base-url "https://www.cryptokitties.co/kitty/")
-
-(defmethod collectibles/render-collectible ck [_ {:keys [id name bio image_url]}]
- [react/view {:style styles/details}
- [react/view {:style styles/details-text}
- [svgimage/svgimage {:style styles/details-image
- :source {:uri image_url}}]
- [react/view {:flex 1}
- [react/text {:style styles/details-name}
- (or name (i18n/label :t/cryptokitty-name {:id id}))]
- [react/text {:number-of-lines 3
- :ellipsize-mode :tail}
- bio]]]
- [action-button/action-button {:label (i18n/label :t/view-cryptokitties)
- :icon :icons/address
- :icon-opts {:color colors/blue}
- :accessibility-label :open-collectible-button
- :on-press #(re-frame/dispatch [:open-browser {:url (str base-url id)}])}]])
diff --git a/src/status_im/ui/screens/wallet/collectibles/cryptokitties/events.cljs b/src/status_im/ui/screens/wallet/collectibles/cryptokitties/events.cljs
new file mode 100644
index 0000000000..6325389839
--- /dev/null
+++ b/src/status_im/ui/screens/wallet/collectibles/cryptokitties/events.cljs
@@ -0,0 +1,31 @@
+(ns status-im.ui.screens.wallet.collectibles.cryptokitties.events
+ (:require [status-im.utils.handlers :as handlers]
+ [status-im.ui.screens.wallet.collectibles.events :as collectibles]
+ [status-im.utils.http :as http]))
+
+(def ck :CK)
+
+(handlers/register-handler-fx
+ :load-kitties
+ (fn [{db :db} [_ ids]]
+ {:db db
+ :http-get-n (mapv (fn [id]
+ {:url (str "https://api.cryptokitties.co/kitties/" id)
+ :success-event-creator (fn [o]
+ [:load-collectible-success ck {id (http/parse-payload o)}])
+ :failure-event-creator (fn [o]
+ [:load-collectible-failure ck {id (http/parse-payload o)}])})
+ ids)}))
+
+;; TODO(andrey) Each HTTP call will return up to 100 kitties. Maybe we need to implement some kind of paging later
+(defmethod collectibles/load-collectibles-fx ck [_ _ items-number address]
+ {:http-get {:url (str "https://api.cryptokitties.co/kitties?offset=0&limit="
+ items-number
+ "&owner_wallet_address="
+ address
+ "&parents=false")
+ :success-event-creator (fn [o]
+ [:load-kitties (map :id (:kitties (http/parse-payload o)))])
+ :failure-event-creator (fn [o]
+ [:load-collectibles-failure (http/parse-payload o)])
+ :timeout-ms 10000}})
\ No newline at end of file
diff --git a/src/status_im/ui/screens/wallet/collectibles/cryptokitties/views.cljs b/src/status_im/ui/screens/wallet/collectibles/cryptokitties/views.cljs
new file mode 100644
index 0000000000..3b71f2995b
--- /dev/null
+++ b/src/status_im/ui/screens/wallet/collectibles/cryptokitties/views.cljs
@@ -0,0 +1,28 @@
+(ns status-im.ui.screens.wallet.collectibles.cryptokitties.views
+ (:require [re-frame.core :as re-frame]
+ [status-im.i18n :as i18n]
+ [status-im.ui.components.action-button.action-button :as action-button]
+ [status-im.ui.components.colors :as colors]
+ [status-im.ui.components.react :as react]
+ [status-im.ui.screens.wallet.collectibles.styles :as styles]
+ [status-im.ui.screens.wallet.collectibles.views :as collectibles]
+ [status-im.ui.components.svgimage :as svgimage]))
+
+(defmethod collectibles/render-collectible :CK [_ {:keys [id name bio image_url]}]
+ [react/view {:style styles/details}
+ [react/view {:style styles/details-text}
+ [svgimage/svgimage {:style styles/details-image
+ :source {:uri image_url}}]
+ [react/view {:flex 1}
+ [react/text {:style styles/details-name}
+ (or name (i18n/label :t/cryptokitty-name {:id id}))]
+ [react/text {:number-of-lines 3
+ :ellipsize-mode :tail}
+ bio]]]
+ [action-button/action-button
+ {:label (i18n/label :t/view-cryptokitties)
+ :icon :icons/address
+ :icon-opts {:color colors/blue}
+ :accessibility-label :open-collectible-button
+ :on-press #(re-frame/dispatch [:open-browser
+ {:url (str "https://www.cryptokitties.co/kitty/" id)}])}]])
\ No newline at end of file
diff --git a/src/status_im/ui/screens/wallet/collectibles/cryptostrikers.cljs b/src/status_im/ui/screens/wallet/collectibles/cryptostrikers.cljs
deleted file mode 100644
index cb731fe5f1..0000000000
--- a/src/status_im/ui/screens/wallet/collectibles/cryptostrikers.cljs
+++ /dev/null
@@ -1,36 +0,0 @@
-(ns status-im.ui.screens.wallet.collectibles.cryptostrikers
- (:require [re-frame.core :as re-frame]
- [status-im.i18n :as i18n]
- [status-im.ui.components.action-button.action-button :as action-button]
- [status-im.ui.components.colors :as colors]
- [status-im.ui.components.react :as react]
- [status-im.ui.screens.wallet.collectibles.styles :as styles]
- [status-im.ui.screens.wallet.collectibles.views :as collectibles]
- [status-im.utils.http :as http]
- [status-im.ui.components.svgimage :as svgimage]))
-
-(def strikers :STRK)
-
-(defmethod collectibles/load-collectible-fx strikers [_ id]
- {:http-get {:url (str "https://us-central1-cryptostrikers-prod.cloudfunctions.net/cards/" id)
- :success-event-creator (fn [o]
- [:load-collectible-success strikers {id (http/parse-payload o)}])
- :failure-event-creator (fn [o]
- [:load-collectible-failure strikers {id (http/parse-payload o)}])}})
-
-(defmethod collectibles/render-collectible strikers [_ {:keys [external_url description name image]}]
- [react/view {:style styles/details}
- [react/view {:style styles/details-text}
- [svgimage/svgimage {:style styles/details-image
- :source {:uri image
- :k 1.4}}]
- [react/view {:flex 1 :justify-content :center}
- [react/text {:style styles/details-name}
- name]
- [react/text
- description]]]
- [action-button/action-button {:label (i18n/label :t/view-cryptostrikers)
- :icon :icons/address
- :icon-opts {:color colors/blue}
- :accessibility-label :open-collectible-button
- :on-press #(re-frame/dispatch [:open-browser {:url external_url}])}]])
diff --git a/src/status_im/ui/screens/wallet/collectibles/cryptostrikers/events.cljs b/src/status_im/ui/screens/wallet/collectibles/cryptostrikers/events.cljs
new file mode 100644
index 0000000000..90f910544b
--- /dev/null
+++ b/src/status_im/ui/screens/wallet/collectibles/cryptostrikers/events.cljs
@@ -0,0 +1,12 @@
+(ns status-im.ui.screens.wallet.collectibles.cryptostrikers.events
+ (:require [status-im.ui.screens.wallet.collectibles.events :as collectibles]
+ [status-im.utils.http :as http]))
+
+(def strikers :STRK)
+
+(defmethod collectibles/load-collectible-fx strikers [_ id]
+ {:http-get {:url (str "https://us-central1-cryptostrikers-prod.cloudfunctions.net/cards/" id)
+ :success-event-creator (fn [o]
+ [:load-collectible-success strikers {id (http/parse-payload o)}])
+ :failure-event-creator (fn [o]
+ [:load-collectible-failure strikers {id (http/parse-payload o)}])}})
\ No newline at end of file
diff --git a/src/status_im/ui/screens/wallet/collectibles/cryptostrikers/views.cljs b/src/status_im/ui/screens/wallet/collectibles/cryptostrikers/views.cljs
new file mode 100644
index 0000000000..7552e6c9e2
--- /dev/null
+++ b/src/status_im/ui/screens/wallet/collectibles/cryptostrikers/views.cljs
@@ -0,0 +1,27 @@
+(ns status-im.ui.screens.wallet.collectibles.cryptostrikers.views
+ (:require [re-frame.core :as re-frame]
+ [status-im.i18n :as i18n]
+ [status-im.ui.components.action-button.action-button :as action-button]
+ [status-im.ui.components.colors :as colors]
+ [status-im.ui.components.react :as react]
+ [status-im.ui.screens.wallet.collectibles.styles :as styles]
+ [status-im.ui.components.svgimage :as svgimage]
+ [status-im.ui.screens.wallet.collectibles.views :as collectibles]))
+
+(defmethod collectibles/render-collectible :STRK [_ {:keys [external_url description name image]}]
+ [react/view {:style styles/details}
+ [react/view {:style styles/details-text}
+ [svgimage/svgimage {:style styles/details-image
+ :source {:uri image
+ :k 1.4}}]
+ [react/view {:flex 1 :justify-content :center}
+ [react/text {:style styles/details-name}
+ name]
+ [react/text
+ description]]]
+ [action-button/action-button
+ {:label (i18n/label :t/view-cryptostrikers)
+ :icon :icons/address
+ :icon-opts {:color colors/blue}
+ :accessibility-label :open-collectible-button
+ :on-press #(re-frame/dispatch [:open-browser {:url external_url}])}]])
diff --git a/src/status_im/ui/screens/wallet/collectibles/etheremon.cljs b/src/status_im/ui/screens/wallet/collectibles/etheremon.cljs
deleted file mode 100644
index 312e4ef278..0000000000
--- a/src/status_im/ui/screens/wallet/collectibles/etheremon.cljs
+++ /dev/null
@@ -1,37 +0,0 @@
-(ns status-im.ui.screens.wallet.collectibles.etheremon
- (:require [re-frame.core :as re-frame]
- [status-im.i18n :as i18n]
- [status-im.ui.components.action-button.action-button :as action-button]
- [status-im.ui.components.colors :as colors]
- [status-im.ui.components.react :as react]
- [status-im.ui.screens.wallet.collectibles.styles :as styles]
- [status-im.ui.screens.wallet.collectibles.views :as collectibles]
- [status-im.utils.http :as http]
- [status-im.ui.components.svgimage :as svgimage]))
-
-(def emona :EMONA)
-
-(defmethod collectibles/load-collectible-fx emona [_ id]
- {:http-get {:url (str "https://www.etheremon.com/api/monster/get_data?monster_ids=" id)
- :success-event-creator (fn [o]
- [:load-collectible-success emona (:data (http/parse-payload o))])
- :failure-event-creator (fn [o]
- [:load-collectible-failure emona {id (http/parse-payload o)}])}})
-
-(def base-url "https://www.etheremon.com/#/mons/")
-
-(defmethod collectibles/render-collectible emona [_ {:keys [monster_id user_defined_name image]}]
- [react/view {:style styles/details}
- [react/view {:style styles/details-text}
- [react/view {:flex 1}
- [svgimage/svgimage {:style styles/details-image
- :source {:uri image
- :k 2}}]]
- [react/view {:flex 1 :justify-content :center}
- [react/text {:style styles/details-name}
- user_defined_name]]]
- [action-button/action-button {:label (i18n/label :t/view-etheremon)
- :icon :icons/address
- :icon-opts {:color colors/blue}
- :accessibility-label :open-collectible-button
- :on-press #(re-frame/dispatch [:open-browser {:url (str base-url monster_id)}])}]])
diff --git a/src/status_im/ui/screens/wallet/collectibles/etheremon/events.cljs b/src/status_im/ui/screens/wallet/collectibles/etheremon/events.cljs
new file mode 100644
index 0000000000..396bd56ba9
--- /dev/null
+++ b/src/status_im/ui/screens/wallet/collectibles/etheremon/events.cljs
@@ -0,0 +1,12 @@
+(ns status-im.ui.screens.wallet.collectibles.etheremon.events
+ (:require [status-im.ui.screens.wallet.collectibles.events :as collectibles]
+ [status-im.utils.http :as http]))
+
+(def emona :EMONA)
+
+(defmethod collectibles/load-collectible-fx emona [_ id]
+ {:http-get {:url (str "https://www.etheremon.com/api/monster/get_data?monster_ids=" id)
+ :success-event-creator (fn [o]
+ [:load-collectible-success emona (:data (http/parse-payload o))])
+ :failure-event-creator (fn [o]
+ [:load-collectible-failure emona {id (http/parse-payload o)}])}})
\ No newline at end of file
diff --git a/src/status_im/ui/screens/wallet/collectibles/etheremon/views.cljs b/src/status_im/ui/screens/wallet/collectibles/etheremon/views.cljs
new file mode 100644
index 0000000000..c1a09a034b
--- /dev/null
+++ b/src/status_im/ui/screens/wallet/collectibles/etheremon/views.cljs
@@ -0,0 +1,27 @@
+(ns status-im.ui.screens.wallet.collectibles.etheremon.views
+ (:require [re-frame.core :as re-frame]
+ [status-im.i18n :as i18n]
+ [status-im.ui.components.action-button.action-button :as action-button]
+ [status-im.ui.components.colors :as colors]
+ [status-im.ui.components.react :as react]
+ [status-im.ui.screens.wallet.collectibles.styles :as styles]
+ [status-im.ui.components.svgimage :as svgimage]
+ [status-im.ui.screens.wallet.collectibles.views :as collectibles]))
+
+(defmethod collectibles/render-collectible :EMONA [_ {:keys [user_defined_name image class_id]}]
+ [react/view {:style styles/details}
+ [react/view {:style styles/details-text}
+ [react/view {:flex 1}
+ [svgimage/svgimage {:style styles/details-image
+ :source {:uri image
+ :k 2}}]]
+ [react/view {:flex 1 :justify-content :center}
+ [react/text {:style styles/details-name}
+ user_defined_name]]]
+ [action-button/action-button
+ {:label (i18n/label :t/view-etheremon)
+ :icon :icons/address
+ :icon-opts {:color colors/blue}
+ :accessibility-label :open-collectible-button
+ :on-press #(re-frame/dispatch [:open-browser
+ {:url (str "https://www.etheremon.com/#/mons/" class_id)}])}]])
diff --git a/src/status_im/ui/screens/wallet/collectibles/events.cljs b/src/status_im/ui/screens/wallet/collectibles/events.cljs
index c957f8e47a..e7d1d797e4 100644
--- a/src/status_im/ui/screens/wallet/collectibles/events.cljs
+++ b/src/status_im/ui/screens/wallet/collectibles/events.cljs
@@ -1,6 +1,45 @@
(ns status-im.ui.screens.wallet.collectibles.events
(:require [re-frame.core :as re-frame]
- [status-im.utils.handlers :as handlers]))
+ [status-im.utils.handlers :as handlers]
+ [status-im.utils.ethereum.erc721 :as erc721]
+ [status-im.utils.ethereum.tokens :as tokens]
+ [status-im.utils.money :as money]))
+
+(defmulti load-collectible-fx (fn [symbol _] symbol))
+
+(defmethod load-collectible-fx :default [_ _] nil)
+
+(defmulti load-collectibles-fx (fn [_ symbol _ _] symbol))
+
+(defmethod load-collectibles-fx :default [web3 symbol items-number address]
+ {:load-collectibles-fx [web3 symbol items-number address]})
+
+(handlers/register-handler-fx
+ :show-collectibles-list
+ (fn [{:keys [db]} [_ address {:keys [symbol amount] :as collectible}]]
+ (let [items-number (money/to-number amount)
+ loaded-items-number (count (get-in db [:collectibles symbol]))]
+ (merge (when (not= items-number loaded-items-number)
+ (load-collectibles-fx (:web3 db) symbol items-number address))
+ {:dispatch [:navigate-to :collectibles-list collectible]}))))
+
+(defn load-token [web3 i items-number contract address symbol]
+ (when (< i items-number)
+ (erc721/token-of-owner-by-index web3 contract address i
+ (fn [v1 v2]
+ (load-token web3 (inc i) items-number contract address symbol)
+ (re-frame/dispatch [:load-collectible symbol (.toNumber v2)])))))
+
+(re-frame/reg-fx
+ :load-collectibles-fx
+ (fn [[web3 symbol items-number address]]
+ (let [contract (:address (tokens/symbol->token :mainnet symbol))]
+ (load-token web3 0 items-number contract address symbol))))
+
+(handlers/register-handler-fx
+ :load-collectible
+ (fn [_ [_ symbol token-id]]
+ (load-collectible-fx symbol token-id)))
(handlers/register-handler-fx
:load-collectible-success
diff --git a/src/status_im/ui/screens/wallet/collectibles/subs.cljs b/src/status_im/ui/screens/wallet/collectibles/subs.cljs
index b0c88f4bcf..d9a7428946 100644
--- a/src/status_im/ui/screens/wallet/collectibles/subs.cljs
+++ b/src/status_im/ui/screens/wallet/collectibles/subs.cljs
@@ -1,6 +1,11 @@
(ns status-im.ui.screens.wallet.collectibles.subs
(:require [re-frame.core :as re-frame]))
-(re-frame/reg-sub :collectibles
- (fn [db [_ s]]
- (vals (get-in db [:collectibles s]))))
+(re-frame/reg-sub :collectibles :collectibles)
+
+(re-frame/reg-sub
+ :screen-collectibles
+ :<- [:collectibles]
+ :<- [:get-screen-params]
+ (fn [[collectibles {:keys [symbol]}]]
+ (mapv #(assoc (second %) :id (first %)) (get collectibles symbol))))
\ No newline at end of file
diff --git a/src/status_im/ui/screens/wallet/collectibles/views.cljs b/src/status_im/ui/screens/wallet/collectibles/views.cljs
index 0c60d6b1c1..f978ff7c3e 100644
--- a/src/status_im/ui/screens/wallet/collectibles/views.cljs
+++ b/src/status_im/ui/screens/wallet/collectibles/views.cljs
@@ -1,7 +1,6 @@
(ns status-im.ui.screens.wallet.collectibles.views
(:require-macros [status-im.utils.views :refer [defview letsubs]])
- (:require [re-frame.core :as re-frame]
- [status-im.ui.components.colors :as colors]
+ (:require [status-im.ui.components.colors :as colors]
[status-im.ui.components.list.views :as list]
[status-im.ui.components.react :as react]
[status-im.ui.components.status-bar.view :as status-bar]
@@ -9,33 +8,24 @@
[status-im.ui.components.toolbar.view :as toolbar]
[status-im.ui.screens.wallet.collectibles.styles :as styles]))
-(defmulti load-collectible-fx (fn [symbol _] symbol))
-
-(defmethod load-collectible-fx :default [_ _] nil)
-
-(defmulti load-collectibles-fx (fn [_ symbol _ _] symbol))
-
-(defmethod load-collectibles-fx :default [web3 symbol i address]
- {:load-collectibles [web3 symbol i address]})
-
(defmulti render-collectible (fn [symbol _] symbol))
(defmethod render-collectible :default [symbol {:keys [id name]}]
[react/view {:style styles/default-collectible}
[react/text (str (clojure.core/name symbol) " #" (or id name))]])
-(defview display-collectible []
- (letsubs [{:keys [name symbol]} [:get-screen-params]]
- (let [collectibles @(re-frame/subscribe [:collectibles symbol])]
- [react/view {:style component.styles/flex}
- (if (seq collectibles)
- [react/view {:style component.styles/flex}
- [status-bar/status-bar]
- [toolbar/toolbar {}
- toolbar/default-nav-back
- [toolbar/content-title name]]
- [list/flat-list {:data collectibles
- :key-fn (comp str :id)
- :render-fn #(render-collectible symbol %)}]]
- [react/view {:style styles/loading-indicator}
- [react/activity-indicator {:animating true :size :large :color colors/blue}]])])))
+(defview collectibles-list []
+ (letsubs [{:keys [name symbol]} [:get-screen-params]
+ collectibles [:screen-collectibles]]
+ [react/view {:style component.styles/flex}
+ [react/view {:style component.styles/flex}
+ [status-bar/status-bar]
+ [toolbar/toolbar {}
+ toolbar/default-nav-back
+ [toolbar/content-title name]]
+ (if (seq collectibles)
+ [list/flat-list {:data collectibles
+ :key-fn (comp str :id)
+ :render-fn #(render-collectible symbol %)}]
+ [react/view {:style styles/loading-indicator}
+ [react/activity-indicator {:animating true :size :large :color colors/blue}]])]]))
\ No newline at end of file
diff --git a/src/status_im/ui/screens/wallet/events.cljs b/src/status_im/ui/screens/wallet/events.cljs
index 536c2979e4..8814b45a3b 100644
--- a/src/status_im/ui/screens/wallet/events.cljs
+++ b/src/status_im/ui/screens/wallet/events.cljs
@@ -14,8 +14,6 @@
status-im.ui.screens.wallet.request.events
[status-im.constants :as constants]
[status-im.ui.screens.navigation :as navigation]
- [status-im.ui.screens.wallet.collectibles.views :as collectibles]
- [status-im.utils.ethereum.erc721 :as erc721]
[status-im.utils.money :as money]))
(defn get-balance [{:keys [web3 account-id on-success on-error]}]
@@ -281,23 +279,4 @@
(fn [{:keys [db]}]
{:db (-> db
(assoc-in [:wallet :send-transaction] {})
- (navigation/navigate-back))}))
-
-(handlers/register-handler-fx
- :wallet/show-collectibles
- (fn [{:keys [db]} [_ i address {:keys [symbol] :as m}]]
- (assoc (collectibles/load-collectibles-fx (:web3 db) symbol i address)
- :dispatch [:navigate-to :display-collectible m])))
-
-(re-frame/reg-fx
- :load-collectibles
- (fn [[web3 symbol i address]]
- (dotimes [n i]
- (erc721/token-of-owner-by-index web3 (:address (tokens/symbol->token :mainnet symbol)) address n
- #(re-frame/dispatch [:load-collectible symbol (.toNumber %2)])))))
-
-(handlers/register-handler-fx
- :load-collectible
- (fn [{db :db} [_ symbol id]]
- (assoc (collectibles/load-collectible-fx symbol id)
- :db db)))
+ (navigation/navigate-back))}))
\ No newline at end of file
diff --git a/src/status_im/ui/screens/wallet/views.cljs b/src/status_im/ui/screens/wallet/views.cljs
index 74bdfc1ebf..25c5d9615d 100644
--- a/src/status_im/ui/screens/wallet/views.cljs
+++ b/src/status_im/ui/screens/wallet/views.cljs
@@ -11,9 +11,9 @@
[status-im.ui.screens.wallet.styles :as styles]
[status-im.ui.screens.wallet.utils :as wallet.utils]
[status-im.utils.money :as money]
- status-im.ui.screens.wallet.collectibles.cryptokitties
- status-im.ui.screens.wallet.collectibles.cryptostrikers
- status-im.ui.screens.wallet.collectibles.etheremon))
+ status-im.ui.screens.wallet.collectibles.etheremon.views
+ status-im.ui.screens.wallet.collectibles.cryptostrikers.views
+ status-im.ui.screens.wallet.collectibles.cryptokitties.views))
(defn toolbar-view []
[toolbar/toolbar {:style styles/toolbar :flat? true}
@@ -88,11 +88,12 @@
[list/item-icon {:icon :icons/forward
:icon-opts {:color :gray}}])
-(defn- render-collectible [address-hex {:keys [symbol name icon amount] :as m}]
- (let [i (money/to-fixed amount)
- details? (pos? i)]
- [react/touchable-highlight (when details?
- {:on-press #(re-frame/dispatch [:wallet/show-collectibles i address-hex m])})
+(defn- render-collectible [address-hex {:keys [symbol name icon amount] :as collectible}]
+ (let [items-number (money/to-fixed amount)
+ details? (pos? items-number)]
+ [react/touchable-highlight
+ (when details?
+ {:on-press #(re-frame/dispatch [:show-collectibles-list address-hex collectible])})
[react/view {:style styles/asset-item-container}
[list/item
[list/item-image icon]
@@ -100,8 +101,9 @@
[react/text {:style styles/asset-item-value
:number-of-lines 1
:ellipsize-mode :tail
- :accessibility-label (str (-> symbol clojure.core/name clojure.string/lower-case) "-collectible-value-text")}
- (or i 0)]
+ :accessibility-label (str (-> symbol clojure.core/name clojure.string/lower-case)
+ "-collectible-value-text")}
+ (or items-number "...")]
[react/text {:style styles/asset-item-currency
:number-of-lines 1}
name]]
diff --git a/src/status_im/utils/ethereum/erc721.cljs b/src/status_im/utils/ethereum/erc721.cljs
index 40ce68f174..bd8a1ef1e8 100644
--- a/src/status_im/utils/ethereum/erc721.cljs
+++ b/src/status_im/utils/ethereum/erc721.cljs
@@ -6,5 +6,9 @@
(defn token-of-owner-by-index [web3 contract address index cb]
(ethereum/call web3
- (ethereum/call-params contract "tokenOfOwnerByIndex(address,uint256)" (ethereum/normalized-address address) index)
+ (ethereum/call-params
+ contract
+ "tokenOfOwnerByIndex(address,uint256)"
+ (ethereum/normalized-address address)
+ (ethereum/int->hex index))
#(cb %1 (ethereum/hex->bignumber %2))))
diff --git a/src/status_im/utils/ethereum/tokens.cljs b/src/status_im/utils/ethereum/tokens.cljs
index 55d0e2246b..adb75ca53a 100644
--- a/src/status_im/utils/ethereum/tokens.cljs
+++ b/src/status_im/utils/ethereum/tokens.cljs
@@ -1,6 +1,7 @@
(ns status-im.utils.ethereum.tokens
(:require-macros [status-im.utils.ethereum.macros :refer [resolve-icons]])
- (:require [clojure.string :as string]))
+ (:require [clojure.string :as string]
+ [status-im.utils.config :as config]))
(defn- asset-border [color]
{:border-color color :border-width 1 :border-radius 32})
@@ -386,21 +387,18 @@
:address "0x9B11EFcAAA1890f6eE52C6bB7CF8153aC5d74139"
:decimals 8
:hidden? true}
- {:symbol :CK
- :nft? true
- :name "CryptoKitties"
- :address "0x06012c8cf97bead5deae237070f9587f8e7a266d"
- :hidden? true}
- {:symbol :EMONA
- :nft? true
- :name "Etheremon"
- :address "0xB2c0782ae4A299f7358758B2D15dA9bF29E1DD99"
- :hidden? true}
- {:symbol :STRK
- :nft? true
- :name "CryptoStrikers"
- :address "0xdcaad9fd9a74144d226dbf94ce6162ca9f09ed7e"
- :hidden? true}])
+ {:symbol :CK
+ :nft? true
+ :name "CryptoKitties"
+ :address "0x06012c8cf97bead5deae237070f9587f8e7a266d"}
+ {:symbol :EMONA
+ :nft? true
+ :name "Etheremon"
+ :address "0xB2c0782ae4A299f7358758B2D15dA9bF29E1DD99"}
+ {:symbol :STRK
+ :nft? true
+ :name "CryptoStrikers"
+ :address "0xdcaad9fd9a74144d226dbf94ce6162ca9f09ed7e"}])
:testnet
(resolve-icons :testnet
[{:name "Status Test Token"
diff --git a/src/status_im/utils/http.cljs b/src/status_im/utils/http.cljs
index c5437894c4..1482a92677 100644
--- a/src/status_im/utils/http.cljs
+++ b/src/status_im/utils/http.cljs
@@ -70,3 +70,6 @@
:keywordize-keys true)
(catch :default _
(log/debug (str "Failed to parse " o))))))
+
+(defn url-sanitized? [uri]
+ (not (nil? (re-find #"^(https:)([/|.|\w|\s|-])*\.(?:jpg|svg|png)$" uri))))
\ No newline at end of file
diff --git a/src/status_im/utils/money.cljs b/src/status_im/utils/money.cljs
index 43b118dc09..c625cd100e 100644
--- a/src/status_im/utils/money.cljs
+++ b/src/status_im/utils/money.cljs
@@ -76,6 +76,10 @@
(when bn
(.toFixed bn)))
+(defn to-number [bn]
+ (when bn
+ (.toNumber bn)))
+
(defn wei->str [unit n]
(str (to-fixed (wei-> unit n)) " " (string/upper-case (name unit))))
diff --git a/test/cljs/status_im/test/runner.cljs b/test/cljs/status_im/test/runner.cljs
index a197cf4bb7..afd3f66969 100644
--- a/test/cljs/status_im/test/runner.cljs
+++ b/test/cljs/status_im/test/runner.cljs
@@ -43,6 +43,7 @@
[status-im.test.utils.prices]
[status-im.test.utils.keychain.core]
[status-im.test.utils.universal-links.core]
+ [status-im.test.utils.http]
[status-im.test.ui.screens.events]
[status-im.test.ui.screens.accounts.login.events]))
@@ -98,5 +99,6 @@
'status-im.test.utils.prices
'status-im.test.utils.keychain.core
'status-im.test.utils.universal-links.core
+ 'status-im.test.utils.http
'status-im.test.ui.screens.events
'status-im.test.ui.screens.accounts.login.events)
diff --git a/test/cljs/status_im/test/utils/http.cljs b/test/cljs/status_im/test/utils/http.cljs
new file mode 100644
index 0000000000..003f747736
--- /dev/null
+++ b/test/cljs/status_im/test/utils/http.cljs
@@ -0,0 +1,40 @@
+(ns status-im.test.utils.http
+ (:require [cljs.test :refer-macros [deftest is testing]]
+ [status-im.utils.http :as http]))
+
+(deftest url-sanitize-check
+ (testing "https://storage.googleapis.com/ck-kitty-image/0x06012c8cf97bead5deae237070f9587f8e7a266d/818934.svg"
+ (testing "it returns true"
+ (is (http/url-sanitized? "https://storage.googleapis.com/ck-kitty-image/0x06012c8cf97bead5deae237070f9587f8e7a266d/818934.svg"))))
+
+ (testing "https://www.cryptostrikers.com/assets/images/cards/017.svg"
+ (testing "it returns true"
+ (is (http/url-sanitized? "https://www.cryptostrikers.com/assets/images/cards/017.svg"))))
+
+ (testing "https://www.etheremon.com/assets/images/mons_origin/025.png"
+ (testing "it returns true"
+ (is (http/url-sanitized? "https://www.etheremon.com/assets/images/mons_origin/025.png"))))
+
+ (testing "http://www.etheremon.com/assets/images/mons_origin/025.png"
+ (testing "it returns false"
+ (is (not (http/url-sanitized? "http://www.etheremon.com/assets/images/mons_origin/025.png")))))
+
+ (testing "xxx:x \\\\x0Aonerror=javascript:alert(1)"
+ (testing "it returns false"
+ (is (not (http/url-sanitized? "xxx:x \\\\x0Aonerror=javascript:alert(1)")))))
+
+ (testing "https://www.etheremon.com/assets/images/mons_origin/025.png'<script>alert('123');</script>"
+ (testing "it returns false"
+ (is (not (http/url-sanitized? "https://www.etheremon.com/assets/images/mons_origin/025.png'<script>alert('123');</script>")))))
+
+ (testing "https://www.etheremon.com/assets/images/mons'<script>alert('123');</script>origin/025.png"
+ (testing "it returns false"
+ (is (not (http/url-sanitized? "https://www.etheremon.com/assets/images/mons'<script>alert('123');</script>origin/025.png")))))
+
+ (testing "https://www.etheremon.com/assets/images/mons_origin/025.png'>"
+ (testing "it returns false"
+ (is (not (http/url-sanitized? "https://www.etheremon.com/assets/images/mons_origin/025.png'>")))))
+
+ (testing "https://www.etheremon.com/assets/images/mons'>origin/025.png"
+ (testing "it returns false"
+ (is (not (http/url-sanitized? "https://www.etheremon.com/assets/images/mons'>origin/025.png"))))))
\ No newline at end of file