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,72 +1,137 @@
(ns quo.components.wallet.network-link.view (ns quo.components.wallet.network-link.view
(:require (: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.schema :as component-schema]
[quo.components.wallet.network-link.style :as style]
[quo.foundations.colors :as colors] [quo.foundations.colors :as colors]
[quo.theme :as quo.theme] [quo.theme :as quo.theme]
[react-native.core :as rn] [react-native.core :as rn]
[react-native.svg :as svg] [react-native.svg :as svg]
[reagent.core :as reagent]
[schema.core :as schema])) [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 (defn link-linear
[{:keys [source theme]}] []
[svg/svg {:xmlns "http://www.w3.org/2000/svg" :width "73" :height "10" :fill :none} (let [container-width (reagent/atom 100)]
[svg/path {:stroke (colors/resolve-color source theme) :d "M68 5H5"}] (fn [{:keys [source theme]}]
[svg/circle (let [stroke-color (colors/resolve-color source theme)
{:cx "68" fill-color (colors/theme-colors colors/white colors/neutral-90 theme)]
:cy "5" [rn/view
:r "4" {:style style/link-linear-container
:fill (colors/theme-colors colors/white colors/neutral-90 theme) :on-layout (fn [e]
:stroke (colors/resolve-color source theme)}] (reset! container-width
[svg/circle (oget e :nativeEvent :layout :width)))}
{:cx "5" [line stroke-color @container-width]
:cy "5" [rn/view {:style style/left-circle-container}
:r "4" [circle fill-color stroke-color]]
:fill (colors/theme-colors colors/white colors/neutral-90 theme) [rn/view {:style style/right-circle-container}
:stroke (colors/resolve-color source theme)}]]) [circle fill-color stroke-color]]]))))
(defn link-1x (defn link-1x
[{:keys [source destination theme]}] []
[svg/svg {:xmlns "http://www.w3.org/2000/svg" :width "73" :height "66" :fill :none} (let [container-width (reagent/atom 100)
[svg/path stroke-color "url(#gradient)"]
{:stroke "url(#gradient)" :d "M68 5h-9.364c-11.046 0-20 8.954-20 20v16c0 11.046-8.955 20-20 20H5"}] (fn [{:keys [source destination theme]}]
[svg/circle (let [source-color (colors/resolve-color source theme)
{:cx "68" destination-color (colors/resolve-color destination theme)
:cy "5" fill-color (colors/theme-colors colors/white colors/neutral-90 theme)
:r "4" view-box (str "0 0 " @container-width " 58")
:fill (colors/theme-colors colors/white colors/neutral-90 theme) side-lines-path (helpers/calculate-side-lines-path-1x @container-width)
:stroke (colors/resolve-color destination theme)}] central-transform (helpers/calculate-transform @container-width)]
[svg/circle [rn/view
{:cx "5" {:style style/link-1x-container
:cy "61" :on-layout (fn [e]
:r "4" (reset! container-width
:fill (colors/theme-colors colors/white colors/neutral-90 theme) (oget e :nativeEvent :layout :width)))}
: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)}]]]])
(defn link-2x
[{:keys [source destination theme]}]
[svg/svg [svg/svg
{:width "73" :height "122" :viewBox "0 0 73 122" :fill "none" :xmlns "http://www.w3.org/2000/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 [svg/path
{:d {: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" "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"
:stroke "url(#gradient)"}] :transform central-transform
[svg/circle :stroke stroke-color}]
{:cx "68" [svg/path
:cy "5" {:d (:right side-lines-path)
:r "4" :stroke destination-color}]
:fill (colors/theme-colors colors/white colors/neutral-90 theme) [svg/defs
:stroke (colors/resolve-color destination theme)}] [svg/linear-gradient
[svg/circle {:id "gradient"
{:cx "5" :x1 "72.271"
:cy "117" :x2 "82.385"
:r "4" :y1 "5"
:fill (colors/theme-colors colors/white colors/neutral-90 theme) :y2 "34.155"
:stroke (colors/resolve-color source theme)}] :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
[]
(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/defs
[svg/linear-gradient [svg/linear-gradient
{:id "gradient" {:id "gradient"
@ -74,9 +139,13 @@
:y1 "5.00001" :y1 "5.00001"
:x2 "102.867" :x2 "102.867"
:y2 "49.0993" :y2 "49.0993"
:gradientUnits "userSpaceOnUse"} :gradient-units "userSpaceOnUse"}
[svg/stop {:stop-color (colors/resolve-color destination theme)}] [svg/stop {:stop-color (colors/resolve-color destination theme)}]
[svg/stop {:offset "1" :stop-color (colors/resolve-color source 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 (defn- view-internal
[{:keys [shape container-style] :as props}] [{:keys [shape container-style] :as props}]

View File

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

View File

@ -1,6 +1,7 @@
(ns status-im.contexts.preview.quo.wallet.network-link (ns status-im.contexts.preview.quo.wallet.network-link
(:require (:require
[quo.core :as quo] [quo.core :as quo]
[react-native.core :as rn]
[reagent.core :as reagent] [reagent.core :as reagent]
[status-im.contexts.preview.quo.preview :as preview])) [status-im.contexts.preview.quo.preview :as preview]))
@ -23,17 +24,21 @@
:options networks} :options networks}
{:key :destination {:key :destination
:type :select :type :select
:options networks}]) :options networks}
{:key :width
:type :number}])
(defn view (defn view
[] []
(let [state (reagent/atom {:shape :linear (let [state (reagent/atom {:shape :linear
:source :ethereum :source :ethereum
:destination :optimism})] :destination :optimism
:width 63})]
(fn [] (fn []
[preview/preview-container [preview/preview-container
{:state state {:state state
:descriptor descriptor :descriptor descriptor
:component-container-style {:padding-top 40 :component-container-style {:padding-top 40
:align-items :center}} :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-label (i18n/label :t/confirm-bridge)
:button-one-props {:icon-left :i/bridge} :button-one-props {:icon-left :i/bridge}
:on-navigate-back (fn [] :on-navigate-back (fn []
(rf/dispatch [:wallet/clean-disabled-from-networks])
(rf/dispatch [:navigate-back]))}]]) (rf/dispatch [:navigate-back]))}]])
(def view (quo.theme/with-theme view-internal)) (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 (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 (defn calculate-gas-fee
[data] [data]
@ -20,3 +21,17 @@
(defn calculate-full-route-gas-fee (defn calculate-full-route-gas-fee
[route] [route]
(reduce money/add (map calculate-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)] expected-eip1559-disabled-result (money/bignumber 0.000760406)]
(is (money/equal-to (utils/calculate-gas-fee data-eip1559-disabled) (is (money/equal-to (utils/calculate-gas-fee data-eip1559-disabled)
expected-eip1559-disabled-result)))))) 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] [native-module.core :as native-module]
[status-im.constants :as constants] [status-im.constants :as constants]
[status-im.contexts.wallet.common.utils :as utils] [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] [status-im.contexts.wallet.send.utils :as send-utils]
[taoensso.timbre :as log] [taoensso.timbre :as log]
[utils.address :as address] [utils.address :as address]
@ -24,45 +25,71 @@
(rf/reg-event-fx :wallet/suggested-routes-success (rf/reg-event-fx :wallet/suggested-routes-success
(fn [{:keys [db]} [suggested-routes timestamp]] (fn [{:keys [db]} [suggested-routes timestamp]]
(when (= (get-in db [:wallet :ui :send :suggested-routes-call-timestamp]) 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) (let [suggested-routes-data (cske/transform-keys transforms/->kebab-case-keyword
chosen-route (:best suggested-routes-data)] 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 {: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 :to-values-by-chain] to-network-values-for-ui)
(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 {:db (-> db
(update-in [:wallet :ui :send] dissoc :suggested-routes) (update-in [:wallet :ui :send] dissoc :suggested-routes :route)
(update-in [:wallet :ui :send] dissoc :route)
(assoc-in [:wallet :ui :send :loading-suggested-routes?] false))})) (assoc-in [:wallet :ui :send :loading-suggested-routes?] false))}))
(rf/reg-event-fx :wallet/clean-suggested-routes (rf/reg-event-fx :wallet/clean-suggested-routes
(fn [{:keys [db]}] (fn [{:keys [db]}]
{:db (-> db {:db (update-in db
(update-in [:wallet :ui :send] dissoc :suggested-routes) [:wallet :ui :send]
(update-in [:wallet :ui :send] dissoc :route) dissoc
(update-in [:wallet :ui :send] dissoc :loading-suggested-routes?))})) :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 (rf/reg-event-fx :wallet/clean-send-address
(fn [{:keys [db]}] (fn [{:keys [db]}]
{:db (update-in db [:wallet :ui :send] dissoc :recipient :to-address)})) {: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 (rf/reg-event-fx
:wallet/select-send-address :wallet/select-send-address
(fn [{:keys [db]} [{:keys [address recipient stack-id start-flow?]}]] (fn [{:keys [db]} [{:keys [address recipient stack-id start-flow?]}]]
(let [[prefix to-address] (utils/split-prefix-and-address address) (let [[prefix to-address] (utils/split-prefix-and-address address)
testnet-enabled? (get-in db [:profile/profile :test-networks-enabled?]) testnet-enabled? (get-in db [:profile/profile :test-networks-enabled?])
goerli-enabled? (get-in db [:profile/profile :is-goerli-enabled?]) goerli-enabled? (get-in db [:profile/profile :is-goerli-enabled?])
prefix-seq (string/split prefix #":") selected-networks (network-utils/resolve-receiver-networks
selected-networks (->> prefix-seq {:prefix prefix
(remove string/blank?)
(mapv
#(utils/network->chain-id
{:network %
:testnet-enabled? testnet-enabled? :testnet-enabled? testnet-enabled?
:goerli-enabled? goerli-enabled?})))] :goerli-enabled? goerli-enabled?})]
{:db (-> db {:db (-> db
(assoc-in [:wallet :ui :send :recipient] (or recipient address)) (assoc-in [:wallet :ui :send :recipient] (or recipient address))
(assoc-in [:wallet :ui :send :to-address] to-address) (assoc-in [:wallet :ui :send :to-address] to-address)
@ -84,7 +111,8 @@
(fn [{:keys [db]} [{:keys [token stack-id start-flow?]}]] (fn [{:keys [db]} [{:keys [token stack-id start-flow?]}]]
{:db (-> db {:db (-> db
(update-in [:wallet :ui :send] dissoc :collectible) (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]] :fx [[:dispatch [:wallet/clean-suggested-routes]]
[:dispatch [:dispatch
[:wallet/wizard-navigate-forward [:wallet/wizard-navigate-forward
@ -95,13 +123,15 @@
(rf/reg-event-fx (rf/reg-event-fx
:wallet/edit-token-to-send :wallet/edit-token-to-send
(fn [{:keys [db]} [token]] (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]] :fx [[:dispatch [:hide-bottom-sheet]]
[:dispatch [:wallet/clean-suggested-routes]]]})) [:dispatch [:wallet/clean-suggested-routes]]]}))
(rf/reg-event-fx :wallet/clean-selected-token (rf/reg-event-fx :wallet/clean-selected-token
(fn [{:keys [db]}] (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 (rf/reg-event-fx :wallet/clean-selected-collectible
(fn [{:keys [db]}] (fn [{:keys [db]}]
@ -110,18 +140,30 @@
[:wallet :ui :send] [:wallet :ui :send]
dissoc dissoc
:collectible :collectible
:token-display-name
:amount :amount
(when (= transaction-type :collecible) :tx-type))}))) (when (= transaction-type :collecible) :tx-type))})))
(rf/reg-event-fx :wallet/send-collectibles-amount (rf/reg-event-fx :wallet/send-collectibles-amount
(fn [{:keys [db]} [{:keys [collectible stack-id amount]}]] (fn [{:keys [db]} [{:keys [collectible stack-id amount]}]]
(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 {:db (-> db
(update-in [:wallet :ui :send] dissoc :token) (update-in [:wallet :ui :send] dissoc :token)
(assoc-in [:wallet :ui :send :collectible] collectible) (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 :tx-type] :collectible)
(assoc-in [:wallet :ui :send :amount] amount)) (assoc-in [:wallet :ui :send :amount] amount))
:fx [[:dispatch [:wallet/get-suggested-routes {:amount amount}]] :fx [[:dispatch [:wallet/get-suggested-routes {:amount amount}]]
[:navigate-to-within-stack [:screen/wallet.transaction-confirmation stack-id]]]})) [:navigate-to-within-stack [:screen/wallet.transaction-confirmation stack-id]]]})))
(rf/reg-event-fx :wallet/select-collectibles-amount (rf/reg-event-fx :wallet/select-collectibles-amount
(fn [{:keys [db]} [{:keys [collectible stack-id]}]] (fn [{:keys [db]} [{:keys [collectible stack-id]}]]
@ -140,6 +182,12 @@
:start-flow? start-flow? :start-flow? start-flow?
:flow-id :wallet-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 (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])
@ -147,6 +195,7 @@
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])
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]))
@ -163,7 +212,7 @@
gas-rates constants/gas-rate-medium gas-rates constants/gas-rate-medium
amount-in (send-utils/amount-in-hex amount (if token token-decimal 0)) amount-in (send-utils/amount-in-hex amount (if token token-decimal 0))
from-address wallet-address from-address wallet-address
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)
[]) [])
@ -218,6 +267,7 @@
{:fx [[:dispatch [:wallet/clean-scanned-address]] {:fx [[:dispatch [:wallet/clean-scanned-address]]
[:dispatch [:wallet/clean-local-suggestions]] [:dispatch [:wallet/clean-local-suggestions]]
[:dispatch [:wallet/clean-send-address]] [:dispatch [:wallet/clean-send-address]]
[:dispatch [:wallet/clean-disabled-from-networks]]
[:dispatch [:wallet/select-address-tab nil]] [:dispatch [:wallet/select-address-tab nil]]
[:dispatch [:dismiss-modal :screen/wallet.transaction-progress]]]})) [:dispatch [:dismiss-modal :screen/wallet.transaction-progress]]]}))

View File

@ -5,6 +5,7 @@
[status-im.contexts.wallet.send.input-amount.view :as input-amount] [status-im.contexts.wallet.send.input-amount.view :as input-amount]
[test-helpers.component :as h] [test-helpers.component :as h]
[utils.debounce :as debounce] [utils.debounce :as debounce]
[utils.money :as money]
[utils.re-frame :as rf])) [utils.re-frame :as rf]))
(set! rf/dispatch #()) (set! rf/dispatch #())
@ -40,6 +41,17 @@
:public-key "0x04371e2d9d66b82f056bc128064" :public-key "0x04371e2d9d66b82f056bc128064"
:removed false} :removed false}
:wallet/wallet-send-token {:symbol :eth :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 :total-balance 100
:market-values-per-currency {:usd {:price 10}}} :market-values-per-currency {:usd {:price 10}}}
:wallet/wallet-send-loading-suggested-routes? false :wallet/wallet-send-loading-suggested-routes? false
@ -52,13 +64,16 @@
: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 {:candidates []}
:wallet/wallet-send-selected-networks [] :wallet/wallet-send-selected-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"
:profile/currency-symbol "$" :profile/currency-symbol "$"
:wallet/token-by-symbol {:symbol :eth :wallet/token-by-symbol {:symbol :eth
:total-balance 100 :total-balance 100
:market-values-per-currency {:usd {:price 10}}}}) :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/describe "Send > input amount screen"
(h/setup-restorable-re-frame) (h/setup-restorable-re-frame)

View File

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

View File

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

View File

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

View File

@ -10,6 +10,8 @@
[input-amount/view [input-amount/view
{:current-screen-id :screen/wallet.send-input-amount {:current-screen-id :screen/wallet.send-input-amount
:button-one-label (i18n/label :t/confirm) :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)) (def view (quo.theme/with-theme view-internal))

View File

@ -1,23 +1,21 @@
(ns status-im.contexts.wallet.send.transaction-confirmation.view (ns status-im.contexts.wallet.send.transaction-confirmation.view
(:require (:require
[clojure.string :as string] [clojure.string :as string]
[legacy.status-im.utils.hex :as utils.hex]
[legacy.status-im.utils.utils :as utils] [legacy.status-im.utils.utils :as utils]
[native-module.core :as native-module]
[quo.core :as quo] [quo.core :as quo]
[quo.theme :as quo.theme] [quo.theme :as quo.theme]
[react-native.core :as rn] [react-native.core :as rn]
[react-native.safe-area :as safe-area] [react-native.safe-area :as safe-area]
[status-im.common.floating-button-page.view :as floating-button-page] [status-im.common.floating-button-page.view :as floating-button-page]
[status-im.common.standard-authentication.core :as standard-auth] [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] [status-im.contexts.wallet.send.transaction-confirmation.style :as style]
[utils.i18n :as i18n] [utils.i18n :as i18n]
[utils.money :as money]
[utils.re-frame :as rf] [utils.re-frame :as rf]
[utils.security.core :as security])) [utils.security.core :as security]))
(defn- transaction-title (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?]}] collectible?]}]
(let [to-network-name (:network-name to-network) (let [to-network-name (:network-name to-network)
to-network-color (if (= to-network-name :mainnet) :ethereum to-network-name)] to-network-color (if (= to-network-name :mainnet) :ethereum to-network-name)]
@ -32,8 +30,8 @@
(i18n/label :t/bridge) (i18n/label :t/bridge)
(i18n/label :t/send))] (i18n/label :t/send))]
[quo/summary-tag [quo/summary-tag
{:token (if collectible? "" token-symbol) {:token (if collectible? "" token-display-name)
:label (str amount " " token-symbol) :label (str amount " " token-display-name)
:type (if collectible? :collectible :token) :type (if collectible? :collectible :token)
:image-source (if collectible? image-url :eth)}]] :image-source (if collectible? image-url :eth)}]]
(if (= transaction-type :bridge) (if (= transaction-type :bridge)
@ -125,68 +123,18 @@
:emoji (:emoji account) :emoji (:emoji account)
:customization-color (:color 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 (defn- user-summary
[{:keys [account-props theme label accessibility-label [{:keys [network-values token-display-name account-props theme label accessibility-label
summary-type network-values] summary-type]}]
:as _props}] (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 [rn/view
{:style {:padding-horizontal 20 {:style {:padding-horizontal 20
:padding-bottom 16}} :padding-bottom 16}}
@ -200,9 +148,9 @@
{:type summary-type {:type summary-type
:networks? true :networks? true
:values network-values :values network-values
:account-props account-props}]]) :account-props account-props}]]))
(defn data-item (defn- data-item
[{:keys [title subtitle]}] [{:keys [title subtitle]}]
[quo/data-item [quo/data-item
{:container-style style/detail-item {:container-style style/detail-item
@ -217,7 +165,8 @@
:subtitle subtitle}]) :subtitle subtitle}])
(defn- transaction-details (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]}] theme]}]
(let [currency-symbol (rf/sub [:profile/currency-symbol]) (let [currency-symbol (rf/sub [:profile/currency-symbol])
route-loaded? (and route (seq route)) route-loaded? (and route (seq route))
@ -253,21 +202,11 @@
(i18n/label :t/bridged-to (i18n/label :t/bridged-to
{:network (:abbreviated-name to-network)}) {:network (:abbreviated-name to-network)})
(i18n/label :t/user-gets {:name (utils/get-shortened-address to-address)})) (i18n/label :t/user-gets {:name (utils/get-shortened-address to-address)}))
:subtitle (str amount " " token-symbol)}]] :subtitle (str amount " " token-display-name)}]]
:else :else
[quo/text {:style {:align-self :center}} [quo/text {:style {:align-self :center}}
(i18n/label :t/no-routes-found-confirmation)])]])) (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 (defn- view-internal
[_] [_]
(let [on-close (fn [] (let [on-close (fn []
@ -275,13 +214,11 @@
(rf/dispatch [:navigate-back]))] (rf/dispatch [:navigate-back]))]
(fn [{:keys [theme]}] (fn [{:keys [theme]}]
(let [send-transaction-data (rf/sub [:wallet/wallet-send]) (let [send-transaction-data (rf/sub [:wallet/wallet-send])
{:keys [token collectible amount route {:keys [token-display-name collectible amount route
to-address bridge-to-chain-id]} send-transaction-data to-address bridge-to-chain-id
from-values-by-chain
to-values-by-chain]} send-transaction-data
collectible? (some? collectible) collectible? (some? collectible)
token-symbol (if collectible
(collectible-token-symbol collectible)
(:symbol token))
token-decimals (if collectible 0 (:decimals token))
image-url (when collectible image-url (when collectible
(get-in collectible [:preview-url :uri])) (get-in collectible [:preview-url :uri]))
transaction-type (:tx-type send-transaction-data) transaction-type (:tx-type send-transaction-data)
@ -297,12 +234,10 @@
:emoji (:emoji account) :emoji (:emoji account)
:type :default :type :default
:name (:name account) :name (:name account)
:address (utils/get-shortened-address :address (utils/get-shortened-address (:address
(:address
account))} account))}
user-props {:full-name to-address user-props {:full-name to-address
:address (utils/get-shortened-address :address (utils/get-shortened-address to-address)}]
to-address)}]
[rn/view {:style {:flex 1}} [rn/view {:style {:flex 1}}
[floating-button-page/view [floating-button-page/view
{:footer-container-padding 0 {:footer-container-padding 0
@ -329,7 +264,7 @@
:customization-color (:color account)} :customization-color (:color account)}
[rn/view [rn/view
[transaction-title [transaction-title
{:token-symbol token-symbol {:token-display-name token-display-name
:amount amount :amount amount
:account account :account account
:to-address to-address :to-address to-address
@ -339,19 +274,16 @@
:transaction-type transaction-type :transaction-type transaction-type
:collectible? collectible?}] :collectible? collectible?}]
[user-summary [user-summary
{:summary-type :status-account {:token-display-name token-display-name
:summary-type :status-account
:accessibility-label :summary-from-label :accessibility-label :summary-from-label
:label (i18n/label :t/from-capitalized) :label (i18n/label :t/from-capitalized)
:network-values from-values-by-chain
:account-props from-account-props :account-props from-account-props
:theme theme :theme theme}]
:network-values (values-by-network {:collectible collectible
:amount amount
:token-symbol token-symbol
:route route
:token-decimals token-decimals
:to? false})}]
[user-summary [user-summary
{:summary-type (if (= transaction-type :bridge) {:token-display-name token-display-name
:summary-type (if (= transaction-type :bridge)
:status-account :status-account
:account) :account)
:accessibility-label :summary-to-label :accessibility-label :summary-to-label
@ -359,17 +291,12 @@
:account-props (if (= transaction-type :bridge) :account-props (if (= transaction-type :bridge)
from-account-props from-account-props
user-props) user-props)
:theme theme :network-values to-values-by-chain
:network-values (values-by-network {:collectible collectible :theme theme}]
:amount amount
:token-symbol token-symbol
:route route
:token-decimals token-decimals
:to? true})}]
[transaction-details [transaction-details
{:estimated-time-min estimated-time-min {:estimated-time-min estimated-time-min
:max-fees max-fees :max-fees max-fees
:token-symbol token-symbol :token-display-name token-display-name
:amount amount :amount amount
:to-address to-address :to-address to-address
:to-network bridge-to-network :to-network bridge-to-network

View File

@ -1,5 +1,8 @@
(ns status-im.contexts.wallet.send.utils (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 (defn amount-in-hex
[amount token-decimal] [amount token-decimal]
@ -20,3 +23,27 @@
value1))) value1)))
{} {}
transaction-hashes)) 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 (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.money :as money]))
(deftest test-amount-in-hex (deftest test-amount-in-hex
(testing "Test amount-in-hex function" (testing "Test amount-in-hex function"
@ -27,3 +28,68 @@
"0x11" {:status :pending "0x11" {:status :pending
:id 61 :id 61
:chain-id :420}}))))) :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] :<- [:wallet/wallet-send]
:-> :token) :-> :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 (rf/reg-sub
:wallet/wallet-send-loading-suggested-routes? :wallet/wallet-send-loading-suggested-routes?
:<- [:wallet/wallet-send] :<- [: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)))))