Fix rounding of fiat and crypto on send page (#20915)
* Fix conversion rounding on send page * fixing review notes * fix amount of allowed input decimals for crypto
This commit is contained in:
parent
9d6b8609be
commit
cc6dcc3636
|
@ -6,16 +6,16 @@
|
|||
(h/describe "Wallet: Token Input"
|
||||
(h/test "Token label renders"
|
||||
(h/render-with-theme-provider [token-input/view
|
||||
{:token :snt
|
||||
:currency :eur
|
||||
:currency-symbol "€"
|
||||
:conversion 1}])
|
||||
{:token :snt
|
||||
:currency :eur
|
||||
:crypto? true
|
||||
:conversion 1}])
|
||||
(h/is-truthy (h/get-by-text "SNT")))
|
||||
|
||||
(h/test "Amount renders"
|
||||
(h/render-with-theme-provider [token-input/view
|
||||
{:token :snt
|
||||
:currency :eur
|
||||
:currency-symbol "€"
|
||||
:converted-value "€0.00"
|
||||
:conversion 1}])
|
||||
(h/is-truthy (h/get-by-text "€0.00"))))
|
||||
|
|
|
@ -11,32 +11,10 @@
|
|||
[quo.components.wallet.token-input.style :as style]
|
||||
[quo.theme :as quo.theme]
|
||||
[react-native.core :as rn]
|
||||
[schema.core :as schema]
|
||||
[utils.number :as number]))
|
||||
|
||||
(defn fiat-format
|
||||
[currency-symbol num-value conversion]
|
||||
(str currency-symbol (.toFixed (* num-value conversion) 2)))
|
||||
|
||||
(defn crypto-format
|
||||
[num-value conversion crypto-decimals token]
|
||||
(str (number/remove-trailing-zeroes
|
||||
(.toFixed (/ num-value conversion) (or crypto-decimals 2)))
|
||||
" "
|
||||
(string/upper-case (or (clj->js token) ""))))
|
||||
|
||||
(defn calc-value
|
||||
[{:keys [crypto? currency-symbol token value conversion crypto-decimals]}]
|
||||
(let [num-value (if (string? value)
|
||||
(or (parse-double value) 0)
|
||||
value)]
|
||||
(if crypto?
|
||||
(fiat-format currency-symbol num-value conversion)
|
||||
(crypto-format num-value conversion crypto-decimals token))))
|
||||
[schema.core :as schema]))
|
||||
|
||||
(defn- data-info
|
||||
[{:keys [theme token crypto-decimals conversion networks title crypto? currency-symbol amount
|
||||
error?]}]
|
||||
[{:keys [theme networks title converted-value error?]}]
|
||||
[rn/view {:style style/data-container}
|
||||
[network-tag/view
|
||||
{:networks networks
|
||||
|
@ -46,12 +24,7 @@
|
|||
{:size :paragraph-2
|
||||
:weight :medium
|
||||
:style (style/fiat-amount theme)}
|
||||
(calc-value {:crypto? crypto?
|
||||
:currency-symbol currency-symbol
|
||||
:token token
|
||||
:value amount
|
||||
:conversion conversion
|
||||
:crypto-decimals crypto-decimals})]])
|
||||
converted-value]])
|
||||
|
||||
(defn- token-name-text
|
||||
[theme text]
|
||||
|
@ -123,16 +96,14 @@
|
|||
:i/reorder]]))))
|
||||
|
||||
(defn- view-internal
|
||||
[{:keys [container-style value on-swap] :as props}]
|
||||
[{:keys [container-style on-swap crypto?] :as props}]
|
||||
(let [theme (quo.theme/use-theme)
|
||||
width (:width (rn/get-window))
|
||||
[value-internal set-value-internal] (rn/use-state nil)
|
||||
[crypto? set-crypto] (rn/use-state true)
|
||||
handle-on-swap (rn/use-callback
|
||||
(fn []
|
||||
(set-crypto (not crypto?))
|
||||
(when on-swap (on-swap (not crypto?))))
|
||||
[crypto? on-swap])]
|
||||
[on-swap])]
|
||||
[rn/view {:style (merge (style/main-container width) container-style)}
|
||||
[rn/view {:style style/amount-container}
|
||||
[input-section
|
||||
|
@ -143,10 +114,6 @@
|
|||
:handle-on-swap handle-on-swap
|
||||
:crypto? crypto?)]]
|
||||
[divider-line/view {:container-style (style/divider theme)}]
|
||||
[data-info
|
||||
(assoc props
|
||||
:theme theme
|
||||
:crypto? crypto?
|
||||
:amount (or value value-internal))]]))
|
||||
[data-info (assoc props :theme theme)]]))
|
||||
|
||||
(def view (schema/instrument #'view-internal component-schema/?schema))
|
||||
|
|
|
@ -2,9 +2,14 @@
|
|||
(:require
|
||||
[quo.core :as quo]
|
||||
[quo.foundations.resources :as resources]
|
||||
[react-native.core :as rn]
|
||||
[react-native.safe-area :as safe-area]
|
||||
[reagent.core :as reagent]
|
||||
[status-im.contexts.preview.quo.preview :as preview]))
|
||||
[status-im.common.controlled-input.utils :as controlled-input]
|
||||
[status-im.contexts.preview.quo.preview :as preview]
|
||||
[status-im.contexts.wallet.common.utils :as utils]
|
||||
[utils.money :as money]
|
||||
[utils.number :as number]))
|
||||
|
||||
(def networks
|
||||
[{:source (resources/get-network :arbitrum)}
|
||||
|
@ -20,8 +25,8 @@
|
|||
{:key :snt}]}
|
||||
{:key :currency
|
||||
:type :select
|
||||
:options [{:key :usd}
|
||||
{:key :eur}]}
|
||||
:options [{:key "$"}
|
||||
{:key "€"}]}
|
||||
{:key :error?
|
||||
:type :boolean}
|
||||
{:key :allow-selection?
|
||||
|
@ -29,30 +34,63 @@
|
|||
|
||||
(defn view
|
||||
[]
|
||||
(let [state (reagent/atom {:token :eth
|
||||
:currency :usd
|
||||
:conversion 0.02
|
||||
:networks networks
|
||||
:title title
|
||||
:customization-color :blue
|
||||
:show-keyboard? false
|
||||
:allow-selection? true})
|
||||
value (reagent/atom "")
|
||||
set-value (fn [v]
|
||||
(swap! value str v))
|
||||
delete (fn [_]
|
||||
(swap! value #(subs % 0 (dec (count %)))))]
|
||||
(let [state (reagent/atom {:token :eth
|
||||
:currency "$"
|
||||
:conversion-rate 3450.28
|
||||
:networks networks
|
||||
:title title
|
||||
:customization-color :blue
|
||||
:show-keyboard? false
|
||||
:allow-selection? true
|
||||
:crypto? true})]
|
||||
(fn []
|
||||
[preview/preview-container
|
||||
{:state state
|
||||
:descriptor descriptor
|
||||
:full-screen? true
|
||||
:component-container-style {:flex 1
|
||||
:justify-content :space-between}}
|
||||
[quo/token-input (assoc @state :value @value)]
|
||||
[quo/numbered-keyboard
|
||||
{:container-style {:padding-bottom (safe-area/get-top)}
|
||||
:left-action :dot
|
||||
:delete-key? true
|
||||
:on-press set-value
|
||||
:on-delete delete}]])))
|
||||
(let [{:keys [currency token conversion-rate
|
||||
crypto?]} @state
|
||||
[input-state set-input-state] (rn/use-state controlled-input/init-state)
|
||||
input-amount (controlled-input/input-value input-state)
|
||||
swap-between-fiat-and-crypto (fn []
|
||||
(set-input-state
|
||||
(fn [input-state]
|
||||
(controlled-input/set-input-value
|
||||
input-state
|
||||
(let [new-value
|
||||
(if-not crypto?
|
||||
(utils/cut-crypto-decimals-to-fit-usd-cents
|
||||
conversion-rate
|
||||
(money/fiat->crypto input-amount
|
||||
conversion-rate))
|
||||
(utils/cut-fiat-balance-to-two-decimals
|
||||
(money/crypto->fiat input-amount
|
||||
conversion-rate)))]
|
||||
(number/remove-trailing-zeroes
|
||||
new-value))))))
|
||||
converted-value (if crypto?
|
||||
(utils/prettify-balance currency
|
||||
(money/crypto->fiat input-amount
|
||||
conversion-rate))
|
||||
(utils/prettify-crypto-balance
|
||||
(or (clj->js token) "")
|
||||
(money/fiat->crypto input-amount conversion-rate)
|
||||
conversion-rate))]
|
||||
[preview/preview-container
|
||||
{:state state
|
||||
:descriptor descriptor
|
||||
:full-screen? true
|
||||
:component-container-style {:flex 1
|
||||
:justify-content :space-between}}
|
||||
[quo/token-input
|
||||
(merge @state
|
||||
{:value input-amount
|
||||
:converted-value converted-value
|
||||
:on-swap (fn [crypto]
|
||||
(swap! state assoc :crypto? crypto)
|
||||
(swap-between-fiat-and-crypto))})]
|
||||
[quo/numbered-keyboard
|
||||
{:container-style {:padding-bottom (safe-area/get-top)}
|
||||
:left-action :dot
|
||||
:delete-key? true
|
||||
:on-press (fn [c]
|
||||
(set-input-state #(controlled-input/add-character % c)))
|
||||
|
||||
:on-delete (fn []
|
||||
(set-input-state controlled-input/delete-last))}]]))))
|
||||
|
|
|
@ -12,14 +12,17 @@
|
|||
[full-name]
|
||||
(first (string/split full-name #" ")))
|
||||
|
||||
(defn prettify-balance
|
||||
[currency-symbol balance]
|
||||
(defn cut-fiat-balance-to-two-decimals
|
||||
[balance]
|
||||
(let [valid-balance? (and balance
|
||||
(or (number? balance) (.-toFixed balance)))]
|
||||
(as-> balance $
|
||||
(if valid-balance? $ 0)
|
||||
(.toFixed $ 2)
|
||||
(str currency-symbol $))))
|
||||
(.toFixed $ 2))))
|
||||
|
||||
(defn prettify-balance
|
||||
[currency-symbol balance]
|
||||
(str currency-symbol (cut-fiat-balance-to-two-decimals balance)))
|
||||
|
||||
(defn get-derivation-path
|
||||
[number-of-accounts]
|
||||
|
@ -47,8 +50,8 @@
|
|||
nil))
|
||||
|
||||
(defn calc-max-crypto-decimals
|
||||
[value]
|
||||
(let [str-representation (str value)
|
||||
[one-cent-value]
|
||||
(let [str-representation (str one-cent-value)
|
||||
decimal-part (second (clojure.string/split str-representation #"\."))
|
||||
exponent (extract-exponent str-representation)
|
||||
zeroes-count (count (take-while #(= \0 %) decimal-part))
|
||||
|
@ -58,29 +61,60 @@
|
|||
(inc max-decimals)
|
||||
max-decimals)))
|
||||
|
||||
(defn get-crypto-decimals-count
|
||||
[{:keys [market-values-per-currency]}]
|
||||
(let [price (get-in market-values-per-currency [:usd :price])
|
||||
one-cent-value (if (pos? price) (/ 0.01 price) 0)]
|
||||
(calc-max-crypto-decimals one-cent-value)))
|
||||
(defn token-usd-price
|
||||
[token]
|
||||
(get-in token [:market-values-per-currency :usd :price]))
|
||||
|
||||
(defn one-cent-value
|
||||
[token-price-in-usd]
|
||||
(if (pos? token-price-in-usd)
|
||||
(/ 0.01 token-price-in-usd)
|
||||
0))
|
||||
|
||||
(defn analyze-token-amount-for-price
|
||||
"For full details: https://github.com/status-im/status-mobile/issues/18225"
|
||||
[token-price-in-usd token-units]
|
||||
(if (or (nil? token-units)
|
||||
(not (money/bignumber? token-units))
|
||||
(money/equal-to token-units 0))
|
||||
{:zero-value? true}
|
||||
(let [cent-value (one-cent-value token-price-in-usd)]
|
||||
{:usd-cent-value cent-value
|
||||
:standardized-decimals-count (if (nil? token-price-in-usd)
|
||||
missing-price-decimals
|
||||
(calc-max-crypto-decimals cent-value))})))
|
||||
|
||||
(defn cut-crypto-decimals-to-fit-usd-cents
|
||||
[token-price-in-usd token-units]
|
||||
(let [{:keys [zero-value? usd-cent-value standardized-decimals-count]}
|
||||
(analyze-token-amount-for-price token-price-in-usd token-units)]
|
||||
(cond
|
||||
zero-value? "0"
|
||||
(< token-units usd-cent-value) "0"
|
||||
:else (number/remove-trailing-zeroes
|
||||
(.toFixed token-units standardized-decimals-count)))))
|
||||
|
||||
(defn prettify-crypto-balance
|
||||
[token-symbol crypto-balance conversion-rate]
|
||||
(str (cut-crypto-decimals-to-fit-usd-cents conversion-rate crypto-balance)
|
||||
" "
|
||||
(string/upper-case token-symbol)))
|
||||
|
||||
(defn get-standard-crypto-format
|
||||
"For full details: https://github.com/status-im/status-mobile/issues/18225"
|
||||
[{:keys [market-values-per-currency]} token-units]
|
||||
(cond (or (nil? token-units)
|
||||
(money/equal-to token-units 0))
|
||||
"0"
|
||||
[token token-units]
|
||||
(let [token-price-in-usd (token-usd-price token)
|
||||
{:keys [zero-value? usd-cent-value standardized-decimals-count]}
|
||||
(analyze-token-amount-for-price token-price-in-usd token-units)]
|
||||
(cond
|
||||
zero-value?
|
||||
"0"
|
||||
|
||||
(nil? (-> market-values-per-currency :usd :price))
|
||||
(number/remove-trailing-zeroes (.toFixed token-units missing-price-decimals))
|
||||
(< token-units usd-cent-value)
|
||||
(str "<" (number/remove-trailing-zeroes (.toFixed usd-cent-value standardized-decimals-count)))
|
||||
|
||||
:else
|
||||
(let [price (-> market-values-per-currency :usd :price)
|
||||
one-cent-value (if (pos? price) (/ 0.01 price) 0)
|
||||
decimals-count (calc-max-crypto-decimals one-cent-value)]
|
||||
(if (< token-units one-cent-value)
|
||||
(str "<" (number/remove-trailing-zeroes (.toFixed one-cent-value decimals-count)))
|
||||
(number/remove-trailing-zeroes (.toFixed token-units decimals-count))))))
|
||||
:else
|
||||
(number/remove-trailing-zeroes (.toFixed token-units standardized-decimals-count)))))
|
||||
|
||||
(defn get-market-value
|
||||
[currency {:keys [market-values-per-currency]}]
|
||||
|
|
|
@ -73,7 +73,12 @@
|
|||
token-units (money/bignumber 0.01)]
|
||||
(is (= (utils/get-standard-crypto-format {:market-values-per-currency market-values-per-currency}
|
||||
token-units)
|
||||
"<2")))))
|
||||
"<2")))
|
||||
(let [market-values-per-currency {:usd {:price 0.005}}
|
||||
token-units "0.01"]
|
||||
(is (= (utils/get-standard-crypto-format {:market-values-per-currency market-values-per-currency}
|
||||
token-units)
|
||||
"0")))))
|
||||
|
||||
(deftest calculate-total-token-balance-test
|
||||
(testing "calculate-total-token-balance function"
|
||||
|
@ -163,5 +168,3 @@
|
|||
expected-order ["DAI" "ETH" "SNT"]]
|
||||
(is (= expected-order sorted-tokens)))))
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -113,34 +113,11 @@
|
|||
:limit-crypto 250
|
||||
:initial-crypto-currency? false}])
|
||||
(h/is-truthy (h/get-by-text "0"))
|
||||
(h/is-truthy (h/get-by-text "ETH"))
|
||||
(h/is-truthy (h/get-by-text "$0.00"))
|
||||
(h/is-truthy (h/get-by-text "USD"))
|
||||
(h/is-truthy (h/get-by-text "0 ETH"))
|
||||
(h/is-truthy (h/get-by-label-text :container))
|
||||
(h/is-disabled (h/get-by-label-text :button-one)))
|
||||
|
||||
(h/test "Fill token input and confirm"
|
||||
(h/setup-subs sub-mocks)
|
||||
(let [on-confirm (h/mock-fn)]
|
||||
(h/render-with-theme-provider [input-amount/view
|
||||
{:on-confirm on-confirm
|
||||
:crypto-decimals 10
|
||||
:limit-crypto 1000
|
||||
:initial-crypto-currency? false}])
|
||||
|
||||
(h/fire-event :press (h/query-by-label-text :keyboard-key-1))
|
||||
(h/fire-event :press (h/query-by-label-text :keyboard-key-2))
|
||||
(h/fire-event :press (h/query-by-label-text :keyboard-key-3))
|
||||
(h/fire-event :press (h/query-by-label-text :keyboard-key-.))
|
||||
(h/fire-event :press (h/query-by-label-text :keyboard-key-4))
|
||||
(h/fire-event :press (h/query-by-label-text :keyboard-key-5))
|
||||
|
||||
(-> (h/wait-for #(h/get-by-text "$1234.50"))
|
||||
(.then (fn []
|
||||
(h/is-truthy (h/get-by-label-text :button-one))
|
||||
(h/is-truthy (h/get-by-label-text :container))
|
||||
(h/fire-event :press (h/get-by-label-text :button-one))
|
||||
(h/was-called on-confirm))))))
|
||||
|
||||
(h/test "Fill token input and confirm"
|
||||
(h/setup-subs sub-mocks)
|
||||
|
||||
|
@ -149,7 +126,7 @@
|
|||
{:crypto-decimals 10
|
||||
:limit-crypto 1000
|
||||
:on-confirm on-confirm
|
||||
:initial-crypto-currency? false}])
|
||||
:initial-crypto-currency? true}])
|
||||
|
||||
(h/fire-event :press (h/query-by-label-text :keyboard-key-1))
|
||||
(h/fire-event :press (h/query-by-label-text :keyboard-key-2))
|
||||
|
|
|
@ -163,7 +163,6 @@
|
|||
{fiat-currency :currency} (rf/sub [:profile/profile])
|
||||
{token-symbol :symbol
|
||||
token-networks :networks
|
||||
token-decimals :decimals
|
||||
:as
|
||||
token} (rf/sub [:wallet/wallet-send-token])
|
||||
send-from-locked-amounts (rf/sub [:wallet/wallet-send-from-locked-amounts])
|
||||
|
@ -178,6 +177,10 @@
|
|||
:market-values-per-currency
|
||||
currency
|
||||
:price)
|
||||
token-decimals (-> token
|
||||
utils/token-usd-price
|
||||
utils/one-cent-value
|
||||
utils/calc-max-crypto-decimals)
|
||||
loading-routes? (rf/sub
|
||||
[:wallet/wallet-send-loading-suggested-routes?])
|
||||
route (rf/sub [:wallet/wallet-send-route])
|
||||
|
@ -295,6 +298,14 @@
|
|||
:valid-input? valid-input?
|
||||
:bounce-duration-ms bounce-duration-ms
|
||||
:token token}))
|
||||
swap-currency (fn [to-crypto? value]
|
||||
(if to-crypto?
|
||||
(utils/cut-crypto-decimals-to-fit-usd-cents
|
||||
conversion-rate
|
||||
(money/fiat->crypto value conversion-rate))
|
||||
(utils/cut-fiat-balance-to-two-decimals
|
||||
(money/crypto->fiat value
|
||||
conversion-rate))))
|
||||
swap-between-fiat-and-crypto (fn [swap-to-crypto-currency?]
|
||||
(set-just-toggled-mode? true)
|
||||
(set-crypto-currency swap-to-crypto-currency?)
|
||||
|
@ -304,13 +315,9 @@
|
|||
input-state
|
||||
(let [value (controlled-input/input-value
|
||||
input-state)
|
||||
new-value (if swap-to-crypto-currency?
|
||||
(.toFixed (/ value
|
||||
conversion-rate)
|
||||
crypto-decimals)
|
||||
(.toFixed (* value
|
||||
conversion-rate)
|
||||
12))]
|
||||
new-value (swap-currency
|
||||
swap-to-crypto-currency?
|
||||
value)]
|
||||
(number/remove-trailing-zeroes
|
||||
new-value))))))]
|
||||
(rn/use-mount
|
||||
|
@ -363,7 +370,18 @@
|
|||
:value input-amount
|
||||
:on-swap swap-between-fiat-and-crypto
|
||||
:on-token-press show-select-asset-sheet
|
||||
:allow-selection? false}]
|
||||
:allow-selection? false
|
||||
:crypto? crypto-currency?
|
||||
:converted-value (if crypto-currency?
|
||||
(utils/prettify-balance
|
||||
currency-symbol
|
||||
(money/crypto->fiat input-amount
|
||||
conversion-rate))
|
||||
(utils/prettify-crypto-balance
|
||||
(or (clj->js token-symbol) "")
|
||||
(money/fiat->crypto input-amount
|
||||
conversion-rate)
|
||||
conversion-rate))}]
|
||||
[routes/view
|
||||
{:token token-by-symbol
|
||||
:send-amount-in-crypto amount-in-crypto
|
||||
|
|
|
@ -6,6 +6,8 @@
|
|||
prefer to use it for more general purpose concepts, such as the re-frame event
|
||||
layer."
|
||||
(:require-macros test-helpers.unit)
|
||||
;; We must require test-helpers.matchers namespace to register the custom cljs.test directive
|
||||
;; `match-strict?`
|
||||
(:require
|
||||
[re-frame.core :as rf]
|
||||
[re-frame.db :as rf-db]
|
||||
|
|
|
@ -262,6 +262,12 @@
|
|||
[bn1 bn2]
|
||||
(.round (.dividedBy ^js bn1 bn2) 0))
|
||||
|
||||
(defn fiat->crypto
|
||||
[crypto fiat-price]
|
||||
(when-let [crypto-bn (bignumber crypto)]
|
||||
(div crypto-bn
|
||||
(bignumber fiat-price))))
|
||||
|
||||
(defn absolute-value
|
||||
[bn]
|
||||
(when bn
|
||||
|
|
Loading…
Reference in New Issue