diff --git a/src/quo/components/list_items/token_info/component_spec.cljs b/src/quo/components/list_items/token_info/component_spec.cljs new file mode 100644 index 0000000000..17cb1c6498 --- /dev/null +++ b/src/quo/components/list_items/token_info/component_spec.cljs @@ -0,0 +1,13 @@ +(ns quo.components.list-items.token-info.component-spec + (:require + [quo.components.list-items.token-info.view :as token-info] + [test-helpers.component :as h])) + +(h/describe "List Items: Token Info" + (h/test "Token label renders" + (h/render-with-theme-provider [token-info/view + {:token :snt + :label "Status" + :state :default + :customization-color :blue}]) + (h/is-truthy (h/get-by-text "Status")))) diff --git a/src/quo/components/list_items/token_info/schema.cljs b/src/quo/components/list_items/token_info/schema.cljs new file mode 100644 index 0000000000..6d20d0ec6e --- /dev/null +++ b/src/quo/components/list_items/token_info/schema.cljs @@ -0,0 +1,12 @@ +(ns quo.components.list-items.token-info.schema) + +(def ?schema + [:=> + [:cat + [:map + [:token [:or :string :keyword]] + [:label :string] + [:on-press {:optional true} [:maybe fn?]] + [:customization-color {:optional true} [:maybe :schema.common/customization-color]] + [:state {:optional true} [:maybe [:enum :default :active :selected :disabled]]]]] + :any]) diff --git a/src/quo/components/list_items/token_info/style.cljs b/src/quo/components/list_items/token_info/style.cljs new file mode 100644 index 0000000000..5edda49519 --- /dev/null +++ b/src/quo/components/list_items/token_info/style.cljs @@ -0,0 +1,42 @@ +(ns quo.components.list-items.token-info.style + (:require [quo.foundations.colors :as colors])) + +(defn- background-color + [state customization-color theme] + (cond + (= state :pressed) (colors/resolve-color customization-color theme 5) + (= state :active) (colors/resolve-color customization-color theme 10) + (= state :selected) (colors/resolve-color customization-color theme 5) + :else :transparent)) + +(defn container + [state customization-color theme] + {:flex-direction :row + :justify-content :space-between + :align-items :center + :padding-horizontal 12 + :padding-vertical 8 + :border-radius 12 + :height 56 + :opacity (when (= state :disabled) 0.3) + :background-color (background-color state customization-color theme)}) + +(def info + {:flex-direction :row + :align-items :center + :width "70%"}) + +(def token-info + {:height 40 + :flex 1}) + +(defn token-description-label + [theme] + {:color (colors/theme-colors colors/neutral-50 colors/neutral-40 theme)}) + +(def token-image + {:border-width 1 + :border-radius 16 + :border-color colors/neutral-80-opa-5 + :margin-right 8 + :background-color colors/neutral-80-opa-5}) diff --git a/src/quo/components/list_items/token_info/view.cljs b/src/quo/components/list_items/token_info/view.cljs new file mode 100644 index 0000000000..c09109e450 --- /dev/null +++ b/src/quo/components/list_items/token_info/view.cljs @@ -0,0 +1,50 @@ +(ns quo.components.list-items.token-info.view + (:require + [quo.components.list-items.token-info.schema :as component-schema] + [quo.components.list-items.token-info.style :as style] + [quo.components.markdown.text :as text] + [quo.components.utilities.token.view :as token] + [quo.theme :as quo.theme] + [react-native.core :as rn] + [schema.core :as schema])) + +(defn- info + [{:keys [token label]}] + (let [theme (quo.theme/use-theme)] + [rn/view {:style style/info} + (when token + [token/view + {:style style/token-image + :size :size-32 + :token token}]) + [rn/view {:style style/token-info} + [text/text + {:weight :semi-bold + :number-of-lines 1} + (if-not (empty? label) label "-")] + [text/text + {:weight :medium + :size :paragraph-2 + :style (style/token-description-label theme) + :number-of-lines 1} + token]]])) + +(defn- view-internal + [{:keys [on-press state customization-color] + :as props + :or {customization-color :blue}}] + (let [theme (quo.theme/use-theme) + [pressed? set-pressed] (rn/use-state false) + on-press-in (rn/use-callback #(set-pressed true)) + on-press-out (rn/use-callback #(set-pressed false)) + internal-state (if pressed? :pressed state)] + [rn/pressable + {:style (style/container internal-state customization-color theme) + :on-press-in on-press-in + :on-press-out on-press-out + :on-press on-press + :disabled (= state :disabled) + :accessibility-label :token} + [info props]])) + +(def view (schema/instrument #'view-internal component-schema/?schema)) diff --git a/src/quo/core.cljs b/src/quo/core.cljs index 808ff8e3d2..d34de74bc4 100644 --- a/src/quo/core.cljs +++ b/src/quo/core.cljs @@ -97,6 +97,7 @@ quo.components.list-items.quiz-item.view quo.components.list-items.saved-address.view quo.components.list-items.saved-contact-address.view + quo.components.list-items.token-info.view quo.components.list-items.token-network.view quo.components.list-items.token-value.view quo.components.list-items.user @@ -348,6 +349,7 @@ (def quiz-item quo.components.list-items.quiz-item.view/view) (def saved-address quo.components.list-items.saved-address.view/view) (def saved-contact-address quo.components.list-items.saved-contact-address.view/view) +(def token-info quo.components.list-items.token-info.view/view) (def token-network quo.components.list-items.token-network.view/view) (def token-value quo.components.list-items.token-value.view/view) (def user quo.components.list-items.user/user) diff --git a/src/quo/core_spec.cljs b/src/quo/core_spec.cljs index 0daf83ee30..8c7a97f81f 100644 --- a/src/quo/core_spec.cljs +++ b/src/quo/core_spec.cljs @@ -63,6 +63,7 @@ quo.components.list-items.quiz-item.component-spec quo.components.list-items.saved-address.component-spec quo.components.list-items.saved-contact-address.component-spec + quo.components.list-items.token-info.component-spec quo.components.list-items.token-network.component-spec quo.components.list-items.token-value.component-spec quo.components.loaders.skeleton-list.component-spec diff --git a/src/status_im/config.cljs b/src/status_im/config.cljs index 98a632fc8c..87903db797 100644 --- a/src/status_im/config.cljs +++ b/src/status_im/config.cljs @@ -10,7 +10,7 @@ (defn enabled? [v] (= "1" v)) -(goog-define INFURA_TOKEN "") +(goog-define INFURA_TOKEN "62f3cee52dbb484198e7339837e263f3") (goog-define POKT_TOKEN "3ef2018191814b7e1009b8d9") (goog-define STATUS_BUILD_PROXY_USER "") (goog-define STATUS_BUILD_PROXY_PASSWORD "") diff --git a/src/status_im/constants.cljs b/src/status_im/constants.cljs index 11d0f2efc8..321fc6f38b 100644 --- a/src/status_im/constants.cljs +++ b/src/status_im/constants.cljs @@ -601,6 +601,8 @@ :terms-and-conditions-url "https://files.paraswap.io/tos_v4.pdf"}) (def ^:const swap-providers {:paraswap swap-provider-paraswap}) +(def ^:const swap-tokens-my :my) +(def ^:const swap-tokens-popular :popular) (def ^:const token-for-fees-symbol "ETH") diff --git a/src/status_im/contexts/profile/login/events.cljs b/src/status_im/contexts/profile/login/events.cljs index 7b1da9d318..52e4979cde 100644 --- a/src/status_im/contexts/profile/login/events.cljs +++ b/src/status_im/contexts/profile/login/events.cljs @@ -104,8 +104,6 @@ [:profile.settings/get-profile-picture key-uid] (when (ff/enabled? ::ff/wallet.wallet-connect) [:dispatch [:wallet-connect/init]]) - (when (ff/enabled? ::ff/wallet.swap) - [:dispatch [:wallet.tokens/get-token-list]]) (when notifications-enabled? [:effects/push-notifications-enable])]}))) diff --git a/src/status_im/contexts/wallet/account/view.cljs b/src/status_im/contexts/wallet/account/view.cljs index 2e990cceb7..1056c35239 100644 --- a/src/status_im/contexts/wallet/account/view.cljs +++ b/src/status_im/contexts/wallet/account/view.cljs @@ -60,7 +60,9 @@ (rf/dispatch [:wallet/clean-send-data]) (rf/dispatch [:wallet/start-bridge])) :swap-action (when (ff/enabled? ::ff/wallet.swap) - #(rf/dispatch [:wallet.swap/start]))}]) + (fn [] + (rf/dispatch [:wallet.tokens/get-token-list]) + (rf/dispatch [:open-modal :screen/wallet.swap-select-asset-to-pay])))}]) [quo/tabs {:style style/tabs :size 32 diff --git a/src/status_im/contexts/wallet/common/asset_list/view.cljs b/src/status_im/contexts/wallet/common/asset_list/view.cljs index 072bc185d5..1ecab1b881 100644 --- a/src/status_im/contexts/wallet/common/asset_list/view.cljs +++ b/src/status_im/contexts/wallet/common/asset_list/view.cljs @@ -34,7 +34,7 @@ (defn view [{:keys [content-container-style search-text on-token-press preselected-token-symbol] :or {content-container-style {:padding-horizontal 8}}}] - (let [filtered-tokens (rf/sub [:wallet/current-viewing-account-tokens-filtered search-text]) + (let [filtered-tokens (rf/sub [:wallet/current-viewing-account-tokens-filtered {:query search-text}]) currency (rf/sub [:profile/currency]) currency-symbol (rf/sub [:profile/currency-symbol])] [gesture/flat-list diff --git a/src/status_im/contexts/wallet/common/token_value/view.cljs b/src/status_im/contexts/wallet/common/token_value/view.cljs index 34a5db6fa8..e61af5ccb5 100644 --- a/src/status_im/contexts/wallet/common/token_value/view.cljs +++ b/src/status_im/contexts/wallet/common/token_value/view.cljs @@ -73,7 +73,7 @@ [token watch-only? entry-point] (let [token-symbol (:token token) token-data (first (rf/sub [:wallet/current-viewing-account-tokens-filtered - token-symbol])) + {:query token-symbol}])) fiat-unformatted-value (get-in token [:values :fiat-unformatted-value]) has-balance? (money/greater-than fiat-unformatted-value (money/bignumber "0")) selected-account? (rf/sub [:wallet/current-viewing-account-address]) diff --git a/src/status_im/contexts/wallet/common/utils.cljs b/src/status_im/contexts/wallet/common/utils.cljs index 5256730567..ad6ff9d89d 100644 --- a/src/status_im/contexts/wallet/common/utils.cljs +++ b/src/status_im/contexts/wallet/common/utils.cljs @@ -4,6 +4,7 @@ [quo.foundations.resources :as resources] [status-im.common.qr-codes.view :as qr-codes] [status-im.constants :as constants] + [status-im.contexts.wallet.common.utils.networks :as network-utils] [utils.hex :as utils.hex] [utils.money :as money] [utils.number :as number] @@ -478,3 +479,17 @@ :toAsset to-asset :fromAmount amount-out :type multi-transaction-type}) + +(defn sort-tokens-by-name + [tokens] + (let [priority #(get constants/token-sort-priority (:symbol %) ##Inf)] + (sort-by (juxt :symbol priority) tokens))) + +(defn tokens-with-balance + [tokens networks chain-ids] + (map (fn [token] + (assoc token + :networks (network-utils/network-list token networks) + :available-balance (calculate-total-token-balance token) + :total-balance (calculate-total-token-balance token chain-ids))) + tokens)) diff --git a/src/status_im/contexts/wallet/common/utils/networks.cljs b/src/status_im/contexts/wallet/common/utils/networks.cljs index bc34b8f857..591ace7e77 100644 --- a/src/status_im/contexts/wallet/common/utils/networks.cljs +++ b/src/status_im/contexts/wallet/common/utils/networks.cljs @@ -189,3 +189,14 @@ nil) (when $ (assoc $ :chain-id chain-id)))) + +(defn sorted-networks-with-details + [networks] + (->> networks + (map + (fn [{:keys [chain-id related-chain-id layer]}] + (assoc (get-network-details chain-id) + :chain-id chain-id + :related-chain-id related-chain-id + :layer layer))) + (sort-by (juxt :layer :short-name)))) diff --git a/src/status_im/contexts/wallet/events.cljs b/src/status_im/contexts/wallet/events.cljs index 50109b29a9..38ff712ce4 100644 --- a/src/status_im/contexts/wallet/events.cljs +++ b/src/status_im/contexts/wallet/events.cljs @@ -15,6 +15,7 @@ [status-im.contexts.wallet.db :as db] [status-im.contexts.wallet.item-types :as item-types] [status-im.contexts.wallet.tokens.events] + [status-im.feature-flags :as ff] [taoensso.timbre :as log] [utils.collection] [utils.ethereum.chain :as chain] @@ -344,7 +345,9 @@ :Prod data-store/rpc->network) data)}] - {:db (assoc-in db [:wallet :networks] network-data)}))) + {:fx [(when (ff/enabled? ::ff/wallet.swap) + [:dispatch [:wallet.tokens/get-token-list]])] + :db (assoc-in db [:wallet :networks] network-data)}))) (rf/reg-event-fx :wallet/find-ens diff --git a/src/status_im/contexts/wallet/sheets/select_asset/asset_list/style.cljs b/src/status_im/contexts/wallet/sheets/select_asset/asset_list/style.cljs new file mode 100644 index 0000000000..cf0571e393 --- /dev/null +++ b/src/status_im/contexts/wallet/sheets/select_asset/asset_list/style.cljs @@ -0,0 +1 @@ +(ns status-im.contexts.wallet.sheets.select-asset.asset-list.style) diff --git a/src/status_im/contexts/wallet/sheets/select_asset/asset_list/view.cljs b/src/status_im/contexts/wallet/sheets/select_asset/asset_list/view.cljs new file mode 100644 index 0000000000..938832e785 --- /dev/null +++ b/src/status_im/contexts/wallet/sheets/select_asset/asset_list/view.cljs @@ -0,0 +1,111 @@ +(ns status-im.contexts.wallet.sheets.select-asset.asset-list.view + (:require + [quo.core :as quo] + [react-native.core :as rn] + [react-native.gesture :as gesture] + [status-im.constants :as constants] + [status-im.contexts.wallet.common.utils :as utils] + [utils.i18n :as i18n] + [utils.re-frame :as rf])) + +(defn- my-token-component + [{token-symbol :symbol + token-name :name + total-balance :total-balance + bridge-disabled? :bridge-disabled? + :as token} + {:keys [currency currency-symbol on-token-press disable-token-fn preselected-token-symbol]}] + (let [fiat-value (utils/calculate-token-fiat-value + {:currency currency + :balance total-balance + :token token}) + crypto-formatted (utils/get-standard-crypto-format token total-balance) + fiat-formatted (utils/get-standard-fiat-format crypto-formatted currency-symbol fiat-value)] + [rn/view {:style {:padding-horizontal 8}} + [quo/token-network + {:token token-symbol + :label token-name + :token-value (str crypto-formatted " " token-symbol) + :fiat-value fiat-formatted + :networks (seq (:networks token)) + :on-press #(on-token-press token) + :state (cond + (or bridge-disabled? + (when disable-token-fn + (disable-token-fn constants/swap-tokens-my token))) + :disabled + + (= preselected-token-symbol token-symbol) + :selected)}]])) + +(defn- popular-token-component + [{token-symbol :symbol + token-name :name + :as token} + {:keys [on-token-press disable-token-fn]}] + [rn/view {:style {:padding-horizontal 8}} + [quo/token-info + {:token token-symbol + :label token-name + :state (when (and disable-token-fn (disable-token-fn constants/swap-tokens-popular token)) + :disabled) + :on-press #(on-token-press token)}]]) + +(defn- list-component + [{:keys [type] :as token} _ _ render-data] + (if (= type constants/swap-tokens-popular) + [popular-token-component token render-data] + [my-token-component token render-data])) + +(defn section-header + [{:keys [title]}] + [quo/divider-label + {:container-style {:padding-horizontal 20 + :margin-top 12 + :margin-bottom 8} + :tight? false} + title]) + +(defn view + [{:keys [search-text on-token-press preselected-token-symbol network hide-token-fn + disable-token-fn]}] + (let [my-tokens (rf/sub [:wallet/current-viewing-account-tokens-filtered + {:query search-text + :chain-ids #{(:chain-id network)} + :hide-token-fn hide-token-fn}]) + popular-tokens (rf/sub [:wallet/tokens-filtered + {:query search-text + :chain-ids #{(:chain-id network)} + :hide-token-fn hide-token-fn}]) + currency (rf/sub [:profile/currency]) + currency-symbol (rf/sub [:profile/currency-symbol]) + sectioned-data (cond-> [] + (pos? (count my-tokens)) + (conj {:title (i18n/label :t/your-assets-on-network + {:network (:full-name network)}) + :data (map #(assoc % :type constants/swap-tokens-my) + my-tokens)}) + + (pos? (count popular-tokens)) + (conj {:title (i18n/label :t/popular-assets-on-network + {:network (:full-name network)}) + :data (map #(assoc % :type constants/swap-tokens-popular) + popular-tokens)})) + render-data {:currency currency + :currency-symbol currency-symbol + :on-token-press on-token-press + :preselected-token-symbol preselected-token-symbol + :disable-token-fn disable-token-fn}] + [gesture/section-list + {:data sectioned-data + :sections sectioned-data + :render-data render-data + :render-fn list-component + :sticky-section-headers-enabled false + :render-section-header-fn section-header + :style {:flex 1} + :content-container-style {:padding-bottom 20} + :keyboard-should-persist-taps :handled + :key-fn (fn [{:keys [type title] :as token}] + (str (:symbol token) "-" type "-" title)) + :on-scroll-to-index-failed identity}])) diff --git a/src/status_im/contexts/wallet/sheets/select_asset/style.cljs b/src/status_im/contexts/wallet/sheets/select_asset/style.cljs new file mode 100644 index 0000000000..66e36925b0 --- /dev/null +++ b/src/status_im/contexts/wallet/sheets/select_asset/style.cljs @@ -0,0 +1,24 @@ +(ns status-im.contexts.wallet.sheets.select-asset.style + (:require + [react-native.navigation :as navigation] + [react-native.platform :as platform])) + +(defn container + [window-height] + {:flex 1 + :display :flex + :height window-height + :padding-top (when platform/android? (navigation/status-bar-height))}) + +(def search-input-container + {:padding-horizontal 20 + :padding-vertical 8}) + +(def subheader-container + {:justify-content :flex-start + :align-items :center + :flex-direction :row + :flex-wrap :wrap + :gap 4 + :padding-horizontal 20 + :margin-bottom 8}) diff --git a/src/status_im/contexts/wallet/sheets/select_asset/view.cljs b/src/status_im/contexts/wallet/sheets/select_asset/view.cljs new file mode 100644 index 0000000000..ad23fb2939 --- /dev/null +++ b/src/status_im/contexts/wallet/sheets/select_asset/view.cljs @@ -0,0 +1,92 @@ +(ns status-im.contexts.wallet.sheets.select-asset.view + (:require + [quo.core :as quo] + [quo.theme] + [react-native.core :as rn] + [status-im.contexts.wallet.sheets.select-asset.asset-list.view :as asset-list] + [status-im.contexts.wallet.sheets.select-asset.style :as style] + [utils.i18n :as i18n] + [utils.re-frame :as rf])) + +(defn- search-input + [search-text on-change-text] + [rn/view {:style style/search-input-container} + [quo/input + {:small? true + :placeholder (i18n/label :t/search-assets) + :icon-name :i/search + :value search-text + :on-change-text on-change-text}]]) + +(defn- assets-view + [{:keys [search-text on-change-text on-select network hide-token-fn disable-token-fn]}] + [:<> + [search-input search-text on-change-text] + [asset-list/view + {:search-text search-text + :on-token-press (fn [token] + (when on-select + (on-select token)) + (rf/dispatch [:hide-bottom-sheet])) + :network network + :hide-token-fn hide-token-fn + :disable-token-fn disable-token-fn}]]) + +(defn- account-view + [{:keys [emoji name color]}] + (let [theme (quo.theme/use-theme)] + [quo/context-tag + {:theme theme + :type :account + :size 24 + :account-name name + :emoji emoji + :customization-color color}])) + +(defn- network-view + [{:keys [full-name source]}] + [quo/context-tag + {:size 24 + :network-logo source + :network-name full-name + :type :network}]) + +(defn- subheader-view + [{:keys [account network]}] + [rn/view {:style style/subheader-container} + [quo/text + {:size :paragraph-2 + :weight :medium} + (i18n/label :t/select-asset-on)] + [account-view account] + [quo/text + {:size :paragraph-2 + :weight :medium} + (i18n/label :t/select-asset-via)] + [network-view network]]) + +(defn view + [{:keys [title on-select hide-token-fn disable-token-fn]}] + (let [account (rf/sub [:wallet/current-viewing-account]) + {:keys [network]} (rf/sub [:wallet/swap]) + [search-text set-search-text] (rn/use-state "") + window-height (:height (rn/get-window))] + [rn/safe-area-view {:style (style/container window-height)} + [quo/page-nav + {:type :no-title + :icon-name :i/close + :on-press (fn [] + (rf/dispatch [:hide-bottom-sheet]))}] + [quo/page-top + {:title title + :title-accessibility-label :title-label}] + [subheader-view + {:account account + :network network}] + [assets-view + {:search-text search-text + :on-change-text #(set-search-text %) + :on-select on-select + :network network + :hide-token-fn hide-token-fn + :disable-token-fn disable-token-fn}]])) diff --git a/src/status_im/contexts/wallet/swap/events.cljs b/src/status_im/contexts/wallet/swap/events.cljs index 94f017d0ac..a2bfdb2f20 100644 --- a/src/status_im/contexts/wallet/swap/events.cljs +++ b/src/status_im/contexts/wallet/swap/events.cljs @@ -5,6 +5,7 @@ [status-im.contexts.wallet.common.utils :as utils] [status-im.contexts.wallet.send.utils :as send-utils] [status-im.contexts.wallet.sheets.network-selection.view :as network-selection] + [status-im.contexts.wallet.swap.utils :as swap-utils] [taoensso.timbre :as log] [utils.address :as address] [utils.debounce :as debounce] @@ -13,32 +14,58 @@ [utils.number :as number])) (rf/reg-event-fx :wallet.swap/start - (fn [{:keys [_db]}] - {:fx [[:dispatch [:open-modal :screen/wallet.swap-select-asset-to-pay]]]})) + (fn [{:keys [db]} [{:keys [asset-to-pay asset-to-receive network]}]] + (let [asset-to-receive' (or asset-to-receive + (swap-utils/select-asset-to-receive + (:wallet db) + (:profile/profile db) + asset-to-pay)) + network' (or network + (swap-utils/select-network asset-to-pay))] + {:db (-> db + (assoc-in [:wallet :ui :swap :asset-to-pay] asset-to-pay) + (assoc-in [:wallet :ui :swap :asset-to-receive] asset-to-receive') + (assoc-in [:wallet :ui :swap :network] network')) + :fx (if network' + [[:dispatch + [:navigate-to-within-stack + [:screen/wallet.setup-swap :screen/wallet.swap-select-asset-to-pay]]] + [:dispatch [:wallet.swap/set-default-slippage]]] + [[:dispatch + [:show-bottom-sheet + {:content (fn [] + [network-selection/view + {:token-symbol (:symbol asset-to-pay) + :on-select-network (fn [network] + (rf/dispatch [:hide-bottom-sheet]) + (rf/dispatch + [:wallet.swap/start + {:asset-to-pay asset-to-pay + :asset-to-receive asset-to-receive' + :network network}]))}])}]]])}))) (rf/reg-event-fx :wallet.swap/select-asset-to-pay - (fn [{:keys [db]} [{:keys [token network]}]] - {:db (-> db - (assoc-in [:wallet :ui :swap :asset-to-pay] token) - (assoc-in [:wallet :ui :swap :network] network)) - :fx (if network - [[:dispatch - [:navigate-to-within-stack - [:screen/wallet.setup-swap :screen/wallet.swap-select-asset-to-pay]]] - [:dispatch [:wallet.swap/set-default-slippage]]] - [[:dispatch - [:show-bottom-sheet - {:content (fn [] - [network-selection/view - {:token-symbol (:symbol token) - :on-select-network (fn [network] - (rf/dispatch [:hide-bottom-sheet]) - (rf/dispatch - [:wallet.swap/select-asset-to-pay - {:token token - :network network - :stack-id - :screen/wallet.swap-select-asset-to-pay}]))}])}]]])})) + (fn [{:keys [db]} [{:keys [token]}]] + {:db (update-in db + [:wallet :ui :swap] + #(-> % + (assoc :asset-to-pay token) + (dissoc :amount + :amount-hex + :last-request-uuid + :swap-proposal + :error-response + :loading-swap-proposal? + :approval-transaction-id + :approved-amount)))})) + +(rf/reg-event-fx :wallet.swap/select-asset-to-receive + (fn [{:keys [db]} [{:keys [token]}]] + {:db (update-in db + [:wallet :ui :swap] + #(-> % + (assoc :asset-to-receive token) + (assoc :loading-swap-proposal? true)))})) (rf/reg-event-fx :wallet.swap/set-default-slippage (fn [{:keys [db]}] @@ -48,10 +75,6 @@ (fn [{:keys [db]} [max-slippage]] {:db (assoc-in db [:wallet :ui :swap :max-slippage] (number/parse-float max-slippage))})) -(rf/reg-event-fx :wallet.swap/select-asset-to-receive - (fn [{:keys [db]} [{:keys [token]}]] - {:db (assoc-in db [:wallet :ui :swap :asset-to-receive] token)})) - (rf/reg-event-fx :wallet/start-get-swap-proposal (fn [{:keys [db]} [{:keys [amount-in amount-out clean-approval-transaction?]}]] (let [wallet-address (get-in db [:wallet :current-viewing-account-address]) diff --git a/src/status_im/contexts/wallet/swap/select_asset_to_pay/view.cljs b/src/status_im/contexts/wallet/swap/select_asset_to_pay/view.cljs index 3b3d82ef54..a6937bfd17 100644 --- a/src/status_im/contexts/wallet/swap/select_asset_to_pay/view.cljs +++ b/src/status_im/contexts/wallet/swap/select_asset_to_pay/view.cljs @@ -21,15 +21,7 @@ (defn- assets-view [search-text on-change-text] (let [on-token-press (fn [token] - (let [token-networks (:networks token) - asset-to-receive (rf/sub [:wallet/token-by-symbol "DAI"])] - (rf/dispatch [:wallet.swap/select-asset-to-pay - {:token token - :network (when (= (count token-networks) 1) - (first token-networks)) - :stack-id :screen/wallet.swap-select-asset-to-pay}]) - (rf/dispatch [:wallet.swap/select-asset-to-receive - {:token asset-to-receive}])))] + (rf/dispatch [:wallet.swap/start {:asset-to-pay token}]))] [:<> [search-input search-text on-change-text] [asset-list/view diff --git a/src/status_im/contexts/wallet/swap/setup_swap/view.cljs b/src/status_im/contexts/wallet/swap/setup_swap/view.cljs index 1345997ca3..bbd9c3c82b 100644 --- a/src/status_im/contexts/wallet/swap/setup_swap/view.cljs +++ b/src/status_im/contexts/wallet/swap/setup_swap/view.cljs @@ -12,6 +12,7 @@ [status-im.contexts.wallet.common.account-switcher.view :as account-switcher] [status-im.contexts.wallet.common.utils :as utils] [status-im.contexts.wallet.sheets.buy-token.view :as buy-token] + [status-im.contexts.wallet.sheets.select-asset.view :as select-asset] [status-im.contexts.wallet.sheets.slippage-settings.view :as slippage-settings] [status-im.contexts.wallet.swap.setup-swap.style :as style] [status-im.contexts.wallet.swap.utils :as swap-utils] @@ -271,6 +272,31 @@ :customization-color account-color :on-press on-press}}])) +(defn- pay-token-bottom-sheet + [] + (let [asset-to-receive (rf/sub [:wallet/swap-asset-to-receive])] + [select-asset/view + {:title (i18n/label :t/select-asset-to-pay) + :on-select (fn [token] + (rf/dispatch [:wallet.swap/select-asset-to-pay {:token token}])) + :hide-token-fn (fn [type {:keys [available-balance]}] + (and (= type constants/swap-tokens-my) + (= available-balance 0))) + :disable-token-fn (fn [_ token] + (= (:symbol token) + (:symbol asset-to-receive)))}])) + +(defn- receive-token-bottom-sheet + [] + (let [asset-to-pay (rf/sub [:wallet/swap-asset-to-pay])] + [select-asset/view + {:title (i18n/label :t/select-asset-to-receive) + :on-select (fn [token] + (rf/dispatch [:wallet.swap/select-asset-to-receive {:token token}])) + :disable-token-fn (fn [_ token] + (= (:symbol token) + (:symbol asset-to-pay)))}])) + (defn view [] (let [[pay-input-state set-pay-input-state] (rn/use-state controlled-input/init-state) @@ -280,6 +306,7 @@ loading-swap-proposal? (rf/sub [:wallet/swap-loading-swap-proposal?]) swap-proposal (rf/sub [:wallet/swap-proposal-without-fees]) asset-to-pay (rf/sub [:wallet/swap-asset-to-pay]) + asset-to-receive (rf/sub [:wallet/swap-asset-to-receive]) network (rf/sub [:wallet/swap-network]) pay-input-amount (controlled-input/input-value pay-input-state) pay-token-decimals (:decimals asset-to-pay) @@ -360,7 +387,7 @@ :clean-approval-transaction? false})))) [valid-pay-input? loading-swap-proposal? pay-input-amount]) - on-asset-to-pay-change (fn [] + refetch-swap-proposal (fn [] (when valid-pay-input? (fetch-swap-proposal {:amount pay-input-amount @@ -394,8 +421,11 @@ (controlled-input/set-input-value input-state swap-amount))) - (on-asset-to-pay-change))))) + (refetch-swap-proposal))))) [asset-to-pay]) + (rn/use-effect + refetch-swap-proposal + [asset-to-receive]) [rn/view {:style style/container} [account-switcher/view {:on-press on-close @@ -407,7 +437,7 @@ {:input-state pay-input-state :on-max-press on-max-press :input-focused? pay-input-focused? - :on-token-press #(js/alert "Token Pressed") + :on-token-press #(rf/dispatch [:show-bottom-sheet {:content pay-token-bottom-sheet}]) :on-approve-press #(rf/dispatch [:open-modal :screen/wallet.swap-set-spending-cap]) :on-input-focus (fn [] (when platform/android? (rf/dispatch [:dismiss-keyboard])) @@ -420,7 +450,7 @@ (rf/dispatch [:wallet.swap/flip-assets]))}] [receive-token-input {:input-focused? (not pay-input-focused?) - :on-token-press #(js/alert "Token Pressed") + :on-token-press #(rf/dispatch [:show-bottom-sheet {:content receive-token-bottom-sheet}]) :on-input-focus #(set-pay-input-focused? false)}]] [rn/view {:style style/footer-container} [alert-banner {:pay-input-error? pay-input-error?}] diff --git a/src/status_im/contexts/wallet/swap/swap_proposal/view.cljs b/src/status_im/contexts/wallet/swap/swap_proposal/view.cljs index 6314046376..153fc7c6bf 100644 --- a/src/status_im/contexts/wallet/swap/swap_proposal/view.cljs +++ b/src/status_im/contexts/wallet/swap/swap_proposal/view.cljs @@ -1,9 +1,10 @@ (ns status-im.contexts.wallet.swap.swap-proposal.view - (:require [quo.core :as quo] - [react-native.core :as rn] - [status-im.contexts.wallet.sheets.slippage-settings.view :as slippage-settings] - [status-im.contexts.wallet.swap.swap-proposal.style :as style] - [utils.re-frame :as rf])) + (:require + [quo.core :as quo] + [react-native.core :as rn] + [status-im.contexts.wallet.sheets.slippage-settings.view :as slippage-settings] + [status-im.contexts.wallet.swap.swap-proposal.style :as style] + [utils.re-frame :as rf])) (defn view [] diff --git a/src/status_im/contexts/wallet/swap/utils.cljs b/src/status_im/contexts/wallet/swap/utils.cljs index 21be39d191..f9c97a38e8 100644 --- a/src/status_im/contexts/wallet/swap/utils.cljs +++ b/src/status_im/contexts/wallet/swap/utils.cljs @@ -1,5 +1,7 @@ (ns status-im.contexts.wallet.swap.utils (:require [status-im.constants :as constants] + [status-im.contexts.wallet.common.utils :as utils] + [status-im.contexts.wallet.common.utils.networks :as network-utils] [utils.i18n :as i18n])) (defn error-message-from-code @@ -27,3 +29,23 @@ (i18n/label :t/not-enough-assets-to-pay-gas-fees) :else (i18n/label :t/something-went-wrong-please-try-again-later))) + +(defn select-asset-to-receive + [wallet profile asset-to-pay] + (let [wallet-address (get-in wallet [:current-viewing-account-address]) + account (-> wallet + :accounts + vals + (utils/get-account-by-address wallet-address)) + test-networks-enabled? (get profile :test-networks-enabled?) + networks (-> (get-in wallet + [:wallet :networks (if test-networks-enabled? :test :prod)]) + (network-utils/sorted-networks-with-details))] + (->> (utils/tokens-with-balance (:tokens account) networks nil) + (remove #(= (:symbol %) (:symbol asset-to-pay))) + first))) + +(defn select-network + [{:keys [networks]}] + (when (= (count networks) 1) + (first networks))) diff --git a/src/status_im/contexts/wallet/tokens/events.cljs b/src/status_im/contexts/wallet/tokens/events.cljs index a51827bd0a..42bd1074f8 100644 --- a/src/status_im/contexts/wallet/tokens/events.cljs +++ b/src/status_im/contexts/wallet/tokens/events.cljs @@ -10,12 +10,13 @@ (rf/reg-event-fx :wallet.tokens/get-token-list (fn [{:keys [db]}] - {:db (assoc-in db [:wallet :ui :loading :token-list] true) - :fx [[:json-rpc/call - [{:method "wallet_getTokenList" - :params [] - :on-success [:wallet.tokens/store-token-list] - :on-error [:wallet.tokens/get-token-list-failed]}]]]})) + (when-not (get-in db [:wallet :tokens]) + {:db (assoc-in db [:wallet :ui :loading :token-list] true) + :fx [[:json-rpc/call + [{:method "wallet_getTokenList" + :params [] + :on-success [:wallet.tokens/store-token-list] + :on-error [:wallet.tokens/get-token-list-failed]}]]]}))) (defn store-token-list [{:keys [db]} [{:keys [data]}]] diff --git a/src/status_im/subs/wallet/networks.cljs b/src/status_im/subs/wallet/networks.cljs index 6259880630..201162219b 100644 --- a/src/status_im/subs/wallet/networks.cljs +++ b/src/status_im/subs/wallet/networks.cljs @@ -21,14 +21,7 @@ :wallet/network-details :<- [:wallet/networks-by-mode] (fn [networks] - (->> networks - (map - (fn [{:keys [chain-id related-chain-id layer]}] - (assoc (network-utils/get-network-details chain-id) - :chain-id chain-id - :related-chain-id related-chain-id - :layer layer))) - (sort-by (juxt :layer :short-name))))) + (network-utils/sorted-networks-with-details networks))) (re-frame/reg-sub :wallet/network-details-by-network-name diff --git a/src/status_im/subs/wallet/wallet.cljs b/src/status_im/subs/wallet/wallet.cljs index c5f8ab992b..b06a76c2ef 100644 --- a/src/status_im/subs/wallet/wallet.cljs +++ b/src/status_im/subs/wallet/wallet.cljs @@ -39,6 +39,11 @@ :<- [:wallet/ui] :-> :scanned-address) +(rf/reg-sub + :wallet/tokens + :<- [:wallet] + :-> :tokens) + (rf/reg-sub :wallet/tokens-loading :<- [:wallet/ui] @@ -418,19 +423,26 @@ :<- [:wallet/current-viewing-account] :<- [:wallet/network-details] :<- [:wallet/wallet-send] - (fn [[account networks send-data] [_ query chain-ids]] - (prn send-data) + (fn [[account networks send-data] [_ {:keys [query chain-ids hide-token-fn]}]] (let [tx-type (:tx-type send-data) - tokens (map (fn [token] - (assoc token - :bridge-disabled? (and (= tx-type :tx/bridge) - (send-utils/bridge-disabled? (:symbol - token))) - :networks (network-utils/network-list token networks) - :available-balance (utils/calculate-total-token-balance token) - :total-balance (utils/calculate-total-token-balance token - chain-ids))) - (:tokens account)) + tokens (->> (:tokens account) + (map + (fn [token] + (assoc token + :bridge-disabled? (and (= tx-type :tx/bridge) + (send-utils/bridge-disabled? (:symbol + token))) + :networks (cond->> (network-utils/network-list token + networks) + chain-ids + (filter #(some #{(:chain-id %)} chain-ids))) + :available-balance (utils/calculate-total-token-balance token) + :total-balance (utils/calculate-total-token-balance + token + chain-ids)))) + (filter (fn [{:keys [networks]}] + (pos? (count networks)))) + (remove #(when hide-token-fn (hide-token-fn constants/swap-tokens-my %)))) sorted-tokens (utils/sort-tokens tokens)] (if query (let [query-string (string/lower-case query)] @@ -439,22 +451,38 @@ sorted-tokens)) sorted-tokens)))) +(rf/reg-sub + :wallet/tokens-filtered + :<- [:wallet/tokens] + (fn [{:keys [by-symbol market-values-per-token details-per-token]} + [_ {:keys [query chain-ids hide-token-fn]}]] + (let [tokens (->> by-symbol + (map (fn [token] + (-> token + (assoc :market-values + (get market-values-per-token (:symbol token))) + (assoc :details (get details-per-token (:symbol token)))))) + (filter (fn [{:keys [chain-id]}] + (some #{chain-id} chain-ids))) + (remove #(when hide-token-fn + (hide-token-fn constants/swap-tokens-popular %)))) + sorted-tokens (utils/sort-tokens-by-name tokens)] + (if query + (let [query-string (string/lower-case query)] + (filter #(or (string/starts-with? (string/lower-case (:name %)) query-string) + (string/starts-with? (string/lower-case (:symbol %)) query-string)) + sorted-tokens)) + sorted-tokens)))) + (rf/reg-sub :wallet/token-by-symbol :<- [:wallet/current-viewing-account] :<- [:wallet/network-details] - (fn [[account networks] [_ token-symbol chain-ids]] - (let [tokens (map (fn [token] - (assoc token - :networks (network-utils/network-list token networks) - :available-balance (utils/calculate-total-token-balance token) - :total-balance (utils/calculate-total-token-balance token - chain-ids))) - (:tokens account)) - token (first (filter #(= (string/lower-case (:symbol %)) - (string/lower-case token-symbol)) - tokens))] - token))) + (fn [[{:keys [tokens]} networks] [_ token-symbol chain-ids]] + (->> (utils/tokens-with-balance tokens networks chain-ids) + (filter #(= (string/lower-case (:symbol %)) + (string/lower-case token-symbol))) + first))) (rf/reg-sub :wallet/accounts-without-current-viewing-account diff --git a/src/status_im/subs/wallet/wallet_test.cljs b/src/status_im/subs/wallet/wallet_test.cljs index 8a8ffdea56..f511fc6426 100644 --- a/src/status_im/subs/wallet/wallet_test.cljs +++ b/src/status_im/subs/wallet/wallet_test.cljs @@ -887,7 +887,7 @@ (assoc :currencies currencies) (assoc-in [:profile/profile :currency] :usd))) (is (match? (count (rf/sub [sub-name ""])) 2)) - (is (match? (count (rf/sub [sub-name "et"])) 1)))) + (is (match? (count (rf/sub [sub-name "et"])) 2)))) (h/deftest-sub :wallet/selected-networks->chain-ids [sub-name] diff --git a/translations/en.json b/translations/en.json index bfbb45db81..d2f48d4d1d 100644 --- a/translations/en.json +++ b/translations/en.json @@ -1929,6 +1929,7 @@ }, "pinned-messages-empty": "Pinned messages will appear here. To pin a message, press and hold it and tap `Pin`", "podcasts": "Podcasts", + "popular-assets-on-network": "Popular assets on {{network}}", "popular-currencies": "Popular currencies", "positive": "Positive", "powered-by-paraswap": "Powered by Paraswap", @@ -2187,7 +2188,10 @@ "select-account-first": "Select an account first", "select-another-account": "Select another account", "select-asset": "Select asset", + "select-asset-on": "On", "select-asset-to-pay": "Select asset to pay", + "select-asset-to-receive": "Select asset to receive", + "select-asset-via": "via", "select-chat": "Select chat to start messaging", "select-network": "Select network", "select-networks": "Select networks", @@ -2833,6 +2837,7 @@ "you-will-start-from-scratch": "You will start from scratch with a new set of keys", "your-addresses": "Your addresses", "your-answer": "Your answer", + "your-assets-on-network": "Your assets on {{network}}", "your-card-is-frozen": "Your Keycard is frozen. Reset card access", "your-contact-code": "Granting access authorizes this DApp to retrieve your chat key", "your-data-belongs-to-you": "If you lose your seed phrase you lose your data and funds",