[#4663] Added collectibles and CK support
Signed-off-by: Pedro Pombeiro <pombeirp@users.noreply.github.com>
After Width: | Height: | Size: 3.3 KiB |
After Width: | Height: | Size: 9.1 KiB |
After Width: | Height: | Size: 16 KiB |
After Width: | Height: | Size: 2.7 KiB |
After Width: | Height: | Size: 7.5 KiB |
After Width: | Height: | Size: 14 KiB |
After Width: | Height: | Size: 2.6 KiB |
After Width: | Height: | Size: 6.9 KiB |
After Width: | Height: | Size: 13 KiB |
|
@ -427,6 +427,7 @@
|
|||
:wallet-exchange "Exchange"
|
||||
:wallet-asset "Asset"
|
||||
:wallet-assets "Assets"
|
||||
:wallet-collectibles "Collectibles"
|
||||
:wallet-add-asset "Add asset"
|
||||
:wallet-total-value "Total value"
|
||||
:wallet-settings "Wallet settings"
|
||||
|
@ -589,6 +590,9 @@
|
|||
:scan-qr-code "Scan a QR code with a wallet address"
|
||||
:reset-default "Reset to default"
|
||||
|
||||
:view-cryptokitties "View in CryptoKitties"
|
||||
:cryptokitty-name "CryptoKitty #"
|
||||
|
||||
;; network settings
|
||||
:new-network "New network"
|
||||
:add-network "Add network"
|
||||
|
|
|
@ -110,6 +110,8 @@
|
|||
|
||||
(spec/def :navigation.screen-params/usage-data vector?)
|
||||
|
||||
(spec/def :navigation.screen-params/display-collectible map?)
|
||||
|
||||
(spec/def :navigation/screen-params (spec/nilable (allowed-keys :opt-un [:navigation.screen-params/network-details
|
||||
:navigation.screen-params/browser
|
||||
:navigation.screen-params/profile-qr-viewer
|
||||
|
@ -117,7 +119,8 @@
|
|||
:navigation.screen-params/group-contacts
|
||||
:navigation.screen-params/edit-contact-group
|
||||
:navigation.screen-params/dapp-description
|
||||
:navigation.screen-params/usage-data])))
|
||||
:navigation.screen-params/usage-data
|
||||
:navigation.screen-params/display-collectible])))
|
||||
|
||||
(spec/def :desktop/desktop (spec/nilable any?))
|
||||
|
||||
|
@ -129,6 +132,9 @@
|
|||
(spec/def :inbox/fetching? (spec/nilable boolean?))
|
||||
(spec/def :inbox/current-id (spec/nilable string?))
|
||||
|
||||
(spec/def ::collectible (spec/nilable map?))
|
||||
(spec/def ::collectibles (spec/nilable map?))
|
||||
|
||||
;;;;NODE
|
||||
|
||||
(spec/def :node/after-start (spec/nilable vector?))
|
||||
|
@ -249,4 +255,6 @@
|
|||
:prices/prices
|
||||
:prices/prices-loading?
|
||||
:notifications/notifications
|
||||
::device-UUID]))
|
||||
::device-UUID
|
||||
::collectible
|
||||
::collectibles]))
|
||||
|
|
|
@ -18,6 +18,7 @@
|
|||
status-im.ui.screens.profile.events
|
||||
status-im.ui.screens.qr-scanner.events
|
||||
status-im.ui.screens.wallet.events
|
||||
status-im.ui.screens.wallet.collectibles.events
|
||||
status-im.ui.screens.wallet.send.events
|
||||
status-im.ui.screens.wallet.settings.events
|
||||
status-im.ui.screens.wallet.transactions.events
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
status-im.ui.screens.contacts.subs
|
||||
status-im.ui.screens.group.subs
|
||||
status-im.ui.screens.wallet.subs
|
||||
status-im.ui.screens.wallet.collectibles.subs
|
||||
status-im.ui.screens.wallet.request.subs
|
||||
status-im.ui.screens.wallet.send.subs
|
||||
status-im.ui.screens.wallet.transactions.subs
|
||||
|
|
|
@ -24,6 +24,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.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]]
|
||||
|
@ -51,6 +52,7 @@
|
|||
|
||||
(defn get-main-component [view-id]
|
||||
(case view-id
|
||||
:display-collectible collectibles/display-collectible
|
||||
:intro intro
|
||||
:create-account create-account
|
||||
:usage-data usage-data
|
||||
|
|
|
@ -0,0 +1,90 @@
|
|||
(ns status-im.ui.screens.wallet.collectibles.cryptokitties
|
||||
(:require-macros [status-im.utils.views :refer [defview letsubs]])
|
||||
(: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.utils.handlers :as handlers])
|
||||
(:refer-clojure :exclude [symbol]))
|
||||
|
||||
(def symbol :CK)
|
||||
|
||||
(handlers/register-handler-fx
|
||||
:load-kitty-success
|
||||
[re-frame/trim-v]
|
||||
(fn [{db :db} [[id collectible]]]
|
||||
{:db (update-in db [:collectibles symbol] assoc id collectible)}))
|
||||
|
||||
(handlers/register-handler-fx
|
||||
:load-kitty-failure
|
||||
[re-frame/trim-v]
|
||||
(fn [{db :db} [_]]
|
||||
{:db db}))
|
||||
|
||||
(defn parse-payload [o]
|
||||
(js->clj (js/JSON.parse o)
|
||||
:keywordize-keys true))
|
||||
|
||||
(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-kitty-success [id (parse-payload o)]])
|
||||
:failure-event-creator (fn [o]
|
||||
[:load-kitty-failure [id (parse-payload o)]])})
|
||||
ids)}))
|
||||
|
||||
(defn kitties-url [address]
|
||||
(str "https://api.cryptokitties.co/kitties?offset=0&limit=100&owner_wallet_address=" address "&parents=false"))
|
||||
|
||||
(handlers/register-handler-fx
|
||||
:load-kitties-success
|
||||
(fn [{db :db} [_ ids]]
|
||||
{:db db
|
||||
:dispatch [:load-kitties ids]}))
|
||||
|
||||
(defmethod collectibles/load-collectibles-fx symbol [_ address]
|
||||
{:http-get {:url (kitties-url address)
|
||||
:success-event-creator (fn [o]
|
||||
[:load-kitties-success (map :id (:kitties (parse-payload o)))])
|
||||
:failure-event-creator (fn [o]
|
||||
[:load-collectibles-failure (parse-payload o)])
|
||||
:timeout-ms 10000}})
|
||||
|
||||
(defn- kitty-name [{:keys [id name]}]
|
||||
(or name (str (i18n/label :t/cryptokitty-name) id)))
|
||||
|
||||
(def view-style
|
||||
{:padding-vertical 10})
|
||||
|
||||
(def text-style
|
||||
{:flex 1
|
||||
:flex-direction :row
|
||||
:align-items :center
|
||||
:padding-horizontal 16})
|
||||
|
||||
(def name-style
|
||||
{:color colors/black
|
||||
:margin-bottom 10})
|
||||
|
||||
(defmethod collectibles/render-collectible symbol [_ {:keys [id bio image_url] :as m}]
|
||||
[react/view {:style view-style}
|
||||
[react/view {:style text-style}
|
||||
;; TODO reenable image once SVG is supported
|
||||
#_[react/image {:style {:width 80 :height 80 :margin 10 :background-color "red"} :source {:uri image_url}}]
|
||||
[react/view {}
|
||||
[react/text {:style name-style}
|
||||
(kitty-name m)]
|
||||
[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)}])}]])
|
|
@ -0,0 +1,9 @@
|
|||
(ns status-im.ui.screens.wallet.collectibles.events
|
||||
(:require [re-frame.core :as re-frame]
|
||||
[status-im.utils.handlers :as handlers]))
|
||||
|
||||
(handlers/register-handler-fx
|
||||
:load-collectibles-failure
|
||||
[re-frame/trim-v]
|
||||
(fn [{db :db} [{:keys [message]}]]
|
||||
{:db (assoc db :collectibles-failure message)}))
|
|
@ -0,0 +1,10 @@
|
|||
(ns status-im.ui.screens.wallet.collectibles.styles)
|
||||
|
||||
(def default-collectible
|
||||
{:padding-left 10
|
||||
:padding-vertical 20})
|
||||
|
||||
(def loading-indicator
|
||||
{:flex 1
|
||||
:align-items :center
|
||||
:justify-content :center})
|
|
@ -0,0 +1,6 @@
|
|||
(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]))))
|
|
@ -0,0 +1,36 @@
|
|||
(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]
|
||||
[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]
|
||||
[status-im.ui.components.styles :as component.styles]
|
||||
[status-im.ui.components.toolbar.view :as toolbar]
|
||||
[status-im.ui.screens.wallet.collectibles.styles :as styles]))
|
||||
|
||||
(defmulti load-collectibles-fx (fn [symbol _] symbol))
|
||||
|
||||
(defmethod load-collectibles-fx :default [_ _] nil)
|
||||
|
||||
(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}]])])))
|
|
@ -12,7 +12,9 @@
|
|||
status-im.ui.screens.wallet.request.events
|
||||
[status-im.utils.money :as money]
|
||||
[status-im.constants :as constants]
|
||||
[status-im.ui.screens.navigation :as navigation]))
|
||||
[status-im.ui.screens.navigation :as navigation]
|
||||
[status-im.ui.screens.wallet.collectibles.views :as collectibles]
|
||||
[clojure.set :as set]))
|
||||
|
||||
(defn get-balance [{:keys [web3 account-id on-success on-error]}]
|
||||
(if (and web3 account-id)
|
||||
|
@ -99,14 +101,18 @@
|
|||
(fn [{:keys [web3 obj success-event]}]
|
||||
(ethereum/estimate-gas-web3 web3 (clj->js obj) #(re-frame/dispatch [success-event %2]))))
|
||||
|
||||
(defn tokens-symbols [v chain]
|
||||
(set/difference (set v) (set (map :symbol (tokens/nfts-for chain)))))
|
||||
|
||||
;; Handlers
|
||||
(handlers/register-handler-fx
|
||||
:update-wallet
|
||||
(fn [{{:keys [web3 account/account network network-status] {:keys [address settings]} :account/account :as db} :db} _]
|
||||
(fn [{{:keys [web3 network network-status] {:keys [address settings]} :account/account :as db} :db} _]
|
||||
(let [network (get-in db [:account/account :networks network])
|
||||
chain (ethereum/network->chain-keyword network)
|
||||
mainnet? (= :mainnet chain)
|
||||
symbols (get-in settings [:wallet :visible-tokens chain])
|
||||
assets (get-in settings [:wallet :visible-tokens chain])
|
||||
tokens (tokens-symbols (get-in settings [:wallet :visible-tokens chain]) chain)
|
||||
currency-id (or (get-in settings [:wallet :currency]) :usd)
|
||||
currency (get constants/currencies currency-id)]
|
||||
(when (not= network-status :offline)
|
||||
|
@ -116,11 +122,11 @@
|
|||
:error-event :update-balance-fail}
|
||||
:get-tokens-balance {:web3 web3
|
||||
:account-id address
|
||||
:symbols symbols
|
||||
:symbols assets
|
||||
:chain chain
|
||||
:success-event :update-token-balance-success
|
||||
:error-event :update-token-balance-fail}
|
||||
:get-prices {:from (if mainnet? (conj symbols "ETH") ["ETH"])
|
||||
:get-prices {:from (if mainnet? (conj tokens "ETH") ["ETH"])
|
||||
:to [(:code currency)]
|
||||
:success-event :update-prices-success
|
||||
:error-event :update-prices-fail}
|
||||
|
@ -268,3 +274,10 @@
|
|||
{:db (-> db
|
||||
(assoc-in [:wallet :send-transaction] {})
|
||||
(navigation/navigate-back))}))
|
||||
|
||||
(handlers/register-handler-fx
|
||||
:wallet/show-collectibles
|
||||
(fn [_ [_ address {:keys [symbol] :as m}]]
|
||||
(if-let [fx (collectibles/load-collectibles-fx symbol address)]
|
||||
(assoc fx :dispatch [:navigate-to :display-collectible m])
|
||||
{:show-error (str "Missing implementation for " (name symbol))})))
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
(ns status-im.ui.screens.wallet.styles
|
||||
(:require-macros [status-im.utils.styles :refer [defstyle]])
|
||||
(:require [status-im.ui.components.colors :as colors]
|
||||
[status-im.ui.components.react :as react]
|
||||
[status-im.ui.components.styles :as styles]))
|
||||
|
||||
;; wallet
|
||||
|
@ -153,11 +152,6 @@
|
|||
:padding-top 16
|
||||
:background-color colors/white})
|
||||
|
||||
(def asset-section-title
|
||||
{:font-size 14
|
||||
:margin-left 16
|
||||
:color colors/gray})
|
||||
|
||||
(def asset-item-container
|
||||
{:flex 1
|
||||
:flex-direction :row
|
||||
|
|
|
@ -9,7 +9,9 @@
|
|||
[status-im.ui.components.icons.vector-icons :as vector-icons]
|
||||
[status-im.ui.screens.wallet.onboarding.views :as onboarding.views]
|
||||
[status-im.ui.screens.wallet.styles :as styles]
|
||||
[status-im.ui.screens.wallet.utils :as wallet.utils]))
|
||||
[status-im.ui.screens.wallet.utils :as wallet.utils]
|
||||
[status-im.utils.money :as money]
|
||||
status-im.ui.screens.wallet.collectibles.cryptokitties))
|
||||
|
||||
(defn toolbar-view []
|
||||
[toolbar/toolbar {:style styles/toolbar :flat? true}
|
||||
|
@ -80,21 +82,56 @@
|
|||
:number-of-lines 1}
|
||||
(if @asset-value @asset-value "...")]]])))
|
||||
|
||||
(defn- asset-section [assets currency]
|
||||
(def item-icon-forward
|
||||
[list/item-icon {:icon :icons/forward
|
||||
:icon-opts {:color :gray}}])
|
||||
|
||||
(defn- render-collectible [address-hex {:keys [symbol 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 address-hex m])})
|
||||
[react/view {:style styles/asset-item-container}
|
||||
[list/item
|
||||
[list/item-image icon]
|
||||
[react/view {:style styles/asset-item-value-container}
|
||||
[react/text {:style styles/asset-item-value
|
||||
:number-of-lines 1
|
||||
:ellipsize-mode :tail
|
||||
:accessibility-label (str (-> symbol name clojure.string/lower-case) "-collectible-value-text")}
|
||||
(or i 0)]
|
||||
[react/text {:style styles/asset-item-currency
|
||||
:uppercase? true
|
||||
:number-of-lines 1}
|
||||
(name symbol)]]
|
||||
(when details?
|
||||
item-icon-forward)]]]))
|
||||
|
||||
(defn group-assets [v]
|
||||
(group-by #(if (:nft? %) :nfts :tokens) v))
|
||||
|
||||
(defn- asset-section [assets currency address-hex]
|
||||
(let [{:keys [tokens nfts]} (group-assets assets)]
|
||||
[react/view styles/asset-section
|
||||
[react/text {:style styles/asset-section-title} (i18n/label :t/wallet-assets)]
|
||||
[list/flat-list
|
||||
[list/section-list
|
||||
{:default-separator? true
|
||||
:scroll-enabled false
|
||||
:key-fn (comp str :symbol)
|
||||
:data assets
|
||||
:render-fn (render-asset currency)}]])
|
||||
:sections [{:title (i18n/label :t/wallet-assets)
|
||||
:key :assets
|
||||
:data tokens
|
||||
:render-fn (render-asset currency)}
|
||||
{:title (i18n/label :t/wallet-collectibles)
|
||||
:key :collectibles
|
||||
:data nfts
|
||||
:render-fn #(render-collectible address-hex %)}]}]]))
|
||||
|
||||
(views/defview wallet-root []
|
||||
(views/letsubs [assets [:wallet/visible-assets-with-amount]
|
||||
currency [:wallet/currency]
|
||||
portfolio-value [:portfolio-value]
|
||||
{:keys [seed-backed-up?]} [:get-current-account]]
|
||||
{:keys [seed-backed-up?]} [:get-current-account]
|
||||
address-hex [:get-current-account-hex]]
|
||||
[react/view styles/main-section
|
||||
[toolbar-view]
|
||||
[react/scroll-view {:refresh-control
|
||||
|
@ -110,7 +147,7 @@
|
|||
[backup-seed-phrase])
|
||||
[list/action-list actions
|
||||
{:container-style styles/action-section}]
|
||||
[asset-section assets currency]
|
||||
[asset-section assets currency address-hex]
|
||||
;; Hack to allow different colors for bottom scroll view (iOS only)
|
||||
[react/view {:style styles/scroll-bottom}]]]))
|
||||
|
||||
|
|
|
@ -0,0 +1,11 @@
|
|||
(ns status-im.utils.ethereum.erc721
|
||||
"
|
||||
Helper functions to interact with [ERC721](https://eips.ethereum.org/EIPS/eip-721) smart contract
|
||||
"
|
||||
(:require [status-im.utils.ethereum.core :as ethereum]
|
||||
[status-im.utils.ethereum.erc20 :as erc20]))
|
||||
|
||||
(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)
|
||||
#(cb %1 (ethereum/hex->bignumber %2))))
|
|
@ -385,7 +385,19 @@
|
|||
:name "Attention Token of Media"
|
||||
:address "0x9B11EFcAAA1890f6eE52C6bB7CF8153aC5d74139"
|
||||
:decimals 8
|
||||
:hidden? true}])
|
||||
:hidden? true}
|
||||
{:symbol :CK
|
||||
:nft? true
|
||||
:name "CryptoKitties"
|
||||
:address "0x06012c8cf97bead5deae237070f9587f8e7a266d"}
|
||||
{:symbol :EMONA
|
||||
:nft? true
|
||||
:name "EtheremonAsset"
|
||||
:address "0xB2c0782ae4A299f7358758B2D15dA9bF29E1DD99"}
|
||||
{:symbol :STRK
|
||||
:nft? true
|
||||
:name "CryptoStrikers"
|
||||
:address "0xdcaad9fd9a74144d226dbf94ce6162ca9f09ed7e"}])
|
||||
:testnet
|
||||
(resolve-icons :testnet
|
||||
[{:name "Status Test Token"
|
||||
|
@ -416,6 +428,9 @@
|
|||
(defn tokens-for [chain]
|
||||
(get all chain))
|
||||
|
||||
(defn nfts-for [chain]
|
||||
(filter :nft? (tokens-for chain)))
|
||||
|
||||
(defn sorted-tokens-for [chain]
|
||||
(->> (tokens-for chain)
|
||||
(filter #(not (:hidden? %)))
|
||||
|
|