From 337ec963b7fc8934da09f600b249567f3f177c3c Mon Sep 17 00:00:00 2001 From: Brian Sztamfater Date: Mon, 9 Sep 2024 01:27:31 -0300 Subject: [PATCH] feat: autorefresh swap proposal (#21143) Signed-off-by: Brian Sztamfater --- src/status_im/constants.cljs | 1 + .../contexts/wallet/swap/events.cljs | 25 +++-- .../wallet/swap/set_spending_cap/view.cljs | 99 ++++++++++--------- .../contexts/wallet/swap/setup_swap/view.cljs | 62 +++++++++--- .../wallet/swap/swap_confirmation/view.cljs | 37 ++++--- src/status_im/subs/bottom_sheet.cljs | 8 ++ src/status_im/subs/root.cljs | 1 + src/status_im/subs/wallet/swap.cljs | 12 +-- src/status_im/subs/wallet/swap_test.cljs | 9 -- 9 files changed, 155 insertions(+), 99 deletions(-) create mode 100644 src/status_im/subs/bottom_sheet.cljs diff --git a/src/status_im/constants.cljs b/src/status_im/constants.cljs index 9778202047..37462b9532 100644 --- a/src/status_im/constants.cljs +++ b/src/status_im/constants.cljs @@ -608,3 +608,4 @@ (def ^:const transaction-status-failed "Failed") (def ^:const min-token-decimals-to-display 6) +(def ^:const swap-proposal-refresh-interval-ms 15000) diff --git a/src/status_im/contexts/wallet/swap/events.cljs b/src/status_im/contexts/wallet/swap/events.cljs index 899444dc9f..9e14ba29a9 100644 --- a/src/status_im/contexts/wallet/swap/events.cljs +++ b/src/status_im/contexts/wallet/swap/events.cljs @@ -54,10 +54,6 @@ (fn [{:keys [db]} [{:keys [token]}]] {:db (assoc-in db [:wallet :ui :swap :asset-to-receive] token)})) -(rf/reg-event-fx :wallet.swap/recalculate-fees - (fn [{:keys [db]} [loading-fees?]] - {:db (assoc-in db [:wallet :ui :swap :loading-fees?] loading-fees?)})) - (rf/reg-event-fx :wallet/start-get-swap-proposal (fn [{:keys [db]} [{:keys [amount-in amount-out]}]] (let [wallet-address (get-in db [:wallet :current-viewing-account-address]) @@ -120,16 +116,25 @@ (rf/reg-event-fx :wallet/swap-proposal-success (fn [{:keys [db]} [swap-proposal]] (let [last-request-uuid (get-in db [:wallet :ui :swap :last-request-uuid]) + view-id (:view-id db) request-uuid (:uuid swap-proposal) best-routes (:best swap-proposal) error-response (:error-response swap-proposal)] (when (= request-uuid last-request-uuid) - {:db (update-in db - [:wallet :ui :swap] - assoc - :swap-proposal (first best-routes) - :error-response (when (empty? best-routes) error-response) - :loading-swap-proposal? false)})))) + (cond-> {:db (update-in db + [:wallet :ui :swap] + assoc + :swap-proposal (first best-routes) + :error-response (when (empty? best-routes) error-response) + :loading-swap-proposal? false)} + ;; Router is unstable and it can return a swap proposal and after auto-refetching it can + ;; return an error. Ideally this shouldn't happen, but adding this behavior so if the + ;; user is in swap confirmation screen or in token approval confirmation screen, we + ;; navigate back to setup swap screen so proper error is displayed. + (and (empty? best-routes) (= view-id :screen/wallet.swap-set-spending-cap)) + (assoc :fx [[:dismiss-modal :screen/wallet.swap-set-spending-cap]]) + (and (empty? best-routes) (= view-id :screen/wallet.swap-confirmation)) + (assoc :fx [[:navigate-back]])))))) (rf/reg-event-fx :wallet/swap-proposal-error (fn [{:keys [db]} [error-message]] diff --git a/src/status_im/contexts/wallet/swap/set_spending_cap/view.cljs b/src/status_im/contexts/wallet/swap/set_spending_cap/view.cljs index 36dbe407e1..24abdc1262 100644 --- a/src/status_im/contexts/wallet/swap/set_spending_cap/view.cljs +++ b/src/status_im/contexts/wallet/swap/set_spending_cap/view.cljs @@ -73,11 +73,12 @@ :style (style/section-label theme) :accessibility-label :spending-cap-label} (i18n/label :t/spending-cap)] - [quo/approval-info - {:type :spending-cap - :unlimited-icon? false - :label (str pay-amount " " pay-token-symbol) - :avatar-props {:token pay-token-symbol}}]])) + (when (and asset-to-pay pay-amount) + [quo/approval-info + {:type :spending-cap + :unlimited-icon? false + :label (str pay-amount " " pay-token-symbol) + :avatar-props {:token pay-token-symbol}}])])) (defn- account-section [] @@ -93,14 +94,15 @@ :style (style/section-label theme) :accessibility-label :account-label} (i18n/label :t/account)] - [quo/approval-info - {:type :account - :unlimited-icon? false - :label (:name account) - :description (address-utils/get-short-wallet-address (:address account)) - :tag-label (str pay-amount " " pay-token-symbol) - :avatar-props {:emoji (:emoji account) - :customization-color (:color account)}}]])) + (when (and asset-to-pay pay-amount) + [quo/approval-info + {:type :account + :unlimited-icon? false + :label (:name account) + :description (address-utils/get-short-wallet-address (:address account)) + :tag-label (str pay-amount " " pay-token-symbol) + :avatar-props {:emoji (:emoji account) + :customization-color (:color account)}}])])) (defn- on-option-press [{:keys [chain-id contract-address]}] @@ -133,15 +135,16 @@ :style (style/section-label theme) :accessibility-label :token-label} (i18n/label :t/token)] - [quo/approval-info - {:type :token-contract - :option-icon :i/options - :on-option-press #(on-option-press {:chain-id network-chain-id - :contract-address pay-token-address}) - :unlimited-icon? false - :label pay-token-symbol - :description (address-utils/get-short-wallet-address pay-token-address) - :avatar-props {:token pay-token-symbol}}]])) + (when asset-to-pay + [quo/approval-info + {:type :token-contract + :option-icon :i/options + :on-option-press #(on-option-press {:chain-id network-chain-id + :contract-address pay-token-address}) + :unlimited-icon? false + :label pay-token-symbol + :description (address-utils/get-short-wallet-address pay-token-address) + :avatar-props {:token pay-token-symbol}}])])) (defn- spender-contract-section [] @@ -156,15 +159,16 @@ :style (style/section-label theme) :accessibility-label :spender-contract-label} (i18n/label :t/spender-contract)] - [quo/approval-info - {:type :token-contract - :option-icon :i/options - :on-option-press #(on-option-press {:chain-id network-chain-id - :contract-address (:contract-address provider)}) - :unlimited-icon? false - :label (:full-name provider) - :description (address-utils/get-short-wallet-address (:contract-address provider)) - :avatar-props {:image (resources/get-network (:name provider))}}]])) + (when provider + [quo/approval-info + {:type :token-contract + :option-icon :i/options + :on-option-press #(on-option-press {:chain-id network-chain-id + :contract-address (:contract-address provider)}) + :unlimited-icon? false + :label (:full-name provider) + :description (address-utils/get-short-wallet-address (:contract-address provider)) + :avatar-props {:image (resources/get-network (:name provider))}}])])) (defn- data-item [{:keys [network-image title subtitle size loading?]}] @@ -181,11 +185,11 @@ (defn- transaction-details [] - (let [network (rf/sub [:wallet/swap-network]) - max-fees (rf/sub [:wallet/wallet-swap-proposal-fee-fiat-formatted - constants/token-for-fees-symbol]) - loading-fees? (rf/sub [:wallet/swap-loading-fees?]) - estimated-time (rf/sub [:wallet/swap-proposal-estimated-time])] + (let [network (rf/sub [:wallet/swap-network]) + max-fees (rf/sub [:wallet/wallet-swap-proposal-fee-fiat-formatted + constants/token-for-fees-symbol]) + loading-swap-proposal? (rf/sub [:wallet/swap-loading-swap-proposal?]) + estimated-time (rf/sub [:wallet/swap-proposal-estimated-time])] [rn/view {:style style/details-container} [:<> [data-item @@ -194,26 +198,31 @@ :network-image (:source network)}] [data-item {:title (i18n/label :t/max-fees) - :subtitle max-fees - :loading? loading-fees? + :subtitle (if (and estimated-time max-fees) max-fees (i18n/label :t/unknown)) + :loading? loading-swap-proposal? :size :small}] [data-item {:title (i18n/label :t/est-time) - :subtitle (i18n/label :t/time-in-mins {:minutes (str estimated-time)})}]]])) + :subtitle (if estimated-time + (i18n/label :t/time-in-mins {:minutes (str estimated-time)}) + (i18n/label :t/unknown)) + :loading? loading-swap-proposal? + :size :small}]]])) (defn- slide-button [] - (let [loading-fees? (rf/sub [:wallet/swap-loading-fees?]) - account (rf/sub [:wallet/current-viewing-account]) - on-auth-success (rn/use-callback #(rf/dispatch - [:wallet/swap-transaction - (security/safe-unmask-data %)]))] + (let [loading-swap-proposal? (rf/sub [:wallet/swap-loading-swap-proposal?]) + swap-proposal (rf/sub [:wallet/swap-proposal]) + account (rf/sub [:wallet/current-viewing-account]) + on-auth-success (rn/use-callback #(rf/dispatch + [:wallet/swap-transaction + (security/safe-unmask-data %)]))] [standard-auth/slide-button {:size :size-48 :track-text (i18n/label :t/slide-to-sign) :container-style {:z-index 2} :customization-color (:color account) - :disabled? loading-fees? + :disabled? (or loading-swap-proposal? (not swap-proposal)) :on-auth-success on-auth-success :auth-button-label (i18n/label :t/confirm)}])) 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 78fad53450..b7e17c162a 100644 --- a/src/status_im/contexts/wallet/swap/setup_swap/view.cljs +++ b/src/status_im/contexts/wallet/swap/setup_swap/view.cljs @@ -49,21 +49,18 @@ (let [max-fees (rf/sub [:wallet/wallet-swap-proposal-fee-fiat-formatted constants/token-for-fees-symbol]) max-slippage (rf/sub [:wallet/swap-max-slippage]) - loading-fees? (rf/sub [:wallet/swap-loading-fees?]) - loading-swap-proposal? (rf/sub [:wallet/swap-loading-swap-proposal?]) - loading? (or loading-fees? loading-swap-proposal?)] + loading-swap-proposal? (rf/sub [:wallet/swap-loading-swap-proposal?])] [rn/view {:style style/details-container} [data-item {:title (i18n/label :t/max-fees) :subtitle max-fees - :loading? loading? + :loading? loading-swap-proposal? :size :small}] [data-item {:title (i18n/label :t/max-slippage) :subtitle max-slippage :subtitle-icon :i/edit - :size :small - :loading? loading?}]])) + :size :small}]])) (defn- pay-token-input [{:keys [input-state on-max-press on-input-focus on-token-press on-approve-press input-focused?]}] @@ -88,7 +85,7 @@ pay-token-fiat-value (str (utils/calculate-token-fiat-value {:currency currency - :balance pay-input-num-value + :balance (or pay-input-num-value 0) :token asset-to-pay})) available-crypto-limit (number/remove-trailing-zeroes (.toFixed (money/bignumber @@ -209,7 +206,6 @@ [{:keys [on-press]}] (let [account-color (rf/sub [:wallet/current-viewing-account-color]) swap-proposal (rf/sub [:wallet/swap-proposal]) - loading-fees? (rf/sub [:wallet/swap-loading-fees?]) loading-swap-proposal? (rf/sub [:wallet/swap-loading-swap-proposal?]) approval-required? (rf/sub [:wallet/swap-proposal-approval-required]) approval-transaction-status (rf/sub [:wallet/swap-approval-transaction-status])] @@ -219,8 +215,7 @@ :button-one-props {:disabled? (or (not swap-proposal) (and approval-required? (not= approval-transaction-status :confirmed)) - loading-swap-proposal? - loading-fees?) + loading-swap-proposal?) :customization-color account-color :on-press on-press}}])) @@ -228,12 +223,29 @@ [] (let [[pay-input-state set-pay-input-state] (rn/use-state controlled-input/init-state) [pay-input-focused? set-pay-input-focused?] (rn/use-state true) + [refetch-interval set-refetch-interval] (rn/use-state nil) error-response (rf/sub [:wallet/swap-error-response]) loading-swap-proposal? (rf/sub [:wallet/swap-loading-swap-proposal?]) swap-proposal (rf/sub [:wallet/swap-proposal]) asset-to-pay (rf/sub [:wallet/swap-asset-to-pay]) + network (rf/sub [:wallet/swap-network]) pay-input-amount (controlled-input/input-value pay-input-state) pay-token-decimals (:decimals asset-to-pay) + pay-input-num-value (controlled-input/numeric-value pay-input-state) + pay-token-balance-selected-chain (get-in asset-to-pay + [:balances-per-chain + (:chain-id network) :balance] + 0) + pay-input-error? (and (not (string/blank? pay-input-amount)) + (money/greater-than + (money/bignumber pay-input-num-value) + (money/bignumber + pay-token-balance-selected-chain))) + valid-pay-input? (and + (not (string/blank? + pay-input-amount)) + (> pay-input-amount 0) + (not pay-input-error?)) on-review-swap-press (rn/use-callback (fn [] (rf/dispatch [:navigate-to-within-stack @@ -265,7 +277,35 @@ (fn [input-state] (controlled-input/set-input-value input-state - max-value)))))] + max-value))))) + on-refresh-swap-proposal (rn/use-callback + (fn [] + (let [bottom-sheets (rf/sub + [:bottom-sheet-sheets])] + (when-not valid-pay-input? + (js/clearInterval refetch-interval) + (set-refetch-interval nil)) + (when (and valid-pay-input? + (not loading-swap-proposal?) + (not bottom-sheets)) + (fetch-swap-proposal + {:amount pay-input-amount + :valid-input? valid-pay-input?})))) + [valid-pay-input? loading-swap-proposal? + pay-input-amount])] + (rn/use-effect (fn [] + (when refetch-interval + (js/clearInterval refetch-interval) + (set-refetch-interval nil)) + (when (or swap-proposal error-response) + (set-refetch-interval (js/setInterval + on-refresh-swap-proposal + constants/swap-proposal-refresh-interval-ms)))) + [swap-proposal error-response]) + (rn/use-unmount (fn [] + (when refetch-interval + (js/clearInterval refetch-interval) + (set-refetch-interval nil)))) [rn/view {:style style/container} [account-switcher/view {:on-press on-close diff --git a/src/status_im/contexts/wallet/swap/swap_confirmation/view.cljs b/src/status_im/contexts/wallet/swap/swap_confirmation/view.cljs index a8503c5d18..a1cad55e5a 100644 --- a/src/status_im/contexts/wallet/swap/swap_confirmation/view.cljs +++ b/src/status_im/contexts/wallet/swap/swap_confirmation/view.cljs @@ -133,38 +133,42 @@ (defn- transaction-details [] - (let [max-fees (rf/sub [:wallet/wallet-swap-proposal-fee-fiat-formatted - constants/token-for-fees-symbol]) - estimated-time (rf/sub [:wallet/swap-proposal-estimated-time]) - loading-fees? (rf/sub [:wallet/swap-loading-fees?]) - max-slippage (rf/sub [:wallet/swap-max-slippage])] + (let [max-fees (rf/sub [:wallet/wallet-swap-proposal-fee-fiat-formatted + constants/token-for-fees-symbol]) + estimated-time (rf/sub [:wallet/swap-proposal-estimated-time]) + loading-swap-proposal? (rf/sub [:wallet/swap-loading-swap-proposal?]) + max-slippage (rf/sub [:wallet/swap-max-slippage])] [rn/view {:style style/details-container} [:<> [data-item {:title (i18n/label :t/est-time) - :subtitle (i18n/label :t/time-in-mins {:minutes (str estimated-time)})}] + :subtitle (if estimated-time + (i18n/label :t/time-in-mins {:minutes (str estimated-time)}) + (i18n/label :t/unknown)) + :loading? loading-swap-proposal?}] [data-item {:title (i18n/label :t/max-fees) - :subtitle max-fees - :loading? loading-fees?}] + :subtitle (if (and estimated-time max-fees) max-fees (i18n/label :t/unknown)) + :loading? loading-swap-proposal?}] [data-item {:title (i18n/label :t/max-slippage) :subtitle (str max-slippage "%")}]]])) (defn- slide-button [] - (let [loading-fees? (rf/sub [:wallet/swap-loading-fees?]) - account (rf/sub [:wallet/current-viewing-account]) - account-color (:color account) - on-auth-success (rn/use-callback #(rf/dispatch - [:wallet/swap-transaction - (security/safe-unmask-data %)]))] + (let [loading-swap-proposal? (rf/sub [:wallet/swap-loading-swap-proposal?]) + swap-proposal (rf/sub [:wallet/swap-proposal]) + account (rf/sub [:wallet/current-viewing-account]) + account-color (:color account) + on-auth-success (rn/use-callback #(rf/dispatch + [:wallet/swap-transaction + (security/safe-unmask-data %)]))] [standard-auth/slide-button {:size :size-48 :track-text (i18n/label :t/slide-to-swap) :container-style {:z-index 2} :customization-color account-color - :disabled? loading-fees? + :disabled? (or loading-swap-proposal? (not swap-proposal)) :auth-button-label (i18n/label :t/confirm) :on-auth-success on-auth-success}])) @@ -179,7 +183,8 @@ [quo/text {:size :paragraph-2 :style (style/swaps-powered-by theme)} - (i18n/label :t/swaps-powered-by {:provider (:full-name provider)})]]])) + (i18n/label :t/swaps-powered-by + {:provider (if provider (:full-name provider) (i18n/label :t/unknown))})]]])) (defn view [] diff --git a/src/status_im/subs/bottom_sheet.cljs b/src/status_im/subs/bottom_sheet.cljs new file mode 100644 index 0000000000..43bc7b06dd --- /dev/null +++ b/src/status_im/subs/bottom_sheet.cljs @@ -0,0 +1,8 @@ +(ns status-im.subs.bottom-sheet + (:require + [re-frame.core :as re-frame])) + +(re-frame/reg-sub + :bottom-sheet-sheets + :<- [:bottom-sheet] + :-> :sheets) diff --git a/src/status_im/subs/root.cljs b/src/status_im/subs/root.cljs index 2bf2aabb00..3c0323dc11 100644 --- a/src/status_im/subs/root.cljs +++ b/src/status_im/subs/root.cljs @@ -4,6 +4,7 @@ status-im.subs.activity-center status-im.subs.alert-banner status-im.subs.biometrics + status-im.subs.bottom-sheet status-im.subs.chats status-im.subs.communities status-im.subs.community.account-selection diff --git a/src/status_im/subs/wallet/swap.cljs b/src/status_im/subs/wallet/swap.cljs index bcf236bc91..7264aeca55 100644 --- a/src/status_im/subs/wallet/swap.cljs +++ b/src/status_im/subs/wallet/swap.cljs @@ -78,11 +78,6 @@ :<- [:wallet/swap] :-> :max-slippage) -(rf/reg-sub - :wallet/swap-loading-fees? - :<- [:wallet/swap] - :-> :loading-fees?) - (rf/reg-sub :wallet/swap-approval-transaction-id :<- [:wallet/swap] @@ -157,9 +152,10 @@ :wallet/swap-proposal-provider :<- [:wallet/swap-proposal] (fn [swap-proposal] - (let [bridge-name (:bridge-name swap-proposal) - provider-key (keyword (string/lower-case bridge-name))] - (get constants/swap-providers provider-key)))) + (when swap-proposal + (let [bridge-name (:bridge-name swap-proposal) + provider-key (keyword (string/lower-case bridge-name))] + (get constants/swap-providers provider-key))))) (rf/reg-sub :wallet/swap-proposal-approval-required diff --git a/src/status_im/subs/wallet/swap_test.cljs b/src/status_im/subs/wallet/swap_test.cljs index 6598cee89c..1d406dd701 100644 --- a/src/status_im/subs/wallet/swap_test.cljs +++ b/src/status_im/subs/wallet/swap_test.cljs @@ -141,7 +141,6 @@ :eip-1559-enabled true :l-1-gas-fee "0"}} :error-response "Error" - :loading-fees? false :loading-swap-proposal? false :max-slippage 0.5}) @@ -218,14 +217,6 @@ swap-data) (is (match? 0.5 (rf/sub [sub-name]))))) -(h/deftest-sub :wallet/swap-loading-fees? - [sub-name] - (testing "Return if swap is loading fees" - (swap! rf-db/app-db assoc-in - [:wallet :ui :swap] - swap-data) - (is (false? (rf/sub [sub-name]))))) - (h/deftest-sub :wallet/swap-loading-swap-proposal? [sub-name] (testing "Return if swap is loading the swap proposal"