From 1e4c1a55986a4cf71c14becc212200713a04da84 Mon Sep 17 00:00:00 2001 From: Roman Volosovskyi Date: Mon, 14 Jun 2021 14:34:12 +0300 Subject: [PATCH] eip1559 changes --- .env | 1 + src/status_im/ethereum/json_rpc.cljs | 1 + src/status_im/multiaccounts/login/core.cljs | 7 +- src/status_im/signing/core.cljs | 47 ++- src/status_im/signing/core_test.cljs | 4 + src/status_im/signing/eip1559.cljs | 64 ++++ src/status_im/signing/gas.cljs | 175 ++++++++++- src/status_im/subs.cljs | 42 ++- src/status_im/ui/screens/popover/views.cljs | 6 +- src/status_im/ui/screens/signing/sheets.cljs | 301 ++++++++++++++++++- src/status_im/ui/screens/signing/views.cljs | 8 +- src/status_im/utils/config.cljs | 1 + src/status_im/utils/money.cljs | 29 +- src/status_im/wallet/core.cljs | 20 +- translations/en.json | 28 +- 15 files changed, 681 insertions(+), 53 deletions(-) create mode 100644 src/status_im/signing/eip1559.cljs diff --git a/.env b/.env index 5cbfccc338..230bbac8fb 100644 --- a/.env +++ b/.env @@ -29,3 +29,4 @@ APN_TOPIC=im.status.ethereum.pr COMMUNITIES_ENABLED=1 DATABASE_MANAGEMENT_ENABLED=1 METRICS_ENABLED=0 +EIP1559_ENABLED=1 diff --git a/src/status_im/ethereum/json_rpc.cljs b/src/status_im/ethereum/json_rpc.cljs index 3b4acea0db..8674cf8d0f 100644 --- a/src/status_im/ethereum/json_rpc.cljs +++ b/src/status_im/ethereum/json_rpc.cljs @@ -23,6 +23,7 @@ "eth_getTransactionByHash" {} "eth_getTransactionReceipt" {} "eth_getBlockByNumber" {} + "eth_maxPriorityFeePerGas" {} "eth_newBlockFilter" {:subscription? true} "eth_newFilter" {:subscription? true} "eth_getCode" {} diff --git a/src/status_im/multiaccounts/login/core.cljs b/src/status_im/multiaccounts/login/core.cljs index 12c7d95cc7..bca922194e 100644 --- a/src/status_im/multiaccounts/login/core.cljs +++ b/src/status_im/multiaccounts/login/core.cljs @@ -36,7 +36,8 @@ [status-im.utils.mobile-sync :as utils.mobile-sync] [status-im.async-storage.core :as async-storage] [status-im.notifications-center.core :as notifications-center] - [status-im.navigation :as navigation])) + [status-im.navigation :as navigation] + [status-im.signing.eip1559 :as eip1559])) (re-frame/reg-fx ::initialize-communities-enabled @@ -277,6 +278,10 @@ (assoc :networks/current-network current-network :networks/networks networks :multiaccount multiaccount)) + ::eip1559/check-eip1559-activation + {:network-id network-id + :on-enabled #(log/info "eip1550 is activated") + :on-disabled #(log/info "eip1559 is not activated")} ::initialize-wallet (fn [accounts custom-tokens favourites] (re-frame/dispatch [::initialize-wallet diff --git a/src/status_im/signing/core.cljs b/src/status_im/signing/core.cljs index 72460d8c4c..f55ff3944f 100644 --- a/src/status_im/signing/core.cljs +++ b/src/status_im/signing/core.cljs @@ -21,7 +21,8 @@ [status-im.wallet.prices :as prices] [status-im.wallet.core :as wallet] [taoensso.timbre :as log] - [clojure.set :as clojure.set])) + [clojure.set :as clojure.set] + [status-im.signing.eip1559 :as eip1559])) (re-frame/reg-fx :signing/send-transaction-fx @@ -92,7 +93,7 @@ {:events [:signing.ui/sign-is-pressed]} [{{:signing/keys [sign tx] :as db} :db :as cofx}] (let [{:keys [in-progress? password]} sign - {:keys [tx-obj gas gasPrice message]} tx + {:keys [tx-obj gas gasPrice maxPriorityFeePerGas maxFeePerGas message]} tx hashed-password (ethereum/sha3 (security/safe-unmask-data password))] (if message (sign-message cofx) @@ -100,7 +101,11 @@ (when gas {:gas (str "0x" (abi-spec/number-to-hex gas))}) (when gasPrice - {:gasPrice (str "0x" (abi-spec/number-to-hex gasPrice))}))] + {:gasPrice (str "0x" (abi-spec/number-to-hex gasPrice))}) + (when maxPriorityFeePerGas + {:maxPriorityFeePerGas (str "0x" (abi-spec/number-to-hex maxPriorityFeePerGas))}) + (when maxFeePerGas + {:maxFeePerGas (str "0x" (abi-spec/number-to-hex maxFeePerGas))}))] (when-not in-progress? {:db (update db :signing/sign assoc :error nil :in-progress? true) :signing/send-transaction-fx {:tx-obj tx-obj-to-send @@ -192,17 +197,21 @@ {:to to :contact {:address (ethereum/normalized-hex to)}}))))) -(defn prepare-tx [db {{:keys [data gas gasPrice] :as tx-obj} :tx-obj :as tx}] +(defn prepare-tx [db {{:keys [data gas gasPrice maxFeePerGas maxPriorityFeePerGas] :as tx-obj} :tx-obj :as tx}] (merge tx (parse-tx-obj db tx-obj) - {:data data - :gas (when gas (money/bignumber gas)) - :gasPrice (when gasPrice (money/bignumber gasPrice))})) + {:data data + :gas (when gas (money/bignumber gas)) + :gasPrice (when gasPrice (money/bignumber gasPrice)) + :maxFeePerGas (when maxFeePerGas + (money/bignumber maxFeePerGas)) + :maxPriorityFeePerGas (when maxPriorityFeePerGas + (money/bignumber maxPriorityFeePerGas))})) (fx/defn show-sign [{:keys [db] :as cofx}] (let [{:signing/keys [queue]} db - {{:keys [gas gasPrice] :as tx-obj} :tx-obj {:keys [data typed? pinless?] :as message} :message :as tx} (last queue) + {{:keys [gas gasPrice maxFeePerGas] :as tx-obj} :tx-obj {:keys [data typed? pinless?] :as message} :message :as tx} (last queue) keycard-multiaccount? (boolean (get-in db [:multiaccount :keycard-pairing])) wallet-set-up-passed? (get-in db [:multiaccount :wallet-set-up-passed?])] (if message @@ -240,10 +249,12 @@ :signing/update-estimated-gas {:obj (dissoc tx-obj :gasPrice) :success-event :signing/update-estimated-gas-success :error-event :signing/update-estimated-gas-error}}) - #(when-not gasPrice + #(when-not (or maxFeePerGas gasPrice) {:db (assoc-in (:db %) [:signing/edit-fee :gas-price-loading?] true) :signing/update-gas-price {:success-event :signing/update-gas-price-success - :error-event :signing/update-gas-price-error}}))))) + :error-event :signing/update-gas-price-error + :network-id (get-in (ethereum/current-network db) + [:config :NetworkId])}}))))) (fx/defn check-queue [{:keys [db] :as cofx}] (let [{:signing/keys [tx queue]} db] @@ -485,7 +496,7 @@ (fx/defn sign-transaction-button-clicked {:events [:wallet.ui/sign-transaction-button-clicked]} - [{:keys [db] :as cofx} {:keys [to amount from token gas gasPrice]}] + [{:keys [db] :as cofx} {:keys [to amount from token gas gasPrice maxFeePerGas maxPriorityFeePerGas]}] (let [{:keys [symbol address]} token amount-hex (str "0x" (abi-spec/number-to-hex amount)) to-norm (ethereum/normalized-hex (if (string? to) to (:address to))) @@ -493,10 +504,16 @@ (fx/merge cofx {:db (dissoc db :wallet/prepare-transaction)} (sign - {:tx-obj (merge {:from from-address - ;;gas and gasPrice from qr (eip681) - :gas gas - :gasPrice gasPrice} + {:tx-obj (merge (if (eip1559/sync-enabled?) + {:from from-address + :gas gas + ;; per eip1559 + :maxFeePerGas maxFeePerGas + :maxPriorityFeePerGas maxPriorityFeePerGas} + {:from from-address + ;;gas and gasPrice from qr (eip681) + :gas gas + :gasPrice gasPrice}) (if (= symbol :ETH) {:to to-norm :value amount-hex} diff --git a/src/status_im/signing/core_test.cljs b/src/status_im/signing/core_test.cljs index 4565c2feb5..45a9637985 100644 --- a/src/status_im/signing/core_test.cljs +++ b/src/status_im/signing/core_test.cljs @@ -29,6 +29,8 @@ {:from {:address nil} :gas nil :gasPrice nil + :maxFeePerGas nil + :maxPriorityFeePerGas nil :data nil :to to :contact {:address to} @@ -54,6 +56,8 @@ {:from {:address nil} :gas nil :gasPrice nil + :maxFeePerGas nil + :maxPriorityFeePerGas nil :data data :to contract :contact {:address contract}})))))))))) diff --git a/src/status_im/signing/eip1559.cljs b/src/status_im/signing/eip1559.cljs new file mode 100644 index 0000000000..5d5c6429e3 --- /dev/null +++ b/src/status_im/signing/eip1559.cljs @@ -0,0 +1,64 @@ +(ns status-im.signing.eip1559 + (:require [re-frame.core :as re-frame] + [status-im.ethereum.json-rpc :as json-rpc] + [status-im.utils.config :as config] + [status-im.utils.money :as money])) + +(def activation-blocks + {"123" (money/bignumber 0)}) + +(defonce activated? (atom {})) + +(defonce activated-on-current-network? (atom nil)) + +(defn get-activation-block [network-id] + (get activation-blocks (str network-id))) + +(defn on-block [network-id callback header] + (let [london-activated? + (boolean + (and + (get-activation-block network-id) + (money/greater-than-or-equals + (money/bignumber (:number header)) + (get-activation-block network-id))))] + (swap! activated? assoc network-id london-activated?) + (reset! activated-on-current-network? london-activated?) + (callback london-activated?))) + +(defn check-activation [network-id callback] + (json-rpc/call + {:method "eth_getBlockByNumber" + :params ["latest" false] + :on-success (partial on-block network-id callback) + :on-error #(callback nil)})) + +(defn sync-enabled? [] + (and config/eip1559-enabled? + @activated-on-current-network?)) + +(defn enabled? [network-id enabled-callback disabled-callback] + (let [london-activated? (get @activated? network-id)] + (cond + (not config/eip1559-enabled?) + (disabled-callback) + + (nil? london-activated?) + (check-activation + network-id + (fn [activated?] + (if activated? + (enabled-callback) + (disabled-callback)))) + + london-activated? + (enabled-callback) + + :else + (disabled-callback)))) + +(re-frame/reg-fx + ::check-eip1559-activation + (fn [{:keys [network-id on-enabled on-disabled]}] + (enabled? network-id on-enabled on-disabled))) + diff --git a/src/status_im/signing/gas.cljs b/src/status_im/signing/gas.cljs index 680bad64f7..99b550e38d 100644 --- a/src/status_im/signing/gas.cljs +++ b/src/status_im/signing/gas.cljs @@ -4,7 +4,10 @@ [status-im.i18n.i18n :as i18n] [status-im.bottom-sheet.core :as bottom-sheet] [status-im.utils.fx :as fx] - [status-im.utils.money :as money])) + [status-im.utils.money :as money] + [status-im.signing.eip1559 :as eip1559] + [taoensso.timbre :as log] + [status-im.popover.core :as popover])) (def min-gas-price-wei ^js (money/bignumber 1)) @@ -59,10 +62,92 @@ (assoc key data) edit-max-fee))) +(def minimum-priority-fee + (money/wei-> :gwei (money/->wei :gwei 1))) + +(def average-priority-fee + (money/wei-> :gwei (money/->wei :gwei 1.5))) + +(defn validate-max-fee [db] + (let [{:keys [maxFeePerGas maxPriorityFeePerGas]} (get db :signing/edit-fee) + latest-base-fee (money/wei-> :gwei + (money/bignumber + (get db :wallet/latest-base-fee))) + fee-error (cond + (or (:error maxFeePerGas) + (:error maxPriorityFeePerGas)) + nil + + (money/greater-than latest-base-fee + (:value-number maxFeePerGas)) + {:label (i18n/label :t/below-base-fee) + :severity :error} + + (money/greater-than (:value-number maxPriorityFeePerGas) + (money/sub (:value-number maxFeePerGas) + latest-base-fee)) + {:label (i18n/label :t/reduced-tip) + :severity :error})] + (if fee-error + (assoc-in db [:signing/edit-fee :maxFeePerGas :fee-error] fee-error) + (update-in db [:signing/edit-fee :maxFeePerGas] dissoc :fee-error)))) + +(defn validate-max-priority-fee [db] + (let [{:keys [maxPriorityFeePerGas]} (get db :signing/edit-fee) + fee-error (cond + (:error maxPriorityFeePerGas) + nil + + (money/greater-than minimum-priority-fee + (:value-number maxPriorityFeePerGas)) + {:label (i18n/label :t/low-tip) + :severity :error} + + (money/greater-than average-priority-fee + (:value-number maxPriorityFeePerGas)) + {:label (i18n/label :t/lower-than-average-tip) + :severity :error})] + (if fee-error + (assoc-in db [:signing/edit-fee :maxPriorityFeePerGas :fee-error] fee-error) + (update-in db [:signing/edit-fee :maxPriorityFeePerGas] dissoc :fee-error)))) + +(defn validate-eip1559-fees [db] + (if (eip1559/sync-enabled?) + (reduce + (fn [acc f] + (f acc)) + db + [validate-max-fee + validate-max-priority-fee]) + db)) + (fx/defn edit-value {:events [:signing.edit-fee.ui/edit-value]} [{:keys [db]} key value] - {:db (update db :signing/edit-fee build-edit key value)}) + {:db (-> db + (update :signing/edit-fee build-edit key value) + validate-eip1559-fees)}) + +(fx/defn set-priority-fee + {:events [:signing.edit-fee.ui/set-priority-fee]} + [{:keys [db]} value] + (let [{:keys [maxFeePerGas maxPriorityFeePerGas]} + (get db :signing/edit-fee) + latest-base-fee (get db :wallet/latest-base-fee) + max-fee-value (:value-number maxFeePerGas) + max-priority-fee-value (:value-number maxPriorityFeePerGas) + new-value (money/bignumber value) + fee-without-tip (money/sub max-fee-value max-priority-fee-value) + base-fee (money/wei-> :gwei (money/bignumber latest-base-fee)) + new-max-fee-value + (money/to-fixed + (if (money/greater-than base-fee fee-without-tip) + (money/add new-value base-fee) + (money/add new-value fee-without-tip)))] + {:db (-> db + (update :signing/edit-fee build-edit :maxPriorityFeePerGas value) + (update :signing/edit-fee build-edit :maxFeePerGas new-max-fee-value) + validate-eip1559-fees)})) (fx/defn update-estimated-gas-success {:events [:signing/update-estimated-gas-success]} @@ -93,29 +178,87 @@ (fx/defn open-fee-sheet {:events [:signing.ui/open-fee-sheet]} [{{:signing/keys [tx] :as db} :db :as cofx} sheet-opts] - (let [{:keys [gas gasPrice]} tx - edit-fee (-> {} - (build-edit :gas (money/to-fixed gas)) - (build-edit :gasPrice (money/to-fixed (money/wei-> :gwei gasPrice))))] + (let [{:keys [gas gasPrice maxFeePerGas maxPriorityFeePerGas]} tx + max-fee (money/to-fixed (money/wei-> :gwei maxFeePerGas)) + max-priority-fee (money/to-fixed (money/wei-> :gwei maxPriorityFeePerGas)) + edit-fee (reduce (partial apply build-edit) + {} + {:gas (money/to-fixed gas) + :gasPrice (money/to-fixed (money/wei-> :gwei gasPrice)) + :maxFeePerGas max-fee + :maxPriorityFeePerGas max-priority-fee})] (fx/merge cofx {:db (assoc db :signing/edit-fee edit-fee)} (bottom-sheet/show-bottom-sheet {:view sheet-opts})))) (fx/defn submit-fee {:events [:signing.edit-fee.ui/submit]} - [{{:signing/keys [edit-fee] :as db} :db :as cofx}] - (let [{:keys [gas gasPrice]} edit-fee] - (fx/merge cofx - {:db (update db :signing/tx assoc :gas (:value-number gas) :gasPrice (:value-number gasPrice))} - (bottom-sheet/hide-bottom-sheet)))) + [{{:signing/keys [edit-fee] :as db} :db :as cofx} force?] + (let [{:keys [gas gasPrice maxFeePerGas maxPriorityFeePerGas]} edit-fee + errors? + (keep + (fn [[k {:keys [fee-error]}]] + (when (= :error (:severity fee-error)) + [k fee-error])) + edit-fee)] + (if (and (seq errors?) + (not force?)) + (popover/show-popover cofx {:view :fees-warning}) + (fx/merge cofx + {:db (update db :signing/tx assoc + :gas (:value-number gas) + :gasPrice (:value-number gasPrice) + :maxFeePerGas (money/->wei :gwei (:value-number maxFeePerGas)) + :maxPriorityFeePerGas (money/->wei :gwei (:value-number maxPriorityFeePerGas)))} + (bottom-sheet/hide-bottom-sheet))))) (re-frame/reg-fx :signing/update-gas-price - (fn [{:keys [success-event error-event]}] - (json-rpc/call - {:method "eth_gasPrice" - :on-success #(re-frame/dispatch [success-event %]) - :on-error #(re-frame/dispatch [error-event %])}))) + (fn [{:keys [success-event error-event network-id] :as params}] + (eip1559/enabled? + network-id + (fn [] + (json-rpc/call + {:method "eth_getBlockByNumber" + :params ["latest" false] + :on-success #(re-frame/dispatch [::header-fetched + (assoc params :header %)]) + :on-error #(re-frame/dispatch [error-event %])})) + (fn [] + (json-rpc/call + {:method "eth_gasPrice" + :on-success #(re-frame/dispatch [success-event %]) + :on-error #(re-frame/dispatch [error-event %])}))))) + +(fx/defn header-fetched + {:events [::header-fetched]} + [_ {:keys [error-event] :as params}] + {::json-rpc/call + [{:method "eth_maxPriorityFeePerGas" + :on-success #(re-frame/dispatch [::max-priority-fee-per-gas-fetched + (assoc params :max-priority-fee %)]) + :on-error (if error-event + #(re-frame/dispatch [error-event %]) + #(log/error "Can't fetch header" %))}]}) + +(def london-block-gas-limit (money/bignumber 30000000)) + +(defn check-base-fee [{:keys [gasUsed baseFeePerGas]}] + {:base-fee baseFeePerGas + :spike? (or (money/greater-than-or-equals + (money/bignumber 0) + (money/bignumber gasUsed)) + (money/greater-than-or-equals + (money/bignumber gasUsed) + (money/bignumber london-block-gas-limit)))}) + +(fx/defn max-priority-fee-per-gas-fetched + {:events [::max-priority-fee-per-gas-fetched]} + [_ {:keys [success-event header max-priority-fee]}] + (let [{:keys [base-fee spike?]} (check-base-fee header)] + {:dispatch [success-event {:base-fee base-fee + :max-priority-fee max-priority-fee + :spike? spike?}]})) (re-frame/reg-fx :signing/update-estimated-gas diff --git a/src/status_im/subs.cljs b/src/status_im/subs.cljs index 48b57e857c..00b0cf9b0b 100644 --- a/src/status_im/subs.cljs +++ b/src/status_im/subs.cljs @@ -39,6 +39,7 @@ [status-im.chat.models.mentions :as mentions] [status-im.notifications.core :as notifications] [status-im.utils.currency :as currency] + [status-im.signing.eip1559 :as eip1559] [clojure.set :as clojure.set])) ;; TOP LEVEL =========================================================================================================== @@ -163,7 +164,8 @@ (reg-root-key-sub :wallet/refreshing-history? :wallet/refreshing-history?) (reg-root-key-sub :wallet/fetching-error :wallet/fetching-error) (reg-root-key-sub :wallet/non-archival-node :wallet/non-archival-node) - +(reg-root-key-sub :wallet/latest-base-fee :wallet/latest-base-fee) +(reg-root-key-sub :wallet/latest-priority-fee :wallet/latest-priority-fee) ;;commands (reg-root-key-sub :commands/select-account :commands/select-account) @@ -2494,8 +2496,29 @@ (re-frame/reg-sub :signing/fee :<- [:signing/tx] - (fn [{:keys [gas gasPrice]}] - (signing.gas/calculate-max-fee gas gasPrice))) + (fn [{:keys [gas gasPrice maxFeePerGas]}] + (signing.gas/calculate-max-fee gas (or maxFeePerGas gasPrice)))) + +(re-frame/reg-sub + :signing/currencies + :<- [:prices] + :<- [:wallet/currency] + :<- [:ethereum/native-currency] + (fn [[prices {:keys [code]} {:keys [symbol]}]] + [(name symbol) + code + (get-in prices [symbol (keyword code) :price])])) + +(re-frame/reg-sub + :signing/priority-fee-suggestions-range + :<- [:wallet/latest-priority-fee] + (fn [latest-fee] + [0 (->> latest-fee + money/bignumber + (money/wei-> :gwei) + (money/mul (money/bignumber 2)) + money/to-fixed + js/parseFloat)])) (re-frame/reg-sub :signing/phrase @@ -2568,12 +2591,13 @@ [(re-frame/subscribe [:signing/tx]) (re-frame/subscribe [:balance address])]) (fn [[{:keys [amount token gas gasPrice approve? gas-error-message]} balance]] - (if (and amount token (not approve?)) - (let [amount-bn (money/formatted->internal (money/bignumber amount) (:symbol token) (:decimals token)) - amount-error (or (get-amount-error amount (:decimals token)) - (get-sufficient-funds-error balance (:symbol token) amount-bn))] - (merge amount-error (get-sufficient-gas-error gas-error-message balance (:symbol token) amount-bn gas gasPrice))) - (get-sufficient-gas-error gas-error-message balance nil nil gas gasPrice)))) + (when-not (eip1559/sync-enabled?) + (if (and amount token (not approve?)) + (let [amount-bn (money/formatted->internal (money/bignumber amount) (:symbol token) (:decimals token)) + amount-error (or (get-amount-error amount (:decimals token)) + (get-sufficient-funds-error balance (:symbol token) amount-bn))] + (merge amount-error (get-sufficient-gas-error gas-error-message balance (:symbol token) amount-bn gas gasPrice))) + (get-sufficient-gas-error gas-error-message balance nil nil gas gasPrice))))) (re-frame/reg-sub :wallet.send/prepare-transaction-with-balance diff --git a/src/status_im/ui/screens/popover/views.cljs b/src/status_im/ui/screens/popover/views.cljs index f780c0bf1c..b70cf3748e 100644 --- a/src/status_im/ui/screens/popover/views.cljs +++ b/src/status_im/ui/screens/popover/views.cljs @@ -20,7 +20,8 @@ [status-im.ui.components.colors :as colors] [status-im.ui.screens.keycard.views :as keycard.views] [status-im.ui.screens.keycard.frozen-card.view :as frozen-card] - [status-im.ui.screens.chat.message.pinned-message :as pinned-message])) + [status-im.ui.screens.chat.message.pinned-message :as pinned-message] + [status-im.ui.screens.signing.sheets :as signing-sheets])) (defn hide-panel-anim [bottom-anim-value alpha-value window-height] @@ -177,6 +178,9 @@ (= :pin-limit view) [pinned-message/pin-limit-popover] + (= :fees-warning view) + [signing-sheets/fees-warning] + :else [view])]]]]])))}))) diff --git a/src/status_im/ui/screens/signing/sheets.cljs b/src/status_im/ui/screens/signing/sheets.cljs index b8cb65bc02..470563369f 100644 --- a/src/status_im/ui/screens/signing/sheets.cljs +++ b/src/status_im/ui/screens/signing/sheets.cljs @@ -4,7 +4,13 @@ [re-frame.core :as re-frame] [status-im.i18n.i18n :as i18n] [quo.core :as quo] - [status-im.ui.components.colors :as colors])) + [quo.design-system.colors :as quo-colors] + [status-im.ui.components.colors :as colors] + [status-im.ui.components.slider :as slider] + [status-im.utils.money :as money] + [status-im.ui.components.icons.icons :as icons] + [clojure.string :as clojure.string] + [status-im.signing.gas :as gas])) (views/defview fee-bottom-sheet [fee-display-symbol] (views/letsubs [{gas-edit :gas gas-price-edit :gasPrice max-fee :max-fee} [:signing/edit-fee]] @@ -60,3 +66,296 @@ :on-press #(re-frame/dispatch [:signing.edit-fee.ui/submit]) :disabled (or (:error gas-edit) (:error gas-price-edit))} (i18n/label :t/update)]]])) + +(declare fee-bottom-sheet-eip1559) + +(defn fee-bottom-sheet-eip1559-custom [fee-display-symbol] + (let [{gas-edit :gas + max-fee-per-gas-edit :maxFeePerGas + max-priority-fee-per-gas-edit :maxPriorityFeePerGas} + @(re-frame/subscribe [:signing/edit-fee]) + base-fee @(re-frame/subscribe [:wallet/latest-base-fee]) + [fee-currency fiat-currency price] + @(re-frame/subscribe [:signing/currencies]) + fee-eth + (if (and (:value-number gas-edit) + (:value-number max-fee-per-gas-edit)) + (money/mul + (money/wei->ether + (money/->wei :gwei (:value-number max-fee-per-gas-edit))) + (:value-number gas-edit)) + (money/bignumber 0))] + [react/view + [react/view {:style {:margin-horizontal 16 :margin-top 8}} + [react/text {:style {:typography :title-bold}} (i18n/label :t/max-priority-fee)] + [react/text {:style {:color (quo-colors/get-color :text-02) + :margin-top 12}} + (i18n/label :t/miners-higher-fee)] + [react/view + {:style {:margin-top 12 + :flex-direction :row + :align-items :center + :justify-content :space-between}} + [quo/text (i18n/label :t/current-base-fee)] + [quo/text + (money/to-fixed (money/wei-> :gwei base-fee)) + " " + (i18n/label :t/gwei)]]] + [react/view {:margin-vertical 12 + :margin-horizontal 16 + :height 1 + :background-color colors/gray-lighter}] + [react/view + {:margin-horizontal 16 + :margin-top 4 + :margin-bottom 26} + [quo/text-input + {:label (i18n/label :t/gas-amount-limit) + :error (:error gas-edit) + :default-value (:value gas-edit) + :on-change-text #(re-frame/dispatch [:signing.edit-fee.ui/edit-value :gas %]) + :show-cancel false}]] + [react/view + {:margin-horizontal 16 + :margin-top 4 + :margin-bottom 26} + [quo/text-input + {:label (i18n/label :t/per-gas-tip-limit) + :error (or (:error max-priority-fee-per-gas-edit) + (get-in max-priority-fee-per-gas-edit [:fee-error :label])) + :default-value (:value max-priority-fee-per-gas-edit) + :on-change-text #(re-frame/dispatch [:signing.edit-fee.ui/edit-value :maxPriorityFeePerGas %]) + :show-cancel false + :after {:component [quo/text + {:style {:color (quo-colors/get-color :text-02)}} + (i18n/label :t/gwei)]}}]] + [react/view + {:margin-horizontal 16 + :margin-top 4 + :margin-bottom 12} + [quo/text-input + {:label (i18n/label :t/per-gas-price-limit) + :error (or (:error max-fee-per-gas-edit) + (get-in max-fee-per-gas-edit [:fee-error :label])) + :default-value (:value max-fee-per-gas-edit) + :on-change-text #(re-frame/dispatch [:signing.edit-fee.ui/edit-value :maxFeePerGas %]) + :show-cancel false + :after {:component [quo/text + {:style {:color (quo-colors/get-color :text-02)}} + (i18n/label :t/gwei)]}}]] + [react/view {:margin-vertical 12 + :height 1 + :background-color colors/gray-lighter}] + [react/view + {:style {:margin-top 4 + :margin-horizontal 16 + :flex-direction :row + :align-items :center + :justify-content :space-between}} + [quo/text (i18n/label :t/maximum-fee) ":"] + [react/view + {:style {:flex-direction :row + :align-items :center + :justify-content :flex-end}} + [quo/text + {:style {:margin-right 6 + :color (quo-colors/get-color :text-02)}} + (str (money/to-fixed fee-eth 6) " " fee-currency)] + [quo/text + (money/to-fixed (money/mul fee-eth (money/bignumber price)) 2) + " " + fiat-currency]]] + [react/text {:style {:color (quo-colors/get-color :text-02) + :margin 16}} + (i18n/label :t/fee-explanation)] + [react/view + {:style {:margin-left 12 + :margin-right 16 + :flex-direction :row + :align-items :center + :justify-content :space-between}} + [quo/button + {:type :secondary + :on-press #(re-frame/dispatch + [:bottom-sheet/show-sheet + {:content (fn [] + [fee-bottom-sheet-eip1559 fee-display-symbol]) + :content-height 270}])} + (i18n/label :t/see-suggestions)] + [quo/button + {:type :primary + :on-press #(re-frame/dispatch [:signing.edit-fee.ui/submit]) + :theme :accent} + (i18n/label :t/save)]]])) + +(defn fee-bottom-sheet-eip1559 [fee-display-symbol] + (let [{priority-fee-edit :maxPriorityFeePerGas + fee-edit :maxFeePerGas + gas :gas} + @(re-frame/subscribe [:signing/edit-fee]) + [min-value max-value] + @(re-frame/subscribe [:signing/priority-fee-suggestions-range]) + [fee-currency fiat-currency price] @(re-frame/subscribe [:signing/currencies]) + fee-eth + (if (and (:value-number gas) + (:value-number fee-edit)) + (money/mul + (money/wei->ether + (money/->wei :gwei (:value-number fee-edit))) + (:value-number gas)) + (money/bignumber 0))] + [react/view + [react/view {:style {:margin-horizontal 16 :margin-top 8}} + [react/text {:style {:typography :title-bold}} (i18n/label :t/max-priority-fee)] + [react/text {:style {:color (quo-colors/get-color :text-02) + :margin-top 12}} + (i18n/label :t/miners-higher-fee)] + [quo/text {:style {:margin-horizontal 8 + :margin-top 24} + :size :large} + (clojure.string/join + " " + [(money/to-fixed fee-eth 6) + fee-currency + "•" + (money/to-fixed (money/mul fee-eth (money/bignumber price)) 2) + fiat-currency])] + [slider/animated-slider + {:style {:margin-horizontal 5 + :margin-vertical 4} + :minimumValue min-value + :value (js/parseFloat (:value priority-fee-edit)) + :onValueChange #(re-frame/dispatch [:signing.edit-fee.ui/set-priority-fee (str %)]) + :maximumValue max-value + :minimumTrackTintColor (quo-colors/get-color :icon-04)}] + [react/view + {:style {:margin-horizontal 8 + :flex-direction :row + :align-items :center + :justify-content :space-between}} + [react/text {:style {:color (quo-colors/get-color :text-02)}} (i18n/label :t/slow)] + [react/text {:style {:color (quo-colors/get-color :text-02)}} (i18n/label :t/optimal)] + [react/text {:style {:color (quo-colors/get-color :text-02)}} (i18n/label :t/fast)]]] + [react/view + {:style {:margin-left 12 + :margin-right 16 + :margin-top 38} + :flex-direction :row + :align-items :center + :justify-content :space-between} + [quo/button + {:type :secondary + :on-press #(re-frame/dispatch + [:bottom-sheet/show-sheet + {:content (fn [] + [fee-bottom-sheet-eip1559-custom fee-display-symbol]) + :content-height 270}])} + (i18n/label :t/set-custom-fee)] + [quo/button + {:type :primary + :theme :accent + :on-press #(re-frame/dispatch [:signing.edit-fee.ui/submit])} + (i18n/label :t/save)]]])) + +(defn gwei [val] + (str val " " (i18n/label :t/gwei))) + +(defn fees-warning [] + (let [base-fee @(re-frame/subscribe [:wallet/latest-base-fee]) + base-fee-gwei (money/wei-> :gwei (money/bignumber base-fee)) + priority-fee @(re-frame/subscribe [:wallet/latest-priority-fee]) + priority-fee-gwei (money/wei-> :gwei (money/bignumber priority-fee)) + {priority-fee-edit :maxPriorityFeePerGas + fee-edit :maxFeePerGas} + @(re-frame/subscribe [:signing/edit-fee])] + [react/view + [react/view {:margin-top 24 + :margin-horizontal 24 + :margin-bottom 32 + :align-items :center} + [react/view {:background-color colors/blue-light + :width 32 + :height 32 + :border-radius 16 + :align-items :center + :justify-content :center} + [icons/icon :main-icons/warning {:color colors/black}]] + [react/text {:style {:typography :title-bold + :margin-top 16 + :margin-bottom 8}} + (i18n/label :t/are-you-sure)] + [react/text {:style {:color colors/gray + :text-align :center + :margin-horizontal 24}} + (i18n/label :t/bad-fees-description)]] + [react/view + {:style {:flex-direction :row + :justify-content :space-between + :margin-horizontal 32}} + [react/text (i18n/label :t/current-base-fee)] + [react/text (gwei (money/to-fixed base-fee-gwei 2))]] + [react/view + {:style {:flex-direction :row + :justify-content :space-between + :margin-horizontal 32}} + [react/text (i18n/label :t/current-minimum-tip)] + [react/text (gwei gas/minimum-priority-fee)]] + [react/view + {:style {:flex-direction :row + :justify-content :space-between + :margin-horizontal 32}} + [react/text (i18n/label :t/current-average-tip)] + [react/text (gwei (money/to-fixed priority-fee-gwei 2))]] + [react/view {:margin-vertical 16 + :height 1 + :background-color colors/gray-lighter}] + [react/view + {:style {:flex-direction :row + :justify-content :space-between + :margin-horizontal 32 + :color :red}} + [react/text {:style {:color (quo-colors/get-color :negative-01)}} + (i18n/label :t/your-tip-limit)] + [react/text {:style {:color (quo-colors/get-color :negative-01)}} + (gwei (money/to-fixed (:value-number priority-fee-edit) 2))]] + [react/view + {:style {:flex-direction :row + :justify-content :space-between + :margin-horizontal 32}} + [react/text {:style {:color (quo-colors/get-color :negative-01)}} + (i18n/label :t/your-price-limit)] + [react/text {:style {:color (quo-colors/get-color :negative-01)}} + (gwei (money/to-fixed (:value-number fee-edit) 2))]] + [react/view {:style + {:background-color colors/gray-lighter + :padding-horizontal 32 + :padding-vertical 16 + :margin-vertical 16}} + [react/view + {:style {:flex-direction :row + :justify-content :space-between}} + [react/text (i18n/label :t/suggested-min-tip)] + [react/text (gwei gas/minimum-priority-fee)]] + [react/view + {:style {:flex-direction :row + :justify-content :space-between}} + [react/text (i18n/label :t/suggested-price-limit)] + [react/text (gwei (money/to-fixed (money/add base-fee-gwei priority-fee-gwei) 2))]]] + [react/view + {:style {:align-items :center + :justify-content :center + :margin-top 8}} + [quo/button + {:type :primary + :on-press #(re-frame/dispatch [:hide-popover])} + (i18n/label :t/change-tip)]] + [react/view + {:style {:align-items :center + :justify-content :center + :margin-top 8 + :margin-bottom 16}} + [quo/button + {:type :secondary + :on-press #(do (re-frame/dispatch [:hide-popover]) + (re-frame/dispatch [:signing.edit-fee.ui/submit true]))} + (i18n/label :t/continue-anyway)]]])) diff --git a/src/status_im/ui/screens/signing/views.cljs b/src/status_im/ui/screens/signing/views.cljs index 59dc68cde8..ad2d2a351d 100644 --- a/src/status_im/ui/screens/signing/views.cljs +++ b/src/status_im/ui/screens/signing/views.cljs @@ -25,7 +25,8 @@ [status-im.ui.screens.keycard.pin.views :as pin.views] [status-im.ui.components.bottom-panel.views :as bottom-panel] [status-im.utils.utils :as utils] - [reagent.core :as reagent])) + [reagent.core :as reagent] + [status-im.signing.eip1559 :as eip1559])) (defn separator [] [react/view {:height 1 :background-color colors/gray-lighter}]) @@ -383,7 +384,10 @@ [react/text {:style {:color colors/gray}} (str " " (:code wallet-currency))]])) :on-press #(re-frame/dispatch [:signing.ui/open-fee-sheet - {:content (fn [] [sheets/fee-bottom-sheet fee-display-symbol]) + {:content (fn [] + (if (eip1559/sync-enabled?) + [sheets/fee-bottom-sheet-eip1559 fee-display-symbol] + [sheets/fee-bottom-sheet fee-display-symbol])) :content-height 270}])}]))) (views/defview network-item [] diff --git a/src/status_im/utils/config.cljs b/src/status_im/utils/config.cljs index ee9a22f0d2..98f8692fd5 100644 --- a/src/status_im/utils/config.cljs +++ b/src/status_im/utils/config.cljs @@ -49,6 +49,7 @@ (def database-management-enabled? (enabled? (get-config :DATABASE_MANAGEMENT_ENABLED "0"))) (def debug-webview? (enabled? (get-config :DEBUG_WEBVIEW "0"))) (def metrics-enabled? (enabled? (get-config :METRICS_ENABLED "0"))) +(def eip1559-enabled? (enabled? (get-config :EIP1559_ENABLED "0"))) ;; CONFIG VALUES (def log-level diff --git a/src/status_im/utils/money.cljs b/src/status_im/utils/money.cljs index 1f2af17330..5ce5508d67 100644 --- a/src/status_im/utils/money.cljs +++ b/src/status_im/utils/money.cljs @@ -33,9 +33,20 @@ (new BigNumber (normalize (str n))) (catch :default _ nil)))) +(defn greater-than-or-equals + [bn1 bn2] + (.greaterThanOrEqualTo bn1 bn2)) + +(defn greater-than + [bn1 bn2] + (.greaterThan bn1 bn2)) + +(defn sub [bn1 bn2] + (.sub bn1 bn2)) + (defn valid? [^js bn] (when bn - (.greaterThanOrEqualTo bn 0))) + (greater-than-or-equals bn 0))) (defn from-decimal [n] (when n @@ -62,9 +73,13 @@ (when-let [^js bn (bignumber n)] (.times bn (eth-units unit)))) -(defn to-fixed [^js bn] - (when bn - (.toFixed bn))) +(defn to-fixed + ([^js bn] + (when bn + (.toFixed bn))) + ([^js bn b] + (when bn + (.toFixed bn b)))) (defn to-number [^js bn] (when bn @@ -154,3 +169,9 @@ (crypto->fiat (get-in prices [from to :price] ^js (bignumber 0))) (with-precision 2) str)) + +(defn add [bn1 n2] + (.add bn1 n2)) + +(defn mul [bn1 bn2] + (.mul bn1 bn2)) diff --git a/src/status_im/wallet/core.cljs b/src/status_im/wallet/core.cljs index 2f1e94e0ab..f6cc031d7d 100644 --- a/src/status_im/wallet/core.cljs +++ b/src/status_im/wallet/core.cljs @@ -27,6 +27,7 @@ status-im.wallet.recipient.core [status-im.async-storage.core :as async-storage] [status-im.popover.core :as popover.core] + [status-im.signing.eip1559 :as eip1559] [clojure.set :as clojure.set])) (defn get-balance @@ -380,7 +381,16 @@ (fx/defn wallet-send-gas-price-success {:events [:wallet.send/update-gas-price-success]} [{db :db} price] - {:db (assoc-in db [:wallet/prepare-transaction :gasPrice] price)}) + (if (eip1559/sync-enabled?) + (let [{:keys [base-fee max-priority-fee]} price + max-priority-fee-bn (money/bignumber max-priority-fee)] + {:db (-> db + (update :wallet/prepare-transaction assoc + :maxFeePerGas (money/to-hex (money/add max-priority-fee-bn base-fee)) + :maxPriorityFeePerGas max-priority-fee) + (assoc :wallet/latest-base-fee base-fee + :wallet/latest-priority-fee max-priority-fee))}) + {:db (assoc-in db [:wallet/prepare-transaction :gasPrice] price)})) (fx/defn set-max-amount {:events [:wallet.send/set-max-amount]} @@ -468,7 +478,9 @@ {:events [::recipient-address-resolved]} [{:keys [db]} address] {:db (assoc-in db [:wallet/prepare-transaction :to :address] address) - :signing/update-gas-price {:success-event :wallet.send/update-gas-price-success}}) + :signing/update-gas-price {:success-event :wallet.send/update-gas-price-success + :network-id (get-in (ethereum/current-network db) + [:config :NetworkId])}}) (fx/defn prepare-transaction-from-chat {:events [:wallet/prepare-transaction-from-chat]} @@ -521,7 +533,9 @@ :symbol :ETH :from-chat? false}) :dispatch [:open-modal :prepare-send-transaction] - :signing/update-gas-price {:success-event :wallet.send/update-gas-price-success}}) + :signing/update-gas-price {:success-event :wallet.send/update-gas-price-success + :network-id (get-in (ethereum/current-network db) + [:config :NetworkId])}}) (fx/defn cancel-transaction-command {:events [:wallet/cancel-transaction-command]} diff --git a/translations/en.json b/translations/en.json index 085a1e02db..29f2268317 100644 --- a/translations/en.json +++ b/translations/en.json @@ -1608,5 +1608,31 @@ }, "pinned-messages-empty": "Pinned messages will appear here. To pin a message, press and hold it and tap `Pin`", "pinned-by": "Pinned by", - "pin-limit-reached": "Pin limit reached. Unpin a previous message first." + "pin-limit-reached": "Pin limit reached. Unpin a previous message first.", + "max-fee": "Max fee", + "max-priority-fee": "Max priority fee", + "miners-higher-fee": "Miners will likely inlcude your transaction earlier if you pay a higher fee.", + "gas-amount-limit": "Gas amount limit", + "per-gas-tip-limit": "Per-gas tip limit", + "per-gas-price-limit": "Per-gas price limit", + "current-base-fee": "Current base fee", + "fee-explanation": "Maximum overall price for the transaction. If the block base fee exceeds this, it will be included in a following block with a lower base fee.", + "slow": "Slow", + "optimal": "Optimal", + "fast": "Fast", + "see-suggestions": "See suggestions", + "maximum-fee": "Maximum fee", + "low-tip": "tip is too low", + "lower-than-average-tip": "lower than average tip", + "below-base-fee": "max fee below base fee", + "reduced-tip": "priority tip will be reduced", + "are-you-sure": "Are you sure?", + "bad-fees-description": "Your priority fee is below our suggested parameters.", + "change-tip": "Change tip", + "current-minimum-tip": "Current minimum tip", + "current-average-tip": "Current average tip", + "your-tip-limit": "Your tip limit", + "your-price-limit": "Your price limit", + "suggested-min-tip": "Suggested min. tip", + "suggested-price-limit": "Suggested price limit" }