Dialog and mock for migrating vesting contract delegations
This commit is contained in:
@@ -1,3 +1,55 @@
|
||||
const delegations = [
|
||||
{
|
||||
mix_id: 1234,
|
||||
node_identity: 'FiojKW7oY9WQmLCiYAsCA21tpowZHS6zcUoyYm319p6Z',
|
||||
delegated_on_iso_datetime: new Date(2021, 1, 1).toDateString(),
|
||||
unclaimed_rewards: { amount: '0.05', denom: 'nym' },
|
||||
amount: { amount: '10', denom: 'nym' },
|
||||
owner: '',
|
||||
block_height: BigInt(100),
|
||||
cost_params: {
|
||||
profit_margin_percent: '0.04',
|
||||
interval_operating_cost: {
|
||||
amount: '20',
|
||||
denom: 'nym',
|
||||
},
|
||||
},
|
||||
stake_saturation: '0.2',
|
||||
avg_uptime_percent: 0.5,
|
||||
accumulated_by_delegates: { amount: '0', denom: 'nym' },
|
||||
accumulated_by_operator: { amount: '0', denom: 'nym' },
|
||||
uses_vesting_contract_tokens: false,
|
||||
pending_events: [],
|
||||
mixnode_is_unbonding: false,
|
||||
errors: null,
|
||||
},
|
||||
{
|
||||
mix_id: 5678,
|
||||
node_identity: 'DT8S942S8AQs2zKHS9SVo1GyHmuca3pfL2uLhLksJ3D8',
|
||||
unclaimed_rewards: { amount: '0.1', denom: 'nym' },
|
||||
amount: { amount: '100', denom: 'nym' },
|
||||
delegated_on_iso_datetime: new Date(2021, 1, 2).toDateString(),
|
||||
owner: '',
|
||||
block_height: BigInt(4000),
|
||||
stake_saturation: '0.5',
|
||||
avg_uptime_percent: 0.1,
|
||||
cost_params: {
|
||||
profit_margin_percent: '0.04',
|
||||
interval_operating_cost: {
|
||||
amount: '60',
|
||||
denom: 'nym',
|
||||
},
|
||||
},
|
||||
accumulated_by_delegates: { amount: '0', denom: 'nym' },
|
||||
accumulated_by_operator: { amount: '0', denom: 'nym' },
|
||||
uses_vesting_contract_tokens: true,
|
||||
pending_events: [],
|
||||
mixnode_is_unbonding: false,
|
||||
errors: null,
|
||||
},
|
||||
];
|
||||
|
||||
|
||||
/**
|
||||
* This is a mock for Tauri's API package (@tauri-apps/api), to prevent stories from being excluded, because they either use
|
||||
* or import dependencies that use Tauri.
|
||||
@@ -29,6 +81,26 @@ module.exports = {
|
||||
},
|
||||
};
|
||||
}
|
||||
case 'get_delegation_summary': {
|
||||
return {
|
||||
delegations,
|
||||
total_delegations: {
|
||||
amount: '1000',
|
||||
denom: 'nymt',
|
||||
},
|
||||
total_rewards: {
|
||||
amount: '42',
|
||||
denom: 'nymt',
|
||||
},
|
||||
};
|
||||
}
|
||||
case 'get_pending_delegation_events' : {
|
||||
return [];
|
||||
}
|
||||
case 'migrate_vested_delegations': {
|
||||
delegations[1].uses_vesting_contract_tokens = false;
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
console.error(
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
import React, { FC } from 'react';
|
||||
import Button from '@mui/material/Button';
|
||||
import Dialog from '@mui/material/Dialog';
|
||||
import DialogActions from '@mui/material/DialogActions';
|
||||
import DialogContent from '@mui/material/DialogContent';
|
||||
import DialogContentText from '@mui/material/DialogContentText';
|
||||
import DialogTitle from '@mui/material/DialogTitle';
|
||||
|
||||
export const VestingWarningModal: FC<{ isVisible: boolean; handleClose: (result: 'yes' | 'no') => void }> = ({
|
||||
isVisible,
|
||||
handleClose,
|
||||
}) => (
|
||||
<Dialog open={isVisible} onClose={handleClose}>
|
||||
<DialogTitle>Migrate all of your delegations?</DialogTitle>
|
||||
<DialogContent>
|
||||
<DialogContentText>
|
||||
By clicking <strong>yes</strong> we will migrate your delegations to the mixnet contract.
|
||||
</DialogContentText>
|
||||
<DialogContentText sx={{ mt: 2 }}>
|
||||
The operation will be instant, you will keep your rewards and they will continue to accumulate. Once migrated,
|
||||
you will be able to withdraw you rewards.
|
||||
</DialogContentText>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button onClick={() => handleClose('yes')}>Yes</Button>
|
||||
<Button onClick={() => handleClose('no')} autoFocus>
|
||||
No
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
);
|
||||
@@ -1,5 +1,5 @@
|
||||
import React, { FC, useContext, useEffect, useState } from 'react';
|
||||
import { Box, Button, Paper, Stack, Typography } from '@mui/material';
|
||||
import { Alert, AlertTitle, Box, Button, Paper, Stack, Typography } from '@mui/material';
|
||||
import { Theme, useTheme } from '@mui/material/styles';
|
||||
import { DecCoin, decimalToFloatApproximation, DelegationWithEverything, FeeDetails } from '@nymproject/types';
|
||||
import { Link } from '@nymproject/react/link/Link';
|
||||
@@ -8,11 +8,11 @@ import { DelegationList } from 'src/components/Delegation/DelegationList';
|
||||
import { TPoolOption } from 'src/components';
|
||||
import { Console } from 'src/utils/console';
|
||||
import { OverSaturatedBlockerModal } from 'src/components/Delegation/DelegateBlocker';
|
||||
import { getSpendableCoins, userBalance } from 'src/requests';
|
||||
import { getSpendableCoins, migrateVestedDelegations, userBalance } from 'src/requests';
|
||||
import { LoadingModal } from 'src/components/Modals/LoadingModal';
|
||||
import { getIntervalAsDate, toPercentIntegerString } from 'src/utils';
|
||||
import { RewardsSummary } from '../../components/Rewards/RewardsSummary';
|
||||
import { DelegationContextProvider, TDelegations, useDelegationContext } from '../../context/delegations';
|
||||
import { DelegationContextProvider, isDelegation, TDelegations, useDelegationContext } from '../../context/delegations';
|
||||
import { RewardsContextProvider, useRewardsContext } from '../../context/rewards';
|
||||
import { DelegateModal } from '../../components/Delegation/DelegateModal';
|
||||
import { UndelegateModal } from '../../components/Delegation/UndelegateModal';
|
||||
@@ -20,6 +20,7 @@ import { DelegationListItemActions } from '../../components/Delegation/Delegatio
|
||||
import { RedeemModal } from '../../components/Rewards/RedeemModal';
|
||||
import { DelegationModal, DelegationModalProps } from '../../components/Delegation/DelegationModal';
|
||||
import { backDropStyles, modalStyles } from '../../../.storybook/storiesStyles';
|
||||
import { VestingWarningModal } from '../../components/VestingWarningModal';
|
||||
|
||||
const storybookStyles = (theme: Theme, isStorybook?: boolean, backdropProps?: object) =>
|
||||
isStorybook
|
||||
@@ -38,6 +39,8 @@ export const Delegation: FC<{ isStorybook?: boolean }> = ({ isStorybook }) => {
|
||||
const [currentDelegationListActionItem, setCurrentDelegationListActionItem] = useState<DelegationWithEverything>();
|
||||
const [saturationError, setSaturationError] = useState<{ action: 'compound' | 'delegate'; saturation: string }>();
|
||||
const [nextEpoch, setNextEpoch] = useState<string | Error>();
|
||||
const [showVestingWarningModal, setShowVestingWarningModal] = useState<boolean>(false);
|
||||
const [showVestingMigrationProgressModal, setShowVestingMigrationProgressModal] = useState<boolean>(false);
|
||||
|
||||
const theme = useTheme();
|
||||
const {
|
||||
@@ -57,6 +60,11 @@ export const Delegation: FC<{ isStorybook?: boolean }> = ({ isStorybook }) => {
|
||||
refresh: refreshDelegations,
|
||||
} = useDelegationContext();
|
||||
|
||||
const delegationsUseVestingTokens: boolean = React.useMemo(
|
||||
() => Boolean(delegations?.filter((d) => isDelegation(d) && d.uses_vesting_contract_tokens).length),
|
||||
[delegations],
|
||||
);
|
||||
|
||||
const { refresh: refreshRewards, claimRewards } = useRewardsContext();
|
||||
|
||||
const refresh = async () => Promise.all([refreshDelegations(), refreshRewards()]);
|
||||
@@ -105,6 +113,13 @@ export const Delegation: FC<{ isStorybook?: boolean }> = ({ isStorybook }) => {
|
||||
return () => clearInterval(timer);
|
||||
}, []);
|
||||
|
||||
const doMigrateNow = async () => {
|
||||
setShowVestingMigrationProgressModal(true);
|
||||
await migrateVestedDelegations();
|
||||
await refresh();
|
||||
setShowVestingMigrationProgressModal(false);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
refreshWithIntervalUpdate();
|
||||
}, [clientDetails, confirmationModalProps]);
|
||||
@@ -119,6 +134,11 @@ export const Delegation: FC<{ isStorybook?: boolean }> = ({ isStorybook }) => {
|
||||
return;
|
||||
}
|
||||
|
||||
if (item.uses_vesting_contract_tokens) {
|
||||
setShowVestingWarningModal(true);
|
||||
return;
|
||||
}
|
||||
|
||||
setCurrentDelegationListActionItem(item);
|
||||
// eslint-disable-next-line default-case
|
||||
switch (action) {
|
||||
@@ -305,12 +325,49 @@ export const Delegation: FC<{ isStorybook?: boolean }> = ({ isStorybook }) => {
|
||||
const delegationsComponent = (delegationItems: TDelegations | undefined) => {
|
||||
if (delegationItems && Boolean(delegationItems?.length)) {
|
||||
return (
|
||||
<DelegationList
|
||||
explorerUrl={urls(network).networkExplorer}
|
||||
isLoading={isLoading && !isActionModalOpen}
|
||||
items={delegationItems}
|
||||
onItemActionClick={handleDelegationItemActionClick}
|
||||
/>
|
||||
<>
|
||||
{delegationsUseVestingTokens && (
|
||||
<>
|
||||
<Alert severity="warning">
|
||||
<AlertTitle sx={{ fontWeight: 600 }}>
|
||||
Some of your delegations are using tokens from the vesting contract!
|
||||
</AlertTitle>
|
||||
<Typography>
|
||||
In order to claim your rewards, you will need to migrate them out of the vesting contract.{' '}
|
||||
</Typography>
|
||||
<Typography mt={1}>
|
||||
<strong>Never fear</strong>, if you do not migrate them,{' '}
|
||||
<strong>you will continue to get rewards</strong>. However, please migrate your delegations as soon as
|
||||
possible.
|
||||
</Typography>
|
||||
<Button
|
||||
variant="contained"
|
||||
size="small"
|
||||
sx={{ mt: 1 }}
|
||||
onClick={() => setShowVestingWarningModal(true)}
|
||||
>
|
||||
Migrate now
|
||||
</Button>
|
||||
</Alert>
|
||||
<VestingWarningModal
|
||||
isVisible={showVestingWarningModal}
|
||||
handleClose={(result) => {
|
||||
setShowVestingWarningModal(false);
|
||||
if (result === 'yes') {
|
||||
doMigrateNow();
|
||||
}
|
||||
}}
|
||||
/>
|
||||
{showVestingMigrationProgressModal && <LoadingModal text="Migrating delegations, please wait..." />}
|
||||
</>
|
||||
)}
|
||||
<DelegationList
|
||||
explorerUrl={urls(network).networkExplorer}
|
||||
isLoading={isLoading && !isActionModalOpen}
|
||||
items={delegationItems}
|
||||
onItemActionClick={handleDelegationItemActionClick}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -31,3 +31,6 @@ export const undelegateAllFromMixnode = async (
|
||||
|
||||
export const delegateToMixnode = async (mixId: number, amount: DecCoin, fee?: Fee) =>
|
||||
invokeWrapper<TransactionExecuteResult>('delegate_to_mixnode', { mixId, amount, fee });
|
||||
|
||||
export const migrateVestedDelegations = async () =>
|
||||
invokeWrapper<TransactionExecuteResult>('migrate_vested_delegations');
|
||||
|
||||
Reference in New Issue
Block a user