diff --git a/src/renderer/components/settings/AppGeneralSettings.tsx b/src/renderer/components/settings/AppGeneralSettings.tsx index 4e00d5dd1..e9a7eb74a 100644 --- a/src/renderer/components/settings/AppGeneralSettings.tsx +++ b/src/renderer/components/settings/AppGeneralSettings.tsx @@ -15,8 +15,10 @@ import { useIntl } from 'react-intl' import { Dex } from '../../../shared/api/types' import { Locale } from '../../../shared/i18n/types' +import { saveInStorage, StorageKey } from '../../helpers/storageHelper' import { LOCALES } from '../../i18n' -import { AVAILABLE_DEXS, AVAILABLE_NETWORKS } from '../../services/const' +import { AVAILABLE_DEXS, AVAILABLE_NETWORKS, BaseUnit, BTC_BASE_UNITS } from '../../services/const' +import { useApp } from '../../store/app/hooks' import { DownIcon } from '../icons' import { Menu } from '../shared/menu' import { BorderButton } from '../uielements/button' @@ -71,6 +73,8 @@ export const AppGeneralSettings: React.FC = (props): JSX.Element => { locale } = props + const { btcBaseUnit, setBtcBaseUnit } = useApp() + const intl = useIntl() const changeLang: MenuProps['onClick'] = useCallback( @@ -122,6 +126,7 @@ export const AppGeneralSettings: React.FC = (props): JSX.Element => { }, [changeNetwork] ) + const changeDexHandler: MenuProps['onClick'] = useCallback( ({ key }: { key: string }) => { const newDex = AVAILABLE_DEXS.find((dex) => dex.chain === key) @@ -132,6 +137,17 @@ export const AppGeneralSettings: React.FC = (props): JSX.Element => { [changeDex] ) + const changeBtcBaseUnit: MenuProps['onClick'] = useCallback( + ({ key }: { key: string }) => { + const newUnit = BTC_BASE_UNITS.find((btcUnit) => btcUnit.unit === key) + if (newUnit) { + setBtcBaseUnit(newUnit) + saveInStorage(StorageKey.BtcBaseUnit, JSON.stringify(newUnit)) + } + }, + [setBtcBaseUnit] + ) + const networkTextColor = useCallback((network: Network) => { switch (network) { case Network.Mainnet: @@ -203,6 +219,29 @@ export const AppGeneralSettings: React.FC = (props): JSX.Element => { ) }, [changeDexHandler, dex, dexTextColor]) + const btcBaseUnitMenu = useMemo(() => { + return ( + ((baseUnit: BaseUnit) => ({ + label: ( +
+ {baseUnit.unit} +
+ ), + key: baseUnit.unit + })) + )} + /> + ) + }, [btcBaseUnit.unit, changeBtcBaseUnit]) + const renderNetworkMenu = useMemo( () => ( @@ -227,6 +266,19 @@ export const AppGeneralSettings: React.FC = (props): JSX.Element => { ), [dexMenu, dexTextColor, dex] ) + const renderBtcBaseUnitMenu = useMemo( + () => ( + +
+

+ {btcBaseUnit.unit} +

+ +
+
+ ), + [btcBaseUnitMenu, btcBaseUnit.unit] + ) const checkUpdatesProps = useMemo(() => { const commonProps = { @@ -243,9 +295,7 @@ export const AppGeneralSettings: React.FC = (props): JSX.Element => { loading: true, disabled: true }), - () => ({ - ...commonProps - }), + () => ({ ...commonProps }), (oVersion) => ({ ...commonProps, ...FP.pipe( @@ -301,6 +351,9 @@ export const AppGeneralSettings: React.FC = (props): JSX.Element => {
{renderDexMenu}
+
+ {renderBtcBaseUnitMenu} +
{renderLangMenu}
diff --git a/src/renderer/components/uielements/assets/assetInput/AssetInput.tsx b/src/renderer/components/uielements/assets/assetInput/AssetInput.tsx index 5254309a2..e1fbb28fb 100644 --- a/src/renderer/components/uielements/assets/assetInput/AssetInput.tsx +++ b/src/renderer/components/uielements/assets/assetInput/AssetInput.tsx @@ -22,7 +22,7 @@ import { CheckButton } from '../../button/CheckButton' import { InputBigNumber } from '../../input' import { AssetSelect } from '../assetSelect' -export const ASSET_SELECT_BUTTON_WIDTH = 'w-[180px]' +export const ASSET_SELECT_BUTTON_WIDTH = 'w-[190px]' export type Props = { title?: string diff --git a/src/renderer/components/uielements/assets/assetSelect/AssetSelect.tsx b/src/renderer/components/uielements/assets/assetSelect/AssetSelect.tsx index 48a43de5f..8035f6f59 100644 --- a/src/renderer/components/uielements/assets/assetSelect/AssetSelect.tsx +++ b/src/renderer/components/uielements/assets/assetSelect/AssetSelect.tsx @@ -66,6 +66,7 @@ export const AssetSelect: React.FC = (props): JSX.Element => { = (props): JSX.Element => { } = props const { amount, asset } = balance + const { btcBaseUnit } = useApp() const intl = useIntl() const onClickHandler = useCallback(() => onClick(amount), [amount, onClick]) @@ -52,11 +54,12 @@ export const MaxBalanceButton: React.FC = (props): JSX.Element => { decimal: 6, trimZeros: true }) - : `${formatAssetAmountCurrency({ + : `${formatAssetAmountCurrencyAsg({ amount: baseToAsset(amount), asset, decimal: 6, - trimZeros: true + trimZeros: true, + baseUnit: btcBaseUnit })} (${formatAssetAmountCurrency({ amount: maxDollarValue.assetAmount, asset: maxDollarValue.asset, @@ -74,7 +77,7 @@ export const MaxBalanceButton: React.FC = (props): JSX.Element => { className={clsx('mr-5px w-auto whitespace-nowrap !p-0', classNameButton)}> {intl.formatMessage({ id: 'common.max' })}:   - {hidePrivateData ? hiddenString : `${valueLabel}`} + {hidePrivateData ? hiddenString : valueLabel} {maxInfoText && } diff --git a/src/renderer/components/wallet/assets/AssetsTableCollapsable.tsx b/src/renderer/components/wallet/assets/AssetsTableCollapsable.tsx index 244a1d815..5c3e94b9b 100644 --- a/src/renderer/components/wallet/assets/AssetsTableCollapsable.tsx +++ b/src/renderer/components/wallet/assets/AssetsTableCollapsable.tsx @@ -633,7 +633,7 @@ export const AssetsTableCollapsable = (props: Props): JSX.Element => { {isEvmChain(chain) && ( - // @asgardexTeam Add Locale for tooltip + // TODO: @asgardexTeam Add Locale for tooltip )} {assetsTxt} diff --git a/src/renderer/components/wallet/txs/send/SendFormUTXO.tsx b/src/renderer/components/wallet/txs/send/SendFormUTXO.tsx index fd80fb428..31f27530f 100644 --- a/src/renderer/components/wallet/txs/send/SendFormUTXO.tsx +++ b/src/renderer/components/wallet/txs/send/SendFormUTXO.tsx @@ -45,6 +45,7 @@ import { PoolAddress, PoolDetails } from '../../../../services/midgard/types' import { FeesWithRatesRD } from '../../../../services/utxo/types' import { SelectedWalletAsset, ValidatePasswordHandler } from '../../../../services/wallet/types' import { WalletBalance } from '../../../../services/wallet/types' +import { useApp } from '../../../../store/app/hooks' import { LedgerConfirmationModal, WalletPasswordConfirmationModal } from '../../../modal/confirmation' import * as StyledR from '../../../shared/form/Radio.styles' import { BaseButton, FlatButton } from '../../../uielements/button' @@ -106,10 +107,13 @@ export const SendFormUTXO: React.FC = (props): JSX.Element => { dex } = props + const { btcBaseUnit } = useApp() const intl = useIntl() const { asset } = balance + const isBTC = useMemo(() => asset.chain === 'BTC' && asset.ticker === 'BTC', [asset]) + const oSavedAddresses: O.Option = useMemo( () => FP.pipe(O.fromNullable(trustedAddresses?.addresses), O.map(A.filter((address) => address.chain === asset.chain))), @@ -694,11 +698,11 @@ export const SendFormUTXO: React.FC = (props): JSX.Element => { // we have to validate input before storing into the state amountValidator(undefined, value) .then(() => { - setAmountToSend(assetToBase(assetAmount(value, balance.amount.decimal))) + setAmountToSend(assetToBase(assetAmount(value, isBTC ? btcBaseUnit.decimal : balance.amount.decimal))) }) .catch(() => {}) // do nothing, Ant' form does the job for us to show an error message }, - [amountValidator, balance.amount.decimal] + [amountValidator, balance.amount.decimal, btcBaseUnit.decimal, isBTC] ) const reloadFees = useCallback(() => { @@ -780,7 +784,7 @@ export const SendFormUTXO: React.FC = (props): JSX.Element => { min={0} size="large" disabled={isLoading} - decimal={balance.amount.decimal} + decimal={isBTC ? btcBaseUnit.decimal : balance.amount.decimal} onChange={onChangeInput} /> diff --git a/src/renderer/helpers/assetHelper.ts b/src/renderer/helpers/assetHelper.ts index df4847c0b..17d8e2906 100644 --- a/src/renderer/helpers/assetHelper.ts +++ b/src/renderer/helpers/assetHelper.ts @@ -12,12 +12,17 @@ import { AnyAsset, assetAmount, AssetAmount, + AssetCurrencySymbol, assetFromString, + assetToBase, AssetType, baseAmount, BaseAmount, bn, Chain, + formatAssetAmount, + formatAssetAmountCurrency, + formatBaseAmount, TokenAsset } from '@xchainjs/xchain-util' import BigNumber from 'bignumber.js' @@ -43,6 +48,7 @@ import { } from '../../shared/utils/asset' import { isSupportedChain } from '../../shared/utils/chain' import { AssetTGTERC20, DEFAULT_PRICE_ASSETS, USD_PRICE_ASSETS } from '../const' +import { BaseUnit } from '../services/const' import { EVMZeroAddress } from '../services/evm/const' import { ARB_TOKEN_WHITELIST } from '../types/generated/mayachain/arberc20whitelist' import { AVAX_TOKEN_WHITELIST } from '../types/generated/thorchain/avaxerc20whitelist' @@ -564,3 +570,42 @@ export const getTwoSigfigAssetAmount = (amount: AssetAmount) => { */ export const getAssetFromNullableString = (assetString?: string): O.Option => FP.pipe(O.fromNullable(assetString), O.map(S.toUpperCase), O.map(assetFromString), O.chain(O.fromNullable)) + +export const formatAssetAmountCurrencyAsg = ({ + amount, + asset, + decimal, + trimZeros = false, + baseUnit +}: { + amount: AssetAmount + asset?: AnyAsset + decimal?: number + trimZeros?: boolean + baseUnit?: BaseUnit +}) => { + const amountFormatted = formatAssetAmount({ + amount, + // strict check for `undefined` value as negate of 0 will return true and passed decimal value will be ignored + decimal: 18, + trimZeros + }) + const ticker = asset?.ticker ?? '' + + if (ticker) { + const regex = new RegExp(AssetBTC.ticker, 'i') + if (ticker.match(regex)) { + const base = assetToBase(amount) + const baseUnitDecimal = baseUnit?.decimal ?? 8 + // format all < ₿ 0.01 in statoshi + if (baseUnitDecimal === 1) { + return `${AssetCurrencySymbol.SATOSHI} ${formatBaseAmount(base)}` + } else if (baseUnitDecimal === 5) { + // TODO: @Thorianite review + return `${formatBaseAmount(base.div(1e3))} mBTC` + } else if (baseUnitDecimal === 8) return `${AssetCurrencySymbol.BTC} ${amountFormatted}` + } + } + + return formatAssetAmountCurrency({ amount, asset, decimal, trimZeros }) +} diff --git a/src/renderer/helpers/storageHelper.ts b/src/renderer/helpers/storageHelper.ts new file mode 100644 index 000000000..7090038b4 --- /dev/null +++ b/src/renderer/helpers/storageHelper.ts @@ -0,0 +1,9 @@ +export enum StorageKey { + BtcBaseUnit = 'btc-base-unit' +} + +export const getFromStorage = (key: StorageKey) => localStorage.getItem(key) + +export const saveInStorage = (key: StorageKey, value: string) => { + localStorage.setItem(key, value) +} diff --git a/src/renderer/services/const.ts b/src/renderer/services/const.ts index 88b937133..857ee42e0 100644 --- a/src/renderer/services/const.ts +++ b/src/renderer/services/const.ts @@ -24,6 +24,14 @@ export const AVAILABLE_NETWORKS: Network[] = envOrDefault(process.env.REACT_APP_ */ export const AVAILABLE_DEXS: Dex[] = [thorDetails, mayaDetails] +export type BaseUnit = { unit: string; decimal: number } + +export const BTC_BASE_UNITS: BaseUnit[] = [ + { unit: 'BTC', decimal: 8 }, + { unit: 'mBTC', decimal: 5 }, + { unit: 'satoshi', decimal: 1 } +] + const ENV_NETWORK = process.env.REACT_APP_DEFAULT_NETWORK /** diff --git a/src/renderer/store/app/hooks.ts b/src/renderer/store/app/hooks.ts index 9f3537d2b..6cb4a7626 100644 --- a/src/renderer/store/app/hooks.ts +++ b/src/renderer/store/app/hooks.ts @@ -2,6 +2,7 @@ import { useCallback } from 'react' import { useDispatch, useSelector } from 'react-redux' +import { BaseUnit } from '../../services/const' import { RootState } from '../store' import { actions } from './slice' @@ -23,9 +24,17 @@ export const useApp = () => { [dispatch] ) + const setBtcBaseUnit = useCallback( + (btcBaseUnit: BaseUnit) => { + dispatch(actions.setBtcBaseUnit(btcBaseUnit)) + }, + [dispatch] + ) + return { ...appState, changePrivateData, - setIsWhitelistModalOpen + setIsWhitelistModalOpen, + setBtcBaseUnit } } diff --git a/src/renderer/store/app/slice.ts b/src/renderer/store/app/slice.ts index a8b372d92..155a5c1c6 100644 --- a/src/renderer/store/app/slice.ts +++ b/src/renderer/store/app/slice.ts @@ -1,10 +1,13 @@ import { createSlice, PayloadAction } from '@reduxjs/toolkit' +import { getFromStorage, saveInStorage, StorageKey } from '../../helpers/storageHelper' +import { BaseUnit, BTC_BASE_UNITS } from '../../services/const' import { AppState } from './types' const initialState: AppState = { isPrivate: false, - isWhitelistModalOpen: false + isWhitelistModalOpen: false, + btcBaseUnit: JSON.parse(getFromStorage(StorageKey.BtcBaseUnit) ?? JSON.stringify(BTC_BASE_UNITS[0])) } const slice = createSlice({ @@ -16,6 +19,10 @@ const slice = createSlice({ }, setIsWhitelistModalOpen(state, action: PayloadAction) { state.isWhitelistModalOpen = action.payload + }, + setBtcBaseUnit(state, action: PayloadAction) { + state.btcBaseUnit = action.payload + saveInStorage(StorageKey.BtcBaseUnit, JSON.stringify(action.payload)) } } }) diff --git a/src/renderer/store/app/types.ts b/src/renderer/store/app/types.ts index b32322e36..c9a5495ad 100644 --- a/src/renderer/store/app/types.ts +++ b/src/renderer/store/app/types.ts @@ -1,4 +1,7 @@ +import { BaseUnit } from '../../services/const' + export type AppState = { isPrivate: boolean isWhitelistModalOpen: boolean + btcBaseUnit: BaseUnit }