fix(swap): display very small max values, fix scientific notation, handle decimal mismatch when changing tokens, display long numbers (#21388)
Signed-off-by: Brian Sztamfater <brian@status.im>
This commit is contained in:
parent
a9e0b3dd6b
commit
5c44cb6399
|
@ -75,3 +75,16 @@
|
|||
|
||||
(def fiat-amount
|
||||
{:color colors/neutral-50})
|
||||
|
||||
(def gradient-common
|
||||
{:width 64
|
||||
:position :absolute
|
||||
:top 0
|
||||
:bottom 0
|
||||
:z-index 1})
|
||||
|
||||
(def gradient-start
|
||||
(assoc gradient-common :left 0))
|
||||
|
||||
(def gradient-end
|
||||
(assoc gradient-common :right 0))
|
||||
|
|
|
@ -11,6 +11,7 @@
|
|||
[quo.foundations.colors :as colors]
|
||||
quo.theme
|
||||
[react-native.core :as rn]
|
||||
[react-native.linear-gradient :as linear-gradient]
|
||||
[schema.core :as schema]))
|
||||
|
||||
(def ?schema
|
||||
|
@ -18,6 +19,7 @@
|
|||
[:catn
|
||||
[:props
|
||||
[:map {:closed true}
|
||||
[:get-ref {:optional true} [:maybe fn?]]
|
||||
[:type {:optional true} [:maybe [:enum :pay :receive]]]
|
||||
[:status {:optional true} [:maybe [:enum :default :typing :disabled :loading]]]
|
||||
[:token {:optional true} [:maybe :string]]
|
||||
|
@ -42,26 +44,65 @@
|
|||
[:container-style {:optional true} [:maybe :map]]]]]
|
||||
:any])
|
||||
|
||||
(def icon-size 32)
|
||||
(def container-padding (* 2 (:padding (style/row-1 false))))
|
||||
(def max-cursor-position 5)
|
||||
|
||||
(defn view-internal
|
||||
[{:keys [type status token value fiat-value show-approval-label? error? network-tag-props
|
||||
approval-label-props default-value auto-focus? input-disabled? enable-swap?
|
||||
currency-symbol on-change-text show-keyboard?
|
||||
currency-symbol on-change-text show-keyboard? get-ref
|
||||
container-style on-swap-press on-token-press on-max-press on-input-focus]}]
|
||||
(let [theme (quo.theme/use-theme)
|
||||
pay? (= type :pay)
|
||||
disabled? (= status :disabled)
|
||||
loading? (= status :loading)
|
||||
typing? (= status :typing)
|
||||
controlled-input? (some? value)
|
||||
input-ref (rn/use-ref-atom nil)
|
||||
set-input-ref (rn/use-callback (fn [ref] (reset! input-ref ref)) [])
|
||||
focus-input (rn/use-callback (fn []
|
||||
(some-> @input-ref
|
||||
(oops/ocall "focus")))
|
||||
[input-ref])]
|
||||
(let [theme (quo.theme/use-theme)
|
||||
pay? (= type :pay)
|
||||
disabled? (= status :disabled)
|
||||
loading? (= status :loading)
|
||||
typing? (= status :typing)
|
||||
controlled-input? (some? value)
|
||||
[container-width
|
||||
set-container-width] (rn/use-state)
|
||||
[overflow?
|
||||
set-overflow?] (rn/use-state false)
|
||||
[cursor-close-to-start?
|
||||
set-cursor-close-to-start?] (rn/use-state false)
|
||||
[label-width
|
||||
set-label-width] (rn/use-state 0)
|
||||
input-ref (rn/use-ref-atom nil)
|
||||
set-input-ref (rn/use-callback (fn [ref]
|
||||
(reset! input-ref ref)
|
||||
(when get-ref (get-ref ref)))
|
||||
[])
|
||||
focus-input (rn/use-callback (fn []
|
||||
(some-> @input-ref
|
||||
(oops/ocall "focus")))
|
||||
[input-ref])
|
||||
on-layout-container (rn/use-callback
|
||||
(fn [e]
|
||||
(let [width (oops/oget e "nativeEvent.layout.width")]
|
||||
(set-container-width width))))
|
||||
on-layout-text-input (rn/use-callback
|
||||
(fn [e]
|
||||
(let [width (oops/oget e "nativeEvent.layout.width")
|
||||
max-width (- container-width
|
||||
icon-size
|
||||
container-padding
|
||||
(* label-width 2))]
|
||||
(set-overflow? (> width max-width))))
|
||||
[container-width label-width])
|
||||
on-layout-label (rn/use-callback
|
||||
(fn [e]
|
||||
(let [width (oops/oget e "nativeEvent.layout.width")]
|
||||
(set-label-width width))))
|
||||
on-selection-change (rn/use-callback
|
||||
(fn [e]
|
||||
(let [selection-start (oops/oget e
|
||||
"nativeEvent.selection.start")]
|
||||
(set-cursor-close-to-start?
|
||||
(< selection-start max-cursor-position)))))]
|
||||
[rn/view
|
||||
{:style container-style
|
||||
:accessibility-label :swap-input}
|
||||
:accessibility-label :swap-input
|
||||
:on-layout on-layout-container}
|
||||
[rn/view {:style (style/content typing? theme)}
|
||||
[rn/view
|
||||
{:style (style/row-1 loading?)}
|
||||
|
@ -75,25 +116,43 @@
|
|||
[rn/pressable
|
||||
{:style style/input-container
|
||||
:on-press focus-input}
|
||||
[rn/text-input
|
||||
(cond-> {:ref set-input-ref
|
||||
:style (style/input disabled? error? theme)
|
||||
:placeholder-text-color (colors/theme-colors colors/neutral-40
|
||||
colors/neutral-50
|
||||
theme)
|
||||
:keyboard-type :numeric
|
||||
:editable (not input-disabled?)
|
||||
:auto-focus auto-focus?
|
||||
:on-focus on-input-focus
|
||||
:on-change-text on-change-text
|
||||
:show-soft-input-on-focus show-keyboard?
|
||||
:default-value default-value
|
||||
:placeholder "0"}
|
||||
controlled-input? (assoc :value value))]
|
||||
[rn/view {:style {:flex-shrink 1}}
|
||||
(when (and overflow? typing? (not cursor-close-to-start?))
|
||||
[linear-gradient/linear-gradient
|
||||
{:start {:x 0 :y 0}
|
||||
:end {:x 1 :y 0}
|
||||
:colors [(colors/theme-colors colors/white colors/neutral-100 theme)
|
||||
(colors/theme-colors colors/white-opa-10 colors/neutral-100-opa-10 theme)]
|
||||
:style style/gradient-start}])
|
||||
(when (and overflow? disabled?)
|
||||
[linear-gradient/linear-gradient
|
||||
{:start {:x 0 :y 0}
|
||||
:end {:x 1 :y 0}
|
||||
:colors [(colors/theme-colors colors/white-opa-10 colors/neutral-100-opa-10 theme)
|
||||
(colors/theme-colors colors/white colors/neutral-100 theme)]
|
||||
:style style/gradient-end}])
|
||||
[rn/text-input
|
||||
(cond-> {:ref set-input-ref
|
||||
:style (style/input disabled? error? theme)
|
||||
:placeholder-text-color (colors/theme-colors colors/neutral-40
|
||||
colors/neutral-50
|
||||
theme)
|
||||
:keyboard-type :numeric
|
||||
:editable (not input-disabled?)
|
||||
:auto-focus auto-focus?
|
||||
:on-focus on-input-focus
|
||||
:on-change-text on-change-text
|
||||
:on-layout on-layout-text-input
|
||||
:on-selection-change on-selection-change
|
||||
:show-soft-input-on-focus show-keyboard?
|
||||
:default-value default-value
|
||||
:placeholder "0"}
|
||||
controlled-input? (assoc :value value))]]
|
||||
[text/text
|
||||
{:size :paragraph-2
|
||||
:weight :semi-bold
|
||||
:style (style/token-symbol theme)}
|
||||
{:size :paragraph-2
|
||||
:weight :semi-bold
|
||||
:style (style/token-symbol theme)
|
||||
:on-layout on-layout-label}
|
||||
token]]
|
||||
(when (and pay? enable-swap?)
|
||||
[buttons/button
|
||||
|
|
|
@ -47,7 +47,7 @@
|
|||
(rf/dispatch [:wallet/bridge-select-token params]))})
|
||||
|
||||
(defn- action-swap
|
||||
[{:keys [token token-symbol testnet-mode?]}]
|
||||
[{:keys [token asset-to-receive token-symbol testnet-mode?]}]
|
||||
{:icon :i/swap
|
||||
:accessibility-label :swap
|
||||
:label (i18n/label :t/swap)
|
||||
|
@ -56,6 +56,7 @@
|
|||
(rf/dispatch [:hide-bottom-sheet])
|
||||
(rf/dispatch [:wallet.swap/start
|
||||
{:asset-to-pay (or token {:symbol token-symbol})
|
||||
:asset-to-receive asset-to-receive
|
||||
:open-new-screen? true}]))})
|
||||
|
||||
(defn- action-manage-tokens
|
||||
|
@ -75,23 +76,26 @@
|
|||
|
||||
(defn token-value-drawer
|
||||
[token watch-only? entry-point]
|
||||
(let [token-symbol (:token token)
|
||||
token-data (first (rf/sub [:wallet/current-viewing-account-tokens-filtered
|
||||
{:query token-symbol}]))
|
||||
selected-account (rf/sub [:wallet/current-viewing-account-address])
|
||||
token-owners (rf/sub [:wallet/operable-addresses-with-token-symbol token-symbol])
|
||||
testnet-mode? (rf/sub [:profile/test-networks-enabled?])
|
||||
params (cond-> {:start-flow? true
|
||||
:owners token-owners
|
||||
:testnet-mode? testnet-mode?}
|
||||
selected-account
|
||||
(assoc :token token-data
|
||||
:stack-id :screen/wallet.accounts
|
||||
:has-balance? (-> (get-in token [:values :fiat-unformatted-value])
|
||||
(money/greater-than (money/bignumber "0"))))
|
||||
(not selected-account)
|
||||
(assoc :token-symbol token-symbol
|
||||
:stack-id :wallet-stack))]
|
||||
(let [token-symbol (:token token)
|
||||
token-data (first (rf/sub [:wallet/current-viewing-account-tokens-filtered
|
||||
{:query token-symbol}]))
|
||||
selected-account (rf/sub [:wallet/current-viewing-account-address])
|
||||
token-owners (rf/sub [:wallet/operable-addresses-with-token-symbol token-symbol])
|
||||
testnet-mode? (rf/sub [:profile/test-networks-enabled?])
|
||||
receive-token-symbol (if (= token-symbol "SNT") "ETH" "SNT")
|
||||
asset-to-receive (rf/sub [:wallet/token-by-symbol receive-token-symbol])
|
||||
params (cond-> {:start-flow? true
|
||||
:owners token-owners
|
||||
:testnet-mode? testnet-mode?
|
||||
:asset-to-receive asset-to-receive}
|
||||
selected-account
|
||||
(assoc :token token-data
|
||||
:stack-id :screen/wallet.accounts
|
||||
:has-balance? (-> (get-in token [:values :fiat-unformatted-value])
|
||||
(money/greater-than (money/bignumber "0"))))
|
||||
(not selected-account)
|
||||
(assoc :token-symbol token-symbol
|
||||
:stack-id :wallet-stack))]
|
||||
[quo/action-drawer
|
||||
[(cond->> [(when (ff/enabled? ::ff/wallet.assets-modal-manage-tokens)
|
||||
(action-manage-tokens watch-only?))
|
||||
|
|
|
@ -14,7 +14,7 @@
|
|||
[utils.number :as number]))
|
||||
|
||||
(rf/reg-event-fx :wallet.swap/start
|
||||
(fn [{:keys [db]} [{:keys [network open-new-screen?] :as data}]]
|
||||
(fn [{:keys [db]} [{:keys [network asset-to-receive open-new-screen?] :as data}]]
|
||||
(let [{:keys [wallet]} db
|
||||
test-networks-enabled? (get-in db [:profile/profile :test-networks-enabled?])
|
||||
account (swap-utils/wallet-account wallet)
|
||||
|
@ -25,12 +25,6 @@
|
|||
:account account
|
||||
:test-networks-enabled? test-networks-enabled?
|
||||
:token-symbol (get-in data [:asset-to-pay :symbol])}))
|
||||
asset-to-receive (or (:asset-to-receive data)
|
||||
(swap-utils/select-default-asset-to-receive
|
||||
{:wallet wallet
|
||||
:account account
|
||||
:test-networks-enabled? test-networks-enabled?
|
||||
:asset-to-pay asset-to-pay}))
|
||||
network' (or network
|
||||
(swap-utils/select-network asset-to-pay))]
|
||||
{:db (-> db
|
||||
|
@ -137,8 +131,6 @@
|
|||
:amount amount
|
||||
:amount-hex amount-in-hex
|
||||
:loading-swap-proposal? true)
|
||||
:always
|
||||
(dissoc :error-response)
|
||||
clean-approval-transaction?
|
||||
(dissoc :approval-transaction-id :approved-amount :swap-proposal)))
|
||||
:json-rpc/call [{:method "wallet_getSuggestedRoutesAsync"
|
||||
|
|
|
@ -20,10 +20,15 @@
|
|||
|
||||
(defn- assets-view
|
||||
[search-text on-change-text]
|
||||
(let [on-token-press (fn [token]
|
||||
(rf/dispatch [:wallet.swap/start
|
||||
{:asset-to-pay token
|
||||
:open-new-screen? false}]))]
|
||||
(let [snt-token (rf/sub [:wallet/token-by-symbol "SNT"])
|
||||
eth-token (rf/sub [:wallet/token-by-symbol "ETH"])
|
||||
on-token-press (fn [token]
|
||||
(let [pay-token-symbol (:symbol token)
|
||||
asset-to-receive (if (= pay-token-symbol "SNT") eth-token snt-token)]
|
||||
(rf/dispatch [:wallet.swap/start
|
||||
{:asset-to-pay token
|
||||
:asset-to-receive asset-to-receive
|
||||
:open-new-screen? false}])))]
|
||||
[:<>
|
||||
[search-input search-text on-change-text]
|
||||
[asset-list/view
|
||||
|
|
|
@ -1,27 +1,29 @@
|
|||
(ns status-im.contexts.wallet.swap.setup-swap.view
|
||||
(:require [clojure.string :as string]
|
||||
[quo.core :as quo]
|
||||
[quo.foundations.colors :as colors]
|
||||
[quo.theme :as quo.theme]
|
||||
[react-native.core :as rn]
|
||||
[react-native.platform :as platform]
|
||||
[react-native.safe-area :as safe-area]
|
||||
[status-im.common.controlled-input.utils :as controlled-input]
|
||||
[status-im.common.events-helper :as events-helper]
|
||||
[status-im.constants :as constants]
|
||||
[status-im.contexts.wallet.common.account-switcher.view :as account-switcher]
|
||||
[status-im.contexts.wallet.common.utils :as utils]
|
||||
[status-im.contexts.wallet.sheets.buy-token.view :as buy-token]
|
||||
[status-im.contexts.wallet.sheets.select-asset.view :as select-asset]
|
||||
[status-im.contexts.wallet.sheets.slippage-settings.view :as slippage-settings]
|
||||
[status-im.contexts.wallet.swap.setup-swap.style :as style]
|
||||
[status-im.contexts.wallet.swap.utils :as swap-utils]
|
||||
[utils.debounce :as debounce]
|
||||
[utils.i18n :as i18n]
|
||||
[utils.money :as money]
|
||||
[utils.number :as number]
|
||||
[utils.re-frame :as rf]
|
||||
[utils.string :as utils.string]))
|
||||
(:require
|
||||
[clojure.string :as string]
|
||||
[oops.core :as oops]
|
||||
[quo.core :as quo]
|
||||
[quo.foundations.colors :as colors]
|
||||
[quo.theme :as quo.theme]
|
||||
[react-native.core :as rn]
|
||||
[react-native.platform :as platform]
|
||||
[react-native.safe-area :as safe-area]
|
||||
[status-im.common.controlled-input.utils :as controlled-input]
|
||||
[status-im.common.events-helper :as events-helper]
|
||||
[status-im.constants :as constants]
|
||||
[status-im.contexts.wallet.common.account-switcher.view :as account-switcher]
|
||||
[status-im.contexts.wallet.common.utils :as utils]
|
||||
[status-im.contexts.wallet.sheets.buy-token.view :as buy-token]
|
||||
[status-im.contexts.wallet.sheets.select-asset.view :as select-asset]
|
||||
[status-im.contexts.wallet.sheets.slippage-settings.view :as slippage-settings]
|
||||
[status-im.contexts.wallet.swap.setup-swap.style :as style]
|
||||
[status-im.contexts.wallet.swap.utils :as swap-utils]
|
||||
[utils.debounce :as debounce]
|
||||
[utils.i18n :as i18n]
|
||||
[utils.money :as money]
|
||||
[utils.number :as number]
|
||||
[utils.re-frame :as rf]
|
||||
[utils.string :as utils.string]))
|
||||
|
||||
(def ^:private default-text-for-unfocused-input "0.00")
|
||||
|
||||
|
@ -97,13 +99,18 @@
|
|||
approved-amount (rf/sub [:wallet/swap-approved-amount])
|
||||
error-response (rf/sub [:wallet/swap-error-response])
|
||||
asset-to-pay (rf/sub [:wallet/token-by-symbol pay-token-symbol])
|
||||
overlay-shown? (boolean (:sheets (rf/sub [:bottom-sheet])))
|
||||
input-ref (rn/use-ref-atom nil)
|
||||
set-input-ref (rn/use-callback (fn [ref] (reset! input-ref ref)))
|
||||
pay-input-num-value (controlled-input/value-numeric input-state)
|
||||
pay-input-amount (controlled-input/input-value input-state)
|
||||
pay-token-decimals (:decimals asset-to-pay)
|
||||
pay-token-balance-selected-chain (get-in asset-to-pay
|
||||
[:balances-per-chain
|
||||
(:chain-id network) :balance]
|
||||
0)
|
||||
pay-token-balance-selected-chain (number/convert-to-whole-number
|
||||
(get-in asset-to-pay
|
||||
[:balances-per-chain
|
||||
(:chain-id network) :raw-balance]
|
||||
0)
|
||||
pay-token-decimals)
|
||||
pay-token-fiat-value (str
|
||||
(utils/calculate-token-fiat-value
|
||||
{:currency currency
|
||||
|
@ -111,10 +118,15 @@
|
|||
:token asset-to-pay}))
|
||||
available-crypto-limit (money/bignumber
|
||||
pay-token-balance-selected-chain)
|
||||
display-decimals (min pay-token-decimals
|
||||
constants/min-token-decimals-to-display)
|
||||
available-crypto-limit-display (number/remove-trailing-zeroes
|
||||
(.toFixed available-crypto-limit
|
||||
(min pay-token-decimals
|
||||
constants/min-token-decimals-to-display)))
|
||||
(.toFixed available-crypto-limit display-decimals))
|
||||
available-crypto-limit-display (if (and (= available-crypto-limit-display "0")
|
||||
(money/greater-than available-crypto-limit
|
||||
(money/bignumber 0)))
|
||||
(number/small-number-threshold display-decimals)
|
||||
available-crypto-limit-display)
|
||||
approval-amount-required-num (when approval-amount-required
|
||||
(str (number/hex->whole approval-amount-required
|
||||
pay-token-decimals)))
|
||||
|
@ -122,8 +134,7 @@
|
|||
(money/greater-than
|
||||
(money/bignumber pay-input-amount)
|
||||
available-crypto-limit))
|
||||
(money/equal-to (money/bignumber
|
||||
available-crypto-limit-display)
|
||||
(money/equal-to available-crypto-limit
|
||||
(money/bignumber 0)))
|
||||
valid-pay-input? (and
|
||||
(not (string/blank?
|
||||
|
@ -141,8 +152,15 @@
|
|||
(fn []
|
||||
(request-fetch-swap-proposal))
|
||||
[pay-input-amount])
|
||||
(rn/use-effect
|
||||
(fn []
|
||||
(when-not overlay-shown?
|
||||
(some-> @input-ref
|
||||
(oops/ocall "focus"))))
|
||||
[overlay-shown?])
|
||||
[quo/swap-input
|
||||
{:type :pay
|
||||
{:get-ref set-input-ref
|
||||
:type :pay
|
||||
:error? pay-input-error?
|
||||
:token pay-token-symbol
|
||||
:customization-color :blue
|
||||
|
@ -156,7 +174,7 @@
|
|||
:else :disabled)
|
||||
:currency-symbol currency-symbol
|
||||
:on-token-press on-token-press
|
||||
:on-max-press #(on-max-press (str available-crypto-limit))
|
||||
:on-max-press #(on-max-press (str pay-token-balance-selected-chain))
|
||||
:on-input-focus on-input-focus
|
||||
:value pay-input-amount
|
||||
:fiat-value pay-token-fiat-value
|
||||
|
@ -315,10 +333,12 @@
|
|||
network (rf/sub [:wallet/swap-network])
|
||||
pay-input-amount (controlled-input/input-value pay-input-state)
|
||||
pay-token-decimals (:decimals asset-to-pay)
|
||||
pay-token-balance-selected-chain (get-in asset-to-pay
|
||||
[:balances-per-chain
|
||||
(:chain-id network) :balance]
|
||||
0)
|
||||
pay-token-balance-selected-chain (number/convert-to-whole-number
|
||||
(get-in asset-to-pay
|
||||
[:balances-per-chain
|
||||
(:chain-id network) :raw-balance]
|
||||
0)
|
||||
pay-token-decimals)
|
||||
pay-input-error? (and (not (string/blank? pay-input-amount))
|
||||
(money/greater-than
|
||||
(money/bignumber pay-input-amount)
|
||||
|
@ -420,13 +440,17 @@
|
|||
(when (and swap-amount refetch-interval)
|
||||
(js/clearTimeout @refetch-interval)
|
||||
(reset! refetch-interval nil))
|
||||
(if (and swap-amount (not= swap-amount pay-input-amount))
|
||||
(set-pay-input-state
|
||||
(fn [input-state]
|
||||
(controlled-input/set-input-value
|
||||
input-state
|
||||
swap-amount)))
|
||||
(refetch-swap-proposal)))))
|
||||
(cond (and swap-amount (not= swap-amount pay-input-amount))
|
||||
(set-pay-input-state
|
||||
(fn [input-state]
|
||||
(controlled-input/set-input-value
|
||||
input-state
|
||||
swap-amount)))
|
||||
(and pay-input-amount
|
||||
(not (number/valid-decimal-count? pay-input-amount (:decimals asset-to-pay))))
|
||||
(set-pay-input-state controlled-input/delete-all)
|
||||
:else
|
||||
(refetch-swap-proposal)))))
|
||||
[asset-to-pay])
|
||||
(rn/use-effect
|
||||
refetch-swap-proposal
|
||||
|
@ -461,7 +485,7 @@
|
|||
:on-token-press #(rf/dispatch [:show-bottom-sheet {:content receive-token-bottom-sheet}])
|
||||
:on-input-focus #(set-pay-input-focused? false)}]]
|
||||
[rn/view {:style style/footer-container}
|
||||
[alert-banner {:pay-input-error? pay-input-error?}]
|
||||
(when-not loading-swap-proposal? [alert-banner {:pay-input-error? pay-input-error?}])
|
||||
(when (or loading-swap-proposal? swap-proposal)
|
||||
[transaction-details])
|
||||
[action-button {:on-press on-review-swap-press}]]
|
||||
|
|
|
@ -492,7 +492,7 @@
|
|||
|
||||
(rf/reg-sub
|
||||
:wallet/token-by-symbol
|
||||
:<- [:wallet/current-viewing-account]
|
||||
:<- [:wallet/current-viewing-account-or-default]
|
||||
:<- [:wallet/network-details]
|
||||
(fn [[{:keys [tokens]} networks] [_ token-symbol chain-ids]]
|
||||
(->> (utils/tokens-with-balance tokens networks chain-ids)
|
||||
|
|
|
@ -2,7 +2,8 @@
|
|||
(:require [clojure.string :as string]
|
||||
[native-module.core :as native-module]
|
||||
[utils.hex :as utils.hex]
|
||||
[utils.money :as utils.money]))
|
||||
[utils.money :as utils.money]
|
||||
[utils.money :as money]))
|
||||
|
||||
(defn naive-round
|
||||
"Quickly and naively round number `n` up to `decimal-places`.
|
||||
|
@ -18,17 +19,6 @@
|
|||
(/ (Math/round (* n scale))
|
||||
scale)))
|
||||
|
||||
(defn convert-to-whole-number
|
||||
"Converts a fractional `amount` to its corresponding whole number representation
|
||||
by dividing it by 10 raised to the power of `decimals`. This is often used in financial
|
||||
calculations where amounts are stored in their smallest units (e.g., cents) and need
|
||||
to be converted to their whole number equivalents (e.g., dollars).
|
||||
|
||||
Example usage:
|
||||
(convert-to-whole-number 12345 2) ; => 123.45"
|
||||
[amount decimals]
|
||||
(/ amount (Math/pow 10 decimals)))
|
||||
|
||||
(defn parse-int
|
||||
"Parses `n` as an integer. Defaults to zero or `default` instead of NaN."
|
||||
([n]
|
||||
|
@ -65,15 +55,44 @@
|
|||
"")
|
||||
""))))
|
||||
|
||||
(defn convert-to-whole-number
|
||||
"Converts a fractional `amount` to its corresponding whole number representation
|
||||
by dividing it by 10 raised to the power of `decimals`. This is often used in financial
|
||||
calculations where amounts are stored in their smallest units (e.g., cents) and need
|
||||
to be converted to their whole number equivalents (e.g., dollars).
|
||||
|
||||
Example usage:
|
||||
(convert-to-whole-number 12345 2) ; => 123.45"
|
||||
[amount decimals]
|
||||
(-> amount
|
||||
(/ (Math/pow 10 decimals))
|
||||
(.toFixed decimals)
|
||||
remove-trailing-zeroes))
|
||||
|
||||
(defn hex->whole
|
||||
[num decimals]
|
||||
(-> num
|
||||
utils.hex/normalize-hex
|
||||
native-module/hex-to-number
|
||||
(convert-to-whole-number decimals)))
|
||||
(convert-to-whole-number decimals)
|
||||
money/bignumber))
|
||||
|
||||
(defn to-fixed
|
||||
[num decimals]
|
||||
(-> num
|
||||
(utils.money/to-fixed decimals)
|
||||
remove-trailing-zeroes))
|
||||
|
||||
(defn small-number-threshold
|
||||
"Receives a decimal count and returns a string like '<0.001' if the decimal count is 3,
|
||||
'<0.000001' if the decimal count is 6, etc."
|
||||
[decimal-count]
|
||||
(if (> decimal-count 0)
|
||||
(str "<0." (apply str (repeat (dec decimal-count) "0")) "1")
|
||||
"0"))
|
||||
|
||||
(defn valid-decimal-count?
|
||||
"Returns false if the number has more decimals than the decimal count, otherwise true."
|
||||
[num decimal-count]
|
||||
(let [decimal-part (second (string/split (str num) #"\."))]
|
||||
(or (nil? decimal-part) (<= (count decimal-part) decimal-count))))
|
||||
|
|
|
@ -5,23 +5,23 @@
|
|||
|
||||
(deftest convert-to-whole-number-test
|
||||
(testing "correctly converts fractional amounts to whole numbers"
|
||||
(is (= 123.45 (utils.number/convert-to-whole-number 12345 2)))
|
||||
(is (= 1.2345 (utils.number/convert-to-whole-number 12345 4)))
|
||||
(is (= 12345.0 (utils.number/convert-to-whole-number 1234500 2)))
|
||||
(is (= 0.123 (utils.number/convert-to-whole-number 123 3)))
|
||||
(is (= 1000.0 (utils.number/convert-to-whole-number 1000000 3))))
|
||||
(is (= "123.45" (utils.number/convert-to-whole-number 12345 2)))
|
||||
(is (= "1.2345" (utils.number/convert-to-whole-number 12345 4)))
|
||||
(is (= "12345" (utils.number/convert-to-whole-number 1234500 2)))
|
||||
(is (= "0.123" (utils.number/convert-to-whole-number 123 3)))
|
||||
(is (= "1000" (utils.number/convert-to-whole-number 1000000 3))))
|
||||
|
||||
(testing "handles zero decimals"
|
||||
(is (= 12345 (utils.number/convert-to-whole-number 12345 0))))
|
||||
(is (= "12345" (utils.number/convert-to-whole-number 12345 0))))
|
||||
|
||||
(testing "handles negative amounts"
|
||||
(is (= -123.45 (utils.number/convert-to-whole-number -12345 2)))
|
||||
(is (= -1.2345 (utils.number/convert-to-whole-number -12345 4)))
|
||||
(is (= -0.123 (utils.number/convert-to-whole-number -123 3))))
|
||||
(is (= "-123.45" (utils.number/convert-to-whole-number -12345 2)))
|
||||
(is (= "-1.2345" (utils.number/convert-to-whole-number -12345 4)))
|
||||
(is (= "-0.123" (utils.number/convert-to-whole-number -123 3))))
|
||||
|
||||
(testing "handles zero amount"
|
||||
(is (= 0 (utils.number/convert-to-whole-number 0 2)))
|
||||
(is (= 0 (utils.number/convert-to-whole-number 0 0)))))
|
||||
(is (= "0" (utils.number/convert-to-whole-number 0 2)))
|
||||
(is (= "0" (utils.number/convert-to-whole-number 0 0)))))
|
||||
|
||||
(deftest parse-int-test
|
||||
(testing "defaults to zero"
|
||||
|
@ -50,3 +50,36 @@
|
|||
(is (= 6 (utils.number/parse-float "6")))
|
||||
(is (= 6.99 (utils.number/parse-float "6.99" 0)))
|
||||
(is (= -6.9 (utils.number/parse-float "-6.9" 0)))))
|
||||
|
||||
(deftest small-number-threshold-test
|
||||
(testing "correctly generates threshold strings based on decimal count"
|
||||
(is (= "<0.1" (utils.number/small-number-threshold 1)))
|
||||
(is (= "<0.01" (utils.number/small-number-threshold 2)))
|
||||
(is (= "<0.001" (utils.number/small-number-threshold 3)))
|
||||
(is (= "<0.000001" (utils.number/small-number-threshold 6)))
|
||||
(is (= "<0.0000000001" (utils.number/small-number-threshold 10)))
|
||||
(is (= "<0.000000000000000001" (utils.number/small-number-threshold 18)))
|
||||
(is (= "<0.000000000000000000001" (utils.number/small-number-threshold 21))))
|
||||
|
||||
(testing "handles edge cases for decimal count"
|
||||
(is (= "0" (utils.number/small-number-threshold 0)))
|
||||
(is (= "0" (utils.number/small-number-threshold -1)))))
|
||||
|
||||
(deftest valid-decimal-count?-test
|
||||
(testing "valid decimal count check for numbers with varying decimals"
|
||||
(is (true? (utils.number/valid-decimal-count? 123 2)))
|
||||
(is (true? (utils.number/valid-decimal-count? 123 0)))
|
||||
|
||||
(is (true? (utils.number/valid-decimal-count? 123.45 2)))
|
||||
(is (true? (utils.number/valid-decimal-count? 123.4 2)))
|
||||
(is (true? (utils.number/valid-decimal-count? 123.456 3)))
|
||||
|
||||
(is (false? (utils.number/valid-decimal-count? 123.456 2)))
|
||||
(is (false? (utils.number/valid-decimal-count? 123.456789 4)))
|
||||
|
||||
(is (true? (utils.number/valid-decimal-count? 123.0 1)))
|
||||
(is (true? (utils.number/valid-decimal-count? -123.45 2)))
|
||||
(is (false? (utils.number/valid-decimal-count? -123.4567 3)))
|
||||
|
||||
(is (true? (utils.number/valid-decimal-count? 1234567890.12 2)))
|
||||
(is (false? (utils.number/valid-decimal-count? 1234567890.12345 3)))))
|
||||
|
|
Loading…
Reference in New Issue