feat: add ability to tap to disable from networks (#19392)

This commit is contained in:
Brian Sztamfater 2024-04-15 12:04:44 -03:00 committed by GitHub
parent 5412092ca1
commit 98d4969ca5
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
22 changed files with 887 additions and 419 deletions

View File

@ -0,0 +1,23 @@
(ns quo.components.wallet.network-link.helpers)
(def ^:private central-figure-width 63)
(defn calculate-side-lines-path-1x
"Calculates the `d` attribute for the side lines based on the SVG width."
[width]
(let [side-offset (/ (- width central-figure-width) 2)]
{:left (str "M0 57 L" side-offset " 57")
:right (str "M" (+ side-offset central-figure-width) " 1 L" width " 1")}))
(defn calculate-transform
"Calculates the transform attribute for the central figure based on the SVG width."
[width]
(let [translate-x (/ (- width central-figure-width) 2)]
(str "translate(" translate-x " 0)")))
(defn calculate-side-lines-path-2x
"Calculates the `d` attribute for the side lines based on the SVG width."
[width]
(let [side-offset (/ (- width central-figure-width) 2)]
{:left (str "M0 113 L" side-offset " 113")
:right (str "M" (+ side-offset central-figure-width) " 1 L" width " 1")}))

View File

@ -0,0 +1,34 @@
(ns quo.components.wallet.network-link.style)
(def left-circle-container
{:position :absolute
:left -3})
(def right-circle-container
{:position :absolute
:right -3})
(def bottom-left-circle-container
{:position :absolute
:bottom -3
:left -3})
(def top-right-circle-container
{:position :absolute
:top -3
:right -3})
(def link-linear-container
{:flex-direction :row
:align-items :center
:height 10})
(def link-1x-container
{:flex 1
:height 58
:justify-content :center})
(def link-2x-container
{:flex 1
:height 114
:justify-content :center})

View File

@ -1,82 +1,151 @@
(ns quo.components.wallet.network-link.view
(:require
[oops.core :refer [oget]]
[quo.components.wallet.network-link.helpers :as helpers]
[quo.components.wallet.network-link.schema :as component-schema]
[quo.components.wallet.network-link.style :as style]
[quo.foundations.colors :as colors]
[quo.theme :as quo.theme]
[react-native.core :as rn]
[react-native.svg :as svg]
[reagent.core :as reagent]
[schema.core :as schema]))
(defn- circle
[fill stroke]
[svg/svg
{:height "8"
:width "8"}
[svg/circle
{:cx "4"
:cy "4"
:r "3.5"
:fill fill
:stroke stroke
:strokeWidth "1"}]])
(defn- line
[stroke width]
[svg/svg
{:height "10"
:width "100%"
:view-box (str "0 0 " width " 10")}
[svg/path
{:d (str "M0,5 L" width ",5")
:stroke stroke
:stroke-width "1"}]])
(defn link-linear
[{:keys [source theme]}]
[svg/svg {:xmlns "http://www.w3.org/2000/svg" :width "73" :height "10" :fill :none}
[svg/path {:stroke (colors/resolve-color source theme) :d "M68 5H5"}]
[svg/circle
{:cx "68"
:cy "5"
:r "4"
:fill (colors/theme-colors colors/white colors/neutral-90 theme)
:stroke (colors/resolve-color source theme)}]
[svg/circle
{:cx "5"
:cy "5"
:r "4"
:fill (colors/theme-colors colors/white colors/neutral-90 theme)
:stroke (colors/resolve-color source theme)}]])
[]
(let [container-width (reagent/atom 100)]
(fn [{:keys [source theme]}]
(let [stroke-color (colors/resolve-color source theme)
fill-color (colors/theme-colors colors/white colors/neutral-90 theme)]
[rn/view
{:style style/link-linear-container
:on-layout (fn [e]
(reset! container-width
(oget e :nativeEvent :layout :width)))}
[line stroke-color @container-width]
[rn/view {:style style/left-circle-container}
[circle fill-color stroke-color]]
[rn/view {:style style/right-circle-container}
[circle fill-color stroke-color]]]))))
(defn link-1x
[{:keys [source destination theme]}]
[svg/svg {:xmlns "http://www.w3.org/2000/svg" :width "73" :height "66" :fill :none}
[svg/path
{:stroke "url(#gradient)" :d "M68 5h-9.364c-11.046 0-20 8.954-20 20v16c0 11.046-8.955 20-20 20H5"}]
[svg/circle
{:cx "68"
:cy "5"
:r "4"
:fill (colors/theme-colors colors/white colors/neutral-90 theme)
:stroke (colors/resolve-color destination theme)}]
[svg/circle
{:cx "5"
:cy "61"
:r "4"
:fill (colors/theme-colors colors/white colors/neutral-90 theme)
:stroke (colors/resolve-color source theme)}]
[svg/defs
[svg/linear-gradient
{:id "gradient" :x1 "72.271" :x2 "82.385" :y1 "5" :y2 "34.155" :gradientUnits "userSpaceOnUse"}
[svg/stop {:stopColor (colors/resolve-color destination theme)}]
[svg/stop {:offset "1" :stopColor (colors/resolve-color source theme)}]]]])
[]
(let [container-width (reagent/atom 100)
stroke-color "url(#gradient)"]
(fn [{:keys [source destination theme]}]
(let [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)
view-box (str "0 0 " @container-width " 58")
side-lines-path (helpers/calculate-side-lines-path-1x @container-width)
central-transform (helpers/calculate-transform @container-width)]
[rn/view
{:style style/link-1x-container
:on-layout (fn [e]
(reset! container-width
(oget e :nativeEvent :layout :width)))}
[svg/svg
{:xmlns "http://www.w3.org/2000/svg"
:height "100%"
:width "100%"
:view-box view-box
:fill :none}
[svg/path
{:d (:left side-lines-path)
:stroke source-color}]
[svg/path
{:d
"M63 1L53.6356 1C42.5899 1 33.6356 9.9543 33.6356 21L33.6356 37C33.6356 48.0457 24.6813 57 13.6356 57L2.85889e-05 57"
:transform central-transform
:stroke stroke-color}]
[svg/path
{:d (:right side-lines-path)
:stroke destination-color}]
[svg/defs
[svg/linear-gradient
{:id "gradient"
:x1 "72.271"
:x2 "82.385"
:y1 "5"
:y2 "34.155"
:gradient-units "userSpaceOnUse"}
[svg/stop {:stop-color (colors/resolve-color destination theme)}]
[svg/stop {:offset "1" :stop-color (colors/resolve-color source theme)}]]]]
[rn/view {:style style/bottom-left-circle-container}
[circle fill-color source-color]]
[rn/view {:style style/top-right-circle-container}
[circle fill-color destination-color]]]))))
(defn link-2x
[{:keys [source destination theme]}]
[svg/svg
{:width "73" :height "122" :viewBox "0 0 73 122" :fill "none" :xmlns "http://www.w3.org/2000/svg"}
[svg/path
{:d
"M67.9999 5L58.6356 5C47.5899 5 38.6356 13.9543 38.6356 25L38.6356 97C38.6356 108.046 29.6813 117 18.6356 117L5.00006 117"
:stroke "url(#gradient)"}]
[svg/circle
{:cx "68"
:cy "5"
:r "4"
:fill (colors/theme-colors colors/white colors/neutral-90 theme)
:stroke (colors/resolve-color destination theme)}]
[svg/circle
{:cx "5"
:cy "117"
:r "4"
:fill (colors/theme-colors colors/white colors/neutral-90 theme)
:stroke (colors/resolve-color source theme)}]
[svg/defs
[svg/linear-gradient
{:id "gradient"
:x1 "72.2711"
:y1 "5.00001"
:x2 "102.867"
:y2 "49.0993"
:gradientUnits "userSpaceOnUse"}
[svg/stop {:stop-color (colors/resolve-color destination theme)}]
[svg/stop {:offset "1" :stop-color (colors/resolve-color source theme)}]]]])
[]
(let [container-width (reagent/atom 100)
stroke-color "url(#gradient)"]
(fn [{:keys [source destination theme]}]
(let [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)
view-box (str "0 0 " @container-width " 114")
side-lines-path (helpers/calculate-side-lines-path-2x @container-width)
central-transform (helpers/calculate-transform @container-width)]
[rn/view
{:style style/link-2x-container
:on-layout #(reset! container-width
(oget % :nativeEvent :layout :width))}
[svg/svg
{:xmlns "http://www.w3.org/2000/svg"
:height "100%"
:width "100%"
:view-box view-box
:fill :none}
[svg/path
{:d (:left side-lines-path)
:stroke source-color}]
[svg/path
{:d
"M62.9999 1L53.6356 1C42.5899 1 33.6356 9.9543 33.6356 21L33.6356 93C33.6356 104.046 24.6813 113 13.6356 113L5.71778e-05 113"
:transform central-transform
:stroke stroke-color}]
[svg/path
{:d (:right side-lines-path)
:stroke destination-color}]
[svg/defs
[svg/linear-gradient
{:id "gradient"
:x1 "72.2711"
:y1 "5.00001"
:x2 "102.867"
:y2 "49.0993"
:gradient-units "userSpaceOnUse"}
[svg/stop {:stop-color (colors/resolve-color destination theme)}]
[svg/stop {:offset "1" :stop-color (colors/resolve-color source theme)}]]]]
[rn/view {:style style/bottom-left-circle-container}
[circle fill-color source-color]]
[rn/view {:style style/top-right-circle-container}
[circle fill-color destination-color]]]))))
(defn- view-internal
[{:keys [shape container-style] :as props}]

View File

@ -448,6 +448,8 @@
(def ^:const optimism-short-name "opt")
(def ^:const arbitrum-short-name "arb1")
(def ^:const default-multichain-address-prefix "eth:opt:arb1:")
(def ^:const mainnet-abbreviated-name "Eth.")
(def ^:const optimism-abbreviated-name "Opt.")
(def ^:const arbitrum-abbreviated-name "Arb1.")

View File

@ -1,6 +1,7 @@
(ns status-im.contexts.preview.quo.wallet.network-link
(:require
[quo.core :as quo]
[react-native.core :as rn]
[reagent.core :as reagent]
[status-im.contexts.preview.quo.preview :as preview]))
@ -23,17 +24,21 @@
:options networks}
{:key :destination
:type :select
:options networks}])
:options networks}
{:key :width
:type :number}])
(defn view
[]
(let [state (reagent/atom {:shape :linear
:source :ethereum
:destination :optimism})]
:destination :optimism
:width 63})]
(fn []
[preview/preview-container
{:state state
:descriptor descriptor
:component-container-style {:padding-top 40
:align-items :center}}
[quo/network-link @state]])))
[rn/view {:style {:width (max (:width @state) 63)}}
[quo/network-link @state]]])))

