Compare commits
22 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| ebd986fae2 | |||
| 82b3db8c3b | |||
| 498aedf15a | |||
| d1bac9a1f3 | |||
| c9b790d3f6 | |||
| 791425014a | |||
| a7c98733ff | |||
| f737cb3bb4 | |||
| f6931d1500 | |||
| 7391463cae | |||
| 30ce58e4b1 | |||
| efd300f8fe | |||
| 07edf1a626 | |||
| 2b2b04f147 | |||
| 094b2db7f7 | |||
| 8ed312fe1c | |||
| 6c6e5d98b7 | |||
| 4fe3ac7da5 | |||
| d7e4969a3e | |||
| 415bfd66aa | |||
| 9af6f824d4 | |||
| ccfb98bdae |
@@ -1,4 +1,4 @@
|
|||||||
import React from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
import { useNavigate } from 'react-router-dom';
|
import { useNavigate } from 'react-router-dom';
|
||||||
import { Box, Button, Chip, Stack, Tooltip, Typography } from '@mui/material';
|
import { Box, Button, Chip, Stack, Tooltip, Typography } from '@mui/material';
|
||||||
import { Link } from '@nymproject/react/link/Link';
|
import { Link } from '@nymproject/react/link/Link';
|
||||||
@@ -11,6 +11,9 @@ import { Node as NodeIcon } from '../../svg-icons/node';
|
|||||||
import { Cell, Header, NodeTable } from './NodeTable';
|
import { Cell, Header, NodeTable } from './NodeTable';
|
||||||
import { BondedMixnodeActions, TBondedMixnodeActions } from './BondedMixnodeActions';
|
import { BondedMixnodeActions, TBondedMixnodeActions } from './BondedMixnodeActions';
|
||||||
import { NodeStats } from './NodeStats';
|
import { NodeStats } from './NodeStats';
|
||||||
|
import { getIntervalAsDate } from 'src/utils';
|
||||||
|
|
||||||
|
const textWhenNotName = 'This node has not yet set a name';
|
||||||
|
|
||||||
const headers: Header[] = [
|
const headers: Header[] = [
|
||||||
{
|
{
|
||||||
@@ -63,6 +66,7 @@ export const BondedMixnode = ({
|
|||||||
network?: Network;
|
network?: Network;
|
||||||
onActionSelect: (action: TBondedMixnodeActions) => void;
|
onActionSelect: (action: TBondedMixnodeActions) => void;
|
||||||
}) => {
|
}) => {
|
||||||
|
const [nextEpoch, setNextEpoch] = useState<string | Error>();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const {
|
const {
|
||||||
name,
|
name,
|
||||||
@@ -78,6 +82,15 @@ export const BondedMixnode = ({
|
|||||||
identityKey,
|
identityKey,
|
||||||
host,
|
host,
|
||||||
} = mixnode;
|
} = mixnode;
|
||||||
|
|
||||||
|
const getNextInterval = async () => {
|
||||||
|
try {
|
||||||
|
const { nextEpoch } = await getIntervalAsDate();
|
||||||
|
setNextEpoch(nextEpoch);
|
||||||
|
} catch {
|
||||||
|
setNextEpoch(Error());
|
||||||
|
}
|
||||||
|
};
|
||||||
const cells: Cell[] = [
|
const cells: Cell[] = [
|
||||||
{
|
{
|
||||||
cell: `${stake.amount} ${stake.denom}`,
|
cell: `${stake.amount} ${stake.denom}`,
|
||||||
@@ -121,6 +134,10 @@ export const BondedMixnode = ({
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
getNextInterval();
|
||||||
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Stack gap={2}>
|
<Stack gap={2}>
|
||||||
<NymCard
|
<NymCard
|
||||||
@@ -133,19 +150,24 @@ export const BondedMixnode = ({
|
|||||||
</Typography>
|
</Typography>
|
||||||
<NodeStatus status={status} />
|
<NodeStatus status={status} />
|
||||||
</Box>
|
</Box>
|
||||||
{name && (
|
{name?.includes(textWhenNotName) ? null : (
|
||||||
<Tooltip title={host} arrow>
|
|
||||||
<Typography fontWeight="regular" variant="h6" width="fit-content">
|
<Typography fontWeight="regular" variant="h6" width="fit-content">
|
||||||
{name}
|
{name}
|
||||||
</Typography>
|
</Typography>
|
||||||
</Tooltip>
|
|
||||||
)}
|
)}
|
||||||
|
<Tooltip title={host} placement="top" arrow>
|
||||||
|
<Box width="fit-content">
|
||||||
<IdentityKey identityKey={identityKey} />
|
<IdentityKey identityKey={identityKey} />
|
||||||
|
</Box>
|
||||||
|
</Tooltip>
|
||||||
</Stack>
|
</Stack>
|
||||||
}
|
}
|
||||||
Action={
|
Action={
|
||||||
isMixnode(mixnode) && (
|
<Box display="flex" flexDirection="column" alignItems="flex-end" justifyContent="space-between" height={70}>
|
||||||
<Tooltip title={mixnode.isUnbonding ? 'You have a pending unbond event. Node settings are disabled.' : ''}>
|
{isMixnode(mixnode) && (
|
||||||
|
<Tooltip
|
||||||
|
title={mixnode.isUnbonding ? 'You have a pending unbond event. Node settings are disabled.' : ''}
|
||||||
|
>
|
||||||
<Box>
|
<Box>
|
||||||
<Button
|
<Button
|
||||||
variant="text"
|
variant="text"
|
||||||
@@ -158,7 +180,13 @@ export const BondedMixnode = ({
|
|||||||
</Button>
|
</Button>
|
||||||
</Box>
|
</Box>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
)
|
)}
|
||||||
|
{nextEpoch instanceof Error ? null : (
|
||||||
|
<Typography fontSize={14} marginRight={1}>
|
||||||
|
Next epoch starts at <b>{nextEpoch}</b>
|
||||||
|
</Typography>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<NodeTable headers={headers} cells={cells} />
|
<NodeTable headers={headers} cells={cells} />
|
||||||
|
|||||||
@@ -29,15 +29,18 @@ export const DelegationItem = ({
|
|||||||
}) => {
|
}) => {
|
||||||
const operatingCost = isDelegation(item) && item.cost_params?.interval_operating_cost;
|
const operatingCost = isDelegation(item) && item.cost_params?.interval_operating_cost;
|
||||||
|
|
||||||
return (
|
const tooltipText = () => {
|
||||||
<Tooltip
|
if (nodeIsUnbonded) {
|
||||||
arrow
|
return 'This node has unbonded and it does not exist anymore. You need to undelegate from it to get your stake and outstanding rewards (if any) back.';
|
||||||
title={
|
} else if (item.uses_vesting_contract_tokens) {
|
||||||
nodeIsUnbonded
|
return 'Delegation made with locked tockens';
|
||||||
? 'This node has unbonded and it does not exist anymore. You need to undelegate from it to get your stake and outstanding rewards (if any) back.'
|
} else {
|
||||||
: ''
|
return '';
|
||||||
}
|
}
|
||||||
>
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Tooltip arrow title={tooltipText()}>
|
||||||
<TableRow key={item.node_identity} sx={{ color: !item.node_identity ? 'error.main' : 'inherit' }}>
|
<TableRow key={item.node_identity} sx={{ color: !item.node_identity ? 'error.main' : 'inherit' }}>
|
||||||
<TableCell sx={{ color: 'inherit', pr: 1 }} padding="normal">
|
<TableCell sx={{ color: 'inherit', pr: 1 }} padding="normal">
|
||||||
{nodeIsUnbonded ? (
|
{nodeIsUnbonded ? (
|
||||||
|
|||||||
+22
-42
@@ -1,24 +1,12 @@
|
|||||||
import React, { useContext, useEffect, useState } from 'react';
|
import React, { useContext, useEffect, useState } from 'react';
|
||||||
import { useForm } from 'react-hook-form';
|
import { useForm } from 'react-hook-form';
|
||||||
import { yupResolver } from '@hookform/resolvers/yup';
|
import { yupResolver } from '@hookform/resolvers/yup';
|
||||||
import {
|
import { Button, Divider, Typography, TextField, InputAdornment, Grid, Box, FormHelperText } from '@mui/material';
|
||||||
Button,
|
|
||||||
Divider,
|
|
||||||
Typography,
|
|
||||||
TextField,
|
|
||||||
InputAdornment,
|
|
||||||
Grid,
|
|
||||||
CircularProgress,
|
|
||||||
Box,
|
|
||||||
FormHelperText,
|
|
||||||
} from '@mui/material';
|
|
||||||
import { useTheme } from '@mui/material/styles';
|
import { useTheme } from '@mui/material/styles';
|
||||||
import { CurrencyDenom, MixNodeCostParams } from '@nymproject/types';
|
import { CurrencyDenom, MixNodeCostParams } from '@nymproject/types';
|
||||||
import { CurrencyFormField } from '@nymproject/react/currency/CurrencyFormField';
|
import { CurrencyFormField } from '@nymproject/react/currency/CurrencyFormField';
|
||||||
import { add, format, fromUnixTime } from 'date-fns';
|
|
||||||
import { isMixnode } from 'src/types';
|
import { isMixnode } from 'src/types';
|
||||||
import {
|
import {
|
||||||
getCurrentInterval,
|
|
||||||
getPendingIntervalEvents,
|
getPendingIntervalEvents,
|
||||||
simulateUpdateMixnodeCostParams,
|
simulateUpdateMixnodeCostParams,
|
||||||
simulateVestingUpdateMixnodeCostParams,
|
simulateVestingUpdateMixnodeCostParams,
|
||||||
@@ -29,6 +17,7 @@ import { TBondedMixnode } from 'src/context/bonding';
|
|||||||
import { SimpleModal } from 'src/components/Modals/SimpleModal';
|
import { SimpleModal } from 'src/components/Modals/SimpleModal';
|
||||||
import { bondedNodeParametersValidationSchema } from 'src/components/Bonding/forms/mixnodeValidationSchema';
|
import { bondedNodeParametersValidationSchema } from 'src/components/Bonding/forms/mixnodeValidationSchema';
|
||||||
import { Console } from 'src/utils/console';
|
import { Console } from 'src/utils/console';
|
||||||
|
import { getIntervalAsDate } from 'src/utils';
|
||||||
import { Alert } from 'src/components/Alert';
|
import { Alert } from 'src/components/Alert';
|
||||||
import { ChangeMixCostParams } from 'src/pages/bonding/types';
|
import { ChangeMixCostParams } from 'src/pages/bonding/types';
|
||||||
import { AppContext } from 'src/context';
|
import { AppContext } from 'src/context';
|
||||||
@@ -39,7 +28,6 @@ import { LoadingModal } from 'src/components/Modals/LoadingModal';
|
|||||||
export const ParametersSettings = ({ bondedNode }: { bondedNode: TBondedMixnode }): JSX.Element => {
|
export const ParametersSettings = ({ bondedNode }: { bondedNode: TBondedMixnode }): JSX.Element => {
|
||||||
const [openConfirmationModal, setOpenConfirmationModal] = useState<boolean>(false);
|
const [openConfirmationModal, setOpenConfirmationModal] = useState<boolean>(false);
|
||||||
const [intervalTime, setIntervalTime] = useState<string>();
|
const [intervalTime, setIntervalTime] = useState<string>();
|
||||||
const [nextEpoch, setNextEpoch] = useState<string>();
|
|
||||||
const [pendingUpdates, setPendingUpdates] = useState<MixNodeCostParams>();
|
const [pendingUpdates, setPendingUpdates] = useState<MixNodeCostParams>();
|
||||||
const { clientDetails } = useContext(AppContext);
|
const { clientDetails } = useContext(AppContext);
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
@@ -63,27 +51,13 @@ export const ParametersSettings = ({ bondedNode }: { bondedNode: TBondedMixnode
|
|||||||
defaultValues,
|
defaultValues,
|
||||||
});
|
});
|
||||||
|
|
||||||
const getIntervalAsDate = async () => {
|
const getNextInterval = async () => {
|
||||||
const interval = await getCurrentInterval();
|
try {
|
||||||
const secondsToNextInterval =
|
const { intervalTime } = await getIntervalAsDate();
|
||||||
Number(interval.epochs_in_interval - interval.current_epoch_id) * Number(interval.epoch_length_seconds);
|
setIntervalTime(intervalTime);
|
||||||
|
} catch {
|
||||||
setIntervalTime(
|
console.log('cant retrieve next interval');
|
||||||
format(
|
}
|
||||||
add(new Date(), {
|
|
||||||
seconds: secondsToNextInterval,
|
|
||||||
}),
|
|
||||||
'MM/dd/yyyy HH:mm',
|
|
||||||
),
|
|
||||||
);
|
|
||||||
setNextEpoch(
|
|
||||||
format(
|
|
||||||
add(fromUnixTime(Number(interval.current_epoch_start_unix)), {
|
|
||||||
seconds: Number(interval.epoch_length_seconds),
|
|
||||||
}),
|
|
||||||
'HH:mm',
|
|
||||||
),
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const getPendingEvents = async () => {
|
const getPendingEvents = async () => {
|
||||||
@@ -107,7 +81,7 @@ export const ParametersSettings = ({ bondedNode }: { bondedNode: TBondedMixnode
|
|||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
getIntervalAsDate();
|
getNextInterval();
|
||||||
getPendingEvents();
|
getPendingEvents();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
@@ -137,7 +111,16 @@ export const ParametersSettings = ({ bondedNode }: { bondedNode: TBondedMixnode
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Grid container xs item>
|
<Grid
|
||||||
|
container
|
||||||
|
xs
|
||||||
|
item
|
||||||
|
sx={{
|
||||||
|
'& .MuiGrid-item': {
|
||||||
|
pl: 0,
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
>
|
||||||
{fee && (
|
{fee && (
|
||||||
<ConfirmTx
|
<ConfirmTx
|
||||||
open
|
open
|
||||||
@@ -152,9 +135,6 @@ export const ParametersSettings = ({ bondedNode }: { bondedNode: TBondedMixnode
|
|||||||
<Alert
|
<Alert
|
||||||
title={
|
title={
|
||||||
<>
|
<>
|
||||||
<Box component="span" sx={{ fontWeight: 600, mr: 2 }}>
|
|
||||||
{`Next epoch ${nextEpoch}`}
|
|
||||||
</Box>
|
|
||||||
<Box component="span" sx={{ fontWeight: 600 }}>{`Next interval: ${intervalTime}`}</Box>
|
<Box component="span" sx={{ fontWeight: 600 }}>{`Next interval: ${intervalTime}`}</Box>
|
||||||
</>
|
</>
|
||||||
}
|
}
|
||||||
@@ -206,7 +186,7 @@ export const ParametersSettings = ({ bondedNode }: { bondedNode: TBondedMixnode
|
|||||||
</Grid>
|
</Grid>
|
||||||
)}
|
)}
|
||||||
</Grid>
|
</Grid>
|
||||||
<Divider flexItem />
|
<Divider flexItem sx={{ position: 'relative', left: '-24px', width: 'calc(100% + 24px)' }} />
|
||||||
<Grid item container direction="row" alignItems="left" justifyContent="space-between" padding={3} spacing={1}>
|
<Grid item container direction="row" alignItems="left" justifyContent="space-between" padding={3} spacing={1}>
|
||||||
<Grid item>
|
<Grid item>
|
||||||
<Typography variant="body1" sx={{ fontWeight: 600, mb: 1 }}>
|
<Typography variant="body1" sx={{ fontWeight: 600, mb: 1 }}>
|
||||||
@@ -249,7 +229,7 @@ export const ParametersSettings = ({ bondedNode }: { bondedNode: TBondedMixnode
|
|||||||
</Grid>
|
</Grid>
|
||||||
</Grid>
|
</Grid>
|
||||||
</Grid>
|
</Grid>
|
||||||
<Divider flexItem />
|
<Divider flexItem sx={{ position: 'relative', left: '-24px', width: 'calc(100% + 24px)' }} />
|
||||||
<Grid container justifyContent="end">
|
<Grid container justifyContent="end">
|
||||||
<Button
|
<Button
|
||||||
size="large"
|
size="large"
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ import { DelegationListItemActions } from '../../components/Delegation/Delegatio
|
|||||||
import { RedeemModal } from '../../components/Rewards/RedeemModal';
|
import { RedeemModal } from '../../components/Rewards/RedeemModal';
|
||||||
import { DelegationModal, DelegationModalProps } from '../../components/Delegation/DelegationModal';
|
import { DelegationModal, DelegationModalProps } from '../../components/Delegation/DelegationModal';
|
||||||
import { backDropStyles, modalStyles } from '../../../.storybook/storiesStyles';
|
import { backDropStyles, modalStyles } from '../../../.storybook/storiesStyles';
|
||||||
import { toPercentIntegerString } from '../../utils';
|
import { toPercentIntegerString, getIntervalAsDate } from 'src/utils';
|
||||||
|
|
||||||
const storybookStyles = (theme: Theme, isStorybook?: boolean, backdropProps?: object) =>
|
const storybookStyles = (theme: Theme, isStorybook?: boolean, backdropProps?: object) =>
|
||||||
isStorybook
|
isStorybook
|
||||||
@@ -36,9 +36,9 @@ export const Delegation: FC<{ isStorybook?: boolean }> = ({ isStorybook }) => {
|
|||||||
const [confirmationModalProps, setConfirmationModalProps] = useState<DelegationModalProps | undefined>();
|
const [confirmationModalProps, setConfirmationModalProps] = useState<DelegationModalProps | undefined>();
|
||||||
const [currentDelegationListActionItem, setCurrentDelegationListActionItem] = useState<DelegationWithEverything>();
|
const [currentDelegationListActionItem, setCurrentDelegationListActionItem] = useState<DelegationWithEverything>();
|
||||||
const [saturationError, setSaturationError] = useState<{ action: 'compound' | 'delegate'; saturation: string }>();
|
const [saturationError, setSaturationError] = useState<{ action: 'compound' | 'delegate'; saturation: string }>();
|
||||||
|
const [nextEpoch, setNextEpoch] = useState<string | Error>();
|
||||||
|
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
|
|
||||||
const {
|
const {
|
||||||
clientDetails,
|
clientDetails,
|
||||||
network,
|
network,
|
||||||
@@ -75,6 +75,15 @@ export const Delegation: FC<{ isStorybook?: boolean }> = ({ isStorybook }) => {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const getNextInterval = async () => {
|
||||||
|
try {
|
||||||
|
const { nextEpoch } = await getIntervalAsDate();
|
||||||
|
setNextEpoch(nextEpoch);
|
||||||
|
} catch {
|
||||||
|
setNextEpoch(Error());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// Refresh the rewards and delegations periodically when page is mounted
|
// Refresh the rewards and delegations periodically when page is mounted
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const timer = setInterval(refresh, 1 * 60 * 1000); // every 1 minute
|
const timer = setInterval(refresh, 1 * 60 * 1000); // every 1 minute
|
||||||
@@ -83,6 +92,7 @@ export const Delegation: FC<{ isStorybook?: boolean }> = ({ isStorybook }) => {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
refresh();
|
refresh();
|
||||||
|
getNextInterval();
|
||||||
}, [clientDetails, confirmationModalProps]);
|
}, [clientDetails, confirmationModalProps]);
|
||||||
|
|
||||||
const handleDelegationItemActionClick = (item: DelegationWithEverything, action: DelegationListItemActions) => {
|
const handleDelegationItemActionClick = (item: DelegationWithEverything, action: DelegationListItemActions) => {
|
||||||
@@ -332,7 +342,9 @@ export const Delegation: FC<{ isStorybook?: boolean }> = ({ isStorybook }) => {
|
|||||||
<>
|
<>
|
||||||
<Paper elevation={0} sx={{ p: 3, mt: 4 }}>
|
<Paper elevation={0} sx={{ p: 3, mt: 4 }}>
|
||||||
<Stack spacing={3}>
|
<Stack spacing={3}>
|
||||||
<Box display="flex" justifyContent="space-between" alignItems="center">
|
<Box display="flex" justifyContent="space-between">
|
||||||
|
{' '}
|
||||||
|
<Box display="flex" flexDirection="column">
|
||||||
<Typography variant="h6" lineHeight={1.334} fontWeight={600}>
|
<Typography variant="h6" lineHeight={1.334} fontWeight={600}>
|
||||||
Delegations
|
Delegations
|
||||||
</Typography>
|
</Typography>
|
||||||
@@ -345,22 +357,30 @@ export const Delegation: FC<{ isStorybook?: boolean }> = ({ isStorybook }) => {
|
|||||||
fontSize={14}
|
fontSize={14}
|
||||||
fontWeight={theme.palette.mode === 'light' ? 400 : 600}
|
fontWeight={theme.palette.mode === 'light' ? 400 : 600}
|
||||||
noIcon
|
noIcon
|
||||||
|
marginTop={1.5}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</Box>
|
</Box>
|
||||||
|
{!!delegations?.length && (
|
||||||
|
<Button
|
||||||
|
variant="contained"
|
||||||
|
disableElevation
|
||||||
|
onClick={() => setShowNewDelegationModal(true)}
|
||||||
|
sx={{ py: 1.5, px: 5, color: 'primary.contrastText', height: 'fit-content' }}
|
||||||
|
>
|
||||||
|
Delegate
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
|
||||||
{!!delegations?.length && (
|
{!!delegations?.length && (
|
||||||
<Box display="flex" justifyContent="space-between" alignItems="end">
|
<Box display="flex" justifyContent="space-between" alignItems="end">
|
||||||
<RewardsSummary isLoading={isLoading} totalDelegation={totalDelegations} totalRewards={totalRewards} />
|
<RewardsSummary isLoading={isLoading} totalDelegation={totalDelegations} totalRewards={totalRewards} />
|
||||||
|
{nextEpoch instanceof Error ? null : (
|
||||||
<Button
|
<Typography fontSize={14}>
|
||||||
variant="contained"
|
Next epoch starts at <b>{nextEpoch}</b>
|
||||||
disableElevation
|
</Typography>
|
||||||
onClick={() => setShowNewDelegationModal(true)}
|
)}
|
||||||
sx={{ py: 1.5, px: 5, color: 'primary.contrastText' }}
|
|
||||||
>
|
|
||||||
Delegate
|
|
||||||
</Button>
|
|
||||||
</Box>
|
</Box>
|
||||||
)}
|
)}
|
||||||
{delegationsComponent(delegations)}
|
{delegationsComponent(delegations)}
|
||||||
|
|||||||
@@ -7,6 +7,8 @@ import { TPoolOption } from 'src/components';
|
|||||||
import { getDefaultMixnodeCostParams, getLockedCoins, getSpendableCoins, userBalance } from '../requests';
|
import { getDefaultMixnodeCostParams, getLockedCoins, getSpendableCoins, userBalance } from '../requests';
|
||||||
import { Console } from './console';
|
import { Console } from './console';
|
||||||
|
|
||||||
|
export * from './nextEpoch';
|
||||||
|
|
||||||
export const validateKey = (key: string, bytesLength: number): boolean => {
|
export const validateKey = (key: string, bytesLength: number): boolean => {
|
||||||
// it must be a valid base58 key
|
// it must be a valid base58 key
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -0,0 +1,23 @@
|
|||||||
|
import { getCurrentInterval } from 'src/requests';
|
||||||
|
import { add, format, fromUnixTime } from 'date-fns';
|
||||||
|
|
||||||
|
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 intervalTime = 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 { intervalTime, nextEpoch };
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user