diff --git a/src/quo/components/settings/data_item/view.cljs b/src/quo/components/settings/data_item/view.cljs index 712a577d48..cce92cfeca 100644 --- a/src/quo/components/settings/data_item/view.cljs +++ b/src/quo/components/settings/data_item/view.cljs @@ -121,6 +121,7 @@ [:card? {:optional true} [:maybe :boolean]] [:right-icon {:optional true} [:maybe :keyword]] [:right-content {:optional true} [:maybe :map]] + [:icon-color {:optional true} [:maybe :schema.common/customization-color]] [:status {:optional true} [:maybe [:enum :default :loading]]] [:subtitle-type {:optional true} [:maybe [:enum :default :icon :network :account :editable]]] [:size {:optional true} [:maybe [:enum :default :small :large]]] diff --git a/src/status_im/constants.cljs b/src/status_im/constants.cljs index 755e56d620..4299c6bb59 100644 --- a/src/status_im/constants.cljs +++ b/src/status_im/constants.cljs @@ -281,7 +281,8 @@ #{wallet-connect-personal-sign-method wallet-connect-eth-sign-method wallet-connect-eth-send-transaction-method - wallet-connect-eth-sign-transaction-method + ;; NOTE: disabled, as we have no clear use cases for it and other wallets don't support it + ;; wallet-connect-eth-sign-transaction-method wallet-connect-eth-sign-typed-method wallet-connect-eth-sign-typed-v4-method}) (def ^:const wallet-connect-supported-events #{"accountsChanged" "chainChanged"}) @@ -512,6 +513,9 @@ (def ^:const optimism-full-name "Optimism") (def ^:const arbitrum-full-name "Arbitrum") +(def ^:const sepolia-full-name "Sepolia") +(def ^:const goerli-full-name "Goerli") + (def ^:const mainnet-network-name :mainnet) (def ^:const ethereum-network-name :ethereum) (def ^:const optimism-network-name :optimism) diff --git a/src/status_im/contexts/wallet/common/utils/networks.cljs b/src/status_im/contexts/wallet/common/utils/networks.cljs index ad20445e0b..bc34b8f857 100644 --- a/src/status_im/contexts/wallet/common/utils/networks.cljs +++ b/src/status_im/contexts/wallet/common/utils/networks.cljs @@ -172,17 +172,20 @@ (defn get-network-details [chain-id] - (condp contains? chain-id - #{constants/ethereum-mainnet-chain-id constants/ethereum-goerli-chain-id - constants/ethereum-sepolia-chain-id} - mainnet-network-details + (as-> chain-id $ + (condp contains? $ + #{constants/ethereum-mainnet-chain-id constants/ethereum-goerli-chain-id + constants/ethereum-sepolia-chain-id} + mainnet-network-details - #{constants/arbitrum-mainnet-chain-id constants/arbitrum-goerli-chain-id - constants/arbitrum-sepolia-chain-id} - arbitrum-network-details + #{constants/arbitrum-mainnet-chain-id constants/arbitrum-goerli-chain-id + constants/arbitrum-sepolia-chain-id} + arbitrum-network-details - #{constants/optimism-mainnet-chain-id constants/optimism-goerli-chain-id - constants/optimism-sepolia-chain-id} - optimism-network-details + #{constants/optimism-mainnet-chain-id constants/optimism-goerli-chain-id + constants/optimism-sepolia-chain-id} + optimism-network-details - nil)) + nil) + (when $ + (assoc $ :chain-id chain-id)))) diff --git a/src/status_im/contexts/wallet/send/input_amount/view.cljs b/src/status_im/contexts/wallet/send/input_amount/view.cljs index 52373ea80c..63d7275321 100644 --- a/src/status_im/contexts/wallet/send/input_amount/view.cljs +++ b/src/status_im/contexts/wallet/send/input_amount/view.cljs @@ -120,7 +120,7 @@ [] [quo/alert-banner {:action? true - :text (i18n/label :t/not-enough-assets) + :text (i18n/label :t/not-enough-assets-to-pay-gas-fees) :button-text (i18n/label :t/buy-eth) :on-button-press #(rf/dispatch [:show-bottom-sheet {:content buy-token/view}])}]) diff --git a/src/status_im/contexts/wallet/wallet_connect/core.cljs b/src/status_im/contexts/wallet/wallet_connect/core.cljs index 047ccaa190..0bf0ffb1cc 100644 --- a/src/status_im/contexts/wallet/wallet_connect/core.cljs +++ b/src/status_im/contexts/wallet/wallet_connect/core.cljs @@ -100,6 +100,17 @@ networks (get-in db [:wallet :networks (if test-mode? :test :prod)])] (mapv #(-> % :chain-id) networks))) +(defn add-full-testnet-name + "Updates the `:full-name` key with the full testnet name if using testnet `:chain-id`.\n + e.g. `{:full-name \"Mainnet\"}` -> `{:full-name \"Mainnet Sepolia\"`}`" + [network] + (let [add-testnet-name (fn [testnet-name] + (update network :full-name #(str % " " testnet-name)))] + (condp #(contains? %1 %2) (:chain-id network) + constants/sepolia-chain-ids (add-testnet-name constants/sepolia-full-name) + constants/goerli-chain-ids (add-testnet-name constants/goerli-full-name) + network))) + (defn event-should-be-handled? [db {:keys [topic]}] (some #(= topic %) diff --git a/src/status_im/contexts/wallet/wallet_connect/effects.cljs b/src/status_im/contexts/wallet/wallet_connect/effects.cljs index bbab603f33..94680770a4 100644 --- a/src/status_im/contexts/wallet/wallet_connect/effects.cljs +++ b/src/status_im/contexts/wallet/wallet_connect/effects.cljs @@ -83,22 +83,33 @@ (promesa/then on-success) (promesa/catch on-error))))) +(rf/reg-fx + :effects.wallet-connect/prepare-transaction + (fn [{:keys [tx chain-id on-success on-error]}] + (-> (transactions/prepare-transaction tx + chain-id + transactions/default-tx-priority) + (promesa/then on-success) + (promesa/catch on-error)))) + (rf/reg-fx :effects.wallet-connect/sign-transaction - (fn [{:keys [password address chain-id tx on-success on-error]}] + (fn [{:keys [password address chain-id tx-hash tx-args on-success on-error]}] (-> (transactions/sign-transaction (security/safe-unmask-data password) address - tx + tx-hash + tx-args chain-id) (promesa/then on-success) (promesa/catch on-error)))) (rf/reg-fx :effects.wallet-connect/send-transaction - (fn [{:keys [password address chain-id tx on-success on-error]}] + (fn [{:keys [password address chain-id tx-hash tx-args on-success on-error]}] (-> (transactions/send-transaction (security/safe-unmask-data password) address - tx + tx-hash + tx-args chain-id) (promesa/then on-success) (promesa/catch on-error)))) diff --git a/src/status_im/contexts/wallet/wallet_connect/events.cljs b/src/status_im/contexts/wallet/wallet_connect/events.cljs index bfc9d9e3ce..d69f7a20ac 100644 --- a/src/status_im/contexts/wallet/wallet_connect/events.cljs +++ b/src/status_im/contexts/wallet/wallet_connect/events.cljs @@ -63,7 +63,7 @@ networks) required-networks-supported? (wallet-connect-core/required-networks-supported? proposal networks)] - (if required-networks-supported? + (if (and (not-empty session-networks) required-networks-supported?) {:db (update db :wallet-connect/current-proposal assoc :request proposal diff --git a/src/status_im/contexts/wallet/wallet_connect/modals/common/fees_data_item/view.cljs b/src/status_im/contexts/wallet/wallet_connect/modals/common/fees_data_item/view.cljs new file mode 100644 index 0000000000..276addd6d5 --- /dev/null +++ b/src/status_im/contexts/wallet/wallet_connect/modals/common/fees_data_item/view.cljs @@ -0,0 +1,31 @@ +(ns status-im.contexts.wallet.wallet-connect.modals.common.fees-data-item.view + (:require [quo.core :as quo] + [quo.foundations.colors :as colors] + [quo.theme] + [status-im.contexts.wallet.wallet-connect.modals.common.style :as style] + [utils.i18n :as i18n])) + +(defn- fees-subtitle + [{:keys [text error?]}] + (let [theme (quo.theme/use-theme)] + [quo/text + {:weight :medium + :size :paragraph-2 + :style {:color (if error? + (colors/resolve-color :danger theme) + (colors/theme-colors colors/neutral-100 + colors/white + theme))}} + text])) + +(defn view + [{:keys [fees fees-error]}] + [quo/data-item + {:size :small + :status :default + :card? false + :container-style style/data-item + :title (i18n/label :t/max-fees) + :custom-subtitle (fn [] [fees-subtitle + {:text (or fees (i18n/label :t/no-fees)) + :error? (= fees-error :not-enough-assets-to-pay-gas-fees)}])}]) diff --git a/src/status_im/contexts/wallet/wallet_connect/modals/common/footer/view.cljs b/src/status_im/contexts/wallet/wallet_connect/modals/common/footer/view.cljs index 4d422c1bf9..14e853eae1 100644 --- a/src/status_im/contexts/wallet/wallet_connect/modals/common/footer/view.cljs +++ b/src/status_im/contexts/wallet/wallet_connect/modals/common/footer/view.cljs @@ -13,7 +13,7 @@ (rf/dispatch [:wallet-connect/respond-current-session password])) (defn view - [{:keys [warning-label slide-button-text disabed?]} & children] + [{:keys [warning-label slide-button-text disabled?]} & children] (let [{:keys [customization-color]} (rf/sub [:wallet-connect/current-request-account-details])] [rn/view {:style style/content-container} (into [rn/view @@ -23,7 +23,7 @@ [standard-authentication/slide-button {:size :size-48 :track-text slide-button-text - :disabled? disabed? + :disabled? disabled? :customization-color customization-color :on-auth-success on-auth-success :auth-button-label (i18n/label :t/confirm)}]] diff --git a/src/status_im/contexts/wallet/wallet_connect/modals/send_transaction/view.cljs b/src/status_im/contexts/wallet/wallet_connect/modals/send_transaction/view.cljs index 3f4724972f..6314945770 100644 --- a/src/status_im/contexts/wallet/wallet_connect/modals/send_transaction/view.cljs +++ b/src/status_im/contexts/wallet/wallet_connect/modals/send_transaction/view.cljs @@ -1,8 +1,11 @@ (ns status-im.contexts.wallet.wallet-connect.modals.send-transaction.view (:require [quo.core :as quo] + [quo.theme] [react-native.core :as rn] [react-native.safe-area :as safe-area] [status-im.contexts.wallet.wallet-connect.modals.common.data-block.view :as data-block] + [status-im.contexts.wallet.wallet-connect.modals.common.fees-data-item.view :as + fees-data-item] [status-im.contexts.wallet.wallet-connect.modals.common.footer.view :as footer] [status-im.contexts.wallet.wallet-connect.modals.common.header.view :as header] [status-im.contexts.wallet.wallet-connect.modals.common.page-nav.view :as page-nav] @@ -40,7 +43,7 @@ :not-enough-assets :t/not-enough-assets))}]) [footer/view - {:warning-label (i18n/label :t/wallet-connect-send-transaction-warning) + {:warning-label (i18n/label :t/wallet-connect-sign-warning) :slide-button-text (i18n/label :t/slide-to-send) :disabled? error-state} [quo/data-item @@ -51,11 +54,7 @@ :subtitle-type :network :network-image (:source network) :subtitle (:full-name network)}] - [quo/data-item - {:size :small - :status :default - :card? false - :container-style style/data-item - :title (i18n/label :t/max-fees) - :subtitle (or max-fees-fiat-formatted (i18n/label :t/no-fees))}]]]])) + [fees-data-item/view + {:fees max-fees-fiat-formatted + :fees-error error-state}]]]])) diff --git a/src/status_im/contexts/wallet/wallet_connect/modals/sign_message/view.cljs b/src/status_im/contexts/wallet/wallet_connect/modals/sign_message/view.cljs index 7ba821434d..32b802d8c5 100644 --- a/src/status_im/contexts/wallet/wallet_connect/modals/sign_message/view.cljs +++ b/src/status_im/contexts/wallet/wallet_connect/modals/sign_message/view.cljs @@ -3,6 +3,8 @@ [react-native.core :as rn] [react-native.safe-area :as safe-area] [status-im.contexts.wallet.wallet-connect.modals.common.data-block.view :as data-block] + [status-im.contexts.wallet.wallet-connect.modals.common.fees-data-item.view :as + fees-data-item] [status-im.contexts.wallet.wallet-connect.modals.common.footer.view :as footer] [status-im.contexts.wallet.wallet-connect.modals.common.header.view :as header] [status-im.contexts.wallet.wallet-connect.modals.common.page-nav.view :as page-nav] @@ -28,13 +30,7 @@ :account account}] [data-block/view]] [footer/view - {:warning-label (i18n/label :t/wallet-connect-sign-message-warning) + {:warning-label (i18n/label :t/wallet-connect-sign-warning) :slide-button-text (i18n/label :t/slide-to-sign)} - [quo/data-item - {:size :small - :status :default - :card? false - :container-style style/data-item - :title (i18n/label :t/max-fees) - :subtitle (i18n/label :t/no-fees)}]]]])) + [fees-data-item/view]]]])) diff --git a/src/status_im/contexts/wallet/wallet_connect/modals/sign_transaction/view.cljs b/src/status_im/contexts/wallet/wallet_connect/modals/sign_transaction/view.cljs index 6504f21118..5c4dfc61c1 100644 --- a/src/status_im/contexts/wallet/wallet_connect/modals/sign_transaction/view.cljs +++ b/src/status_im/contexts/wallet/wallet_connect/modals/sign_transaction/view.cljs @@ -3,6 +3,8 @@ [react-native.core :as rn] [react-native.safe-area :as safe-area] [status-im.contexts.wallet.wallet-connect.modals.common.data-block.view :as data-block] + [status-im.contexts.wallet.wallet-connect.modals.common.fees-data-item.view :as + fees-data-item] [status-im.contexts.wallet.wallet-connect.modals.common.footer.view :as footer] [status-im.contexts.wallet.wallet-connect.modals.common.header.view :as header] [status-im.contexts.wallet.wallet-connect.modals.common.page-nav.view :as page-nav] @@ -40,7 +42,7 @@ :not-enough-assets :t/not-enough-assets))}]) [footer/view - {:warning-label (i18n/label :t/wallet-connect-sign-transaction-warning) + {:warning-label (i18n/label :t/wallet-connect-sign-warning) :slide-button-text (i18n/label :t/slide-to-sign) :disabled? error-state} [quo/data-item @@ -51,11 +53,7 @@ :subtitle-type :network :network-image (:source network) :subtitle (:full-name network)}] - [quo/data-item - {:size :small - :status :default - :card? false - :container-style style/data-item - :title (i18n/label :t/max-fees) - :subtitle (or max-fees-fiat-formatted (i18n/label :t/no-fees))}]]]])) + [fees-data-item/view + {:fees max-fees-fiat-formatted + :fees-error error-state}]]]])) diff --git a/src/status_im/contexts/wallet/wallet_connect/processing_events.cljs b/src/status_im/contexts/wallet/wallet_connect/processing_events.cljs index 2d5e7814a0..33adb4fc1d 100644 --- a/src/status_im/contexts/wallet/wallet_connect/processing_events.cljs +++ b/src/status_im/contexts/wallet/wallet_connect/processing_events.cljs @@ -1,18 +1,32 @@ (ns status-im.contexts.wallet.wallet-connect.processing-events - (:require [clojure.string :as string] + (:require [cljs-bean.core :as bean] + [clojure.string :as string] [native-module.core :as native-module] [re-frame.core :as rf] [status-im.constants :as constants] [status-im.contexts.wallet.wallet-connect.core :as wallet-connect-core] + [status-im.contexts.wallet.wallet-connect.transactions :as transactions] [taoensso.timbre :as log] [utils.transforms :as transforms])) (rf/reg-event-fx - :wallet-connect/process-session-request - (fn [{:keys [db]} [event]] - (let [method (wallet-connect-core/get-request-method event) + :wallet-connect/show-request-modal + (fn [{:keys [db]}] + (let [event (get-in db [:wallet-connect/current-request :event]) + method (wallet-connect-core/get-request-method event) screen (wallet-connect-core/method-to-screen method)] (if screen + {:fx [[:dispatch [:open-modal screen]]]} + (log/error "Didn't find screen for Wallet Connect method" + {:method method + :event :wallet-connect/process-session-request}))))) +(rf/reg-event-fx + :wallet-connect/process-session-request + (fn [{:keys [db]} [event]] + (let [method (wallet-connect-core/get-request-method event) + existing-event (get-in db [:wallet-connect/current-request :event])] + ;; NOTE: make sure we don't show two requests at the same time + (when-not existing-event {:db (assoc-in db [:wallet-connect/current-request :event] event) :fx [(condp = method constants/wallet-connect-eth-send-transaction-method @@ -31,12 +45,7 @@ [:dispatch [:wallet-connect/process-sign-typed]] constants/wallet-connect-personal-sign-method - [:dispatch [:wallet-connect/process-personal-sign]]) - - [:dispatch [:open-modal screen]]]} - (log/error "Didn't find screen for Wallet Connect method" - {:method method - :event :wallet-connect/process-session-request}))))) + [:dispatch [:wallet-connect/process-personal-sign]])]})))) (rf/reg-event-fx :wallet-connect/process-personal-sign @@ -48,7 +57,8 @@ assoc :address (string/lower-case address) :raw-data raw-data - :display-data (or parsed-data raw-data))}))) + :display-data (or parsed-data raw-data)) + :fx [[:dispatch [:wallet-connect/show-request-modal]]]}))) (rf/reg-event-fx :wallet-connect/process-eth-sign @@ -60,44 +70,53 @@ assoc :address (string/lower-case address) :raw-data raw-data - :display-data (or parsed-data raw-data))}))) + :display-data (or parsed-data raw-data)) + :fx [[:dispatch [:wallet-connect/show-request-modal]]]}))) + +(rf/reg-event-fx + :wallet-connect/prepare-transaction-success + (fn [{:keys [db]} [prepared-tx chain-id]] + (let [{:keys [tx-args]} prepared-tx + tx (bean/->clj tx-args) + address (-> tx :from string/lower-case) + display-data (transactions/beautify-transaction tx)] + {:db (update-in db + [:wallet-connect/current-request] + assoc + :address address + :raw-data prepared-tx + :transaction tx + :chain-id chain-id + :display-data display-data) + :fx [[:dispatch [:wallet-connect/show-request-modal]]]}))) (rf/reg-event-fx :wallet-connect/process-eth-send-transaction (fn [{:keys [db]}] - (let [event (wallet-connect-core/get-db-current-request-event db) - display-data (-> event - clj->js - (js/JSON.stringify nil 2)) - - {:keys [from] :as tx} (-> event wallet-connect-core/get-request-params first) - chain-id (-> event - (get-in [:params :chainId]) - wallet-connect-core/eip155->chain-id)] - {:db (update-in db - [:wallet-connect/current-request] - assoc - :address (string/lower-case from) - :raw-data tx - :chain-id chain-id - :display-data display-data)}))) + (let [event (wallet-connect-core/get-db-current-request-event db) + tx (-> event wallet-connect-core/get-request-params first) + chain-id (-> event + (get-in [:params :chainId]) + wallet-connect-core/eip155->chain-id)] + {:fx [[:effects.wallet-connect/prepare-transaction + {:tx tx + :chain-id chain-id + :on-success #(rf/dispatch [:wallet-connect/prepare-transaction-success % chain-id]) + :on-error #(rf/dispatch [:wallet-connect/on-processing-error %])}]]}))) (rf/reg-event-fx :wallet-connect/process-eth-sign-transaction (fn [{:keys [db]}] - (let [event (wallet-connect-core/get-db-current-request-event db) - display-data (.stringify js/JSON (clj->js event) nil 2) - {:keys [from] :as tx} (-> event wallet-connect-core/get-request-params first) - chain-id (-> event - (get-in [:params :chainId]) - wallet-connect-core/eip155->chain-id)] - {:db (update-in db - [:wallet-connect/current-request] - assoc - :address (string/lower-case from) - :raw-data tx - :chain-id chain-id - :display-data display-data)}))) + (let [event (wallet-connect-core/get-db-current-request-event db) + tx (-> event wallet-connect-core/get-request-params first) + chain-id (-> event + (get-in [:params :chainId]) + wallet-connect-core/eip155->chain-id)] + {:fx [[:effects.wallet-connect/prepare-transaction + {:tx tx + :chain-id chain-id + :on-success #(rf/dispatch [:wallet-connect/prepare-transaction-success % chain-id]) + :on-error #(rf/dispatch [:wallet-connect/on-processing-error %])}]]}))) (rf/reg-event-fx :wallet-connect/process-sign-typed @@ -108,11 +127,31 @@ (transforms/js-dissoc :types :primaryType) (transforms/js-stringify 2)) (catch js/Error _ nil))] - ;; TODO: decide if we should proceed if the typed-data is invalid JSON or fail ahead of time - (when (nil? parsed-data) (log/error "Invalid typed data")) - {:db (update-in db - [:wallet-connect/current-request] - assoc - :address (string/lower-case address) - :display-data (or parsed-data raw-data) - :raw-data raw-data)}))) + (if (nil? parsed-data) + {:fx [[:dispatch + [:wallet-connect/on-processing-error + (ex-info "Failed to parse JSON typed data" {:data raw-data})]]]} + {:db (update-in db + [:wallet-connect/current-request] + assoc + :address (string/lower-case address) + :display-data (or parsed-data raw-data) + :raw-data raw-data) + :fx [[:dispatch [:wallet-connect/show-request-modal]]]})))) + +;; TODO: we should reject a request if processing fails +(rf/reg-event-fx + :wallet-connect/on-processing-error + (fn [{:keys [db]} [error]] + (let [{:keys [address event]} (get db :wallet-connect/current-request) + method (wallet-connect-core/get-request-method event) + screen (wallet-connect-core/method-to-screen method)] + (log/error "Failed to process Wallet Connect request" + {:error error + :address address + :method method + :wallet-connect-event event + :event :wallet-connect/on-processing-error}) + + {:fx [[:dispatch [:dismiss-modal screen]] + [:dispatch [:wallet-connect/reset-current-request]]]}))) diff --git a/src/status_im/contexts/wallet/wallet_connect/responding_events.cljs b/src/status_im/contexts/wallet/wallet_connect/responding_events.cljs index 2a665fde6e..16c2c9a6e7 100644 --- a/src/status_im/contexts/wallet/wallet_connect/responding_events.cljs +++ b/src/status_im/contexts/wallet/wallet_connect/responding_events.cljs @@ -58,34 +58,38 @@ (rf/reg-event-fx :wallet-connect/respond-send-transaction-data (fn [{:keys [db]} [password]] - (let [{:keys [chain-id raw-data address]} (get db :wallet-connect/current-request)] + (let [{:keys [chain-id raw-data address]} (get db :wallet-connect/current-request) + {:keys [tx-hash tx-args]} raw-data] {:fx [[:effects.wallet-connect/send-transaction {:password password :address address :chain-id chain-id - :tx raw-data + :tx-hash tx-hash + :tx-args tx-args :on-error #(rf/dispatch [:wallet-connect/on-sign-error %]) :on-success #(rf/dispatch [:wallet-connect/send-response {:result %}])}]]}))) (rf/reg-event-fx :wallet-connect/respond-sign-transaction-data (fn [{:keys [db]} [password]] - (let [{:keys [chain-id raw-data address]} (get db :wallet-connect/current-request)] + (let [{:keys [chain-id raw-data address]} (get db :wallet-connect/current-request) + {:keys [tx-hash tx-args]} raw-data] {:fx [[:effects.wallet-connect/sign-transaction {:password password :address address :chain-id chain-id - :tx raw-data + :tx-hash tx-hash + :tx-params tx-args :on-error #(rf/dispatch [:wallet-connect/on-sign-error %]) :on-success #(rf/dispatch [:wallet-connect/send-response {:result %}])}]]}))) +;; TODO: should reject if "signing" fails (rf/reg-event-fx :wallet-connect/on-sign-error (fn [{:keys [db]} [error]] - (let [event (get-in db [:wallet-connect/current-request :event]) - {:keys [raw-data address]} (get db :wallet-connect/current-request) - method (wallet-connect-core/get-request-method event) - screen (wallet-connect-core/method-to-screen method)] + (let [{:keys [raw-data address event]} (get db :wallet-connect/current-request) + method (wallet-connect-core/get-request-method event) + screen (wallet-connect-core/method-to-screen method)] (log/error "Failed to sign Wallet Connect request" {:error error :address address diff --git a/src/status_im/contexts/wallet/wallet_connect/rpc.cljs b/src/status_im/contexts/wallet/wallet_connect/rpc.cljs index 3b1ce23afe..cfba96da43 100644 --- a/src/status_im/contexts/wallet/wallet_connect/rpc.cljs +++ b/src/status_im/contexts/wallet/wallet_connect/rpc.cljs @@ -59,3 +59,8 @@ password chain-id legacy?)) + +(defn wallet-get-suggested-fees + [chain-id] + (-> (call-rpc "wallet_getSuggestedFees" chain-id) + (promesa/then transforms/js->clj))) diff --git a/src/status_im/contexts/wallet/wallet_connect/transactions.cljs b/src/status_im/contexts/wallet/wallet_connect/transactions.cljs index e23f196ecd..d74b5686cc 100644 --- a/src/status_im/contexts/wallet/wallet_connect/transactions.cljs +++ b/src/status_im/contexts/wallet/wallet_connect/transactions.cljs @@ -1,10 +1,24 @@ (ns status-im.contexts.wallet.wallet-connect.transactions (:require [cljs-bean.core :as bean] [clojure.string :as string] + [native-module.core :as native-module] [promesa.core :as promesa] + [status-im.constants :as constants] + [status-im.contexts.wallet.wallet-connect.core :as core] [status-im.contexts.wallet.wallet-connect.rpc :as rpc] + [utils.money :as money] [utils.transforms :as transforms])) +(defn transaction-request? + [event] + (->> (core/get-request-method event) + (contains? #{constants/wallet-connect-eth-send-transaction-method + constants/wallet-connect-eth-sign-transaction-method}))) + +;; NOTE: Currently we don't allow the user to configure the tx priority as we don't +;; show the estimated time, but when we implement it, we should allow to change it +(def ^:constant default-tx-priority :medium) + (defn- strip-hex-prefix "Strips the extra 0 in hex value if present" [hex-value] @@ -15,12 +29,12 @@ (defn- format-tx-hex-values "Due to how status-go expects hex values, we should remove the extra 0s in transaction hex values e.g. 0x0f -> 0xf" - [tx] + [tx f] (let [tx-keys [:gasLimit :gas :gasPrice :nonce :value :maxFeePerGas :maxPriorityFeePerGas]] (reduce (fn [acc tx-key] (if (and (contains? tx tx-key) (not (nil? (get tx tx-key)))) - (update acc tx-key strip-hex-prefix) + (update acc tx-key f) acc)) tx tx-keys))) @@ -29,26 +43,100 @@ "Formats the transaction and transforms it into a stringified JS object, ready to be passed to an RPC call." [tx] (-> tx - format-tx-hex-values + ;; NOTE: removing `:nonce` to compute it when building the transaction on status-go + (dissoc :nonce) + (format-tx-hex-values strip-hex-prefix) bean/->js (transforms/js-stringify 0))) +(defn beautify-transaction + [tx] + (let [hex->number #(-> % (subs 2) native-module/hex-to-number)] + (-> tx + (format-tx-hex-values hex->number) + clj->js + (js/JSON.stringify nil 2)))) + +(defn- gwei->hex + [gwei] + (->> gwei + money/gwei->wei + native-module/number-to-hex + (str "0x"))) + +(defn- get-max-fee-per-gas-key + "Mapping transaction priority (which determines how quickly a tx is processed) + to the `suggested-routes` key that should be used for `:maxPriorityFeePerGas`. + + Returns `:high` | `:medium` | `:low`" + [tx-priority] + (get {:high :maxFeePerGasHigh + :medium :maxFeePerGasMedium + :low :maxFeePerGasLow} + tx-priority)) + +(defn- dynamic-fee-tx? + "Checks if a transaction has dynamic fees (EIP1559)" + [tx] + (every? tx [:maxFeePerGas :maxPriorityFeePerGas])) + +(defn- tx->eip1559-tx + "Adds `:maxFeePerGas` and `:maxPriorityFeePerGas` for dynamic fee support (EIP1559) and + removes `:gasPrice`, if the chain supports EIP1559 and the transaction doesn't already + have dynamic fees." + [tx suggested-fees tx-priority] + (if (and (:eip1559Enabled suggested-fees) + (not (dynamic-fee-tx? tx))) + (let [max-fee-per-gas-key (get-max-fee-per-gas-key tx-priority) + max-fee-per-gas (-> suggested-fees max-fee-per-gas-key gwei->hex) + max-priority-fee-per-gas (-> suggested-fees :maxPriorityFeePerGas gwei->hex)] + (-> tx + (assoc + :maxFeePerGas max-fee-per-gas + :maxPriorityFeePerGas max-priority-fee-per-gas) + ;; NOTE: `:gasPrice` is used only for legacy Tx, so we discard it in favor of dynamic fees + (dissoc :gasPrice))) + tx)) + +(defn- prepare-transaction-fees + "Makes sure the transaction has the correct gas and fees properties" + [tx tx-priority suggested-fees] + (-> (assoc tx + ;; NOTE: `gasLimit` is ignored on status-go when building a transaction + ;; (`wallet_buildTransaction`), so we're setting it as the `gas` property + :gas + (or (:gasLimit tx) + (:gas tx))) + (dissoc :gasLimit) + (tx->eip1559-tx suggested-fees tx-priority))) + +(defn prepare-transaction + "Formats and builds the incoming transaction, adding the missing properties and returning the final + transaction, along with the transaction hash and the suggested fees" + [tx chain-id tx-priority] + (promesa/let [suggested-fees (rpc/wallet-get-suggested-fees chain-id) + {:keys [tx-args message-to-sign]} (->> + (prepare-transaction-fees tx + tx-priority + suggested-fees) + prepare-transaction-for-rpc + (rpc/wallet-build-transaction chain-id))] + {:tx-args tx-args + :tx-hash message-to-sign + :suggested-fees suggested-fees})) + (defn sign-transaction - [password address tx chain-id] + [password address tx-hash tx-args chain-id] (promesa/let - [formatted-tx (prepare-transaction-for-rpc tx) - {:keys [message-to-sign tx-args]} (rpc/wallet-build-transaction chain-id formatted-tx) - signature (rpc/wallet-sign-message message-to-sign address password) - raw-tx (rpc/wallet-build-raw-transaction chain-id tx-args signature)] + [signature (rpc/wallet-sign-message tx-hash address password) + raw-tx (rpc/wallet-build-raw-transaction chain-id tx-args signature)] raw-tx)) (defn send-transaction - [password address tx chain-id] + [password address tx-hash tx-args chain-id] (promesa/let - [formatted-tx (prepare-transaction-for-rpc tx) - {:keys [message-to-sign tx-args]} (rpc/wallet-build-transaction chain-id formatted-tx) - signature (rpc/wallet-sign-message message-to-sign address password) - tx (rpc/wallet-send-transaction-with-signature chain-id - tx-args - signature)] + [signature (rpc/wallet-sign-message tx-hash address password) + tx (rpc/wallet-send-transaction-with-signature chain-id + tx-args + signature)] tx)) diff --git a/src/status_im/subs/wallet/wallet_connect.cljs b/src/status_im/subs/wallet/wallet_connect.cljs index da205dcee9..6b4fe542a6 100644 --- a/src/status_im/subs/wallet/wallet_connect.cljs +++ b/src/status_im/subs/wallet/wallet_connect.cljs @@ -4,6 +4,7 @@ [status-im.contexts.wallet.common.utils :as wallet-utils] [status-im.contexts.wallet.common.utils.networks :as networks] [status-im.contexts.wallet.wallet-connect.core :as wallet-connect-core] + [status-im.contexts.wallet.wallet-connect.transactions :as transactions] [utils.money :as money])) (rf/reg-sub @@ -57,66 +58,95 @@ sessions))) (rf/reg-sub - :wallet-connect/current-request-network + :wallet-connect/chain-id :<- [:wallet-connect/current-request] (fn [request] (-> request (get-in [:event :params :chainId]) - (wallet-connect-core/eip155->chain-id) - (networks/get-network-details)))) + (wallet-connect-core/eip155->chain-id)))) + +(rf/reg-sub + :wallet-connect/current-request-network + :<- [:wallet-connect/chain-id] + (fn [chain-id] + (-> chain-id + (networks/get-network-details) + (wallet-connect-core/add-full-testnet-name)))) + +(rf/reg-sub + :wallet-connect/transaction-args + :<- [:wallet-connect/current-request] + (fn [{:keys [event transaction]}] + (when (transactions/transaction-request? event) + transaction))) + +(rf/reg-sub + :wallet-connect/transaction-suggested-fees + :<- [:wallet-connect/current-request] + (fn [{:keys [event raw-data]}] + (when (transactions/transaction-request? event) + (:suggested-fees raw-data)))) + +(rf/reg-sub + :wallet-connect/transaction-max-fees-wei + :<- [:wallet-connect/transaction-args] + :<- [:wallet-connect/transaction-suggested-fees] + (fn [[transaction suggested-fees]] + (when transaction + (let [{:keys [gasPrice gas gasLimit maxFeePerGas]} transaction + eip-1559-chain? (:eip1559Enabled suggested-fees) + gas-limit (or gasLimit gas) + max-gas-fee (if eip-1559-chain? maxFeePerGas gasPrice)] + (money/bignumber (* max-gas-fee gas-limit)))))) + +(rf/reg-sub + :wallet-connect/account-eth-token + :<- [:wallet-connect/current-request-address] + :<- [:wallet/accounts] + (fn [[address accounts]] + (let [fee-token "ETH" + find-account #(when (= (:address %) address) %) + find-token #(when (= (:symbol %) fee-token) %)] + (->> accounts + (some find-account) + :tokens + (some find-token))))) (rf/reg-sub :wallet-connect/current-request-transaction-information - :<- [:wallet-connect/current-request] - :<- [:wallet/accounts] + :<- [:wallet-connect/chain-id] + :<- [:wallet-connect/transaction-max-fees-wei] + :<- [:wallet-connect/transaction-args] + :<- [:wallet-connect/account-eth-token] :<- [:profile/currency] :<- [:profile/currency-symbol] - (fn [[request accounts currency currency-symbol]] - (let [chain-id (-> request - (get-in [:raw-data :params :chainId]) - (wallet-connect-core/eip155->chain-id)) - all-tokens (->> accounts - (filter #(= (:address %) - (:address request))) - (first) - :tokens) - eth-token (->> all-tokens - (filter #(= (:symbol %) "ETH")) - (first)) - {:keys [gasPrice gasLimit value]} (-> request - :raw-data - wallet-connect-core/get-request-params - first) - max-fees-wei (money/bignumber (* gasPrice gasLimit))] - (when (and gasPrice gasLimit) - (let [max-fees-ether (money/wei->ether max-fees-wei) - token-fiat-value (wallet-utils/calculate-token-fiat-value {:currency currency - :balance max-fees-ether - :token eth-token}) - crypto-formatted (wallet-utils/get-standard-crypto-format eth-token max-fees-ether) - fiat-formatted (wallet-utils/get-standard-fiat-format crypto-formatted - currency-symbol - token-fiat-value) - balance (-> eth-token - (get-in [:balances-per-chain chain-id :raw-balance]) - (money/bignumber)) - value (money/bignumber value) - total-transaction-value (money/add max-fees-wei value)] - {:total-transaction-value total-transaction-value - :balance balance - :max-fees max-fees-wei - :max-fees-fiat-value token-fiat-value - :max-fees-fiat-formatted fiat-formatted - :error-state (cond - (and - (money/sufficient-funds? value balance) - (not (money/sufficient-funds? total-transaction-value balance))) - :not-enough-assets-to-pay-gas-fees + (fn [[chain-id max-fees-wei transaction eth-token currency currency-symbol]] + (when transaction + (let [max-fees-ether (money/wei->ether max-fees-wei) + max-fees-fiat (wallet-utils/calculate-token-fiat-value {:currency currency + :balance max-fees-ether + :token eth-token}) + max-fees-fiat-formatted (-> max-fees-ether + (wallet-utils/get-standard-crypto-format eth-token) + (wallet-utils/get-standard-fiat-format currency-symbol + max-fees-fiat)) + balance (-> eth-token + (get-in [:balances-per-chain chain-id :raw-balance]) + money/bignumber) + tx-value (money/bignumber (:value transaction)) + total-transaction-value (money/add max-fees-wei tx-value)] + {:total-transaction-value total-transaction-value + :balance balance + :max-fees max-fees-wei + :max-fees-fiat-value max-fees-fiat + :max-fees-fiat-formatted max-fees-fiat-formatted + :error-state (cond + (not (money/sufficient-funds? tx-value balance)) + :not-enough-assets - (not (money/sufficient-funds? value balance)) - :not-enough-assets - - :else nil)}))))) + (not (money/sufficient-funds? total-transaction-value + balance)) + :not-enough-assets-to-pay-gas-fees)})))) (rf/reg-sub :wallet-connect/current-proposal-request diff --git a/src/utils/money.cljs b/src/utils/money.cljs index a160ec065c..1af5125d59 100644 --- a/src/utils/money.cljs +++ b/src/utils/money.cljs @@ -148,6 +148,10 @@ [n] (wei-> :gwei n)) +(defn gwei->wei + [n] + (->wei :gwei n)) + (defn ether->wei [^js bn] (when bn diff --git a/translations/en.json b/translations/en.json index de6b4c281e..26fbdc5afa 100644 --- a/translations/en.json +++ b/translations/en.json @@ -1941,8 +1941,8 @@ "pinned-by": "Pinned by", "pin-limit-reached": "Pin limit reached. Unpin a previous message first.", "no-fees": "No fees", - "not-enough-assets-to-pay-gas-fees": "Not enough assets to pay gas fees.", - "not-enough-assets": "Not enough assets to complete transaction.", + "not-enough-assets-to-pay-gas-fees": "Not enough assets to pay gas fees", + "not-enough-assets": "Not enough assets to complete transaction", "max-fee": "Max fee", "max-fees": "Max fees", "max-priority-fee": "Max priority fee", @@ -2052,6 +2052,7 @@ "wallet-connect-sign-message-header": "wants you to sign the message with", "wallet-connect-send-transaction-header": "wants you to send this transaction with", "wallet-connect-sign-transaction-header": "wants you to sign this transaction with", + "wallet-connect-sign-warning": "Sign only if you trust the dApp", "wallet-connect-sign-message-warning": "Sign messages only if you trust the dApp", "wallet-connect-send-transaction-warning": "Send transactions only if you trust the dApp", "wallet-connect-sign-transaction-warning": "Sign transactions only if you trust the dApp", @@ -2746,7 +2747,6 @@ "saved-address-network-preference-selection-description": "Only change if you know which networks the address owner is happy to to receive funds on", "add-preferences": "Add preferences", "buy-eth": "Buy ETH", - "not-enough-assets": "Not enough assets to pay gas fees", "send-from-network": "Send from {{network}}", "define-amount-sent-from-network": "Define amount sent from {{network}} network", "dont-auto-recalculate-network": "Don't auto recalculate {{network}}",