View File

@ -15,6 +15,7 @@
:button-one-label (i18n/label :t/confirm-bridge)
:button-one-props {:icon-left :i/bridge}
:on-navigate-back (fn []
(rf/dispatch [:wallet/clean-disabled-from-networks])
(rf/dispatch [:navigate-back]))}]])
(def view (quo.theme/with-theme view-internal))

View File

@ -0,0 +1,18 @@
(ns status-im.contexts.wallet.common.utils.networks
(:require [clojure.string :as string]
[status-im.constants :as constants]
[status-im.contexts.wallet.common.utils :as utils]))
(defn resolve-receiver-networks
[{:keys [prefix testnet-enabled? goerli-enabled?]}]
(let [prefix (if (string/blank? prefix)
constants/default-multichain-address-prefix
prefix)
prefix-seq (string/split prefix #":")]
(->> prefix-seq
(remove string/blank?)
(mapv
#(utils/network->chain-id
{:network %
:testnet-enabled? testnet-enabled?
:goerli-enabled? goerli-enabled?})))))

View File

@ -1,5 +1,6 @@
(ns status-im.contexts.wallet.common.utils.send
(:require [utils.money :as money]))
(:require [clojure.string :as string]
[utils.money :as money]))
(defn calculate-gas-fee
[data]
@ -20,3 +21,17 @@
(defn calculate-full-route-gas-fee
[route]
(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

@ -20,3 +20,44 @@
expected-eip1559-disabled-result (money/bignumber 0.000760406)]
(is (money/equal-to (utils/calculate-gas-fee data-eip1559-disabled)
expected-eip1559-disabled-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

@ -5,6 +5,7 @@
[native-module.core :as native-module]
[status-im.constants :as constants]
[status-im.contexts.wallet.common.utils :as utils]
[status-im.contexts.wallet.common.utils.networks :as network-utils]
[status-im.contexts.wallet.send.utils :as send-utils]
[taoensso.timbre :as log]
[utils.address :as address]
@ -24,45 +25,71 @@
(rf/reg-event-fx :wallet/suggested-routes-success
(fn [{:keys [db]} [suggested-routes timestamp]]
(when (= (get-in db [:wallet :ui :send :suggested-routes-call-timestamp]) timestamp)
(let [suggested-routes-data (cske/transform-keys transforms/->kebab-case-keyword suggested-routes)
chosen-route (:best suggested-routes-data)]
(let [suggested-routes-data (cske/transform-keys transforms/->kebab-case-keyword
suggested-routes)
chosen-route (:best suggested-routes-data)
token (get-in db [:wallet :ui :send :token])
collectible (get-in db [:wallet :ui :send :collectible])
token-display-name (get-in db [:wallet :ui :send :token-display-name])
token-decimals (if collectible 0 (:decimals token))
native-token? (and token (= token-display-name "ETH"))
from-network-amounts-by-chain (send-utils/network-amounts-by-chain {:route chosen-route
:token-decimals
token-decimals
:native-token?
native-token?
:to? false})
from-network-values-for-ui (send-utils/network-values-for-ui from-network-amounts-by-chain)
to-network-amounts-by-chain (send-utils/network-amounts-by-chain {:route chosen-route
:token-decimals
token-decimals
:native-token?
native-token?
:to? true})
to-network-values-for-ui (send-utils/network-values-for-ui to-network-amounts-by-chain)]
{:db (-> db
(assoc-in [:wallet :ui :send :suggested-routes] suggested-routes-data)
(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 :to-values-by-chain] to-network-values-for-ui)
(assoc-in [:wallet :ui :send :loading-suggested-routes?] false))}))))
(rf/reg-event-fx :wallet/suggested-routes-error
(fn [{:keys [db]} [_error]]
{:db (-> db
(update-in [:wallet :ui :send] dissoc :suggested-routes)
(update-in [:wallet :ui :send] dissoc :route)
(update-in [:wallet :ui :send] dissoc :suggested-routes :route)
(assoc-in [:wallet :ui :send :loading-suggested-routes?] false))}))
(rf/reg-event-fx :wallet/clean-suggested-routes
(fn [{:keys [db]}]
{:db (-> db
(update-in [:wallet :ui :send] dissoc :suggested-routes)
(update-in [:wallet :ui :send] dissoc :route)
(update-in [:wallet :ui :send] dissoc :loading-suggested-routes?))}))
{:db (update-in db
[:wallet :ui :send]
dissoc
:suggested-routes
:route
:from-values-by-chain
:to-values-by-chain
:loading-suggested-routes?
:suggested-routes-call-timestamp)}))
(rf/reg-event-fx :wallet/clean-send-address
(fn [{:keys [db]}]
{:db (update-in db [:wallet :ui :send] dissoc :recipient :to-address)}))
(rf/reg-event-fx :wallet/clean-disabled-from-networks
(fn [{:keys [db]}]
{:db (update-in db [:wallet :ui :send] dissoc :disabled-from-chain-ids)}))
(rf/reg-event-fx
:wallet/select-send-address
(fn [{:keys [db]} [{:keys [address recipient stack-id start-flow?]}]]
(let [[prefix to-address] (utils/split-prefix-and-address address)
testnet-enabled? (get-in db [:profile/profile :test-networks-enabled?])
goerli-enabled? (get-in db [:profile/profile :is-goerli-enabled?])
prefix-seq (string/split prefix #":")
selected-networks (->> prefix-seq
(remove string/blank?)
(mapv
#(utils/network->chain-id
{:network %
:testnet-enabled? testnet-enabled?
:goerli-enabled? goerli-enabled?})))]
selected-networks (network-utils/resolve-receiver-networks
{:prefix prefix
:testnet-enabled? testnet-enabled?
:goerli-enabled? goerli-enabled?})]
{:db (-> db
(assoc-in [:wallet :ui :send :recipient] (or recipient address))
(assoc-in [:wallet :ui :send :to-address] to-address)
@ -84,7 +111,8 @@
(fn [{:keys [db]} [{:keys [token stack-id start-flow?]}]]
{:db (-> db
(update-in [:wallet :ui :send] dissoc :collectible)
(assoc-in [:wallet :ui :send :token] token))
(assoc-in [:wallet :ui :send :token] token)
(assoc-in [:wallet :ui :send :token-display-name] (:symbol token)))
:fx [[:dispatch [:wallet/clean-suggested-routes]]
[:dispatch
[:wallet/wizard-navigate-forward
@ -95,13 +123,15 @@
(rf/reg-event-fx
:wallet/edit-token-to-send
(fn [{:keys [db]} [token]]
{:db (assoc-in db [:wallet :ui :send :token] token)
{:db (-> db
(assoc-in [:wallet :ui :send :token] token)
(assoc-in [:wallet :ui :send :token-display-name] token))
:fx [[:dispatch [:hide-bottom-sheet]]
[:dispatch [:wallet/clean-suggested-routes]]]}))
(rf/reg-event-fx :wallet/clean-selected-token
(fn [{:keys [db]}]
{:db (update-in db [:wallet :ui :send] dissoc :token :tx-type)}))
{:db (update-in db [:wallet :ui :send] dissoc :token :token-display-name :tx-type)}))
(rf/reg-event-fx :wallet/clean-selected-collectible
(fn [{:keys [db]}]
@ -110,18 +140,30 @@
[:wallet :ui :send]
dissoc
:collectible
:token-display-name
:amount
(when (= transaction-type :collecible) :tx-type))})))
(rf/reg-event-fx :wallet/send-collectibles-amount
(fn [{:keys [db]} [{:keys [collectible stack-id amount]}]]
{:db (-> db
(update-in [:wallet :ui :send] dissoc :token)
(assoc-in [:wallet :ui :send :collectible] collectible)
(assoc-in [:wallet :ui :send :tx-type] :collectible)
(assoc-in [:wallet :ui :send :amount] amount))
:fx [[:dispatch [:wallet/get-suggested-routes {:amount amount}]]
[:navigate-to-within-stack [:screen/wallet.transaction-confirmation stack-id]]]}))
(let [collection-data (:collection-data collectible)
collectible-data (:collectible-data collectible)
collectible-id (get-in collectible [:id :token-id])
token-display-name (cond
(and collectible
(not (string/blank? (:name collectible-data))))
(:name collectible-data)
collectible
(str (:name collection-data) " #" collectible-id))]
{:db (-> db
(update-in [:wallet :ui :send] dissoc :token)
(assoc-in [:wallet :ui :send :collectible] collectible)
(assoc-in [:wallet :ui :send :token-display-name] token-display-name)
(assoc-in [:wallet :ui :send :tx-type] :collectible)
(assoc-in [:wallet :ui :send :amount] amount))
:fx [[:dispatch [:wallet/get-suggested-routes {:amount amount}]]
[:navigate-to-within-stack [:screen/wallet.transaction-confirmation stack-id]]]})))
(rf/reg-event-fx :wallet/select-collectibles-amount
(fn [{:keys [db]} [{:keys [collectible stack-id]}]]
@ -140,6 +182,12 @@
:start-flow? start-flow?
:flow-id :wallet-flow}]]]}))
(rf/reg-event-fx :wallet/disable-from-networks
(fn [{:keys [db]} [chain-ids]]
{:db (-> db
(assoc-in [:wallet :ui :send :disabled-from-chain-ids] chain-ids)
(assoc-in [:wallet :ui :send :loading-suggested-routes?] true))}))
(rf/reg-event-fx :wallet/get-suggested-routes
(fn [{:keys [db now]} [{:keys [amount]}]]
(let [wallet-address (get-in db [:wallet :current-viewing-account-address])
@ -147,6 +195,7 @@
transaction-type (get-in db [:wallet :ui :send :tx-type])
collectible (get-in db [:wallet :ui :send :collectible])
to-address (get-in db [:wallet :ui :send :to-address])
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?])
networks ((if test-networks-enabled? :test :prod)
(get-in db [:wallet :networks]))
@ -163,7 +212,7 @@
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-to-chain-ids (if (= transaction-type :bridge)
(filter #(not= % bridge-to-chain-id) network-chain-ids)
[])
@ -218,6 +267,7 @@
{:fx [[:dispatch [:wallet/clean-scanned-address]]
[:dispatch [:wallet/clean-local-suggestions]]
[:dispatch [:wallet/clean-send-address]]
[:dispatch [:wallet/clean-disabled-from-networks]]
[:dispatch [:wallet/select-address-tab nil]]
[:dispatch [:dismiss-modal :screen/wallet.transaction-progress]]]}))

View File

@ -5,60 +5,75 @@
[status-im.contexts.wallet.send.input-amount.view :as input-amount]
[test-helpers.component :as h]
[utils.debounce :as debounce]
[utils.money :as money]
[utils.re-frame :as rf]))
(set! rf/dispatch #())
(set! debounce/debounce-and-dispatch #())
(def sub-mocks
{:profile/profile {:currency :usd}
:wallet/selected-network-details [{:source 525
:short-name "eth"
:network-name :mainnet
:chain-id 1
:related-chain-id 5}]
:wallet/current-viewing-account {:path "m/44'/60'/0'/0/1"
:emoji "💎"
:key-uid "0x2f5ea39"
:address "0x1"
:wallet false
:name "Account One"
:type :generated
:watch-only? false
:chat false
:test-preferred-chain-ids #{5 420 421613}
:color :purple
:hidden false
:prod-preferred-chain-ids #{1 10 42161}
:network-preferences-names #{:mainnet :arbitrum
:optimism}
:position 1
:clock 1698945829328
:created-at 1698928839000
:operable "fully"
:mixedcase-address "0x7bcDfc75c431"
:public-key "0x04371e2d9d66b82f056bc128064"
:removed false}
:wallet/wallet-send-token {:symbol :eth
:total-balance 100
:market-values-per-currency {:usd {:price 10}}}
:wallet/wallet-send-loading-suggested-routes? false
:wallet/wallet-send-route [{:from {:chainid 1
:native-currency-symbol "ETH"}
:to {:chain-id 1
:native-currency-symbol "ETH"}
:gas-amount "23487"
:gas-fees {:base-fee "32.325296406"
:max-priority-fee-per-gas "0.011000001"
:eip1559-enabled true}}]
:wallet/wallet-send-suggested-routes {:candidates []}
:wallet/wallet-send-selected-networks []
:view-id :screen/wallet.send-input-amount
:wallet/wallet-send-to-address "0x04371e2d9d66b82f056bc128064"
:profile/currency-symbol "$"
:wallet/token-by-symbol {:symbol :eth
:total-balance 100
:market-values-per-currency {:usd {:price 10}}}})
{:profile/profile {:currency :usd}
:wallet/selected-network-details [{:source 525
:short-name "eth"
:network-name :mainnet
:chain-id 1
:related-chain-id 5}]
:wallet/current-viewing-account {:path "m/44'/60'/0'/0/1"
:emoji "💎"
:key-uid "0x2f5ea39"
:address "0x1"
:wallet false
:name "Account One"
:type :generated
:watch-only? false
:chat false
:test-preferred-chain-ids #{5 420 421613}
:color :purple
:hidden false
:prod-preferred-chain-ids #{1 10 42161}
:network-preferences-names #{:mainnet :arbitrum
:optimism}
:position 1
:clock 1698945829328
:created-at 1698928839000
:operable "fully"
:mixedcase-address "0x7bcDfc75c431"
:public-key "0x04371e2d9d66b82f056bc128064"
:removed false}
:wallet/wallet-send-token {:symbol :eth
:networks [{:source 879
:short-name "eth"
:network-name :mainnet
:abbreviated-name "Eth."
:chain-id 1
:related-chain-id 1
:layer 1}]}
:wallet/current-viewing-account-tokens-filtered {:balances-per-chain {1 {:raw-balance
(money/bignumber
"2500")
:has-error false}}
:total-balance 100
:market-values-per-currency {:usd {:price 10}}}
:wallet/wallet-send-loading-suggested-routes? false
:wallet/wallet-send-route [{:from {:chainid 1
:native-currency-symbol "ETH"}
:to {:chain-id 1
:native-currency-symbol "ETH"}
:gas-amount "23487"
:gas-fees {:base-fee "32.325296406"
:max-priority-fee-per-gas "0.011000001"
:eip1559-enabled true}}]
:wallet/wallet-send-suggested-routes {:candidates []}
:wallet/wallet-send-selected-networks [1]
:view-id :screen/wallet.send-input-amount
:wallet/wallet-send-to-address "0x04371e2d9d66b82f056bc128064"
:profile/currency-symbol "$"
:wallet/token-by-symbol {:symbol :eth
:total-balance 100
:market-values-per-currency {:usd {:price 10}}}
:wallet/wallet-send-disabled-from-chain-ids []
:wallet/wallet-send-from-values-by-chain {1 (money/bignumber "250")}
:wallet/wallet-send-to-values-by-chain {1 (money/bignumber "250")}})
(h/describe "Send > input amount screen"
(h/setup-restorable-re-frame)

View File

@ -190,8 +190,9 @@
(reset-input-error num-value current-limit-amount input-error)
(reagent/flush))))
on-navigate-back on-navigate-back
fetch-routes (fn [input-num-value current-limit-amount]
(let [nav-current-screen-id (rf/sub [:view-id])]
fetch-routes (fn [input-num-value current-limit-amount bounce-duration-ms]
(let [nav-current-screen-id (rf/sub [:view-id])
input-num-value (or input-num-value 0)]
; this check is to prevent effect being triggered when screen is
; loaded but not being shown to the user (deep in the navigation
; stack) and avoid undesired behaviors
@ -201,7 +202,7 @@
(> input-num-value current-limit-amount))
(debounce/debounce-and-dispatch
[:wallet/get-suggested-routes {:amount @input-value}]
2000)
bounce-duration-ms)
(rf/dispatch [:wallet/clean-suggested-routes])))))
handle-on-confirm (fn []
(rf/dispatch [:wallet/send-select-amount
@ -215,65 +216,92 @@
(reset! input-selection selection)
(reagent/flush))]
(fn []
(let [{fiat-currency :currency} (rf/sub [:profile/profile])
{token-balance :total-balance
token-symbol :symbol
token-networks :networks
:as token} (rf/sub [:wallet/wallet-send-token])
conversion-rate (-> token :market-values-per-currency :usd :price)
loading-routes? (rf/sub [:wallet/wallet-send-loading-suggested-routes?])
suggested-routes (rf/sub [:wallet/wallet-send-suggested-routes])
best-routes (when suggested-routes (or (:best suggested-routes) []))
route (rf/sub [:wallet/wallet-send-route])
to-address (rf/sub [:wallet/wallet-send-to-address])
on-confirm (or default-on-confirm handle-on-confirm)
crypto-decimals (or default-crypto-decimals
(utils/get-crypto-decimals-count token))
crypto-limit (or default-limit-crypto
(utils/get-standard-crypto-format token token-balance))
fiat-limit (.toFixed (* token-balance conversion-rate) 2)
current-limit #(if @crypto-currency? crypto-limit fiat-limit)
current-currency (if @crypto-currency? token-symbol fiat-currency)
limit-label (make-limit-label {:amount (current-limit)
:currency current-currency})
input-num-value (parse-double @input-value)
confirm-disabled? (or (nil? route)
(empty? route)
(empty? @input-value)
(<= input-num-value 0)
(> input-num-value (current-limit)))
amount-text (str @input-value " " token-symbol)
native-currency-symbol (when-not confirm-disabled?
(get-in route [:from :native-currency-symbol]))
native-token (when native-currency-symbol
(rf/sub [:wallet/token-by-symbol native-currency-symbol]))
fee-in-native-token (when-not confirm-disabled?
(send-utils/calculate-full-route-gas-fee route))
fee-in-crypto-formatted (when fee-in-native-token
(utils/get-standard-crypto-format native-token
fee-in-native-token))
fee-in-fiat (when-not confirm-disabled?
(utils/calculate-token-fiat-value
{:currency fiat-currency
:balance fee-in-native-token
:token native-token}))
currency-symbol (rf/sub [:profile/currency-symbol])
fee-formatted (when fee-in-fiat
(utils/get-standard-fiat-format fee-in-crypto-formatted
currency-symbol
fee-in-fiat))
show-select-asset-sheet #(rf/dispatch
[:show-bottom-sheet
{:content (fn []
[select-asset-bottom-sheet clear-input!])}])]
(let [{fiat-currency :currency} (rf/sub [:profile/profile])
{token-symbol :symbol
token-networks :networks} (rf/sub [:wallet/wallet-send-token])
{token-balance :total-balance
token-balances-per-chain :balances-per-chain
:as
token} (rf/sub
[:wallet/current-viewing-account-tokens-filtered
(str token-symbol)])
conversion-rate (-> token :market-values-per-currency :usd :price)
loading-routes? (rf/sub
[:wallet/wallet-send-loading-suggested-routes?])
suggested-routes (rf/sub [:wallet/wallet-send-suggested-routes])
best-routes (when suggested-routes
(or (:best suggested-routes) []))
route (rf/sub [:wallet/wallet-send-route])
to-address (rf/sub [:wallet/wallet-send-to-address])
disabled-from-chain-ids (rf/sub
[:wallet/wallet-send-disabled-from-chain-ids])
from-values-by-chain (rf/sub [:wallet/wallet-send-from-values-by-chain])
to-values-by-chain (rf/sub [:wallet/wallet-send-to-values-by-chain])
on-confirm (or default-on-confirm handle-on-confirm)
crypto-decimals (or default-crypto-decimals
(utils/get-crypto-decimals-count token))
crypto-limit (or default-limit-crypto
(utils/get-standard-crypto-format
token
token-balance))
fiat-limit (.toFixed (* token-balance conversion-rate) 2)
current-limit #(if @crypto-currency? crypto-limit fiat-limit)
current-currency (if @crypto-currency? token-symbol fiat-currency)
limit-label (make-limit-label {:amount (current-limit)
:currency current-currency})
input-num-value (parse-double @input-value)
confirm-disabled? (or (nil? route)
(empty? route)
(empty? @input-value)
(<= input-num-value 0)
(> input-num-value (current-limit)))
amount-text (str @input-value " " token-symbol)
native-currency-symbol (when-not confirm-disabled?
(get-in route [:from :native-currency-symbol]))
native-token (when native-currency-symbol
(rf/sub [:wallet/token-by-symbol
native-currency-symbol]))
fee-in-native-token (when-not confirm-disabled?
(send-utils/calculate-full-route-gas-fee route))
fee-in-crypto-formatted (when fee-in-native-token
(utils/get-standard-crypto-format
native-token
fee-in-native-token))
fee-in-fiat (when-not confirm-disabled?
(utils/calculate-token-fiat-value
{:currency fiat-currency
:balance fee-in-native-token
:token native-token}))
currency-symbol (rf/sub [:profile/currency-symbol])
fee-formatted (when fee-in-fiat
(utils/get-standard-fiat-format
fee-in-crypto-formatted
currency-symbol
fee-in-fiat))
show-select-asset-sheet #(rf/dispatch
[:show-bottom-sheet
{:content (fn []
[select-asset-bottom-sheet
clear-input!])}])
selected-networks (rf/sub [:wallet/wallet-send-selected-networks])
affordable-networks (send-utils/find-affordable-networks
{:balances-per-chain token-balances-per-chain
:input-value @input-value
:selected-networks selected-networks
:disabled-chain-ids disabled-from-chain-ids})]
(rn/use-mount
(fn []
(let [dismiss-keyboard-fn #(when (= % "active") (rn/dismiss-keyboard!))
app-keyboard-listener (.addEventListener rn/app-state "change" dismiss-keyboard-fn)]
#(.remove app-keyboard-listener))))
(rn/use-effect
#(fetch-routes input-num-value (current-limit))
#(when (> (count affordable-networks) 0)
(fetch-routes input-num-value (current-limit) 2000))
[@input-value])
(rn/use-effect
#(when (> (count affordable-networks) 0)
(fetch-routes input-num-value (current-limit) 0))
[disabled-from-chain-ids])
[rn/view
{:style style/screen
:accessibility-label (str "container" (when @input-error "-error"))}
@ -303,11 +331,28 @@
:limit-crypto crypto-limit})
:on-token-press show-select-asset-sheet}]
[routes/view
{:amount amount-text
:routes best-routes
:token token
:input-value @input-value
:fetch-routes #(fetch-routes % (current-limit))}]
{:from-values-by-chain from-values-by-chain
:to-values-by-chain to-values-by-chain
:affordable-networks affordable-networks
:routes best-routes
:token token
:input-value @input-value
:fetch-routes #(fetch-routes % (current-limit) 2000)
:disabled-from-networks disabled-from-chain-ids
:on-press-from-network (fn [chain-id _]
(let [disabled-chain-ids (if (contains? (set
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]))))}]
(when (or loading-routes? (seq route))
[estimated-fees
{:loading-suggested-routes? loading-routes?

View File

@ -12,20 +12,23 @@
{:flex-direction :row
:justify-content :space-between})
(def routes-inner-container
{:margin-top 8
(defn routes-inner-container
[first-item?]
{:margin-top (if first-item? 7.5 11)
:flex-direction :row
:align-items :center
:justify-content :space-between})
(defn section-label
[margin-left]
{:flex 0.5
:margin-left margin-left})
(def section-label-right
{:width 135})
(def section-label-left
{:width 136})
(def network-link
{:right 6
:z-index 1})
{:margin-horizontal -1.5
:z-index 1
:flex 1})
(def empty-container
{:flex-grow 1
@ -33,9 +36,8 @@
:justify-content :center})
(def add-network
{:margin-top 8
:align-self :flex-end
:right 12})
{:margin-top 11
:align-self :flex-end})
(defn warning-container
[color theme]

View File

@ -7,19 +7,17 @@
[quo.theme :as quo.theme]
[react-native.core :as rn]
[reagent.core :as reagent]
[status-im.constants :as constants]
[status-im.contexts.wallet.common.utils :as utils]
[status-im.contexts.wallet.send.routes.style :as style]
[utils.i18n :as i18n]
[utils.re-frame :as rf]))
[utils.re-frame :as rf]
[utils.vector :as vector-utils]))
(defn- find-affordable-networks
[{:keys [balances-per-chain]} input-value selected-networks]
(->> balances-per-chain
(filter (fn [[_ {:keys [balance chain-id]}]]
(and
(>= (js/parseFloat balance) input-value)
(some #(= % chain-id) selected-networks))))
(map first)))
(def ^:private network-priority-score
{:ethereum 1
:optimism 2
:arbitrum 3})
(defn- make-network-item
[{:keys [network-name chain-id] :as _network}
@ -34,6 +32,36 @@
:checked? (some #(= % chain-id) @network-preferences)
:on-change on-change}})
(defn- find-network-link-insertion-index
[network-links chain-id loading-suggested-routes?]
(let [network (utils/id->network chain-id)
inserted-network-link-priority-score (network-priority-score network)]
(or (->> network-links
(keep-indexed (fn [idx network-link]
(let [network-link (utils/id->network (if loading-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 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 (utils/id->network chain-id)}]
(vector-utils/insert-element-at acc-network-links disabled-network-link index)))
network-links
sorted-networks)))
(defn networks-drawer
[{:keys [fetch-routes theme]}]
(let [network-details (rf/sub [:wallet/network-details])
@ -90,7 +118,9 @@
:customization-color color}}]])))
(defn route-item
[{:keys [amount from-network to-network status theme fetch-routes]}]
[{:keys [first-item? from-amount to-amount token-symbol from-chain-id to-chain-id from-network
to-network on-press-from-network on-press-to-network status theme fetch-routes disabled?
loading?]}]
(if (= status :add)
[quo/network-bridge
{:status :add
@ -99,59 +129,115 @@
{:content (fn [] [networks-drawer
{:theme theme
:fetch-routes fetch-routes}])}])}]
[rn/view {:style style/routes-inner-container}
[rn/view {:style (style/routes-inner-container first-item?)}
[quo/network-bridge
{:amount amount
:network from-network
:status status}]
{:amount (str from-amount " " token-symbol)
:network from-network
:status status
:on-press #(when (and on-press-from-network (not loading?))
(on-press-from-network from-chain-id from-amount))}]
(if (= status :default)
[quo/network-link
{:shape :linear
:source from-network
:destination to-network
:container-style style/network-link}]
[rn/view {:style {:width 73}}])
[rn/view {:style {:flex 1}}])
[quo/network-bridge
{:amount amount
:network to-network
:status status
:container-style {:right 12}}]]))
{: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
[item index _
{:keys [from-values-by-chain to-values-by-chain theme fetch-routes on-press-from-network
on-press-to-network token-symbol loading-suggested-routes?]}]
(let [first-item? (zero? index)
disabled-network? (= (:status item) :disabled)
from-chain-id (get-in item [:from :chain-id])
to-chain-id (get-in item [:to :chain-id])
from-amount (when from-chain-id
(from-values-by-chain from-chain-id))
to-amount (when to-chain-id
(to-values-by-chain to-chain-id))]
[route-item
{:first-item? first-item?
:from-amount (if disabled-network? 0 from-amount)
:to-amount (if disabled-network? 0 to-amount)
:token-symbol token-symbol
:disabled? disabled-network?
:loading? loading-suggested-routes?
:theme theme
:fetch-routes fetch-routes
:status (cond
(= (:status item) :add) :add
(= (:status item) :disabled) :disabled
loading-suggested-routes? :loading
:else :default)
:from-chain-id (or from-chain-id (:chain-id item))
:to-chain-id (or to-chain-id (:chain-id item))
:from-network (cond (and loading-suggested-routes?
(not disabled-network?))
(utils/id->network item)
disabled-network?
(utils/id->network (:chain-id
item))
:else
(utils/id->network from-chain-id))
:to-network (cond (and loading-suggested-routes?
(not disabled-network?))
(utils/id->network item)
disabled-network?
(utils/id->network (:chain-id
item))
:else
(utils/id->network to-chain-id))
:on-press-from-network on-press-from-network
:on-press-to-network on-press-to-network}]))
(defn- view-internal
[{:keys [amount routes token input-value theme fetch-routes]}]
(let [selected-networks (rf/sub [:wallet/wallet-send-selected-networks])
loading-networks (find-affordable-networks token input-value selected-networks)
[{:keys [from-values-by-chain to-values-by-chain routes token theme fetch-routes
affordable-networks disabled-from-networks on-press-from-network on-press-to-network]}]
(let [token-symbol (:symbol token)
loading-suggested-routes? (rf/sub [:wallet/wallet-send-loading-suggested-routes?])
data (if loading-suggested-routes? loading-networks routes)]
(if (or (and (not-empty loading-networks) loading-suggested-routes?) (not-empty routes))
[rn/flat-list
{:data (if (and (< (count data) 3) (pos? (count data)))
(concat data [{:status :add}])
data)
:content-container-style style/routes-container
:header [rn/view {:style style/routes-header-container}
[quo/section-label
{:section (i18n/label :t/from-label)
:container-style (style/section-label 0)}]
[quo/section-label
{:section (i18n/label :t/to-label)
:container-style (style/section-label 64)}]]
:render-fn (fn [item]
[route-item
{:amount amount
:theme theme
:fetch-routes fetch-routes
:status (cond
(= (:status item) :add) :add
loading-suggested-routes? :loading
:else :default)
:from-network (if loading-suggested-routes?
(utils/id->network item)
(utils/id->network (get-in item [:from :chain-id])))
:to-network (if loading-suggested-routes?
(utils/id->network item)
(utils/id->network (get-in item
[:to :chain-id])))}])}]
network-links (if loading-suggested-routes? affordable-networks routes)]
(if (or (and (not-empty affordable-networks) loading-suggested-routes?) (not-empty routes))
(let [initial-network-links-count (count network-links)
disabled-count (count disabled-from-networks)
network-links (if (not-empty disabled-from-networks)
(add-disabled-networks network-links
disabled-from-networks
loading-suggested-routes?)
network-links)
network-links-with-add-button (if (and (< (- (count network-links) disabled-count)
constants/default-network-count)
(pos? initial-network-links-count))
(concat network-links [{:status :add}])
network-links)]
[rn/flat-list
{:data network-links-with-add-button
:content-container-style style/routes-container
:header [rn/view {:style style/routes-header-container}
[quo/section-label
{:section (i18n/label :t/from-label)
:container-style style/section-label-left}]
[quo/section-label
{:section (i18n/label :t/to-label)
:container-style style/section-label-right}]]
:render-data {:from-values-by-chain from-values-by-chain
:to-values-by-chain to-values-by-chain
:theme theme
:fetch-routes fetch-routes
:on-press-from-network on-press-from-network
: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

@ -132,6 +132,7 @@
(rf/dispatch [:wallet/clean-selected-token])
(rf/dispatch [:wallet/clean-selected-collectible])
(rf/dispatch [:wallet/clean-send-address])
(rf/dispatch [:wallet/clean-disabled-from-networks])
(rf/dispatch [:wallet/select-address-tab nil])
(rf/dispatch [:navigate-back]))
on-change-tab #(rf/dispatch [:wallet/select-address-tab %])

View File

@ -10,6 +10,8 @@
[input-amount/view
{:current-screen-id :screen/wallet.send-input-amount
:button-one-label (i18n/label :t/confirm)
:on-navigate-back #(rf/dispatch [:navigate-back])}])
:on-navigate-back (fn []
(rf/dispatch [:wallet/clean-disabled-from-networks])
(rf/dispatch [:navigate-back]))}])
(def view (quo.theme/with-theme view-internal))

View File

@ -1,23 +1,21 @@
(ns status-im.contexts.wallet.send.transaction-confirmation.view
(:require
[clojure.string :as string]
[legacy.status-im.utils.hex :as utils.hex]
[legacy.status-im.utils.utils :as utils]
[native-module.core :as native-module]
[quo.core :as quo]
[quo.theme :as quo.theme]
[react-native.core :as rn]
[react-native.safe-area :as safe-area]
[status-im.common.floating-button-page.view :as floating-button-page]
[status-im.common.standard-authentication.core :as standard-auth]
[status-im.contexts.wallet.common.utils :as wallet-utils]
[status-im.contexts.wallet.send.transaction-confirmation.style :as style]
[utils.i18n :as i18n]
[utils.money :as money]
[utils.re-frame :as rf]
[utils.security.core :as security]))
(defn- transaction-title
[{:keys [token-symbol amount account to-address route to-network image-url transaction-type
[{:keys [token-display-name amount account to-address route to-network image-url transaction-type
collectible?]}]
(let [to-network-name (:network-name to-network)
to-network-color (if (= to-network-name :mainnet) :ethereum to-network-name)]
@ -32,8 +30,8 @@
(i18n/label :t/bridge)
(i18n/label :t/send))]
[quo/summary-tag
{:token (if collectible? "" token-symbol)
:label (str amount " " token-symbol)
{:token (if collectible? "" token-display-name)
:label (str amount " " token-display-name)
:type (if collectible? :collectible :token)
:image-source (if collectible? image-url :eth)}]]
(if (= transaction-type :bridge)
@ -125,84 +123,34 @@
:emoji (:emoji account)
:customization-color (:color account)}]])]))
(defn network-name-from-chain-id
[chain-id]
(let [network-name (-> (rf/sub [:wallet/network-details-by-chain-id chain-id])
:network-name)]
(if (= network-name :mainnet) :ethereum network-name)))
(defn- network-amounts-from-route
[{:keys [route token-symbol token-decimals to?]}]
(reduce (fn [acc path]
(let [network (if to? (:to path) (:from path))
chain-id (:chain-id network)
amount-hex (if to? (:amount-in path) (:amount-out path))
amount-units (native-module/hex-to-number
(utils.hex/normalize-hex amount-hex))
amount (money/with-precision
(if (= token-symbol "ETH")
(money/wei->ether amount-units)
(money/token->unit amount-units
token-decimals))
6)
network-name (network-name-from-chain-id chain-id)]
(merge-with money/add acc {network-name amount})))
{}
route))
(defn- network-values-from-amounts
[network-amounts token-symbol]
(reduce-kv (fn [acc k v]
(assoc acc
k
{:amount v
:token-symbol token-symbol}))
{}
network-amounts))
(defn- sanitize-network-values
[network-values]
(into {}
(map (fn [[k v]]
[k
(if (money/equal-to (v :amount) 0)
(assoc v :amount "<0.01")
v)])
network-values)))
(defn- values-by-network
[{:keys [collectible amount token-symbol route token-decimals to?]}]
(if collectible
(let [collectible-chain-id (get-in collectible [:id :contract-id :chain-id])
network-name (network-name-from-chain-id collectible-chain-id)]
{network-name {:amount amount :token-symbol token-symbol}})
(let [network-amounts (network-amounts-from-route {:route route
:token-symbol token-symbol
:token-decimals token-decimals
:to? to?})
network-values (network-values-from-amounts network-amounts token-symbol)]
(sanitize-network-values network-values))))
(defn- user-summary
[{:keys [account-props theme label accessibility-label
summary-type network-values]
:as _props}]
[rn/view
{:style {:padding-horizontal 20
:padding-bottom 16}}
[quo/text
{:size :paragraph-2
:weight :medium
:style (style/section-label theme)
:accessibility-label accessibility-label}
label]
[quo/summary-info
{:type summary-type
:networks? true
:values network-values
:account-props account-props}]])
[{:keys [network-values token-display-name account-props theme label accessibility-label
summary-type]}]
(let [network-values
(reduce-kv
(fn [acc chain-id amount]
(let [network-name (wallet-utils/id->network chain-id)]
(assoc acc
(if (= network-name :mainnet) :ethereum network-name)
{:amount amount :token-symbol token-display-name})))
{}
network-values)]
[rn/view
{:style {:padding-horizontal 20
:padding-bottom 16}}
[quo/text
{:size :paragraph-2
:weight :medium
:style (style/section-label theme)
:accessibility-label accessibility-label}
label]
[quo/summary-info
{:type summary-type
:networks? true
:values network-values
:account-props account-props}]]))
(defn data-item
(defn- data-item
[{:keys [title subtitle]}]
[quo/data-item
{:container-style style/detail-item
@ -217,7 +165,8 @@
:subtitle subtitle}])
(defn- transaction-details
[{:keys [estimated-time-min max-fees token-symbol amount to-address to-network route transaction-type
[{:keys [estimated-time-min max-fees token-display-name amount to-address to-network route
transaction-type
theme]}]
(let [currency-symbol (rf/sub [:profile/currency-symbol])
route-loaded? (and route (seq route))
@ -253,56 +202,42 @@
(i18n/label :t/bridged-to
{:network (:abbreviated-name to-network)})
(i18n/label :t/user-gets {:name (utils/get-shortened-address to-address)}))
:subtitle (str amount " " token-symbol)}]]
:subtitle (str amount " " token-display-name)}]]
:else
[quo/text {:style {:align-self :center}}
(i18n/label :t/no-routes-found-confirmation)])]]))
(defn collectible-token-symbol
[collectible]
(let [collection-data (:collection-data collectible)
collectible-data (:collectible-data collectible)
collectible-id (get-in collectible [:id :token-id])]
(first (remove
string/blank?
[(:name collectible-data)
(str (:name collection-data) " #" collectible-id)]))))
(defn- view-internal
[_]
(let [on-close (fn []
(rf/dispatch [:wallet/clean-suggested-routes])
(rf/dispatch [:navigate-back]))]
(fn [{:keys [theme]}]
(let [send-transaction-data (rf/sub [:wallet/wallet-send])
{:keys [token collectible amount route
to-address bridge-to-chain-id]} send-transaction-data
collectible? (some? collectible)
token-symbol (if collectible
(collectible-token-symbol collectible)
(:symbol token))
token-decimals (if collectible 0 (:decimals token))
image-url (when collectible
(get-in collectible [:preview-url :uri]))
transaction-type (:tx-type send-transaction-data)
estimated-time-min (reduce + (map :estimated-time route))
max-fees "-"
account (rf/sub [:wallet/current-viewing-account])
account-color (:color account)
bridge-to-network (when bridge-to-chain-id
(rf/sub [:wallet/network-details-by-chain-id
bridge-to-chain-id]))
from-account-props {:customization-color account-color
:size 32
:emoji (:emoji account)
:type :default
:name (:name account)
:address (utils/get-shortened-address
(:address
account))}
user-props {:full-name to-address
:address (utils/get-shortened-address
to-address)}]
(let [send-transaction-data (rf/sub [:wallet/wallet-send])
{:keys [token-display-name collectible amount route
to-address bridge-to-chain-id
from-values-by-chain
to-values-by-chain]} send-transaction-data
collectible? (some? collectible)
image-url (when collectible
(get-in collectible [:preview-url :uri]))
transaction-type (:tx-type send-transaction-data)
estimated-time-min (reduce + (map :estimated-time route))
max-fees "-"
account (rf/sub [:wallet/current-viewing-account])
account-color (:color account)
bridge-to-network (when bridge-to-chain-id
(rf/sub [:wallet/network-details-by-chain-id
bridge-to-chain-id]))
from-account-props {:customization-color account-color
:size 32
:emoji (:emoji account)
:type :default
:name (:name account)
:address (utils/get-shortened-address (:address
account))}
user-props {:full-name to-address
:address (utils/get-shortened-address to-address)}]
[rn/view {:style {:flex 1}}
[floating-button-page/view
{:footer-container-padding 0
@ -329,29 +264,26 @@
:customization-color (:color account)}
[rn/view
[transaction-title
{:token-symbol token-symbol
:amount amount
:account account
:to-address to-address
:route route
:to-network bridge-to-network
:image-url image-url
:transaction-type transaction-type
:collectible? collectible?}]
{:token-display-name token-display-name
:amount amount
:account account
:to-address to-address
:route route
:to-network bridge-to-network
:image-url image-url
:transaction-type transaction-type
:collectible? collectible?}]
[user-summary
{:summary-type :status-account
{:token-display-name token-display-name
:summary-type :status-account
:accessibility-label :summary-from-label
:label (i18n/label :t/from-capitalized)
:network-values from-values-by-chain
:account-props from-account-props
:theme theme
:network-values (values-by-network {:collectible collectible
:amount amount
:token-symbol token-symbol
:route route
:token-decimals token-decimals
:to? false})}]
:theme theme}]
[user-summary
{:summary-type (if (= transaction-type :bridge)
{:token-display-name token-display-name
:summary-type (if (= transaction-type :bridge)
:status-account
:account)
:accessibility-label :summary-to-label
@ -359,17 +291,12 @@
:account-props (if (= transaction-type :bridge)
from-account-props
user-props)
:theme theme
:network-values (values-by-network {:collectible collectible
:amount amount
:token-symbol token-symbol
:route route
:token-decimals token-decimals
:to? true})}]
:network-values to-values-by-chain
:theme theme}]
[transaction-details
{:estimated-time-min estimated-time-min
:max-fees max-fees
:token-symbol token-symbol
:token-display-name token-display-name
:amount amount
:to-address to-address
:to-network bridge-to-network

View File

@ -1,5 +1,8 @@
(ns status-im.contexts.wallet.send.utils
(:require [utils.money :as money]))
(:require
[legacy.status-im.utils.hex :as utils.hex]
[native-module.core :as native-module]
[utils.money :as money]))
(defn amount-in-hex
[amount token-decimal]
@ -20,3 +23,27 @@
value1)))
{}
transaction-hashes))
(defn network-amounts-by-chain
[{:keys [route token-decimals native-token? to?]}]
(reduce (fn [acc path]
(let [amount-hex (if to? (:amount-in path) (:amount-out 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 to? (get-in path [:to :chain-id]) (get-in path [:from :chain-id]))]
(update acc chain-id money/add amount)))
{}
route))
(defn network-values-for-ui
[amounts]
(reduce-kv (fn [acc k v]
(assoc acc k (if (money/equal-to v 0) "<0.01" v)))
{}
amounts))

View File

@ -1,6 +1,7 @@
(ns status-im.contexts.wallet.send.utils-test
(:require [cljs.test :refer [deftest is testing]]
[status-im.contexts.wallet.send.utils :as utils]))
[status-im.contexts.wallet.send.utils :as utils]
[utils.money :as money]))
(deftest test-amount-in-hex
(testing "Test amount-in-hex function"
@ -27,3 +28,68 @@
"0x11" {:status :pending
:id 61
:chain-id :420}})))))
(deftest test-network-amounts-by-chain
(testing "Correctly calculates network amounts for transaction with native token"
(let [route [{:amount-in "0xde0b6b3a7640000"
:to {:chain-id "1"}}
{:amount-in "0xde0b6b3a7640000"
:to {:chain-id "2"}}]
token-decimals 18
native-token? true
to? true
result (utils/network-amounts-by-chain {:route route
:token-decimals token-decimals
:native-token? native-token?
:to? to?})
expected {"1" (money/bignumber "1")
"2" (money/bignumber "1")}]
(doseq [[chain-id exp-value] expected]
(is (money/equal-to (get result chain-id) exp-value)))))
(testing
"Correctly calculates network amounts for transaction with native token and multiple routes to same chain-id"
(let [route [{:amount-in "0xde0b6b3a7640000"
:to {:chain-id "1"}}
{:amount-in "0xde0b6b3a7640000"
:to {:chain-id "1"}}]
token-decimals 18
native-token? true
to? true
result (utils/network-amounts-by-chain {:route route
:token-decimals token-decimals
:native-token? native-token?
:to? to?})
expected {"1" (money/bignumber "2")}]
(doseq [[chain-id exp-value] expected]
(is (money/equal-to (get result chain-id) exp-value)))))
(testing "Correctly calculates network amounts for transaction with non-native token"
(let [route [{:amount-out "0x1e8480"
:from {:chain-id "1"}}
{:amount-out "0x1e8480"
:from {:chain-id "2"}}]
token-decimals 6
native-token? false
to? false
result (utils/network-amounts-by-chain {:route route
:token-decimals token-decimals
:native-token? native-token?
:to? to?})
expected {"1" (money/bignumber "2")
"2" (money/bignumber "2")}]
(doseq [[chain-id exp-value] expected]
(is (money/equal-to (get result chain-id) exp-value))))))
(deftest test-network-values-for-ui
(testing "Sanitizes values correctly for display"
(let [amounts {"1" (money/bignumber "0")
"2" (money/bignumber "2.5")
"3" (money/bignumber "0.005")}
result (utils/network-values-for-ui amounts)
expected {"1" "<0.01"
"2" (money/bignumber "2.5")
"3" (money/bignumber "0.005")}]
(doseq [[chain-id exp-value] expected]
(is #(or (= (get result chain-id) exp-value)
(money/equal-to (get result chain-id) exp-value)))))))

