Connect delegate modal to keplr balance
WIP WIP WIP WIP
This commit is contained in:
@@ -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>
|
||||
);
|
||||
};
|
||||
+90
-94
@@ -1,24 +1,22 @@
|
||||
import React, { useCallback, useContext, useState } from 'react';
|
||||
import React, { useCallback, useContext, useState, useEffect } from 'react';
|
||||
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 { useGetFee } from 'src/hooks/useGetFee';
|
||||
// import { simulateDelegateToMixnode, simulateVestingDelegateToMixnode, tryConvertIdentityToMixId } from 'src/requests';
|
||||
import { Console } from '../utils/console';
|
||||
import { useGetFee } from '../hooks/useGetFee';
|
||||
import { debounce } from 'lodash';
|
||||
import { SimpleModal } from './SimpleModal';
|
||||
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 { TokenPoolSelector, TPoolOption } from '../TokenPoolSelector';
|
||||
// import { ConfirmTx } from '../ConfirmTX';
|
||||
|
||||
// import { getMixnodeStakeSaturation } from '../../requests';
|
||||
// import { ErrorModal } from '../Modals/ErrorModal';
|
||||
// import { BalanceWarning } from '../FeeWarning';
|
||||
import { TPoolOption, checkTokenBalance, validateAmount, validateKey } from '../utils';
|
||||
|
||||
import { useChain } from '@cosmos-kit/react';
|
||||
import { uNYMtoNYM } from '../utils';
|
||||
import { ErrorModal } from './ErrorModal';
|
||||
import { ConfirmTx } from './ConfirmTX';
|
||||
import { BalanceWarning } from './FeeWarning';
|
||||
import { getMixnodeStakeSaturation, simulateDelegateToMixnode, tryConvertIdentityToMixId } from '../requests';
|
||||
|
||||
const MIN_AMOUNT_TO_DELEGATE = 10;
|
||||
|
||||
@@ -29,8 +27,8 @@ export const DelegateModal: FCWithChildren<{
|
||||
mixId: number,
|
||||
identityKey: string,
|
||||
amount: DecCoin,
|
||||
// tokenPool: TPoolOption,
|
||||
// fee?: FeeDetails,
|
||||
tokenPool: TPoolOption,
|
||||
fee?: FeeDetails,
|
||||
) => Promise<void>;
|
||||
identityKey?: string;
|
||||
onIdentityKeyChanged?: (identityKey: string) => void;
|
||||
@@ -38,7 +36,7 @@ export const DelegateModal: FCWithChildren<{
|
||||
header?: string;
|
||||
buttonText?: string;
|
||||
rewardInterval: string;
|
||||
accountBalance?: string;
|
||||
// accountBalance?: string;
|
||||
estimatedReward?: number;
|
||||
profitMarginPercentage?: string | null;
|
||||
nodeUptimePercentage?: number | null;
|
||||
@@ -57,7 +55,7 @@ export const DelegateModal: FCWithChildren<{
|
||||
buttonText,
|
||||
identityKey: initialIdentityKey,
|
||||
rewardInterval,
|
||||
accountBalance,
|
||||
// accountBalance,
|
||||
estimatedReward,
|
||||
denom,
|
||||
profitMarginPercentage,
|
||||
@@ -76,47 +74,45 @@ export const DelegateModal: FCWithChildren<{
|
||||
const [errorIdentityKey, setErrorIdentityKey] = useState<string>();
|
||||
const [mixIdError, setMixIdError] = useState<string>();
|
||||
|
||||
// const { fee, getFee, resetFeeState, feeError } = useGetFee();
|
||||
// const { userBalance } = useContext(AppContext);
|
||||
const { fee, getFee, resetFeeState, feeError } = useGetFee();
|
||||
|
||||
// const handleCheckStakeSaturation = async (newMixId: number) => {
|
||||
// try {
|
||||
// const newSaturation = decimalToFloatApproximation(
|
||||
// (await getMixnodeStakeSaturation(newMixId)).uncapped_saturation,
|
||||
// );
|
||||
// if (newSaturation && newSaturation > 1) {
|
||||
// const saturationPercentage = Math.round(newSaturation * 100);
|
||||
// return { isOverSaturated: true, saturationPercentage };
|
||||
// }
|
||||
// return { isOverSaturated: false, saturationPercentage: undefined };
|
||||
// } catch (e) {
|
||||
// Console.error('Error fetching the saturation, error:', e);
|
||||
// return { isOverSaturated: false, saturationPercentage: undefined };
|
||||
// }
|
||||
// };
|
||||
const { username, connect, disconnect, wallet, openView, address, getCosmWasmClient, isWalletConnected } =
|
||||
useChain('nyx');
|
||||
const [balance, setBalance] = useState<{
|
||||
status: 'loading' | 'success';
|
||||
data?: string;
|
||||
}>({ status: 'loading', data: undefined });
|
||||
|
||||
useEffect(() => {
|
||||
const getBalance = async (walletAddress: string) => {
|
||||
setBalance({ status: 'loading', data: undefined });
|
||||
|
||||
const account = await getCosmWasmClient();
|
||||
const uNYMBalance = await account.getBalance(walletAddress, 'unym');
|
||||
const NYMBalance = uNYMtoNYM(uNYMBalance.amount).asString();
|
||||
|
||||
setBalance({ status: 'success', data: NYMBalance });
|
||||
};
|
||||
|
||||
if (address) {
|
||||
getBalance(address);
|
||||
}
|
||||
}, [address, getCosmWasmClient]);
|
||||
|
||||
const validate = async () => {
|
||||
let newValidatedValue = true;
|
||||
let errorAmountMessage;
|
||||
let errorIdentityKeyMessage;
|
||||
|
||||
// if (!identityKey || !validateKey(identityKey, 32)) {
|
||||
// newValidatedValue = false;
|
||||
// errorIdentityKeyMessage = undefined;
|
||||
// }
|
||||
if (!identityKey) {
|
||||
newValidatedValue = false;
|
||||
errorIdentityKeyMessage = 'Please enter a valid identity key';
|
||||
}
|
||||
|
||||
// if (identityKey && mixId && validateKey(identityKey, 32)) {
|
||||
// const { isOverSaturated, saturationPercentage } = await handleCheckStakeSaturation(mixId);
|
||||
// if (isOverSaturated) {
|
||||
// newValidatedValue = false;
|
||||
// errorIdentityKeyMessage = `This node is over saturated (${saturationPercentage}%), please select another node`;
|
||||
// }
|
||||
// }
|
||||
|
||||
// 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()}`;
|
||||
@@ -127,9 +123,9 @@ export const DelegateModal: FCWithChildren<{
|
||||
newValidatedValue = false;
|
||||
}
|
||||
|
||||
if (!mixId) {
|
||||
newValidatedValue = false;
|
||||
}
|
||||
// if (!mixId) {
|
||||
// newValidatedValue = false;
|
||||
// }
|
||||
|
||||
setErrorIdentityKey(errorIdentityKeyMessage);
|
||||
if (mixIdError && !errorIdentityKeyMessage) {
|
||||
@@ -141,21 +137,22 @@ export const DelegateModal: FCWithChildren<{
|
||||
|
||||
const handleOk = async () => {
|
||||
if (onOk && amount && identityKey && mixId) {
|
||||
onOk(mixId, identityKey, { amount, denom }); //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 = checkTokenBalance(tokenPool, value.amount, balance.data || '0');
|
||||
|
||||
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 });
|
||||
@@ -184,15 +181,14 @@ export const DelegateModal: FCWithChildren<{
|
||||
|
||||
const resolveMixId = useCallback(
|
||||
debounce(async (idKey) => {
|
||||
if (!idKey) {
|
||||
//|| !validateKey(idKey, 32))
|
||||
if (!idKey || !validateKey(idKey, 32)) {
|
||||
return;
|
||||
}
|
||||
let res;
|
||||
try {
|
||||
// res = await tryConvertIdentityToMixId(idKey);
|
||||
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) {
|
||||
@@ -209,38 +205,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
|
||||
@@ -293,7 +289,7 @@ export const DelegateModal: FCWithChildren<{
|
||||
/>
|
||||
</Box>
|
||||
<Box sx={{ mt: 3 }}>
|
||||
<ModalListItem label="Account balance" value={accountBalance?.toUpperCase()} divider fontWeight={600} />
|
||||
<ModalListItem label="Account balance" value={`${balance.data} NYM`} divider fontWeight={600} />
|
||||
</Box>
|
||||
|
||||
<ModalListItem label="Rewards payout interval" value={rewardInterval} hidden divider />
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
import React from 'react';
|
||||
import { Box, Button, Modal, SxProps, Typography } from '@mui/material';
|
||||
import { modalStyle } from '../../../../nym-wallet/src/components/Modals/styles';
|
||||
import { modalStyle } from '../../../../../nym-wallet/src/components/Modals/styles';
|
||||
|
||||
export const ErrorModal: FCWithChildren<{
|
||||
open: boolean;
|
||||
@@ -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 />}
|
||||
</>
|
||||
);
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
import React from 'react';
|
||||
import { Box, Stack, SxProps, Typography, TypographyProps } from '@mui/material';
|
||||
import { ModalDivider } from '../../../../nym-wallet/src/components/Modals/ModalDivider';
|
||||
import { ModalDivider } from '../../../../../nym-wallet/src/components/Modals/ModalDivider';
|
||||
|
||||
export const ModalListItem: FCWithChildren<{
|
||||
label: string;
|
||||
+1
-1
@@ -3,7 +3,7 @@ import { Box, Button, Modal, Stack, SxProps, Typography } from '@mui/material';
|
||||
import CloseIcon from '@mui/icons-material/Close';
|
||||
import ErrorOutline from '@mui/icons-material/ErrorOutline';
|
||||
import InfoOutlinedIcon from '@mui/icons-material/InfoOutlined';
|
||||
import { modalStyle } from '../../../../nym-wallet/src/components/Modals/styles';
|
||||
import { modalStyle } from '../../../../../nym-wallet/src/components/Modals/styles';
|
||||
|
||||
import ArrowBackIosNewIcon from '@mui/icons-material/ArrowBackIosNew';
|
||||
|
||||
@@ -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%' };
|
||||
};
|
||||
@@ -0,0 +1,4 @@
|
||||
export const config = {
|
||||
IS_DEV_MODE: process.env.NODE_ENV === 'development',
|
||||
LOG_TAURI_OPERATIONS: process.env.NODE_ENV === 'development',
|
||||
};
|
||||
@@ -0,0 +1,170 @@
|
||||
import React, { createContext, Dispatch, SetStateAction, useContext, useEffect, useMemo, useState } from 'react';
|
||||
import { AccountEntry } from '@nymproject/types';
|
||||
import { addAccount as addAccountRequest, renameAccount, showMnemonicForAccount } from '../requests';
|
||||
import { useSnackbar } from 'notistack';
|
||||
import { AppContext } from './main';
|
||||
|
||||
type TAccounts = {
|
||||
accounts?: AccountEntry[];
|
||||
selectedAccount?: AccountEntry;
|
||||
accountToEdit?: AccountEntry;
|
||||
dialogToDisplay?: TAccountsDialog;
|
||||
isLoading: boolean;
|
||||
error?: string;
|
||||
accountMnemonic: TAccountMnemonic;
|
||||
setError: Dispatch<SetStateAction<string | undefined>>;
|
||||
setAccountMnemonic: Dispatch<SetStateAction<TAccountMnemonic>>;
|
||||
handleAddAccount: (data: { accountName: string; mnemonic: string; password: string }) => void;
|
||||
setDialogToDisplay: (dialog?: TAccountsDialog) => void;
|
||||
handleSelectAccount: (data: { accountName: string; password: string }) => Promise<boolean>;
|
||||
handleAccountToEdit: (accountId: string | undefined) => void;
|
||||
handleEditAccount: ({
|
||||
account,
|
||||
newAccountName,
|
||||
password,
|
||||
}: {
|
||||
account: AccountEntry;
|
||||
newAccountName: string;
|
||||
password: string;
|
||||
}) => Promise<void>;
|
||||
handleImportAccount: (account: AccountEntry) => void;
|
||||
handleGetAccountMnemonic: (data: { password: string; accountName: string }) => void;
|
||||
};
|
||||
|
||||
export type TAccountsDialog = 'Accounts' | 'Add' | 'Edit' | 'Import' | 'Mnemonic';
|
||||
export type TAccountMnemonic = { value?: string; accountName?: string };
|
||||
|
||||
export const AccountsContext = createContext({} as TAccounts);
|
||||
|
||||
export const AccountsProvider: FCWithChildren = ({ children }) => {
|
||||
const [accounts, setAccounts] = useState<AccountEntry[]>([]);
|
||||
const [selectedAccount, setSelectedAccount] = useState<AccountEntry>();
|
||||
const [accountToEdit, setAccountToEdit] = useState<AccountEntry>();
|
||||
const [dialogToDisplay, setDialogToDisplay] = useState<TAccountsDialog>();
|
||||
const [accountMnemonic, setAccountMnemonic] = useState<TAccountMnemonic>({
|
||||
value: undefined,
|
||||
accountName: undefined,
|
||||
});
|
||||
const [error, setError] = useState<string>();
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const { onAccountChange, storedAccounts } = useContext(AppContext);
|
||||
const { enqueueSnackbar } = useSnackbar();
|
||||
|
||||
const handleAddAccount = async ({
|
||||
accountName,
|
||||
mnemonic,
|
||||
password,
|
||||
}: {
|
||||
accountName: string;
|
||||
mnemonic: string;
|
||||
password: string;
|
||||
}) => {
|
||||
setIsLoading(true);
|
||||
try {
|
||||
const newAccount = await addAccountRequest({
|
||||
accountName,
|
||||
mnemonic,
|
||||
password,
|
||||
});
|
||||
setAccounts((accs) => [...accs, newAccount]);
|
||||
enqueueSnackbar('New account created', { variant: 'success' });
|
||||
} catch (e) {
|
||||
setError(`Error adding account: ${e}`);
|
||||
throw new Error();
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
const handleEditAccount = async ({
|
||||
account,
|
||||
newAccountName,
|
||||
password,
|
||||
}: {
|
||||
account: AccountEntry;
|
||||
newAccountName: string;
|
||||
password: string;
|
||||
}) => {
|
||||
setIsLoading(true);
|
||||
try {
|
||||
await renameAccount({ accountName: account.id, newAccountName, password });
|
||||
setAccounts((accs) =>
|
||||
accs?.map((acc) => (acc.address === account.address ? { ...acc, id: newAccountName } : acc)),
|
||||
);
|
||||
if (selectedAccount?.id === account.id) {
|
||||
setSelectedAccount({ ...selectedAccount, id: newAccountName });
|
||||
}
|
||||
setDialogToDisplay('Accounts');
|
||||
} catch (e) {
|
||||
throw new Error(`Error editing account: ${e}`);
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleImportAccount = (account: AccountEntry) => setAccounts((accs) => [...(accs ? [...accs] : []), account]);
|
||||
|
||||
const handleAccountToEdit = (accountName: string | undefined) =>
|
||||
setAccountToEdit(accounts?.find((acc) => acc.id === accountName));
|
||||
|
||||
const handleSelectAccount = async ({ accountName, password }: { accountName: string; password: string }) => {
|
||||
try {
|
||||
await onAccountChange({ accountId: accountName, password });
|
||||
const match = accounts?.find((acc) => acc.id === accountName);
|
||||
setSelectedAccount(match);
|
||||
return true;
|
||||
} catch (e) {
|
||||
setError('Error switching account. Please check your password');
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
const handleGetAccountMnemonic = async ({ password, accountName }: { password: string; accountName: string }) => {
|
||||
try {
|
||||
setIsLoading(true);
|
||||
const mnemonic = await showMnemonicForAccount({ password, accountName });
|
||||
setAccountMnemonic({ value: mnemonic, accountName });
|
||||
} catch (e) {
|
||||
setError(e as string);
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (storedAccounts) {
|
||||
setAccounts(storedAccounts);
|
||||
}
|
||||
|
||||
if (storedAccounts && !selectedAccount) {
|
||||
setSelectedAccount(storedAccounts[0]);
|
||||
}
|
||||
}, [storedAccounts]);
|
||||
|
||||
return (
|
||||
<AccountsContext.Provider
|
||||
value={useMemo(
|
||||
() => ({
|
||||
error,
|
||||
setError,
|
||||
accounts,
|
||||
selectedAccount,
|
||||
accountToEdit,
|
||||
dialogToDisplay,
|
||||
accountMnemonic,
|
||||
setDialogToDisplay,
|
||||
setAccountMnemonic,
|
||||
isLoading,
|
||||
handleAddAccount,
|
||||
handleEditAccount,
|
||||
handleAccountToEdit,
|
||||
handleSelectAccount,
|
||||
handleImportAccount,
|
||||
handleGetAccountMnemonic,
|
||||
}),
|
||||
[accounts, selectedAccount, accountToEdit, dialogToDisplay, isLoading, error, accountMnemonic],
|
||||
)}
|
||||
>
|
||||
{children}
|
||||
</AccountsContext.Provider>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,59 @@
|
||||
import React, { createContext, useCallback, useContext, useEffect, useMemo, useState } from 'react';
|
||||
import { sign } from '../requests';
|
||||
import { Console } from '../utils/console';
|
||||
// import { AppContext } from './main';
|
||||
|
||||
export type TBuyContext = {
|
||||
loading: boolean;
|
||||
error?: string;
|
||||
signMessage: (message: string) => Promise<string | undefined>;
|
||||
refresh: () => Promise<void>;
|
||||
};
|
||||
|
||||
export const BuyContext = createContext<TBuyContext>({
|
||||
loading: false,
|
||||
signMessage: async () => '',
|
||||
refresh: async () => undefined,
|
||||
});
|
||||
|
||||
export const BuyContextProvider: FCWithChildren = ({ children }): JSX.Element => {
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [error, setError] = useState<string>();
|
||||
|
||||
const refresh = useCallback(async () => {
|
||||
setError(undefined);
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
refresh();
|
||||
}, [refresh]);
|
||||
|
||||
const signMessage = async (message: string) => {
|
||||
let signature;
|
||||
setLoading(true);
|
||||
try {
|
||||
signature = await sign(message);
|
||||
} catch (e: any) {
|
||||
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 {
|
||||
setLoading(false);
|
||||
}
|
||||
return signature;
|
||||
};
|
||||
|
||||
const memoizedValue = useMemo(
|
||||
() => ({
|
||||
loading,
|
||||
error,
|
||||
refresh,
|
||||
signMessage,
|
||||
}),
|
||||
[loading, error],
|
||||
);
|
||||
|
||||
return <BuyContext.Provider value={memoizedValue}>{children}</BuyContext.Provider>;
|
||||
};
|
||||
|
||||
export const useBuyContext = () => useContext<TBuyContext>(BuyContext);
|
||||
@@ -0,0 +1,3 @@
|
||||
export * from './main';
|
||||
export * from './accounts';
|
||||
export * from './buy';
|
||||
@@ -0,0 +1,366 @@
|
||||
import React, { createContext, useEffect, useMemo, useState } from 'react';
|
||||
import { forage } from '@tauri-apps/tauri-forage';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { useSnackbar } from 'notistack';
|
||||
import { Account, AccountEntry, MixNodeDetails } from '@nymproject/types';
|
||||
import { getVersion } from '@tauri-apps/api/app';
|
||||
import { AppEnv, Network } from '../types';
|
||||
import { TUseuserBalance, useGetBalance } from '../hooks/useGetBalance';
|
||||
import {
|
||||
getEnv,
|
||||
getMixnodeBondDetails,
|
||||
listAccounts,
|
||||
selectNetwork,
|
||||
signInWithMnemonic,
|
||||
signInWithPassword,
|
||||
signOut,
|
||||
switchAccount,
|
||||
} from '../requests';
|
||||
import Big from 'big.js';
|
||||
|
||||
import { Console } from '../utils/console';
|
||||
import { createSignInWindow, getReactState, setReactState } from '../requests/app';
|
||||
// import { toDisplay } from '../utils';
|
||||
|
||||
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;
|
||||
};
|
||||
|
||||
export const urls = (networkName?: Network) =>
|
||||
networkName === 'MAINNET'
|
||||
? {
|
||||
mixnetExplorer: 'https://mixnet.explorers.guru/',
|
||||
blockExplorer: 'https://blocks.nymtech.net',
|
||||
networkExplorer: 'https://explorer.nymtech.net',
|
||||
}
|
||||
: {
|
||||
blockExplorer: `https://${networkName}-blocks.nymtech.net`,
|
||||
networkExplorer: `https://${networkName}-explorer.nymtech.net`,
|
||||
};
|
||||
|
||||
type TLoginType = 'mnemonic' | 'password';
|
||||
|
||||
export type TAppContext = {
|
||||
mode: 'light' | 'dark';
|
||||
appEnv?: AppEnv;
|
||||
appVersion?: string;
|
||||
clientDetails?: Account;
|
||||
storedAccounts?: AccountEntry[];
|
||||
mixnodeDetails?: MixNodeDetails | null;
|
||||
userBalance: TUseuserBalance;
|
||||
showAdmin: boolean;
|
||||
showTerminal: boolean;
|
||||
network?: Network;
|
||||
isLoading: boolean;
|
||||
isAdminAddress: boolean;
|
||||
error?: string;
|
||||
loginType?: TLoginType;
|
||||
showSendModal: boolean;
|
||||
showReceiveModal: boolean;
|
||||
onAccountChange: ({ accountId, password }: { accountId: string; password: string }) => void;
|
||||
handleSwitchMode: () => void;
|
||||
handleShowSendModal: () => void;
|
||||
handleShowReceiveModal: () => void;
|
||||
setIsLoading: (isLoading: boolean) => void;
|
||||
setError: (value?: string) => void;
|
||||
switchNetwork: (network: Network) => void;
|
||||
getBondDetails: () => Promise<void>;
|
||||
handleShowAdmin: () => void;
|
||||
logIn: (opts: { type: TLoginType; value: string }) => void;
|
||||
handleShowTerminal: () => void;
|
||||
signInWithPassword: (password: string) => void;
|
||||
logOut: () => void;
|
||||
keepState: () => Promise<void>;
|
||||
printBalance: string;
|
||||
printVestedBalance?: string; // spendable vested token
|
||||
};
|
||||
|
||||
interface RustState {
|
||||
network?: Network;
|
||||
loginType?: 'mnemonic' | 'password';
|
||||
}
|
||||
|
||||
export const AppContext = createContext({} as TAppContext);
|
||||
|
||||
export const AppProvider: FCWithChildren = ({ children }) => {
|
||||
const [clientDetails, setClientDetails] = useState<Account>();
|
||||
const [storedAccounts, setStoredAccounts] = useState<AccountEntry[]>();
|
||||
const [mixnodeDetails, setMixnodeDetails] = useState<MixNodeDetails | null>(null);
|
||||
const [network, setNetwork] = useState<Network | undefined>();
|
||||
const [appEnv, setAppEnv] = useState<AppEnv>();
|
||||
const [showAdmin, setShowAdmin] = useState(false);
|
||||
const [showTerminal, setShowTerminal] = useState(false);
|
||||
const [mode, setMode] = useState<'light' | 'dark'>('light');
|
||||
const [loginType, setLoginType] = useState<'mnemonic' | 'password'>();
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [error, setError] = useState<string>();
|
||||
const [appVersion, setAppVersion] = useState<string>();
|
||||
const [isAdminAddress, setIsAdminAddress] = useState<boolean>(false);
|
||||
const [showSendModal, setShowSendModal] = useState(false);
|
||||
const [showReceiveModal, setShowReceiveModal] = useState(false);
|
||||
const [printBalance, setPrintBalance] = useState<string>('-');
|
||||
const [printVestedBalance, setPrintVestedBalance] = useState<string | undefined>();
|
||||
|
||||
const userBalance = useGetBalance(clientDetails);
|
||||
const navigate = useNavigate();
|
||||
const { enqueueSnackbar } = useSnackbar();
|
||||
|
||||
const initFromRustState = async () => {
|
||||
const stateJson = await getReactState();
|
||||
if (stateJson) {
|
||||
const state: RustState = JSON.parse(stateJson);
|
||||
setNetwork(state.network);
|
||||
setLoginType(state.loginType);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
initFromRustState();
|
||||
}, []);
|
||||
|
||||
const keepState = async () => {
|
||||
// add any state from this context to store in the Rust process
|
||||
const state: RustState = {
|
||||
network,
|
||||
loginType,
|
||||
};
|
||||
setReactState(JSON.stringify(state));
|
||||
};
|
||||
|
||||
const clearState = () => {
|
||||
userBalance.clearAll();
|
||||
setStoredAccounts(undefined);
|
||||
setNetwork(undefined);
|
||||
setError(undefined);
|
||||
setIsLoading(false);
|
||||
setMixnodeDetails(null);
|
||||
};
|
||||
|
||||
const loadAccount = async (n: Network) => {
|
||||
try {
|
||||
const client = await selectNetwork(n);
|
||||
setClientDetails(client);
|
||||
} catch (e) {
|
||||
enqueueSnackbar('Error loading account', { variant: 'error' });
|
||||
Console.error(e as string);
|
||||
}
|
||||
};
|
||||
|
||||
const loadStoredAccounts = async () => {
|
||||
const accounts = await listAccounts();
|
||||
setStoredAccounts(accounts);
|
||||
};
|
||||
|
||||
const getBondDetails = async () => {
|
||||
setMixnodeDetails(null);
|
||||
try {
|
||||
const mixnode = await getMixnodeBondDetails();
|
||||
setMixnodeDetails(mixnode);
|
||||
} catch (e) {
|
||||
Console.error(e as string);
|
||||
}
|
||||
};
|
||||
|
||||
const refreshAccount = async (_network: Network) => {
|
||||
await loadAccount(_network);
|
||||
if (loginType === 'password') {
|
||||
await loadStoredAccounts();
|
||||
}
|
||||
};
|
||||
|
||||
const getModeFromStorage = async () => {
|
||||
try {
|
||||
const modeFromStorage = await forage.getItem({ key: 'nym-wallet-mode' })();
|
||||
if (modeFromStorage) setMode(modeFromStorage);
|
||||
} catch (e) {
|
||||
Console.error(e);
|
||||
}
|
||||
};
|
||||
|
||||
const setModeInStorage = async (newMode: 'light' | 'dark') => {
|
||||
await forage.setItem({
|
||||
key: 'nym-wallet-mode',
|
||||
value: newMode,
|
||||
})();
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
getVersion().then(setAppVersion);
|
||||
getModeFromStorage();
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (!clientDetails) {
|
||||
clearState();
|
||||
navigate('/');
|
||||
}
|
||||
}, [clientDetails]);
|
||||
|
||||
useEffect(() => {
|
||||
if (network) {
|
||||
refreshAccount(network);
|
||||
getEnv().then(setAppEnv);
|
||||
}
|
||||
}, [network]);
|
||||
|
||||
useEffect(() => {
|
||||
const currency = clientDetails?.display_mix_denom.toUpperCase() || 'NYM';
|
||||
if (userBalance.originalVesting) {
|
||||
setPrintVestedBalance(`${toDisplay(userBalance.tokenAllocation?.spendableVestedCoins || 0)} ${currency}`);
|
||||
}
|
||||
if (userBalance?.balance?.amount) {
|
||||
setPrintBalance(`${toDisplay(userBalance.balance.amount.amount)} ${currency}`);
|
||||
} else {
|
||||
setPrintBalance(`${toDisplay(0)} ${currency}`);
|
||||
}
|
||||
}, [userBalance, clientDetails]);
|
||||
|
||||
useEffect(() => {
|
||||
let newValue = false;
|
||||
if (network && appEnv?.ADMIN_ADDRESS && clientDetails?.client_address) {
|
||||
try {
|
||||
const adminAddressMap = JSON.parse(appEnv.ADMIN_ADDRESS);
|
||||
const adminAddresses = adminAddressMap[network] || [];
|
||||
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,
|
||||
});
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
Console.error('Failed to check admin addresses', e);
|
||||
}
|
||||
}
|
||||
setIsAdminAddress(newValue);
|
||||
}, [appEnv, network, clientDetails?.client_address]);
|
||||
|
||||
const logIn = async ({ type, value }: { type: TLoginType; value: string }) => {
|
||||
if (value.length === 0) {
|
||||
setError(`A ${type} must be provided`);
|
||||
return;
|
||||
}
|
||||
try {
|
||||
setIsLoading(true);
|
||||
if (type === 'mnemonic') {
|
||||
await signInWithMnemonic(value);
|
||||
setLoginType('mnemonic');
|
||||
} else {
|
||||
await signInWithPassword(value);
|
||||
setLoginType('password');
|
||||
}
|
||||
setNetwork('MAINNET');
|
||||
navigate('/balance');
|
||||
} catch (e) {
|
||||
setError(e as string);
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const logOut = async () => {
|
||||
setIsLoading(true);
|
||||
try {
|
||||
await signOut();
|
||||
await setReactState(undefined);
|
||||
setClientDetails(undefined);
|
||||
enqueueSnackbar('Successfully logged out', { variant: 'success' });
|
||||
await createSignInWindow();
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const onAccountChange = async ({ accountId, password }: { accountId: string; password: string }) => {
|
||||
if (network) {
|
||||
setIsLoading(true);
|
||||
try {
|
||||
await switchAccount({ accountId, password });
|
||||
await loadAccount(network);
|
||||
enqueueSnackbar('Account switch success', { variant: 'success', preventDuplicate: true });
|
||||
} catch (e) {
|
||||
throw new Error(`Error swtiching account: ${e}`);
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const handleShowAdmin = () => setShowAdmin((show) => !show);
|
||||
const handleShowTerminal = () => setShowTerminal((show) => !show);
|
||||
const switchNetwork = (_network: Network) => setNetwork(_network);
|
||||
const handleShowSendModal = () => setShowSendModal((show) => !show);
|
||||
const handleShowReceiveModal = () => setShowReceiveModal((show) => !show);
|
||||
const handleSwitchMode = () =>
|
||||
setMode((currentMode) => {
|
||||
const newMode = currentMode === 'light' ? 'dark' : 'light';
|
||||
setModeInStorage(newMode);
|
||||
return newMode;
|
||||
});
|
||||
|
||||
const memoizedValue = useMemo(
|
||||
() => ({
|
||||
mode,
|
||||
appEnv,
|
||||
appVersion,
|
||||
isAdminAddress,
|
||||
isLoading,
|
||||
error,
|
||||
clientDetails,
|
||||
storedAccounts,
|
||||
mixnodeDetails,
|
||||
userBalance,
|
||||
showAdmin,
|
||||
showTerminal,
|
||||
network,
|
||||
loginType,
|
||||
setIsLoading,
|
||||
setError,
|
||||
signInWithPassword,
|
||||
switchNetwork,
|
||||
getBondDetails,
|
||||
handleShowAdmin,
|
||||
handleShowTerminal,
|
||||
logIn,
|
||||
logOut,
|
||||
keepState,
|
||||
onAccountChange,
|
||||
showSendModal,
|
||||
showReceiveModal,
|
||||
handleShowSendModal,
|
||||
handleShowReceiveModal,
|
||||
handleSwitchMode,
|
||||
printBalance,
|
||||
printVestedBalance,
|
||||
}),
|
||||
[
|
||||
appVersion,
|
||||
loginType,
|
||||
isAdminAddress,
|
||||
mode,
|
||||
appEnv,
|
||||
isLoading,
|
||||
error,
|
||||
clientDetails,
|
||||
mixnodeDetails,
|
||||
userBalance,
|
||||
showAdmin,
|
||||
network,
|
||||
storedAccounts,
|
||||
showTerminal,
|
||||
showSendModal,
|
||||
showReceiveModal,
|
||||
],
|
||||
);
|
||||
|
||||
return <AppContext.Provider value={memoizedValue}>{children}</AppContext.Provider>;
|
||||
};
|
||||
@@ -0,0 +1,59 @@
|
||||
import { useCallback, useContext, useEffect, useState } from 'react';
|
||||
import { Console } from '../utils/console';
|
||||
// eslint-disable-next-line import/no-cycle
|
||||
import { AppContext } from '../context/main';
|
||||
import { checkGatewayOwnership, checkMixnodeOwnership, getVestingPledgeInfo } from '../requests';
|
||||
import { EnumNodeType, TNodeOwnership } from '../types';
|
||||
|
||||
const initial: TNodeOwnership = {
|
||||
hasOwnership: false,
|
||||
nodeType: undefined,
|
||||
vestingPledge: undefined,
|
||||
};
|
||||
|
||||
export const useCheckOwnership = () => {
|
||||
const { clientDetails } = useContext(AppContext);
|
||||
|
||||
const [ownership, setOwnership] = useState<TNodeOwnership>(initial);
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
const [error, setError] = useState<string>();
|
||||
|
||||
const checkOwnership = useCallback(async () => {
|
||||
const status = { ...initial };
|
||||
try {
|
||||
const [ownsMixnode, ownsGateway] = await Promise.all([checkMixnodeOwnership(), checkGatewayOwnership()]);
|
||||
|
||||
if (ownsMixnode) {
|
||||
status.hasOwnership = true;
|
||||
status.nodeType = EnumNodeType.mixnode;
|
||||
status.vestingPledge = await getVestingPledgeInfo({
|
||||
address: clientDetails?.client_address!,
|
||||
type: EnumNodeType.mixnode,
|
||||
});
|
||||
}
|
||||
|
||||
if (ownsGateway) {
|
||||
status.hasOwnership = true;
|
||||
status.nodeType = EnumNodeType.gateway;
|
||||
status.vestingPledge = await getVestingPledgeInfo({
|
||||
address: clientDetails?.client_address!,
|
||||
type: EnumNodeType.gateway,
|
||||
});
|
||||
}
|
||||
|
||||
setOwnership(status);
|
||||
} catch (e) {
|
||||
Console.error(e as string);
|
||||
setError(e as string);
|
||||
setOwnership(initial);
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
}, [clientDetails]);
|
||||
|
||||
useEffect(() => {
|
||||
checkOwnership();
|
||||
}, [clientDetails]);
|
||||
|
||||
return { isLoading, error, ownership, checkOwnership };
|
||||
};
|
||||
@@ -0,0 +1,147 @@
|
||||
/* eslint-disable import/no-cycle */
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
import { Account, Balance, DecCoin, OriginalVestingResponse, Period, VestingAccountInfo } from '@nymproject/types';
|
||||
import {
|
||||
getVestingCoins,
|
||||
getVestedCoins,
|
||||
getLockedCoins,
|
||||
getSpendableCoins,
|
||||
getOriginalVesting,
|
||||
getCurrentVestingPeriod,
|
||||
getVestingAccountInfo,
|
||||
getSpendableRewardCoins,
|
||||
getSpendableVestedCoins,
|
||||
userBalance,
|
||||
} from '../requests';
|
||||
import { Console } from '../utils/console';
|
||||
|
||||
type TTokenAllocation = {
|
||||
[key in
|
||||
| 'vesting'
|
||||
| 'vested'
|
||||
| 'locked'
|
||||
| 'spendable'
|
||||
| 'spendableRewardCoins'
|
||||
| 'spendableVestedCoins']: DecCoin['amount'];
|
||||
};
|
||||
|
||||
export type TUseuserBalance = {
|
||||
error?: string;
|
||||
balance?: Balance;
|
||||
tokenAllocation?: TTokenAllocation;
|
||||
originalVesting?: OriginalVestingResponse;
|
||||
currentVestingPeriod?: Period;
|
||||
vestingAccountInfo?: VestingAccountInfo;
|
||||
isLoading: boolean;
|
||||
fetchBalance: () => Promise<void>;
|
||||
fetchTokenAllocation: () => Promise<void>;
|
||||
clearBalance: () => void;
|
||||
clearAll: () => void;
|
||||
refreshBalances: () => Promise<void>;
|
||||
};
|
||||
|
||||
export const useGetBalance = (clientDetails?: Account): TUseuserBalance => {
|
||||
const [balance, setBalance] = useState<Balance>();
|
||||
const [error, setError] = useState<string>();
|
||||
const [tokenAllocation, setTokenAllocation] = useState<TTokenAllocation>();
|
||||
const [originalVesting, setOriginalVesting] = useState<OriginalVestingResponse>();
|
||||
const [currentVestingPeriod, setCurrentVestingPeriod] = useState<Period>();
|
||||
const [vestingAccountInfo, setVestingAccountInfo] = useState<VestingAccountInfo>();
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
|
||||
const clearBalance = () => setBalance(undefined);
|
||||
const clearTokenAllocation = () => setTokenAllocation(undefined);
|
||||
const clearOriginalVesting = () => setOriginalVesting(undefined);
|
||||
|
||||
const fetchTokenAllocation = async () => {
|
||||
setIsLoading(true);
|
||||
if (clientDetails?.client_address) {
|
||||
try {
|
||||
const [
|
||||
originalVestingValue,
|
||||
vestingCoins,
|
||||
vestedCoins,
|
||||
lockedCoins,
|
||||
spendableCoins,
|
||||
spendableVestedCoins,
|
||||
spendableRewardCoins,
|
||||
currentPeriod,
|
||||
vestingAccountDetail,
|
||||
] = await Promise.all([
|
||||
getOriginalVesting(clientDetails?.client_address),
|
||||
getVestingCoins(clientDetails?.client_address),
|
||||
getVestedCoins(clientDetails?.client_address),
|
||||
getLockedCoins(),
|
||||
getSpendableCoins(),
|
||||
getSpendableVestedCoins(),
|
||||
getSpendableRewardCoins(),
|
||||
getCurrentVestingPeriod(clientDetails?.client_address),
|
||||
getVestingAccountInfo(clientDetails?.client_address),
|
||||
]);
|
||||
setOriginalVesting(originalVestingValue);
|
||||
setCurrentVestingPeriod(currentPeriod);
|
||||
setTokenAllocation({
|
||||
vesting: vestingCoins.amount,
|
||||
vested: vestedCoins.amount,
|
||||
locked: lockedCoins.amount,
|
||||
spendable: spendableCoins.amount,
|
||||
spendableVestedCoins: spendableVestedCoins.amount,
|
||||
spendableRewardCoins: spendableRewardCoins.amount,
|
||||
});
|
||||
setVestingAccountInfo(vestingAccountDetail);
|
||||
} catch (e) {
|
||||
clearTokenAllocation();
|
||||
clearOriginalVesting();
|
||||
Console.error(e as string);
|
||||
}
|
||||
}
|
||||
setIsLoading(false);
|
||||
};
|
||||
|
||||
const fetchBalance = useCallback(async () => {
|
||||
setIsLoading(true);
|
||||
setError(undefined);
|
||||
try {
|
||||
const bal = await userBalance();
|
||||
setBalance(bal);
|
||||
} catch (err) {
|
||||
setError(err as string);
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
}, []);
|
||||
|
||||
const clearAll = () => {
|
||||
clearBalance();
|
||||
clearTokenAllocation();
|
||||
clearOriginalVesting();
|
||||
};
|
||||
|
||||
const refreshBalances = async () => {
|
||||
if (clientDetails?.client_address) {
|
||||
await fetchBalance();
|
||||
await fetchTokenAllocation();
|
||||
} else {
|
||||
clearAll();
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
refreshBalances();
|
||||
}, [clientDetails]);
|
||||
|
||||
return {
|
||||
error,
|
||||
isLoading,
|
||||
balance,
|
||||
tokenAllocation,
|
||||
originalVesting,
|
||||
currentVestingPeriod,
|
||||
vestingAccountInfo,
|
||||
fetchBalance,
|
||||
clearBalance,
|
||||
clearAll,
|
||||
fetchTokenAllocation,
|
||||
refreshBalances,
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,49 @@
|
||||
import { DecCoin, FeeDetails } from '@nymproject/types';
|
||||
import { useState } from 'react';
|
||||
import { Console } from '../utils/console';
|
||||
import { getCustomFees } from '../requests';
|
||||
|
||||
export function useGetFee() {
|
||||
const [fee, setFee] = useState<FeeDetails>();
|
||||
const [isFeeLoading, setIsFeeLoading] = useState(false);
|
||||
const [feeError, setFeeError] = useState<string>();
|
||||
|
||||
async function getFee<T>(operation: (args: T) => Promise<FeeDetails>, args: T) {
|
||||
try {
|
||||
setIsFeeLoading(true);
|
||||
const simulatedFee = await operation(args);
|
||||
setFee(simulatedFee);
|
||||
} catch (e) {
|
||||
// Console.error(e);
|
||||
setFeeError(e as string);
|
||||
}
|
||||
setIsFeeLoading(false);
|
||||
}
|
||||
|
||||
async function setFeeManually(amount: DecCoin) {
|
||||
try {
|
||||
setIsFeeLoading(true);
|
||||
const fees = await getCustomFees({ feesAmount: amount });
|
||||
setFee(fees);
|
||||
} catch (e) {
|
||||
Console.error(e);
|
||||
setFeeError(e as string);
|
||||
}
|
||||
setIsFeeLoading(false);
|
||||
}
|
||||
|
||||
const resetFeeState = () => {
|
||||
setFee(undefined);
|
||||
setIsFeeLoading(false);
|
||||
setFeeError(undefined);
|
||||
};
|
||||
|
||||
return {
|
||||
fee,
|
||||
isFeeLoading,
|
||||
feeError,
|
||||
getFee,
|
||||
setFeeManually,
|
||||
resetFeeState,
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
import { useState, useEffect } from 'react';
|
||||
|
||||
export function useKeyPress(targetKey: string): boolean {
|
||||
// State for keeping track of whether key is pressed
|
||||
const [keyPressed, setKeyPressed] = useState(false);
|
||||
// If pressed key is our target key then set to true
|
||||
function downHandler({ key }: { key: string }): void {
|
||||
if (key === targetKey) {
|
||||
setKeyPressed(true);
|
||||
}
|
||||
}
|
||||
// If released key is our target key then set to false
|
||||
const upHandler = ({ key }: { key: string }): void => {
|
||||
if (key === targetKey) {
|
||||
setKeyPressed(false);
|
||||
}
|
||||
};
|
||||
// Add event listeners
|
||||
useEffect(() => {
|
||||
window.addEventListener('keydown', downHandler);
|
||||
window.addEventListener('keyup', upHandler);
|
||||
// Remove event listeners on cleanup
|
||||
return () => {
|
||||
window.removeEventListener('keydown', downHandler);
|
||||
window.removeEventListener('keyup', upHandler);
|
||||
};
|
||||
}, []); // Empty array ensures that effect is only run on mount and unmount
|
||||
return keyPressed;
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
import { Account, Balance, AccountEntry } from '@nymproject/types';
|
||||
import { invokeWrapper } from './wrapper';
|
||||
|
||||
export const signInWithMnemonic = async (mnemonic: string): Promise<Account> =>
|
||||
invokeWrapper('connect_with_mnemonic', { mnemonic });
|
||||
|
||||
export const userBalance = async () => invokeWrapper<Balance>('get_balance');
|
||||
|
||||
export const createMnemonic = async () => invokeWrapper<string>('create_new_mnemonic');
|
||||
|
||||
export const validateMnemonic = async (mnemonic: string) => invokeWrapper<boolean>('validate_mnemonic', { mnemonic });
|
||||
|
||||
export const signOut = async () => invokeWrapper<void>('logout');
|
||||
|
||||
export const isPasswordCreated = async () => invokeWrapper<boolean>('does_password_file_exist');
|
||||
|
||||
export const createPassword = async ({ mnemonic, password }: { mnemonic: string; password: string }) =>
|
||||
invokeWrapper<void>('create_password', { mnemonic, password });
|
||||
|
||||
export const updatePassword = async ({
|
||||
currentPassword,
|
||||
newPassword,
|
||||
}: {
|
||||
currentPassword: string;
|
||||
newPassword: string;
|
||||
}) => invokeWrapper<void>('update_password', { currentPassword, newPassword });
|
||||
|
||||
export const signInWithPassword = async (password: string) =>
|
||||
invokeWrapper<Account>('sign_in_with_password', { password });
|
||||
|
||||
export const switchAccount = async ({ accountId, password }: { accountId: string; password: string }) =>
|
||||
invokeWrapper<Account>('sign_in_with_password_and_account_id', { accountId, password });
|
||||
|
||||
export const addAccount = async ({
|
||||
mnemonic,
|
||||
password,
|
||||
accountName,
|
||||
}: {
|
||||
mnemonic: string;
|
||||
password: string;
|
||||
accountName: string;
|
||||
}) => invokeWrapper<AccountEntry>('add_account_for_password', { mnemonic, password, accountId: accountName });
|
||||
|
||||
export const removeAccount = async ({ password, accountName }: { password: string; accountName: string }) =>
|
||||
invokeWrapper<void>('remove_account_for_password', { password, innerId: accountName });
|
||||
|
||||
export const listAccounts = async () => invokeWrapper<AccountEntry[]>('list_accounts');
|
||||
|
||||
export const archiveWalletFile = async () => invokeWrapper<void>('archive_wallet_file');
|
||||
|
||||
export const showMnemonicForAccount = async ({ password, accountName }: { password: string; accountName: string }) =>
|
||||
invokeWrapper<string>('show_mnemonic_for_account_in_password', { password, accountId: accountName });
|
||||
|
||||
export const renameAccount = async ({
|
||||
password,
|
||||
accountName,
|
||||
newAccountName,
|
||||
}: {
|
||||
password: string;
|
||||
accountName: string;
|
||||
newAccountName: string;
|
||||
}) =>
|
||||
invokeWrapper<AccountEntry>('rename_account_for_password', {
|
||||
password,
|
||||
accountId: accountName,
|
||||
newAccountId: newAccountName,
|
||||
});
|
||||
@@ -0,0 +1,54 @@
|
||||
import {
|
||||
Fee,
|
||||
DecCoin,
|
||||
SendTxResult,
|
||||
TransactionExecuteResult,
|
||||
MixNodeConfigUpdate,
|
||||
MixNodeCostParams,
|
||||
GatewayConfigUpdate,
|
||||
} from '@nymproject/types';
|
||||
import {
|
||||
EnumNodeType,
|
||||
TBondGatewayArgs,
|
||||
TBondGatewaySignatureArgs,
|
||||
TBondMixNodeArgs,
|
||||
TBondMixnodeSignatureArgs,
|
||||
TUpdateBondArgs,
|
||||
} from '../types';
|
||||
import { invokeWrapper } from './wrapper';
|
||||
|
||||
export const bondGateway = async (args: TBondGatewayArgs) =>
|
||||
invokeWrapper<TransactionExecuteResult>('bond_gateway', args);
|
||||
|
||||
export const generateGatewayMsgPayload = async (args: Omit<TBondGatewaySignatureArgs, 'tokenPool'>) =>
|
||||
invokeWrapper<string>('generate_gateway_bonding_msg_payload', args);
|
||||
|
||||
export const unbondGateway = async (fee?: Fee) => invokeWrapper<TransactionExecuteResult>('unbond_gateway', { fee });
|
||||
|
||||
export const bondMixNode = async (args: TBondMixNodeArgs) =>
|
||||
invokeWrapper<TransactionExecuteResult>('bond_mixnode', args);
|
||||
|
||||
export const generateMixnodeMsgPayload = async (args: Omit<TBondMixnodeSignatureArgs, 'tokenPool'>) =>
|
||||
invokeWrapper<string>('generate_mixnode_bonding_msg_payload', args);
|
||||
|
||||
export const unbondMixNode = async (fee?: Fee) => invokeWrapper<TransactionExecuteResult>('unbond_mixnode', { fee });
|
||||
|
||||
export const updateMixnodeCostParams = async (newCosts: MixNodeCostParams, fee?: Fee) =>
|
||||
invokeWrapper<TransactionExecuteResult>('update_mixnode_cost_params', { newCosts, fee });
|
||||
|
||||
export const updateMixnodeConfig = async (update: MixNodeConfigUpdate, fee?: Fee) =>
|
||||
invokeWrapper<TransactionExecuteResult>('update_mixnode_config', { update, fee });
|
||||
|
||||
export const updateGatewayConfig = async (update: GatewayConfigUpdate, fee?: Fee) =>
|
||||
invokeWrapper<TransactionExecuteResult>('update_gateway_config', { update, fee });
|
||||
|
||||
export const send = async (args: { amount: DecCoin; address: string; memo: string; fee?: Fee }) =>
|
||||
invokeWrapper<SendTxResult>('send', args);
|
||||
|
||||
export const unbond = async (type: EnumNodeType) => {
|
||||
if (type === EnumNodeType.mixnode) return unbondMixNode();
|
||||
return unbondGateway();
|
||||
};
|
||||
|
||||
export const updateBond = async (args: TUpdateBondArgs) =>
|
||||
invokeWrapper<TransactionExecuteResult>('update_pledge', args);
|
||||
@@ -0,0 +1,14 @@
|
||||
import { invokeWrapper } from './wrapper';
|
||||
import { AppVersion } from '../types/rust/AppVersion';
|
||||
|
||||
export const checkVersion = async () => invokeWrapper<AppVersion>('check_version');
|
||||
|
||||
export const createMainWindow = async (): Promise<void> => invokeWrapper<void>('create_main_window');
|
||||
|
||||
export const createSignInWindow = async (): Promise<void> => invokeWrapper<void>('create_auth_window');
|
||||
|
||||
export const setReactState = async (newState?: string): Promise<void> =>
|
||||
invokeWrapper<void>('set_react_state', { newState });
|
||||
|
||||
export const getReactState = async (): Promise<string | undefined> =>
|
||||
invokeWrapper<string | undefined>('get_react_state');
|
||||
@@ -0,0 +1,7 @@
|
||||
import { TauriContractStateParams } from '../types';
|
||||
import { invokeWrapper } from './wrapper';
|
||||
|
||||
export const getContractParams = async () => invokeWrapper<TauriContractStateParams>('get_contract_settings');
|
||||
|
||||
export const setContractParams = async (params: TauriContractStateParams) =>
|
||||
invokeWrapper<TauriContractStateParams>('update_contract_settings', { params });
|
||||
@@ -0,0 +1,33 @@
|
||||
import {
|
||||
DelegationWithEverything,
|
||||
DelegationsSummaryResponse,
|
||||
TransactionExecuteResult,
|
||||
DecCoin,
|
||||
FeeDetails,
|
||||
Fee,
|
||||
} from '@nymproject/types';
|
||||
import { invokeWrapper } from './wrapper';
|
||||
|
||||
export const getMixNodeDelegationsForCurrentAccount = async () =>
|
||||
invokeWrapper<DelegationWithEverything[]>('get_all_mix_delegations');
|
||||
|
||||
export const getDelegationSummary = async () => invokeWrapper<DelegationsSummaryResponse>('get_delegation_summary');
|
||||
|
||||
export const undelegateFromMixnode = async (mixId: number, fee?: Fee) =>
|
||||
invokeWrapper<TransactionExecuteResult>('undelegate_from_mixnode', { mixId, fee });
|
||||
|
||||
export const undelegateAllFromMixnode = async (
|
||||
mixId: number,
|
||||
usesVestingContractTokens: boolean,
|
||||
fee_liquid?: FeeDetails,
|
||||
fee_vesting?: FeeDetails,
|
||||
) =>
|
||||
invokeWrapper<TransactionExecuteResult[]>('undelegate_all_from_mixnode', {
|
||||
mixId,
|
||||
usesVestingContractTokens,
|
||||
fee_liquid,
|
||||
fee_vesting,
|
||||
});
|
||||
|
||||
export const delegateToMixnode = async (mixId: number, amount: DecCoin, fee?: Fee) =>
|
||||
invokeWrapper<TransactionExecuteResult>('delegate_to_mixnode', { mixId, amount, fee });
|
||||
@@ -0,0 +1,14 @@
|
||||
export * from './app';
|
||||
export * from './account';
|
||||
export * from './actions';
|
||||
export * from './contract';
|
||||
export * from './delegation';
|
||||
export * from './logging';
|
||||
export * from './network';
|
||||
export * from './queries';
|
||||
export * from './rewards';
|
||||
export * from './signature';
|
||||
export * from './simulate';
|
||||
export * from './utils';
|
||||
export * from './vesting';
|
||||
export * from './pendingEvents';
|
||||
@@ -0,0 +1,3 @@
|
||||
import { invokeWrapper } from './wrapper';
|
||||
|
||||
export const helpLogToggleWindow = async () => invokeWrapper<void>('help_log_toggle_window', {});
|
||||
@@ -0,0 +1,16 @@
|
||||
import { Account } from '@nymproject/types';
|
||||
import { Network } from '../types';
|
||||
import { invokeWrapper } from './wrapper';
|
||||
|
||||
export const selectNetwork = async (network: Network) => invokeWrapper<Account>('switch_network', { network });
|
||||
|
||||
export const getSelectedValidatorUrl = async (network: Network) =>
|
||||
invokeWrapper<string | null>('get_selected_nyxd_url', { network });
|
||||
|
||||
export const getDefaultValidatorUrl = async (network: Network) =>
|
||||
invokeWrapper<string | null>('get_default_nyxd_url', { network });
|
||||
|
||||
export const setSelectedValidatorUrl = async (args: { network: Network; url: string }) =>
|
||||
invokeWrapper<void>('select_nyxd_url', args);
|
||||
|
||||
export const resetValidatorUrl = async (network: Network) => invokeWrapper<void>('reset_nyxd_url', { network });
|
||||
@@ -0,0 +1,4 @@
|
||||
import { PendingEpochEvent } from '@nymproject/types';
|
||||
import { invokeWrapper } from './wrapper';
|
||||
|
||||
export const getPendingEpochEvents = async () => invokeWrapper<PendingEpochEvent[]>('get_pending_epoch_events');
|
||||
@@ -0,0 +1,63 @@
|
||||
import {
|
||||
DecCoin,
|
||||
GatewayBond,
|
||||
InclusionProbabilityResponse,
|
||||
MixNodeDetails,
|
||||
MixnodeStatusResponse,
|
||||
PendingIntervalEvent,
|
||||
RewardEstimationResponse,
|
||||
StakeSaturationResponse,
|
||||
WrappedDelegationEvent,
|
||||
} from '@nymproject/types';
|
||||
import { Interval, TGatewayReport, TNodeDescription } from '../types';
|
||||
import { invokeWrapper } from './wrapper';
|
||||
|
||||
export const getAllPendingDelegations = async () =>
|
||||
invokeWrapper<WrappedDelegationEvent[]>('get_pending_delegation_events');
|
||||
|
||||
export const getMixnodeBondDetails = async () => invokeWrapper<MixNodeDetails | null>('mixnode_bond_details');
|
||||
export const getGatewayBondDetails = async () => invokeWrapper<GatewayBond | null>('gateway_bond_details');
|
||||
export const getMixnodeAvgUptime = async () => invokeWrapper<number | null>('get_mixnode_avg_uptime');
|
||||
|
||||
export const getPendingOperatorRewards = async (address: string) =>
|
||||
invokeWrapper<DecCoin>('get_pending_operator_rewards', { address });
|
||||
|
||||
export const getMixnodeStakeSaturation = async (mixId: number) =>
|
||||
invokeWrapper<StakeSaturationResponse>('mixnode_stake_saturation', { mixId });
|
||||
|
||||
export const getMixnodeRewardEstimation = async (mixId: number) =>
|
||||
invokeWrapper<RewardEstimationResponse>('mixnode_reward_estimation', { mixId });
|
||||
|
||||
export const getMixnodeStatus = async (mixId: number) =>
|
||||
invokeWrapper<MixnodeStatusResponse>('mixnode_status', { mixId });
|
||||
|
||||
export const checkMixnodeOwnership = async () => invokeWrapper<boolean>('owns_mixnode');
|
||||
|
||||
export const checkGatewayOwnership = async () => invokeWrapper<boolean>('owns_gateway');
|
||||
|
||||
export const getInclusionProbability = async (mixId: number) =>
|
||||
invokeWrapper<InclusionProbabilityResponse>('mixnode_inclusion_probability', { mixId });
|
||||
|
||||
export const getCurrentInterval = async () => invokeWrapper<Interval>('get_current_interval');
|
||||
|
||||
export const getNumberOfMixnodeDelegators = async (mixId: number) =>
|
||||
invokeWrapper<number>('get_number_of_mixnode_delegators', { mixId });
|
||||
|
||||
export const getNodeDescription = async (host: string, port: number) =>
|
||||
invokeWrapper<TNodeDescription>('get_mix_node_description', { host, port });
|
||||
|
||||
export const getPendingIntervalEvents = async () =>
|
||||
invokeWrapper<PendingIntervalEvent[]>('get_pending_interval_events');
|
||||
|
||||
export const getGatewayReport = async (identity: string) =>
|
||||
invokeWrapper<TGatewayReport>('gateway_report', { identity });
|
||||
|
||||
export const computeMixnodeRewardEstimation = async (args: {
|
||||
mixId: number;
|
||||
performance: string;
|
||||
pledgeAmount: number;
|
||||
totalDelegation: number;
|
||||
profitMarginPercent: string;
|
||||
intervalOperatingCost: { denom: 'unym'; amount: string };
|
||||
}) => invokeWrapper<RewardEstimationResponse>('compute_mixnode_reward_estimation', args);
|
||||
export const getMixnodeUptime = async (mixId: number) => invokeWrapper<number>('get_mixnode_uptime', { mixId });
|
||||
@@ -0,0 +1,14 @@
|
||||
import { Fee, FeeDetails, RewardingParams, TransactionExecuteResult } from '@nymproject/types';
|
||||
import { invokeWrapper } from './wrapper';
|
||||
|
||||
export const claimOperatorReward = async (fee?: Fee) =>
|
||||
invokeWrapper<TransactionExecuteResult>('claim_operator_reward', { fee });
|
||||
|
||||
export const claimDelegatorRewards = async (mixId: number, fee?: FeeDetails) =>
|
||||
invokeWrapper<TransactionExecuteResult[]>('claim_locked_and_unlocked_delegator_reward', {
|
||||
mixId,
|
||||
fee: fee?.fee,
|
||||
});
|
||||
|
||||
export const getCurrentRewardingParameter = async () =>
|
||||
invokeWrapper<RewardingParams>('get_current_rewarding_parameters', {});
|
||||
@@ -0,0 +1,9 @@
|
||||
import { invokeWrapper } from './wrapper';
|
||||
|
||||
export const sign = async (message: string): Promise<string> => invokeWrapper<string>('sign', { message });
|
||||
|
||||
export const verify = async (
|
||||
signatureAsHex: string,
|
||||
message: string,
|
||||
publicKeyAsJsonOrAccountAddress?: string | null,
|
||||
): Promise<string> => invokeWrapper<string>('verify', { publicKeyAsJsonOrAccountAddress, signatureAsHex, message });
|
||||
@@ -0,0 +1,87 @@
|
||||
import {
|
||||
FeeDetails,
|
||||
DecCoin,
|
||||
Gateway,
|
||||
MixNodeCostParams,
|
||||
MixNodeConfigUpdate,
|
||||
GatewayConfigUpdate,
|
||||
} from '@nymproject/types';
|
||||
import { TBondGatewayArgs, TBondMixNodeArgs, TSimulateUpdateBondArgs } from '../types';
|
||||
import { invokeWrapper } from './wrapper';
|
||||
|
||||
export const simulateBondGateway = async (args: TBondGatewayArgs) =>
|
||||
invokeWrapper<FeeDetails>('simulate_bond_gateway', args);
|
||||
|
||||
export const simulateUnbondGateway = async (args: any) => invokeWrapper<FeeDetails>('simulate_unbond_gateway', args);
|
||||
|
||||
export const simulateBondMixnode = async (args: TBondMixNodeArgs) =>
|
||||
invokeWrapper<FeeDetails>('simulate_bond_mixnode', args);
|
||||
|
||||
export const simulateUnbondMixnode = async (args: any) => invokeWrapper<FeeDetails>('simulate_unbond_mixnode', args);
|
||||
|
||||
export const simulateUpdateMixnodeCostParams = async (newCosts: MixNodeCostParams) =>
|
||||
invokeWrapper<FeeDetails>('simulate_update_mixnode_cost_params', { newCosts });
|
||||
|
||||
export const simulateUpdateMixnodeConfig = async (update: MixNodeConfigUpdate) =>
|
||||
invokeWrapper<FeeDetails>('simulate_update_mixnode_config', { update });
|
||||
|
||||
export const simulateUpdateGatewayConfig = async (update: GatewayConfigUpdate) =>
|
||||
invokeWrapper<FeeDetails>('simulate_update_gateway_config', { update });
|
||||
|
||||
export const simulateDelegateToMixnode = async (args: { mixId: number; amount: DecCoin }) =>
|
||||
invokeWrapper<FeeDetails>('simulate_delegate_to_mixnode', args);
|
||||
|
||||
export const simulateUndelegateFromMixnode = async (mixId: number) =>
|
||||
invokeWrapper<FeeDetails>('simulate_undelegate_from_mixnode', { mixId });
|
||||
|
||||
export const simulateClaimDelegatorReward = async (mixId: number) =>
|
||||
invokeWrapper<FeeDetails>('simulate_claim_delegator_reward', { mixId });
|
||||
|
||||
export const simulateVestingClaimDelegatorReward = async (mixId: number) =>
|
||||
invokeWrapper<FeeDetails>('simulate_vesting_claim_delegator_reward', { mixId });
|
||||
|
||||
export const simulateVestingUndelegateFromMixnode = async (args: any) =>
|
||||
invokeWrapper<FeeDetails>('simulate_vesting_undelegate_from_mixnode', args);
|
||||
|
||||
export const simulateVestingBondGateway = async (args: { gateway: Gateway; pledge: DecCoin; msgSignature: string }) =>
|
||||
invokeWrapper<FeeDetails>('simulate_vesting_bond_gateway', args);
|
||||
|
||||
export const simulateVestingUnbondGateway = async (args: any) =>
|
||||
invokeWrapper<FeeDetails>('simulate_vesting_unbond_gateway', args);
|
||||
|
||||
export const simulateVestingDelegateToMixnode = async (args: { mixId: number }) =>
|
||||
invokeWrapper<FeeDetails>('simulate_vesting_delegate_to_mixnode', args);
|
||||
|
||||
export const simulateVestingBondMixnode = async (args: TBondMixNodeArgs) =>
|
||||
invokeWrapper<FeeDetails>('simulate_vesting_bond_mixnode', args);
|
||||
|
||||
export const simulateVestingUnbondMixnode = async () => invokeWrapper<FeeDetails>('simulate_vesting_unbond_mixnode');
|
||||
|
||||
export const simulateVestingUpdateMixnodeCostParams = async (newCosts: MixNodeCostParams) =>
|
||||
invokeWrapper<FeeDetails>('simulate_vesting_update_mixnode_cost_params', { newCosts });
|
||||
|
||||
export const simulateVestingUpdateMixnodeConfig = async (update: MixNodeConfigUpdate) =>
|
||||
invokeWrapper<FeeDetails>('simulate_vesting_update_mixnode_config', { update });
|
||||
|
||||
export const simulateVestingUpdateGatewayConfig = async (update: GatewayConfigUpdate) =>
|
||||
invokeWrapper<FeeDetails>('simulate_vesting_update_gateway_config', { update });
|
||||
|
||||
export const simulateWithdrawVestedCoins = async (args: any) =>
|
||||
invokeWrapper<FeeDetails>('simulate_withdraw_vested_coins', args);
|
||||
|
||||
export const simulateSend = async ({ address, amount }: { address: string; amount: DecCoin }) =>
|
||||
invokeWrapper<FeeDetails>('simulate_send', { address, amount });
|
||||
|
||||
export const getCustomFees = async ({ feesAmount }: { feesAmount: DecCoin }) =>
|
||||
invokeWrapper<FeeDetails>('get_custom_fees', { feesAmount });
|
||||
|
||||
export const simulateClaimOperatorReward = async () => invokeWrapper<FeeDetails>('simulate_claim_operator_reward');
|
||||
|
||||
export const simulateVestingClaimOperatorReward = async () =>
|
||||
invokeWrapper<FeeDetails>('simulate_vesting_claim_operator_reward');
|
||||
|
||||
export const simulateUpdateBond = async (args: TSimulateUpdateBondArgs) =>
|
||||
invokeWrapper<FeeDetails>('simulate_update_pledge', args);
|
||||
|
||||
export const simulateVestingUpdateBond = async (args: TSimulateUpdateBondArgs) =>
|
||||
invokeWrapper<FeeDetails>('simulate_vesting_update_pledge', args);
|
||||
@@ -0,0 +1,11 @@
|
||||
import { MixNodeCostParams } from '@nymproject/types';
|
||||
import { AppEnv } from '../types';
|
||||
import { invokeWrapper } from './wrapper';
|
||||
|
||||
export const getEnv = async () => invokeWrapper<AppEnv>('get_env');
|
||||
|
||||
export const tryConvertIdentityToMixId = async (mixIdentity: string) =>
|
||||
invokeWrapper<number | null>('try_convert_pubkey_to_mix_id', { mixIdentity });
|
||||
|
||||
export const getDefaultMixnodeCostParams = async (profitMarginPercent: string) =>
|
||||
invokeWrapper<MixNodeCostParams>('default_mixnode_cost_params', { profitMarginPercent });
|
||||
@@ -0,0 +1,125 @@
|
||||
import {
|
||||
TNodeType,
|
||||
Gateway,
|
||||
DecCoin,
|
||||
MixNode,
|
||||
OriginalVestingResponse,
|
||||
Period,
|
||||
PledgeData,
|
||||
TransactionExecuteResult,
|
||||
VestingAccountInfo,
|
||||
MixNodeCostParams,
|
||||
MixNodeConfigUpdate,
|
||||
GatewayConfigUpdate,
|
||||
} from '@nymproject/types';
|
||||
import { Fee } from '@nymproject/types/dist/types/rust/Fee';
|
||||
import { invokeWrapper } from './wrapper';
|
||||
import { TBondGatewaySignatureArgs, TBondMixnodeSignatureArgs, TUpdateBondArgs } from '../types';
|
||||
|
||||
export const getLockedCoins = async (): Promise<DecCoin> => invokeWrapper<DecCoin>('locked_coins');
|
||||
|
||||
export const getSpendableCoins = async (): Promise<DecCoin> => invokeWrapper<DecCoin>('spendable_coins');
|
||||
|
||||
export const getSpendableVestedCoins = async (): Promise<DecCoin> => invokeWrapper<DecCoin>('spendable_vested_coins');
|
||||
|
||||
export const getSpendableRewardCoins = async (): Promise<DecCoin> => invokeWrapper<DecCoin>('spendable_reward_coins');
|
||||
|
||||
export const getVestingCoins = async (vestingAccountAddress: string): Promise<DecCoin> =>
|
||||
invokeWrapper<DecCoin>('vesting_coins', { vestingAccountAddress });
|
||||
|
||||
export const getVestedCoins = async (vestingAccountAddress: string): Promise<DecCoin> =>
|
||||
invokeWrapper<DecCoin>('vested_coins', { vestingAccountAddress });
|
||||
|
||||
export const getOriginalVesting = async (vestingAccountAddress: string): Promise<OriginalVestingResponse> => {
|
||||
const res = await invokeWrapper<OriginalVestingResponse>('original_vesting', { vestingAccountAddress });
|
||||
return { ...res, amount: res.amount };
|
||||
};
|
||||
|
||||
export const getCurrentVestingPeriod = async (address: string) =>
|
||||
invokeWrapper<Period>('get_current_vesting_period', { address });
|
||||
|
||||
export const vestingBondGateway = async ({
|
||||
gateway,
|
||||
pledge,
|
||||
msgSignature,
|
||||
}: {
|
||||
gateway: Gateway;
|
||||
pledge: DecCoin;
|
||||
msgSignature: string;
|
||||
}) => invokeWrapper<TransactionExecuteResult>('vesting_bond_gateway', { gateway, msgSignature, pledge });
|
||||
|
||||
export const vestingGenerateGatewayMsgPayload = async (args: Omit<TBondGatewaySignatureArgs, 'tokenPool'>) =>
|
||||
invokeWrapper<string>('vesting_generate_gateway_bonding_msg_payload', args);
|
||||
|
||||
export const vestingUnbondGateway = async (fee?: Fee) =>
|
||||
invokeWrapper<TransactionExecuteResult>('vesting_unbond_gateway', { fee });
|
||||
|
||||
export const vestingBondMixNode = async ({
|
||||
mixnode,
|
||||
costParams,
|
||||
pledge,
|
||||
msgSignature,
|
||||
}: {
|
||||
mixnode: MixNode;
|
||||
costParams: MixNodeCostParams;
|
||||
pledge: DecCoin;
|
||||
msgSignature: string;
|
||||
}) => invokeWrapper<TransactionExecuteResult>('vesting_bond_mixnode', { mixnode, costParams, msgSignature, pledge });
|
||||
|
||||
export const vestingGenerateMixnodeMsgPayload = async (args: Omit<TBondMixnodeSignatureArgs, 'tokenPool'>) =>
|
||||
invokeWrapper<string>('vesting_generate_mixnode_bonding_msg_payload', args);
|
||||
|
||||
export const vestingUnbondMixnode = async (fee?: Fee) =>
|
||||
invokeWrapper<TransactionExecuteResult>('vesting_unbond_mixnode', { fee });
|
||||
|
||||
export const withdrawVestedCoins = async (amount: DecCoin, fee?: Fee) =>
|
||||
invokeWrapper<TransactionExecuteResult>('withdraw_vested_coins', { amount, fee });
|
||||
|
||||
export const vestingUpdateMixnodeCostParams = async (newCosts: MixNodeCostParams, fee?: Fee) =>
|
||||
invokeWrapper<TransactionExecuteResult>('vesting_update_mixnode_cost_params', { newCosts, fee });
|
||||
|
||||
export const vestingUpdateMixnodeConfig = async (update: MixNodeConfigUpdate, fee?: Fee) =>
|
||||
invokeWrapper<TransactionExecuteResult>('vesting_update_mixnode_config', { update, fee });
|
||||
|
||||
export const vestingUpdateGatewayConfig = async (update: GatewayConfigUpdate, fee?: Fee) =>
|
||||
invokeWrapper<TransactionExecuteResult>('vesting_update_gateway_config', { update, fee });
|
||||
|
||||
export const vestingDelegateToMixnode = async (mixId: number, amount: DecCoin, fee?: Fee) =>
|
||||
invokeWrapper<TransactionExecuteResult>('vesting_delegate_to_mixnode', { mixId, amount, fee });
|
||||
|
||||
export const vestingUndelegateFromMixnode = async (mixId: number) =>
|
||||
invokeWrapper<TransactionExecuteResult>('vesting_undelegate_from_mixnode', { mixId });
|
||||
|
||||
export const getVestingAccountInfo = async (address: string) =>
|
||||
invokeWrapper<VestingAccountInfo>('get_account_info', { address });
|
||||
|
||||
export const getVestingPledgeInfo = async ({
|
||||
address,
|
||||
type,
|
||||
}: {
|
||||
address?: string;
|
||||
type: TNodeType;
|
||||
}): Promise<PledgeData | undefined> => {
|
||||
try {
|
||||
return await invokeWrapper<PledgeData>(`vesting_get_${type}_pledge`, { address });
|
||||
} catch (e) {
|
||||
return undefined;
|
||||
}
|
||||
};
|
||||
|
||||
export const vestingDelegatedFree = async (vestingAccountAddress: string) =>
|
||||
invokeWrapper<DecCoin>('delegated_free', { vestingAccountAddress });
|
||||
|
||||
export const vestingUnbond = async (type: TNodeType) => {
|
||||
if (type === 'mixnode') return vestingUnbondMixnode();
|
||||
return vestingUnbondGateway();
|
||||
};
|
||||
|
||||
export const vestingClaimOperatorReward = async (fee?: Fee) =>
|
||||
invokeWrapper<TransactionExecuteResult>('vesting_claim_operator_reward', { fee });
|
||||
|
||||
export const vestingClaimDelegatorRewards = async (mixId: number) =>
|
||||
invokeWrapper<TransactionExecuteResult>('vesting_claim_delegator_reward', { mixId });
|
||||
|
||||
export const vestingUpdateBond = async (args: TUpdateBondArgs) =>
|
||||
invokeWrapper<TransactionExecuteResult>('vesting_update_pledge', args);
|
||||
@@ -0,0 +1,20 @@
|
||||
import { invoke } from '@tauri-apps/api';
|
||||
import { config } from '../config';
|
||||
import { Console } from '../utils/console';
|
||||
|
||||
export async function invokeWrapper<T>(operationName: string, args?: any): Promise<T> {
|
||||
const res = await invoke<T>(operationName, args);
|
||||
if (config.LOG_TAURI_OPERATIONS) {
|
||||
const argsToLog: any = {};
|
||||
if (args) {
|
||||
Object.keys(args).forEach((key) => {
|
||||
// check if the key should be excluded from logs
|
||||
if (!['mnemonic', 'password', 'currentPassword', 'newPassword'].includes(key)) {
|
||||
argsToLog[key] = args[key];
|
||||
}
|
||||
});
|
||||
}
|
||||
Console.log({ operationName, argsToLog, res });
|
||||
}
|
||||
return res;
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
import { DecCoin, Gateway, MixNode, MixNodeCostParams, PledgeData } from '@nymproject/types';
|
||||
import { Fee } from '@nymproject/types/dist/types/rust/Fee';
|
||||
// eslint-disable-next-line import/no-cycle
|
||||
// import { TBondedGateway, TBondedMixnode } from '../context';
|
||||
|
||||
export enum EnumNodeType {
|
||||
mixnode = 'mixnode',
|
||||
gateway = 'gateway',
|
||||
}
|
||||
|
||||
export type TNodeOwnership = {
|
||||
hasOwnership: boolean;
|
||||
nodeType?: EnumNodeType;
|
||||
vestingPledge?: PledgeData;
|
||||
};
|
||||
|
||||
export type TPendingDelegation = {
|
||||
block_height: number;
|
||||
};
|
||||
|
||||
export type TDelegation = {
|
||||
owner: string;
|
||||
node_identity: string;
|
||||
amount: DecCoin;
|
||||
block_height: number;
|
||||
proxy: string; // proxy address used to delegate the funds on behalf of another address
|
||||
pending?: TPendingDelegation;
|
||||
};
|
||||
|
||||
export type TBondGatewayArgs = {
|
||||
gateway: Gateway;
|
||||
pledge: DecCoin;
|
||||
msgSignature: string;
|
||||
fee?: Fee;
|
||||
};
|
||||
|
||||
export type TBondMixNodeArgs = {
|
||||
mixnode: MixNode;
|
||||
costParams: MixNodeCostParams;
|
||||
pledge: DecCoin;
|
||||
msgSignature: string;
|
||||
fee?: Fee;
|
||||
};
|
||||
|
||||
export type TBondMixnodeSignatureArgs = {
|
||||
mixnode: MixNode;
|
||||
costParams: MixNodeCostParams;
|
||||
pledge: DecCoin;
|
||||
tokenPool: 'balance' | 'locked';
|
||||
};
|
||||
|
||||
export type TBondGatewaySignatureArgs = {
|
||||
gateway: Gateway;
|
||||
pledge: DecCoin;
|
||||
tokenPool: 'balance' | 'locked';
|
||||
};
|
||||
|
||||
export type TUpdateBondArgs = {
|
||||
currentPledge: DecCoin;
|
||||
newPledge: DecCoin;
|
||||
fee?: Fee;
|
||||
};
|
||||
|
||||
export type TSimulateUpdateBondArgs = Omit<TUpdateBondArgs, 'fee'>;
|
||||
|
||||
export type TNodeDescription = {
|
||||
name: string;
|
||||
description: string;
|
||||
link: string;
|
||||
location: string;
|
||||
};
|
||||
|
||||
export type TDelegateArgs = {
|
||||
identity: string;
|
||||
amount: DecCoin;
|
||||
};
|
||||
|
||||
export type Period = 'Before' | { In: number } | 'After';
|
||||
|
||||
export type TAccount = {
|
||||
name: string;
|
||||
address: string;
|
||||
mnemonic: string;
|
||||
};
|
||||
|
||||
export type TGatewayReport = {
|
||||
identity: string;
|
||||
owner: string;
|
||||
last_day: number;
|
||||
last_hour: number;
|
||||
most_recent: number;
|
||||
};
|
||||
|
||||
// export const isMixnode = (node: TBondedMixnode | TBondedGateway): node is TBondedMixnode =>
|
||||
// (node as TBondedMixnode).profitMargin !== undefined;
|
||||
|
||||
// export const isGateway = (node: TBondedMixnode | TBondedGateway): node is TBondedGateway => !isMixnode(node);
|
||||
@@ -0,0 +1,7 @@
|
||||
export * from './global';
|
||||
export * from './rust/AppEnv';
|
||||
export * from './rust/Interval';
|
||||
export * from './rust/Network';
|
||||
export * from './rust/StateParams';
|
||||
export * from './rust/ValidatorUrl';
|
||||
export * from './rust/ValidatorUrls';
|
||||
@@ -0,0 +1,5 @@
|
||||
export interface AppEnv {
|
||||
ADMIN_ADDRESS: string | null;
|
||||
SHOW_TERMINAL: string | null;
|
||||
ENABLE_QA_MODE: string | null;
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
|
||||
export interface AppVersion {
|
||||
current_version: string;
|
||||
latest_version: string;
|
||||
is_update_available: boolean;
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
|
||||
export interface Epoch {
|
||||
id: number;
|
||||
start: bigint;
|
||||
end: bigint;
|
||||
duration_seconds: bigint;
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
export interface Interval {
|
||||
id: number;
|
||||
epochs_in_interval: number;
|
||||
current_epoch_start_unix: bigint;
|
||||
current_epoch_id: number;
|
||||
epoch_length_seconds: bigint;
|
||||
total_elapsed_epochs: number;
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
|
||||
export type Network = 'QA' | 'SANDBOX' | 'MAINNET';
|
||||
@@ -0,0 +1,8 @@
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
import type { DecCoin } from '@nymproject/types/src/types/rust/DecCoin';
|
||||
|
||||
export interface TauriContractStateParams {
|
||||
minimum_mixnode_pledge: DecCoin;
|
||||
minimum_gateway_pledge: DecCoin;
|
||||
minimum_mixnode_delegation: DecCoin | null;
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
|
||||
export interface ValidatorUrl {
|
||||
url: string;
|
||||
name: string | null;
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
import type { ValidatorUrl } from './ValidatorUrl';
|
||||
|
||||
export interface ValidatorUrls {
|
||||
urls: Array<ValidatorUrl>;
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
declare type FCWithChildren<P = {}> = React.FC<React.PropsWithChildren<P>>;
|
||||
@@ -0,0 +1,9 @@
|
||||
declare module '*.jpeg' {
|
||||
const value: any;
|
||||
export default value;
|
||||
}
|
||||
|
||||
declare module '*.jpg' {
|
||||
const value: any;
|
||||
export default value;
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
declare module '*.json' {
|
||||
const content: any;
|
||||
export default content;
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
declare module '*.png' {
|
||||
const content: any;
|
||||
export default content;
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
declare module '*.svg' {
|
||||
const content: any;
|
||||
export default content;
|
||||
}
|
||||
@@ -0,0 +1,255 @@
|
||||
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 } 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 (balance: string, allocationValue: string): Promise<boolean> => {
|
||||
// try {
|
||||
// // const walletValue = await userBalance();
|
||||
|
||||
// const remainingBalance = +balance - +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 = (tokenPool: TPoolOption, amount: string, balance: string) => {
|
||||
let hasEnoughFunds = false;
|
||||
// if (tokenPool === 'locked') {
|
||||
// hasEnoughFunds = await checkHasEnoughLockedTokens(amount);
|
||||
// }
|
||||
|
||||
if (tokenPool === 'balance') {
|
||||
const remainingBalance = +balance - +amount;
|
||||
hasEnoughFunds = remainingBalance >= 0;
|
||||
}
|
||||
|
||||
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 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));
|
||||
@@ -1,10 +1,12 @@
|
||||
import * as React from 'react';
|
||||
import React, { FC, useContext, useEffect, useState, useMemo } from 'react';
|
||||
import { Box, TextField, MenuItem, FormControl, Button } from '@mui/material';
|
||||
import Select, { SelectChangeEvent } from '@mui/material/Select';
|
||||
import { Filters } from './Filters/Filters';
|
||||
import { useIsMobile } from '../hooks/useIsMobile';
|
||||
import { useState } from 'react';
|
||||
import { DelegateModal } from './Delegations/DelegateModal';
|
||||
import { DelegateModal } from './Delegations/components/DelegateModal';
|
||||
import { ChainProvider } from '@cosmos-kit/react';
|
||||
import { assets, chains } from 'chain-registry';
|
||||
import { wallets as keplr } from '@cosmos-kit/keplr';
|
||||
|
||||
const fieldsHeight = '42.25px';
|
||||
|
||||
@@ -28,6 +30,32 @@ export const TableToolbar: FCWithChildren<TableToolBarProps> = ({
|
||||
withFilters,
|
||||
}) => {
|
||||
const [showNewDelegationModal, setShowNewDelegationModal] = useState<boolean>(false);
|
||||
const assetsFixedUp = useMemo(() => {
|
||||
const nyx = assets.find((a) => a.chain_name === 'nyx');
|
||||
if (nyx) {
|
||||
const nyxCoin = nyx.assets.find((a) => a.name === 'nyx');
|
||||
if (nyxCoin) {
|
||||
nyxCoin.coingecko_id = 'nyx';
|
||||
}
|
||||
nyx.assets = nyx.assets.reverse();
|
||||
}
|
||||
return assets;
|
||||
}, [assets]);
|
||||
|
||||
const chainsFixedUp = useMemo(() => {
|
||||
const nyx = chains.find((c) => c.chain_id === 'nyx');
|
||||
if (nyx) {
|
||||
if (!nyx.staking) {
|
||||
nyx.staking = {
|
||||
staking_tokens: [{ denom: 'unyx' }],
|
||||
lock_duration: {
|
||||
blocks: 10000,
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
return chains;
|
||||
}, [chains]);
|
||||
|
||||
const isMobile = useIsMobile();
|
||||
return (
|
||||
@@ -104,18 +132,27 @@ export const TableToolbar: FCWithChildren<TableToolBarProps> = ({
|
||||
</Box>
|
||||
|
||||
{showNewDelegationModal && (
|
||||
<DelegateModal
|
||||
open={showNewDelegationModal}
|
||||
onClose={() => setShowNewDelegationModal(false)}
|
||||
onOk={undefined}
|
||||
header="Delegate"
|
||||
buttonText="Delegate stake"
|
||||
denom={'nym'} // clientDetails?.display_mix_denom || 'nym'}
|
||||
accountBalance={'balance?.printable_balance'}
|
||||
rewardInterval="weekly"
|
||||
hasVestingContract={true}
|
||||
// {...storybookStyles(theme, isStorybook)}
|
||||
/>
|
||||
<ChainProvider
|
||||
chains={chainsFixedUp}
|
||||
assetLists={assetsFixedUp}
|
||||
wallets={[...keplr]}
|
||||
signerOptions={{
|
||||
preferredSignType: () => 'amino',
|
||||
}}
|
||||
>
|
||||
<DelegateModal
|
||||
open={showNewDelegationModal}
|
||||
onClose={() => setShowNewDelegationModal(false)}
|
||||
onOk={undefined}
|
||||
header="Delegate"
|
||||
buttonText="Delegate stake"
|
||||
denom={'nym'} // clientDetails?.display_mix_denom || 'nym'}
|
||||
// accountBalance={'balance?.printable_balance' || 'error reading balance'}
|
||||
rewardInterval="weekly"
|
||||
hasVestingContract={true}
|
||||
// {...storybookStyles(theme, isStorybook)}
|
||||
/>
|
||||
</ChainProvider>
|
||||
)}
|
||||
</Box>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user