diff --git a/.env b/.env index 558d337982..7ee60c452c 100644 --- a/.env +++ b/.env @@ -33,3 +33,4 @@ DELETE_MESSAGE_ENABLED=1 COLLECTIBLES_ENABLED=1 COMMANDS_ENABLED=1 TWO_MINUTES_SYNCING=1 +SWAP_ENABLED=1 diff --git a/ios/Podfile.lock b/ios/Podfile.lock index 03860e6a0e..ca91ec4c67 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -230,7 +230,7 @@ PODS: - React - react-native-status (1.0.0): - React - - react-native-status-keycard (2.5.36): + - react-native-status-keycard (2.5.37): - Keycard - React - react-native-webview (11.3.0): @@ -626,7 +626,7 @@ SPEC CHECKSUMS: FBLazyVector: 3bb422f41b18121b71783a905c10e58606f7dc3e FBReactNativeSpec: f2c97f2529dd79c083355182cc158c9f98f4bd6e Folly: b73c3869541e86821df3c387eb0af5f65addfab4 - glog: 5bc68409594b19a3e5c5cbced7b1ecf61053b709 + glog: 61334f8bdb4deea07543d4fbac3fb5948e78a7a5 Keycard: dd96182888da0aacf4de821b641103143bbb26cc libwebp: 98a37e597e40bfdb4c911fc98f2c53d0b12d05fc Permission-Camera: afad27bf90337684d4a86f3825112d648c8c4d3b @@ -653,7 +653,7 @@ SPEC CHECKSUMS: react-native-slider: 12bd76d3d568c9c5500825db54123d44b48e4ad4 react-native-splash-screen: 200d11d188e2e78cea3ad319964f6142b6384865 react-native-status: 45dbf1302ce3c258b459dfab137cd1c2c68c295d - react-native-status-keycard: eb84554a23315510948613a9467e7e3481be340c + react-native-status-keycard: 961d01ca190889ddf220206822fd752f8f4f3f7a react-native-webview: af9990b21a9aeafa8e8347746eb4116c0de086af React-RCTActionSheet: 89a0ca9f4a06c1f93c26067af074ccdce0f40336 React-RCTAnimation: 1bde3ecc0c104c55df246eda516e0deb03c4e49b diff --git a/resources/images/icons/gas@2x.png b/resources/images/icons/gas@2x.png new file mode 100644 index 0000000000..688640dd9b Binary files /dev/null and b/resources/images/icons/gas@2x.png differ diff --git a/resources/images/icons/gas@3x.png b/resources/images/icons/gas@3x.png new file mode 100644 index 0000000000..67da2c7315 Binary files /dev/null and b/resources/images/icons/gas@3x.png differ diff --git a/src/quo/components/list/item.cljs b/src/quo/components/list/item.cljs index b245280ff6..7689388e80 100644 --- a/src/quo/components/list/item.cljs +++ b/src/quo/components/list/item.cljs @@ -197,17 +197,19 @@ left-side-alignment icon-color icon-bg-color title subtitle subtitle-secondary active on-press on-long-press chevron size text-size accessory-text accessibility-label title-accessibility-label accessory-style - haptic-feedback haptic-type error animated animated-accessory? title-text-weight container-style] - :or {subtitle-max-lines 1 - theme :main - haptic-feedback true - animated platform/ios? - haptic-type :selection}}] + haptic-feedback haptic-type error animated animated-accessory? title-text-weight container-style + active-background-enabled] + :or {subtitle-max-lines 1 + theme :main + haptic-feedback true + animated platform/ios? + active-background-enabled true + haptic-type :selection}}] (let [theme (if disabled :disabled theme) {:keys [text-color active-background passive-background]} (themes theme) - icon-color (or icon-color (:icon-color (themes theme))) - icon-bg-color (or icon-bg-color (:icon-bg-color (themes theme))) + icon-color (or icon-color (:icon-color (themes theme))) + icon-bg-color (or icon-bg-color (:icon-bg-color (themes theme))) optional-haptic (fn [] (when haptic-feedback (haptic/trigger haptic-type))) @@ -223,7 +225,7 @@ [component (merge {:type :list-item :disabled disabled - :bg-color active-background + :bg-color (when active-background-enabled active-background) :accessibility-label accessibility-label} (when on-press {:on-press (fn [] diff --git a/src/status_im/subs.cljs b/src/status_im/subs.cljs index ebf848bb3c..056a38f8e7 100644 --- a/src/status_im/subs.cljs +++ b/src/status_im/subs.cljs @@ -180,6 +180,12 @@ (reg-root-key-sub :wallet/fetching-collection-assets :wallet/fetching-collection-assets) (reg-root-key-sub :wallet/collectible-assets :wallet/collectible-assets) (reg-root-key-sub :wallet/selected-collectible :wallet/selected-collectible) +(reg-root-key-sub :wallet/modal-selecting-source-token? :wallet/modal-selecting-source-token?) +(reg-root-key-sub :wallet/swap-from-token :wallet/swap-from-token) +(reg-root-key-sub :wallet/swap-to-token :wallet/swap-to-token) +(reg-root-key-sub :wallet/swap-from-token-amount :wallet/swap-from-token-amount) +(reg-root-key-sub :wallet/swap-to-token-amount :wallet/swap-to-token-amount) +(reg-root-key-sub :wallet/swap-advanced-mode? :wallet/swap-advanced-mode?) ;;commands (reg-root-key-sub :commands/select-account :commands/select-account) diff --git a/src/status_im/ui/screens/screens.cljs b/src/status_im/ui/screens/screens.cljs index 03ac242ece..0f6007e9ed 100644 --- a/src/status_im/ui/screens/screens.cljs +++ b/src/status_im/ui/screens/screens.cljs @@ -44,6 +44,17 @@ [status-im.ui.screens.currency-settings.views :as currency-settings] [status-im.ui.screens.dapps-permissions.views :as dapps-permissions] [status-im.ui.screens.default-sync-period-settings.view :as default-sync-period-settings] + [status-im.ui.screens.wallet.settings.views :as wallet-settings] + [status-im.ui.screens.wallet.transactions.views :as wallet-transactions] + [status-im.ui.screens.wallet.custom-tokens.views :as custom-tokens] + [status-im.ui.screens.wallet.accounts.views :as wallet.accounts] + [status-im.ui.screens.wallet.collectibles.views :as wallet.collectibles] + [status-im.ui.screens.wallet.account.views :as wallet.account] + [status-im.ui.screens.wallet.add-new.views :as add-account] + [status-im.ui.screens.wallet.account-settings.views :as account-settings] + [status-im.ui.screens.wallet.swap.views :as wallet.swap] + [status-im.ui.screens.status.views :as status.views] + [status-im.ui.screens.profile.user.views :as profile.user] [status-im.ui.screens.ens.views :as ens] [status-im.ui.screens.fleet-settings.views :as fleet-settings] [status-im.ui.screens.glossary.view :as glossary] @@ -91,7 +102,6 @@ [status-im.ui.screens.profile.contact.views :as contact] [status-im.ui.screens.profile.group-chat.views :as profile.group-chat] [status-im.ui.screens.profile.seed.views :as profile.seed] - [status-im.ui.screens.profile.user.views :as profile.user] [status-im.ui.screens.progress.views :as progress] [status-im.ui.screens.qr-scanner.views :as qr-scanner] [status-im.ui.screens.referrals.public-chat :as referrals.public-chat] @@ -99,7 +109,6 @@ [status-im.ui.screens.reset-password.views :as reset-password] [status-im.ui.screens.rpc-usage-info :as rpc-usage-info] [status-im.ui.screens.status.new.views :as status.new] - [status-im.ui.screens.status.views :as status.views] [status-im.ui.screens.stickers.views :as stickers] [status-im.ui.screens.sync-settings.views :as sync-settings] [status-im.ui.screens.terms-of-service.views :as terms-of-service] @@ -107,18 +116,10 @@ :as edit-wakuv2-node] [status-im.ui.screens.wakuv2-settings.views :as wakuv2-settings] - [status-im.ui.screens.wallet.account-settings.views :as account-settings] - [status-im.ui.screens.wallet.account.views :as wallet.account] [status-im.ui.screens.wallet.accounts-manage.views :as accounts-manage] - [status-im.ui.screens.wallet.accounts.views :as wallet.accounts] - [status-im.ui.screens.wallet.add-new.views :as add-account] [status-im.ui.screens.wallet.buy-crypto.views :as wallet.buy-crypto] - [status-im.ui.screens.wallet.collectibles.views :as wallet.collectibles] - [status-im.ui.screens.wallet.custom-tokens.views :as custom-tokens] [status-im.ui.screens.wallet.recipient.views :as recipient] - [status-im.ui.screens.wallet.send.views :as wallet.send] - [status-im.ui.screens.wallet.settings.views :as wallet-settings] - [status-im.ui.screens.wallet.transactions.views :as wallet-transactions])) + [status-im.ui.screens.wallet.send.views :as wallet.send])) (def components [{:name :chat-toolbar @@ -220,9 +221,9 @@ ;Chat {:name :chat :options {:popGesture false - :topBar {:title {:component {:name :chat-toolbar :id :chat-toolbar} - :alignment :fill} - :rightButtons (right-button-options :chat :more)}} + :topBar {:title {:component {:name :chat-toolbar :id :chat-toolbar} + :alignment :fill} + :rightButtons (right-button-options :chat :more)}} :right-handler chat/topbar-button :component chat/chat} @@ -395,6 +396,32 @@ :options {:topBar {:title {:text (i18n/label :t/wallet-manage-accounts)}}} :component accounts-manage/manage} + {:name :token-swap + ;;TODO dynamic title + :options {:topBar {:visible false}} + :component wallet.swap/swap} + + {:name :token-swap-advanced-nonce + :options {:topBar {:title {:text (i18n/label :t/nonce)}}} + :component wallet.swap/nonce-modal} + + {:name :token-swap-advanced-approve-token + :options {:topBar {:title {:text (i18n/label :t/approve-token)}}} + :component wallet.swap/approve-token-modal} + + {:name :token-swap-advanced-transaction-fee + :options {:topBar {:title {:text (i18n/label :t/transaction-fee)}}} + :component wallet.swap/transaction-fee-modal} + + {:name :token-swap-advanced-swap-details + :options {:topBar {:title {:text (i18n/label :t/swap-details)}}} + :component wallet.swap/swap-details-modal} + + {:name :swap-asset-selector + ;;TODO dynamic title + :options {:topBar {:visible false}} + :component wallet.swap/asset-selector} + ;;MY STATUS {:name :status @@ -703,8 +730,8 @@ :options {:topBar {:visible false}} :component wallet.buy-crypto/website} - {:name :nft-details - :insets {:bottom true} + {:name :nft-details + :insets {:bottom true} ;;TODO dynamic title :options {:topBar {:visible false}} :component wallet.collectibles/nft-details-modal} diff --git a/src/status_im/ui/screens/wallet/account/styles.cljs b/src/status_im/ui/screens/wallet/account/styles.cljs index 35e7675814..e1a4a00ece 100644 --- a/src/status_im/ui/screens/wallet/account/styles.cljs +++ b/src/status_im/ui/screens/wallet/account/styles.cljs @@ -42,3 +42,19 @@ :duration 200 :easing (.-ease ^js animation/easing) :useNativeDriver true})) + +(def round-action-button + {:background-color colors/blue + :height 44 + :flex 1 + :justify-content :center + :align-items :center + :width 44 + :border-radius 44}) + +(def top-actions + {:flex 1 + :flex-direction :row + :justify-content :space-between + :width "60%" + :align-self :center}) diff --git a/src/status_im/ui/screens/wallet/account/views.cljs b/src/status_im/ui/screens/wallet/account/views.cljs index 8520942762..bda115efe9 100644 --- a/src/status_im/ui/screens/wallet/account/views.cljs +++ b/src/status_im/ui/screens/wallet/account/views.cljs @@ -7,16 +7,17 @@ [quo.design-system.colors :as colors] [status-im.ui.components.icons.icons :as icons] [quo.core :as quo] + [quo.design-system.spacing :as spacing] [status-im.ui.components.react :as react] [status-im.ui.components.topbar :as topbar] [status-im.utils.config :as config] [status-im.ui.screens.wallet.account.styles :as styles] [status-im.ui.screens.wallet.accounts.sheets :as sheets] [status-im.ui.screens.wallet.accounts.views :as accounts] - [status-im.ui.screens.wallet.buy-crypto.views :as buy-crypto] [status-im.ui.screens.wallet.transactions.views :as history] [status-im.ui.components.tabs :as tabs] - [status-im.ui.screens.wallet.collectibles.views :as collectibles.views]) + [status-im.ui.screens.wallet.collectibles.views :as collectibles.views] + [status-im.ui.screens.wallet.buy-crypto.views :as buy-crypto]) (:require-macros [status-im.utils.views :as views])) (def state (reagent/atom {:tab :assets})) @@ -102,7 +103,6 @@ currency [:wallet/currency] opensea-enabled? [:opensea-enabled?] collectible-collection [:wallet/collectible-collection address] - mainnet? [:mainnet?] ethereum-network? [:ethereum-network?]] (let [{:keys [tab]} @state] [react/view {:flex 1} @@ -111,11 +111,10 @@ (when ethereum-network? [tabs/tab-title state :nft (i18n/label :t/wallet-collectibles) (= tab :nft)]) [tabs/tab-title state :history (i18n/label :t/history) (= tab :history)]] + [quo/separator {:style {:margin-top -8}}] (cond (= tab :assets) [:<> - (when mainnet? - [buy-crypto/banner]) (for [item tokens] ^{:key (:name item)} [accounts/render-asset item nil nil (:code currency)])] @@ -181,10 +180,31 @@ (styles/bottom-send-recv-buttons-lower anim-y button-group-height) #(reset! to-show false)))))))) +(defn round-action-button [{:keys [icon title on-press]}] + [react/view {:style {:flex 1 + :align-items :center + :margin-vertical (:large spacing/spacing)}} + [react/touchable-opacity {:style styles/round-action-button + :on-press on-press} + (icons/icon icon {:color colors/white})] + [quo/text {:color :secondary + :size :small + :style {:margin-top (:tiny spacing/spacing)}} + title]]) + +(defn top-actions [] + [react/view {:style styles/top-actions} + [round-action-button {:icon :main-icons/add + :title (i18n/label :t/buy-crypto) + :on-press #(re-frame/dispatch [:buy-crypto.ui/open-screen])}] + [round-action-button {:icon :main-icons/change + :title (i18n/label :t/swap) + :on-press #(re-frame/dispatch [:open-modal :token-swap])}]]) + (views/defview account [] (views/letsubs [{:keys [name address] :as account} [:multiaccount/current-account] fetching-error [:wallet/fetching-error]] - (let [anim-y (animation/create-value button-group-height) + (let [anim-y (animation/create-value button-group-height) scroll-y (animation/create-value 0)] (anim-listener anim-y scroll-y) [:<> @@ -206,9 +226,9 @@ @accounts/updates-counter @(re-frame/subscribe [:wallet/refreshing-history?])))} (when fetching-error - [react/view {:style {:flex 1 + [react/view {:style {:flex 1 :align-items :center - :margin 8}} + :margin 8}} [icons/icon :main-icons/warning {:color :red @@ -233,5 +253,8 @@ [react/scroll-view {:horizontal true} [react/view {:flex-direction :row :padding-top 8 :padding-bottom 12} [account-card account]]]] + (if config/swap-enabled? + [top-actions] + [buy-crypto/banner]) [assets-and-collections address]] [bottom-send-recv-buttons account anim-y]]))) diff --git a/src/status_im/ui/screens/wallet/swap/views.cljs b/src/status_im/ui/screens/wallet/swap/views.cljs new file mode 100644 index 0000000000..ff7149e380 --- /dev/null +++ b/src/status_im/ui/screens/wallet/swap/views.cljs @@ -0,0 +1,433 @@ +(ns status-im.ui.screens.wallet.swap.views + (:require [quo.core :as quo] + [quo.design-system.colors :as colors] + [re-frame.core :as re-frame] + [status-im.ethereum.tokens :as tokens] + [status-im.i18n.i18n :as i18n] + [status-im.ui.components.chat-icon.screen :as chat-icon] + [status-im.ui.components.icons.icons :as icons] + [status-im.ui.components.keyboard-avoid-presentation + :as + kb-presentation] + [status-im.ui.components.react :as react] + [status-im.ui.components.search-input.view :as search-input] + [status-im.ui.components.slider :as slider] + [status-im.wallet.swap.core :as wallet.swap] + [status-im.ui.components.toolbar :as toolbar] + [status-im.ui.components.topbar :as topbar] + [status-im.ui.screens.wallet.components.views :as wallet.components] + [status-im.utils.handlers :refer [ + [topbar/topbar + {:title (if source? + (i18n/label :t/select-token-to-swap) + (i18n/label :t/select-token-to-receive)) + :modal? true}] + + [search-input/search-input + {:search-active? true}] + + [react/scroll-view + (for [token tokens] + ^{:key (:name token)} + [render-asset {:token token + :on-press #(re-frame/dispatch + [(if source? + ::wallet.swap/set-from-token + ::wallet.swap/set-to-token) + (:symbol token)]) + :currency (:code currency)}])]])) + +(defn pill-button [{:keys [on-press label margin-left]}] + [react/touchable-opacity {:on-press on-press + :style {:background-color colors/blue-light + :padding-horizontal 12 + :padding-vertical 2 + :border-radius 24 + :margin-left (or margin-left 8)}} + [quo/text {:color :link + :weight :medium} label]]) + +(defn token-display + "Show token and act as an anchor to open selector." + [{:keys [token source?]}] + (let [token-icon-source (-> token :icon :source)] + [react/touchable-highlight + {:on-press #(re-frame/dispatch [::wallet.swap/open-asset-selector-modal source?])} + [react/view {:style {:flex-direction :row + :align-items :center + :border-width 1 + :border-color colors/gray-lighter + :border-radius 8 + :margin-left 16 + :padding-horizontal 8 + :padding-vertical 2} + :accessibility-label + :choose-asset-button} + [quo/text {:style {:margin-right 8}} + (-> token :symbol name)] + [react/image {:source (if (fn? token-icon-source) + (token-icon-source) + token-icon-source)}]]])) + +(defn token-input + "Component to get the amount and type of tokens" + [{:keys [amount error label token max-from source?]}] + (let [window-width (token all-tokens (or from-symbol :DGX)) + to-token (tokens/symbol->token all-tokens (or to-symbol :SNT))] + + [kb-presentation/keyboard-avoiding-view {:style (merge + {:flex 1}) + :ignore-offset true} + [topbar/topbar + {:title name + :subtitle (str/upper-case (i18n/label :t/powered-by-paraswap)) + :modal? true}] + + [react/view (merge {:padding-horizontal 16 + :margin-vertical 32} + (when-not advanced-mode? + {:flex 1})) + [token-input {:amount amount + :error nil + :label (i18n/label :t/amount) + :token from-token + :source? true + :max-from 67.28}] + + [separator-with-icon] + + [token-input {:amount "0.01" + :error nil + :label (i18n/label :t/minimum-received) + :source? false + :token to-token}]] + + (when-not advanced-mode? + [react/view {:style {:flex-direction :row + :justify-content :space-between + :padding-horizontal 16 + :align-items :center}} + [react/view {:style {:flex-direction :row}} + [quo/text {} (i18n/label :t/priority)] + [pill-button {:label (i18n/label :t/advanced) + :on-press #(re-frame/dispatch [::wallet.swap/set-advanced-mode true])}]] + + [quo/text {:color :secondary} "0.0034 ETH/ $ 8.09"]]) + + (comment + (re-frame/dispatch [::wallet.swap/set-advanced-mode false])) + + (when-not advanced-mode? + [react/view {:style {:padding-horizontal 16}} + [slider/animated-slider + {:minimum-value 0 + :maximum-value 100 + :style {:margin-vertical 8}}]]) + + (when advanced-mode? + [quo/text "here"] + [advanced-settings]) + + [toolbar/toolbar + {:show-border? true + :right [quo/button {:theme :accent} + (i18n/label :t/swap)]}]])) + diff --git a/src/status_im/utils/config.cljs b/src/status_im/utils/config.cljs index a977bc0762..6edbe504ae 100644 --- a/src/status_im/utils/config.cljs +++ b/src/status_im/utils/config.cljs @@ -53,6 +53,7 @@ (def collectibles-enabled? (enabled? (get-config :COLLECTIBLES_ENABLED "1"))) (def test-stateofus? (enabled? (get-config :TEST_STATEOFUS "0"))) (def two-minutes-syncing? (enabled? (get-config :TWO_MINUTES_SYNCING "0"))) +(def swap-enabled? (enabled? (get-config :SWAP_ENABLED "0"))) ;; CONFIG VALUES (def log-level diff --git a/src/status_im/wallet/swap/core.cljs b/src/status_im/wallet/swap/core.cljs new file mode 100644 index 0000000000..4a1675455d --- /dev/null +++ b/src/status_im/wallet/swap/core.cljs @@ -0,0 +1,51 @@ +(ns status-im.wallet.swap.core + (:require [status-im.utils.fx :as fx] + [re-frame.db :as re-frame.db] + [status-im.navigation :as navigation])) + +(fx/defn open-asset-selector-modal + "source? true signinfies we are selecting the source asset. false implies selection of sink asset" + {:events [::open-asset-selector-modal]} + [{:keys [db]} source?] + (fx/merge {:db (assoc db :wallet/modal-selecting-source-token? source?)} + (navigation/open-modal :swap-asset-selector {}))) + +(fx/defn set-from-token + {:events [::set-from-token]} + [{:keys [db]} from-symbol] + (fx/merge {:db (assoc db :wallet/swap-from-token from-symbol)} + (navigation/navigate-back))) + +(fx/defn set-to-token + {:events [::set-to-token]} + [{:keys [db]} to-symbol] + (fx/merge {:db (assoc db :wallet/swap-to-token to-symbol)} + (navigation/navigate-back))) + +(fx/defn set-from-token-amount + [{:keys [db]} from-amount] + {:db (assoc db :wallet/swap-from-token-amount from-amount)}) + +(fx/defn set-max-from-token-amount + [{:keys [db]} _] + {:db (assoc db :wallet/swap-from-token-amount 0)}) + +(fx/defn switch-from-token-with-to + {:events [::switch-from-token-with-to]} + [{:keys [db]}] + {:db (assoc db + :wallet/swap-from-token (:wallet/swap-to-token db) + :wallet/swap-to-token (:wallet/swap-from-token db))}) + +(fx/defn set-advanced-mode + {:events [::set-advanced-mode]} + [{:keys [db]} mode] + {:db (assoc db :wallet/swap-advanced-mode? mode)}) + +(comment + (->> re-frame.db/app-db + deref + :wallet/all-tokens + vals + (map #(str (:name %) "-" (:symbol %))))) + diff --git a/translations/en.json b/translations/en.json index 40652539c5..35c90bfc0f 100644 --- a/translations/en.json +++ b/translations/en.json @@ -1704,5 +1704,30 @@ "status-always-online": "Always Online", "status-inactive": "Inactive", "status-inactive-subtitle": "Hides your online status", - "two-minutes": "two minutes" + "two-minutes": "two minutes", + "swap": "Swap", + "select-token-to-swap": "Select token to Swap", + "select-token-to-receive": "Select token to receive", + "minimum-received": "Minimum received", + "powered-by-paraswap": "Powered by Paraswap", + "priority": "Priority", + "switch-to-simple-interface":"Switch to simple interface", + "transaction-fee": "Transaction fee", + "swap-details": "Swap details", + "slippage": "Slippage", + "price-impact": "Price impact", + "total-gas": "Total gas", + "token": "Token", + "approve-limit": "Approve limit", + "approve-token": "Approve token", + "approve-token-contract-desc": "Approving a token with a contract allows it to spend your token balance. If you feel that a project is untrustworthy, don’t approve the token with them, or approve only the amount you will use with them.", + "unlimited": "Unlimited", + "approve": "Approve", + "limit": "Limit", + "last-transaction": "Last transaction", + "price-impact-desc": "Estimated price impact for this transaction. If the current block base fee exceeds this, your transaction will be included in a following block with a lower base fee.", + "safe-estimate": "Safe estimate", + "current-average": "Current average", + "current-base": "Current base", + "maximum-fee-desc": "Maximum overall price for the transaction. If the current block base fee exceeds this, your transaction will be included in a following block with a lower base fee." }