View File

@ -95,6 +95,21 @@
:<- [:wallet/wallet-send]
:-> :token)
(rf/reg-sub
:wallet/wallet-send-disabled-from-chain-ids
:<- [:wallet/wallet-send]
:-> :disabled-from-chain-ids)
(rf/reg-sub
:wallet/wallet-send-from-values-by-chain
:<- [:wallet/wallet-send]
:-> :from-values-by-chain)
(rf/reg-sub
:wallet/wallet-send-to-values-by-chain
:<- [:wallet/wallet-send]
:-> :to-values-by-chain)
(rf/reg-sub
:wallet/wallet-send-loading-suggested-routes?
:<- [:wallet/wallet-send]

7
src/utils/vector.cljs Normal file
View File

@ -0,0 +1,7 @@
(ns utils.vector)
(defn insert-element-at
[data element index]
(let [before (take index data)
after (drop index data)]
(vec (concat before [element] after))))

View File

@ -0,0 +1,17 @@
(ns utils.vector-test
(:require
[cljs.test :refer-macros [deftest is testing]]
[utils.vector :as vector]))
(deftest test-insert-element-at
(testing "Inserting into an empty vector"
(is (= [42] (vector/insert-element-at [] 42 0))))
(testing "Inserting at the beginning of a vector"
(is (= [42 1 2 3] (vector/insert-element-at [1 2 3] 42 0))))
(testing "Inserting in the middle of a vector"
(is (= [1 42 2 3] (vector/insert-element-at [1 2 3] 42 1))))
(testing "Inserting at the end of a vector"
(is (= [1 2 3 42] (vector/insert-element-at [1 2 3] 42 3)))))