refactor: suggested routes rendering (#19768)

Signed-off-by: Brian Sztamfater <brian@status.im>
This commit is contained in:
Brian Sztamfater 2024-05-03 13:18:14 -03:00 committed by GitHub
parent d8d1e030e4
commit 355e144ef7
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
19 changed files with 865 additions and 443 deletions

View File

@ -30,7 +30,7 @@
[{:keys [network status amount container-style on-press] :as args}] [{:keys [network status amount container-style on-press] :as args}]
(let [theme (quo.theme/use-theme)] (let [theme (quo.theme/use-theme)]
(if (= status :add) (if (= status :add)
[network-bridge-add args] [network-bridge-add (assoc args :theme theme)]
[rn/pressable [rn/pressable
{:style (merge (style/container network status theme) container-style) {:style (merge (style/container network status theme) container-style)
:accessible true :accessible true

View File

@ -25,10 +25,10 @@
(def link-1x-container (def link-1x-container
{:flex 1 {:flex 1
:height 58 :height 57
:justify-content :center}) :justify-content :center})
(def link-2x-container (def link-2x-container
{:flex 1 {:flex 1
:height 114 :height 112
:justify-content :center}) :justify-content :center})

View File

@ -24,33 +24,50 @@
:strokeWidth "1"}]]) :strokeWidth "1"}]])
(defn- line (defn- line
[stroke width] [{:keys [stroke-color source-color destination-color width theme]}]
[svg/svg [svg/svg
{:height "10" {:height "10"
:width "100%" :width "100%"
:view-box (str "0 0 " width " 10")} :view-box (str "0 0 " width " 10")}
[svg/path [svg/path
{:d (str "M0,5 L" width ",5") {:d (str "M0,5 L" width ",5")
:stroke stroke :stroke stroke-color
:stroke-width "1"}]]) :stroke-width "1"}]
[svg/defs
[svg/linear-gradient
{:id "gradient"
:x1 "0%"
:x2 "100%"
:y1 "0%"
:y2 "0%"
:gradient-units "objectBoundingBox"}
[svg/stop {:offset "0%" :stop-color (colors/resolve-color source-color theme)}]
[svg/stop {:offset "100%" :stop-color (colors/resolve-color destination-color theme)}]]]])
(defn link-linear (defn link-linear
[{:keys [source]}] [{:keys [source destination]}]
(let [theme (quo.theme/use-theme) (let [theme (quo.theme/use-theme)
[container-width [container-width
set-container-width] (rn/use-state 100) set-container-width] (rn/use-state 100)
stroke-color (colors/resolve-color source theme) stroke-color "url(#gradient)"
source-color (colors/resolve-color source theme)
destination-color (colors/resolve-color destination theme)
fill-color (colors/theme-colors colors/white colors/neutral-90 theme) fill-color (colors/theme-colors colors/white colors/neutral-90 theme)
on-layout (rn/use-callback #(set-container-width on-layout (rn/use-callback #(set-container-width
(oget % :nativeEvent :layout :width)))] (oget % :nativeEvent :layout :width)))]
[rn/view [rn/view
{:style style/link-linear-container {:style style/link-linear-container
:on-layout on-layout} :on-layout on-layout}
[line stroke-color container-width] [line
{:stroke-color stroke-color
:source-color source-color
:destination-color destination-color
:width container-width
:theme theme}]
[rn/view {:style style/left-circle-container} [rn/view {:style style/left-circle-container}
[circle fill-color stroke-color]] [circle fill-color source-color]]
[rn/view {:style style/right-circle-container} [rn/view {:style style/right-circle-container}
[circle fill-color stroke-color]]])) [circle fill-color destination-color]]]))
(defn link-1x (defn link-1x
[{:keys [source destination]}] [{:keys [source destination]}]

View File

@ -263,3 +263,11 @@
{} {}
tokens) tokens)
(update-vals #(prettify-balance currency-symbol %)))) (update-vals #(prettify-balance currency-symbol %))))
(defn format-token-id
[token collectible]
(if token
(:symbol token)
(str (get-in collectible [:id :contract-id :address])
":"
(get-in collectible [:id :token-id]))))

View File

@ -1,34 +0,0 @@
(ns status-im.contexts.wallet.common.utils.send
(:require [clojure.string :as string]
[utils.money :as money]))
(defn calculate-gas-fee
[data]
(let [gas-amount (money/bignumber (get data :gas-amount))
gas-fees (get data :gas-fees)
eip1559-enabled? (get gas-fees :eip-1559-enabled)
optimal-price-gwei (money/bignumber (if eip1559-enabled?
(get gas-fees :max-fee-per-gas-medium)
(get gas-fees :gas-price)))
total-gas-fee-wei (money/mul (money/->wei :gwei optimal-price-gwei) gas-amount)
l1-fee-wei (money/->wei :gwei (get gas-fees :l-1-gas-fee))]
(money/add total-gas-fee-wei l1-fee-wei)))
(defn calculate-full-route-gas-fee
"Sums all the routes fees in wei and then convert the total value to ether"
[route]
(money/wei->ether (reduce money/add (map calculate-gas-fee route))))
(defn find-affordable-networks
[{:keys [balances-per-chain input-value selected-networks disabled-chain-ids]}]
(let [input-value (if (string/blank? input-value) 0 input-value)]
(->> balances-per-chain
(filter (fn [[_
{:keys [balance chain-id]
:or {balance 0}}]]
(and
(money/greater-than-or-equals (money/bignumber balance)
(money/bignumber input-value))
(some #(= % chain-id) selected-networks)
(not-any? #(= % chain-id) disabled-chain-ids))))
(map first))))

View File

@ -1,116 +0,0 @@
(ns status-im.contexts.wallet.common.utils.send-test
(:require [cljs.test :refer [deftest is testing]]
[status-im.contexts.wallet.common.utils.send :as utils]
[utils.money :as money]))
(deftest test-calculate-gas-fee
(testing "EIP-1559 transaction without L1 fee"
(let [data {:gas-amount "23487"
:gas-fees {:max-fee-per-gas-medium "2.259274911"
:eip-1559-enabled true
:l-1-gas-fee "0"}}
expected-result (money/bignumber "53063589834657")] ; This is in Wei
(is (money/equal-to (utils/calculate-gas-fee data)
expected-result))))
(testing "EIP-1559 transaction with L1 fee of 60,000 Gwei"
(let [data {:gas-amount "23487"
:gas-fees {:max-fee-per-gas-medium "2.259274911"
:eip-1559-enabled true
:l-1-gas-fee "60000"}}
expected-result (money/bignumber "113063589834657")] ; Added 60,000 Gwei in Wei to the
; previous result
(is (money/equal-to (utils/calculate-gas-fee data)
expected-result))))
(testing "Non-EIP-1559 transaction with specified gas price"
(let [data {:gas-amount "23487"
:gas-fees {:gas-price "2.872721089"
:eip-1559-enabled false
:l-1-gas-fee "0"}}
expected-result (money/bignumber "67471600217343")] ; This is in Wei, for the specified
; gas amount and price
(is (money/equal-to (utils/calculate-gas-fee data)
expected-result)))))
(deftest test-calculate-full-route-gas-fee
(testing "Route with a single EIP-1559 transaction, no L1 fees"
(let [route [{:gas-amount "23487"
:gas-fees {:max-fee-per-gas-medium "2.259274911"
:eip-1559-enabled true
:l-1-gas-fee "0"}}]
expected-result (money/bignumber "0.000053063589834657")] ; The Wei amount for the
; transaction, converted to
; Ether
(is (money/equal-to (utils/calculate-full-route-gas-fee route)
expected-result))))
(testing "Route with two EIP-1559 transactions, no L1 fees"
(let [route [{:gas-amount "23487"
:gas-fees {:max-fee-per-gas-medium "2.259274911"
:eip-1559-enabled true
:l-1-gas-fee "0"}}
{:gas-amount "23487"
:gas-fees {:max-fee-per-gas-medium "2.259274911"
:eip-1559-enabled true
:l-1-gas-fee "0"}}]
expected-result (money/bignumber "0.000106127179669314")] ; Sum of both transactions' Wei
; amounts, converted to Ether
(is (money/equal-to (utils/calculate-full-route-gas-fee route)
expected-result))))
(testing "Route with two EIP-1559 transactions, one with L1 fee of 60,000 Gwei"
(let [route [{:gas-amount "23487"
:gas-fees {:max-fee-per-gas-medium "2.259274911"
:eip-1559-enabled true
:l-1-gas-fee "0"}}
{:gas-amount "23487"
:gas-fees {:max-fee-per-gas-medium "2.259274911"
:eip-1559-enabled true
:l-1-gas-fee "60000"}}]
expected-result (money/bignumber "0.000166127179669314")] ; Added 60,000 Gwei in Wei to
; the previous total and
; converted to Ether
(is (money/equal-to (utils/calculate-full-route-gas-fee route)
expected-result)))))
(deftest test-find-affordable-networks
(testing "All networks affordable and selected, none disabled"
(let [balances-per-chain {"1" {:balance "50.0" :chain-id "1"}
"2" {:balance "40.0" :chain-id "2"}}
input-value 20
selected-networks ["1" "2"]
disabled-chain-ids []
expected ["1" "2"]]
(is (= (set (utils/find-affordable-networks {:balances-per-chain balances-per-chain
:input-value input-value
:selected-networks selected-networks
:disabled-chain-ids disabled-chain-ids}))
(set expected)))))
(testing "No networks affordable"
(let [balances-per-chain {"1" {:balance "5.0" :chain-id "1"}
"2" {:balance "1.0" :chain-id "2"}}
input-value 10
selected-networks ["1" "2"]
disabled-chain-ids []
expected []]
(is (= (set (utils/find-affordable-networks {:balances-per-chain balances-per-chain
:input-value input-value
:selected-networks selected-networks
:disabled-chain-ids disabled-chain-ids}))
(set expected)))))
(testing "Selected networks subset, with some disabled"
(let [balances-per-chain {"1" {:balance "100.0" :chain-id "1"}
"2" {:balance "50.0" :chain-id "2"}
"3" {:balance "20.0" :chain-id "3"}}
input-value 15
selected-networks ["1" "2" "3"]
disabled-chain-ids ["2"]
expected ["1" "3"]]
(is (= (set (utils/find-affordable-networks {:balances-per-chain balances-per-chain
:input-value input-value
:selected-networks selected-networks
:disabled-chain-ids disabled-chain-ids}))
(set expected))))))

View File

@ -31,8 +31,13 @@
token (get-in db [:wallet :ui :send :token]) token (get-in db [:wallet :ui :send :token])
collectible (get-in db [:wallet :ui :send :collectible]) collectible (get-in db [:wallet :ui :send :collectible])
token-display-name (get-in db [:wallet :ui :send :token-display-name]) token-display-name (get-in db [:wallet :ui :send :token-display-name])
receiver-networks (get-in db [:wallet :ui :send :receiver-networks])
receiver-network-values (get-in db [:wallet :ui :send :receiver-network-values])
sender-network-values (get-in db [:wallet :ui :send :sender-network-values])
disabled-from-chain-ids (or (get-in db [:wallet :ui :send :disabled-from-chain-ids]) [])
token-decimals (if collectible 0 (:decimals token)) token-decimals (if collectible 0 (:decimals token))
native-token? (and token (= token-display-name "ETH")) native-token? (and token (= token-display-name "ETH"))
routes-available? (pos? (count chosen-route))
from-network-amounts-by-chain (send-utils/network-amounts-by-chain {:route chosen-route from-network-amounts-by-chain (send-utils/network-amounts-by-chain {:route chosen-route
:token-decimals :token-decimals
token-decimals token-decimals
@ -46,19 +51,54 @@
:native-token? :native-token?
native-token? native-token?
:to? true}) :to? true})
to-network-values-for-ui (send-utils/network-values-for-ui to-network-amounts-by-chain)] to-network-values-for-ui (send-utils/network-values-for-ui to-network-amounts-by-chain)
sender-network-values (if routes-available?
(send-utils/network-amounts from-network-values-for-ui
disabled-from-chain-ids
receiver-networks
false)
(send-utils/reset-network-amounts-to-zero
sender-network-values))
receiver-network-values (if routes-available?
(send-utils/network-amounts to-network-values-for-ui
disabled-from-chain-ids
receiver-networks
true)
(send-utils/reset-network-amounts-to-zero
receiver-network-values))
network-links (when routes-available?
(send-utils/network-links chosen-route
sender-network-values
receiver-network-values))]
{:db (-> db {:db (-> db
(assoc-in [:wallet :ui :send :suggested-routes] suggested-routes-data) (assoc-in [:wallet :ui :send :suggested-routes] suggested-routes-data)
(assoc-in [:wallet :ui :send :route] chosen-route) (assoc-in [:wallet :ui :send :route] chosen-route)
(assoc-in [:wallet :ui :send :from-values-by-chain] from-network-values-for-ui) (assoc-in [:wallet :ui :send :from-values-by-chain] from-network-values-for-ui)
(assoc-in [:wallet :ui :send :to-values-by-chain] to-network-values-for-ui) (assoc-in [:wallet :ui :send :to-values-by-chain] to-network-values-for-ui)
(assoc-in [:wallet :ui :send :sender-network-values] sender-network-values)
(assoc-in [:wallet :ui :send :receiver-network-values] receiver-network-values)
(assoc-in [:wallet :ui :send :network-links] network-links)
(assoc-in [:wallet :ui :send :loading-suggested-routes?] false))})))) (assoc-in [:wallet :ui :send :loading-suggested-routes?] false))}))))
(rf/reg-event-fx :wallet/suggested-routes-error (rf/reg-event-fx :wallet/suggested-routes-error
(fn [{:keys [db]} [_error]] (fn [{:keys [db]} [error]]
{:db (-> db (let [cleaned-sender-network-values (-> (get-in db [:wallet :ui :send :sender-network-values])
(update-in [:wallet :ui :send] dissoc :suggested-routes :route) (send-utils/reset-network-amounts-to-zero))
(assoc-in [:wallet :ui :send :loading-suggested-routes?] false))})) cleaned-receiver-network-values (-> (get-in db [:wallet :ui :send :receiver-network-values])
(send-utils/reset-network-amounts-to-zero))]
{:db (-> db
(update-in [:wallet :ui :send]
dissoc
:route)
(assoc-in [:wallet :ui :send :sender-network-values] cleaned-sender-network-values)
(assoc-in [:wallet :ui :send :receiver-network-values] cleaned-receiver-network-values)
(assoc-in [:wallet :ui :send :loading-suggested-routes?] false)
(assoc-in [:wallet :ui :send :suggested-routes] {:best []}))
:fx [[:dispatch
[:toasts/upsert
{:id :send-transaction-error
:type :negative
:text (:message error)}]]]})))
(rf/reg-event-fx :wallet/clean-suggested-routes (rf/reg-event-fx :wallet/clean-suggested-routes
(fn [{:keys [db]}] (fn [{:keys [db]}]
@ -69,6 +109,9 @@
:route :route
:from-values-by-chain :from-values-by-chain
:to-values-by-chain :to-values-by-chain
:sender-network-values
:receiver-network-values
:network-links
:loading-suggested-routes? :loading-suggested-routes?
:suggested-routes-call-timestamp)})) :suggested-routes-call-timestamp)}))
@ -203,56 +246,74 @@
(rf/reg-event-fx :wallet/get-suggested-routes (rf/reg-event-fx :wallet/get-suggested-routes
(fn [{:keys [db now]} [{:keys [amount]}]] (fn [{:keys [db now]} [{:keys [amount]}]]
(let [wallet-address (get-in db [:wallet :current-viewing-account-address]) (let [wallet-address (get-in db [:wallet :current-viewing-account-address])
token (get-in db [:wallet :ui :send :token]) token (get-in db [:wallet :ui :send :token])
transaction-type (get-in db [:wallet :ui :send :tx-type]) transaction-type (get-in db [:wallet :ui :send :tx-type])
collectible (get-in db [:wallet :ui :send :collectible]) collectible (get-in db [:wallet :ui :send :collectible])
to-address (get-in db [:wallet :ui :send :to-address]) to-address (get-in db [:wallet :ui :send :to-address])
receiver-networks (get-in db [:wallet :ui :send :receiver-networks]) receiver-networks (get-in db [:wallet :ui :send :receiver-networks])
disabled-from-chain-ids (or (get-in db [:wallet :ui :send :disabled-from-chain-ids]) []) disabled-from-chain-ids (or (get-in db [:wallet :ui :send :disabled-from-chain-ids]) [])
test-networks-enabled? (get-in db [:profile/profile :test-networks-enabled?]) test-networks-enabled? (get-in db [:profile/profile :test-networks-enabled?])
networks ((if test-networks-enabled? :test :prod) networks ((if test-networks-enabled? :test :prod)
(get-in db [:wallet :networks])) (get-in db [:wallet :networks]))
network-chain-ids (map :chain-id networks) network-chain-ids (map :chain-id networks)
bridge-to-chain-id (get-in db [:wallet :ui :send :bridge-to-chain-id]) bridge-to-chain-id (get-in db [:wallet :ui :send :bridge-to-chain-id])
token-decimal (when token (:decimals token)) token-decimal (when token (:decimals token))
token-id (if token token-id (utils/format-token-id token collectible)
(:symbol token) to-token-id ""
(str (get-in collectible [:id :contract-id :address]) network-preferences (if token [] [(get-in collectible [:id :contract-id :chain-id])])
":" gas-rates constants/gas-rate-medium
(get-in collectible [:id :token-id]))) amount-in (send-utils/amount-in-hex amount (if token token-decimal 0))
to-token-id "" from-address wallet-address
network-preferences (if token [] [(get-in collectible [:id :contract-id :chain-id])])
gas-rates constants/gas-rate-medium
amount-in (send-utils/amount-in-hex amount (if token token-decimal 0))
from-address wallet-address
disabled-from-chain-ids disabled-from-chain-ids disabled-from-chain-ids disabled-from-chain-ids
disabled-to-chain-ids (if (= transaction-type :bridge) disabled-to-chain-ids (if (= transaction-type :bridge)
(filter #(not= % bridge-to-chain-id) network-chain-ids) (filter #(not= % bridge-to-chain-id) network-chain-ids)
(filter (fn [chain-id] (filter (fn [chain-id]
(not (some #(= chain-id %) (not (some #(= chain-id %)
receiver-networks))) receiver-networks)))
network-chain-ids)) network-chain-ids))
from-locked-amount {} from-locked-amount {}
transaction-type-param (case transaction-type transaction-type-param (case transaction-type
:collectible constants/send-type-erc-721-transfer :collectible constants/send-type-erc-721-transfer
:bridge constants/send-type-bridge :bridge constants/send-type-bridge
constants/send-type-transfer) constants/send-type-transfer)
request-params [transaction-type-param balances-per-chain (when token (:balances-per-chain token))
from-address token-available-networks-for-suggested-routes
to-address (when token
amount-in (send-utils/token-available-networks-for-suggested-routes {:balances-per-chain
token-id balances-per-chain
to-token-id :disabled-chain-ids
disabled-from-chain-ids}))
sender-network-values (when token-available-networks-for-suggested-routes
(send-utils/loading-network-amounts
token-available-networks-for-suggested-routes
disabled-from-chain-ids disabled-from-chain-ids
disabled-to-chain-ids receiver-networks
network-preferences false))
gas-rates receiver-network-values (when token-available-networks-for-suggested-routes
from-locked-amount]] (send-utils/loading-network-amounts
token-available-networks-for-suggested-routes
disabled-from-chain-ids
receiver-networks
true))
request-params [transaction-type-param
from-address
to-address
amount-in
token-id
to-token-id
disabled-from-chain-ids
disabled-to-chain-ids
network-preferences
gas-rates
from-locked-amount]]
{:db (-> db {:db (-> db
(assoc-in [:wallet :ui :send :amount] amount) (assoc-in [:wallet :ui :send :amount] amount)
(assoc-in [:wallet :ui :send :loading-suggested-routes?] true) (assoc-in [:wallet :ui :send :loading-suggested-routes?] true)
(assoc-in [:wallet :ui :send :suggested-routes-call-timestamp] now)) (assoc-in [:wallet :ui :send :sender-network-values] sender-network-values)
(assoc-in [:wallet :ui :send :receiver-network-values] receiver-network-values)
(assoc-in [:wallet :ui :send :suggested-routes-call-timestamp] now)
(update-in [:wallet :ui :send] dissoc :network-links))
:json-rpc/call [{:method "wallet_getSuggestedRoutes" :json-rpc/call [{:method "wallet_getSuggestedRoutes"
:params request-params :params request-params
:on-success (fn [suggested-routes] :on-success (fn [suggested-routes]

View File

@ -63,7 +63,7 @@
:gas-fees {:base-fee "32.325296406" :gas-fees {:base-fee "32.325296406"
:max-priority-fee-per-gas "0.011000001" :max-priority-fee-per-gas "0.011000001"
:eip1559-enabled true}}] :eip1559-enabled true}}]
:wallet/wallet-send-suggested-routes {:candidates []} :wallet/wallet-send-suggested-routes nil
:wallet/wallet-send-receiver-networks [1] :wallet/wallet-send-receiver-networks [1]
:view-id :screen/wallet.send-input-amount :view-id :screen/wallet.send-input-amount
:wallet/wallet-send-to-address "0x04371e2d9d66b82f056bc128064" :wallet/wallet-send-to-address "0x04371e2d9d66b82f056bc128064"
@ -73,7 +73,10 @@
:market-values-per-currency {:usd {:price 10}}} :market-values-per-currency {:usd {:price 10}}}
:wallet/wallet-send-disabled-from-chain-ids [] :wallet/wallet-send-disabled-from-chain-ids []
:wallet/wallet-send-from-values-by-chain {1 (money/bignumber "250")} :wallet/wallet-send-from-values-by-chain {1 (money/bignumber "250")}
:wallet/wallet-send-to-values-by-chain {1 (money/bignumber "250")}}) :wallet/wallet-send-to-values-by-chain {1 (money/bignumber "250")}
:wallet/wallet-send-sender-network-values nil
:wallet/wallet-send-receiver-network-values nil
:wallet/wallet-send-network-links nil})
(h/describe "Send > input amount screen" (h/describe "Send > input amount screen"
(h/setup-restorable-re-frame) (h/setup-restorable-re-frame)

View File

@ -33,3 +33,8 @@
{:flex 1 {:flex 1
:height 40 :height 40
:background-color :transparent}) :background-color :transparent})
(def no-routes-found-container
{:height 40
:width "100%"
:align-items :center})

View File

@ -9,11 +9,12 @@
[status-im.contexts.wallet.common.account-switcher.view :as account-switcher] [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.asset-list.view :as asset-list]
[status-im.contexts.wallet.common.utils :as utils] [status-im.contexts.wallet.common.utils :as utils]
[status-im.contexts.wallet.common.utils.send :as send-utils]
[status-im.contexts.wallet.send.input-amount.style :as style] [status-im.contexts.wallet.send.input-amount.style :as style]
[status-im.contexts.wallet.send.routes.view :as routes] [status-im.contexts.wallet.send.routes.view :as routes]
[status-im.contexts.wallet.send.utils :as send-utils]
[utils.address :as address] [utils.address :as address]
[utils.i18n :as i18n] [utils.i18n :as i18n]
[utils.money :as money]
[utils.re-frame :as rf])) [utils.re-frame :as rf]))
(defn- make-limit-label (defn- make-limit-label
@ -52,6 +53,15 @@
:title (i18n/label :t/user-gets {:name receiver}) :title (i18n/label :t/user-gets {:name receiver})
:subtitle amount}]]) :subtitle amount}]])
(defn- every-network-value-is-zero?
[sender-network-values]
(every? (fn [{:keys [total-amount]}]
(and
total-amount
(money/equal-to total-amount
(money/bignumber "0"))))
sender-network-values))
(defn select-asset-bottom-sheet (defn select-asset-bottom-sheet
[clear-input!] [clear-input!]
(let [{preselected-token-symbol :symbol} (rf/sub [:wallet/wallet-send-token])] (let [{preselected-token-symbol :symbol} (rf/sub [:wallet/wallet-send-token])]
@ -157,7 +167,18 @@
[:show-bottom-sheet [:show-bottom-sheet
{:content (fn [] {:content (fn []
[select-asset-bottom-sheet [select-asset-bottom-sheet
clear-input!])}])] clear-input!])}])
loading-suggested-routes? (rf/sub
[:wallet/wallet-send-loading-suggested-routes?])
sender-network-values (rf/sub
[:wallet/wallet-send-sender-network-values])
suggested-routes (rf/sub [:wallet/wallet-send-suggested-routes])
routes (when suggested-routes
(or (:best suggested-routes) []))
no-routes-found? (and
(every-network-value-is-zero? sender-network-values)
(not (nil? routes))
(not loading-suggested-routes?))]
(rn/use-mount (rn/use-mount
(fn [] (fn []
(let [dismiss-keyboard-fn #(when (= % "active") (rn/dismiss-keyboard!)) (let [dismiss-keyboard-fn #(when (= % "active") (rn/dismiss-keyboard!))
@ -200,12 +221,28 @@
:fees fee-formatted :fees fee-formatted
:amount amount-text :amount amount-text
:receiver (address/get-shortened-key to-address)}]) :receiver (address/get-shortened-key to-address)}])
(when no-routes-found?
[rn/view {:style style/no-routes-found-container}
[quo/info-message
{:type :error
:icon :i/alert
:size :default
:style {:margin-top 15}}
(i18n/label :t/no-routes-found)]])
[quo/bottom-actions [quo/bottom-actions
{:actions :one-action {:actions :one-action
:button-one-label button-one-label :button-one-label (if no-routes-found?
(i18n/label :t/try-again)
button-one-label)
:button-one-props (merge button-one-props :button-one-props (merge button-one-props
{:disabled? confirm-disabled? {:disabled? (and (not no-routes-found?) confirm-disabled?)
:on-press on-confirm})}] :on-press (if no-routes-found?
#(rf/dispatch [:wallet/get-suggested-routes
{:amount (controlled-input/input-value
input-state)}])
on-confirm)}
(when no-routes-found?
{:type :grey}))}]
[quo/numbered-keyboard [quo/numbered-keyboard
{:container-style (style/keyboard-container bottom) {:container-style (style/keyboard-container bottom)
:left-action :dot :left-action :dot

View File

@ -3,20 +3,16 @@
(def routes-container (def routes-container
{:padding-horizontal 20 {:padding-horizontal 20
:flex 1 :flex-grow 1
:padding-vertical 16 :padding-vertical 16
:width "100%" :width "100%"})
:height "100%"})
(def routes-header-container (def routes-header-container
{:flex-direction :row {:flex-direction :row
:justify-content :space-between}) :justify-content :space-between})
(defn routes-inner-container (def routes-inner-container
[first-item?] {:flex-direction :row
{:margin-top (if first-item? 7.5 11)
:flex-direction :row
:align-items :center
:justify-content :space-between}) :justify-content :space-between})
(def section-label-right (def section-label-right
@ -25,20 +21,26 @@
(def section-label-left (def section-label-left
{:width 136}) {:width 136})
(def network-link (def network-links-container
{:margin-horizontal -1.5 {:margin-horizontal -1.5
:z-index 1 :margin-top 7.5
:z-index 3
:flex 1}) :flex 1})
(defn network-link-container
[margin-top inverted?]
(cond-> {:position :absolute
:left 0
:right 0
:top margin-top}
inverted?
(assoc :transform [{:scaleY -1}])))
(def empty-container (def empty-container
{:flex-grow 1 {:flex-grow 1
:align-items :center :align-items :center
:justify-content :center}) :justify-content :center})
(def add-network
{:margin-top 11
:align-self :flex-end})
(defn warning-container (defn warning-container
[color theme] [color theme]
{:flex-direction :row {:flex-direction :row

View File

@ -6,19 +6,18 @@
[quo.foundations.resources :as resources] [quo.foundations.resources :as resources]
[react-native.core :as rn] [react-native.core :as rn]
[reagent.core :as reagent] [reagent.core :as reagent]
[status-im.constants :as constants] [status-im.contexts.wallet.common.utils.networks :as network-utils]
[status-im.contexts.wallet.common.utils.networks :as networks-utils]
[status-im.contexts.wallet.common.utils.send :as send-utils]
[status-im.contexts.wallet.send.routes.style :as style] [status-im.contexts.wallet.send.routes.style :as style]
[status-im.contexts.wallet.send.utils :as send-utils]
[utils.debounce :as debounce] [utils.debounce :as debounce]
[utils.i18n :as i18n] [utils.i18n :as i18n]
[utils.re-frame :as rf] [utils.re-frame :as rf]))
[utils.vector :as vector-utils]))
(def ^:private network-priority-score (def row-height 44)
{:ethereum 1 (def space-between-rows 11)
:optimism 2 (def network-link-linear-height 10)
:arbitrum 3}) (def network-link-1x-height 56)
(def network-link-2x-height 111)
(defn- make-network-item (defn- make-network-item
[{:keys [network-name chain-id] :as _network} [{:keys [network-name chain-id] :as _network}
@ -33,40 +32,16 @@
:checked? (some #(= % chain-id) @network-preferences) :checked? (some #(= % chain-id) @network-preferences)
:on-change on-change}}) :on-change on-change}})
(defn- find-network-link-insertion-index (defn fetch-routes
[network-links chain-id loading-suggested-routes?] [amount valid-input? bounce-duration-ms]
(let [network (networks-utils/id->network chain-id) (if valid-input?
inserted-network-link-priority-score (network-priority-score network)] (debounce/debounce-and-dispatch
(or (->> network-links [:wallet/get-suggested-routes {:amount amount}]
(keep-indexed (fn [idx network-link] bounce-duration-ms)
(let [network-link (networks-utils/id->network (if loading-suggested-routes? (rf/dispatch [:wallet/clean-suggested-routes])))
network-link
(get-in network-link
[:from
:chain-id])))]
(when (> (network-priority-score network-link)
inserted-network-link-priority-score)
idx))))
first)
(count network-links))))
(defn- add-disabled-networks
[network-links disabled-from-networks loading-suggested-routes?]
(let [sorted-networks (sort-by (comp network-priority-score networks-utils/id->network)
disabled-from-networks)]
(reduce (fn [acc-network-links chain-id]
(let [index (find-network-link-insertion-index acc-network-links
chain-id
loading-suggested-routes?)
disabled-network-link {:status :disabled
:chain-id chain-id
:network (networks-utils/id->network chain-id)}]
(vector-utils/insert-element-at acc-network-links disabled-network-link index)))
network-links
sorted-networks)))
(defn networks-drawer (defn networks-drawer
[{:keys [fetch-routes theme]}] [{:keys [on-save theme]}]
(let [network-details (rf/sub [:wallet/network-details]) (let [network-details (rf/sub [:wallet/network-details])
{:keys [color]} (rf/sub [:wallet/current-viewing-account]) {:keys [color]} (rf/sub [:wallet/current-viewing-account])
selected-networks (rf/sub [:wallet/wallet-send-receiver-networks]) selected-networks (rf/sub [:wallet/wallet-send-receiver-networks])
@ -119,188 +94,149 @@
(rf/dispatch [:wallet/update-receiver-networks (rf/dispatch [:wallet/update-receiver-networks
@network-preferences]) @network-preferences])
(rf/dispatch [:hide-bottom-sheet]) (rf/dispatch [:hide-bottom-sheet])
(fetch-routes)) (on-save))
:customization-color color}}]]))) :customization-color color}}]])))
(defn route-item (defn render-network-values
[{:keys [first-item? from-amount to-amount token-symbol from-chain-id to-chain-id from-network [{:keys [network-values token-symbol on-press theme on-save to? loading-suggested-routes?]}]
to-network on-press-from-network on-press-to-network status theme fetch-routes disabled? [rn/view
loading?]}] (map-indexed (fn [index {:keys [chain-id total-amount type]}]
(if (= status :add) [rn/view
[quo/network-bridge {:key (str (if to? "to" "from") "-" chain-id)
{:status :add :style {:margin-top (if (pos? index) 11 7.5)}}
:container-style style/add-network [quo/network-bridge
:on-press #(rf/dispatch [:show-bottom-sheet {:amount (str total-amount " " token-symbol)
{:content (fn [] [networks-drawer :network (network-utils/id->network chain-id)
{:theme theme :status type
:fetch-routes fetch-routes}])}])}] :on-press #(when (not loading-suggested-routes?)
[rn/view {:style (style/routes-inner-container first-item?)} (cond
[quo/network-bridge (= type :add)
{:amount (str from-amount " " token-symbol) (rf/dispatch [:show-bottom-sheet
:network from-network {:content (fn []
:status status [networks-drawer
:on-press #(when (and on-press-from-network (not loading?)) {:theme theme
(on-press-from-network from-chain-id from-amount))}] :on-save on-save}])}])
(if (= status :default) on-press (on-press chain-id total-amount)))}]])
[quo/network-link network-values)])
{:shape :linear
:source from-network
:destination to-network
:container-style style/network-link}]
[rn/view {:style {:flex 1}}])
[quo/network-bridge
{:amount (str to-amount " " token-symbol)
:network to-network
:status (cond
(and disabled? loading?) :loading
(and disabled? (not loading?)) :default
:else status)
:on-press #(when (and on-press-to-network (not loading?))
(on-press-to-network to-chain-id to-amount))}]]))
(defn- render-network-link (defn render-network-links
[item index _ [{:keys [network-links sender-network-values]}]
{:keys [from-values-by-chain to-values-by-chain theme fetch-routes on-press-from-network [rn/view {:style style/network-links-container}
on-press-to-network token-symbol loading-suggested-routes?]}] (map
(let [first-item? (zero? index) (fn [{:keys [from-chain-id to-chain-id position-diff]}]
disabled-network? (= (:status item) :disabled) (let [position-diff-absolute (js/Math.abs position-diff)
from-chain-id (get-in item [:from :chain-id]) shape (case position-diff-absolute
to-chain-id (get-in item [:to :chain-id]) 0 :linear
from-amount (when from-chain-id 1 :1x
(from-values-by-chain from-chain-id)) 2 :2x)
to-amount (when to-chain-id height (case position-diff-absolute
(to-values-by-chain to-chain-id))] 0 network-link-linear-height
[route-item 1 network-link-1x-height
{:first-item? first-item? 2 network-link-2x-height)
:from-amount (if disabled-network? 0 from-amount) inverted? (neg? position-diff)
:to-amount (if disabled-network? 0 to-amount) source (network-utils/id->network from-chain-id)
:token-symbol token-symbol destination (network-utils/id->network to-chain-id)
:disabled? disabled-network? from-chain-id-index (first (keep-indexed #(when (= from-chain-id (:chain-id %2)) %1)
:loading? loading-suggested-routes? sender-network-values))
:theme theme base-margin-top (* (+ row-height space-between-rows)
:fetch-routes fetch-routes from-chain-id-index)
:status (cond margin-top (if (zero? position-diff)
(= (:status item) :add) :add (+ base-margin-top
(= (:status item) :disabled) :disabled (- (/ row-height 2) (/ height 2)))
loading-suggested-routes? :loading (+ base-margin-top
:else :default) (- (/ row-height 2) height)
:from-chain-id (or from-chain-id (:chain-id item)) (if inverted? height 0)))]
:to-chain-id (or to-chain-id (:chain-id item)) [rn/view
:from-network (cond (and loading-suggested-routes? {:key (str "from-" from-chain-id "-to-" to-chain-id)
(not disabled-network?)) :style (style/network-link-container margin-top inverted?)}
(networks-utils/id->network item) [rn/view {:style {:flex 1}}
disabled-network? [quo/network-link
(networks-utils/id->network (:chain-id {:shape shape
item)) :source source
:else :destination destination}]]]))
(networks-utils/id->network from-chain-id)) network-links)])
:to-network (cond (and loading-suggested-routes?
(not disabled-network?))
(networks-utils/id->network item)
disabled-network?
(networks-utils/id->network (:chain-id
item))
:else
(networks-utils/id->network to-chain-id))
:on-press-from-network on-press-from-network
:on-press-to-network on-press-to-network}]))
(defn fetch-routes (defn disable-chain
[amount valid-input? bounce-duration-ms] [chain-id disabled-from-chain-ids token-available-networks-for-suggested-routes]
(if valid-input? (let [disabled-chain-ids
(debounce/debounce-and-dispatch (if (contains? (set
[:wallet/get-suggested-routes {:amount amount}] disabled-from-chain-ids)
bounce-duration-ms) chain-id)
(rf/dispatch [:wallet/clean-suggested-routes]))) (vec (remove #(= % chain-id)
disabled-from-chain-ids))
(conj disabled-from-chain-ids
chain-id))
re-enabling-chain?
(< (count disabled-chain-ids)
(count disabled-from-chain-ids))]
(if (or re-enabling-chain?
(> (count token-available-networks-for-suggested-routes) 1))
(rf/dispatch [:wallet/disable-from-networks
disabled-chain-ids])
(rf/dispatch [:toasts/upsert
{:id :disable-chain-error
:type :negative
:text (i18n/label :t/at-least-one-network-must-be-activated)}]))))
(defn view (defn view
[{:keys [token theme input-value valid-input? [{:keys [token theme input-value valid-input?
on-press-to-network current-screen-id]}] on-press-to-network current-screen-id]}]
(let [token-symbol (:symbol token)
(let [token-symbol (:symbol token) nav-current-screen-id (rf/sub [:view-id])
nav-current-screen-id (rf/sub [:view-id]) active-screen? (= nav-current-screen-id current-screen-id)
active-screen? (= nav-current-screen-id current-screen-id) loading-suggested-routes? (rf/sub
loading-suggested-routes? (rf/sub [:wallet/wallet-send-loading-suggested-routes?])
[:wallet/wallet-send-loading-suggested-routes?]) sender-network-values (rf/sub
from-values-by-chain (rf/sub [:wallet/wallet-send-sender-network-values])
[:wallet/wallet-send-from-values-by-chain]) receiver-network-values (rf/sub
to-values-by-chain (rf/sub [:wallet/wallet-send-to-values-by-chain]) [:wallet/wallet-send-receiver-network-values])
suggested-routes (rf/sub [:wallet/wallet-send-suggested-routes]) network-links (rf/sub [:wallet/wallet-send-network-links])
selected-networks (rf/sub [:wallet/wallet-send-receiver-networks]) disabled-from-chain-ids (rf/sub
disabled-from-chain-ids (rf/sub [:wallet/wallet-send-disabled-from-chain-ids])
[:wallet/wallet-send-disabled-from-chain-ids])
routes (when suggested-routes
(or (:best suggested-routes) []))
{token-balances-per-chain :balances-per-chain} (rf/sub {token-balances-per-chain :balances-per-chain} (rf/sub
[:wallet/current-viewing-account-tokens-filtered [:wallet/current-viewing-account-tokens-filtered
(str token-symbol)]) (str token-symbol)])
affordable-networks (send-utils/find-affordable-networks token-available-networks-for-suggested-routes
{:balances-per-chain token-balances-per-chain (send-utils/token-available-networks-for-suggested-routes
:input-value input-value {:balances-per-chain token-balances-per-chain
:selected-networks selected-networks :disabled-chain-ids disabled-from-chain-ids})
:disabled-chain-ids disabled-from-chain-ids}) show-routes? (not-empty sender-network-values)]
network-links (if loading-suggested-routes?
affordable-networks
routes)
show-routes? (or (and (not-empty affordable-networks)
loading-suggested-routes?)
(not-empty routes))]
(rn/use-effect (rn/use-effect
#(when (and active-screen? (> (count affordable-networks) 0)) #(when (and active-screen? (> (count token-available-networks-for-suggested-routes) 0))
(fetch-routes input-value valid-input? 2000)) (fetch-routes input-value valid-input? 2000))
[input-value valid-input?]) [input-value valid-input?])
(rn/use-effect (rn/use-effect
#(when (and active-screen? (> (count affordable-networks) 0)) #(when (and active-screen? (> (count token-available-networks-for-suggested-routes) 0))
(fetch-routes input-value valid-input? 0)) (fetch-routes input-value valid-input? 0))
[disabled-from-chain-ids]) [disabled-from-chain-ids])
(if show-routes? [rn/scroll-view {:content-container-style style/routes-container}
(let [initial-network-links-count (count network-links) (when show-routes?
disabled-count (count disabled-from-chain-ids) [rn/view {:style style/routes-header-container}
network-links (if (not-empty disabled-from-chain-ids) [quo/section-label
(add-disabled-networks network-links {:section (i18n/label :t/from-label)
disabled-from-chain-ids :container-style style/section-label-left}]
loading-suggested-routes?) [quo/section-label
network-links) {:section (i18n/label :t/to-label)
network-links-with-add-button (if (and (< (- (count network-links) disabled-count) :container-style style/section-label-right}]])
constants/default-network-count) [rn/view {:style style/routes-inner-container}
(pos? initial-network-links-count)) [render-network-values
(concat network-links [{:status :add}]) {:token-symbol token-symbol
network-links)] :network-values sender-network-values
[rn/flat-list :on-press #(disable-chain %1
{:data network-links-with-add-button disabled-from-chain-ids
:content-container-style style/routes-container token-available-networks-for-suggested-routes)
:header [rn/view {:style style/routes-header-container} :to? false
[quo/section-label :theme theme
{:section (i18n/label :t/from-label) :loading-suggested-routes? loading-suggested-routes?}]
:container-style style/section-label-left}] [render-network-links
[quo/section-label {:network-links network-links
{:section (i18n/label :t/to-label) :sender-network-values sender-network-values}]
:container-style style/section-label-right}]] [render-network-values
:render-data {:token-symbol token-symbol
{:from-values-by-chain from-values-by-chain :network-values receiver-network-values
:to-values-by-chain to-values-by-chain :on-press on-press-to-network
:theme theme :to? true
:fetch-routes #(fetch-routes % valid-input? 2000) :loading-suggested-routes? loading-suggested-routes?
:on-press-from-network (fn [chain-id _] :theme theme
(let [disabled-chain-ids (if (contains? (set :on-save #(fetch-routes input-value valid-input? 0)}]]]))
disabled-from-chain-ids)
chain-id)
(vec (remove #(= % chain-id)
disabled-from-chain-ids))
(conj disabled-from-chain-ids
chain-id))
re-enabling-chain? (< (count disabled-chain-ids)
(count disabled-from-chain-ids))]
(when (or re-enabling-chain?
(> (count affordable-networks) 1))
(rf/dispatch [:wallet/disable-from-networks
disabled-chain-ids]))))
:on-press-to-network on-press-to-network
:token-symbol token-symbol
:loading-suggested-routes? loading-suggested-routes?}
:render-fn render-network-link}])
[rn/view {:style style/empty-container}
(when (and (not (nil? routes)) (not loading-suggested-routes?))
[quo/text (i18n/label :t/no-routes-found)])])))

View File

@ -209,9 +209,7 @@
(defn view (defn view
[_] [_]
(let [on-close (fn [] (let [on-close #(rf/dispatch [:navigate-back])]
(rf/dispatch [:wallet/clean-suggested-routes])
(rf/dispatch [:navigate-back]))]
(fn [] (fn []
(let [theme (quo.theme/use-theme) (let [theme (quo.theme/use-theme)
send-transaction-data (rf/sub [:wallet/wallet-send]) send-transaction-data (rf/sub [:wallet/wallet-send])

View File

@ -2,6 +2,7 @@
(:require (:require
[legacy.status-im.utils.hex :as utils.hex] [legacy.status-im.utils.hex :as utils.hex]
[native-module.core :as native-module] [native-module.core :as native-module]
[status-im.contexts.wallet.common.utils.networks :as network-utils]
[utils.money :as money])) [utils.money :as money]))
(defn amount-in-hex (defn amount-in-hex
@ -24,6 +25,23 @@
{} {}
transaction-hashes)) transaction-hashes))
(defn calculate-gas-fee
[data]
(let [gas-amount (money/bignumber (get data :gas-amount))
gas-fees (get data :gas-fees)
eip1559-enabled? (get gas-fees :eip-1559-enabled)
optimal-price-gwei (money/bignumber (if eip1559-enabled?
(get gas-fees :max-fee-per-gas-medium)
(get gas-fees :gas-price)))
total-gas-fee-wei (money/mul (money/->wei :gwei optimal-price-gwei) gas-amount)
l1-fee-wei (money/->wei :gwei (get gas-fees :l-1-gas-fee))]
(money/add total-gas-fee-wei l1-fee-wei)))
(defn calculate-full-route-gas-fee
"Sums all the routes fees in wei and then convert the total value to ether"
[route]
(money/wei->ether (reduce money/add (map calculate-gas-fee route))))
(defn network-amounts-by-chain (defn network-amounts-by-chain
[{:keys [route token-decimals native-token? to?]}] [{:keys [route token-decimals native-token? to?]}]
(reduce (fn [acc path] (reduce (fn [acc path]
@ -47,3 +65,104 @@
(assoc acc k (if (money/equal-to v 0) "<0.01" v))) (assoc acc k (if (money/equal-to v 0) "<0.01" v)))
{} {}
amounts)) amounts))
(defn token-available-networks-for-suggested-routes
[{:keys [balances-per-chain disabled-chain-ids]}]
(let [disabled-set (set disabled-chain-ids)]
(->> balances-per-chain
(filter (fn [[_ {:keys [chain-id]}]]
(not (contains? disabled-set chain-id))))
(map first))))
(def ^:private network-priority-score
{:ethereum 1
:optimism 2
:arbitrum 3})
(def ^:private available-networks-count
(count (set (keys network-priority-score))))
(defn reset-network-amounts-to-zero
[network-amounts]
(map
(fn [network-amount]
(cond-> network-amount
(= (:type network-amount) :loading)
(assoc :total-amount (money/bignumber "0")
:type :default)))
network-amounts))
(defn network-amounts
[network-values disabled-chain-ids receiver-networks to?]
(let [disabled-set (set disabled-chain-ids)
receiver-networks-set (set receiver-networks)
network-values-keys (set (keys network-values))
routes-found? (pos? (count network-values-keys))
updated-network-values (when routes-found?
(reduce (fn [acc k]
(if (or (contains? network-values-keys k)
(and to?
(not (contains? receiver-networks-set k))))
acc
(assoc acc k (money/bignumber "0"))))
network-values
disabled-chain-ids))]
(cond-> (->> updated-network-values
(map
(fn [[k v]]
{:chain-id k
:total-amount v
:type (if (or to? (not (contains? disabled-set k))) :default :disabled)}))
(sort-by #(get network-priority-score (network-utils/id->network (:chain-id %))))
(filter
#(or (and to?
(or (contains? receiver-networks-set (:chain-id %))
(money/greater-than (:total-amount %) (money/bignumber "0"))))
(not to?)))
(vec))
(and to?
routes-found?
(< (count updated-network-values) available-networks-count))
(conj {:type :add}))))
(defn loading-network-amounts
[valid-networks disabled-chain-ids receiver-networks to?]
(let [disabled-set (set disabled-chain-ids)
receiver-networks-set (set receiver-networks)
receiver-networks-count (count receiver-networks)
valid-networks (concat valid-networks disabled-chain-ids)]
(cond-> (->> valid-networks
(map (fn [k]
(cond-> {:chain-id k
:type (if (or to?
(not (contains? disabled-set k)))
:loading
:disabled)}
(and (not to?) (contains? disabled-set k))
(assoc :total-amount (money/bignumber "0")))))
(sort-by (fn [item]
(get network-priority-score
(network-utils/id->network (:chain-id item)))))
(filter
#(or (and to? (contains? receiver-networks-set (:chain-id %)))
(and (not to?)
(not (contains? disabled-chain-ids (:chain-id %))))))
(vec))
(and to? (< receiver-networks-count available-networks-count)) (conj {:type :add}))))
(defn network-links
[route from-values-by-chain to-values-by-chain]
(reduce (fn [acc path]
(let [from-chain-id (get-in path [:from :chain-id])
to-chain-id (get-in path [:to :chain-id])
from-chain-id-index (first (keep-indexed #(when (= from-chain-id (:chain-id %2)) %1)
from-values-by-chain))
to-chain-id-index (first (keep-indexed #(when (= to-chain-id (:chain-id %2)) %1)
to-values-by-chain))
position-diff (- from-chain-id-index to-chain-id-index)]
(conj acc
{:from-chain-id from-chain-id
:to-chain-id to-chain-id
:position-diff position-diff})))
[]
route))

View File

@ -1,6 +1,7 @@
(ns status-im.contexts.wallet.send.utils-test (ns status-im.contexts.wallet.send.utils-test
(:require [cljs.test :refer [deftest is testing]] (:require [cljs.test :refer [deftest is testing]]
[status-im.contexts.wallet.send.utils :as utils] [status-im.contexts.wallet.send.utils :as utils]
[utils.map :as map]
[utils.money :as money])) [utils.money :as money]))
(deftest test-amount-in-hex (deftest test-amount-in-hex
@ -93,3 +94,350 @@
(doseq [[chain-id exp-value] expected] (doseq [[chain-id exp-value] expected]
(is #(or (= (get result chain-id) exp-value) (is #(or (= (get result chain-id) exp-value)
(money/equal-to (get result chain-id) exp-value))))))) (money/equal-to (get result chain-id) exp-value)))))))
(deftest test-calculate-gas-fee
(testing "EIP-1559 transaction without L1 fee"
(let [data {:gas-amount "23487"
:gas-fees {:max-fee-per-gas-medium "2.259274911"
:eip-1559-enabled true
:l-1-gas-fee "0"}}
expected-result (money/bignumber "53063589834657")] ; This is in Wei
(is (money/equal-to (utils/calculate-gas-fee data)
expected-result))))
(testing "EIP-1559 transaction with L1 fee of 60,000 Gwei"
(let [data {:gas-amount "23487"
:gas-fees {:max-fee-per-gas-medium "2.259274911"
:eip-1559-enabled true
:l-1-gas-fee "60000"}}
expected-result (money/bignumber "113063589834657")] ; Added 60,000 Gwei in Wei to the
; previous result
(is (money/equal-to (utils/calculate-gas-fee data)
expected-result))))
(testing "Non-EIP-1559 transaction with specified gas price"
(let [data {:gas-amount "23487"
:gas-fees {:gas-price "2.872721089"
:eip-1559-enabled false
:l-1-gas-fee "0"}}
expected-result (money/bignumber "67471600217343")] ; This is in Wei, for the specified
; gas amount and price
(is (money/equal-to (utils/calculate-gas-fee data)
expected-result)))))
(deftest test-calculate-full-route-gas-fee
(testing "Route with a single EIP-1559 transaction, no L1 fees"
(let [route [{:gas-amount "23487"
:gas-fees {:max-fee-per-gas-medium "2.259274911"
:eip-1559-enabled true
:l-1-gas-fee "0"}}]
expected-result (money/bignumber "0.000053063589834657")] ; The Wei amount for the
; transaction, converted to
; Ether
(is (money/equal-to (utils/calculate-full-route-gas-fee route)
expected-result))))
(testing "Route with two EIP-1559 transactions, no L1 fees"
(let [route [{:gas-amount "23487"
:gas-fees {:max-fee-per-gas-medium "2.259274911"
:eip-1559-enabled true
:l-1-gas-fee "0"}}
{:gas-amount "23487"
:gas-fees {:max-fee-per-gas-medium "2.259274911"
:eip-1559-enabled true
:l-1-gas-fee "0"}}]
expected-result (money/bignumber "0.000106127179669314")] ; Sum of both transactions' Wei
; amounts, converted to Ether
(is (money/equal-to (utils/calculate-full-route-gas-fee route)
expected-result))))
(testing "Route with two EIP-1559 transactions, one with L1 fee of 60,000 Gwei"
(let [route [{:gas-amount "23487"
:gas-fees {:max-fee-per-gas-medium "2.259274911"
:eip-1559-enabled true
:l-1-gas-fee "0"}}
{:gas-amount "23487"
:gas-fees {:max-fee-per-gas-medium "2.259274911"
:eip-1559-enabled true
:l-1-gas-fee "60000"}}]
expected-result (money/bignumber "0.000166127179669314")] ; Added 60,000 Gwei in Wei to
; the previous total and
; converted to Ether
(is (money/equal-to (utils/calculate-full-route-gas-fee route)
expected-result)))))
(deftest test-token-available-networks-for-suggested-routes
(testing "Excludes disabled chain-ids correctly"
(let [balances-per-chain {"1" {:chain-id "1" :balance 100}
"10" {:chain-id "10" :balance 200}
"42161" {:chain-id "42161" :balance 300}}
disabled-chain-ids ["10"]
expected ["1" "42161"]]
(is (= expected
(utils/token-available-networks-for-suggested-routes {:balances-per-chain balances-per-chain
:disabled-chain-ids
disabled-chain-ids})))))
(testing "Returns all chains when no disabled chains are specified"
(let [balances-per-chain {"1" {:chain-id "1" :balance 100}
"10" {:chain-id "10" :balance 200}
"42161" {:chain-id "42161" :balance 300}}
disabled-chain-ids []
expected ["1" "10" "42161"]]
(is (= expected
(utils/token-available-networks-for-suggested-routes {:balances-per-chain balances-per-chain
:disabled-chain-ids
disabled-chain-ids})))))
(testing "Returns empty list when all chains are disabled"
(let [balances-per-chain {"1" {:chain-id "1" :balance 100}
"10" {:chain-id "10" :balance 200}
"42161" {:chain-id "42161" :balance 300}}
disabled-chain-ids ["1" "10" "42161"]
expected []]
(is (= expected
(utils/token-available-networks-for-suggested-routes {:balances-per-chain balances-per-chain
:disabled-chain-ids
disabled-chain-ids})))))
(testing "Handles non-existent chain-ids gracefully"
(let [balances-per-chain {"59144" {:chain-id "59144" :balance 400}}
disabled-chain-ids ["1" "10" "42161"]
expected ["59144"]]
(is (= expected
(utils/token-available-networks-for-suggested-routes {:balances-per-chain balances-per-chain
:disabled-chain-ids
disabled-chain-ids}))))))
(deftest test-reset-network-amounts-to-zero
(testing "Correctly resets loading network amounts to zero and changes type to default"
(let [network-amounts [{:chain-id "1" :total-amount (money/bignumber "100") :type :loading}
{:chain-id "10" :total-amount (money/bignumber "200") :type :default}]
expected [{:chain-id "1" :total-amount (money/bignumber "0") :type :default}
{:chain-id "10" :total-amount (money/bignumber "200") :type :default}]
result (utils/reset-network-amounts-to-zero network-amounts)
comparisons (map #(map/deep-compare %1 %2)
expected
result)]
(is (every? identity comparisons))))
(testing "Leaves non-loading types unchanged"
(let [network-amounts [{:chain-id "1" :total-amount (money/bignumber "100") :type :default}
{:chain-id "10" :total-amount (money/bignumber "0") :type :disabled}]
expected [{:chain-id "1" :total-amount (money/bignumber "100") :type :default}
{:chain-id "10" :total-amount (money/bignumber "0") :type :disabled}]
result (utils/reset-network-amounts-to-zero network-amounts)
comparisons (map #(map/deep-compare %1 %2)
expected
result)]
(is (every? identity comparisons))))
(testing "Processes an empty list without error"
(let [network-amounts []
expected []
result (utils/reset-network-amounts-to-zero network-amounts)
comparisons (map #(map/deep-compare %1 %2)
expected
result)]
(is (every? identity comparisons))))
(testing "Applies transformations to multiple loading entries"
(let [network-amounts [{:chain-id "1" :total-amount (money/bignumber "100") :type :loading}
{:chain-id "10" :total-amount (money/bignumber "200") :type :loading}]
expected [{:chain-id "1" :total-amount (money/bignumber "0") :type :default}
{:chain-id "10" :total-amount (money/bignumber "0") :type :default}]
result (utils/reset-network-amounts-to-zero network-amounts)
comparisons (map #(map/deep-compare %1 %2)
expected
result)]
(is (every? identity comparisons))))
(testing "Mix of loading and non-loading types"
(let [network-amounts [{:chain-id "1" :total-amount (money/bignumber "100") :type :loading}
{:chain-id "10" :total-amount (money/bignumber "200") :type :default}
{:chain-id "42161" :total-amount (money/bignumber "300") :type :loading}
{:chain-id "59144" :total-amount (money/bignumber "0") :type :disabled}]
expected [{:chain-id "1" :total-amount (money/bignumber "0") :type :default}
{:chain-id "10" :total-amount (money/bignumber "200") :type :default}
{:chain-id "42161" :total-amount (money/bignumber "0") :type :default}
{:chain-id "59144" :total-amount (money/bignumber "0") :type :disabled}]
result (utils/reset-network-amounts-to-zero network-amounts)
comparisons (map #(map/deep-compare %1 %2)
expected
result)]
(is (every? identity comparisons)))))
(deftest test-network-amounts
(testing "Handles disabled and receiver networks correctly when to? is true"
(let [network-values {"1" (money/bignumber "100")
"10" (money/bignumber "200")}
disabled-chain-ids ["1"]
receiver-networks ["10"]
to? true
expected [{:chain-id "1"
:total-amount (money/bignumber "100")
:type :default}
{:chain-id "10"
:total-amount (money/bignumber "200")
:type :default}
{:type :add}]
result (utils/network-amounts network-values
disabled-chain-ids
receiver-networks
to?)]
(is (every? identity (map #(map/deep-compare %1 %2) expected result)))))
(testing "Adds default amount for non-disabled non-receiver networks when to? is false"
(let [network-values {"1" (money/bignumber "100")}
disabled-chain-ids ["10"]
receiver-networks []
to? false
expected [{:chain-id "1"
:total-amount (money/bignumber "100")
:type :default}
{:chain-id "10"
:total-amount (money/bignumber "0")
:type :disabled}]
result (utils/network-amounts network-values
disabled-chain-ids
receiver-networks
to?)]
(is (every? identity (map #(map/deep-compare %1 %2) expected result)))))
(testing "Handles empty inputs correctly"
(let [network-values {}
disabled-chain-ids []
receiver-networks []
to? true
expected []
result (utils/network-amounts network-values
disabled-chain-ids
receiver-networks
to?)]
(is (= expected result))))
(testing "Processes case with multiple network interactions"
(let [network-values {"1" (money/bignumber "300")
"10" (money/bignumber "400")
"42161" (money/bignumber "500")}
disabled-chain-ids ["1" "42161"]
receiver-networks ["10"]
to? true
expected [{:chain-id "1"
:total-amount (money/bignumber "300")
:type :default}
{:chain-id "10"
:total-amount (money/bignumber "400")
:type :default}
{:chain-id "42161"
:total-amount (money/bignumber "500")
:type :default}]
result (utils/network-amounts network-values
disabled-chain-ids
receiver-networks
to?)]
(is (every? identity (map #(map/deep-compare %1 %2) expected result))))))
(deftest test-loading-network-amounts
(testing "Assigns :loading type to valid networks except for disabled ones"
(let [valid-networks ["1" "10" "42161"]
disabled-chain-ids ["42161"]
receiver-networks ["1" "10"]
to? true
expected [{:chain-id "1" :type :loading}
{:chain-id "10" :type :loading}
{:type :add}]
result (utils/loading-network-amounts valid-networks
disabled-chain-ids
receiver-networks
to?)
comparisons (map #(map/deep-compare %1 %2)
expected
result)]
(is (every? identity comparisons))))
(testing "Assigns :disabled type with zero total-amount to disabled networks when to? is false"
(let [valid-networks ["1" "10" "42161"]
disabled-chain-ids ["10" "42161"]
receiver-networks ["1"]
to? false
expected [{:chain-id "1" :type :loading}
{:chain-id "10" :type :disabled :total-amount (money/bignumber "0")}
{:chain-id "42161" :type :disabled :total-amount (money/bignumber "0")}]
result (utils/loading-network-amounts valid-networks
disabled-chain-ids
receiver-networks
to?)
comparisons (map #(map/deep-compare %1 %2)
expected
result)]
(is (every? identity comparisons))))
(testing "Filters out networks not in receiver networks when to? is true"
(let [valid-networks ["1" "10" "42161" "59144"]
disabled-chain-ids ["10"]
receiver-networks ["1" "42161"]
to? true
expected [{:chain-id "1" :type :loading}
{:chain-id "42161" :type :loading}]
result (utils/loading-network-amounts valid-networks
disabled-chain-ids
receiver-networks
to?)
comparisons (map #(map/deep-compare %1 %2)
expected
result)]
(is (every? identity comparisons))))
(testing "Appends :add type if receiver network count is less than available networks and to? is true"
(let [valid-networks ["1" "10" "42161"]
disabled-chain-ids ["10"]
receiver-networks ["1"]
to? true
expected [{:chain-id "1" :type :loading}
{:type :add}]
result (utils/loading-network-amounts valid-networks
disabled-chain-ids
receiver-networks
to?)
comparisons (map #(map/deep-compare %1 %2)
expected
result)]
(is (every? identity comparisons)))))
(deftest test-network-links
(testing "Calculates position differences correctly"
(let [route [{:from {:chain-id "1"} :to {:chain-id "42161"}}
{:from {:chain-id "10"} :to {:chain-id "1"}}
{:from {:chain-id "42161"} :to {:chain-id "10"}}]
from-values-by-chain [{:chain-id "1"} {:chain-id "10"} {:chain-id "42161"}]
to-values-by-chain [{:chain-id "42161"} {:chain-id "1"} {:chain-id "10"}]
expected [{:from-chain-id "1" :to-chain-id "42161" :position-diff 0}
{:from-chain-id "10" :to-chain-id "1" :position-diff 0}
{:from-chain-id "42161" :to-chain-id "10" :position-diff 0}]
result (utils/network-links route from-values-by-chain to-values-by-chain)]
(is (= expected result))))
(testing "Handles cases with no position difference"
(let [route [{:from {:chain-id "1"} :to {:chain-id "1"}}]
from-values-by-chain [{:chain-id "1"} {:chain-id "10"} {:chain-id "42161"}]
to-values-by-chain [{:chain-id "1"} {:chain-id "10"} {:chain-id "42161"}]
expected [{:from-chain-id "1" :to-chain-id "1" :position-diff 0}]
result (utils/network-links route from-values-by-chain to-values-by-chain)]
(is (= expected result))))
(testing "Handles empty route"
(let [route []
from-values-by-chain []
to-values-by-chain []
expected []
result (utils/network-links route from-values-by-chain to-values-by-chain)]
(is (= expected result))))
(testing "Verifies negative position differences"
(let [route [{:from {:chain-id "1"} :to {:chain-id "42161"}}]
from-values-by-chain [{:chain-id "1"} {:chain-id "10"} {:chain-id "42161"}]
to-values-by-chain [{:chain-id "1"} {:chain-id "10"} {:chain-id "42161"}]
expected [{:from-chain-id "1" :to-chain-id "42161" :position-diff -2}]
result (utils/network-links route from-values-by-chain to-values-by-chain)]
(is (= expected result)))))

View File

@ -121,6 +121,21 @@
:<- [:wallet/wallet-send] :<- [:wallet/wallet-send]
:-> :suggested-routes) :-> :suggested-routes)
(rf/reg-sub
:wallet/wallet-send-sender-network-values
:<- [:wallet/wallet-send]
:-> :sender-network-values)
(rf/reg-sub
:wallet/wallet-send-receiver-network-values
:<- [:wallet/wallet-send]
:-> :receiver-network-values)
(rf/reg-sub
:wallet/wallet-send-network-links
:<- [:wallet/wallet-send]
:-> :network-links)
(rf/reg-sub (rf/reg-sub
:wallet/keypairs :wallet/keypairs
:<- [:wallet] :<- [:wallet]

17
src/utils/map.cljs Normal file
View File

@ -0,0 +1,17 @@
(ns utils.map
(:require [utils.money :as money]))
(defn compare-values
"Compares two values, using special handling for BigNumbers and regular equality for others."
[v1 v2]
(cond
(and (money/bignumber? v1) (money/bignumber? v2)) (money/equal-to v1 v2)
:else (= v1 v2)))
(defn deep-compare
"Recursively compare two maps, specially handling BigNumber values within the maps."
[map1 map2]
(and
(= (set (keys map1)) (set (keys map2)))
(every? (fn [k] (compare-values (get map1 k) (get map2 k)))
(keys map1))))

View File

@ -37,6 +37,11 @@
(new BigNumber (normalize (str n))) (new BigNumber (normalize (str n)))
(catch :default _ nil)))) (catch :default _ nil))))
(defn bignumber?
"Check if the value is a bignumber."
[x]
(instance? BigNumber x))
(defn greater-than-or-equals (defn greater-than-or-equals
[^js bn1 ^js bn2] [^js bn1 ^js bn2]
(.greaterThanOrEqualTo bn1 bn2)) (.greaterThanOrEqualTo bn1 bn2))

View File

@ -2595,5 +2595,6 @@
"this-account-has-no-activity": "This account has no activity", "this-account-has-no-activity": "This account has no activity",
"this-address-has-activity": "This address has activity", "this-address-has-activity": "This address has activity",
"scanning-for-activity": "Scanning for activity...", "scanning-for-activity": "Scanning for activity...",
"send-community-link": "Send community link" "send-community-link": "Send community link",
"at-least-one-network-must-be-activated": "At least 1 network must be activated"
} }