Dialog and mock for migrating vesting contract delegations

This commit is contained in:
Mark Sinclair
2024-07-18 17:19:19 +01:00
committed by fmtabbara
parent b76802e6eb
commit 61fcd4ac69
4 changed files with 172 additions and 9 deletions
@@ -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>
);
+66 -9
View File
@@ -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}
/>
</>
);
}
+3
View File
@@ -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');