Compare commits
19 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 61f8b1710a | |||
| 496b284bcd | |||
| 1f249a3386 | |||
| 8ca7c48094 | |||
| abbcfbb6c2 | |||
| c0cc019b97 | |||
| e8896352a1 | |||
| 278b2e1657 | |||
| 87437785f9 | |||
| 788a67e9f4 | |||
| 4f58a63cb6 | |||
| 35e3961f75 | |||
| 2dac633873 | |||
| 23d08b993c | |||
| 42b5472886 | |||
| 48071f11ab | |||
| 72b5aedad3 | |||
| 4fa5a1ad37 | |||
| bcc450eb9f |
@@ -19,6 +19,8 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@cosmjs/math": "^0.26.2",
|
||||
"@cosmos-kit/keplr": "^2.4.7",
|
||||
"@cosmos-kit/react": "^2.9.6",
|
||||
"@emotion/react": "^11.4.1",
|
||||
"@emotion/styled": "^11.3.0",
|
||||
"@mui/icons-material": "^5.0.0",
|
||||
@@ -27,13 +29,19 @@
|
||||
"@mui/system": "^5.0.1",
|
||||
"@mui/x-data-grid": "^5.0.0-beta.5",
|
||||
"@nymproject/mui-theme": "^1.0.0",
|
||||
"@nymproject/node-tester": "^1.0.0",
|
||||
"@nymproject/nym-validator-client": "^0.18.0",
|
||||
"@nymproject/react": "^1.0.0",
|
||||
"@nymproject/types": "^1.0.0",
|
||||
"@tauri-apps/api": "^1.5.1",
|
||||
"big.js": "^6.2.1",
|
||||
"bs58": "^5.0.0",
|
||||
"buffer": "^6.0.3",
|
||||
"chain-registry": "^1.19.0",
|
||||
"d3-scale": "^4.0.0",
|
||||
"date-fns": "^2.24.0",
|
||||
"lodash": "^4.17.21",
|
||||
"i18n-iso-countries": "^6.8.0",
|
||||
"lodash": "^4.17.21",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-error-boundary": "^3.1.4",
|
||||
|
||||
@@ -0,0 +1,120 @@
|
||||
import React from 'react';
|
||||
import { useChain } from '@cosmos-kit/react';
|
||||
import { Box, Button, Card, Typography, IconButton } from '@mui/material';
|
||||
import Big from 'big.js';
|
||||
import CloseIcon from '@mui/icons-material/Close';
|
||||
import { useTheme } from '@mui/material/styles';
|
||||
|
||||
import { useEffect, useState, useMemo } from 'react';
|
||||
|
||||
import '@interchain-ui/react/styles';
|
||||
import { TokenSVG } from '../icons/TokenSVG';
|
||||
import { ElipsSVG } from '../icons/ElipsSVG';
|
||||
|
||||
export function useIsClient() {
|
||||
const [isClient, setIsClient] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
setIsClient(true);
|
||||
}, []);
|
||||
|
||||
return isClient;
|
||||
}
|
||||
|
||||
export const uNYMtoNYM = (unym: string, rounding = 6) => {
|
||||
const nym = Big(unym).div(1000000).toFixed(rounding);
|
||||
|
||||
return {
|
||||
asString: () => {
|
||||
return nym;
|
||||
},
|
||||
asNumber: () => {
|
||||
return Number(nym);
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
export const trimAddress = (address = '', trimBy = 6) => {
|
||||
return `${address.slice(0, trimBy)}...${address.slice(-trimBy)}`;
|
||||
};
|
||||
|
||||
export default function ConnectKeplrWallet() {
|
||||
const {
|
||||
username,
|
||||
connect,
|
||||
disconnect,
|
||||
wallet,
|
||||
openView,
|
||||
address,
|
||||
getCosmWasmClient,
|
||||
isWalletConnected,
|
||||
isWalletConnecting,
|
||||
} = useChain('nyx');
|
||||
const isClient = useIsClient();
|
||||
const theme = useTheme();
|
||||
|
||||
const color = theme.palette.text.primary;
|
||||
|
||||
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]);
|
||||
|
||||
if (!isClient) return null;
|
||||
|
||||
const getGlobalbutton = () => {
|
||||
if (isWalletConnecting) {
|
||||
return <Button onClick={() => connect()}>{`Connecting ${wallet?.prettyName}`}</Button>;
|
||||
}
|
||||
if (isWalletConnected) {
|
||||
return (
|
||||
<Box display={'flex'} alignItems={'center'} gap={2}>
|
||||
<Box display={'flex'} alignItems={'center'} gap={1}>
|
||||
<TokenSVG />
|
||||
<Typography variant="body1" fontWeight={600}>
|
||||
{balance.data} NYM
|
||||
</Typography>
|
||||
</Box>
|
||||
<Box display={'flex'} alignItems={'center'} gap={1}>
|
||||
<ElipsSVG />
|
||||
<Typography variant="body1" fontWeight={600}>
|
||||
{trimAddress(address, 7)}
|
||||
</Typography>
|
||||
</Box>
|
||||
<IconButton
|
||||
onClick={async () => {
|
||||
await disconnect();
|
||||
// setGlobalStatus(WalletStatus.Disconnected);
|
||||
}}
|
||||
>
|
||||
<CloseIcon sx={{ color: 'white' }} />
|
||||
</IconButton>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
return <Button onClick={() => connect()}>Connect Wallet</Button>;
|
||||
};
|
||||
|
||||
return (
|
||||
<Box>
|
||||
<div className="flex justify-start space-x-5">{getGlobalbutton()}</div>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
@@ -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>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,83 @@
|
||||
import React from 'react';
|
||||
import {
|
||||
Breakpoint,
|
||||
Button,
|
||||
Paper,
|
||||
Dialog,
|
||||
DialogActions,
|
||||
DialogContent,
|
||||
DialogTitle,
|
||||
SxProps,
|
||||
Typography,
|
||||
} from '@mui/material';
|
||||
|
||||
export interface ConfirmationModalProps {
|
||||
open: boolean;
|
||||
onConfirm: () => void;
|
||||
onClose?: () => void;
|
||||
children?: React.ReactNode;
|
||||
title: React.ReactNode | string;
|
||||
subTitle?: React.ReactNode | string;
|
||||
confirmButton: React.ReactNode | string;
|
||||
disabled?: boolean;
|
||||
sx?: SxProps;
|
||||
fullWidth?: boolean;
|
||||
maxWidth?: Breakpoint;
|
||||
backdropProps?: object;
|
||||
}
|
||||
|
||||
export const ConfirmationModal = ({
|
||||
open,
|
||||
onConfirm,
|
||||
onClose,
|
||||
children,
|
||||
title,
|
||||
subTitle,
|
||||
confirmButton,
|
||||
disabled,
|
||||
sx,
|
||||
fullWidth,
|
||||
maxWidth,
|
||||
backdropProps,
|
||||
}: ConfirmationModalProps) => {
|
||||
const Title = (
|
||||
<DialogTitle id="responsive-dialog-title" sx={{ pb: 2 }}>
|
||||
{title}
|
||||
{subTitle &&
|
||||
(typeof subTitle === 'string' ? (
|
||||
<Typography fontWeight={400} variant="subtitle1" fontSize={12} color={'grey'}>
|
||||
{subTitle}
|
||||
</Typography>
|
||||
) : (
|
||||
subTitle
|
||||
))}
|
||||
</DialogTitle>
|
||||
);
|
||||
const ConfirmButton =
|
||||
typeof confirmButton === 'string' ? (
|
||||
<Button onClick={onConfirm} variant="contained" fullWidth disabled={disabled} sx={{ py: 1.6 }}>
|
||||
<Typography variant="button" fontSize="large">
|
||||
{confirmButton}
|
||||
</Typography>
|
||||
</Button>
|
||||
) : (
|
||||
confirmButton
|
||||
);
|
||||
return (
|
||||
<Dialog
|
||||
open={open}
|
||||
onClose={onClose}
|
||||
aria-labelledby="responsive-dialog-title"
|
||||
maxWidth={maxWidth || 'sm'}
|
||||
sx={{ textAlign: 'center', ...sx }}
|
||||
fullWidth={fullWidth}
|
||||
BackdropProps={backdropProps}
|
||||
PaperComponent={Paper}
|
||||
PaperProps={{ elevation: 0 }}
|
||||
>
|
||||
{Title}
|
||||
<DialogContent>{children}</DialogContent>
|
||||
<DialogActions sx={{ px: 3, pb: 3 }}>{ConfirmButton}</DialogActions>
|
||||
</Dialog>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,28 @@
|
||||
import * as React from 'react';
|
||||
import { useClipboard } from 'use-clipboard-copy';
|
||||
import ContentCopyIcon from '@mui/icons-material/ContentCopy';
|
||||
import DoneIcon from '@mui/icons-material/Done';
|
||||
import { Box, Tooltip } from '@mui/material';
|
||||
import { SxProps } from '@mui/system';
|
||||
import { DelegateSVG } from '../../../icons/DelevateSVG';
|
||||
import { useChain } from '@cosmos-kit/react';
|
||||
|
||||
export const DelegateIconButton: FCWithChildren<{
|
||||
tooltip?: React.ReactNode;
|
||||
onDelegate: () => void;
|
||||
sx?: SxProps;
|
||||
}> = ({ tooltip, onDelegate, sx }) => {
|
||||
const { address, getCosmWasmClient, isWalletConnected, getSigningCosmWasmClient } = useChain('nyx');
|
||||
console.log('isWalletConnected :>> ', isWalletConnected);
|
||||
|
||||
const handleDelegateClick = () => {
|
||||
onDelegate();
|
||||
};
|
||||
return (
|
||||
<Tooltip title={isWalletConnected ? undefined : 'Connect your wallet to delegate'}>
|
||||
<Box sx={sx} onClick={isWalletConnected ? handleDelegateClick : undefined}>
|
||||
<DelegateSVG />
|
||||
</Box>
|
||||
</Tooltip>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,288 @@
|
||||
import React, { useState, useEffect, ChangeEvent } from 'react';
|
||||
import { Box, Typography, SxProps, TextField } from '@mui/material';
|
||||
import { IdentityKeyFormField } from '@nymproject/react/mixnodes/IdentityKeyFormField';
|
||||
import { CurrencyFormField } from '@nymproject/react/currency/CurrencyFormField';
|
||||
import { CurrencyDenom, DecCoin } from '@nymproject/types';
|
||||
import { SimpleModal } from './SimpleModal';
|
||||
import { ModalListItem } from './ModalListItem';
|
||||
import { Console, urls, validateAmount } from '../utils';
|
||||
import { useChain } from '@cosmos-kit/react';
|
||||
import { StdFee } from '@cosmjs/amino';
|
||||
import { ExecuteResult } from '@cosmjs/cosmwasm-stargate';
|
||||
import { uNYMtoNYM } from '../utils';
|
||||
import { DelegationModalProps } from './DelegationModal';
|
||||
|
||||
const MIN_AMOUNT_TO_DELEGATE = 10;
|
||||
const MIXNET_CONTRACT_ADDRESS = 'n17srjznxl9dvzdkpwpw24gg668wc73val88a6m5ajg6ankwvz9wtst0cznr';
|
||||
// const sandboxContractAddress = 'n1xr3rq8yvd7qplsw5yx90ftsr2zdhg4e9z60h5duusgxpv72hud3sjkxkav';
|
||||
|
||||
export const DelegateModal: FCWithChildren<{
|
||||
open: boolean;
|
||||
onClose: () => void;
|
||||
onOk?: (delegationModalProps: DelegationModalProps) => void;
|
||||
initialIdentityKey?: string;
|
||||
initialMixId?: number;
|
||||
onIdentityKeyChanged?: (identityKey: string) => void;
|
||||
onAmountChanged?: (amount: string) => void;
|
||||
header?: string;
|
||||
buttonText?: string;
|
||||
rewardInterval?: string;
|
||||
// accountBalance?: string;
|
||||
estimatedReward?: number;
|
||||
profitMarginPercentage?: string | null;
|
||||
nodeUptimePercentage?: number | null;
|
||||
denom: CurrencyDenom;
|
||||
initialAmount?: string;
|
||||
hasVestingContract?: boolean;
|
||||
sx?: SxProps;
|
||||
backdropProps?: object;
|
||||
}> = ({
|
||||
open,
|
||||
onIdentityKeyChanged,
|
||||
onAmountChanged,
|
||||
onClose,
|
||||
onOk,
|
||||
header,
|
||||
buttonText,
|
||||
initialIdentityKey,
|
||||
initialMixId,
|
||||
// accountBalance,
|
||||
denom,
|
||||
sx,
|
||||
backdropProps,
|
||||
}) => {
|
||||
const [mixId, setMixId] = useState<number | undefined>(initialMixId);
|
||||
const [identityKey, setIdentityKey] = useState<string | undefined>(initialIdentityKey);
|
||||
const [amount, setAmount] = useState<DecCoin | undefined>();
|
||||
const [isValidated, setValidated] = useState<boolean>(false);
|
||||
const [errorAmount, setErrorAmount] = useState<string | undefined>();
|
||||
const [errorIdentityKey, setErrorIdentityKey] = useState<string>();
|
||||
const [mixIdError, setMixIdError] = useState<string>();
|
||||
const [cosmWasmSignerClient, setCosmWasmSignerClient] = useState<any>();
|
||||
const [balance, setBalance] = useState<{
|
||||
status: 'loading' | 'success';
|
||||
data?: string;
|
||||
}>({ status: 'loading', data: undefined });
|
||||
|
||||
const { address, getCosmWasmClient, isWalletConnected, getSigningCosmWasmClient } = useChain('nyx');
|
||||
|
||||
useEffect(() => {
|
||||
const getClient = async () => {
|
||||
await getSigningCosmWasmClient()
|
||||
.then((res) => {
|
||||
setCosmWasmSignerClient(res);
|
||||
console.log('res :>> ', res);
|
||||
})
|
||||
.catch((e) => console.log('e :>> ', e));
|
||||
};
|
||||
|
||||
isWalletConnected && getClient();
|
||||
}, [isWalletConnected]);
|
||||
|
||||
const getBalance = async (walletAddress: string) => {
|
||||
const account = await getCosmWasmClient();
|
||||
const uNYMBalance = await account.getBalance(walletAddress, 'unym');
|
||||
const NYMBalance = uNYMtoNYM(uNYMBalance.amount).asString();
|
||||
|
||||
setBalance({ status: 'success', data: NYMBalance });
|
||||
};
|
||||
useEffect(() => {
|
||||
if (address) {
|
||||
getBalance(address);
|
||||
}
|
||||
}, [address, getCosmWasmClient]);
|
||||
|
||||
const validate = async () => {
|
||||
let newValidatedValue = true;
|
||||
let errorAmountMessage;
|
||||
let errorIdentityKeyMessage;
|
||||
|
||||
if (!identityKey) {
|
||||
newValidatedValue = false;
|
||||
errorIdentityKeyMessage = 'Please enter a valid identity key';
|
||||
}
|
||||
|
||||
if (amount && !(await validateAmount(amount.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()}`;
|
||||
newValidatedValue = false;
|
||||
}
|
||||
|
||||
if (!amount?.amount.length) {
|
||||
newValidatedValue = false;
|
||||
}
|
||||
|
||||
if (!mixId) {
|
||||
newValidatedValue = false;
|
||||
}
|
||||
|
||||
if (amount && balance.data && +balance.data - +amount <= 0) {
|
||||
errorAmountMessage = 'Not enough funds';
|
||||
newValidatedValue = false;
|
||||
}
|
||||
|
||||
setErrorIdentityKey(errorIdentityKeyMessage);
|
||||
if (mixIdError && !errorIdentityKeyMessage) {
|
||||
setErrorIdentityKey(mixIdError);
|
||||
}
|
||||
setErrorAmount(errorAmountMessage);
|
||||
setValidated(newValidatedValue);
|
||||
};
|
||||
|
||||
const delegateToMixnode = async (
|
||||
{
|
||||
mixId,
|
||||
}: {
|
||||
mixId: number;
|
||||
},
|
||||
fee: number | StdFee | 'auto' = 'auto',
|
||||
memo?: string,
|
||||
funds?: DecCoin[],
|
||||
): Promise<ExecuteResult> => {
|
||||
const amount = (Number(funds![0].amount) * 1000000).toString();
|
||||
const uNymFunds = [{ amount: amount, denom: 'unym' }];
|
||||
console.log('cosmWasmSignerClient :>> ', cosmWasmSignerClient);
|
||||
return await cosmWasmSignerClient.execute(
|
||||
address,
|
||||
MIXNET_CONTRACT_ADDRESS,
|
||||
{
|
||||
delegate_to_mixnode: {
|
||||
mix_id: mixId,
|
||||
},
|
||||
},
|
||||
fee,
|
||||
memo,
|
||||
uNymFunds,
|
||||
);
|
||||
};
|
||||
|
||||
const handleConfirm = async () => {
|
||||
const memo: string = 'test delegation';
|
||||
const fee = { gas: '1000000', amount: [{ amount: '25000', denom: 'unym' }] };
|
||||
|
||||
if (mixId && amount && onOk && cosmWasmSignerClient) {
|
||||
onOk({
|
||||
status: 'loading',
|
||||
action: 'delegate',
|
||||
});
|
||||
try {
|
||||
const tx = await delegateToMixnode({ mixId }, fee, memo, [amount]);
|
||||
|
||||
onOk({
|
||||
status: 'success',
|
||||
action: 'delegate',
|
||||
message: 'This operation can take up to one hour to process',
|
||||
transactions: [
|
||||
{ url: `${urls('MAINNET').blockExplorer}/transaction/${tx.transactionHash}`, hash: tx.transactionHash },
|
||||
],
|
||||
});
|
||||
} catch (e) {
|
||||
Console.error('Failed to addDelegation', e);
|
||||
onOk({
|
||||
status: 'error',
|
||||
action: 'delegate',
|
||||
message: (e as Error).message,
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const handleIdentityKeyChanged = (newIdentityKey: string) => {
|
||||
setIdentityKey(newIdentityKey);
|
||||
|
||||
if (onIdentityKeyChanged) {
|
||||
onIdentityKeyChanged(newIdentityKey);
|
||||
}
|
||||
};
|
||||
|
||||
const handleMixIDChanged = (event: ChangeEvent<HTMLInputElement>) => {
|
||||
const newValue = event.target.value;
|
||||
setMixId(Number(newValue));
|
||||
};
|
||||
|
||||
const handleAmountChanged = (newAmount: DecCoin) => {
|
||||
setAmount(newAmount);
|
||||
|
||||
if (onAmountChanged) {
|
||||
onAmountChanged(newAmount.amount);
|
||||
}
|
||||
};
|
||||
|
||||
React.useEffect(() => {
|
||||
validate();
|
||||
}, [amount, identityKey, mixId]);
|
||||
|
||||
return (
|
||||
<SimpleModal
|
||||
open={open}
|
||||
onClose={onClose}
|
||||
// onOk={async () => {
|
||||
// if (mixId && amount) {
|
||||
// handleConfirm({ mixId, value: { amount, denom } });
|
||||
// }
|
||||
// }}
|
||||
onOk={async () => handleConfirm()}
|
||||
header={header || 'Delegate'}
|
||||
okLabel={buttonText || 'Delegate stake'}
|
||||
okDisabled={!isValidated}
|
||||
sx={sx}
|
||||
backdropProps={backdropProps}
|
||||
>
|
||||
<Box sx={{ mt: 3 }} gap={2}>
|
||||
<IdentityKeyFormField
|
||||
required
|
||||
fullWidth
|
||||
label="Node identity key"
|
||||
onChanged={handleIdentityKeyChanged}
|
||||
initialValue={identityKey}
|
||||
readOnly={Boolean(initialIdentityKey)}
|
||||
textFieldProps={{
|
||||
autoFocus: !initialIdentityKey,
|
||||
}}
|
||||
showTickOnValid={false}
|
||||
/>
|
||||
<Typography
|
||||
component="div"
|
||||
textAlign="left"
|
||||
variant="caption"
|
||||
sx={{ color: 'error.main', mx: 2, mt: errorIdentityKey && 1 }}
|
||||
>
|
||||
{errorIdentityKey}
|
||||
</Typography>
|
||||
</Box>
|
||||
<Box sx={{ mt: 3 }} gap={2}>
|
||||
<TextField
|
||||
fullWidth={true}
|
||||
required
|
||||
label={'MixID'}
|
||||
error={mixIdError !== undefined}
|
||||
helperText={mixIdError}
|
||||
onChange={handleMixIDChanged}
|
||||
InputLabelProps={{ shrink: true }}
|
||||
value={mixId?.toString() || ''}
|
||||
/>
|
||||
</Box>
|
||||
<Box display="flex" gap={2} alignItems="center" sx={{ mt: 3 }}>
|
||||
<CurrencyFormField
|
||||
required
|
||||
fullWidth
|
||||
label="Amount"
|
||||
// initialValue={amount}
|
||||
autoFocus={Boolean(initialIdentityKey)}
|
||||
onChanged={handleAmountChanged}
|
||||
denom={denom}
|
||||
validationError={errorAmount}
|
||||
/>
|
||||
</Box>
|
||||
<Box sx={{ mt: 3 }}>
|
||||
<ModalListItem label="Account balance" value={`${balance.data} NYM`} divider fontWeight={600} />
|
||||
</Box>
|
||||
|
||||
<ModalListItem label="Est. fee for this transaction will be calculated in the next page" />
|
||||
</SimpleModal>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,80 @@
|
||||
import React from 'react';
|
||||
import { Typography, SxProps, Stack } from '@mui/material';
|
||||
import { Link } from '@nymproject/react/link/Link';
|
||||
import { LoadingModal } from './LoadingModal';
|
||||
import { ConfirmationModal } from './ConfirmationModal';
|
||||
import { ErrorModal } from './ErrorModal';
|
||||
|
||||
export type ActionType = 'delegate' | 'undelegate' | 'redeem' | 'redeem-all' | 'compound';
|
||||
|
||||
const actionToHeader = (action: ActionType): string => {
|
||||
// eslint-disable-next-line default-case
|
||||
switch (action) {
|
||||
case 'redeem':
|
||||
return 'Rewards redeemed successfully';
|
||||
case 'redeem-all':
|
||||
return 'All rewards redeemed successfully';
|
||||
case 'delegate':
|
||||
return 'Delegation successful';
|
||||
case 'undelegate':
|
||||
return 'Undelegation successful';
|
||||
case 'compound':
|
||||
return 'Rewards compounded successfully';
|
||||
default:
|
||||
throw new Error('Unknown type');
|
||||
}
|
||||
};
|
||||
|
||||
export type DelegationModalProps = {
|
||||
status: 'loading' | 'success' | 'error';
|
||||
action: ActionType;
|
||||
message?: string;
|
||||
transactions?: {
|
||||
url: string;
|
||||
hash: string;
|
||||
}[];
|
||||
};
|
||||
|
||||
export const DelegationModal: FCWithChildren<
|
||||
DelegationModalProps & {
|
||||
open: boolean;
|
||||
onClose: () => void;
|
||||
sx?: SxProps;
|
||||
backdropProps?: object;
|
||||
children?: React.ReactNode;
|
||||
}
|
||||
> = ({ status, action, message, transactions, open, onClose, children, sx, backdropProps }) => {
|
||||
if (status === 'loading') return <LoadingModal sx={sx} backdropProps={backdropProps} />;
|
||||
|
||||
if (status === 'error') {
|
||||
return (
|
||||
<ErrorModal message={message} sx={sx} open={open} onClose={onClose}>
|
||||
{children}
|
||||
</ErrorModal>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<ConfirmationModal
|
||||
open={open}
|
||||
onConfirm={onClose || (() => {})}
|
||||
title={actionToHeader(action)}
|
||||
confirmButton="Done"
|
||||
>
|
||||
<Stack alignItems="center" spacing={2} mb={0}>
|
||||
{message && <Typography>{message}</Typography>}
|
||||
{transactions?.length === 1 && (
|
||||
<Link href={transactions[0].url} target="_blank" sx={{ ml: 1 }} text="View on blockchain" noIcon />
|
||||
)}
|
||||
{transactions && transactions.length > 1 && (
|
||||
<Stack alignItems="center" spacing={1}>
|
||||
<Typography>View the transactions on blockchain:</Typography>
|
||||
{transactions.map(({ url, hash }) => (
|
||||
<Link href={url} target="_blank" sx={{ ml: 1 }} text={hash.slice(0, 6)} key={hash} noIcon />
|
||||
))}
|
||||
</Stack>
|
||||
)}
|
||||
</Stack>
|
||||
</ConfirmationModal>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,28 @@
|
||||
import React from 'react';
|
||||
import { Box, Button, Modal, SxProps, Typography } from '@mui/material';
|
||||
import { modalStyle } from '../../../../../nym-wallet/src/components/Modals/styles';
|
||||
|
||||
export const ErrorModal: FCWithChildren<{
|
||||
open: boolean;
|
||||
title?: string;
|
||||
message?: string;
|
||||
sx?: SxProps;
|
||||
backdropProps?: object;
|
||||
onClose: () => void;
|
||||
children?: React.ReactNode;
|
||||
}> = ({ children, open, title, message, sx, backdropProps, onClose }) => (
|
||||
<Modal open={open} onClose={onClose} BackdropProps={backdropProps}>
|
||||
<Box sx={{ border: (t) => `1px solid #fff`, ...modalStyle, ...sx }} textAlign="center">
|
||||
<Typography color={(theme) => theme.palette.error.main} mb={1}>
|
||||
{title || 'Oh no! Something went wrong...'}
|
||||
</Typography>
|
||||
<Typography my={5} color="text.primary" sx={{ textOverflow: 'wrap', overflowWrap: 'break-word' }}>
|
||||
{message}
|
||||
</Typography>
|
||||
{children}
|
||||
<Button variant="contained" onClick={onClose}>
|
||||
Close
|
||||
</Button>
|
||||
</Box>
|
||||
</Modal>
|
||||
);
|
||||
@@ -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,29 @@
|
||||
import React from 'react';
|
||||
import { Box, CircularProgress, Modal, Stack, Typography, SxProps } from '@mui/material';
|
||||
|
||||
const modalStyle: SxProps = {
|
||||
position: 'absolute',
|
||||
top: '50%',
|
||||
left: '50%',
|
||||
transform: 'translate(-50%, -50%)',
|
||||
width: 500,
|
||||
bgcolor: 'background.paper',
|
||||
boxShadow: 24,
|
||||
borderRadius: '16px',
|
||||
p: 4,
|
||||
};
|
||||
|
||||
export const LoadingModal: FCWithChildren<{
|
||||
text?: string;
|
||||
sx?: SxProps;
|
||||
backdropProps?: object;
|
||||
}> = ({ sx, backdropProps, text = 'Please wait...' }) => (
|
||||
<Modal open BackdropProps={backdropProps}>
|
||||
<Box sx={{ border: (t) => `1px solid grey`, ...modalStyle, ...sx }} textAlign="center">
|
||||
<Stack spacing={4} direction="row" alignItems="center">
|
||||
<CircularProgress />
|
||||
<Typography sx={{ color: 'text.primary' }}>{text}</Typography>
|
||||
</Stack>
|
||||
</Box>
|
||||
</Modal>
|
||||
);
|
||||
@@ -0,0 +1,6 @@
|
||||
import React from 'react';
|
||||
import { Box, SxProps } from '@mui/material';
|
||||
|
||||
export const ModalDivider: FCWithChildren<{
|
||||
sx?: SxProps;
|
||||
}> = ({ sx }) => <Box borderTop="1px solid" borderColor="rgba(141, 147, 153, 0.2)" my={1} sx={sx} />;
|
||||
@@ -0,0 +1,35 @@
|
||||
import React from 'react';
|
||||
import { FeeDetails } from '@nymproject/types';
|
||||
import { CircularProgress } from '@mui/material';
|
||||
import { ModalListItem } from './ModalListItem';
|
||||
import { ModalDivider } from './ModalDivider';
|
||||
|
||||
type TFeeProps = { fee?: FeeDetails; isLoading: boolean; error?: string; divider?: boolean };
|
||||
type TTotalAmountProps = { fee?: FeeDetails; amount?: string; isLoading: boolean; error?: string; divider?: boolean };
|
||||
|
||||
const getValue = ({ fee, amount, isLoading, error }: TTotalAmountProps) => {
|
||||
if (isLoading) return <CircularProgress size={15} />;
|
||||
if (error && !isLoading) return 'n/a';
|
||||
if (fee) {
|
||||
const numericFee = Number(fee.amount?.amount);
|
||||
const numericAmountToTransfer = Number(amount);
|
||||
return amount
|
||||
? `${numericFee + numericAmountToTransfer} ${fee.amount?.denom.toUpperCase()}`
|
||||
: `${fee.amount?.amount} ${fee.amount?.denom.toUpperCase()}`;
|
||||
}
|
||||
return '-';
|
||||
};
|
||||
|
||||
export const ModalFee = ({ fee, isLoading, error, divider }: TFeeProps) => (
|
||||
<>
|
||||
<ModalListItem label="Fee for this transaction" value={getValue({ fee, isLoading, error })} />
|
||||
{divider && <ModalDivider />}
|
||||
</>
|
||||
);
|
||||
|
||||
export const ModalTotalAmount = ({ fee, amount, isLoading, error, divider }: TTotalAmountProps) => (
|
||||
<>
|
||||
<ModalListItem label="Total amount" value={getValue({ fee, amount, isLoading, error })} fontWeight={600} />
|
||||
{divider && <ModalDivider />}
|
||||
</>
|
||||
);
|
||||
@@ -0,0 +1,32 @@
|
||||
import React from 'react';
|
||||
import { Box, Stack, SxProps, Typography, TypographyProps } from '@mui/material';
|
||||
import { ModalDivider } from '../../../../../nym-wallet/src/components/Modals/ModalDivider';
|
||||
|
||||
export const ModalListItem: FCWithChildren<{
|
||||
label: string;
|
||||
divider?: boolean;
|
||||
hidden?: boolean;
|
||||
fontWeight?: TypographyProps['fontWeight'];
|
||||
fontSize?: TypographyProps['fontSize'];
|
||||
light?: boolean;
|
||||
value?: React.ReactNode;
|
||||
sxValue?: SxProps;
|
||||
}> = ({ label, value, hidden, fontWeight, fontSize, divider, sxValue }) => (
|
||||
<Box sx={{ display: hidden ? 'none' : 'block' }}>
|
||||
<Stack direction="row" justifyContent="space-between" alignItems="center">
|
||||
<Typography fontSize="smaller" fontWeight={fontWeight} sx={{ color: 'text.primary', fontSize: 14 }}>
|
||||
{label}
|
||||
</Typography>
|
||||
{value && (
|
||||
<Typography
|
||||
fontSize="smaller"
|
||||
fontWeight={fontWeight}
|
||||
sx={{ color: 'text.primary', fontSize: fontSize || 14, ...sxValue }}
|
||||
>
|
||||
{value}
|
||||
</Typography>
|
||||
)}
|
||||
</Stack>
|
||||
{divider && <ModalDivider />}
|
||||
</Box>
|
||||
);
|
||||
@@ -0,0 +1,106 @@
|
||||
import React from 'react';
|
||||
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 ArrowBackIosNewIcon from '@mui/icons-material/ArrowBackIosNew';
|
||||
|
||||
export const StyledBackButton = ({
|
||||
onBack,
|
||||
label,
|
||||
fullWidth,
|
||||
sx,
|
||||
}: {
|
||||
onBack: () => void;
|
||||
label?: string;
|
||||
fullWidth?: boolean;
|
||||
sx?: SxProps;
|
||||
}) => (
|
||||
<Button disableFocusRipple size="large" fullWidth={fullWidth} variant="outlined" onClick={onBack} sx={sx}>
|
||||
{label || <ArrowBackIosNewIcon fontSize="small" />}
|
||||
</Button>
|
||||
);
|
||||
|
||||
export const SimpleModal: FCWithChildren<{
|
||||
open: boolean;
|
||||
hideCloseIcon?: boolean;
|
||||
displayErrorIcon?: boolean;
|
||||
displayInfoIcon?: boolean;
|
||||
headerStyles?: SxProps;
|
||||
subHeaderStyles?: SxProps;
|
||||
buttonFullWidth?: boolean;
|
||||
onClose?: () => void;
|
||||
onOk?: () => Promise<void>;
|
||||
onBack?: () => void;
|
||||
header: string | React.ReactNode;
|
||||
subHeader?: string;
|
||||
okLabel: string;
|
||||
backLabel?: string;
|
||||
backButtonFullWidth?: boolean;
|
||||
okDisabled?: boolean;
|
||||
sx?: SxProps;
|
||||
backdropProps?: object;
|
||||
children?: React.ReactNode;
|
||||
}> = ({
|
||||
open,
|
||||
hideCloseIcon,
|
||||
displayErrorIcon,
|
||||
displayInfoIcon,
|
||||
headerStyles,
|
||||
subHeaderStyles,
|
||||
buttonFullWidth,
|
||||
onClose,
|
||||
okDisabled,
|
||||
onOk,
|
||||
onBack,
|
||||
header,
|
||||
subHeader,
|
||||
okLabel,
|
||||
backLabel,
|
||||
backButtonFullWidth,
|
||||
sx,
|
||||
children,
|
||||
backdropProps,
|
||||
}) => (
|
||||
<Modal open={open} onClose={onClose} BackdropProps={backdropProps}>
|
||||
<Box sx={{ border: (t) => `1px solid #fff`, ...modalStyle, ...sx }}>
|
||||
{displayErrorIcon && <ErrorOutline color="error" sx={{ mb: 3 }} />}
|
||||
{displayInfoIcon && <InfoOutlinedIcon sx={{ mb: 2, color: 'blue' }} />}
|
||||
<Stack direction="row" justifyContent="space-between" alignItems="center">
|
||||
{typeof header === 'string' ? (
|
||||
<Typography fontSize={20} fontWeight={600} sx={{ color: 'text.primary', ...headerStyles }}>
|
||||
{header}
|
||||
</Typography>
|
||||
) : (
|
||||
header
|
||||
)}
|
||||
{!hideCloseIcon && <CloseIcon onClick={onClose} cursor="pointer" />}
|
||||
</Stack>
|
||||
|
||||
<Typography
|
||||
mt={subHeader ? 0.5 : 0}
|
||||
mb={3}
|
||||
fontSize={12}
|
||||
color={(theme) => theme.palette.text.secondary}
|
||||
// sx={{ color: (theme) => theme.palette.nym.nymWallet.text.muted, ...subHeaderStyles }}
|
||||
>
|
||||
{subHeader}
|
||||
</Typography>
|
||||
|
||||
{children}
|
||||
|
||||
{(onOk || onBack) && (
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 2, mt: 2, width: buttonFullWidth ? '100%' : null }}>
|
||||
{onBack && <StyledBackButton onBack={onBack} label={backLabel} fullWidth={backButtonFullWidth} />}
|
||||
{onOk && (
|
||||
<Button variant="contained" fullWidth size="large" onClick={onOk} disabled={okDisabled}>
|
||||
{okLabel}
|
||||
</Button>
|
||||
)}
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
</Modal>
|
||||
);
|
||||
@@ -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,268 @@
|
||||
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';
|
||||
import { Network } from '../types';
|
||||
|
||||
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 };
|
||||
};
|
||||
|
||||
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`,
|
||||
};
|
||||
@@ -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));
|
||||
@@ -16,6 +16,7 @@ export const MixNodeDetailSection: FCWithChildren<MixNodeDetailProps> = ({ mixNo
|
||||
const palette = [theme.palette.text.primary];
|
||||
const isMobile = useIsMobile();
|
||||
const statusText = React.useMemo(() => getMixNodeStatusText(mixNodeRow.status), [mixNodeRow.status]);
|
||||
console.log('mixNodeRow :>> ', mixNodeRow);
|
||||
return (
|
||||
<Grid container>
|
||||
<Grid item xs={12} md={6}>
|
||||
|
||||
@@ -24,6 +24,13 @@ import { Socials } from './Socials';
|
||||
import { Footer } from './Footer';
|
||||
import { DarkLightSwitchDesktop } from './Switch';
|
||||
import { NavOptionType } from '../context/nav';
|
||||
import ConnectKeplrWallet from './ConnectKeplrWallet';
|
||||
import { assets, chains } from 'chain-registry';
|
||||
import { ChainProvider } from '@cosmos-kit/react';
|
||||
import { wallets as keplr } from '@cosmos-kit/keplr';
|
||||
import { useMemo } from 'react';
|
||||
|
||||
import '@interchain-ui/react/styles';
|
||||
|
||||
const drawerWidth = 255;
|
||||
const bannerHeight = 80;
|
||||
@@ -272,6 +279,33 @@ export const Nav: FCWithChildren = ({ children }) => {
|
||||
}
|
||||
};
|
||||
|
||||
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]);
|
||||
|
||||
return (
|
||||
<Box sx={{ display: 'flex' }}>
|
||||
<AppBar
|
||||
@@ -341,6 +375,16 @@ export const Nav: FCWithChildren = ({ children }) => {
|
||||
alignItems: 'center',
|
||||
}}
|
||||
>
|
||||
<ChainProvider
|
||||
chains={chainsFixedUp}
|
||||
assetLists={assetsFixedUp}
|
||||
wallets={[...keplr]}
|
||||
signerOptions={{
|
||||
preferredSignType: () => 'amino',
|
||||
}}
|
||||
>
|
||||
<ConnectKeplrWallet />
|
||||
</ChainProvider>
|
||||
<Socials />
|
||||
<DarkLightSwitchDesktop defaultChecked />
|
||||
</Box>
|
||||
|
||||
@@ -1,8 +1,13 @@
|
||||
import * as React from 'react';
|
||||
import { Box, TextField, MenuItem, FormControl } from '@mui/material';
|
||||
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 { 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';
|
||||
import { DelegationModal } from './Delegations/components/DelegationModal';
|
||||
|
||||
const fieldsHeight = '42.25px';
|
||||
|
||||
@@ -15,6 +20,17 @@ type TableToolBarProps = {
|
||||
childrenBefore?: React.ReactNode;
|
||||
childrenAfter?: React.ReactNode;
|
||||
};
|
||||
type ActionType = 'delegate' | 'undelegate' | 'redeem' | 'redeem-all' | 'compound';
|
||||
|
||||
type DelegationModalProps = {
|
||||
status: 'loading' | 'success' | 'error';
|
||||
action: ActionType;
|
||||
message?: string;
|
||||
transactions?: {
|
||||
url: string;
|
||||
hash: string;
|
||||
}[];
|
||||
};
|
||||
|
||||
export const TableToolbar: FCWithChildren<TableToolBarProps> = ({
|
||||
searchTerm,
|
||||
@@ -25,6 +41,42 @@ export const TableToolbar: FCWithChildren<TableToolBarProps> = ({
|
||||
childrenAfter,
|
||||
withFilters,
|
||||
}) => {
|
||||
const [showNewDelegationModal, setShowNewDelegationModal] = useState<boolean>(false);
|
||||
const [confirmationModalProps, setConfirmationModalProps] = useState<DelegationModalProps | undefined>();
|
||||
|
||||
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,
|
||||
},
|
||||
};
|
||||
if (nyx.apis) nyx.apis.rpc = [{ address: 'https://rpc.nymtech.net', provider: 'nym' }];
|
||||
}
|
||||
}
|
||||
return chains;
|
||||
}, [chains]);
|
||||
|
||||
const handleNewDelegation = (delegationModalProps: DelegationModalProps) => {
|
||||
setShowNewDelegationModal(false);
|
||||
setConfirmationModalProps(delegationModalProps);
|
||||
};
|
||||
|
||||
const isMobile = useIsMobile();
|
||||
return (
|
||||
<Box
|
||||
@@ -77,6 +129,7 @@ export const TableToolbar: FCWithChildren<TableToolBarProps> = ({
|
||||
/>
|
||||
)}
|
||||
</Box>
|
||||
|
||||
<Box
|
||||
sx={{
|
||||
display: 'flex',
|
||||
@@ -86,9 +139,50 @@ export const TableToolbar: FCWithChildren<TableToolBarProps> = ({
|
||||
marginTop: isMobile ? 2 : 0,
|
||||
}}
|
||||
>
|
||||
<Button
|
||||
size="large"
|
||||
variant="contained"
|
||||
disableElevation
|
||||
onClick={() => setShowNewDelegationModal(true)}
|
||||
sx={{ px: 5, color: 'primary.contrastText' }}
|
||||
>
|
||||
Delegate
|
||||
</Button>
|
||||
{withFilters && <Filters />}
|
||||
{childrenAfter}
|
||||
</Box>
|
||||
|
||||
{showNewDelegationModal && (
|
||||
<ChainProvider
|
||||
chains={chainsFixedUp}
|
||||
assetLists={assetsFixedUp}
|
||||
wallets={[...keplr]}
|
||||
signerOptions={{
|
||||
preferredSignType: () => 'amino',
|
||||
}}
|
||||
>
|
||||
<DelegateModal
|
||||
open={showNewDelegationModal}
|
||||
onClose={() => setShowNewDelegationModal(false)}
|
||||
header="Delegate"
|
||||
buttonText="Delegate stake"
|
||||
denom={'nym'} // clientDetails?.display_mix_denom || 'nym'}
|
||||
onOk={(delegationModalProps: DelegationModalProps) => handleNewDelegation(delegationModalProps)}
|
||||
// accountBalance={balance?.printable_balance}
|
||||
/>
|
||||
</ChainProvider>
|
||||
)}
|
||||
|
||||
{confirmationModalProps && (
|
||||
<DelegationModal
|
||||
{...confirmationModalProps}
|
||||
open={Boolean(confirmationModalProps)}
|
||||
onClose={async () => {
|
||||
setConfirmationModalProps(undefined);
|
||||
// await fetchBalance();
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
import * as React from 'react';
|
||||
import { useTheme } from '@mui/material/styles';
|
||||
|
||||
export const DelegateSVG: FCWithChildren = () => {
|
||||
const theme = useTheme();
|
||||
const color = theme.palette.text.primary;
|
||||
|
||||
return (
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16" fill="none">
|
||||
<path
|
||||
d="M2.6665 7.99967V9.99967H3.99984V7.99967H2.6665ZM10.6665 4.66634L9.7265 3.72634L8.6665 4.77967V1.33301H7.33317V4.79301L6.25984 3.73967L5.33317 4.66634L7.99984 7.33301L10.6665 4.66634ZM2.6665 11.333H13.3332V9.99967H2.6665V11.333Z"
|
||||
fill="white"
|
||||
/>
|
||||
<path
|
||||
d="M13.3332 13.6663C13.3332 14.2186 12.8855 14.6663 12.3332 14.6663H3.6665C3.11422 14.6663 2.6665 14.2186 2.6665 13.6663V13.333H13.3332V13.6663Z"
|
||||
fill="white"
|
||||
/>
|
||||
<rect x="12" y="8" width="1.33333" height="2" fill="white" />
|
||||
<rect x="12" y="11.333" width="1.33333" height="2" fill="white" />
|
||||
<rect x="2.6665" y="11.333" width="1.33333" height="2" fill="white" />
|
||||
</svg>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,26 @@
|
||||
import * as React from 'react';
|
||||
import { useTheme } from '@mui/material/styles';
|
||||
|
||||
export const ElipsSVG: FCWithChildren = () => {
|
||||
const theme = useTheme();
|
||||
const color = theme.palette.text.primary;
|
||||
|
||||
return (
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="25" viewBox="0 0 24 25" fill="none">
|
||||
<circle cx="12" cy="12.5" r="12" fill="url(#paint0_angular_2549_7570)" />
|
||||
<defs>
|
||||
<radialGradient
|
||||
id="paint0_angular_2549_7570"
|
||||
cx="0"
|
||||
cy="0"
|
||||
r="1"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
gradientTransform="translate(12 12.5) rotate(90) scale(12)"
|
||||
>
|
||||
<stop stop-color="#22D27E" />
|
||||
<stop offset="1" stop-color="#9002FF" />
|
||||
</radialGradient>
|
||||
</defs>
|
||||
</svg>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,27 @@
|
||||
import * as React from 'react';
|
||||
import { useTheme } from '@mui/material/styles';
|
||||
|
||||
export const TokenSVG: FCWithChildren = () => {
|
||||
const theme = useTheme();
|
||||
const color = 'white';
|
||||
|
||||
return (
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="25" viewBox="0 0 24 25" fill="none">
|
||||
<g clip-path="url(#clip0_2549_7563)">
|
||||
<path
|
||||
d="M20.4841 4.01607C15.8041 -0.67593 8.19607 -0.67593 3.51607 4.01607C-1.17593 8.70807 -1.17593 16.3041 3.51607 20.9841C8.20807 25.6761 15.8041 25.6761 20.4841 20.9841C25.1761 16.3041 25.1761 8.69607 20.4841 4.01607ZM19.4521 19.9521C15.3361 24.0681 8.65207 24.0681 4.53607 19.9521C0.42007 15.8361 0.42007 9.15207 4.53607 5.03607C8.65207 0.92007 15.3361 0.92007 19.4521 5.03607C23.5801 9.16407 23.5801 15.8361 19.4521 19.9521Z"
|
||||
fill={color}
|
||||
/>
|
||||
<path
|
||||
d="M18.48 19.4965V5.50447C17.868 4.92847 17.184 4.42447 16.452 4.02847V17.4085L7.62002 3.98047C6.85202 4.38847 6.14402 4.89247 5.52002 5.49247V19.4965C6.13202 20.0725 6.81602 20.5765 7.54802 20.9725V7.59247L16.38 21.0205C17.148 20.6125 17.856 20.0965 18.48 19.4965Z"
|
||||
fill={color}
|
||||
/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_2549_7563">
|
||||
<rect width="24" height="24" fill="white" transform="translate(0 0.5)" />
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
);
|
||||
};
|
||||
@@ -19,6 +19,13 @@ import { splice } from '../../utils';
|
||||
import { getMixNodeStatusColor } from '../../components/MixNodes/Status';
|
||||
import { MixNodeStatusDropdown } from '../../components/MixNodes/StatusDropdown';
|
||||
import { Tooltip } from '../../components/Tooltip';
|
||||
import { DelegateIconButton } from '../../components/Delegations/components/DelegateIconButton';
|
||||
import { ChainProvider } from '@cosmos-kit/react';
|
||||
import { assets, chains } from 'chain-registry';
|
||||
import { wallets as keplr } from '@cosmos-kit/keplr';
|
||||
import { DelegationModal, DelegationModalProps } from '../../components/Delegations/components/DelegationModal';
|
||||
import { useMemo, useState } from 'react';
|
||||
import { DelegateModal } from '../../components/Delegations/components/DelegateModal';
|
||||
|
||||
const getCellFontStyle = (theme: Theme, row: MixnodeRowType, textColor?: string) => {
|
||||
const color = textColor || getMixNodeStatusColor(theme, row.status);
|
||||
@@ -40,10 +47,59 @@ export const PageMixnodes: FCWithChildren = () => {
|
||||
const [filteredMixnodes, setFilteredMixnodes] = React.useState<MixNodeResponse>([]);
|
||||
const [pageSize, setPageSize] = React.useState<string>('10');
|
||||
const [searchTerm, setSearchTerm] = React.useState<string>('');
|
||||
const [mixId, setMixId] = useState<number | undefined>();
|
||||
const [identityKey, setIdentityKey] = useState<string | undefined>();
|
||||
const [showNewDelegationModal, setShowNewDelegationModal] = useState<boolean>(false);
|
||||
const [confirmationModalProps, setConfirmationModalProps] = useState<DelegationModalProps | undefined>();
|
||||
const theme = useTheme();
|
||||
const { status } = useParams<{ status: MixnodeStatusWithAll | undefined }>();
|
||||
const navigate = useNavigate();
|
||||
|
||||
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,
|
||||
},
|
||||
};
|
||||
if (nyx.apis) nyx.apis.rpc = [{ address: 'https://rpc.nymtech.net', provider: 'nym' }];
|
||||
}
|
||||
}
|
||||
return chains;
|
||||
}, [chains]);
|
||||
3;
|
||||
|
||||
const openDelegationModal = (identityKey: string, mixId: number) => {
|
||||
setMixId(mixId);
|
||||
setIdentityKey(identityKey);
|
||||
};
|
||||
|
||||
React.useEffect(() => {
|
||||
if (identityKey && mixId) {
|
||||
setShowNewDelegationModal(true);
|
||||
}
|
||||
}, [identityKey, mixId]);
|
||||
|
||||
const handleNewDelegation = (delegationModalProps: DelegationModalProps) => {
|
||||
setShowNewDelegationModal(false);
|
||||
setConfirmationModalProps(delegationModalProps);
|
||||
};
|
||||
|
||||
const handleSearch = (str: string) => {
|
||||
setSearchTerm(str.toLowerCase());
|
||||
};
|
||||
@@ -88,7 +144,7 @@ export const PageMixnodes: FCWithChildren = () => {
|
||||
disableColumnMenu: true,
|
||||
renderHeader: () => <CustomColumnHeading headingTitle="Mix ID" />,
|
||||
headerClassName: 'MuiDataGrid-header-override',
|
||||
width: 100,
|
||||
width: 70,
|
||||
headerAlign: 'left',
|
||||
renderCell: (params: GridRenderCellParams) => (
|
||||
<MuiLink
|
||||
@@ -107,10 +163,27 @@ export const PageMixnodes: FCWithChildren = () => {
|
||||
disableColumnMenu: true,
|
||||
renderHeader: () => <CustomColumnHeading headingTitle="Identity Key" />,
|
||||
headerClassName: 'MuiDataGrid-header-override',
|
||||
width: 170,
|
||||
width: 190,
|
||||
headerAlign: 'left',
|
||||
renderCell: (params: GridRenderCellParams) => (
|
||||
<>
|
||||
{/* <ChainProvider
|
||||
chains={chainsFixedUp}
|
||||
assetLists={assetsFixedUp}
|
||||
wallets={[...keplr]}
|
||||
signerOptions={{
|
||||
preferredSignType: () => 'amino',
|
||||
}}
|
||||
>
|
||||
<DelegateIconButton
|
||||
sx={{ ...getCellFontStyle(theme, params.row), marginRight: 1 }}
|
||||
// tooltip={walletConnected ? undefined : 'Connect your wallet to delegate'}
|
||||
onDelegate={() => {
|
||||
openDelegationModal(params.value, params.row.mix_id);
|
||||
console.log('object :>> ', params.value, params.row.mix_id);
|
||||
}}
|
||||
/>
|
||||
</ChainProvider> */}
|
||||
<CopyToClipboard
|
||||
sx={{ ...getCellFontStyle(theme, params.row), mr: 1 }}
|
||||
value={params.value}
|
||||
@@ -363,6 +436,40 @@ export const PageMixnodes: FCWithChildren = () => {
|
||||
</Card>
|
||||
</Grid>
|
||||
</Grid>
|
||||
|
||||
{showNewDelegationModal && (
|
||||
<ChainProvider
|
||||
chains={chainsFixedUp}
|
||||
assetLists={assetsFixedUp}
|
||||
wallets={[...keplr]}
|
||||
signerOptions={{
|
||||
preferredSignType: () => 'amino',
|
||||
}}
|
||||
>
|
||||
<DelegateModal
|
||||
open={showNewDelegationModal}
|
||||
onClose={() => setShowNewDelegationModal(false)}
|
||||
header="Delegate"
|
||||
buttonText="Delegate stake"
|
||||
denom={'nym'} // clientDetails?.display_mix_denom || 'nym'}
|
||||
onOk={(delegationModalProps: DelegationModalProps) => handleNewDelegation(delegationModalProps)}
|
||||
// accountBalance={balance?.printable_balance}
|
||||
initialIdentityKey={identityKey}
|
||||
initialMixId={mixId}
|
||||
/>
|
||||
</ChainProvider>
|
||||
)}
|
||||
|
||||
{confirmationModalProps && (
|
||||
<DelegationModal
|
||||
{...confirmationModalProps}
|
||||
open={Boolean(confirmationModalProps)}
|
||||
onClose={async () => {
|
||||
setConfirmationModalProps(undefined);
|
||||
// await fetchBalance();
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -2,7 +2,8 @@
|
||||
"extends": "../ts-packages/tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"jsx": "react-jsx",
|
||||
"outDir": "./dist"
|
||||
"outDir": "./dist",
|
||||
"module": "CommonJS",
|
||||
},
|
||||
"include": [
|
||||
"./src/**/*.ts",
|
||||
|
||||
@@ -36,6 +36,9 @@ module.exports = mergeWithRules({
|
||||
|
||||
// this can be included automatically by the dev server, however build mode fails if missing
|
||||
new webpack.HotModuleReplacementPlugin(),
|
||||
new webpack.ProvidePlugin({
|
||||
Buffer: ['buffer', 'Buffer'],
|
||||
}),
|
||||
],
|
||||
|
||||
target: 'web',
|
||||
|
||||
+22434
File diff suppressed because it is too large
Load Diff
@@ -25,6 +25,7 @@ export const Mixnodes = () => {
|
||||
setBusy(true);
|
||||
const client = await getClient();
|
||||
const { nodes } = await client.getMixNodesDetailed({});
|
||||
|
||||
setMixnodes(nodes);
|
||||
setBusy(false);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user