diff --git a/nym-wallet/src/context/delegations.tsx b/nym-wallet/src/context/delegations.tsx index 25e9ae7df7..4db6416fcf 100644 --- a/nym-wallet/src/context/delegations.tsx +++ b/nym-wallet/src/context/delegations.tsx @@ -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; - updateDelegation: (newDelegation: DelegationWithEverything) => Promise; - undelegate: (identity: string) => Promise; + undelegate: (identity: string, proxy: string | null) => Promise; }; export type TDelegationTransaction = { @@ -36,9 +40,6 @@ export const DelegationContext = createContext({ 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 => { - 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], diff --git a/nym-wallet/src/context/mocks/rewards.tsx b/nym-wallet/src/context/mocks/rewards.tsx index 8621f39a15..1202fde78f 100644 --- a/nym-wallet/src/context/mocks/rewards.tsx +++ b/nym-wallet/src/context/mocks/rewards.tsx @@ -7,7 +7,7 @@ export const MockRewardsContextProvider: FC = ({ children }) => { const [isLoading, setIsLoading] = useState(true); const [error, setError] = useState(); const [totalRewards, setTotalRewards] = useState(); - 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', diff --git a/nym-wallet/src/hooks/useGetBalance.ts b/nym-wallet/src/hooks/useGetBalance.ts index 5216ee45c7..40317c8cfc 100644 --- a/nym-wallet/src/hooks/useGetBalance.ts +++ b/nym-wallet/src/hooks/useGetBalance.ts @@ -31,10 +31,10 @@ export type TUseuserBalance = { currentVestingPeriod?: Period; vestingAccountInfo?: VestingAccountInfo; isLoading: boolean; - fetchBalance: () => void; + fetchBalance: () => Promise; + fetchTokenAllocation: () => Promise; 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, diff --git a/nym-wallet/src/pages/delegation/index.tsx b/nym-wallet/src/pages/delegation/index.tsx index 076af967ea..db5f0b9e8d 100644 --- a/nym-wallet/src/pages/delegation/index.tsx +++ b/nym-wallet/src/pages/delegation/index.tsx @@ -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(); const [currentDelegationListActionItem, setCurrentDelegationListActionItem] = useState(); - 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 = () => { Delegations { 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}