🎨 NFT Support via OpenSea

Baic skeleton 🦴
NFT Details page. The API is not returning properties
Add traits, add opensea link, fetch nfts when new account is added
Toggle privacy options
Hide NFT behind FF
Update sgv
Fix lint and rename opensea to collectibles

Signed-off-by: Shivek Khurana <shivek@status.im>
This commit is contained in:
Shivek Khurana 2021-08-24 17:06:49 +05:30
parent 5246617c69
commit e2ddf7517f
No known key found for this signature in database
GPG Key ID: 9BEB56E6E62968C7
15 changed files with 300 additions and 77 deletions

1
.env
View File

@ -31,3 +31,4 @@ DATABASE_MANAGEMENT_ENABLED=1
METRICS_ENABLED=0
EIP1559_ENABLED=1
DELETE_MESSAGE_ENABLED=1
COLLECTIBLES_ENABLED=1

View File

@ -21,3 +21,5 @@ ENABLE_REFERRAL_INVITE=0
METRICS_ENABLED=0
EIP1559_ENABLED=1
DELETE_MESSAGE_ENABLED=0
COLLECTIBLES_ENABLED=0

View File

@ -182,6 +182,8 @@
"wallet_getFavourites" {}
"wallet_deleteCustomToken" {}
"wallet_getCryptoOnRamps" {}
"wallet_getOpenseaCollectionsByOwner" {}
"wallet_getOpenseaAssetsByOwnerAndCollection" {}
"browsers_getBrowsers" {}
"browsers_addBrowser" {}
"browsers_deleteBrowser" {}

View File

@ -106,6 +106,7 @@
(wallet/initialize-tokens custom-tokens)
(wallet/initialize-favourites favourites)
(wallet/get-pending-transactions)
(wallet/fetch-collectibles-collection)
(cond (and new-account?
(not scan-all-tokens?))
(wallet/set-zero-balances (first accounts))

View File

@ -46,3 +46,12 @@
{:db (if setting-value
(assoc-in db [:multiaccount setting] setting-value)
(update db :multiaccount dissoc setting))}))
(fx/defn toggle-opensea-nfts-visibility
{:events [::toggle-opensea-nfts-visiblity]}
[cofx visible?]
(fx/merge cofx
{:db (assoc-in (:db cofx) [:multiaccount :opensea-enabled?] visible?)
;; need to add fully qualified namespace to counter circular deps
:dispatch [:status-im.wallet.core/fetch-collectibles-collection]}
(multiaccount-update :opensea-enabled? visible? {})))

View File

