diff --git a/apps/core/src/components/stake/StakedCard.tsx b/apps/core/src/components/stake/StakedCard.tsx
index dad5c6f091a..58bc5c82f20 100644
--- a/apps/core/src/components/stake/StakedCard.tsx
+++ b/apps/core/src/components/stake/StakedCard.tsx
@@ -7,27 +7,10 @@ import { Card, CardImage, CardType, CardBody, CardAction, CardActionType } from
import { useMemo } from 'react';
import { useIotaClientQuery } from '@iota/dapp-kit';
import { ImageIcon } from '../icon';
-import { determineCountDownText, ExtendedDelegatedStake } from '../../utils';
-import { TimeUnit, useFormatCoin, useGetTimeBeforeEpochNumber, useTimeAgo } from '../../hooks';
-import { NUM_OF_EPOCH_BEFORE_STAKING_REWARDS_REDEEMABLE } from '../../constants';
+import { ExtendedDelegatedStake } from '../../utils';
+import { useFormatCoin, useStakeRewardStatus } from '../../hooks';
import React from 'react';
-export enum StakeState {
- WarmUp = 'WARM_UP',
- Earning = 'EARNING',
- CoolDown = 'COOL_DOWN',
- Withdraw = 'WITHDRAW',
- InActive = 'IN_ACTIVE',
-}
-
-const STATUS_COPY: { [key in StakeState]: string } = {
- [StakeState.WarmUp]: 'Starts Earning',
- [StakeState.Earning]: 'Staking Rewards',
- [StakeState.CoolDown]: 'Available to withdraw',
- [StakeState.Withdraw]: 'Withdraw',
- [StakeState.InActive]: 'Inactive',
-};
-
interface StakedCardProps {
extendedStake: ExtendedDelegatedStake;
currentEpoch: number;
@@ -45,48 +28,20 @@ export function StakedCard({
}: StakedCardProps) {
const { principal, stakeRequestEpoch, estimatedReward, validatorAddress } = extendedStake;
- // TODO: Once two step withdraw is available, add cool down and withdraw now logic
- // For cool down epoch, show Available to withdraw add rewards to principal
- // Reward earning epoch is 2 epochs after stake request epoch
- const earningRewardsEpoch =
- Number(stakeRequestEpoch) + NUM_OF_EPOCH_BEFORE_STAKING_REWARDS_REDEEMABLE;
- const isEarnedRewards = currentEpoch >= Number(earningRewardsEpoch);
- const delegationState = inactiveValidator
- ? StakeState.InActive
- : isEarnedRewards
- ? StakeState.Earning
- : StakeState.WarmUp;
-
- const rewards = isEarnedRewards && estimatedReward ? BigInt(estimatedReward) : 0n;
+ const { rewards, title, subtitle } = useStakeRewardStatus({
+ stakeRequestEpoch,
+ currentEpoch,
+ estimatedReward,
+ inactiveValidator,
+ });
// For inactive validator, show principal + rewards
const [principalStaked, symbol] = useFormatCoin(
inactiveValidator ? principal + rewards : principal,
IOTA_TYPE_ARG,
);
- const [rewardsStaked] = useFormatCoin(rewards, IOTA_TYPE_ARG);
-
- // Applicable only for warm up
- const epochBeforeRewards = delegationState === StakeState.WarmUp ? earningRewardsEpoch : null;
-
- const statusText = {
- // Epoch time before earning
- [StakeState.WarmUp]: `Epoch #${earningRewardsEpoch}`,
- [StakeState.Earning]: `${rewardsStaked} ${symbol}`,
- // Epoch time before redrawing
- [StakeState.CoolDown]: `Epoch #`,
- [StakeState.Withdraw]: 'Now',
- [StakeState.InActive]: 'Not earning rewards',
- };
const { data } = useIotaClientQuery('getLatestIotaSystemState');
- const { data: rewardEpochTime } = useGetTimeBeforeEpochNumber(Number(epochBeforeRewards) || 0);
- const timeAgo = useTimeAgo({
- timeFrom: rewardEpochTime || null,
- shortedTimeLabel: false,
- shouldEnd: true,
- maxTimeUnit: TimeUnit.ONE_HOUR,
- });
const validatorMeta = useMemo(() => {
if (!data) return null;
@@ -97,17 +52,6 @@ export function StakedCard({
);
}, [validatorAddress, data]);
- const rewardTime = () => {
- if (Number(epochBeforeRewards) && rewardEpochTime > 0) {
- return determineCountDownText({
- timeAgo,
- label: 'in',
- });
- }
-
- return statusText[delegationState];
- };
-
return (
@@ -118,11 +62,7 @@ export function StakedCard({
/>
-
+
);
}
diff --git a/apps/core/src/hooks/index.ts b/apps/core/src/hooks/index.ts
index 122b9b01294..89aced3f57f 100644
--- a/apps/core/src/hooks/index.ts
+++ b/apps/core/src/hooks/index.ts
@@ -46,5 +46,6 @@ export * from './useNFTBasicData';
export * from './useOwnedNFT';
export * from './useNftDetails';
export * from './useCountdownByTimestamp';
+export * from './useStakeRewardStatus';
export * from './stake';
diff --git a/apps/core/src/hooks/useStakeRewardStatus.ts b/apps/core/src/hooks/useStakeRewardStatus.ts
new file mode 100644
index 00000000000..415b3efc5ac
--- /dev/null
+++ b/apps/core/src/hooks/useStakeRewardStatus.ts
@@ -0,0 +1,92 @@
+// Copyright (c) 2024 IOTA Stiftung
+// SPDX-License-Identifier: Apache-2.0
+
+import { IOTA_TYPE_ARG } from '@iota/iota-sdk/utils';
+import { NUM_OF_EPOCH_BEFORE_STAKING_REWARDS_REDEEMABLE } from '../constants';
+import { useFormatCoin, useGetTimeBeforeEpochNumber, useTimeAgo, TimeUnit } from '.';
+import { determineCountDownText } from '../utils';
+
+export function useStakeRewardStatus({
+ stakeRequestEpoch,
+ currentEpoch,
+ inactiveValidator,
+ estimatedReward,
+}: {
+ stakeRequestEpoch: string;
+ currentEpoch: number;
+ inactiveValidator: boolean;
+ estimatedReward?: string | number | bigint;
+}) {
+ // TODO: Once two step withdraw is available, add cool down and withdraw now logic
+ // For cool down epoch, show Available to withdraw add rewards to principal
+ // Reward earning epoch is 2 epochs after stake request epoch
+ const earningRewardsEpoch =
+ Number(stakeRequestEpoch) + NUM_OF_EPOCH_BEFORE_STAKING_REWARDS_REDEEMABLE;
+
+ const isEarnedRewards = currentEpoch >= Number(earningRewardsEpoch);
+
+ const delegationState = inactiveValidator
+ ? StakeState.InActive
+ : isEarnedRewards
+ ? StakeState.Earning
+ : StakeState.WarmUp;
+
+ const rewards = isEarnedRewards && estimatedReward ? BigInt(estimatedReward) : 0n;
+
+ const [rewardsStaked, symbol] = useFormatCoin(rewards, IOTA_TYPE_ARG);
+
+ // Applicable only for warm up
+ const epochBeforeRewards = delegationState === StakeState.WarmUp ? earningRewardsEpoch : null;
+
+ const statusText = {
+ // Epoch time before earning
+ [StakeState.WarmUp]: `Epoch #${earningRewardsEpoch}`,
+ [StakeState.Earning]: `${rewardsStaked} ${symbol}`,
+ // Epoch time before redrawing
+ [StakeState.CoolDown]: `Epoch #`,
+ [StakeState.Withdraw]: 'Now',
+ [StakeState.InActive]: 'Not earning rewards',
+ };
+
+ const { data: rewardEpochTime } = useGetTimeBeforeEpochNumber(Number(epochBeforeRewards) || 0);
+
+ const timeAgo = useTimeAgo({
+ timeFrom: rewardEpochTime || null,
+ shortedTimeLabel: false,
+ shouldEnd: true,
+ maxTimeUnit: TimeUnit.ONE_HOUR,
+ });
+
+ const rewardTime = () => {
+ if (Number(epochBeforeRewards) && rewardEpochTime > 0) {
+ return determineCountDownText({
+ timeAgo,
+ label: 'in',
+ });
+ }
+
+ return statusText[delegationState];
+ };
+
+ return {
+ rewards,
+ title: rewardTime(),
+ subtitle: STATUS_COPY[delegationState],
+ };
+}
+export enum StakeState {
+ WarmUp = 'WARM_UP',
+ Earning = 'EARNING',
+ CoolDown = 'COOL_DOWN',
+ Withdraw = 'WITHDRAW',
+ InActive = 'IN_ACTIVE',
+}
+export const STATUS_COPY: {
+ [key in StakeState]: string;
+} = {
+ [StakeState.WarmUp]: 'Starts Earning',
+ [StakeState.Earning]: 'Staking Rewards',
+ [StakeState.CoolDown]: 'Available to withdraw',
+ [StakeState.Withdraw]: 'Withdraw',
+ [StakeState.InActive]: 'Inactive',
+};
diff --git a/apps/wallet-dashboard/app/(protected)/layout.tsx b/apps/wallet-dashboard/app/(protected)/layout.tsx
index 8d42d2ff757..a77c8922ab9 100644
--- a/apps/wallet-dashboard/app/(protected)/layout.tsx
+++ b/apps/wallet-dashboard/app/(protected)/layout.tsx
@@ -25,11 +25,14 @@ function DashboardLayout({ children }: PropsWithChildren): JSX.Element {
-
-
-
+ {/* This padding need to have aligned left/right content's position, because of sidebar overlap on the small screens */}
+
diff --git a/apps/wallet-dashboard/app/(protected)/vesting/page.tsx b/apps/wallet-dashboard/app/(protected)/vesting/page.tsx
index 0488681830d..b925d57e97c 100644
--- a/apps/wallet-dashboard/app/(protected)/vesting/page.tsx
+++ b/apps/wallet-dashboard/app/(protected)/vesting/page.tsx
@@ -6,6 +6,7 @@
import {
Banner,
StakeDialog,
+ StakeDialogView,
TimelockedUnstakePopup,
useStakeDialog,
VestingScheduleDialog,
@@ -37,8 +38,9 @@ import {
CardType,
ImageType,
ImageShape,
- ButtonType,
Button,
+ ButtonType,
+ LoadingIndicator,
} from '@iota/apps-ui-kit';
import {
Theme,
@@ -52,19 +54,26 @@ import {
useCountdownByTimestamp,
Feature,
} from '@iota/core';
-import { useCurrentAccount, useIotaClient, useSignAndExecuteTransaction } from '@iota/dapp-kit';
+import {
+ useCurrentAccount,
+ useIotaClient,
+ useIotaClientQuery,
+ useSignAndExecuteTransaction,
+} from '@iota/dapp-kit';
import { IotaValidatorSummary } from '@iota/iota-sdk/client';
import { IOTA_TYPE_ARG } from '@iota/iota-sdk/utils';
import { Calendar, StarHex } from '@iota/ui-icons';
import { useQueryClient } from '@tanstack/react-query';
import { useRouter } from 'next/navigation';
import { useEffect, useState } from 'react';
+import { StakedTimelockObject } from '@/components';
function VestingDashboardPage(): JSX.Element {
const account = useCurrentAccount();
const queryClient = useQueryClient();
const iotaClient = useIotaClient();
const router = useRouter();
+ const { data: system } = useIotaClientQuery('getLatestIotaSystemState');
const [isVestingScheduleDialogOpen, setIsVestingScheduleDialogOpen] = useState(false);
const { addNotification } = useNotifications();
const { openPopup, closePopup } = usePopups();
@@ -73,7 +82,10 @@ function VestingDashboardPage(): JSX.Element {
const { data: timelockedObjects } = useGetAllOwnedObjects(account?.address || '', {
StructType: TIMELOCK_IOTA_TYPE,
});
- const { data: timelockedStakedObjects } = useGetTimelockedStakedObjects(account?.address || '');
+
+ const { data: timelockedStakedObjects, isLoading: istimelockedStakedObjectsLoading } =
+ useGetTimelockedStakedObjects(account?.address || '');
+
const { mutateAsync: signAndExecuteTransaction } = useSignAndExecuteTransaction();
const { theme } = useTheme();
@@ -151,6 +163,16 @@ function VestingDashboardPage(): JSX.Element {
);
}
+ const [totalStakedFormatted, totalStakedSymbol] = useFormatCoin(
+ vestingSchedule.totalStaked,
+ IOTA_TYPE_ARG,
+ );
+
+ const [totalEarnedFormatted, totalEarnedSymbol] = useFormatCoin(
+ vestingSchedule.totalEarned,
+ IOTA_TYPE_ARG,
+ );
+
const unlockedTimelockedObjects = timelockedMapped?.filter((timelockedObject) =>
isTimelockedUnlockable(timelockedObject, Number(currentEpochMs)),
);
@@ -232,75 +254,85 @@ function VestingDashboardPage(): JSX.Element {
router.push('/');
}
}, [router, supplyIncreaseVestingEnabled]);
+
+ if (istimelockedStakedObjectsLoading) {
+ return (
+
+
+
+ );
+ }
+
return (
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {vestingPortfolio && (
+
+ )}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- {vestingPortfolio && (
-
- )}
-
-
- {timelockedstakedMapped.length === 0 ? (
- <>
+
+
+ {timelockedstakedMapped.length === 0 ? (
handleNewStake()}
buttonText="Stake"
/>
- >
- ) : (
-
-
Staked Vesting
-
-
- Your stake
- {vestingSchedule.totalStaked}
+ ) : null}
+
+
+ {timelockedstakedMapped.length !== 0 ? (
+
+
+ {
+ setStakeDialogView(StakeDialogView.SelectValidator);
+ }}
+ />
+ }
+ />
+
+
-
-
Total Unlocked
-
{vestingSchedule.totalUnlocked}
+
+
+ {system &&
+ timelockedStakedObjectsGrouped?.map(
+ (timelockedStakedObject) => {
+ return (
+
+ );
+ },
+ )}
+
-
-
- {timelockedStakedObjectsGrouped?.map((timelockedStakedObject) => {
- return (
-
-
- Validator:{' '}
- {getValidatorByAddress(
- timelockedStakedObject.validatorAddress,
- )?.name || timelockedStakedObject.validatorAddress}
-
-
- Stake Request Epoch:{' '}
- {timelockedStakedObject.stakeRequestEpoch}
-
- Stakes: {timelockedStakedObject.stakes.length}
-
-
- );
- })}
-
-
- )}
+ ) : null}
void;
+ getValidatorByAddress: (validatorAddress: string) => IotaValidatorSummary | undefined;
+ currentEpoch: number;
+}
+
+export function StakedTimelockObject({
+ getValidatorByAddress,
+ timelockedStakedObject,
+ handleUnstake,
+ currentEpoch,
+}: StakedTimelockObjectProps) {
+ const name =
+ getValidatorByAddress(timelockedStakedObject.validatorAddress)?.name ||
+ timelockedStakedObject.validatorAddress;
+
+ // TODO probably we could calculate estimated reward on grouping stage.
+ const summary = timelockedStakedObject.stakes.reduce(
+ (acc, stake) => {
+ const estimatedReward = stake.status === 'Active' ? stake.estimatedReward : 0;
+
+ return {
+ principal: BigInt(stake.principal) + acc.principal,
+ estimatedReward: BigInt(estimatedReward) + acc.estimatedReward,
+ stakeRequestEpoch: stake.stakeRequestEpoch,
+ };
+ },
+ {
+ principal: 0n,
+ estimatedReward: 0n,
+ stakeRequestEpoch: '',
+ },
+ );
+
+ const supportingText = useStakeRewardStatus({
+ currentEpoch,
+ stakeRequestEpoch: summary.stakeRequestEpoch,
+ estimatedReward: summary.estimatedReward,
+ inactiveValidator: false,
+ });
+
+ const [sumPrincipalFormatted, sumPrincipalSymbol] = useFormatCoin(
+ summary.principal,
+ IOTA_TYPE_ARG,
+ );
+
+ return (
+ handleUnstake(timelockedStakedObject)}>
+
+
+
+
+
+
+ );
+}
diff --git a/apps/wallet-dashboard/components/staked-timelock-object/index.ts b/apps/wallet-dashboard/components/staked-timelock-object/index.ts
new file mode 100644
index 00000000000..2be2613f3ef
--- /dev/null
+++ b/apps/wallet-dashboard/components/staked-timelock-object/index.ts
@@ -0,0 +1,4 @@
+// Copyright (c) 2024 IOTA Stiftung
+// SPDX-License-Identifier: Apache-2.0
+
+export * from './StakedTimelockObject';
diff --git a/apps/wallet-dashboard/lib/interfaces/vesting.interface.ts b/apps/wallet-dashboard/lib/interfaces/vesting.interface.ts
index c997986f065..ba846d71e6c 100644
--- a/apps/wallet-dashboard/lib/interfaces/vesting.interface.ts
+++ b/apps/wallet-dashboard/lib/interfaces/vesting.interface.ts
@@ -14,10 +14,11 @@ export interface SupplyIncreaseVestingPayout {
export type SupplyIncreaseVestingPortfolio = SupplyIncreaseVestingPayout[];
export interface VestingOverview {
- totalVested: number;
- totalUnlocked: number;
- totalLocked: number;
- totalStaked: number;
- availableClaiming: number;
- availableStaking: number;
+ totalVested: bigint;
+ totalUnlocked: bigint;
+ totalLocked: bigint;
+ totalStaked: bigint;
+ totalEarned: bigint;
+ availableClaiming: bigint;
+ availableStaking: bigint;
}
diff --git a/apps/wallet-dashboard/lib/utils/vesting/vesting.spec.ts b/apps/wallet-dashboard/lib/utils/vesting/vesting.spec.ts
index 5e1cea77657..e0bb1f37bd5 100644
--- a/apps/wallet-dashboard/lib/utils/vesting/vesting.spec.ts
+++ b/apps/wallet-dashboard/lib/utils/vesting/vesting.spec.ts
@@ -21,6 +21,10 @@ import {
const MOCKED_CURRENT_EPOCH_TIMESTAMP = Date.now() + MILLISECONDS_PER_HOUR * 6; // 6 hours later
+function bigIntRound(n: number) {
+ return BigInt(Math.floor(n));
+}
+
describe('get last supply increase vesting payout', () => {
it('should get the object with highest expirationTimestampMs', () => {
const timelockedObjects = MOCKED_SUPPLY_INCREASE_VESTING_TIMELOCKED_OBJECTS;
@@ -121,11 +125,12 @@ describe('vesting overview', () => {
it('should get correct vesting overview data with timelocked objects', () => {
const timelockedObjects = MOCKED_SUPPLY_INCREASE_VESTING_TIMELOCKED_OBJECTS;
const lastPayout = timelockedObjects[timelockedObjects.length - 1];
- const totalAmount =
+ const totalAmount = bigIntRound(
(SUPPLY_INCREASE_STAKER_VESTING_DURATION *
SUPPLY_INCREASE_VESTING_PAYOUTS_IN_1_YEAR *
lastPayout.locked.value) /
- 0.9;
+ 0.9,
+ );
const vestingOverview = getVestingOverview(timelockedObjects, Date.now());
expect(vestingOverview.totalVested).toEqual(totalAmount);
@@ -140,25 +145,31 @@ describe('vesting overview', () => {
const lockedAmount = vestingPortfolio.reduce(
(acc, current) =>
- current.expirationTimestampMs > Date.now() ? acc + current.amount : acc,
- 0,
+ current.expirationTimestampMs > Date.now()
+ ? acc + bigIntRound(current.amount)
+ : acc,
+ 0n,
);
expect(vestingOverview.totalLocked).toEqual(lockedAmount);
expect(vestingOverview.totalUnlocked).toEqual(totalAmount - lockedAmount);
// In this scenario there are no staked objects
- expect(vestingOverview.totalStaked).toEqual(0);
+ expect(vestingOverview.totalStaked).toEqual(0n);
const lockedObjectsAmount = timelockedObjects.reduce(
(acc, current) =>
- current.expirationTimestampMs > Date.now() ? acc + current.locked.value : acc,
- 0,
+ current.expirationTimestampMs > Date.now()
+ ? acc + bigIntRound(current.locked.value)
+ : acc,
+ 0n,
);
const unlockedObjectsAmount = timelockedObjects.reduce(
(acc, current) =>
- current.expirationTimestampMs <= Date.now() ? acc + current.locked.value : acc,
- 0,
+ current.expirationTimestampMs <= Date.now()
+ ? acc + bigIntRound(current.locked.value)
+ : acc,
+ 0n,
);
expect(vestingOverview.availableClaiming).toEqual(unlockedObjectsAmount);
@@ -172,11 +183,13 @@ describe('vesting overview', () => {
const lastPayout =
extendedTimelockedStakedObjects[extendedTimelockedStakedObjects.length - 1];
const lastPayoutValue = Number(lastPayout.principal);
- const totalAmount =
+ const totalAmount = bigIntRound(
(SUPPLY_INCREASE_STAKER_VESTING_DURATION *
SUPPLY_INCREASE_VESTING_PAYOUTS_IN_1_YEAR *
lastPayoutValue) /
- 0.9;
+ 0.9,
+ );
+
const vestingOverview = getVestingOverview(extendedTimelockedStakedObjects, Date.now());
expect(vestingOverview.totalVested).toEqual(totalAmount);
@@ -190,18 +203,20 @@ describe('vesting overview', () => {
const lockedAmount = vestingPortfolio.reduce(
(acc, current) =>
- current.expirationTimestampMs > Date.now() ? acc + current.amount : acc,
- 0,
+ current.expirationTimestampMs > Date.now()
+ ? acc + bigIntRound(current.amount)
+ : acc,
+ 0n,
);
expect(vestingOverview.totalLocked).toEqual(lockedAmount);
expect(vestingOverview.totalUnlocked).toEqual(totalAmount - lockedAmount);
- let totalStaked: number = 0;
+ let totalStaked = 0n;
for (const timelockedStakedObject of timelockedStakedObjects) {
const stakesAmount = timelockedStakedObject.stakes.reduce(
- (acc, current) => acc + Number(current.principal),
- 0,
+ (acc, current) => acc + bigIntRound(Number(current.principal)),
+ 0n,
);
totalStaked += stakesAmount;
}
@@ -209,8 +224,8 @@ describe('vesting overview', () => {
expect(vestingOverview.totalStaked).toEqual(totalStaked);
// In this scenario there are no objects to stake or claim because they are all staked
- expect(vestingOverview.availableClaiming).toEqual(0);
- expect(vestingOverview.availableStaking).toEqual(0);
+ expect(vestingOverview.availableClaiming).toEqual(0n);
+ expect(vestingOverview.availableStaking).toEqual(0n);
});
it('should get correct vesting overview data with mixed objects', () => {
@@ -224,11 +239,12 @@ describe('vesting overview', () => {
mixedObjects,
MOCKED_CURRENT_EPOCH_TIMESTAMP,
)!;
- const totalAmount =
+ const totalAmount = bigIntRound(
(SUPPLY_INCREASE_STAKER_VESTING_DURATION *
SUPPLY_INCREASE_VESTING_PAYOUTS_IN_1_YEAR *
lastPayout.amount) /
- 0.9;
+ 0.9,
+ );
const vestingOverview = getVestingOverview(mixedObjects, Date.now());
expect(vestingOverview.totalVested).toEqual(totalAmount);
@@ -243,16 +259,18 @@ describe('vesting overview', () => {
const lockedAmount = vestingPortfolio.reduce(
(acc, current) =>
- current.expirationTimestampMs > Date.now() ? acc + current.amount : acc,
- 0,
+ current.expirationTimestampMs > Date.now()
+ ? acc + bigIntRound(current.amount)
+ : acc,
+ 0n,
);
expect(vestingOverview.totalLocked).toEqual(lockedAmount);
expect(vestingOverview.totalUnlocked).toEqual(totalAmount - lockedAmount);
const totalStaked = extendedTimelockedStakedObjects.reduce(
- (acc, current) => acc + Number(current.principal),
- 0,
+ (acc, current) => acc + bigIntRound(Number(current.principal)),
+ 0n,
);
expect(vestingOverview.totalStaked).toEqual(totalStaked);
@@ -260,13 +278,17 @@ describe('vesting overview', () => {
const timelockObjects = mixedObjects.filter(isTimelockedObject);
const availableClaiming = timelockObjects.reduce(
(acc, current) =>
- current.expirationTimestampMs <= Date.now() ? acc + current.locked.value : acc,
- 0,
+ current.expirationTimestampMs <= Date.now()
+ ? acc + bigIntRound(current.locked.value)
+ : acc,
+ 0n,
);
const availableStaking = timelockObjects.reduce(
(acc, current) =>
- current.expirationTimestampMs > Date.now() ? acc + current.locked.value : acc,
- 0,
+ current.expirationTimestampMs > Date.now()
+ ? acc + bigIntRound(current.locked.value)
+ : acc,
+ 0n,
);
expect(vestingOverview.availableClaiming).toEqual(availableClaiming);
expect(vestingOverview.availableStaking).toEqual(availableStaking);
diff --git a/apps/wallet-dashboard/lib/utils/vesting/vesting.ts b/apps/wallet-dashboard/lib/utils/vesting/vesting.ts
index c4bd743b5ef..e0bb8a29158 100644
--- a/apps/wallet-dashboard/lib/utils/vesting/vesting.ts
+++ b/apps/wallet-dashboard/lib/utils/vesting/vesting.ts
@@ -149,52 +149,61 @@ export function getVestingOverview(
if (vestingObjects.length === 0 || !latestPayout) {
return {
- totalVested: 0,
- totalUnlocked: 0,
- totalLocked: 0,
- totalStaked: 0,
- availableClaiming: 0,
- availableStaking: 0,
+ totalVested: 0n,
+ totalUnlocked: 0n,
+ totalLocked: 0n,
+ totalStaked: 0n,
+ totalEarned: 0n,
+ availableClaiming: 0n,
+ availableStaking: 0n,
};
}
const userType = getSupplyIncreaseVestingUserType([latestPayout]);
const vestingPayoutsCount = getSupplyIncreaseVestingPayoutsCount(userType!);
// Note: we add the initial payout to the total rewards, 10% of the total rewards are paid out immediately
- const totalVestedAmount = (vestingPayoutsCount * latestPayout.amount) / 0.9;
+ const totalVestedAmount = BigInt(Math.floor((vestingPayoutsCount * latestPayout.amount) / 0.9));
const vestingPortfolio = buildSupplyIncreaseVestingSchedule(
latestPayout,
currentEpochTimestamp,
);
const totalLockedAmount = vestingPortfolio.reduce(
(acc, current) =>
- current.expirationTimestampMs > currentEpochTimestamp ? acc + current.amount : acc,
- 0,
+ current.expirationTimestampMs > currentEpochTimestamp
+ ? acc + BigInt(current.amount)
+ : acc,
+ 0n,
);
const totalUnlockedVestedAmount = totalVestedAmount - totalLockedAmount;
const timelockedStakedObjects = vestingObjects.filter(isTimelockedStakedIota);
const totalStaked = timelockedStakedObjects.reduce(
- (acc, current) => acc + Number(current.principal),
- 0,
+ (acc, current) => acc + BigInt(current.principal),
+ 0n,
);
+ const totalEarned = timelockedStakedObjects
+ .filter((t) => t.status === 'Active')
+ .reduce((acc, current) => {
+ return acc + BigInt(current.estimatedReward);
+ }, 0n);
+
const timelockedObjects = vestingObjects.filter(isTimelockedObject);
const totalAvailableClaimingAmount = timelockedObjects.reduce(
(acc, current) =>
current.expirationTimestampMs <= currentEpochTimestamp
- ? acc + current.locked.value
+ ? acc + BigInt(current.locked.value)
: acc,
- 0,
+ 0n,
);
const totalAvailableStakingAmount = timelockedObjects.reduce(
(acc, current) =>
current.expirationTimestampMs > currentEpochTimestamp &&
current.locked.value >= MIN_STAKING_THRESHOLD
- ? acc + current.locked.value
+ ? acc + BigInt(current.locked.value)
: acc,
- 0,
+ 0n,
);
return {
@@ -202,6 +211,7 @@ export function getVestingOverview(
totalUnlocked: totalUnlockedVestedAmount,
totalLocked: totalLockedAmount,
totalStaked: totalStaked,
+ totalEarned: totalEarned,
availableClaiming: totalAvailableClaimingAmount,
availableStaking: totalAvailableStakingAmount,
};