From e2ddf7517faec876ab0fe83423a860edd60db9aa Mon Sep 17 00:00:00 2001 From: Shivek Khurana Date: Tue, 24 Aug 2021 17:06:49 +0530 Subject: [PATCH] =?UTF-8?q?=F0=9F=8E=A8=20NFT=20Support=20via=20OpenSea?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- .env | 1 + .env.release | 2 + src/status_im/ethereum/json_rpc.cljs | 2 + src/status_im/multiaccounts/login/core.cljs | 1 + src/status_im/multiaccounts/update/core.cljs | 9 + src/status_im/subs.cljs | 39 ++-- src/status_im/ui/components/accordion.cljs | 46 +++-- .../privacy_and_security_settings/views.cljs | 11 +- src/status_im/ui/screens/screens.cljs | 12 +- .../ui/screens/wallet/account/views.cljs | 183 +++++++++++++++--- src/status_im/utils/config.cljs | 20 +- src/status_im/wallet/accounts/core.cljs | 1 + src/status_im/wallet/core.cljs | 40 ++++ status-go-version.json | 6 +- translations/en.json | 4 +- 15 files changed, 300 insertions(+), 77 deletions(-) diff --git a/.env b/.env index 4298c79082..0c268547f8 100644 --- a/.env +++ b/.env @@ -31,3 +31,4 @@ DATABASE_MANAGEMENT_ENABLED=1 METRICS_ENABLED=0 EIP1559_ENABLED=1 DELETE_MESSAGE_ENABLED=1 +COLLECTIBLES_ENABLED=1 diff --git a/.env.release b/.env.release index 0a3ac89d2a..6d14adedac 100644 --- a/.env.release +++ b/.env.release @@ -21,3 +21,5 @@ ENABLE_REFERRAL_INVITE=0 METRICS_ENABLED=0 EIP1559_ENABLED=1 DELETE_MESSAGE_ENABLED=0 +COLLECTIBLES_ENABLED=0 + diff --git a/src/status_im/ethereum/json_rpc.cljs b/src/status_im/ethereum/json_rpc.cljs index e35c144d11..829ea49374 100644 --- a/src/status_im/ethereum/json_rpc.cljs +++ b/src/status_im/ethereum/json_rpc.cljs @@ -182,6 +182,8 @@ "wallet_getFavourites" {} "wallet_deleteCustomToken" {} "wallet_getCryptoOnRamps" {} + "wallet_getOpenseaCollectionsByOwner" {} + "wallet_getOpenseaAssetsByOwnerAndCollection" {} "browsers_getBrowsers" {} "browsers_addBrowser" {} "browsers_deleteBrowser" {} diff --git a/src/status_im/multiaccounts/login/core.cljs b/src/status_im/multiaccounts/login/core.cljs index 0ce0bbbbfb..11de7c644e 100644 --- a/src/status_im/multiaccounts/login/core.cljs +++ b/src/status_im/multiaccounts/login/core.cljs @@ -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)) diff --git a/src/status_im/multiaccounts/update/core.cljs b/src/status_im/multiaccounts/update/core.cljs index ee4e61a011..86b6f81fb7 100644 --- a/src/status_im/multiaccounts/update/core.cljs +++ b/src/status_im/multiaccounts/update/core.cljs @@ -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? {}))) diff --git a/src/status_im/subs.cljs b/src/status_im/subs.cljs index e1ae6a9fb2..b3a0fe1f2a 100644 --- a/src/status_im/subs.cljs +++ b/src/status_im/subs.cljs @@ -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 diff --git a/src/status_im/ui/components/accordion.cljs b/src/status_im/ui/components/accordion.cljs index 5b1eb98b7d..62f099e683 100644 --- a/src/status_im/ui/components/accordion.cljs +++ b/src/status_im/ui/components/accordion.cljs @@ -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)])))) diff --git a/src/status_im/ui/screens/privacy_and_security_settings/views.cljs b/src/status_im/ui/screens/privacy_and_security_settings/views.cljs index 1c84e83859..1cdc0d5f81 100644 --- a/src/status_im/ui/screens/privacy_and_security_settings/views.cljs +++ b/src/status_im/ui/screens/privacy_and_security_settings/views.cljs @@ -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 diff --git a/src/status_im/ui/screens/screens.cljs b/src/status_im/ui/screens/screens.cljs index aff62f2d94..71e143952d 100644 --- a/src/status_im/ui/screens/screens.cljs +++ b/src/status_im/ui/screens/screens.cljs @@ -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} diff --git a/src/status_im/ui/screens/wallet/account/views.cljs b/src/status_im/ui/screens/wallet/account/views.cljs index 0a28aaf95a..8f63f69d27 100644 --- a/src/status_im/ui/screens/wallet/account/views.cljs +++ b/src/status_im/ui/screens/wallet/account/views.cljs @@ -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 [ + (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 ( + (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 ( + [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])]))) diff --git a/src/status_im/utils/config.cljs b/src/status_im/utils/config.cljs index 64e2ad8570..d14730d5a4 100644 --- a/src/status_im/utils/config.cljs +++ b/src/status_im/utils/config.cljs @@ -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])) diff --git a/src/status_im/wallet/accounts/core.cljs b/src/status_im/wallet/accounts/core.cljs index 4a58fe73f1..b920b9fa33 100644 --- a/src/status_im/wallet/accounts/core.cljs +++ b/src/status_im/wallet/accounts/core.cljs @@ -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))))) diff --git a/src/status_im/wallet/core.cljs b/src/status_im/wallet/core.cljs index 30b948cea0..2473ec0ee6 100644 --- a/src/status_im/wallet/core.cljs +++ b/src/status_im/wallet/core.cljs @@ -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 diff --git a/status-go-version.json b/status-go-version.json index 716ffd951f..135c1aa221 100644 --- a/status-go-version.json +++ b/status-go-version.json @@ -2,7 +2,7 @@ "_comment": "DO NOT EDIT THIS FILE BY HAND. USE 'scripts/update-status-go.sh ' 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" } diff --git a/translations/en.json b/translations/en.json index 763c63105f..994079da6a 100644 --- a/translations/en.json +++ b/translations/en.json @@ -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" }