Merge pull request #3598 from nymtech/bug-fix/balanceVSfeeWarning

TX warning when fee > balance
This commit is contained in:
Tommy Verrall
2023-07-25 09:55:34 +02:00
committed by GitHub
16 changed files with 425 additions and 288 deletions
@@ -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<string>();
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 && (
<BalanceWarning fee={fee.amount?.amount} tx={amountData.amount.amount} />
)}
</ConfirmTx>
);
}
@@ -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<string>();
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 && (
<BalanceWarning fee={fee.amount?.amount} tx={amountData.amount.amount} />
)}
</ConfirmTx>
);
}
@@ -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
/>
<ModalFee fee={fee} isLoading={isFeeLoading} divider />
<ModalListItem label="Balance" value={userBalance.balance?.printable_balance.toUpperCase()} divider />
<ModalListItem label="Rewards will be transferred to the account you are logged in with" value="" />
{userBalance.balance?.amount.amount && fee?.amount?.amount && (
<Box sx={{ my: 2 }}>
<BalanceWarning fee={fee?.amount?.amount} />
</Box>
)}
</SimpleModal>
);
};
@@ -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<DecCoin | undefined>();
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 = ({
>
<ModalListItem label="New bond details" value={newBondToDisplay()} divider />
<ModalListItem label="Change bond details" value={`${currentBond.amount} ${currentBond.denom}`} divider />
{userBalance.balance?.amount.amount && fee?.amount?.amount && (
<Box sx={{ my: 2 }}>
<BalanceWarning fee={fee?.amount?.amount} tx={newBond?.amount} />
</Box>
)}
</ConfirmTx>
);
@@ -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<string>();
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 && (
<Box sx={{ my: 2 }}>
<BalanceWarning fee={fee?.amount?.amount} tx={amount} />
</Box>
)}
<ModalListItem label="Node identity key" value={identityKey} divider />
<ModalListItem label="Amount" value={`${amount} ${denom.toUpperCase()}`} divider />
</ConfirmTx>
@@ -1,11 +1,13 @@
import React, { useContext, useEffect } from 'react';
import { Box, SxProps } from '@mui/material';
import React, { 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<{
<ModalListItem label="Delegation amount" value={`${amount} ${currency.toUpperCase()}`} divider />
<ModalFee fee={fee} isLoading={isFeeLoading} error={feeError} divider />
<ModalListItem label=" Tokens will be transferred to account you are logged in with now" value="" divider />
{userBalance.balance?.amount.amount && fee?.amount?.amount && (
<Box sx={{ my: 2 }}>
<BalanceWarning fee={fee?.amount?.amount} />
</Box>
)}
</Box>
</SimpleModal>
);
@@ -0,0 +1,20 @@
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<typeof BalanceWarning>;
const Template: ComponentStory<typeof BalanceWarning> = (args) => (
<Box mt={2} height={800}>
<BalanceWarning {...args} />
</Box>
);
export const WithWarning = Template.bind({});
WithWarning.args = {
fee: '200',
};
+22 -3
View File
@@ -1,16 +1,35 @@
import React from 'react';
import React, { useContext } 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';
import { AppContext } from 'src/context';
export const FeeWarning = ({ fee, amount }: { fee: FeeDetails; amount: number }) => {
if (fee.amount && +fee.amount.amount > amount) {
return (
<Alert color="warning" sx={{ mt: 3 }} icon={<Warning />}>
<AlertTitle>Warning: fees are greater than the reward</AlertTitle>
<AlertTitle>Warning: Fees are greater than the reward</AlertTitle>
The fees for redeeming rewards will cost more than the rewards. Are you sure you want to continue?
</Alert>
);
}
return null;
};
export const BalanceWarning = ({ tx, fee }: { fee: string; tx?: string }) => {
const { userBalance } = useContext(AppContext);
const hasEnoughBalanace = isBalanceEnough(fee, tx, userBalance.balance?.amount.amount);
if (hasEnoughBalanace) return null;
return (
<Alert color="warning" icon={<Warning />}>
<AlertTitle>Warning: Transaction amount is greater than your balance</AlertTitle>
The transaction amount (inc fees) is greater than your current balance, which could cause this transaction to
fail.
<Box sx={{ mt: 0.5 }}>Do you want to continue?</Box>
</Alert>
);
};
@@ -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<{
<ModalFee fee={fee} isLoading={isFeeLoading} error={feeError} divider />
<ModalListItem label="Rewards will be transferred to account you are logged in with now" value="" divider />
{fee && <FeeWarning amount={amount} fee={fee} />}
{userBalance.balance?.amount.amount && fee?.amount?.amount && (
<Box sx={{ my: 2 }}>
<BalanceWarning fee={fee?.amount?.amount} />
</Box>
)}
</SimpleModal>
);
};
@@ -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;
}) => (
<SimpleModal
header="Send details"
open
onClose={onClose}
okLabel="Confirm"
onOk={async () => amount && onSend({ val: amount, to: toAddress })}
onBack={onPrev}
sx={sx}
backdropProps={backdropProps}
>
<Stack gap={0.5} sx={{ mt: 3 }}>
<ModalListItem label="From" value={fromAddress} divider />
<ModalListItem label="To" value={toAddress} divider />
<ModalListItem label="Amount" value={`${amount?.amount} ${denom.toUpperCase()}`} divider />
<ModalFee fee={fee} divider isLoading={false} />
{memo && (
<ModalListItem
label="Memo"
value={memo}
sxValue={{
textOverflow: 'ellipsis',
overflow: 'hidden',
whiteSpace: 'nowrap',
overflowWrap: 'anywhere',
maxWidth: '300px',
}}
divider
/>
}) => {
const { userBalance } = useContext(AppContext);
return (
<SimpleModal
header="Send details"
open
onClose={onClose}
okLabel="Confirm"
onOk={async () => amount && onSend({ val: amount, to: toAddress })}
onBack={onPrev}
sx={sx}
backdropProps={backdropProps}
>
<Stack gap={0.5} sx={{ mt: 3 }}>
<ModalListItem label="From" value={fromAddress} divider />
<ModalListItem label="To" value={toAddress} divider />
<ModalListItem label="Amount" value={`${amount?.amount} ${denom.toUpperCase()}`} divider />
<ModalFee fee={fee} divider isLoading={false} />
{memo && (
<ModalListItem
label="Memo"
value={memo}
sxValue={{
textOverflow: 'ellipsis',
overflow: 'hidden',
whiteSpace: 'nowrap',
overflowWrap: 'anywhere',
maxWidth: '300px',
}}
divider
/>
)}
<ModalTotalAmount fee={fee} amount={amount?.amount} divider isLoading={false} />
</Stack>
{userBalance.balance?.amount.amount && fee?.amount?.amount && (
<Box sx={{ my: 2 }}>
<BalanceWarning fee={fee?.amount?.amount} tx={amount?.amount} />
</Box>
)}
<ModalTotalAmount fee={fee} amount={amount?.amount} divider isLoading={false} />
</Stack>
</SimpleModal>
);
</SimpleModal>
);
};
+3 -3
View File
@@ -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 {
@@ -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<boolean>(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 && (
<Box sx={{ mb: 2 }}>
<BalanceWarning fee={fee.amount.amount} />
</Box>
)}
</ConfirmTx>
)}
{isSubmitting && <LoadingModal />}
<Alert
@@ -1,8 +1,8 @@
import React, { useState } from 'react';
import React, { useContext, useState } from 'react';
import { useForm } from 'react-hook-form';
import { yupResolver } from '@hookform/resolvers/yup';
import { clean } from 'semver';
import { Button, Divider, Grid, Stack, TextField, Typography } from '@mui/material';
import { Box, Button, Divider, Grid, Stack, TextField, Typography } from '@mui/material';
import { useTheme } from '@mui/material/styles';
import { isMixnode } from 'src/types';
import { simulateUpdateMixnodeConfig, simulateVestingUpdateMixnodeConfig, updateMixnodeConfig } from 'src/requests';
@@ -15,10 +15,13 @@ import { vestingUpdateMixnodeConfig } from 'src/requests/vesting';
import { ConfirmTx } from 'src/components/ConfirmTX';
import { useGetFee } from 'src/hooks/useGetFee';
import { LoadingModal } from 'src/components/Modals/LoadingModal';
import { BalanceWarning } from 'src/components/FeeWarning';
import { AppContext } from 'src/context';
export const GeneralMixnodeSettings = ({ bondedNode }: { bondedNode: TBondedMixnode | TBondedGateway }) => {
const [openConfirmationModal, setOpenConfirmationModal] = useState<boolean>(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 && (
<Box sx={{ mb: 2 }}>
<BalanceWarning fee={fee.amount.amount} />
</Box>
)}
</ConfirmTx>
)}
{isSubmitting && <LoadingModal />}
<Alert
@@ -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({
+250
View File
@@ -0,0 +1,250 @@
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<boolean> => {
// 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<boolean> => {
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<T extends object, K extends keyof T>(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<MixNodeCostParams> =>
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 current balance
* @param fee - The fee for the tx
* @param tx - The amount of the tx
* @returns boolean
*
*/
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 () => {
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 };
};
+2 -231
View File
@@ -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<boolean> => {
// 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<boolean> => {
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<T extends object, K extends keyof T>(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<MixNodeCostParams> =>
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 './common';
export * from './fireRequests';
export * from './console';
export { default as fireRequests } from './fireRequests';