diff --git a/src/quo/components/settings/data_item/view.cljs b/src/quo/components/settings/data_item/view.cljs index d318492ab7..88eecc460c 100644 --- a/src/quo/components/settings/data_item/view.cljs +++ b/src/quo/components/settings/data_item/view.cljs @@ -55,26 +55,36 @@ theme))}])])) (defn- left-title - [{:keys [title blur?]}] + [{:keys [title blur? title-icon]}] (let [theme (quo.theme/use-theme)] [rn/view {:style style/title-container} [text/text {:weight :regular :size :paragraph-2 :style (style/title blur? theme)} - title]])) + title] + (when title-icon + [icons/icon title-icon + {:accessibility-label :title-icon + :size 12 + :color (if blur? + colors/neutral-40 + (colors/theme-colors colors/neutral-50 + colors/neutral-40 + theme))}])])) (defn- left-side "The description can either be given as a string `subtitle-type` or a component `custom-subtitle`" [{:keys [title status size blur? custom-subtitle icon subtitle subtitle-type subtitle-color icon-color - customization-color network-image emoji] + customization-color network-image emoji title-icon] :as props}] (let [theme (quo.theme/use-theme)] [rn/view {:style style/left-side} - [left-title - {:title title - :blur? blur? - :theme theme}] + [rn/view + [left-title + {:title title + :title-icon title-icon + :blur? blur?}]] (if (= status :loading) [left-loading {:size size @@ -132,6 +142,7 @@ [:subtitle {:optional true} [:maybe [:or :string :double]]] [:custom-subtitle {:optional true} [:maybe fn?]] [:icon {:optional true} [:maybe :keyword]] + [:title-icon {:optional true} [:maybe :keyword]] [:emoji {:optional true} [:maybe :string]] [:customization-color {:optional true} [:maybe :schema.common/customization-color]] [:network-image {:optional true} [:maybe :schema.common/image-source]] diff --git a/src/status_im/constants.cljs b/src/status_im/constants.cljs index f4b6651cba..b3ffdb0898 100644 --- a/src/status_im/constants.cljs +++ b/src/status_im/constants.cljs @@ -309,6 +309,8 @@ (def ^:const token-sort-priority {"SNT" 1 "STT" 1 "ETH" 2 "DAI" 3}) +(def ^:const token-display-precision 6) + (def ^:const dapp-permission-contact-code "contact-code") (def ^:const dapp-permission-web3 "web3") (def ^:const dapp-permission-qr-code "qr-code") diff --git a/src/status_im/contexts/preview/quo/settings/data_item.cljs b/src/status_im/contexts/preview/quo/settings/data_item.cljs index 0714391bc3..b744ff49da 100644 --- a/src/status_im/contexts/preview/quo/settings/data_item.cljs +++ b/src/status_im/contexts/preview/quo/settings/data_item.cljs @@ -25,6 +25,12 @@ {:key :i/copy} {:key nil :value "None"}]} + {:type :select + :key :title-icon + :options [{:key :i/chevron-right} + {:key :i/copy} + {:key nil + :value "None"}]} {:type :select :key :right-content :options [{:key nil diff --git a/src/status_im/contexts/wallet/common/utils.cljs b/src/status_im/contexts/wallet/common/utils.cljs index 3c5bf2b965..8ead59a1fd 100644 --- a/src/status_im/contexts/wallet/common/utils.cljs +++ b/src/status_im/contexts/wallet/common/utils.cljs @@ -457,7 +457,7 @@ (defn transaction-path [{:keys [from-address to-address token-id-from token-address token-id-to route data slippage-percentage eth-transfer?]}] - (let [{:keys [bridge-name amount-in bonder-fees from + (let [{:keys [bridge-name amount-in from to]} route tx-data (transaction-data {:from-address from-address :to-address to-address @@ -465,6 +465,7 @@ :route route :data data :eth-transfer? eth-transfer?}) + bonder-fees (-> route :bounder-fees money/to-string) to-chain-id (:chain-id to) from-chain-id (:chain-id from)] (cond-> {:BridgeName bridge-name diff --git a/src/status_im/contexts/wallet/data_store.cljs b/src/status_im/contexts/wallet/data_store.cljs index 7a7e9ce739..db82a249a7 100644 --- a/src/status_im/contexts/wallet/data_store.cljs +++ b/src/status_im/contexts/wallet/data_store.cljs @@ -233,13 +233,12 @@ (defn new->old-route-path [new-path] - (let [bonder-fees (:tx-bonder-fees new-path) - token-fees (+ (money/wei->ether bonder-fees) - (money/wei->ether (:tx-token-fees new-path)))] + (let [bonder-fees (-> new-path :tx-bonder-fees money/bignumber) + token-fees (-> new-path :tx-token-fees money/bignumber)] {:from (:from-chain new-path) :amount-in-locked (:amount-in-locked new-path) :amount-in (:amount-in new-path) - :max-amount-in "0x0" + :max-amount-in (:max-amount-in new-path) :gas-fees {:gas-price "0" :base-fee (send-utils/convert-to-gwei (:tx-base-fee new-path) diff --git a/src/status_im/contexts/wallet/send/input_amount/component_spec.cljs b/src/status_im/contexts/wallet/send/input_amount/component_spec.cljs index e70fd3b59f..798b9a41bf 100644 --- a/src/status_im/contexts/wallet/send/input_amount/component_spec.cljs +++ b/src/status_im/contexts/wallet/send/input_amount/component_spec.cljs @@ -104,16 +104,19 @@ :wallet/wallet-send-tx-type :tx/send :wallet/wallet-send-fee-fiat-formatted "$5,00" :wallet/sending-collectible? false - :wallet/total-amount (money/bignumber "250")}) + :wallet/send-total-amount-formatted "250 ETH" + :wallet/total-amount (money/bignumber "250") + :wallet/bridge-to-network-details nil + :wallet/send-amount-fixed "" + :wallet/send-display-token-decimals 5}) (h/describe "Send > input amount screen" (h/setup-restorable-re-frame) (h/test "Default render" - (h/setup-subs sub-mocks) + (h/setup-subs (assoc sub-mocks :wallet/send-display-token-decimals 2)) (h/render-with-theme-provider [input-amount/view - {:crypto-decimals 2 - :limit-crypto (money/bignumber 250) + {:limit-crypto (money/bignumber 250) :initial-crypto-currency? false}]) (h/is-truthy (h/get-by-text "0")) (h/is-truthy (h/get-by-text "USD")) @@ -122,12 +125,11 @@ (h/is-disabled (h/get-by-label-text :button-one))) (h/test "Fill token input and confirm" - (h/setup-subs sub-mocks) + (h/setup-subs (assoc sub-mocks :wallet/send-display-token-decimals 10)) (let [on-confirm (h/mock-fn)] (h/render-with-theme-provider [input-amount/view - {:crypto-decimals 10 - :limit-crypto (money/bignumber 1000) + {:limit-crypto (money/bignumber 1000) :on-confirm on-confirm :initial-crypto-currency? true}]) @@ -147,10 +149,9 @@ (h/was-called on-confirm))))))) (h/test "Try to fill more than limit" - (h/setup-subs sub-mocks) + (h/setup-subs (assoc sub-mocks :wallet/send-display-token-decimals 1)) (h/render-with-theme-provider [input-amount/view - {:crypto-decimals 1 - :limit-crypto (money/bignumber 1)}]) + {:limit-crypto (money/bignumber 1)}]) (h/fire-event :press (h/query-by-label-text :keyboard-key-2)) (h/fire-event :press (h/query-by-label-text :keyboard-key-9)) @@ -159,11 +160,10 @@ (h/is-truthy (h/get-by-label-text :container-error))) (h/test "Switch from crypto to fiat and check limit" - (h/setup-subs sub-mocks) + (h/setup-subs (assoc sub-mocks :wallet/send-display-token-decimals 1)) (h/render-with-theme-provider [input-amount/view - {:crypto-decimals 1 - :limit-crypto (money/bignumber 10) - :on-confirm #()}]) + {:limit-crypto (money/bignumber 10) + :on-confirm #()}]) (h/fire-event :press (h/query-by-label-text :keyboard-key-9)) (h/fire-event :press (h/query-by-label-text :keyboard-key-9)) diff --git a/src/status_im/contexts/wallet/send/input_amount/estimated_fees.cljs b/src/status_im/contexts/wallet/send/input_amount/estimated_fees.cljs new file mode 100644 index 0000000000..4fff022feb --- /dev/null +++ b/src/status_im/contexts/wallet/send/input_amount/estimated_fees.cljs @@ -0,0 +1,67 @@ +(ns status-im.contexts.wallet.send.input-amount.estimated-fees + (:require + [quo.core :as quo] + [quo.theme] + [react-native.core :as rn] + [status-im.common.not-implemented :as not-implemented] + [status-im.contexts.wallet.send.input-amount.style :as style] + [status-im.feature-flags :as ff] + [utils.i18n :as i18n] + [utils.re-frame :as rf])) + +(defn- bonder-fee-info-sheet + [] + [:<> + [quo/drawer-top + {:title (i18n/label :t/understanding-bonder-fees-title)}] + [rn/view {:style {:padding-horizontal 20}} + [quo/text + {:weight :regular + :size :paragraph-2} + (i18n/label :t/understanding-bonder-fees-description)]]]) + +(defn- show-bonder-fee-info + [] + (rf/dispatch + [:show-bottom-sheet {:content bonder-fee-info-sheet}])) + +(defn- received-amount + [{:keys [loading-routes?]}] + (let [amount (rf/sub [:wallet/send-total-amount-formatted]) + tx-type (rf/sub [:wallet/wallet-send-tx-type]) + {:keys [full-name]} (rf/sub [:wallet/bridge-to-network-details])] + [quo/data-item + (cond-> {:container-style style/amount-data-item + :status (if loading-routes? :loading :default) + :size :small + :title (i18n/label :t/recipient-gets) + :subtitle amount} + + (= tx-type :tx/bridge) + (assoc + :title-icon :i/info + :title (i18n/label :t/bridged-to + {:network full-name}) + :on-press show-bonder-fee-info))])) + +(defn view + [{:keys [loading-routes? fees]}] + [rn/view {:style style/estimated-fees-container} + (when (ff/enabled? ::ff/wallet.advanced-sending) + [rn/view {:style style/estimated-fees-content-container} + [quo/button + {:icon-only? true + :type :outline + :size 32 + :inner-style {:opacity 1} + :accessibility-label :advanced-button + :disabled? loading-routes? + :on-press not-implemented/alert} + :i/advanced]]) + [quo/data-item + {:container-style style/fees-data-item + :status (if loading-routes? :loading :default) + :size :small + :title (i18n/label :t/fees) + :subtitle fees}] + [received-amount {:loading-routes? loading-routes?}]]) 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 2c27829441..276450dd27 100644 --- a/src/status_im/contexts/wallet/send/input_amount/view.cljs +++ b/src/status_im/contexts/wallet/send/input_amount/view.cljs @@ -11,45 +11,17 @@ [status-im.contexts.wallet.common.account-switcher.view :as account-switcher] [status-im.contexts.wallet.common.asset-list.view :as asset-list] [status-im.contexts.wallet.common.utils :as utils] + [status-im.contexts.wallet.send.input-amount.estimated-fees :as estimated-fees] [status-im.contexts.wallet.send.input-amount.style :as style] [status-im.contexts.wallet.send.routes.view :as routes] [status-im.contexts.wallet.sheets.buy-token.view :as buy-token] [status-im.contexts.wallet.sheets.unpreferred-networks-alert.view :as unpreferred-networks-alert] - [status-im.feature-flags :as ff] [status-im.setup.hot-reload :as hot-reload] [utils.debounce :as debounce] [utils.i18n :as i18n] [utils.money :as money] - [utils.number :as number] [utils.re-frame :as rf])) -(defn- estimated-fees - [{:keys [loading-routes? fees amount]}] - [rn/view {:style style/estimated-fees-container} - (when (ff/enabled? ::ff/wallet.advanced-sending) - [rn/view {:style style/estimated-fees-content-container} - [quo/button - {:icon-only? true - :type :outline - :size 32 - :inner-style {:opacity 1} - :accessibility-label :advanced-button - :disabled? loading-routes? - :on-press #(js/alert "Not implemented yet")} - :i/advanced]]) - [quo/data-item - {:container-style style/fees-data-item - :status (if loading-routes? :loading :default) - :size :small - :title (i18n/label :t/fees) - :subtitle fees}] - [quo/data-item - {:container-style style/amount-data-item - :status (if loading-routes? :loading :default) - :size :small - :title (i18n/label :t/recipient-gets) - :subtitle amount}]]) - (defn- every-network-value-is-zero? [sender-network-values] (every? (fn [{:keys [total-amount]}] @@ -165,7 +137,6 @@ ;; for component tests only [{default-on-confirm :on-confirm default-limit-crypto :limit-crypto - default-crypto-decimals :crypto-decimals on-navigate-back :on-navigate-back button-one-label :button-one-label button-one-props :button-one-props @@ -199,17 +170,13 @@ :market-values-per-currency currency :price) - token-decimals (-> token - utils/token-usd-price - utils/one-cent-value - utils/calc-max-crypto-decimals) + token-decimals (rf/sub [:wallet/send-display-token-decimals]) [input-state set-input-state] (rn/use-state controlled-input/init-state) clear-input! #(set-input-state controlled-input/delete-all) currency-symbol (rf/sub [:profile/currency-symbol]) loading-routes? (rf/sub [:wallet/wallet-send-loading-suggested-routes?]) route (rf/sub [:wallet/wallet-send-route]) on-confirm (or default-on-confirm handle-on-confirm) - crypto-decimals (or token-decimals default-crypto-decimals) max-limit (if crypto-currency? (utils/cut-crypto-decimals-to-fit-usd-cents token-balance @@ -221,15 +188,8 @@ (controlled-input/input-error input-state))) amount-in-crypto (if crypto-currency? input-value - (number/remove-trailing-zeroes - (.toFixed (/ input-value conversion-rate) - crypto-decimals))) - total-amount-receiver (rf/sub [:wallet/total-amount true]) - amount-text (str (number/remove-trailing-zeroes - (.toFixed total-amount-receiver - (min token-decimals 6))) - " " - token-symbol) + (rf/sub [:wallet/send-amount-fixed + (/ input-value conversion-rate)])) show-select-asset-sheet #(rf/dispatch [:show-bottom-sheet {:content (fn [] @@ -382,10 +342,9 @@ [not-enough-asset]) (when (or (and (not no-routes-found?) (or loading-routes? route)) not-enough-asset?) - [estimated-fees + [estimated-fees/view {:loading-routes? loading-routes? - :fees fee-formatted - :amount amount-text}]) + :fees fee-formatted}]) (when show-no-routes? [no-routes-found]) [quo/bottom-actions @@ -417,7 +376,7 @@ :delete-key? true :on-press (fn [c] (let [new-text (str input-value c) - max-decimals (if crypto-currency? crypto-decimals 2) + max-decimals (if crypto-currency? token-decimals 2) regex-pattern (str "^\\d*\\.?\\d{0," max-decimals "}$") regex (re-pattern regex-pattern)] (when (re-matches regex new-text) diff --git a/src/status_im/contexts/wallet/send/routes/view.cljs b/src/status_im/contexts/wallet/send/routes/view.cljs index a78cf06805..3994d3cba1 100644 --- a/src/status_im/contexts/wallet/send/routes/view.cljs +++ b/src/status_im/contexts/wallet/send/routes/view.cljs @@ -247,20 +247,22 @@ {chain-id :chain-id network-value-type :type total-amount :total-amount}] - (let [status (cond (and (= network-value-type :not-available) - loading-routes? - token-not-supported-in-receiver-networks?) - :loading - (= network-value-type :not-available) - :disabled - :else network-value-type)] + (let [status (cond (and (= network-value-type :not-available) + loading-routes? + token-not-supported-in-receiver-networks?) + :loading + (= network-value-type :not-available) + :disabled + :else network-value-type) + amount-formatted (-> (rf/sub [:wallet/send-amount-fixed total-amount]) + (str " " token-symbol))] [rn/view {:key (str (if receiver? "to" "from") "-" chain-id) :style {:margin-top (if (pos? index) 11 7.5)}} [quo/network-bridge {:amount (if (= network-value-type :not-available) (i18n/label :t/not-available) - (str total-amount " " token-symbol)) + amount-formatted) :network (network-utils/id->network chain-id) :status status :on-press #(when (not loading-routes?) diff --git a/src/status_im/contexts/wallet/send/transaction_confirmation/view.cljs b/src/status_im/contexts/wallet/send/transaction_confirmation/view.cljs index 3bbf35e4d6..e467ff82f8 100644 --- a/src/status_im/contexts/wallet/send/transaction_confirmation/view.cljs +++ b/src/status_im/contexts/wallet/send/transaction_confirmation/view.cljs @@ -165,10 +165,11 @@ :subtitle subtitle}]) (defn- transaction-details - [{:keys [estimated-time-min max-fees token-display-name amount to-network route + [{:keys [estimated-time-min max-fees to-network route transaction-type]}] (let [route-loaded? (and route (seq route)) - loading-suggested-routes? (rf/sub [:wallet/wallet-send-loading-suggested-routes?])] + loading-suggested-routes? (rf/sub [:wallet/wallet-send-loading-suggested-routes?]) + amount (rf/sub [:wallet/send-total-amount-formatted])] [rn/view {:style (style/details-container {:loading-suggested-routes? loading-suggested-routes? @@ -189,7 +190,7 @@ (i18n/label :t/bridged-to {:network (:abbreviated-name to-network)}) (i18n/label :t/recipient-gets)) - :subtitle (str amount " " token-display-name)}]] + :subtitle amount}]] :else [quo/text {:style {:align-self :center}} (i18n/label :t/no-routes-found-confirmation)])])) @@ -223,7 +224,6 @@ bridge-to-chain-id])) loading-suggested-routes? (rf/sub [:wallet/wallet-send-loading-suggested-routes?]) - total-amount-receiver (rf/sub [:wallet/total-amount true]) from-account-props {:customization-color account-color :size 32 :emoji (:emoji account) @@ -248,8 +248,6 @@ [transaction-details {:estimated-time-min estimated-time-min :max-fees fee-formatted - :token-display-name token-symbol - :amount total-amount-receiver :to-network bridge-to-network :theme theme :route route diff --git a/src/status_im/contexts/wallet/send/utils.cljs b/src/status_im/contexts/wallet/send/utils.cljs index 742ee343d3..7ac6c3ec47 100644 --- a/src/status_im/contexts/wallet/send/utils.cljs +++ b/src/status_im/contexts/wallet/send/utils.cljs @@ -1,9 +1,7 @@ (ns status-im.contexts.wallet.send.utils (:require - [native-module.core :as native-module] [status-im.constants :as constants] [status-im.contexts.wallet.common.utils.networks :as network-utils] - [utils.hex :as utils.hex] [utils.money :as money])) (defn amount-in-hex @@ -43,21 +41,67 @@ [route] (money/wei->ether (reduce money/add (map calculate-gas-fee route)))) +(defn- path-amount-in + [path] + (-> path :amount-in money/from-hex)) + +(defn- path-amount-out + [path] + (if (= (:bridge-name path) constants/bridge-name-hop) + (let [{:keys [token-fees bonder-fees amount-in]} path] + (-> amount-in + money/from-hex + (money/sub token-fees) + (money/add bonder-fees))) + (-> path :amount-out money/from-hex))) + +(defn- convert-wei-to-eth + [amount native-token? token-decimals] + (money/with-precision + (if native-token? + (money/wei->ether amount) + (money/token->unit amount token-decimals)) + constants/token-display-precision)) + (defn network-amounts-by-chain [{:keys [route token-decimals native-token? receiver?]}] (reduce (fn [acc path] - (let [amount-hex (if receiver? (:amount-out path) (:amount-in path)) - amount-units (native-module/hex-to-number - (utils.hex/normalize-hex amount-hex)) - amount (money/with-precision - (if native-token? - (money/wei->ether amount-units) - (money/token->unit amount-units - token-decimals)) - 6) - chain-id (if receiver? (get-in path [:to :chain-id]) (get-in path [:from :chain-id]))] - (update acc chain-id money/add amount))) + (let [amount (if receiver? + (path-amount-out path) + (path-amount-in path)) + chain-id (if receiver? + (get-in path [:to :chain-id]) + (get-in path [:from :chain-id]))] + (as-> amount $ + (convert-wei-to-eth $ native-token? token-decimals) + (update acc chain-id money/add $)))) + {} + route)) + +(defn path-estimated-received + "Calculates the (`bignumber`) estimated received token amount. For + bridge transactions, the amount is the difference between the + `amount-in` and the `token-fees`." + [path] + (if (-> path :bridge-name (= constants/bridge-name-hop)) + (-> path + :amount-in + money/from-hex + (money/sub (:token-fees path))) + (-> path + :amount-out + money/from-hex))) + +(defn estimated-received-by-chain + [route token-decimals native-token?] + (reduce + (fn [acc path] + (let [chain-id (get-in path [:to :chain-id]) + estimated-received (-> path + path-estimated-received + (convert-wei-to-eth native-token? token-decimals))] + (update acc chain-id money/add estimated-received))) {} route)) diff --git a/src/status_im/contexts/wallet/send/utils_test.cljs b/src/status_im/contexts/wallet/send/utils_test.cljs index a5e37c21a0..24cd5e541f 100644 --- a/src/status_im/contexts/wallet/send/utils_test.cljs +++ b/src/status_im/contexts/wallet/send/utils_test.cljs @@ -79,9 +79,91 @@ :receiver? receiver?}) expected {1 (money/bignumber "2") 10 (money/bignumber "2")}] + (doseq [[chain-id exp-value] expected] + (is (money/equal-to (get result chain-id) exp-value))))) + + (testing "Correctly calculates network (out) amounts for bridge transaction" + (let [route [{:bridge-name "Hop" + :amount-in "0xde0b6b3a7640000" + :bonder-fees (money/bignumber "200000000000000") + :token-fees (money/bignumber "230000000000000") + :to {:chain-id 1}} + {:bridge-name "Hop" + :bonder-fees (money/bignumber "300000000000000") + :token-fees (money/bignumber "410000000000000") + :amount-in "0xde0b6b3a7640000" + :to {:chain-id 10}}] + token-decimals 6 + native-token? true + receiver? true + result (utils/network-amounts-by-chain {:route route + :token-decimals token-decimals + :native-token? native-token? + :receiver? receiver?}) + expected {1 (money/bignumber "0.99997") + 10 (money/bignumber "0.99989")}] (doseq [[chain-id exp-value] expected] (is (money/equal-to (get result chain-id) exp-value)))))) +(deftest estimated-received-by-chain-test + (testing "Correctly calculates the bridge estimated received amount" + (let [chain-id 1 + route [{:bridge-name "Hop" + :amount-in "0xde0b6b3a7640000" + :token-fees (money/bignumber "230000000000000") + :to {:chain-id 1}}] + token-decimals 18 + native-token? true + result (utils/estimated-received-by-chain route token-decimals native-token?) + expected (money/bignumber "0.99977")] + (is (money/equal-to (get result chain-id) expected)))) + + (testing "Correctly calculates the estimated received amount with native token" + (let [chain-id 1 + route [{:amount-out "0x1bc16d674ec80000" + :to {:chain-id chain-id}}] + token-decimals 18 + native-token? true + result (utils/estimated-received-by-chain route token-decimals native-token?) + expected (money/bignumber "2")] + (is (money/equal-to (get result chain-id) expected)))) + + (testing "Correctly calculates the estimated received amount with non-native token" + (let [chain-id 10 + route [{:amount-out "0x1bc16d674ec80000" + :to {:chain-id chain-id}}] + token-decimals 18 + native-token? false + result (utils/estimated-received-by-chain route token-decimals native-token?) + expected (money/bignumber "2")] + (is (money/equal-to (get result chain-id) expected)))) + + (testing + "Correctly calculates the estimated received amount with multiple routes on different networks" + (let [route [{:amount-out "0x1bc16d674ec80000" + :to {:chain-id 1}} + {:amount-out "0xde0b6b3a7640000" + :to {:chain-id 10}}] + token-decimals 18 + native-token? false + result (utils/estimated-received-by-chain route token-decimals native-token?) + expected {10 (money/bignumber "1") + 1 (money/bignumber "2")}] + (doseq [[chain-id exp-value] expected] + (is (money/equal-to (get result chain-id) exp-value))))) + + (testing "Correctly calculates the estimated received amount with multiple routes on the same network" + (let [chain-id 10 + route [{:amount-out "0x1bc16d674ec80000" + :to {:chain-id chain-id}} + {:amount-out "0x1bc16d674ec80000" + :to {:chain-id chain-id}}] + token-decimals 18 + native-token? false + result (utils/estimated-received-by-chain route token-decimals native-token?) + expected (money/bignumber "4")] + (is (money/equal-to (get result chain-id) expected))))) + (deftest network-values-for-ui-test (testing "Sanitizes values correctly for display" (let [amounts {1 (money/bignumber "0") diff --git a/src/status_im/subs/wallet/networks.cljs b/src/status_im/subs/wallet/networks.cljs index 201162219b..28184a0756 100644 --- a/src/status_im/subs/wallet/networks.cljs +++ b/src/status_im/subs/wallet/networks.cljs @@ -1,7 +1,8 @@ (ns status-im.subs.wallet.networks (:require [re-frame.core :as re-frame] [status-im.contexts.wallet.common.utils.networks :as network-utils] - [utils.money :as money])) + [utils.money :as money] + [utils.number :as number])) (def max-network-prefixes 2) @@ -62,28 +63,18 @@ (re-frame/reg-sub :wallet/network-values :<- [:wallet/wallet-send] - (fn [{:keys [from-values-by-chain to-values-by-chain token-display-name] :as send-data} [_ to-values?]] + :<- [:wallet/send-display-token-decimals] + (fn [[{:keys [from-values-by-chain to-values-by-chain token-display-name] :as send-data} token-decimals] + [_ to-values?]] (let [network-values (if to-values? to-values-by-chain from-values-by-chain) token-symbol (or token-display-name (-> send-data :token :symbol))] (reduce-kv (fn [acc chain-id amount] - (let [network-name (network-utils/id->network chain-id)] + (let [network-name (network-utils/id->network chain-id) + amount-fixed (number/to-fixed (money/->bignumber amount) token-decimals)] (assoc acc (if (= network-name :mainnet) :ethereum network-name) - {:amount amount :token-symbol token-symbol}))) + {:amount amount-fixed :token-symbol token-symbol}))) {} network-values)))) - -(re-frame/reg-sub - :wallet/total-amount - :<- [:wallet/wallet-send] - (fn [{:keys [from-values-by-chain to-values-by-chain]} [_ to-values?]] - (let [network-values (if to-values? to-values-by-chain from-values-by-chain)] - (reduce - (fn [acc amount] - (if (money/bignumber? amount) - (money/add acc amount) - acc)) - (money/bignumber 0) - (vals network-values))))) diff --git a/src/status_im/subs/wallet/networks_test.cljs b/src/status_im/subs/wallet/networks_test.cljs index e69fac3c87..bc76fde5f2 100644 --- a/src/status_im/subs/wallet/networks_test.cljs +++ b/src/status_im/subs/wallet/networks_test.cljs @@ -110,7 +110,7 @@ :to-values-by-chain {42161 100} :token-display-name "ETH"}) (is - (match? {:ethereum {:amount 100 :token-symbol "ETH"}} (rf/sub [sub-name false])))) + (match? {:ethereum {:amount "100" :token-symbol "ETH"}} (rf/sub [sub-name false])))) (testing "network values for the to account are returned correctly" (swap! rf-db/app-db assoc-in @@ -119,4 +119,4 @@ :to-values-by-chain {42161 100} :token-display-name "ARB1"}) (is - (match? {:arbitrum {:amount 100 :token-symbol "ARB1"}} (rf/sub [sub-name true]))))) + (match? {:arbitrum {:amount "100" :token-symbol "ARB1"}} (rf/sub [sub-name true]))))) diff --git a/src/status_im/subs/wallet/send.cljs b/src/status_im/subs/wallet/send.cljs index aea21aa7bb..0458839c76 100644 --- a/src/status_im/subs/wallet/send.cljs +++ b/src/status_im/subs/wallet/send.cljs @@ -1,9 +1,12 @@ (ns status-im.subs.wallet.send (:require [re-frame.core :as rf] - [status-im.contexts.wallet.common.activity-tab.constants :as constants] + [status-im.constants :as constants] + [status-im.contexts.wallet.common.activity-tab.constants :as activity-constants] + [status-im.contexts.wallet.common.utils :as common-utils] [status-im.contexts.wallet.send.utils :as send-utils] - [utils.number])) + [utils.money :as money] + [utils.number :as number])) (rf/reg-sub :wallet/send-tab @@ -21,6 +24,16 @@ :<- [:wallet/wallet-send] :-> :recipient) +(rf/reg-sub + :wallet/send-route + :<- [:wallet/wallet-send] + :-> :route) + +(rf/reg-sub + :wallet/send-token + :<- [:wallet/wallet-send] + :-> :token) + (rf/reg-sub :wallet/send-transaction-ids :<- [:wallet/wallet-send] @@ -64,7 +77,7 @@ (->> address-activity (sort :timestamp) (keep (fn [{:keys [activity-type recipient]}] - (when (= constants/wallet-activity-type-send activity-type) + (when (= activity-constants/wallet-activity-type-send activity-type) recipient))) (distinct))))) @@ -91,3 +104,71 @@ (when (not= (:chain-id network) bridge-to-chain-id) (:chain-id network))) networks))) + +(rf/reg-sub + :wallet/send-token-decimals + :<- [:wallet/wallet-send] + (fn [{:keys [token collectible]}] + (if collectible 0 (:decimals token)))) + +(rf/reg-sub + :wallet/send-display-token-decimals + :<- [:wallet/wallet-send] + (fn [{:keys [token collectible]}] + (if collectible + 0 + (-> token + common-utils/token-usd-price + common-utils/one-cent-value + common-utils/calc-max-crypto-decimals + (min constants/min-token-decimals-to-display))))) + +(rf/reg-sub + :wallet/send-native-token? + :<- [:wallet/wallet-send] + (fn [{:keys [token token-display-name]}] + (and token (= token-display-name "ETH")))) + +(rf/reg-sub + :wallet/total-amount + :<- [:wallet/send-route] + :<- [:wallet/send-token-decimals] + :<- [:wallet/send-native-token?] + (fn [[route token-decimals native-token?]] + (let [default-amount (money/bignumber 0)] + (if route + (->> (send-utils/estimated-received-by-chain + route + token-decimals + native-token?) + vals + (reduce money/add default-amount)) + default-amount)))) + +(rf/reg-sub + :wallet/send-total-amount-formatted + :<- [:wallet/total-amount] + :<- [:wallet/send-display-token-decimals] + :<- [:wallet/wallet-send-token-symbol] + (fn [[amount token-decimals token-symbol]] + (-> amount + (number/to-fixed token-decimals) + (str " " token-symbol)))) + +(rf/reg-sub + :wallet/send-amount-fixed + :<- [:wallet/send-display-token-decimals] + (fn [token-decimals [_ amount]] + (number/to-fixed (money/->bignumber amount) token-decimals))) + +(rf/reg-sub + :wallet/bridge-to-network-details + :<- [:wallet/wallet-send] + :<- [:wallet/network-details] + (fn [[{:keys [bridge-to-chain-id]} networks]] + (when bridge-to-chain-id + (some (fn [network] + (when + (= (:chain-id network) bridge-to-chain-id) + network)) + networks)))) diff --git a/src/status_im/subs/wallet/send_test.cljs b/src/status_im/subs/wallet/send_test.cljs index 16714d4af3..99845bf1a4 100644 --- a/src/status_im/subs/wallet/send_test.cljs +++ b/src/status_im/subs/wallet/send_test.cljs @@ -6,8 +6,15 @@ [status-im.subs.root] [status-im.subs.wallet.send] [test-helpers.unit :as h] + [utils.money :as money] [utils.re-frame :as rf])) +(def ^:private token-mock + {:decimals 8 + :symbol "ETH" + :balances-per-chain {1 {:raw-balance "100"}} + :market-values-per-currency {:usd {:price 10000}}}) + (h/deftest-sub :wallet/send-tab [sub-name] (testing "returns active tab for selecting address" @@ -84,3 +91,144 @@ :timestamp 1588464000}}}) (assoc-in [:wallet :current-viewing-account-address] "acc1")))) (is (match? ["acc2" "acc4"] (rf/sub [sub-name]))))) + +(h/deftest-sub :wallet/send-token-decimals + [sub-name] + (testing "returns the decimals for the chosen token" + (swap! rf-db/app-db assoc-in [:wallet :ui :send :token] token-mock) + (is (= 8 (rf/sub [sub-name])))) + + (testing "returns 0 if sending collectibles" + (swap! rf-db/app-db assoc-in [:wallet :ui :send :collectible] {}) + (is (match? 0 (rf/sub [sub-name]))))) + +(h/deftest-sub :wallet/send-display-token-decimals + [sub-name] + (testing "returns the decimals based on the token price for the chosen token" + (swap! rf-db/app-db assoc-in + [:wallet :ui :send :token] + {:symbol "ETH" + :balances-per-chain {1 {:raw-balance "100"}} + :market-values-per-currency {:usd {:price 10000}}}) + (is (match? 6 (rf/sub [sub-name])))) + + (testing "returns 0 if sending collectibles" + (swap! rf-db/app-db assoc-in [:wallet :ui :send :collectible] {}) + (is (match? 0 (rf/sub [sub-name]))))) + +(h/deftest-sub :wallet/send-native-token? + [sub-name] + (testing "returns true if the chosen token is native (ETH)" + (swap! rf-db/app-db + (fn [db] + (-> db + (assoc-in [:wallet :ui :send :token] {}) + (assoc-in [:wallet :ui :send :token-display-name] "ETH")))) + (is (rf/sub [sub-name]))) + + (testing "returns false if the chosen token is not native" + (swap! rf-db/app-db + (swap! rf-db/app-db + (fn [db] + (-> db + (assoc-in [:wallet :ui :send :token] {}) + (assoc-in [:wallet :ui :send :token-display-name] "SNT"))))) + (is (not (rf/sub [sub-name]))))) + +(h/deftest-sub :wallet/total-amount + [sub-name] + (testing "returns the total SEND amount when the route is present" + (swap! rf-db/app-db + (fn [db] + (-> db + (assoc-in [:wallet :ui :send :token] token-mock) + (assoc-in [:wallet :ui :send :token-display-name] "ETH") + (assoc-in [:wallet :ui :send :route] + [{:amount-out "0x1bc16d674ec80000" + :to {:chain-id 1}}])))) + (is (match? (money/bignumber 2) (rf/sub [sub-name])))) + + (testing "returns the total BRIDGE amount when the route is present" + (swap! rf-db/app-db + (fn [db] + (-> db + (assoc-in [:wallet :ui :send :token] token-mock) + (assoc-in [:wallet :ui :send :token-display-name] "ETH") + (assoc-in [:wallet :ui :send :route] + [{:bridge-name "Hop" + :amount-in "0xde0b6b3a7640000" + :token-fees (money/bignumber "230000000000000") + :to {:chain-id 1}}])))) + (is (match? (money/bignumber 0.99977) (rf/sub [sub-name])))) + + (testing "returns the default total amount (0) when the route is not present" + (swap! rf-db/app-db + (fn [db] + (-> db + (assoc-in [:wallet :ui :send :token] token-mock) + (assoc-in [:wallet :ui :send :token-display-name] "ETH")))) + (is (match? (money/bignumber 0) (rf/sub [sub-name]))))) + +(h/deftest-sub :wallet/send-total-amount-formatted + [sub-name] + (testing "returns the formatted total amount" + (swap! rf-db/app-db + (fn [db] + (-> db + (assoc-in [:wallet :ui :send :token] token-mock) + (assoc-in [:wallet :ui :send :token-display-name] "ETH") + (assoc-in [:wallet :ui :send :route] + [{:amount-out "0x2b139f68a611c00" ;; 193999990000000000 + :to {:chain-id 1}}])))) + (is (match? "0.194 ETH" (rf/sub [sub-name]))))) + +(h/deftest-sub :wallet/send-amount-fixed + [sub-name] + (testing "returns the fixed value when the amount is a string" + (swap! rf-db/app-db + (fn [db] + (-> db + (assoc-in [:wallet :ui :send :token] token-mock)))) + (is (match? "2" (rf/sub [sub-name "1.9999999"])))) + + (testing "returns the fixed value when the amount is a number" + (swap! rf-db/app-db + (fn [db] + (-> db + (assoc-in [:wallet :ui :send :token] token-mock)))) + (is (match? "2" (rf/sub [sub-name 1.9999999])))) + + (testing "returns the fixed value when the amount is a bignumber" + (swap! rf-db/app-db + (fn [db] + (-> db + (assoc-in [:wallet :ui :send :token] token-mock)))) + (is (match? "2" (rf/sub [sub-name (money/bignumber "1.9999999")]))))) + +(def ^:private mainnet-network + {:short-name "eth" + :network-name :mainnet + :abbreviated-name "Eth." + :full-name "Mainnet" + :chain-id 1 + :related-chain-id 1 + :layer 1}) + +(h/deftest-sub :wallet/bridge-to-network-details + [sub-name] + (testing "returns the network details for bridge transactions" + (swap! rf-db/app-db + (fn [db] + (-> db + (assoc-in [:wallet :networks] {:prod [mainnet-network]}) + (assoc-in [:profile/profile :test-networks-enabled?] false) + (assoc-in [:wallet :ui :send] {:bridge-to-chain-id 1})))) + (is (match? mainnet-network (rf/sub [sub-name])))) + + (testing "returns nil if not on the bridge flow" + (swap! rf-db/app-db + (fn [db] + (-> db + (assoc-in [:wallet :networks] {:prod [mainnet-network]}) + (assoc-in [:profile/profile :test-networks-enabled?] false)))) + (is (match? nil (rf/sub [sub-name]))))) diff --git a/src/status_im/subs/wallet/wallet.cljs b/src/status_im/subs/wallet/wallet.cljs index 19ed8ddbea..9c906f7fca 100644 --- a/src/status_im/subs/wallet/wallet.cljs +++ b/src/status_im/subs/wallet/wallet.cljs @@ -154,7 +154,8 @@ (rf/reg-sub :wallet/wallet-send-token-symbol :<- [:wallet/wallet-send] - :-> :token-symbol) + (fn [{:keys [token-symbol token]}] + (or token-symbol (:symbol token)))) (rf/reg-sub :wallet/wallet-send-disabled-from-chain-ids diff --git a/translations/en.json b/translations/en.json index 9a90e08e3b..1ea91b0e91 100644 --- a/translations/en.json +++ b/translations/en.json @@ -2637,6 +2637,8 @@ "unblock": "Unblock", "unblock-contact": "Unblock this user", "unblocking-a-user-message": "After unblocking {{username}}, you will be able to connect with them as a contact and see their messages in group chats and communities.", + "understanding-bonder-fees-description": "The bridged amount can be lower than the amount you send due to Bonder fees. These fees cover the cost of providing liquidity and managing risks associated with your transfer. The fee ranges from 0.05% to 0.30%, depending on factors such as transaction volume and route specifics.", + "understanding-bonder-fees-title": "Understanding Bonder fees", "undo": "Undo", "unique-identifiers": "Unique profile identifiers", "universally-unique-identifiers-of-device": "Universally Unique Identifiers of device",