From e2d597401c6ec26f95395178f988eac4d1eadc6d Mon Sep 17 00:00:00 2001 From: Brian Sztamfater Date: Fri, 6 Sep 2024 11:30:15 -0300 Subject: [PATCH] feat(swap): trigger swap transactions (#21134) Signed-off-by: Brian Sztamfater --- .../wallet/approval_label/schema.cljs | 1 + .../wallet/approval_label/view.cljs | 6 +- src/status_im/constants.cljs | 3 + .../contexts/wallet/common/utils.cljs | 20 +- .../contexts/wallet/send/events.cljs | 2 +- src/status_im/contexts/wallet/signals.cljs | 16 +- .../contexts/wallet/swap/events.cljs | 212 ++++++++++----- .../wallet/swap/select_asset_to_pay/view.cljs | 2 +- .../wallet/swap/set_spending_cap/view.cljs | 62 ++--- .../contexts/wallet/swap/setup_swap/view.cljs | 40 +-- .../wallet/swap/swap_confirmation/style.cljs | 4 + .../wallet/swap/swap_confirmation/view.cljs | 249 ++++++++++-------- src/status_im/subs/wallet/swap.cljs | 43 ++- translations/en.json | 3 + 14 files changed, 414 insertions(+), 249 deletions(-) diff --git a/src/quo/components/wallet/approval_label/schema.cljs b/src/quo/components/wallet/approval_label/schema.cljs index 94c4ad9d11..02e91233d0 100644 --- a/src/quo/components/wallet/approval_label/schema.cljs +++ b/src/quo/components/wallet/approval_label/schema.cljs @@ -9,6 +9,7 @@ [:token-value :string] [:token-symbol :string] [:container-style {:optional true} [:maybe :map]] + [:show-view-button? {:optional true} [:maybe :boolean]] [:button-props {:optional true} [:maybe :map]] [:customization-color {:optional true} [:maybe :schema.common/customization-color]]]]] :any]) diff --git a/src/quo/components/wallet/approval_label/view.cljs b/src/quo/components/wallet/approval_label/view.cljs index 711b6e289a..b7a2b1d3f9 100644 --- a/src/quo/components/wallet/approval_label/view.cljs +++ b/src/quo/components/wallet/approval_label/view.cljs @@ -24,7 +24,7 @@ (defn- view-internal [{:keys [status token-value token-symbol - container-style button-props] + container-style button-props show-view-button?] :as props}] (let [theme (quo.theme/use-theme) customization-color (or (:customization-color props) :blue) @@ -56,7 +56,9 @@ (i18n/label (status-message status) {:amount token-value :symbol token-symbol})]] - (when button-props + (when (and button-props + (or (= status :approve) + (and (not= status :approve) show-view-button?))) [button/button (merge {:type (if (= status :approve) :primary :grey) :background (when-not (= status :approve) :blur) diff --git a/src/status_im/constants.cljs b/src/status_im/constants.cljs index d769362282..9778202047 100644 --- a/src/status_im/constants.cljs +++ b/src/status_im/constants.cljs @@ -565,6 +565,7 @@ (def ^:const bridge-name-erc-721-transfer "ERC721Transfer") (def ^:const bridge-name-erc-1155-transfer "ERC1155Transfer") (def ^:const bridge-name-hop "Hop") +(def ^:const bridge-name-paraswap "Paraswap") (def ^:const bridge-assets #{"ETH" "USDT" "USDC" "DAI"}) @@ -605,3 +606,5 @@ (def ^:const transaction-status-success "Success") (def ^:const transaction-status-pending "Pending") (def ^:const transaction-status-failed "Failed") + +(def ^:const min-token-decimals-to-display 6) diff --git a/src/status_im/contexts/wallet/common/utils.cljs b/src/status_im/contexts/wallet/common/utils.cljs index d13fd97527..5256730567 100644 --- a/src/status_im/contexts/wallet/common/utils.cljs +++ b/src/status_im/contexts/wallet/common/utils.cljs @@ -407,7 +407,8 @@ :TransferTx tx-data})) (defn transaction-path - [{:keys [from-address to-address token-id token-address route data eth-transfer?]}] + [{:keys [from-address to-address token-id-from token-address token-id-to route data + slippage-percentage eth-transfer?]}] (let [{:keys [bridge-name amount-in bonder-fees from to]} route tx-data (transaction-data {:from-address from-address @@ -425,14 +426,14 @@ (assoc :ERC721TransferTx (assoc tx-data :Recipient to-address - :TokenID token-id + :TokenID token-id-from :ChainID to-chain-id)) (= bridge-name constants/bridge-name-erc-1155-transfer) (assoc :ERC1155TransferTx (assoc tx-data :Recipient to-address - :TokenID token-id + :TokenID token-id-from :ChainID to-chain-id :Amount amount-in)) @@ -444,18 +445,27 @@ (assoc tx-data :ChainID from-chain-id :ChainIDTo to-chain-id - :Symbol token-id + :Symbol token-id-from :Recipient to-address :Amount amount-in :BonderFee bonder-fees)) + (= bridge-name constants/bridge-name-paraswap) + (assoc :SwapTx + (assoc tx-data + :ChainID from-chain-id + :ChainIDTo to-chain-id + :TokenIDFrom token-id-from + :TokenIDTo token-id-to + :SlippagePercentage slippage-percentage)) + (not (or (= bridge-name constants/bridge-name-erc-721-transfer) (= bridge-name constants/bridge-name-transfer) (= bridge-name constants/bridge-name-hop))) (assoc :CbridgeTx (assoc tx-data :ChainID to-chain-id - :Symbol token-id + :Symbol token-id-from :Recipient to-address :Amount amount-in))))) diff --git a/src/status_im/contexts/wallet/send/events.cljs b/src/status_im/contexts/wallet/send/events.cljs index 4ea77ffcc3..807455c792 100644 --- a/src/status_im/contexts/wallet/send/events.cljs +++ b/src/status_im/contexts/wallet/send/events.cljs @@ -618,7 +618,7 @@ :from-address from-address :route route :token-address token-address - :token-id (if collectible + :token-id-from (if collectible (utils.hex/number-to-hex token-id) token-id) diff --git a/src/status_im/contexts/wallet/signals.cljs b/src/status_im/contexts/wallet/signals.cljs index 99b6a66377..2d1f882c20 100644 --- a/src/status_im/contexts/wallet/signals.cljs +++ b/src/status_im/contexts/wallet/signals.cljs @@ -20,10 +20,22 @@ (= tx-status constants/transaction-status-failed) :failed) swap-approval-transaction-id (get-in db [:wallet :ui :swap :approval-transaction-id]) - swap-approval-transaction? (= swap-approval-transaction-id tx-hash)] + swap-approval-transaction? (= swap-approval-transaction-id tx-hash) + swap-transaction-ids (get-in db [:wallet :swap-transaction-ids]) + swap-transaction? (and swap-transaction-ids + (contains? swap-transaction-ids tx-hash))] (cond-> {:db (update-in db [:wallet :transactions tx-hash] assoc :status status)} swap-approval-transaction? - (assoc :fx [[:dispatch [:wallet.swap/approve-transaction-update status]]]))))) + (assoc :fx + [[:dispatch + [:wallet.swap/approve-transaction-update + {:status status}]]]) + swap-transaction? + (assoc :fx + [[:dispatch + [:wallet.swap/swap-transaction-update + {:tx-hash tx-hash + :status status}]]]))))) (rf/reg-event-fx :wallet/signal-received diff --git a/src/status_im/contexts/wallet/swap/events.cljs b/src/status_im/contexts/wallet/swap/events.cljs index 3a2cf4be8c..899444dc9f 100644 --- a/src/status_im/contexts/wallet/swap/events.cljs +++ b/src/status_im/contexts/wallet/swap/events.cljs @@ -7,8 +7,11 @@ [status-im.contexts.wallet.sheets.network-selection.view :as network-selection] [taoensso.timbre :as log] [utils.address :as address] + [utils.debounce :as debounce] + [utils.hex :as hex] [utils.i18n :as i18n] - [utils.number])) + [utils.number] + [utils.number :as number])) (rf/reg-event-fx :wallet.swap/start (fn [{:keys [_db]}] @@ -95,6 +98,7 @@ :fromLockedAmount from-locked-amount} amount-in (assoc :amountIn amount-in-hex) amount-out (assoc :amountOut amount-out-hex))]] + (when-let [amount (or amount-in amount-out)] {:db (update-in db [:wallet :ui :swap] @@ -165,63 +169,113 @@ (rf/reg-event-fx :wallet/swap-transaction (fn [{:keys [db]} [sha3-pwd]] - (let [wallet-address (get-in db [:wallet :current-viewing-account-address]) - {:keys [asset-to-pay swap-proposal network - approval-transaction-id]} (get-in db [:wallet :ui :swap]) - transactions (get-in db [:wallet :transactions]) - approval-transaction (when approval-transaction-id - (get transactions approval-transaction-id)) - already-approved? (and approval-transaction - (= (:status approval-transaction) :confirmed)) - approval-required? (and (:approval-required swap-proposal) - (not already-approved?)) - multi-transaction-type constants/multi-transaction-type-swap - swap-chain-id (:chain-id network) - token-id (:symbol asset-to-pay) - erc20-transfer? (and asset-to-pay (not= token-id "ETH")) - eth-transfer? (and asset-to-pay (not erc20-transfer?)) - token-address (when erc20-transfer? - (get-in asset-to-pay - [:balances-per-chain swap-chain-id :address])) - data (when erc20-transfer? - (native-module/encode-transfer - (address/normalized-hex wallet-address) - (:amount-in swap-proposal))) - transaction-paths (if approval-required? - [(utils/approval-path {:route swap-proposal - :token-address token-address - :from-address wallet-address - :to-address wallet-address})] - [(utils/transaction-path - {:to-address wallet-address - :from-address wallet-address - :route swap-proposal - :token-address token-address - :token-id token-id - :data data - :eth-transfer? eth-transfer?})]) - request-params [(utils/multi-transaction-command - {:from-address wallet-address - :to-address wallet-address - :from-asset token-id - :to-asset token-id - :amount-out (if eth-transfer? - (:amount-out swap-proposal) - "0x0") - :multi-transaction-type multi-transaction-type}) - transaction-paths - sha3-pwd]] + (let [wallet-address (get-in db + [:wallet + :current-viewing-account-address]) + {:keys [asset-to-pay asset-to-receive + swap-proposal network amount + approval-transaction-id + max-slippage]} (get-in db [:wallet :ui :swap]) + transactions (get-in db [:wallet :transactions]) + approval-transaction (when approval-transaction-id + (get transactions approval-transaction-id)) + already-approved? (and approval-transaction + (= (:status approval-transaction) + :confirmed)) + approval-required? (and (:approval-required swap-proposal) + (not already-approved?)) + multi-transaction-type constants/multi-transaction-type-swap + swap-chain-id (:chain-id network) + token-id-from (:symbol asset-to-pay) + token-id-to (:symbol asset-to-receive) + erc20-transfer? (and asset-to-pay (not= token-id-from "ETH")) + eth-transfer? (and asset-to-pay (not erc20-transfer?)) + token-address (when erc20-transfer? + (get-in asset-to-pay + [:balances-per-chain swap-chain-id + :address])) + data (when erc20-transfer? + (native-module/encode-transfer + (address/normalized-hex wallet-address) + (:amount-in swap-proposal))) + transaction-paths (if approval-required? + [(utils/approval-path + {:route swap-proposal + :token-address token-address + :from-address wallet-address + :to-address wallet-address})] + [(utils/transaction-path + {:to-address wallet-address + :from-address wallet-address + :route swap-proposal + :token-address token-address + :token-id-from token-id-from + :token-id-to token-id-to + :data data + :slippage-percentage max-slippage + :eth-transfer? eth-transfer?})]) + request-params [(utils/multi-transaction-command + {:from-address wallet-address + :to-address wallet-address + :from-asset token-id-from + :to-asset (if approval-required? + token-id-from + token-id-to) + :amount-out (if eth-transfer? + (:amount-out swap-proposal) + "0x0") + :multi-transaction-type + multi-transaction-type}) + transaction-paths + sha3-pwd]] (log/info "multi transaction called") {:json-rpc/call [{:method "wallet_createMultiTransaction" :params request-params :on-success (fn [result] (when result - (rf/dispatch [:wallet.swap/add-authorized-transaction - {:transaction result - :approval-transaction? approval-required?}]) - (rf/dispatch [:dismiss-modal - :screen/wallet.swap-set-spending-cap]) - (rf/dispatch [:hide-bottom-sheet]))) + (let [receive-token-decimals (:decimals asset-to-receive) + amount-out (:amount-out swap-proposal) + decimals-to-display + (min + receive-token-decimals + constants/min-token-decimals-to-display) + receive-amount (when amount-out + (number/remove-trailing-zeroes + (.toFixed (number/convert-to-whole-number + (native-module/hex-to-number + (hex/normalize-hex + amount-out)) + receive-token-decimals) + decimals-to-display)))] + (rf/dispatch [:wallet.swap/add-authorized-transaction + (cond-> {:transaction result + :approval-transaction? + approval-required?} + (not approval-required?) + (assoc :swap-data + {:pay-token-symbol token-id-from + :pay-amount amount + :receive-token-symbol token-id-to + :receive-amount receive-amount}))]) + (rf/dispatch [:hide-bottom-sheet]) + (rf/dispatch [:dismiss-modal + (if approval-required? + :screen/wallet.swap-set-spending-cap + :screen/wallet.swap-confirmation)]) + (when-not approval-required? + (rf/dispatch [:wallet/select-account-tab :activity]) + (debounce/debounce-and-dispatch [:wallet/clean-swap] 1000) + (debounce/debounce-and-dispatch + [:toasts/upsert + {:id :swap-transaction-pending + :icon :i/info + :type :neutral + :text (i18n/label :t/swapping-to + {:pay-amount amount + :pay-token-symbol token-id-from + :receive-token-symbol token-id-to + :receive-amount receive-amount})}] + 500))))) :on-error (fn [error] (log/error "failed swap transaction" {:event :wallet/swap-transaction @@ -233,20 +287,30 @@ :text (:message error)}]))}]}))) (rf/reg-event-fx :wallet.swap/add-authorized-transaction - (fn [{:keys [db]} [{:keys [transaction approval-transaction?]}]] - (let [transaction-batch-id (:id transaction) + (fn [{:keys [db]} [{:keys [transaction swap-data approval-transaction?]}]] + (let [transactions (get-in db [:wallet :transactions] {}) + transaction-batch-id (:id transaction) transaction-hashes (:hashes transaction) transaction-ids (flatten (vals transaction-hashes)) - transaction-details (send-utils/map-multitransaction-by-ids transaction-batch-id - transaction-hashes)] + transaction-id (first transaction-ids) + transaction-details (cond-> (send-utils/map-multitransaction-by-ids transaction-batch-id + transaction-hashes) + :always (assoc-in [transaction-id :tx-type] :swap) + swap-data (assoc-in [transaction-id :swap-data] swap-data)) + swap-transaction-ids (get-in db [:wallet :swap-transaction-ids])] {:db (cond-> db - :always (assoc-in [:wallet :transactions] transaction-details) - :always (assoc-in [:wallet :ui :swap :transaction-ids] transaction-ids) - approval-transaction? (assoc-in [:wallet :ui :swap :approval-transaction-id] - (first transaction-ids)))}))) + :always (assoc-in [:wallet :transactions] + (merge transactions transaction-details)) + :always (assoc-in [:wallet :ui :swap :transaction-ids] transaction-ids) + approval-transaction? (assoc-in [:wallet :ui :swap :approval-transaction-id] + transaction-id) + (not approval-transaction?) (assoc-in [:wallet :swap-transaction-ids] + (if swap-transaction-ids + (conj swap-transaction-ids transaction-id) + (hash-set transaction-id))))}))) (rf/reg-event-fx :wallet.swap/approve-transaction-update - (fn [{:keys [db]} [status]] + (fn [{:keys [db]} [{:keys [status]}]] (let [{:keys [amount asset-to-pay swap-proposal]} (get-in db [:wallet :ui :swap]) provider-name (:bridge-name swap-proposal) token-symbol (:symbol asset-to-pay) @@ -276,3 +340,27 @@ :account-name account-name}))}]]]} (not transaction-confirmed?) (assoc :db (update-in db [:wallet :ui :swap] dissoc :approval-transaction-id))))))) + +(rf/reg-event-fx :wallet.swap/swap-transaction-update + (fn [{:keys [db]} [{:keys [tx-hash status]}]] + (let [{:keys [pay-amount pay-token-symbol + receive-amount receive-token-symbol]} (get-in db + [:wallet :transactions tx-hash + :swap-data]) + transaction-confirmed-or-failed? (#{:confirmed :failed} status) + transaction-confirmed? (= status :confirmed)] + (when transaction-confirmed-or-failed? + {:db (-> db + (update-in [:wallet :swap-transaction-ids] disj tx-hash) + (update-in [:wallet :transactions] dissoc tx-hash)) + :fx [[:dispatch + [:toasts/upsert + {:id :swap-transaction-update + :type (if transaction-confirmed? :positive :negative) + :text (if transaction-confirmed? + (i18n/label :t/swapped-to + {:pay-amount pay-amount + :pay-token-symbol pay-token-symbol + :receive-token-symbol receive-token-symbol + :receive-amount receive-amount}) + (i18n/label :t/swap-failed))}]]]})))) 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 c42af3dfaa..3b3d82ef54 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 @@ -22,7 +22,7 @@ [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 "SNT"])] + 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) 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 acf7859f31..36dbe407e1 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 @@ -1,6 +1,5 @@ (ns status-im.contexts.wallet.swap.set-spending-cap.view (:require - [native-module.core :as native-module] [quo.core :as quo] [quo.foundations.resources :as resources] [quo.theme :as quo.theme] @@ -12,26 +11,17 @@ [status-im.contexts.wallet.common.utils.external-links :as external-links] [status-im.contexts.wallet.swap.set-spending-cap.style :as style] [utils.address :as address-utils] - [utils.hex :as hex] [utils.i18n :as i18n] - [utils.number :as number] [utils.re-frame :as rf] [utils.security.core :as security])) (defn- swap-title [] - (let [asset-to-pay (rf/sub [:wallet/swap-asset-to-pay]) - amount-in (rf/sub [:wallet/swap-proposal-amount-in]) - account (rf/sub [:wallet/current-viewing-account]) - provider (rf/sub [:wallet/swap-proposal-provider]) - pay-token-symbol (:symbol asset-to-pay) - pay-token-decimals (:decimals asset-to-pay) - pay-amount (when amount-in - (number/convert-to-whole-number - (native-module/hex-to-number - (hex/normalize-hex - amount-in)) - pay-token-decimals))] + (let [asset-to-pay (rf/sub [:wallet/swap-asset-to-pay]) + pay-amount (rf/sub [:wallet/swap-pay-amount]) + account (rf/sub [:wallet/current-viewing-account]) + provider (rf/sub [:wallet/swap-proposal-provider]) + pay-token-symbol (:symbol asset-to-pay)] [rn/view {:style style/content-container} [rn/view {:style {:flex-direction :row}} [quo/text @@ -72,17 +62,10 @@ (defn- spending-cap-section [] - (let [theme (quo.theme/use-theme) - asset-to-pay (rf/sub [:wallet/swap-asset-to-pay]) - amount-in (rf/sub [:wallet/swap-proposal-amount-in]) - pay-token-symbol (:symbol asset-to-pay) - pay-token-decimals (:decimals asset-to-pay) - pay-amount (when amount-in - (number/convert-to-whole-number - (native-module/hex-to-number - (hex/normalize-hex - amount-in)) - pay-token-decimals))] + (let [theme (quo.theme/use-theme) + asset-to-pay (rf/sub [:wallet/swap-asset-to-pay]) + pay-amount (rf/sub [:wallet/swap-pay-amount]) + pay-token-symbol (:symbol asset-to-pay)] [rn/view {:style style/summary-section-container} [quo/text {:size :paragraph-2 @@ -98,18 +81,11 @@ (defn- account-section [] - (let [theme (quo.theme/use-theme) - asset-to-pay (rf/sub [:wallet/swap-asset-to-pay]) - amount-in (rf/sub [:wallet/swap-proposal-amount-in]) - account (rf/sub [:wallet/current-viewing-account]) - pay-token-symbol (:symbol asset-to-pay) - pay-token-decimals (:decimals asset-to-pay) - pay-amount (when amount-in - (number/convert-to-whole-number - (native-module/hex-to-number - (hex/normalize-hex - amount-in)) - pay-token-decimals))] + (let [theme (quo.theme/use-theme) + asset-to-pay (rf/sub [:wallet/swap-asset-to-pay]) + account (rf/sub [:wallet/current-viewing-account]) + pay-amount (rf/sub [:wallet/swap-pay-amount]) + pay-token-symbol (:symbol asset-to-pay)] [rn/view {:style style/summary-section-container} [quo/text {:size :paragraph-2 @@ -216,14 +192,14 @@ {:title (i18n/label :t/network) :subtitle (:full-name network) :network-image (:source network)}] - [data-item - {:title (i18n/label :t/est-time) - :subtitle (i18n/label :t/time-in-mins {:minutes (str estimated-time)})}] [data-item {:title (i18n/label :t/max-fees) :subtitle max-fees :loading? loading-fees? - :size :small}]]])) + :size :small}] + [data-item + {:title (i18n/label :t/est-time) + :subtitle (i18n/label :t/time-in-mins {:minutes (str estimated-time)})}]]])) (defn- slide-button [] @@ -234,7 +210,7 @@ (security/safe-unmask-data %)]))] [standard-auth/slide-button {:size :size-48 - :track-text (i18n/label :t/slide-to-swap) + :track-text (i18n/label :t/slide-to-sign) :container-style {:z-index 2} :customization-color (:color account) :disabled? loading-fees? 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 32546012ec..78fad53450 100644 --- a/src/status_im/contexts/wallet/swap/setup_swap/view.cljs +++ b/src/status_im/contexts/wallet/swap/setup_swap/view.cljs @@ -3,6 +3,7 @@ [native-module.core :as native-module] [quo.core :as quo] [react-native.core :as rn] + [react-native.platform :as platform] [react-native.safe-area :as safe-area] [status-im.common.controlled-input.utils :as controlled-input] [status-im.common.events-helper :as events-helper] @@ -17,7 +18,6 @@ [utils.re-frame :as rf] [utils.string :as utils.string])) -(def ^:private min-token-decimals-to-display 6) (def ^:private default-text-for-unfocused-input "0.00") (defn- on-close @@ -94,7 +94,7 @@ (.toFixed (money/bignumber pay-token-balance-selected-chain) (min pay-token-decimals - min-token-decimals-to-display))) + constants/min-token-decimals-to-display))) approval-amount-required-num (when approval-amount-required (str (number/convert-to-whole-number (native-module/hex-to-number @@ -128,13 +128,14 @@ :customization-color :blue :show-approval-label? (and swap-proposal approval-required) :auto-focus? true + :show-keyboard? false :status (cond (and loading-swap-proposal? (not input-focused?)) :loading input-focused? :typing :else :disabled) :currency-symbol currency-symbol :on-token-press on-token-press - :on-max-press on-max-press + :on-max-press #(on-max-press available-crypto-limit) :on-input-focus on-input-focus :value pay-input-amount :fiat-value pay-token-fiat-value @@ -177,7 +178,8 @@ amount-out)) receive-token-decimals)) amount-out-num (if amount-out-whole-number - (str amount-out-whole-number) + (number/remove-trailing-zeroes + (.toFixed amount-out-whole-number receive-token-decimals)) default-text-for-unfocused-input) receive-token-fiat-value (str (utils/calculate-token-fiat-value {:currency currency @@ -191,6 +193,7 @@ :show-approval-label? false :enable-swap? true :input-disabled? true + :show-keyboard? false :status (cond (and loading-swap-proposal? (not input-focused?)) :loading input-focused? :typing @@ -226,17 +229,16 @@ (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) error-response (rf/sub [:wallet/swap-error-response]) - network (rf/sub [:wallet/swap-network]) 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]) pay-input-amount (controlled-input/input-value pay-input-state) pay-token-decimals (:decimals asset-to-pay) - network-chain-id (:chain-id network) - pay-token-balance-selected-chain (get-in asset-to-pay - [:balances-per-chain network-chain-id - :balance] - 0) + on-review-swap-press (rn/use-callback + (fn [] + (rf/dispatch [:navigate-to-within-stack + [:screen/wallet.swap-confirmation + :screen/wallet.setup-swap]]))) on-press (rn/use-callback (fn [c] (let @@ -256,7 +258,14 @@ (fn [] (set-pay-input-state controlled-input/delete-last) - (rf/dispatch [:wallet/clean-swap-proposal])))] + (rf/dispatch [:wallet/clean-swap-proposal]))) + on-max-press (rn/use-callback + (fn [max-value] + (set-pay-input-state + (fn [input-state] + (controlled-input/set-input-value + input-state + max-value)))))] [rn/view {:style style/container} [account-switcher/view {:on-press on-close @@ -266,11 +275,13 @@ [rn/view {:style style/inputs-container} [pay-token-input {:input-state pay-input-state - :on-max-press #(set-pay-input-state pay-token-balance-selected-chain) + :on-max-press on-max-press :input-focused? pay-input-focused? :on-token-press #(js/alert "Token Pressed") :on-approve-press #(rf/dispatch [:open-modal :screen/wallet.swap-set-spending-cap]) - :on-input-focus #(set-pay-input-focused? true)}] + :on-input-focus (fn [] + (when platform/android? (rf/dispatch [:dismiss-keyboard])) + (set-pay-input-focused? true))}] [swap-order-button {:on-press #(js/alert "Swap Order Pressed")}] [receive-token-input {:input-focused? (not pay-input-focused?) @@ -283,8 +294,7 @@ :text (i18n/label :t/something-went-wrong-please-try-again-later)}]) (when (or loading-swap-proposal? swap-proposal) [transaction-details]) - [action-button - {:on-press #(js/alert "Review swap pressed")}]] + [action-button {:on-press on-review-swap-press}]] [quo/numbered-keyboard {:container-style style/keyboard-container :left-action :dot diff --git a/src/status_im/contexts/wallet/swap/swap_confirmation/style.cljs b/src/status_im/contexts/wallet/swap/swap_confirmation/style.cljs index dc6c644670..253f65bbe8 100644 --- a/src/status_im/contexts/wallet/swap/swap_confirmation/style.cljs +++ b/src/status_im/contexts/wallet/swap/swap_confirmation/style.cljs @@ -37,3 +37,7 @@ (def providers-container {:align-items :center :margin-top 12}) + +(defn swaps-powered-by + [theme] + {:color (colors/theme-colors colors/neutral-80-opa-40 colors/white-opa-70 theme)}) 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 a7509acdee..a8503c5d18 100644 --- a/src/status_im/contexts/wallet/swap/swap_confirmation/view.cljs +++ b/src/status_im/contexts/wallet/swap/swap_confirmation/view.cljs @@ -1,58 +1,66 @@ (ns status-im.contexts.wallet.swap.swap-confirmation.view (:require [quo.core :as quo] - [quo.foundations.colors :as colors] [quo.theme :as quo.theme] [react-native.core :as rn] [react-native.safe-area :as safe-area] [status-im.common.floating-button-page.view :as floating-button-page] [status-im.common.standard-authentication.core :as standard-auth] + [status-im.constants :as constants] [status-im.contexts.wallet.swap.swap-confirmation.style :as style] [utils.address :as address-utils] [utils.i18n :as i18n] - [utils.re-frame :as rf])) + [utils.re-frame :as rf] + [utils.security.core :as security])) (defn- on-close-action [] (rf/dispatch [:navigate-back])) (defn- swap-title - [{:keys [pay-token-symbol pay-amount receive-token-symbol receive-amount account]}] - [rn/view {:style style/content-container} - [rn/view {:style {:flex-direction :row}} - [quo/text - {:size :heading-1 - :weight :semi-bold - :style style/title-container - :accessibility-label :title-label} - (i18n/label :t/swap)] - [quo/summary-tag - {:token pay-token-symbol - :label (str pay-amount " " pay-token-symbol) - :type :token}]] - [rn/view {:style style/title-line-with-margin-top} - [quo/text - {:size :heading-1 - :weight :semi-bold - :style style/title-container - :accessibility-label :title-label} - (i18n/label :t/to)] - [quo/summary-tag - {:token receive-token-symbol - :label (str receive-amount " " receive-token-symbol) - :type :token}]] - [rn/view {:style style/title-line-with-margin-top} - [quo/text - {:size :heading-1 - :weight :semi-bold - :style style/title-container - :accessibility-label :send-label} - (i18n/label :t/in)] - [quo/summary-tag - {:label (:name account) - :type :account - :emoji (:emoji account) - :customization-color (:color account)}]]]) + [] + (let [asset-to-pay (rf/sub [:wallet/swap-asset-to-pay]) + asset-to-receive (rf/sub [:wallet/swap-asset-to-receive]) + account (rf/sub [:wallet/current-viewing-account]) + receive-amount (rf/sub [:wallet/swap-receive-amount]) + pay-amount (rf/sub [:wallet/swap-pay-amount]) + pay-token-symbol (:symbol asset-to-pay) + receive-token-symbol (:symbol asset-to-receive)] + [rn/view {:style style/content-container} + [rn/view {:style {:flex-direction :row}} + [quo/text + {:size :heading-1 + :weight :semi-bold + :style style/title-container + :accessibility-label :title-label} + (i18n/label :t/swap)] + [quo/summary-tag + {:token pay-token-symbol + :label (str pay-amount " " pay-token-symbol) + :type :token}]] + [rn/view {:style style/title-line-with-margin-top} + [quo/text + {:size :heading-1 + :weight :semi-bold + :style style/title-container + :accessibility-label :title-label} + (i18n/label :t/to)] + [quo/summary-tag + {:token receive-token-symbol + :label (str receive-amount " " receive-token-symbol) + :type :token}]] + [rn/view {:style style/title-line-with-margin-top} + [quo/text + {:size :heading-1 + :weight :semi-bold + :style style/title-container + :accessibility-label :send-label} + (i18n/label :t/in)] + [quo/summary-tag + {:label (:name account) + :type :account + :emoji (:emoji account) + :customization-color (:color account)}]]])) (defn- summary-section [{:keys [theme label title-accessibility-label amount token-symbol token-address network]}] @@ -74,6 +82,44 @@ :address (address-utils/get-shortened-compressed-key token-address) :size 32}}]])) +(defn- pay-section + [] + (let [theme (quo.theme/use-theme) + asset-to-pay (rf/sub [:wallet/swap-asset-to-pay]) + network (rf/sub [:wallet/swap-network]) + pay-amount (rf/sub [:wallet/swap-pay-amount]) + network-chain-id (:chain-id network) + network-name (:network-name network) + pay-token-symbol (:symbol asset-to-pay) + pay-token-address (get-in asset-to-pay [:balances-per-chain network-chain-id :address])] + [summary-section + {:title-accessibility-label :summary-section-pay + :label (i18n/label :t/pay) + :token-symbol pay-token-symbol + :amount pay-amount + :token-address pay-token-address + :network network-name + :theme theme}])) + +(defn- receive-section + [] + (let [theme (quo.theme/use-theme) + asset-to-receive (rf/sub [:wallet/swap-asset-to-receive]) + network (rf/sub [:wallet/swap-network]) + receive-amount (rf/sub [:wallet/swap-receive-amount]) + network-chain-id (:chain-id network) + network-name (:network-name network) + receive-token-symbol (:symbol asset-to-receive) + receive-token-address (get-in asset-to-receive [:balances-per-chain network-chain-id :address])] + [summary-section + {:title-accessibility-label :summary-section-receive + :label (i18n/label :t/receive) + :token-symbol receive-token-symbol + :amount receive-amount + :token-address receive-token-address + :network network-name + :theme theme}])) + (defn- data-item [{:keys [title subtitle loading?]}] [quo/data-item @@ -86,64 +132,59 @@ :subtitle subtitle}]) (defn- transaction-details - [{:keys [estimated-time-min max-fees max-slippage loading-fees?]}] - [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-min)})}] - [data-item - {:title (i18n/label :t/max-fees) - :subtitle max-fees - :loading? loading-fees?}] - [data-item - {:title (i18n/label :t/max-slippage) - :subtitle (str 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-fees? (rf/sub [:wallet/swap-loading-fees?]) + 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)})}] + [data-item + {:title (i18n/label :t/max-fees) + :subtitle max-fees + :loading? loading-fees?}] + [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 %)]))] + [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? + :auth-button-label (i18n/label :t/confirm) + :on-auth-success on-auth-success}])) (defn footer - [{:keys [estimated-time-min native-currency-symbol max-slippage theme account-color provider - loading-fees?]}] - (let [fee-formatted (rf/sub [:wallet/wallet-send-fee-fiat-formatted native-currency-symbol])] + [] + (let [provider (rf/sub [:wallet/swap-proposal-provider]) + theme (quo.theme/use-theme)] [:<> - [transaction-details - {:estimated-time-min estimated-time-min - :max-fees fee-formatted - :max-slippage max-slippage - :loading-fees? loading-fees? - :theme theme}] - [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? - :auth-button-label (i18n/label :t/confirm)}] + [transaction-details] + [slide-button] [rn/view {:style style/providers-container} [quo/text {:size :paragraph-2 - :style {:color (colors/theme-colors colors/neutral-80-opa-40 - colors/white-opa-70 - theme)}} - (i18n/label :t/swaps-powered-by {:provider (:name provider)})]]])) + :style (style/swaps-powered-by theme)} + (i18n/label :t/swaps-powered-by {:provider (:full-name provider)})]]])) (defn view [] - (let [theme (quo.theme/use-theme) - swap-transaction-data (rf/sub [:wallet/swap]) - {:keys [asset-to-pay max-slippage network - pay-amount providers swap-proposal - loading-fees?]} swap-transaction-data - receive-amount (:receive-amount swap-proposal) - receive-token (:receive-token swap-proposal) - receive-token-symbol (:symbol receive-token) - receive-token-address (:address receive-token) - estimated-time-min (:estimated-time swap-proposal) - pay-token-symbol (:symbol asset-to-pay) - pay-token-address (:address asset-to-pay) - native-currency-symbol (get-in swap-proposal [:from :native-currency-symbol]) - account (rf/sub [:wallet/current-viewing-account]) - account-color (:color account) - provider (first providers)] + (let [account (rf/sub [:wallet/current-viewing-account]) + account-color (:color account)] [rn/view {:style {:flex 1}} [floating-button-page/view {:footer-container-padding 0 @@ -153,36 +194,10 @@ :margin-top (safe-area/get-top) :background :blur :accessibility-label :top-bar}] - :footer [footer - {:estimated-time-min estimated-time-min - :native-currency-symbol native-currency-symbol - :max-slippage max-slippage - :account-color account-color - :provider provider - :loading-fees? loading-fees? - :theme theme}] + :footer [footer] :gradient-cover? true :customization-color account-color} [rn/view - [swap-title - {:pay-token-symbol pay-token-symbol - :pay-amount pay-amount - :receive-token-symbol receive-token-symbol - :receive-amount receive-amount - :account account}] - [summary-section - {:title-accessibility-label :summary-section-pay - :label (i18n/label :t/pay) - :token-symbol pay-token-symbol - :amount pay-amount - :token-address pay-token-address - :network (:network-name network) - :theme theme}] - [summary-section - {:title-accessibility-label :summary-section-receive - :label (i18n/label :t/receive) - :token-symbol receive-token-symbol - :amount receive-amount - :token-address receive-token-address - :network (:network-name network) - :theme theme}]]]])) + [swap-title] + [pay-section] + [receive-section]]]])) diff --git a/src/status_im/subs/wallet/swap.cljs b/src/status_im/subs/wallet/swap.cljs index 02bdc9075d..bcf236bc91 100644 --- a/src/status_im/subs/wallet/swap.cljs +++ b/src/status_im/subs/wallet/swap.cljs @@ -1,10 +1,13 @@ (ns status-im.subs.wallet.swap (:require [clojure.string :as string] + [native-module.core :as native-module] [re-frame.core :as rf] [status-im.constants :as constants] [status-im.contexts.wallet.common.utils :as utils] [status-im.contexts.wallet.send.utils :as send-utils] - [utils.money :as money])) + [utils.hex :as hex] + [utils.money :as money] + [utils.number :as number])) (rf/reg-sub :wallet/swap @@ -112,6 +115,44 @@ :<- [:wallet/swap-proposal] :-> :amount-in) +(rf/reg-sub + :wallet/swap-receive-amount + :<- [:wallet/swap-proposal-amount-out] + :<- [:wallet/swap-asset-to-receive] + (fn [[amount-out asset-to-receive]] + (let [receive-token-decimals (:decimals asset-to-receive) + receive-amount (when amount-out + (number/convert-to-whole-number + (native-module/hex-to-number + (hex/normalize-hex + amount-out)) + receive-token-decimals)) + receive-amount (when amount-out + (number/remove-trailing-zeroes + (.toFixed receive-amount + (min receive-token-decimals + constants/min-token-decimals-to-display))))] + (or receive-amount 0)))) + +(rf/reg-sub + :wallet/swap-pay-amount + :<- [:wallet/swap-proposal-amount-in] + :<- [:wallet/swap-asset-to-pay] + (fn [[amount-in asset-to-pay]] + (let [pay-token-decimals (:decimals asset-to-pay) + pay-amount (when amount-in + (number/convert-to-whole-number + (native-module/hex-to-number + (hex/normalize-hex + amount-in)) + pay-token-decimals)) + pay-amount (when amount-in + (number/remove-trailing-zeroes + (.toFixed pay-amount + (min pay-token-decimals + constants/min-token-decimals-to-display))))] + (or pay-amount 0)))) + (rf/reg-sub :wallet/swap-proposal-provider :<- [:wallet/swap-proposal] diff --git a/translations/en.json b/translations/en.json index bebdd77b75..ec31b6650b 100644 --- a/translations/en.json +++ b/translations/en.json @@ -2368,6 +2368,9 @@ "sun": "Sun", "swap": "Swap", "swap-details": "Swap details", + "swap-failed": "Swap failed", + "swapped-to": "Swapped {{pay-amount}} {{pay-token-symbol}} to {{receive-amount}} {{receive-token-symbol}}", + "swapping-to": "Swapping {{pay-amount}} {{pay-token-symbol}} to {{receive-amount}} {{receive-token-symbol}}", "swaps-powered-by": "Swaps powered by {{provider}}", "switch-to-simple-interface": "Switch to simple interface", "symbol": "Symbol",