handle undelegation of locked tokens
This commit is contained in:
@@ -7,7 +7,12 @@ import {
|
||||
TransactionExecuteResult,
|
||||
} from '@nymproject/types';
|
||||
import type { Network } from 'src/types';
|
||||
import { delegateToMixnode, getAllPendingDelegations, vestingDelegateToMixnode } from 'src/requests';
|
||||
import {
|
||||
delegateToMixnode,
|
||||
getAllPendingDelegations,
|
||||
vestingDelegateToMixnode,
|
||||
vestingUndelegateFromMixnode,
|
||||
} from 'src/requests';
|
||||
import { TPoolOption } from 'src/components';
|
||||
|
||||
export type TDelegationContext = {
|
||||
@@ -22,8 +27,7 @@ export type TDelegationContext = {
|
||||
data: { identity: string; amount: MajorCurrencyAmount },
|
||||
tokenPool: TPoolOption,
|
||||
) => Promise<TransactionExecuteResult>;
|
||||
updateDelegation: (newDelegation: DelegationWithEverything) => Promise<TDelegationTransaction>;
|
||||
undelegate: (identity: string) => Promise<TransactionExecuteResult>;
|
||||
undelegate: (identity: string, proxy: string | null) => Promise<TransactionExecuteResult>;
|
||||
};
|
||||
|
||||
export type TDelegationTransaction = {
|
||||
@@ -36,9 +40,6 @@ export const DelegationContext = createContext<TDelegationContext>({
|
||||
addDelegation: async () => {
|
||||
throw new Error('Not implemented');
|
||||
},
|
||||
updateDelegation: async () => {
|
||||
throw new Error('Not implemented');
|
||||
},
|
||||
undelegate: async () => {
|
||||
throw new Error('Not implemented');
|
||||
},
|
||||
@@ -68,12 +69,15 @@ export const DelegationContextProvider: FC<{
|
||||
}
|
||||
};
|
||||
|
||||
const updateDelegation = async (): Promise<TDelegationTransaction> => {
|
||||
throw new Error('Not implemented');
|
||||
};
|
||||
|
||||
const undelegate = async (identity: string) => {
|
||||
const delegationResult = await undelegateFromMixnode(identity);
|
||||
const undelegate = async (identity: string, proxy: string | null) => {
|
||||
let delegationResult;
|
||||
if ((proxy || '').trim().length === 0) {
|
||||
// the owner of the delegation is main account (the owner of the vesting account), so it is delegation with unlocked tokens
|
||||
delegationResult = await undelegateFromMixnode(identity);
|
||||
} else {
|
||||
// the delegation is with locked tokens, so use the vesting contract
|
||||
delegationResult = await vestingUndelegateFromMixnode(identity);
|
||||
}
|
||||
await refresh();
|
||||
return delegationResult;
|
||||
};
|
||||
@@ -101,7 +105,6 @@ export const DelegationContextProvider: FC<{
|
||||
}, [network]);
|
||||
|
||||
useEffect(() => {
|
||||
// reset state and refresh
|
||||
resetState();
|
||||
refresh();
|
||||
}, [network]);
|
||||
@@ -116,7 +119,6 @@ export const DelegationContextProvider: FC<{
|
||||
totalRewards,
|
||||
refresh,
|
||||
addDelegation,
|
||||
updateDelegation,
|
||||
undelegate,
|
||||
}),
|
||||
[isLoading, error, delegations, pendingDelegations, totalDelegations],
|
||||
|
||||
@@ -7,7 +7,7 @@ export const MockRewardsContextProvider: FC = ({ children }) => {
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
const [error, setError] = useState<string>();
|
||||
const [totalRewards, setTotalRewards] = useState<undefined | string>();
|
||||
const { delegations, updateDelegation } = useDelegationContext();
|
||||
const { delegations } = useDelegationContext();
|
||||
const delegationsHash = delegations?.map((d) => d.accumulated_rewards).join(',');
|
||||
|
||||
const resetState = () => {
|
||||
@@ -55,10 +55,6 @@ export const MockRewardsContextProvider: FC = ({ children }) => {
|
||||
|
||||
await mockSleep(1000);
|
||||
|
||||
// the `updateDelegation as any` is hacking typescript with type erasure so that
|
||||
// the hook interface can stay the same and the mock implementation is easy
|
||||
await (updateDelegation as any)({ ...d, reward: undefined }, true);
|
||||
|
||||
return {
|
||||
transactionUrl:
|
||||
'https://sandbox-blocks.nymtech.net/transactions/55303CD4B91FAC4C2715E40EBB52BB3B92829D9431B3A279D37B5CC58432E354',
|
||||
@@ -72,14 +68,6 @@ export const MockRewardsContextProvider: FC = ({ children }) => {
|
||||
|
||||
await mockSleep(1000);
|
||||
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
for (const d of delegations) {
|
||||
// the `updateDelegation as any` is hacking typescript with type erasure so that
|
||||
// the hook interface can stay the same and the mock implementation is easy
|
||||
// eslint-disable-next-line no-await-in-loop
|
||||
await (updateDelegation as any)({ ...d, reward: undefined }, true);
|
||||
}
|
||||
|
||||
return {
|
||||
transactionUrl:
|
||||
'https://sandbox-blocks.nymtech.net/transactions/55303CD4B91FAC4C2715E40EBB52BB3B92829D9431B3A279D37B5CC58432E354',
|
||||
|
||||
@@ -31,10 +31,10 @@ export type TUseuserBalance = {
|
||||
currentVestingPeriod?: Period;
|
||||
vestingAccountInfo?: VestingAccountInfo;
|
||||
isLoading: boolean;
|
||||
fetchBalance: () => void;
|
||||
fetchBalance: () => Promise<void>;
|
||||
fetchTokenAllocation: () => Promise<void>;
|
||||
clearBalance: () => void;
|
||||
clearAll: () => void;
|
||||
fetchTokenAllocation: () => void;
|
||||
};
|
||||
|
||||
export const useGetBalance = (clientDetails?: Account): TUseuserBalance => {
|
||||
@@ -60,7 +60,7 @@ export const useGetBalance = (clientDetails?: Account): TUseuserBalance => {
|
||||
vestedCoins,
|
||||
lockedCoins,
|
||||
spendableCoins,
|
||||
currentVestingPer,
|
||||
currentVestingPeriod,
|
||||
vestingAccountDetail,
|
||||
] = await Promise.all([
|
||||
getOriginalVesting(clientDetails?.client_address),
|
||||
@@ -72,7 +72,7 @@ export const useGetBalance = (clientDetails?: Account): TUseuserBalance => {
|
||||
getVestingAccountInfo(clientDetails?.client_address),
|
||||
]);
|
||||
setOriginalVesting(originalVestingValue);
|
||||
setCurrentVestingPeriod(currentVestingPer);
|
||||
setCurrentVestingPeriod(currentVestingPeriod);
|
||||
setTokenAllocation({
|
||||
vesting: vestingCoins.amount,
|
||||
vested: vestedCoins.amount,
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
import React, { FC, useContext, useEffect, useState } from 'react';
|
||||
import { Box, Button, Link, Paper, Stack, Typography } from '@mui/material';
|
||||
import { DelegationWithEverything, MajorCurrencyAmount } from '@nymproject/types';
|
||||
import { AppContext } from 'src/context/main';
|
||||
import { AppContext, urls } from 'src/context/main';
|
||||
import { DelegationList } from 'src/components/Delegation/DelegationList';
|
||||
import { PendingEvents } from 'src/components/Delegation/PendingEvents';
|
||||
import { TPoolOption } from 'src/components';
|
||||
import { getSpendableCoins, userBalance } from 'src/requests';
|
||||
import { RewardsSummary } from '../../components/Rewards/RewardsSummary';
|
||||
import { useDelegationContext, DelegationContextProvider } from '../../context/delegations';
|
||||
import { RewardsContextProvider, useRewardsContext } from '../../context/rewards';
|
||||
@@ -11,8 +14,6 @@ import { UndelegateModal } from '../../components/Delegation/UndelegateModal';
|
||||
import { DelegationListItemActions } from '../../components/Delegation/DelegationActions';
|
||||
import { RedeemModal } from '../../components/Rewards/RedeemModal';
|
||||
import { DelegationModal, DelegationModalProps } from '../../components/Delegation/DelegationModal';
|
||||
import { PendingEvents } from 'src/components/Delegation/PendingEvents';
|
||||
import { TPoolOption } from 'src/components';
|
||||
|
||||
const explorerUrl = 'https://sandbox-explorer.nymtech.net';
|
||||
|
||||
@@ -25,7 +26,11 @@ export const Delegation: FC = () => {
|
||||
const [confirmationModalProps, setConfirmationModalProps] = useState<DelegationModalProps | undefined>();
|
||||
const [currentDelegationListActionItem, setCurrentDelegationListActionItem] = useState<DelegationWithEverything>();
|
||||
|
||||
const { clientDetails, userBalance } = useContext(AppContext);
|
||||
const {
|
||||
clientDetails,
|
||||
network,
|
||||
userBalance: { balance, originalVesting },
|
||||
} = useContext(AppContext);
|
||||
const { redeemAllRewards, redeemRewards, totalRewards, isLoading: isLoadingRewards } = useRewardsContext();
|
||||
const {
|
||||
delegations,
|
||||
@@ -33,7 +38,6 @@ export const Delegation: FC = () => {
|
||||
totalDelegations,
|
||||
isLoading: isLoadingDelegations,
|
||||
addDelegation,
|
||||
updateDelegation,
|
||||
undelegate,
|
||||
refresh,
|
||||
} = useDelegationContext();
|
||||
@@ -44,8 +48,6 @@ export const Delegation: FC = () => {
|
||||
return () => clearInterval(timer);
|
||||
}, []);
|
||||
|
||||
// TODO: replace with real operation
|
||||
|
||||
const handleDelegationItemActionClick = (item: DelegationWithEverything, action: DelegationListItemActions) => {
|
||||
setCurrentDelegationListActionItem(item);
|
||||
// eslint-disable-next-line default-case
|
||||
@@ -77,12 +79,20 @@ export const Delegation: FC = () => {
|
||||
},
|
||||
tokenPool,
|
||||
);
|
||||
await userBalance.fetchBalance();
|
||||
|
||||
const balance = await userBalance();
|
||||
let spendableLocked;
|
||||
|
||||
if (tokenPool === 'locked') spendableLocked = await getSpendableCoins();
|
||||
|
||||
setConfirmationModalProps({
|
||||
status: 'success',
|
||||
action: 'delegate',
|
||||
balance: userBalance.balance?.printable_balance || '-',
|
||||
transactionUrl: tx.transaction_hash,
|
||||
balance:
|
||||
tokenPool === 'locked'
|
||||
? `${spendableLocked?.amount} ${spendableLocked?.denom}`
|
||||
: balance?.printable_balance || '-',
|
||||
transactionUrl: `${urls(network).blockExplorer}/transaction/${tx.transaction_hash}`,
|
||||
});
|
||||
} catch (e) {
|
||||
setConfirmationModalProps({
|
||||
@@ -117,12 +127,17 @@ export const Delegation: FC = () => {
|
||||
},
|
||||
tokenPool,
|
||||
);
|
||||
await userBalance.fetchBalance();
|
||||
let balance = await userBalance();
|
||||
let spendableLocked;
|
||||
|
||||
if (originalVesting) spendableLocked = await getSpendableCoins();
|
||||
|
||||
setConfirmationModalProps({
|
||||
status: 'success',
|
||||
action: 'delegate',
|
||||
balance: userBalance.balance?.printable_balance || '-',
|
||||
transactionUrl: tx.transaction_hash,
|
||||
balance:
|
||||
tokenPool === 'locked' ? `${spendableLocked} ${clientDetails?.denom}` : balance?.printable_balance || '-',
|
||||
transactionUrl: `${urls(network).blockExplorer}/transaction/${tx.transaction_hash}`,
|
||||
});
|
||||
} catch (e) {
|
||||
setConfirmationModalProps({
|
||||
@@ -133,21 +148,23 @@ export const Delegation: FC = () => {
|
||||
}
|
||||
};
|
||||
|
||||
const handleUndelegate = async (identityKey: string) => {
|
||||
const handleUndelegate = async (identityKey: string, proxy: string | null) => {
|
||||
setConfirmationModalProps({
|
||||
status: 'loading',
|
||||
action: 'undelegate',
|
||||
});
|
||||
setShowUndelegateModal(false);
|
||||
setCurrentDelegationListActionItem(undefined);
|
||||
|
||||
try {
|
||||
const tx = await undelegate(identityKey);
|
||||
await userBalance.fetchBalance();
|
||||
const tx = await undelegate(identityKey, proxy);
|
||||
const balance = await userBalance();
|
||||
|
||||
setConfirmationModalProps({
|
||||
status: 'success',
|
||||
action: 'undelegate',
|
||||
balance: userBalance.balance?.printable_balance || '-',
|
||||
transactionUrl: tx.transaction_hash,
|
||||
balance: balance?.printable_balance || '-',
|
||||
transactionUrl: `${urls(network).blockExplorer}/transaction/${tx.transaction_hash}`,
|
||||
});
|
||||
} catch (e) {
|
||||
setConfirmationModalProps({
|
||||
@@ -175,13 +192,13 @@ export const Delegation: FC = () => {
|
||||
if (clientDetails?.client_address) {
|
||||
try {
|
||||
const tx = await redeemRewards(identityKey);
|
||||
await userBalance.fetchBalance();
|
||||
const balance = await userBalance();
|
||||
setConfirmationModalProps({
|
||||
status: 'success',
|
||||
action: 'redeem',
|
||||
balance: userBalance.balance?.printable_balance || '-',
|
||||
balance: balance?.printable_balance || '-',
|
||||
recipient: clientDetails?.client_address,
|
||||
transactionUrl: tx.transactionUrl,
|
||||
transactionUrl: `${urls(network).blockExplorer}/${tx}}`,
|
||||
});
|
||||
} catch (e) {
|
||||
setConfirmationModalProps({
|
||||
@@ -202,11 +219,12 @@ export const Delegation: FC = () => {
|
||||
setCurrentDelegationListActionItem(undefined);
|
||||
try {
|
||||
const tx = await redeemAllRewards();
|
||||
await userBalance.fetchBalance();
|
||||
const balance = await userBalance();
|
||||
|
||||
setConfirmationModalProps({
|
||||
status: 'success',
|
||||
action: 'redeem-all',
|
||||
balance: userBalance.balance?.printable_balance || '-',
|
||||
balance: balance?.printable_balance || '-',
|
||||
recipient: clientDetails?.client_address,
|
||||
transactionUrl: tx.transactionUrl,
|
||||
});
|
||||
@@ -226,7 +244,7 @@ export const Delegation: FC = () => {
|
||||
<Box display="flex" justifyContent="space-between" alignItems="center">
|
||||
<Typography variant="h6">Delegations</Typography>
|
||||
<Link
|
||||
href={`${explorerUrl}/network-components/mixnodes/`}
|
||||
href={`${urls(network).networkExplorer}/network-components/mixnodes/`}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
underline="hover"
|
||||
@@ -280,12 +298,9 @@ export const Delegation: FC = () => {
|
||||
buttonText="Delegate stake"
|
||||
currency={clientDetails!.denom}
|
||||
fee={0.004375}
|
||||
estimatedMonthlyReward={50.123}
|
||||
accountBalance={userBalance.balance?.printable_balance}
|
||||
nodeUptimePercentage={99.28394}
|
||||
profitMarginPercentage={11.12334234}
|
||||
accountBalance={balance?.printable_balance}
|
||||
rewardInterval="weekly"
|
||||
hasVestingContract={Boolean(userBalance.originalVesting)}
|
||||
hasVestingContract={Boolean(originalVesting)}
|
||||
/>
|
||||
)}
|
||||
|
||||
@@ -299,12 +314,12 @@ export const Delegation: FC = () => {
|
||||
identityKey={currentDelegationListActionItem.node_identity}
|
||||
currency={clientDetails!.denom}
|
||||
fee={0.004375}
|
||||
estimatedMonthlyReward={0}
|
||||
accountBalance={userBalance.balance?.printable_balance}
|
||||
estimatedReward={0}
|
||||
accountBalance={balance?.printable_balance}
|
||||
nodeUptimePercentage={currentDelegationListActionItem.avg_uptime_percent}
|
||||
profitMarginPercentage={currentDelegationListActionItem.profit_margin_percent}
|
||||
rewardInterval="weekly"
|
||||
hasVestingContract={Boolean(userBalance.originalVesting)}
|
||||
hasVestingContract={Boolean(originalVesting)}
|
||||
/>
|
||||
)}
|
||||
|
||||
@@ -313,6 +328,7 @@ export const Delegation: FC = () => {
|
||||
open={showUndelegateModal}
|
||||
onClose={() => setShowUndelegateModal(false)}
|
||||
onOk={handleUndelegate}
|
||||
proxy={currentDelegationListActionItem.proxy}
|
||||
currency={currentDelegationListActionItem.amount.denom}
|
||||
fee={0.1}
|
||||
amount={+currentDelegationListActionItem.amount.amount}
|
||||
|
||||
Reference in New Issue
Block a user