From f1175280cfedc69590b184ce36b670e986525cfc Mon Sep 17 00:00:00 2001 From: fmtabbara Date: Mon, 26 Jun 2023 12:17:52 +0100 Subject: [PATCH 1/4] add warning to tx modals when fee exceeds balance --- .../Bonding/modals/RedeemRewardsModal.tsx | 13 +- .../components/Delegation/UndelegateModal.tsx | 10 +- .../src/components/FeeWarning.stories.tsx | 27 ++ nym-wallet/src/components/FeeWarning.tsx | 19 +- .../src/components/Rewards/RedeemModal.tsx | 13 +- nym-wallet/src/pages/delegation/index.tsx | 1 - nym-wallet/src/utils/generalUtils.ts | 245 ++++++++++++++++++ nym-wallet/src/utils/index.ts | 233 +---------------- 8 files changed, 321 insertions(+), 240 deletions(-) create mode 100644 nym-wallet/src/components/FeeWarning.stories.tsx create mode 100644 nym-wallet/src/utils/generalUtils.ts diff --git a/nym-wallet/src/components/Bonding/modals/RedeemRewardsModal.tsx b/nym-wallet/src/components/Bonding/modals/RedeemRewardsModal.tsx index 76b9192df9..88490e7e0e 100644 --- a/nym-wallet/src/components/Bonding/modals/RedeemRewardsModal.tsx +++ b/nym-wallet/src/components/Bonding/modals/RedeemRewardsModal.tsx @@ -1,11 +1,13 @@ -import React, { useEffect } from 'react'; +import React, { useContext, useEffect } from 'react'; import { FeeDetails } from '@nymproject/types'; import { ModalListItem } from 'src/components/Modals/ModalListItem'; import { SimpleModal } from 'src/components/Modals/SimpleModal'; import { ModalFee } from 'src/components/Modals/ModalFee'; import { useGetFee } from 'src/hooks/useGetFee'; import { simulateClaimOperatorReward, simulateVestingClaimOperatorReward } from 'src/requests'; -import { TBondedMixnode } from 'src/context'; +import { AppContext, TBondedMixnode } from 'src/context'; +import { BalanceWarning } from 'src/components/FeeWarning'; +import { Box } from '@mui/material'; export const RedeemRewardsModal = ({ node, @@ -19,6 +21,7 @@ export const RedeemRewardsModal = ({ onClose: () => void; }) => { const { fee, getFee, isFeeLoading, feeError } = useGetFee(); + const { userBalance } = useContext(AppContext); useEffect(() => { if (feeError) onError(feeError); @@ -49,7 +52,13 @@ export const RedeemRewardsModal = ({ divider /> + + {userBalance.balance?.amount.amount && fee?.amount?.amount && ( + + + + )} ); }; diff --git a/nym-wallet/src/components/Delegation/UndelegateModal.tsx b/nym-wallet/src/components/Delegation/UndelegateModal.tsx index fe518d37ff..12662ef3d5 100644 --- a/nym-wallet/src/components/Delegation/UndelegateModal.tsx +++ b/nym-wallet/src/components/Delegation/UndelegateModal.tsx @@ -1,11 +1,13 @@ import { Box, SxProps } from '@mui/material'; -import React, { useEffect } from 'react'; +import React, { useContext, useEffect } from 'react'; import { FeeDetails } from '@nymproject/types'; import { useGetFee } from 'src/hooks/useGetFee'; import { simulateUndelegateFromMixnode, simulateVestingUndelegateFromMixnode } from 'src/requests'; +import { AppContext } from 'src/context'; import { ModalFee } from '../Modals/ModalFee'; import { ModalListItem } from '../Modals/ModalListItem'; import { SimpleModal } from '../Modals/SimpleModal'; +import { BalanceWarning } from '../FeeWarning'; export const UndelegateModal: FCWithChildren<{ open: boolean; @@ -20,6 +22,7 @@ export const UndelegateModal: FCWithChildren<{ backdropProps?: object; }> = ({ mixId, identityKey, open, onClose, onOk, amount, currency, usesVestingContractTokens, sx, backdropProps }) => { const { fee, isFeeLoading, feeError, getFee } = useGetFee(); + const { userBalance } = useContext(AppContext); useEffect(() => { if (usesVestingContractTokens) getFee(simulateVestingUndelegateFromMixnode, { mixId }); @@ -50,6 +53,11 @@ export const UndelegateModal: FCWithChildren<{ + {userBalance.balance?.amount.amount && fee?.amount?.amount && ( + + + + )} ); diff --git a/nym-wallet/src/components/FeeWarning.stories.tsx b/nym-wallet/src/components/FeeWarning.stories.tsx new file mode 100644 index 0000000000..9202ecc300 --- /dev/null +++ b/nym-wallet/src/components/FeeWarning.stories.tsx @@ -0,0 +1,27 @@ +import * as React from 'react'; +import { ComponentMeta, ComponentStory } from '@storybook/react'; +import { Box } from '@mui/material'; +import { BalanceWarning } from './FeeWarning'; + +export default { + title: 'Wallet / Balance warning', + component: BalanceWarning, +} as ComponentMeta; + +const Template: ComponentStory = (args) => ( + + + +); + +export const WithWarning = Template.bind({}); +WithWarning.args = { + balance: '100', + fee: '200', +}; + +export const NoWarning = Template.bind({}); +NoWarning.args = { + balance: '100', + fee: '100', +}; diff --git a/nym-wallet/src/components/FeeWarning.tsx b/nym-wallet/src/components/FeeWarning.tsx index b26a7f8369..8f22a07591 100644 --- a/nym-wallet/src/components/FeeWarning.tsx +++ b/nym-wallet/src/components/FeeWarning.tsx @@ -1,16 +1,31 @@ import React from 'react'; import { Warning } from '@mui/icons-material'; import { FeeDetails } from '@nymproject/types'; -import { Alert, AlertTitle } from '@mui/material'; +import { Alert, AlertTitle, Box } from '@mui/material'; +import { isBalanceEnough } from 'src/utils'; export const FeeWarning = ({ fee, amount }: { fee: FeeDetails; amount: number }) => { if (fee.amount && +fee.amount.amount > amount) { return ( }> - Warning: fees are greater than the reward + Warning: Fees are greater than the reward The fees for redeeming rewards will cost more than the rewards. Are you sure you want to continue? ); } return null; }; + +export const BalanceWarning = ({ balance, fee }: { balance: string; fee: string }) => { + const hasEnoughBalanace = isBalanceEnough(balance, fee); + + if (!balance || !fee || hasEnoughBalanace) return null; + + return ( + }> + Warning: Fees are greater than your balance + The fess are greater than your current balance which cause this transaction to fail. + Do you want to continue? + + ); +}; diff --git a/nym-wallet/src/components/Rewards/RedeemModal.tsx b/nym-wallet/src/components/Rewards/RedeemModal.tsx index 9d80a5d048..ec6bde1846 100644 --- a/nym-wallet/src/components/Rewards/RedeemModal.tsx +++ b/nym-wallet/src/components/Rewards/RedeemModal.tsx @@ -1,12 +1,13 @@ -import React, { useEffect } from 'react'; +import React, { useContext, useEffect } from 'react'; import { IdentityKeyFormField } from '@nymproject/react/mixnodes/IdentityKeyFormField'; import { CurrencyDenom, FeeDetails } from '@nymproject/types'; -import { SxProps } from '@mui/material'; +import { Box, SxProps } from '@mui/material'; import { useGetFee } from 'src/hooks/useGetFee'; import { simulateClaimDelegatorReward, simulateVestingClaimDelegatorReward } from 'src/requests'; +import { AppContext } from 'src/context'; import { ModalFee } from '../Modals/ModalFee'; import { SimpleModal } from '../Modals/SimpleModal'; -import { FeeWarning } from '../FeeWarning'; +import { BalanceWarning, FeeWarning } from '../FeeWarning'; import { ModalListItem } from '../Modals/ModalListItem'; export const RedeemModal: FCWithChildren<{ @@ -23,6 +24,7 @@ export const RedeemModal: FCWithChildren<{ usesVestingTokens: boolean; }> = ({ open, onClose, onOk, mixId, identityKey, amount, denom, message, usesVestingTokens, sx, backdropProps }) => { const { fee, isFeeLoading, feeError, getFee } = useGetFee(); + const { userBalance } = useContext(AppContext); const handleOk = async () => { if (onOk) { @@ -56,6 +58,11 @@ export const RedeemModal: FCWithChildren<{ {fee && } + {userBalance.balance?.amount.amount && fee?.amount?.amount && ( + + + + )} ); }; diff --git a/nym-wallet/src/pages/delegation/index.tsx b/nym-wallet/src/pages/delegation/index.tsx index ce5156aee5..77f0db7091 100644 --- a/nym-wallet/src/pages/delegation/index.tsx +++ b/nym-wallet/src/pages/delegation/index.tsx @@ -249,7 +249,6 @@ export const Delegation: FC<{ isStorybook?: boolean }> = ({ isStorybook }) => { tx = await undelegate(mixId, fee?.fee); } - // const txs = await undelegate(mixId, usesVestingContractTokens, fee); const balances = await getAllBalances(); setConfirmationModalProps({ diff --git a/nym-wallet/src/utils/generalUtils.ts b/nym-wallet/src/utils/generalUtils.ts new file mode 100644 index 0000000000..39366fa724 --- /dev/null +++ b/nym-wallet/src/utils/generalUtils.ts @@ -0,0 +1,245 @@ +import { appWindow } from '@tauri-apps/api/window'; +import bs58 from 'bs58'; +import Big from 'big.js'; +import { valid } from 'semver'; +import { add, format, fromUnixTime } from 'date-fns'; +import { DecCoin, isValidRawCoin, MixNodeCostParams } from '@nymproject/types'; +import { TPoolOption } from 'src/components'; +import { + getCurrentInterval, + getDefaultMixnodeCostParams, + getLockedCoins, + getSpendableCoins, + userBalance, +} from '../requests'; +import { Console } from './console'; + +export const validateKey = (key: string, bytesLength: number): boolean => { + // it must be a valid base58 key + try { + const bytes = bs58.decode(key); + // of length 32 + return bytes.length === bytesLength; + } catch (e) { + Console.error(e as string); + return false; + } +}; + +export const validateAmount = async ( + majorAmountAsString: DecCoin['amount'], + minimumAmountAsString: DecCoin['amount'], +): Promise => { + // tests basic coin value requirements, like no more than 6 decimal places, value lower than total supply, etc + if (!Number(majorAmountAsString)) { + return false; + } + + if (!isValidRawCoin(majorAmountAsString)) { + return false; + } + + const majorValueFloat = parseInt(majorAmountAsString, Number(10)); + + return majorValueFloat >= parseInt(minimumAmountAsString, Number(10)); + + // this conversion seems really iffy but I'm not sure how to better approach it +}; + +export const isValidHostname = (value: string) => { + // regex for ipv4 and ipv6 and hhostname- source http://jsfiddle.net/DanielD/8S4nq/ + const hostnameRegex = + /((^((([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5]))\s*$)|(^\s*((([0-9A-Fa-f]{1,4}:){7}([0-9A-Fa-f]{1,4}|:))|(([0-9A-Fa-f]{1,4}:){6}(:[0-9A-Fa-f]{1,4}|((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){5}(((:[0-9A-Fa-f]{1,4}){1,2})|:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){4}(((:[0-9A-Fa-f]{1,4}){1,3})|((:[0-9A-Fa-f]{1,4})?:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){3}(((:[0-9A-Fa-f]{1,4}){1,4})|((:[0-9A-Fa-f]{1,4}){0,2}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){2}(((:[0-9A-Fa-f]{1,4}){1,5})|((:[0-9A-Fa-f]{1,4}){0,3}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){1}(((:[0-9A-Fa-f]{1,4}){1,6})|((:[0-9A-Fa-f]{1,4}){0,4}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(:(((:[0-9A-Fa-f]{1,4}){1,7})|((:[0-9A-Fa-f]{1,4}){0,5}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:)))(%.+)?\s*$))|(^\s*((?=.{1,255}$)(?=.*[A-Za-z].*)[0-9A-Za-z](?:(?:[0-9A-Za-z]|\b-){0,61}[0-9A-Za-z])?(?:\.[0-9A-Za-z](?:(?:[0-9A-Za-z]|\b-){0,61}[0-9A-Za-z])?)*)\s*$)/; + + return hostnameRegex.test(value); +}; + +export const validateVersion = (version: string): boolean => { + try { + return valid(version) !== null; + } catch (e) { + return false; + } +}; + +export const validateLocation = (location: string): boolean => { + const locationRegex = /^[a-z]+$/i; + return locationRegex.test(location); +}; + +export const validateRawPort = (rawPort: number): boolean => !Number.isNaN(rawPort) && rawPort >= 1 && rawPort <= 65535; + +export const truncate = (text: string, trim: number) => `${text.substring(0, trim)}...`; + +export const isGreaterThan = (a: number, b: number) => a > b; + +export const isLessThan = (a: number, b: number) => a < b; + +export const checkHasEnoughFunds = async (allocationValue: string): Promise => { + try { + const walletValue = await userBalance(); + + const remainingBalance = +walletValue.amount.amount - +allocationValue; + return remainingBalance >= 0; + } catch (e) { + Console.log(e as string); + return false; + } +}; + +export const checkHasEnoughLockedTokens = async (allocationValue: string) => { + try { + const lockedTokens = await getLockedCoins(); + const spendableTokens = await getSpendableCoins(); + const remainingBalance = +lockedTokens.amount + +spendableTokens.amount - +allocationValue; + return remainingBalance >= 0; + } catch (e) { + Console.error(e as string); + } + return false; +}; + +export const randomNumberBetween = (min: number, max: number) => { + const minCeil = Math.ceil(min); + const maxFloor = Math.floor(max); + return Math.floor(Math.random() * (maxFloor - minCeil + 1) + minCeil); +}; + +export const splice = (size: number, address?: string): string => { + if (address) { + return `${address.slice(0, size)}...${address.slice(-size)}`; + } + return ''; +}; + +export const maximizeWindow = async () => { + await appWindow.maximize(); +}; + +export function removeObjectDuplicates(arr: T[], id: K) { + return arr.filter((v, i, a) => a.findIndex((v2) => v2[id] === v[id]) === i); +} + +export const checkTokenBalance = async (tokenPool: TPoolOption, amount: string) => { + let hasEnoughFunds = false; + if (tokenPool === 'locked') { + hasEnoughFunds = await checkHasEnoughLockedTokens(amount); + } + + if (tokenPool === 'balance') { + hasEnoughFunds = await checkHasEnoughFunds(amount); + } + + return hasEnoughFunds; +}; + +export const isDecimal = (value: number) => value - Math.floor(value) !== 0; + +export const attachDefaultOperatingCost = async (profitMarginPercent: string): Promise => + getDefaultMixnodeCostParams(profitMarginPercent); + +/** + * Converts a stringified percentage integer (0-100) to a stringified float (0.0-1.0). + * + * @param value - the percentage to convert + * @returns A stringified float + */ +export const toPercentFloatString = (value: string) => (Number(value) / 100).toString(); + +/** + * Converts a stringified percentage float (0.0-1.0) to a stringified integer (0-100). + * + * @param value - the percentage to convert + * @returns A stringified integer + */ +export const toPercentIntegerString = (value: string) => Math.round(Number(value) * 100).toString(); + +/** + * Converts a decimal number to a pretty representation + * with fixed decimal places. + * + * @param val - a decimal number of string form + * @param dp - number of decimal places (4 by default ie. 0.0000) + * @returns A prettified decimal number + */ +export const toDisplay = (val: string | number | Big, dp = 4) => { + let displayValue; + try { + displayValue = Big(val).toFixed(dp); + } catch (e: any) { + Console.warn(`${displayValue} not a valid decimal number: ${e}`); + } + return displayValue; +}; + +/** + * Takes a DecCoin and prettify its amount to a representation + * with fixed decimal places. + * + * @param coin - a DecCoin + * @param dp - number of decimal places to apply to amount (4 by default ie. 0.0000) + * @returns A DecCoin with prettified amount + */ +export const decCoinToDisplay = (coin: DecCoin, dp = 4) => { + const displayCoin = { ...coin }; + try { + displayCoin.amount = Big(coin.amount).toFixed(dp); + } catch (e: any) { + Console.warn(`${coin.amount} not a valid decimal number: ${e}`); + } + return displayCoin; +}; + +/** + * Converts a decimal number of μNYM (micro NYM) to NYM. + * + * @param unym - string representation of a decimal number of μNYM + * @param dp - number of decimal places (4 by default ie. 0.0000) + * @returns The corresponding decimal number in NYM + */ +export const unymToNym = (unym: string | Big, dp = 4) => { + let nym; + try { + nym = Big(unym).div(1_000_000).toFixed(dp); + } catch (e: any) { + Console.warn(`${unym} not a valid decimal number: ${e}`); + } + return nym; +}; + +/** + * + * Checks if the user's balance is enough to pay the fee + * @param balance - The user's balance to check + * @param fee - The fee to check + * @returns boolean + * + */ + +export const isBalanceEnough = (balance: string, fee: string) => { + if (!fee || !userBalance) return false; + if (!balance) return false; + return Number(balance) >= Number(fee); +}; + +export const getIntervalAsDate = async () => { + const interval = await getCurrentInterval(); + const secondsToNextInterval = + Number(interval.epochs_in_interval - interval.current_epoch_id) * Number(interval.epoch_length_seconds); + + const nextInterval = format( + add(new Date(), { + seconds: secondsToNextInterval, + }), + 'dd/MM/yyyy, HH:mm', + ); + + const nextEpoch = format( + add(fromUnixTime(Number(interval.current_epoch_start_unix)), { + seconds: Number(interval.epoch_length_seconds), + }), + 'HH:mm', + ); + + return { nextEpoch, nextInterval }; +}; diff --git a/nym-wallet/src/utils/index.ts b/nym-wallet/src/utils/index.ts index 1f713a7b1d..e0e8ee8101 100644 --- a/nym-wallet/src/utils/index.ts +++ b/nym-wallet/src/utils/index.ts @@ -1,233 +1,4 @@ -import { appWindow } from '@tauri-apps/api/window'; -import bs58 from 'bs58'; -import Big from 'big.js'; -import { valid } from 'semver'; -import { add, format, fromUnixTime } from 'date-fns'; -import { DecCoin, isValidRawCoin, MixNodeCostParams } from '@nymproject/types'; -import { TPoolOption } from 'src/components'; -import { - getCurrentInterval, - getDefaultMixnodeCostParams, - getLockedCoins, - getSpendableCoins, - userBalance, -} from '../requests'; -import { Console } from './console'; - -export const validateKey = (key: string, bytesLength: number): boolean => { - // it must be a valid base58 key - try { - const bytes = bs58.decode(key); - // of length 32 - return bytes.length === bytesLength; - } catch (e) { - Console.error(e as string); - return false; - } -}; - -export const validateAmount = async ( - majorAmountAsString: DecCoin['amount'], - minimumAmountAsString: DecCoin['amount'], -): Promise => { - // tests basic coin value requirements, like no more than 6 decimal places, value lower than total supply, etc - if (!Number(majorAmountAsString)) { - return false; - } - - if (!isValidRawCoin(majorAmountAsString)) { - return false; - } - - const majorValueFloat = parseInt(majorAmountAsString, Number(10)); - - return majorValueFloat >= parseInt(minimumAmountAsString, Number(10)); - - // this conversion seems really iffy but I'm not sure how to better approach it -}; - -export const isValidHostname = (value: string) => { - // regex for ipv4 and ipv6 and hhostname- source http://jsfiddle.net/DanielD/8S4nq/ - const hostnameRegex = - /((^((([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5]))\s*$)|(^\s*((([0-9A-Fa-f]{1,4}:){7}([0-9A-Fa-f]{1,4}|:))|(([0-9A-Fa-f]{1,4}:){6}(:[0-9A-Fa-f]{1,4}|((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){5}(((:[0-9A-Fa-f]{1,4}){1,2})|:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){4}(((:[0-9A-Fa-f]{1,4}){1,3})|((:[0-9A-Fa-f]{1,4})?:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){3}(((:[0-9A-Fa-f]{1,4}){1,4})|((:[0-9A-Fa-f]{1,4}){0,2}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){2}(((:[0-9A-Fa-f]{1,4}){1,5})|((:[0-9A-Fa-f]{1,4}){0,3}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){1}(((:[0-9A-Fa-f]{1,4}){1,6})|((:[0-9A-Fa-f]{1,4}){0,4}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(:(((:[0-9A-Fa-f]{1,4}){1,7})|((:[0-9A-Fa-f]{1,4}){0,5}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:)))(%.+)?\s*$))|(^\s*((?=.{1,255}$)(?=.*[A-Za-z].*)[0-9A-Za-z](?:(?:[0-9A-Za-z]|\b-){0,61}[0-9A-Za-z])?(?:\.[0-9A-Za-z](?:(?:[0-9A-Za-z]|\b-){0,61}[0-9A-Za-z])?)*)\s*$)/; - - return hostnameRegex.test(value); -}; - -export const validateVersion = (version: string): boolean => { - try { - return valid(version) !== null; - } catch (e) { - return false; - } -}; - -export const validateLocation = (location: string): boolean => { - const locationRegex = /^[a-z]+$/i; - return locationRegex.test(location); -}; - -export const validateRawPort = (rawPort: number): boolean => !Number.isNaN(rawPort) && rawPort >= 1 && rawPort <= 65535; - -export const truncate = (text: string, trim: number) => `${text.substring(0, trim)}...`; - -export const isGreaterThan = (a: number, b: number) => a > b; - -export const isLessThan = (a: number, b: number) => a < b; - -export const checkHasEnoughFunds = async (allocationValue: string): Promise => { - try { - const walletValue = await userBalance(); - - const remainingBalance = +walletValue.amount.amount - +allocationValue; - return remainingBalance >= 0; - } catch (e) { - Console.log(e as string); - return false; - } -}; - -export const checkHasEnoughLockedTokens = async (allocationValue: string) => { - try { - const lockedTokens = await getLockedCoins(); - const spendableTokens = await getSpendableCoins(); - const remainingBalance = +lockedTokens.amount + +spendableTokens.amount - +allocationValue; - return remainingBalance >= 0; - } catch (e) { - Console.error(e as string); - } - return false; -}; - -export const randomNumberBetween = (min: number, max: number) => { - const minCeil = Math.ceil(min); - const maxFloor = Math.floor(max); - return Math.floor(Math.random() * (maxFloor - minCeil + 1) + minCeil); -}; - -export const splice = (size: number, address?: string): string => { - if (address) { - return `${address.slice(0, size)}...${address.slice(-size)}`; - } - return ''; -}; - -export const maximizeWindow = async () => { - await appWindow.maximize(); -}; - -export function removeObjectDuplicates(arr: T[], id: K) { - return arr.filter((v, i, a) => a.findIndex((v2) => v2[id] === v[id]) === i); -} - -export const checkTokenBalance = async (tokenPool: TPoolOption, amount: string) => { - let hasEnoughFunds = false; - if (tokenPool === 'locked') { - hasEnoughFunds = await checkHasEnoughLockedTokens(amount); - } - - if (tokenPool === 'balance') { - hasEnoughFunds = await checkHasEnoughFunds(amount); - } - - return hasEnoughFunds; -}; - -export const isDecimal = (value: number) => value - Math.floor(value) !== 0; - -export const attachDefaultOperatingCost = async (profitMarginPercent: string): Promise => - getDefaultMixnodeCostParams(profitMarginPercent); - -/** - * Converts a stringified percentage integer (0-100) to a stringified float (0.0-1.0). - * - * @param value - the percentage to convert - * @returns A stringified float - */ -export const toPercentFloatString = (value: string) => (Number(value) / 100).toString(); - -/** - * Converts a stringified percentage float (0.0-1.0) to a stringified integer (0-100). - * - * @param value - the percentage to convert - * @returns A stringified integer - */ -export const toPercentIntegerString = (value: string) => Math.round(Number(value) * 100).toString(); - -/** - * Converts a decimal number to a pretty representation - * with fixed decimal places. - * - * @param val - a decimal number of string form - * @param dp - number of decimal places (4 by default ie. 0.0000) - * @returns A prettified decimal number - */ -export const toDisplay = (val: string | number | Big, dp = 4) => { - let displayValue; - try { - displayValue = Big(val).toFixed(dp); - } catch (e: any) { - Console.warn(`${displayValue} not a valid decimal number: ${e}`); - } - return displayValue; -}; - -/** - * Takes a DecCoin and prettify its amount to a representation - * with fixed decimal places. - * - * @param coin - a DecCoin - * @param dp - number of decimal places to apply to amount (4 by default ie. 0.0000) - * @returns A DecCoin with prettified amount - */ -export const decCoinToDisplay = (coin: DecCoin, dp = 4) => { - const displayCoin = { ...coin }; - try { - displayCoin.amount = Big(coin.amount).toFixed(dp); - } catch (e: any) { - Console.warn(`${coin.amount} not a valid decimal number: ${e}`); - } - return displayCoin; -}; - -/** - * Converts a decimal number of μNYM (micro NYM) to NYM. - * - * @param unym - string representation of a decimal number of μNYM - * @param dp - number of decimal places (4 by default ie. 0.0000) - * @returns The corresponding decimal number in NYM - */ -export const unymToNym = (unym: string | Big, dp = 4) => { - let nym; - try { - nym = Big(unym).div(1_000_000).toFixed(dp); - } catch (e: any) { - Console.warn(`${unym} not a valid decimal number: ${e}`); - } - return nym; -}; - -export const getIntervalAsDate = async () => { - const interval = await getCurrentInterval(); - const secondsToNextInterval = - Number(interval.epochs_in_interval - interval.current_epoch_id) * Number(interval.epoch_length_seconds); - - const nextInterval = format( - add(new Date(), { - seconds: secondsToNextInterval, - }), - 'dd/MM/yyyy, HH:mm', - ); - - const nextEpoch = format( - add(fromUnixTime(Number(interval.current_epoch_start_unix)), { - seconds: Number(interval.epoch_length_seconds), - }), - 'HH:mm', - ); - - return { nextEpoch, nextInterval }; -}; - +export * from './generalUtils'; export * from './fireRequests'; +export * from './console'; export { default as fireRequests } from './fireRequests'; From eadd116ae4acdb356881e9c88382422f3ac7715e Mon Sep 17 00:00:00 2001 From: fmtabbara Date: Mon, 26 Jun 2023 14:50:49 +0100 Subject: [PATCH 2/4] add warning modal for more txs --- .../Bonding/modals/UpdateBondAmountModal.tsx | 8 +++++++- nym-wallet/src/components/Delegation/DelegateModal.tsx | 10 +++++++++- .../src/components/Delegation/UndelegateModal.tsx | 2 +- nym-wallet/src/components/FeeWarning.tsx | 2 +- nym-wallet/src/hooks/useGetBalance.ts | 6 +++--- nym-wallet/src/utils/{generalUtils.ts => common.ts} | 0 nym-wallet/src/utils/index.ts | 2 +- 7 files changed, 22 insertions(+), 8 deletions(-) rename nym-wallet/src/utils/{generalUtils.ts => common.ts} (100%) diff --git a/nym-wallet/src/components/Bonding/modals/UpdateBondAmountModal.tsx b/nym-wallet/src/components/Bonding/modals/UpdateBondAmountModal.tsx index 552f2b1f4c..07be4bcf47 100644 --- a/nym-wallet/src/components/Bonding/modals/UpdateBondAmountModal.tsx +++ b/nym-wallet/src/components/Bonding/modals/UpdateBondAmountModal.tsx @@ -11,6 +11,7 @@ import { decCoinToDisplay, validateAmount } from 'src/utils'; import { simulateUpdateBond, simulateVestingUpdateBond } from 'src/requests'; import { TSimulateUpdateBondArgs, TUpdateBondArgs } from 'src/types'; import { AppContext, TBondedMixnode } from 'src/context'; +import { BalanceWarning } from 'src/components/FeeWarning'; import { TPoolOption } from '../../TokenPoolSelector'; export const UpdateBondAmountModal = ({ @@ -30,7 +31,7 @@ export const UpdateBondAmountModal = ({ const [newBond, setNewBond] = useState(); const [errorAmount, setErrorAmount] = useState(false); - const { printBalance, printVestedBalance } = useContext(AppContext); + const { printBalance, printVestedBalance, userBalance } = useContext(AppContext); useEffect(() => { if (feeError) { @@ -104,6 +105,11 @@ export const UpdateBondAmountModal = ({ > + {userBalance.balance?.amount.amount && fee?.amount?.amount && ( + + + + )} ); diff --git a/nym-wallet/src/components/Delegation/DelegateModal.tsx b/nym-wallet/src/components/Delegation/DelegateModal.tsx index 61762357de..12d076f7c8 100644 --- a/nym-wallet/src/components/Delegation/DelegateModal.tsx +++ b/nym-wallet/src/components/Delegation/DelegateModal.tsx @@ -1,4 +1,4 @@ -import React, { useCallback, useState } from 'react'; +import React, { useCallback, useContext, useState } from 'react'; import { Box, Typography, SxProps } from '@mui/material'; import { IdentityKeyFormField } from '@nymproject/react/mixnodes/IdentityKeyFormField'; import { CurrencyFormField } from '@nymproject/react/currency/CurrencyFormField'; @@ -7,6 +7,7 @@ import { Console } from 'src/utils/console'; import { useGetFee } from 'src/hooks/useGetFee'; import { simulateDelegateToMixnode, simulateVestingDelegateToMixnode, tryConvertIdentityToMixId } from 'src/requests'; import { debounce } from 'lodash'; +import { AppContext } from 'src/context'; import { SimpleModal } from '../Modals/SimpleModal'; import { ModalListItem } from '../Modals/ModalListItem'; import { checkTokenBalance, validateAmount, validateKey } from '../../utils'; @@ -15,6 +16,7 @@ import { ConfirmTx } from '../ConfirmTX'; import { getMixnodeStakeSaturation } from '../../requests'; import { ErrorModal } from '../Modals/ErrorModal'; +import { BalanceWarning } from '../FeeWarning'; const MIN_AMOUNT_TO_DELEGATE = 10; @@ -73,6 +75,7 @@ export const DelegateModal: FCWithChildren<{ const [mixIdError, setMixIdError] = useState(); const { fee, getFee, resetFeeState, feeError } = useGetFee(); + const { userBalance } = useContext(AppContext); const handleCheckStakeSaturation = async (newMixId: number) => { try { @@ -213,6 +216,11 @@ export const DelegateModal: FCWithChildren<{ onPrev={resetFeeState} onConfirm={handleOk} > + {userBalance.balance?.amount.amount && fee?.amount?.amount && ( + + + + )} diff --git a/nym-wallet/src/components/Delegation/UndelegateModal.tsx b/nym-wallet/src/components/Delegation/UndelegateModal.tsx index 12662ef3d5..607ee7afdb 100644 --- a/nym-wallet/src/components/Delegation/UndelegateModal.tsx +++ b/nym-wallet/src/components/Delegation/UndelegateModal.tsx @@ -1,5 +1,5 @@ -import { Box, SxProps } from '@mui/material'; import React, { useContext, useEffect } from 'react'; +import { Box, SxProps } from '@mui/material'; import { FeeDetails } from '@nymproject/types'; import { useGetFee } from 'src/hooks/useGetFee'; import { simulateUndelegateFromMixnode, simulateVestingUndelegateFromMixnode } from 'src/requests'; diff --git a/nym-wallet/src/components/FeeWarning.tsx b/nym-wallet/src/components/FeeWarning.tsx index 8f22a07591..444b8228dd 100644 --- a/nym-wallet/src/components/FeeWarning.tsx +++ b/nym-wallet/src/components/FeeWarning.tsx @@ -24,7 +24,7 @@ export const BalanceWarning = ({ balance, fee }: { balance: string; fee: string return ( }> Warning: Fees are greater than your balance - The fess are greater than your current balance which cause this transaction to fail. + The fess are greater than your current balance which could cause this transaction to fail. Do you want to continue? ); diff --git a/nym-wallet/src/hooks/useGetBalance.ts b/nym-wallet/src/hooks/useGetBalance.ts index 240910e493..d43eeb1c04 100644 --- a/nym-wallet/src/hooks/useGetBalance.ts +++ b/nym-wallet/src/hooks/useGetBalance.ts @@ -1,5 +1,4 @@ import { useCallback, useEffect, useState } from 'react'; -import { invoke } from '@tauri-apps/api'; import { Account, Balance, DecCoin, OriginalVestingResponse, Period, VestingAccountInfo } from '@nymproject/types'; import { getVestingCoins, @@ -11,6 +10,7 @@ import { getVestingAccountInfo, getSpendableRewardCoins, getSpendableVestedCoins, + userBalance, } from '../requests'; import { Console } from '../utils/console'; @@ -101,8 +101,8 @@ export const useGetBalance = (clientDetails?: Account): TUseuserBalance => { setIsLoading(true); setError(undefined); try { - const bal = await invoke('get_balance'); - setBalance(bal as Balance); + const bal = await userBalance(); + setBalance(bal); } catch (err) { setError(err as string); } finally { diff --git a/nym-wallet/src/utils/generalUtils.ts b/nym-wallet/src/utils/common.ts similarity index 100% rename from nym-wallet/src/utils/generalUtils.ts rename to nym-wallet/src/utils/common.ts diff --git a/nym-wallet/src/utils/index.ts b/nym-wallet/src/utils/index.ts index e0e8ee8101..b1a2ade95a 100644 --- a/nym-wallet/src/utils/index.ts +++ b/nym-wallet/src/utils/index.ts @@ -1,4 +1,4 @@ -export * from './generalUtils'; +export * from './common'; export * from './fireRequests'; export * from './console'; export { default as fireRequests } from './fireRequests'; From 96c29b974b0156b6892ce40e4231374b40231944 Mon Sep 17 00:00:00 2001 From: fmtabbara Date: Mon, 26 Jun 2023 17:08:52 +0100 Subject: [PATCH 3/4] add warning for more txs --- .../src/components/Send/SendDetailsModal.tsx | 80 +++++++++++-------- .../GeneralGatewaySettings.tsx | 13 ++- .../GeneralMixnodeSettings.tsx | 15 +++- 3 files changed, 68 insertions(+), 40 deletions(-) diff --git a/nym-wallet/src/components/Send/SendDetailsModal.tsx b/nym-wallet/src/components/Send/SendDetailsModal.tsx index 366bf7ba9c..b31f5bd4a0 100644 --- a/nym-wallet/src/components/Send/SendDetailsModal.tsx +++ b/nym-wallet/src/components/Send/SendDetailsModal.tsx @@ -1,9 +1,11 @@ -import React from 'react'; -import { Stack, SxProps } from '@mui/material'; +import React, { useContext } from 'react'; +import { Box, Stack, SxProps } from '@mui/material'; import { FeeDetails, DecCoin, CurrencyDenom } from '@nymproject/types'; +import { AppContext } from 'src/context'; import { SimpleModal } from '../Modals/SimpleModal'; import { ModalListItem } from '../Modals/ModalListItem'; import { ModalFee, ModalTotalAmount } from '../Modals/ModalFee'; +import { BalanceWarning } from '../FeeWarning'; export const SendDetailsModal = ({ amount, @@ -29,37 +31,45 @@ export const SendDetailsModal = ({ sx?: SxProps; backdropProps?: object; memo?: string; -}) => ( - amount && onSend({ val: amount, to: toAddress })} - onBack={onPrev} - sx={sx} - backdropProps={backdropProps} - > - - - - - - {memo && ( - +}) => { + const { userBalance } = useContext(AppContext); + return ( + amount && onSend({ val: amount, to: toAddress })} + onBack={onPrev} + sx={sx} + backdropProps={backdropProps} + > + + + + + + {memo && ( + + )} + + + {userBalance.balance?.amount.amount && fee?.amount?.amount && ( + + + )} - - - -); + + ); +}; diff --git a/nym-wallet/src/pages/bonding/node-settings/settings-pages/general-settings/GeneralGatewaySettings.tsx b/nym-wallet/src/pages/bonding/node-settings/settings-pages/general-settings/GeneralGatewaySettings.tsx index f024fcc149..8c1b3849a0 100644 --- a/nym-wallet/src/pages/bonding/node-settings/settings-pages/general-settings/GeneralGatewaySettings.tsx +++ b/nym-wallet/src/pages/bonding/node-settings/settings-pages/general-settings/GeneralGatewaySettings.tsx @@ -1,4 +1,4 @@ -import React, { useState } from 'react'; +import React, { useContext, useState } from 'react'; import { useForm } from 'react-hook-form'; import { clean } from 'semver'; import { yupResolver } from '@hookform/resolvers/yup'; @@ -18,11 +18,14 @@ import { ConfirmTx } from 'src/components/ConfirmTX'; import { useGetFee } from 'src/hooks/useGetFee'; import { LoadingModal } from 'src/components/Modals/LoadingModal'; import { updateGatewayValidationSchema } from 'src/components/Bonding/forms/gatewayValidationSchema'; +import { BalanceWarning } from 'src/components/FeeWarning'; +import { AppContext } from 'src/context'; export const GeneralGatewaySettings = ({ bondedNode }: { bondedNode: TBondedGateway }) => { const [openConfirmationModal, setOpenConfirmationModal] = useState(false); const { getFee, fee, resetFeeState } = useGetFee(); const { refresh } = useBondingContext(); + const { userBalance } = useContext(AppContext); const theme = useTheme(); @@ -78,7 +81,13 @@ export const GeneralGatewaySettings = ({ bondedNode }: { bondedNode: TBondedGate onConfirm={handleSubmit((d) => onSubmit(d))} onPrev={resetFeeState} onClose={resetFeeState} - /> + > + {fee.amount?.amount && userBalance?.balance?.amount.amount && ( + + + + )} + )} {isSubmitting && } { const [openConfirmationModal, setOpenConfirmationModal] = useState(false); const { getFee, fee, resetFeeState } = useGetFee(); + const { userBalance } = useContext(AppContext); const theme = useTheme(); @@ -72,7 +75,13 @@ export const GeneralMixnodeSettings = ({ bondedNode }: { bondedNode: TBondedMixn onConfirm={handleSubmit((d) => onSubmit(d))} onPrev={resetFeeState} onClose={resetFeeState} - /> + > + {fee.amount?.amount && userBalance?.balance?.amount.amount && ( + + + + )} + )} {isSubmitting && } Date: Wed, 28 Jun 2023 12:20:47 +0100 Subject: [PATCH 4/4] include tx amount when checking balance --- .../Bonding/modals/BondGatewayModal.tsx | 8 +++++++- .../Bonding/modals/BondMixnodeModal.tsx | 8 +++++++- .../Bonding/modals/RedeemRewardsModal.tsx | 2 +- .../Bonding/modals/UpdateBondAmountModal.tsx | 2 +- .../src/components/Delegation/DelegateModal.tsx | 2 +- .../components/Delegation/UndelegateModal.tsx | 2 +- .../src/components/FeeWarning.stories.tsx | 7 ------- nym-wallet/src/components/FeeWarning.tsx | 16 ++++++++++------ .../src/components/Rewards/RedeemModal.tsx | 2 +- .../src/components/Send/SendDetailsModal.tsx | 2 +- .../general-settings/GeneralGatewaySettings.tsx | 2 +- .../general-settings/GeneralMixnodeSettings.tsx | 2 +- nym-wallet/src/utils/common.ts | 17 +++++++++++------ 13 files changed, 43 insertions(+), 29 deletions(-) diff --git a/nym-wallet/src/components/Bonding/modals/BondGatewayModal.tsx b/nym-wallet/src/components/Bonding/modals/BondGatewayModal.tsx index 0a3cd718a5..defdac3f95 100644 --- a/nym-wallet/src/components/Bonding/modals/BondGatewayModal.tsx +++ b/nym-wallet/src/components/Bonding/modals/BondGatewayModal.tsx @@ -1,4 +1,4 @@ -import React, { useEffect, useState } from 'react'; +import React, { useContext, useEffect, useState } from 'react'; import { Box } from '@mui/material'; import { CurrencyDenom, TNodeType } from '@nymproject/types'; import { ConfirmTx } from 'src/components/ConfirmTX'; @@ -9,6 +9,8 @@ import { useGetFee } from 'src/hooks/useGetFee'; import { GatewayAmount, GatewayData, Signature } from 'src/pages/bonding/types'; import { simulateBondGateway, simulateVestingBondGateway } from 'src/requests'; import { TBondGatewayArgs } from 'src/types'; +import { BalanceWarning } from 'src/components/FeeWarning'; +import { AppContext } from 'src/context'; import { BondGatewayForm } from '../forms/BondGatewayForm'; import { gatewayToTauri } from '../utils'; @@ -50,6 +52,7 @@ export const BondGatewayModal = ({ const [signature, setSignature] = useState(); const { fee, getFee, resetFeeState, feeError } = useGetFee(); + const { userBalance } = useContext(AppContext); useEffect(() => { if (feeError) { @@ -123,6 +126,9 @@ export const BondGatewayModal = ({ value={`${amountData.amount.amount} ${amountData.amount.denom.toUpperCase()}`} divider /> + {fee.amount?.amount && userBalance.balance && ( + + )} ); } diff --git a/nym-wallet/src/components/Bonding/modals/BondMixnodeModal.tsx b/nym-wallet/src/components/Bonding/modals/BondMixnodeModal.tsx index 6cad10f9ab..4e4c39f9cf 100644 --- a/nym-wallet/src/components/Bonding/modals/BondMixnodeModal.tsx +++ b/nym-wallet/src/components/Bonding/modals/BondMixnodeModal.tsx @@ -1,4 +1,4 @@ -import React, { useEffect, useState } from 'react'; +import React, { useContext, useEffect, useState } from 'react'; import { CurrencyDenom, TNodeType } from '@nymproject/types'; import { ConfirmTx } from 'src/components/ConfirmTX'; import { ModalListItem } from 'src/components/Modals/ModalListItem'; @@ -8,6 +8,8 @@ import { useGetFee } from 'src/hooks/useGetFee'; import { MixnodeAmount, MixnodeData, Signature } from 'src/pages/bonding/types'; import { simulateBondMixnode, simulateVestingBondMixnode } from 'src/requests'; import { TBondMixNodeArgs } from 'src/types'; +import { BalanceWarning } from 'src/components/FeeWarning'; +import { AppContext } from 'src/context'; import { BondMixnodeForm } from '../forms/BondMixnodeForm'; import { costParamsToTauri, mixnodeToTauri } from '../utils'; @@ -50,6 +52,7 @@ export const BondMixnodeModal = ({ const [signature, setSignature] = useState(); const { fee, getFee, resetFeeState, feeError } = useGetFee(); + const { userBalance } = useContext(AppContext); useEffect(() => { if (feeError) { @@ -125,6 +128,9 @@ export const BondMixnodeModal = ({ value={`${amountData.amount.amount} ${amountData.amount.denom.toUpperCase()}`} divider /> + {fee.amount?.amount && userBalance.balance && ( + + )} ); } diff --git a/nym-wallet/src/components/Bonding/modals/RedeemRewardsModal.tsx b/nym-wallet/src/components/Bonding/modals/RedeemRewardsModal.tsx index 88490e7e0e..ba802967d8 100644 --- a/nym-wallet/src/components/Bonding/modals/RedeemRewardsModal.tsx +++ b/nym-wallet/src/components/Bonding/modals/RedeemRewardsModal.tsx @@ -56,7 +56,7 @@ export const RedeemRewardsModal = ({ {userBalance.balance?.amount.amount && fee?.amount?.amount && ( - + )} diff --git a/nym-wallet/src/components/Bonding/modals/UpdateBondAmountModal.tsx b/nym-wallet/src/components/Bonding/modals/UpdateBondAmountModal.tsx index 07be4bcf47..2148313b70 100644 --- a/nym-wallet/src/components/Bonding/modals/UpdateBondAmountModal.tsx +++ b/nym-wallet/src/components/Bonding/modals/UpdateBondAmountModal.tsx @@ -107,7 +107,7 @@ export const UpdateBondAmountModal = ({ {userBalance.balance?.amount.amount && fee?.amount?.amount && ( - + )} diff --git a/nym-wallet/src/components/Delegation/DelegateModal.tsx b/nym-wallet/src/components/Delegation/DelegateModal.tsx index 12d076f7c8..211b4ae2b9 100644 --- a/nym-wallet/src/components/Delegation/DelegateModal.tsx +++ b/nym-wallet/src/components/Delegation/DelegateModal.tsx @@ -218,7 +218,7 @@ export const DelegateModal: FCWithChildren<{ > {userBalance.balance?.amount.amount && fee?.amount?.amount && ( - + )} diff --git a/nym-wallet/src/components/Delegation/UndelegateModal.tsx b/nym-wallet/src/components/Delegation/UndelegateModal.tsx index 607ee7afdb..65820eb2bb 100644 --- a/nym-wallet/src/components/Delegation/UndelegateModal.tsx +++ b/nym-wallet/src/components/Delegation/UndelegateModal.tsx @@ -55,7 +55,7 @@ export const UndelegateModal: FCWithChildren<{ {userBalance.balance?.amount.amount && fee?.amount?.amount && ( - + )} diff --git a/nym-wallet/src/components/FeeWarning.stories.tsx b/nym-wallet/src/components/FeeWarning.stories.tsx index 9202ecc300..7a553aae79 100644 --- a/nym-wallet/src/components/FeeWarning.stories.tsx +++ b/nym-wallet/src/components/FeeWarning.stories.tsx @@ -16,12 +16,5 @@ const Template: ComponentStory = (args) => ( export const WithWarning = Template.bind({}); WithWarning.args = { - balance: '100', fee: '200', }; - -export const NoWarning = Template.bind({}); -NoWarning.args = { - balance: '100', - fee: '100', -}; diff --git a/nym-wallet/src/components/FeeWarning.tsx b/nym-wallet/src/components/FeeWarning.tsx index 444b8228dd..745353b2c6 100644 --- a/nym-wallet/src/components/FeeWarning.tsx +++ b/nym-wallet/src/components/FeeWarning.tsx @@ -1,8 +1,9 @@ -import React from 'react'; +import React, { useContext } from 'react'; import { Warning } from '@mui/icons-material'; import { FeeDetails } from '@nymproject/types'; import { Alert, AlertTitle, Box } from '@mui/material'; import { isBalanceEnough } from 'src/utils'; +import { AppContext } from 'src/context'; export const FeeWarning = ({ fee, amount }: { fee: FeeDetails; amount: number }) => { if (fee.amount && +fee.amount.amount > amount) { @@ -16,15 +17,18 @@ export const FeeWarning = ({ fee, amount }: { fee: FeeDetails; amount: number }) return null; }; -export const BalanceWarning = ({ balance, fee }: { balance: string; fee: string }) => { - const hasEnoughBalanace = isBalanceEnough(balance, fee); +export const BalanceWarning = ({ tx, fee }: { fee: string; tx?: string }) => { + const { userBalance } = useContext(AppContext); - if (!balance || !fee || hasEnoughBalanace) return null; + const hasEnoughBalanace = isBalanceEnough(fee, tx, userBalance.balance?.amount.amount); + + if (hasEnoughBalanace) return null; return ( }> - Warning: Fees are greater than your balance - The fess are greater than your current balance which could cause this transaction to fail. + Warning: Transaction amount is greater than your balance + The transaction amount (inc fees) is greater than your current balance, which could cause this transaction to + fail. Do you want to continue? ); diff --git a/nym-wallet/src/components/Rewards/RedeemModal.tsx b/nym-wallet/src/components/Rewards/RedeemModal.tsx index ec6bde1846..b9c774cb2e 100644 --- a/nym-wallet/src/components/Rewards/RedeemModal.tsx +++ b/nym-wallet/src/components/Rewards/RedeemModal.tsx @@ -60,7 +60,7 @@ export const RedeemModal: FCWithChildren<{ {fee && } {userBalance.balance?.amount.amount && fee?.amount?.amount && ( - + )} diff --git a/nym-wallet/src/components/Send/SendDetailsModal.tsx b/nym-wallet/src/components/Send/SendDetailsModal.tsx index b31f5bd4a0..061345c68b 100644 --- a/nym-wallet/src/components/Send/SendDetailsModal.tsx +++ b/nym-wallet/src/components/Send/SendDetailsModal.tsx @@ -67,7 +67,7 @@ export const SendDetailsModal = ({ {userBalance.balance?.amount.amount && fee?.amount?.amount && ( - + )} diff --git a/nym-wallet/src/pages/bonding/node-settings/settings-pages/general-settings/GeneralGatewaySettings.tsx b/nym-wallet/src/pages/bonding/node-settings/settings-pages/general-settings/GeneralGatewaySettings.tsx index 8c1b3849a0..892c1e245c 100644 --- a/nym-wallet/src/pages/bonding/node-settings/settings-pages/general-settings/GeneralGatewaySettings.tsx +++ b/nym-wallet/src/pages/bonding/node-settings/settings-pages/general-settings/GeneralGatewaySettings.tsx @@ -84,7 +84,7 @@ export const GeneralGatewaySettings = ({ bondedNode }: { bondedNode: TBondedGate > {fee.amount?.amount && userBalance?.balance?.amount.amount && ( - + )} diff --git a/nym-wallet/src/pages/bonding/node-settings/settings-pages/general-settings/GeneralMixnodeSettings.tsx b/nym-wallet/src/pages/bonding/node-settings/settings-pages/general-settings/GeneralMixnodeSettings.tsx index 1523612195..509c605174 100644 --- a/nym-wallet/src/pages/bonding/node-settings/settings-pages/general-settings/GeneralMixnodeSettings.tsx +++ b/nym-wallet/src/pages/bonding/node-settings/settings-pages/general-settings/GeneralMixnodeSettings.tsx @@ -78,7 +78,7 @@ export const GeneralMixnodeSettings = ({ bondedNode }: { bondedNode: TBondedMixn > {fee.amount?.amount && userBalance?.balance?.amount.amount && ( - + )} diff --git a/nym-wallet/src/utils/common.ts b/nym-wallet/src/utils/common.ts index 39366fa724..f4ddba7275 100644 --- a/nym-wallet/src/utils/common.ts +++ b/nym-wallet/src/utils/common.ts @@ -210,16 +210,21 @@ export const unymToNym = (unym: string | Big, dp = 4) => { /** * * Checks if the user's balance is enough to pay the fee - * @param balance - The user's balance to check - * @param fee - The fee to check + * @param balance - The user's current balance + * @param fee - The fee for the tx + * @param tx - The amount of the tx * @returns boolean * */ -export const isBalanceEnough = (balance: string, fee: string) => { - if (!fee || !userBalance) return false; - if (!balance) return false; - return Number(balance) >= Number(fee); +export const isBalanceEnough = (fee: string, tx: string = '0', balance: string = '0') => { + console.log('balance', balance, fee, tx); + try { + return Big(balance).gte(Big(fee).plus(Big(tx))); + } catch (e) { + console.log(e); + return false; + } }; export const getIntervalAsDate = async () => {