This commit is contained in:
Yana
2023-11-07 15:06:45 +00:00
parent 23d08b993c
commit 2dac633873
22 changed files with 562 additions and 73 deletions
+14 -5
View File
@@ -39,8 +39,17 @@ export const trimAddress = (address = '', trimBy = 6) => {
};
export default function ConnectKeplrWallet() {
const { username, connect, disconnect, wallet, openView, address, getCosmWasmClient, isWalletConnected } =
useChain('nyx');
const {
username,
connect,
disconnect,
wallet,
openView,
address,
getCosmWasmClient,
isWalletConnected,
isWalletConnecting,
} = useChain('nyx');
const isClient = useIsClient();
const theme = useTheme();
@@ -70,9 +79,9 @@ export default function ConnectKeplrWallet() {
if (!isClient) return null;
const getGlobalbutton = () => {
// if (globalStatus === 'Connecting') {
// return <Button onClick={() => connect()}>{`Connecting ${wallet?.prettyName}`}</Button>;
// }
if (isWalletConnecting) {
return <Button onClick={() => connect()}>{`Connecting ${wallet?.prettyName}`}</Button>;
}
if (isWalletConnected) {
return (
<Box display={'flex'} alignItems={'center'} gap={2}>
@@ -0,0 +1,48 @@
import React from 'react';
import { FeeDetails } from '@nymproject/types';
import { Box } from '@mui/material';
import { useTheme, Theme } from '@mui/material/styles';
import { SimpleModal } from './SimpleModal';
import { ModalFee } from './ModalFee';
import { ModalDivider } from './ModalDivider';
import { backDropStyles, modalStyles } from './styles';
const storybookStyles = (theme: Theme, isStorybook?: boolean, backdropProps?: object) =>
isStorybook
? {
backdropProps: { ...backDropStyles(theme), ...backdropProps },
sx: modalStyles(theme),
}
: {};
export const ConfirmTx: FCWithChildren<{
open: boolean;
header: string;
subheader?: string;
fee: FeeDetails;
onConfirm: () => Promise<void>;
onClose?: () => void;
onPrev: () => void;
isStorybook?: boolean;
children?: React.ReactNode;
}> = ({ open, fee, onConfirm, onClose, header, subheader, onPrev, children, isStorybook }) => {
const theme = useTheme();
return (
<SimpleModal
open={open}
header={header}
subHeader={subheader}
okLabel="Confirm"
onOk={onConfirm}
onClose={onClose}
onBack={onPrev}
{...storybookStyles(theme, isStorybook)}
>
<Box sx={{ mt: 3 }}>
{children}
<ModalFee fee={fee} isLoading={false} />
<ModalDivider />
</Box>
</SimpleModal>
);
};
@@ -3,7 +3,7 @@ import { Box, Typography, SxProps } from '@mui/material';
import { IdentityKeyFormField } from '@nymproject/react/mixnodes/IdentityKeyFormField';
import { CurrencyFormField } from '@nymproject/react/currency/CurrencyFormField';
import { CurrencyDenom, FeeDetails, DecCoin, decimalToFloatApproximation } from '@nymproject/types';
// import { Console } from 'src/utils/console';
import { Console } from '../utils/console';
import { useGetFee } from '../hooks/useGetFee';
// import { simulateDelegateToMixnode, simulateVestingDelegateToMixnode, tryConvertIdentityToMixId } from 'src/requests';
import { debounce } from 'lodash';
@@ -12,15 +12,17 @@ import { ModalListItem } from './ModalListItem';
// import { AppContext } from 'src/context';
// import { SimpleModal } from '../Modals/SimpleModal';
// import { ModalListItem } from '../Modals/ModalListItem';
// import { checkTokenBalance, validateAmount, validateKey } from '../../utils';
import { TPoolOption, checkTokenBalance, validateAmount, validateKey } from '../utils';
// import { TokenPoolSelector, TPoolOption } from './TokenPoolSelector';
// import { ConfirmTx } from '../ConfirmTX';
// import { getMixnodeStakeSaturation } from '../../requests';
// import { ErrorModal } from '../Modals/ErrorModal';
// import { BalanceWarning } from '../FeeWarning';
import { useChain } from '@cosmos-kit/react';
import { uNYMtoNYM } from '../../ConnectKeplrWallet';
import { uNYMtoNYM } from '../utils';
import { ErrorModal } from './ErrorModal';
import { ConfirmTx } from './ConfirmTX';
import { BalanceWarning } from './FeeWarning';
const MIN_AMOUNT_TO_DELEGATE = 10;
@@ -31,7 +33,7 @@ export const DelegateModal: FCWithChildren<{
mixId: number,
identityKey: string,
amount: DecCoin,
// tokenPool: TPoolOption,
tokenPool: TPoolOption,
fee?: FeeDetails,
) => Promise<void>;
identityKey?: string;
@@ -102,7 +104,6 @@ export const DelegateModal: FCWithChildren<{
getBalance(address);
}
}, [address, getCosmWasmClient]);
// const { userBalance } = useContext(AppContext);
// const handleCheckStakeSaturation = async (newMixId: number) => {
// try {
@@ -138,10 +139,10 @@ export const DelegateModal: FCWithChildren<{
// }
// }
// if (amount && !(await validateAmount(amount, '0'))) {
// newValidatedValue = false;
// errorAmountMessage = 'Please enter a valid amount';
// }
if (amount && !(await validateAmount(amount, '0'))) {
newValidatedValue = false;
errorAmountMessage = 'Please enter a valid amount';
}
if (amount && Number(amount) < MIN_AMOUNT_TO_DELEGATE) {
errorAmountMessage = `Min. delegation amount: ${MIN_AMOUNT_TO_DELEGATE} ${denom.toUpperCase()}`;
@@ -166,21 +167,22 @@ export const DelegateModal: FCWithChildren<{
const handleOk = async () => {
if (onOk && amount && identityKey && mixId) {
onOk(mixId, identityKey, { amount, denom }, fee); //tokenPool, fee);
onOk(mixId, identityKey, { amount, denom }, 'balance', fee);
}
};
const handleConfirm = async ({ mixId: id, value }: { mixId: number; value: DecCoin }) => {
const hasEnoughTokens = true; // await checkTokenBalance(tokenPool, value.amount);
const tokenPool = 'balance';
const hasEnoughTokens = await checkTokenBalance(tokenPool, value.amount);
if (!hasEnoughTokens) {
setErrorAmount('Not enough funds');
return;
}
// if (tokenPool === 'balance') {
// getFee(simulateDelegateToMixnode, { mixId: id, amount: value });
// }
// if (tokenPool === 'balance') {
// getFee(simulateDelegateToMixnode, { mixId: id, amount: value });
// }
// if (tokenPool === 'locked') {
// getFee(simulateVestingDelegateToMixnode, { mixId: id, amount: value });
@@ -217,7 +219,7 @@ export const DelegateModal: FCWithChildren<{
try {
// res = await tryConvertIdentityToMixId(idKey);
} catch (e) {
// Console.warn(`failed to resolve mix_id for "${idKey}": ${e}`);
Console.warn(`failed to resolve mix_id for "${idKey}": ${e}`);
return;
}
if (res) {
@@ -234,38 +236,38 @@ export const DelegateModal: FCWithChildren<{
resolveMixId(identityKey);
}, [identityKey]);
// if (fee) {
// return (
// <ConfirmTx
// open
// header="Delegation details"
// fee={fee}
// onClose={onClose}
// 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>
// );
// }
if (fee) {
return (
<ConfirmTx
open
header="Delegation details"
fee={fee}
onClose={onClose}
onPrev={resetFeeState}
onConfirm={handleOk}
>
{balance.data && 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>
);
}
// if (feeError) {
// return (
// <ErrorModal
// title="Something went wrong while calculating fee. Are you sure you entered a valid node address?"
// message={feeError}
// sx={sx}
// open={open}
// onClose={onClose}
// />
// );
// }
if (feeError) {
return (
<ErrorModal
title="Something went wrong while calculating fee. Are you sure you entered a valid node address?"
message={feeError}
sx={sx}
open={open}
onClose={onClose}
/>
);
}
return (
<SimpleModal
@@ -318,7 +320,7 @@ export const DelegateModal: FCWithChildren<{
/>
</Box>
<Box sx={{ mt: 3 }}>
<ModalListItem label="Account balance" value={balance.data} divider fontWeight={600} />
<ModalListItem label="Account balance" value={`${balance.data} NYM`} divider fontWeight={600} />
</Box>
<ModalListItem label="Rewards payout interval" value={rewardInterval} hidden divider />
@@ -0,0 +1,35 @@
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 '../utils';
import { AppContext } from '../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>
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>
);
};
@@ -0,0 +1,6 @@
import React from 'react';
import { Box, SxProps } from '@mui/material';
export const ModalDivider: FCWithChildren<{
sx?: SxProps;
}> = ({ sx }) => <Box borderTop="1px solid" borderColor="rgba(141, 147, 153, 0.2)" my={1} sx={sx} />;
@@ -0,0 +1,35 @@
import React from 'react';
import { FeeDetails } from '@nymproject/types';
import { CircularProgress } from '@mui/material';
import { ModalListItem } from './ModalListItem';
import { ModalDivider } from './ModalDivider';
type TFeeProps = { fee?: FeeDetails; isLoading: boolean; error?: string; divider?: boolean };
type TTotalAmountProps = { fee?: FeeDetails; amount?: string; isLoading: boolean; error?: string; divider?: boolean };
const getValue = ({ fee, amount, isLoading, error }: TTotalAmountProps) => {
if (isLoading) return <CircularProgress size={15} />;
if (error && !isLoading) return 'n/a';
if (fee) {
const numericFee = Number(fee.amount?.amount);
const numericAmountToTransfer = Number(amount);
return amount
? `${numericFee + numericAmountToTransfer} ${fee.amount?.denom.toUpperCase()}`
: `${fee.amount?.amount} ${fee.amount?.denom.toUpperCase()}`;
}
return '-';
};
export const ModalFee = ({ fee, isLoading, error, divider }: TFeeProps) => (
<>
<ModalListItem label="Fee for this transaction" value={getValue({ fee, isLoading, error })} />
{divider && <ModalDivider />}
</>
);
export const ModalTotalAmount = ({ fee, amount, isLoading, error, divider }: TTotalAmountProps) => (
<>
<ModalListItem label="Total amount" value={getValue({ fee, amount, isLoading, error })} fontWeight={600} />
{divider && <ModalDivider />}
</>
);
@@ -0,0 +1,21 @@
import { Theme } from '@mui/material/styles';
export const backDropStyles = (theme: Theme) => {
const { mode } = theme.palette;
return {
style: {
left: mode === 'light' ? '0' : '50%',
width: '50%',
},
};
};
export const modalStyles = (theme: Theme) => {
const { mode } = theme.palette;
return { left: mode === 'light' ? '25%' : '75%' };
};
export const dialogStyles = (theme: Theme) => {
const { mode } = theme.palette;
return { left: mode === 'light' ? '-50%' : '50%' };
};
@@ -1,6 +1,6 @@
import React, { createContext, useCallback, useContext, useEffect, useMemo, useState } from 'react';
import { sign } from '../requests';
// import { Console } from 'src/utils/console';
import { Console } from '../utils/console';
// import { AppContext } from './main';
export type TBuyContext = {
@@ -34,7 +34,7 @@ export const BuyContextProvider: FCWithChildren = ({ children }): JSX.Element =>
try {
signature = await sign(message);
} catch (e: any) {
// Console.log(`Sign message operation failed: ${e}`);
Console.log(`Sign message operation failed: ${e}`);
console.log('`Sign message operation failed: ${e}` :>> ', `Sign message operation failed: ${e}`);
setError(`Sign message operation failed: ${e}`);
} finally {
@@ -18,7 +18,7 @@ import {
} from '../requests';
import Big from 'big.js';
// import { Console } from '../utils/console';
import { Console } from '../utils/console';
import { createSignInWindow, getReactState, setReactState } from '../requests/app';
// import { toDisplay } from '../utils';
@@ -27,8 +27,7 @@ const toDisplay = (val: string | number | Big, dp = 4) => {
try {
displayValue = Big(val).toFixed(dp);
} catch (e: any) {
// Console.warn(`${displayValue} not a valid decimal number: ${e}`);
console.log('object :>> ', `${displayValue} not a valid decimal number: ${e}`);
Console.warn(`${displayValue} not a valid decimal number: ${e}`);
}
return displayValue;
};
@@ -149,7 +148,7 @@ export const AppProvider: FCWithChildren = ({ children }) => {
setClientDetails(client);
} catch (e) {
enqueueSnackbar('Error loading account', { variant: 'error' });
// Console.error(e as string);
Console.error(e as string);
}
};
@@ -164,7 +163,7 @@ export const AppProvider: FCWithChildren = ({ children }) => {
const mixnode = await getMixnodeBondDetails();
setMixnodeDetails(mixnode);
} catch (e) {
// Console.error(e as string);
Console.error(e as string);
}
};
@@ -180,7 +179,7 @@ export const AppProvider: FCWithChildren = ({ children }) => {
const modeFromStorage = await forage.getItem({ key: 'nym-wallet-mode' })();
if (modeFromStorage) setMode(modeFromStorage);
} catch (e) {
// Console.error(e);
Console.error(e);
}
};
@@ -231,15 +230,15 @@ export const AppProvider: FCWithChildren = ({ children }) => {
if (adminAddresses.length) {
newValue = adminAddresses.includes(clientDetails?.client_address);
if (newValue) {
// Console.log('Wallet is in admin mode: ', {
// network,
// adminAddress: adminAddressMap[network],
// clientAddress: clientDetails?.client_address,
// });
Console.log('Wallet is in admin mode: ', {
network,
adminAddress: adminAddressMap[network],
clientAddress: clientDetails?.client_address,
});
}
}
} catch (e) {
// Console.error('Failed to check admin addresses', e);
Console.error('Failed to check admin addresses', e);
}
}
setIsAdminAddress(newValue);
@@ -1,5 +1,5 @@
import { useCallback, useContext, useEffect, useState } from 'react';
// import { Console } from '../utils/console';
import { Console } from '../utils/console';
// eslint-disable-next-line import/no-cycle
import { AppContext } from '../context/main';
import { checkGatewayOwnership, checkMixnodeOwnership, getVestingPledgeInfo } from '../requests';
@@ -43,7 +43,7 @@ export const useCheckOwnership = () => {
setOwnership(status);
} catch (e) {
// Console.error(e as string);
Console.error(e as string);
setError(e as string);
setOwnership(initial);
} finally {
@@ -13,7 +13,7 @@ import {
getSpendableVestedCoins,
userBalance,
} from '../requests';
// import { Console } from '../utils/console';
import { Console } from '../utils/console';
type TTokenAllocation = {
[key in
@@ -92,7 +92,7 @@ export const useGetBalance = (clientDetails?: Account): TUseuserBalance => {
} catch (e) {
clearTokenAllocation();
clearOriginalVesting();
// Console.error(e as string);
Console.error(e as string);
}
}
setIsLoading(false);
@@ -1,6 +1,6 @@
import { DecCoin, FeeDetails } from '@nymproject/types';
import { useState } from 'react';
// import { Console } from 'src/utils/console';
import { Console } from '../utils/console';
import { getCustomFees } from '../requests';
export function useGetFee() {
@@ -26,7 +26,7 @@ export function useGetFee() {
const fees = await getCustomFees({ feesAmount: amount });
setFee(fees);
} catch (e) {
// Console.error(e);
Console.error(e);
setFeeError(e as string);
}
setIsFeeLoading(false);
+1
View File
@@ -0,0 +1 @@
declare type FCWithChildren<P = {}> = React.FC<React.PropsWithChildren<P>>;
+9
View File
@@ -0,0 +1,9 @@
declare module '*.jpeg' {
const value: any;
export default value;
}
declare module '*.jpg' {
const value: any;
export default value;
}
+4
View File
@@ -0,0 +1,4 @@
declare module '*.json' {
const content: any;
export default content;
}
+4
View File
@@ -0,0 +1,4 @@
declare module '*.png' {
const content: any;
export default content;
}
+4
View File
@@ -0,0 +1,4 @@
declare module '*.svg' {
const content: any;
export default content;
}
@@ -0,0 +1,273 @@
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 {
getCurrentInterval,
getDefaultMixnodeCostParams,
getLockedCoins,
getSpendableCoins,
userBalance,
} from '../requests';
import { Console } from './console';
export type TPoolOption = 'balance' | 'locked';
export const uNYMtoNYM = (unym: string, rounding = 6) => {
const nym = Big(unym).div(1000000).toFixed(rounding);
return {
asString: () => nym,
asNumber: () => Number(nym),
};
};
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 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 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 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 };
};
@@ -0,0 +1,10 @@
/* eslint-disable no-console */
import { config } from '../config';
export const Console = {
log: (message?: any, ...optionalParams: any[]) =>
config.IS_DEV_MODE ? console.log(message, ...optionalParams) : undefined,
warn: (message?: any, ...optionalParams: any[]) =>
config.IS_DEV_MODE ? console.warn(message, ...optionalParams) : undefined,
error: (message?: any, ...optionalParams: any[]) => console.error(message, ...optionalParams),
};
@@ -0,0 +1,22 @@
import { Console } from './console';
export type TauriReq<Req extends Function & ((a: any, b?: any) => Promise<any>)> = {
name: Req['name'];
request: () => ReturnType<Req>;
onFulfilled: (value: Awaited<ReturnType<Req>>) => void;
};
async function fireRequests(requests: TauriReq<any>[]) {
const promises = await Promise.allSettled(requests.map((r) => r.request()));
promises.forEach((res, index) => {
if (res.status === 'rejected') {
Console.warn(`${requests[index].name} request fails`, res.reason);
}
if (res.status === 'fulfilled') {
requests[index].onFulfilled(res.value as any);
}
});
}
export default fireRequests;
@@ -0,0 +1,4 @@
export * from './common';
export * from './fireRequests';
export * from './console';
export { default as fireRequests } from './fireRequests';
@@ -0,0 +1,3 @@
export const sleep = (delayMilliseconds: number) =>
// eslint-disable-next-line no-promise-executor-return
new Promise((resolve) => setTimeout(resolve, delayMilliseconds));