@ -152,8 +152,6 @@
;;wallet
(reg-root-key-sub :wallet :wallet)
(reg-root-key-sub :prices :prices)
(reg-root-key-sub :collectibles :collectibles)
(reg-root-key-sub :wallet/all-tokens :wallet/all-tokens)
(reg-root-key-sub :prices-loading? :prices-loading?)
(reg-root-key-sub :wallet.transactions :wallet.transactions)
(reg-root-key-sub :wallet/custom-token-screen :wallet/custom-token-screen)
@ -170,6 +168,10 @@
(reg-root-key-sub :wallet/fast-base-fee :wallet/fast-base-fee)
(reg-root-key-sub :wallet/current-priority-fee :wallet/current-priority-fee)
(reg-root-key-sub :wallet/transactions-management-enabled? :wallet/transactions-management-enabled?)
(reg-root-key-sub :wallet/all-tokens :wallet/all-tokens)
(reg-root-key-sub :wallet/collectible-collections :wallet/collectible-collections)
(reg-root-key-sub :wallet/collectible-assets :wallet/collectible-assets)
(reg-root-key-sub :wallet/current-collectible-asset :wallet/current-collectible-asset)
;;commands
(reg-root-key-sub :commands/select-account :commands/select-account)
@ -645,6 +647,12 @@
(fn [multiaccount]
(fleet/current-fleet-sub multiaccount)))
(re-frame/reg-sub
:opensea-enabled?
:<- [:multiaccount]
(fn [{:keys [opensea-enabled?]}]
(boolean opensea-enabled?)))
(re-frame/reg-sub
:log-level/current-log-level
:<- [:multiaccount]
@ -761,12 +769,6 @@
;;CHAT ==============================================================================================================
(re-frame/reg-sub
:get-collectible-token
:<- [:collectibles]
(fn [collectibles [_ {:keys [symbol token]}]]
(get-in collectibles [(keyword symbol) (js/parseInt token)])))
(re-frame/reg-sub
:chats/chat
:<- [:chats/active-chats]
@ -1812,6 +1814,19 @@
(string/lower-case search-filter))
favs)))))
(re-frame/reg-sub
:wallet/collectible-collection
:<- [:wallet/collectible-collections]
(fn [all-collections [_ address]]
(when address
(get all-collections (string/lower-case address) []))))
(re-frame/reg-sub
:wallet/collectible-assets-by-collection-and-address
:<- [:wallet/collectible-assets]
(fn [all-assets [_ address collectible-slug]]
(get-in all-assets [address collectible-slug] [])))
;;ACTIVITY CENTER NOTIFICATIONS ========================================================================================
(defn- group-notifications-by-date
@ -2094,14 +2109,6 @@
:<- [:wallet]
:request-transaction)
(re-frame/reg-sub
:screen-collectibles
:<- [:collectibles]
:<- [:get-screen-params]
(fn [[collectibles {:keys [symbol]}]]
(when-let [v (get collectibles symbol)]
(mapv #(assoc (second %) :id (first %)) v))))
;;UI ==============================================================================================================
(re-frame/reg-sub

View File

@ -5,11 +5,11 @@
[status-im.ui.components.react :as react]
[status-im.ui.components.icons.icons :as icons]))
(defn drop-down-icon [opened?]
(defn drop-down-icon [{:keys [opened? dropdown-margin-left]}]
[react/view {:flex-direction :row :align-items :center}
[icons/icon (if opened? :main-icons/dropdown-up :main-icons/dropdown)
{:container-style {:align-items :center
:margin-left 8
:margin-left dropdown-margin-left
:justify-content :center}
:resize-mode :center
:color colors/black}]])
@ -18,20 +18,30 @@
"Render collapsible section"
[_props]
(let [opened? (reagent/atom false)]
(fn [{:keys [title content icon opened disabled]}]
[react/view {:padding-vertical 8}
(if (string? title)
[quo/list-item
{:title title
:icon icon
:on-press #(swap! opened? not)
:accessory [drop-down-icon (or @opened? opened)]}]
[react/touchable-opacity {:on-press #(swap! opened? not) :disabled disabled}
[react/view {:flex-direction :row
:margin-right 14
:justify-content :space-between}
title
[drop-down-icon (or @opened? opened)]]])
(when (or @opened? opened)
content)])))
(fn [{:keys [title content icon opened disabled
padding-vertical dropdown-margin-left
on-open on-close]
:or {padding-vertical 8
dropdown-margin-left 8
on-open #()
on-close #()}}]
(let [on-press #(do
(apply (if @opened? on-close on-open) [])
(swap! opened? not))]
[react/view {:padding-vertical padding-vertical}
(if (string? title)
[quo/list-item
{:title title
:icon icon
:on-press on-press
:accessory [drop-down-icon (or @opened? opened)]}]
[react/touchable-opacity {:on-press on-press :disabled disabled}
[react/view {:flex-direction :row
:margin-right 14
:justify-content :space-between}
title
[drop-down-icon {:opened? (or @opened? opened)
:dropdown-margin-left dropdown-margin-left}]]])
(when (or @opened? opened)
content)]))))

View File

@ -7,6 +7,7 @@
[status-im.ui.components.common.common :as components.common]
[status-im.ui.components.react :as react]
[status-im.utils.config :as config]
[status-im.multiaccounts.update.core :as multiaccounts.update]
[status-im.multiaccounts.biometric.core :as biometric]
[status-im.utils.platform :as platform])
(:require-macros [status-im.utils.views :as views]))
@ -18,7 +19,8 @@
(views/letsubs [{:keys [mnemonic
preview-privacy?
messages-from-contacts-only
webview-allow-permission-requests?]} [:multiaccount]
webview-allow-permission-requests?
opensea-enabled?]} [:multiaccount]
supported-biometric-auth [:supported-biometric-auth]
keycard? [:keycard-multiaccount?]
auth-method [:auth-method]]
@ -57,6 +59,13 @@
:on-press #(re-frame/dispatch
[:multiaccounts.ui/preview-privacy-mode-switched
((complement boolean) preview-privacy?)])}]
(when config/collectibles-enabled?
[quo/list-item {:size :small
:title (i18n/label :t/enable-opensea-nfts)
:container-margin-bottom 8
:active opensea-enabled?
:accessory :switch
:on-press #(re-frame/dispatch [::multiaccounts.update/toggle-opensea-nfts-visiblity (not opensea-enabled?)])}])
[quo/list-item {:size :small
:title (i18n/label :t/chat-link-previews)
:chevron true

View File

@ -11,7 +11,7 @@
[quo.previews.main :as quo.preview]
[status-im.ui.screens.profile.contact.views :as contact]
[status-im.ui.screens.notifications-settings.views :as notifications-settings]
[status-im.ui.screens.wallet.send.views :as wallet]
[status-im.ui.screens.wallet.send.views :as wallet.send]
[status-im.ui.screens.status.new.views :as status.new]
[status-im.ui.screens.browser.bookmarks.views :as bookmarks]
[status-im.ui.screens.communities.invite :as communities.invite]
@ -653,7 +653,7 @@
:options {:topBar {:title {:text (i18n/label :t/send-transaction)}}
:swipeToDismiss false
:hardwareBackButton {:dismissModalOnPress false}}
:component wallet/prepare-send-transaction}
:component wallet.send/prepare-send-transaction}
;[Wallet] Request Transaction
{:name :request-transaction
@ -662,7 +662,7 @@
:options {:topBar {:title {:text (i18n/label :t/request-transaction)}}
:swipeToDismiss false
:hardwareBackButton {:dismissModalOnPress false}}
:component wallet/request-transaction}
:component wallet.send/request-transaction}
;[Wallet] Buy crypto
{:name :buy-crypto
@ -676,6 +676,12 @@
:options {:topBar {:visible false}}
:component wallet.buy-crypto/website}
{:name :nft-details
:insets {:bottom true}
;;TODO dynamic title
:options {:topBar {:visible false}}
:component wallet.account/nft-details}
;My Status
{:name :my-status
:insets {:bottom true}

View File

@ -6,19 +6,22 @@
[status-im.ui.components.animation :as animation]
[status-im.ui.components.colors :as colors]
[status-im.ui.components.icons.icons :as icons]
[status-im.ui.components.accordion :as accordion]
[status-im.react-native.resources :as resources]
[quo.core :as quo]
[status-im.ui.components.react :as react]
[status-im.ui.components.topbar :as topbar]
[status-im.utils.config :as config]
[status-im.ui.screens.wallet.account.styles :as styles]
[status-im.ui.screens.wallet.accounts.sheets :as sheets]
[status-im.ui.screens.wallet.accounts.views :as accounts]
[status-im.ui.screens.wallet.buy-crypto.views :as buy-crypto]
[status-im.ui.screens.wallet.transactions.views :as history]
[status-im.utils.money :as money]
[status-im.wallet.utils :as wallet.utils]
[status-im.wallet.core :as wallet]
[status-im.ui.components.tabs :as tabs]
[quo.design-system.colors :as quo-colors]
[status-im.ui.screens.wallet.components.views :as wallet.components])
[status-im.multiaccounts.update.core :as multiaccounts.update]
[status-im.utils.handlers :refer [<sub]])
(:require-macros [status-im.utils.views :as views]))
(def state (reagent/atom {:tab :assets}))
@ -77,22 +80,13 @@
colors/white-persist
#(re-frame/dispatch [:wallet/share-popover address])]]]))
(defn render-collectible [{:keys [name icon amount] :as collectible}]
(let [items-number (money/to-fixed amount)]
[quo/list-item
{:title (wallet.utils/display-symbol collectible)
:subtitle name
:icon [wallet.components/token-icon icon]
:accessory :text
:accessory-text items-number}]))
(views/defview transactions [address]
(views/letsubs [data [:wallet.transactions.history/screen address]]
[history/history-list data address]))
(defn collectibles-link []
(defn opensea-link [address]
[react/touchable-highlight
{:on-press #(re-frame/dispatch [:browser.ui/open-url "https://opensea.io/account/"])}
{:on-press #(re-frame/dispatch [:browser.ui/open-url (str "https://opensea.io/" address)])}
[react/view
{:style {:flex 1
:padding-horizontal 14
@ -108,9 +102,141 @@
{:style {:color colors/blue}}
(i18n/label :t/check-on-opensea)]]])
(defn nft-assets-skeleton [num-assets]
[:<>
(for [i (range num-assets)]
^{:key i}
[react/view {:style {:width "48%"
:margin-bottom 16}}
[react/view {:style {:flex 1
:aspect-ratio 1
:border-width 1
:background-color colors/gray-transparent-10
:border-color colors/gray-lighter
:border-radius 16}}]])])
(defn nft-assets [{:keys [num-assets address collectible-slug]}]
(let [assets (<sub [:wallet/collectible-assets-by-collection-and-address address collectible-slug])]
[react/view {:flex 1
:flex-wrap :wrap
:justify-content :space-between
:flex-direction :row
:style {:padding-horizontal 16}}
(if (seq assets)
(for [asset assets]
^{:key (:id asset)}
[react/touchable-opacity
{:style {:width "48%"
:margin-bottom 16}
:on-press #(re-frame/dispatch [::wallet/show-nft-details asset])}
[react/image {:style {:flex 1
:aspect-ratio 1
:border-width 1
:border-color colors/gray-lighter
:border-radius 16}
:source {:uri (:image_url asset)}}]])
[nft-assets-skeleton num-assets])]))
(defn nft-collections [address]
(let [collection (<sub [:wallet/collectible-collection address])]
[:<>
(for [collectible collection]
^{:key (:slug collectible)}
[accordion/section
{:title
[react/view {:flex 1}
[quo/list-item
{:title (:name collectible)
:text-size :large
:icon
[list/item-image {:style {:border-radius 40
:overflow :hidden
:border-width 1
:border-color "#EEF2F5"}
:source {:uri (:image_url collectible)}}]
:accessory :text
:accessory-text (:owned_asset_count collectible)}]]
:padding-vertical 0
:dropdown-margin-left -12
:on-open #(re-frame/dispatch [::wallet/fetch-collectible-assets-by-owner-and-collection
address
(:slug collectible)
(:owned_asset_count collectible)])
:content [nft-assets {:address address
:num-assets (:owned_asset_count collectible)
:collectible-slug (:slug collectible)}]}])]))
(defn nft-traits [traits]
[react/view {:flex 1
:margin-bottom 24
:flex-direction :row
:flex-wrap :wrap}
(for [trait traits]
^{:key (:trait_type trait)}
[react/view {:style {:border-width 1
:border-radius 12
:padding-horizontal 8
:padding-vertical 4
:margin-right 8
:margin-bottom 8
:border-color colors/gray-lighter}}
[quo/text {:size :small
:color :secondary}
(:trait_type trait)]
[quo/text {}
(:value trait)]])])
(defn nft-details []
(let [nft (<sub [:wallet/current-collectible-asset])]
[:<>
[topbar/topbar
{:title (:name nft)
:subtitle (-> nft :collection :name)
:border-bottom false
:right-accessories
[{:icon :main-icons/browser
:on-press #(re-frame/dispatch [:browser.ui/open-url (:permalink nft)])}]}]
[react/scroll-view {:padding 16}
[react/image {:source {:uri (:image_url nft)}
:style {:width "100%"
:aspect-ratio 1
:border-radius 4
:border-width 1
:border-color "#EEF2F5"}}]
[react/text {:style {:margin-top 24
:margin-bottom 16}}
(:description nft)]
[nft-traits (:traits nft)]]]))
(defn enable-opensea-view []
[react/view {:style {:border-width 1
:border-color colors/gray-lighter
:border-radius 8
:margin 16}}
[react/view {:style {:padding 16}}
[react/image {:source (resources/get-theme-image :unfurl)
:style {:align-self :center
:width 132
:height 94}}]
[quo/text {:size :small
:align :center
:style {:margin-top 6}}
(i18n/label :t/enable-opensea-nfts)]
[quo/text {:size :small
:color :secondary
:align :center
:style {:margin-top 2}}
(i18n/label :t/opensea-nfts-leak-metadata)]]
[quo/separator]
[quo/button {:on-press #(re-frame/dispatch [::multiaccounts.update/toggle-opensea-nfts-visiblity true])
:type :secondary}
(i18n/label :enable)]])
(views/defview assets-and-collections [address]
(views/letsubs [{:keys [tokens nfts]} [:wallet/visible-assets-with-values address]
currency [:wallet/currency]]
(views/letsubs [{:keys [tokens]} [:wallet/visible-assets-with-values address]
currency [:wallet/currency]
opensea-enabled? [:opensea-enabled?]
collectible-collection [:wallet/collectible-collection address]]
(let [{:keys [tab]} @state]
[react/view {:flex 1}
[react/view {:flex-direction :row :margin-bottom 8 :padding-horizontal 4}
@ -125,16 +251,21 @@
^{:key (:name item)}
[accounts/render-asset item nil nil (:code currency)])]
(= tab :nft)
[react/view
[collectibles-link]
(if (seq nfts)
[:<>
(for [item nfts]
^{:key (:name item)}
[render-collectible item nil nil (:code currency)])]
[react/view {:align-items :center :margin-top 32}
[react/text {:style {:color colors/gray}}
(i18n/label :t/no-collectibles)]])]
[:<>
[opensea-link address]
;; Hide collectibles behind a feature flag
(when config/collectibles-enabled?
(cond
(not opensea-enabled?)
[enable-opensea-view]
(and opensea-enabled? (seq collectible-collection))
[nft-collections address]
:else
[react/view {:align-items :center :margin-top 32}
[react/text {:style {:color colors/gray}}
(i18n/label :t/no-collectibles)]]))]
(= tab :history)
[transactions address])])))

View File

@ -51,6 +51,7 @@
(def metrics-enabled? (enabled? (get-config :METRICS_ENABLED "0")))
(def eip1559-enabled? (enabled? (get-config :EIP1559_ENABLED "0")))
(def delete-message-enabled? (enabled? (get-config :DELETE_MESSAGE_ENABLED "0")))
(def collectibles-enabled? (enabled? (get-config :COLLECTIBLES_ENABLED "1")))
;; CONFIG VALUES
(def log-level
@ -76,16 +77,17 @@
(def verify-ens-contract-address (get-config :VERIFY_ENS_CONTRACT_ADDRESS ((ethereum/chain-id->chain-keyword verify-ens-chain-id) ens/ens-registries)))
(def default-multiaccount
{:preview-privacy? blank-preview?
:wallet/visible-tokens {:mainnet #{:SNT}}
:currency :usd
:appearance 0
:profile-pictures-visibility 1
:log-level log-level
{:preview-privacy? blank-preview?
:wallet/visible-tokens {:mainnet #{:SNT}}
:currency :usd
:appearance 0
:profile-pictures-visibility 1
:log-level log-level
:webview-allow-permission-requests? false
:anon-metrics/should-send? false
:link-previews-enabled-sites #{}
:link-preview-request-enabled true})
:anon-metrics/should-send? false
:opensea-enabled? false
:link-previews-enabled-sites #{}
:link-preview-request-enabled true})
(defn default-visible-tokens [chain]
(get-in default-multiaccount [:wallet/visible-tokens chain]))

View File

@ -210,6 +210,7 @@
(if (utils.mobile-sync/syncing-allowed? cofx)
(wallet/set-max-block address 0)
(wallet/update-balances nil true))
(wallet/fetch-collectibles-collection)
(prices/update-prices)
(navigation/navigate-back)))))

View File

@ -211,6 +211,46 @@
:wallet/get-tokens-balances
get-token-balances)
(fx/defn collectibles-collection-fetch-success
{:events [::collectibles-collection-fetch-success]}
[{:keys [db]} address collection]
{:db (assoc-in db [:wallet/collectible-collections address] collection)})
(fx/defn fetch-collectibles-collection
{:events [::fetch-collectibles-collection]}
[{:keys [db]}]
(let [addresses (map (comp string/lower-case :address)
(get db :multiaccount/accounts))]
(when (get-in db [:multiaccount :opensea-enabled?])
{::json-rpc/call (map (fn [address]
{:method "wallet_getOpenseaCollectionsByOwner"
:params [address]
:on-error (fn [error]
(log/error "Unable to get Opensea collections" address error))
:on-success #(re-frame/dispatch [::collectibles-collection-fetch-success address %])})
addresses)})))
(fx/defn collectible-assets-fetch-success
{:events [::collectible-assets-fetch-success]}
[{:keys [db]} address collectible-slug assets]
{:db (assoc-in db [:wallet/collectible-assets address collectible-slug] assets)})
(fx/defn fetch-collectible-assets-by-owner-and-collection
{:events [::fetch-collectible-assets-by-owner-and-collection]}
[_ address collectible-slug limit]
{::json-rpc/call [{:method "wallet_getOpenseaAssetsByOwnerAndCollection"
:params [address collectible-slug limit]
:on-error (fn [error]
(log/error "Unable to get collectible assets" address error))
:on-success #(re-frame/dispatch [::collectible-assets-fetch-success address collectible-slug %])}]})
(fx/defn show-nft-details
{:events [::show-nft-details]}
[cofx asset]
(fx/merge cofx
{:db (assoc (:db cofx) :wallet/current-collectible-asset asset)}
(navigation/navigate-to :nft-details {})))
(defn rpc->token [tokens]
(reduce (fn [acc {:keys [address] :as token}]
(assoc acc

View File

@ -2,7 +2,7 @@
"_comment": "DO NOT EDIT THIS FILE BY HAND. USE 'scripts/update-status-go.sh <tag>' instead",
"owner": "status-im",
"repo": "status-go",
"version": "v0.86.2",
"commit-sha1": "d8d5d797dafdb5d042f0ec9eb27d0395840c57e6",
"src-sha256": "0pl75amwyydj58l2ff8f4nv148vnsz19nhpdas6y60g145h2nfqw"
"version": "v0.86.3",
"commit-sha1": "778d95778032be907967290e9cecc5571534a1fc",
"src-sha256": "02627x2ab6l2xcm8in2m761nhnafspfv06b55gnnf8ljwbdrms3k"
}

View File

@ -1652,5 +1652,7 @@
"normal": "Normal",
"fee-options": "Suggested fee options",
"fee-cap": "Fee cap",
"tip-cap": "Tip cap"
"tip-cap": "Tip cap",
"enable-opensea-nfts": "Enable loading NFTs from OpenSea ?",
"opensea-nfts-leak-metadata": "Loading NFTs from OpenSea shares metadata with third party services"
}