Skip to content

Commit

Permalink
feat(wallet-dashboard): group and display timelocked stakes (#1779)
Browse files Browse the repository at this point in the history
* feat(wallet-dashboard): fetch supply increase timelocked portfolio

* feat(wallet-dashboard): add new route to vesting

* feat(wallet-dashboard): update request timelocked objets to get all

* feat(wallet-dashboard): improve the way to get timelock objects

* fix(wallet-dashboard): remove testing constants

* feat(wallet-dashboard): update filter in get timelocked objects

* fix(wallet-dashboard): remove TESTING_SUPPLY_INCREASE_VESTING_LABEL reference

* feat(wallet-dashboard): hook collect(unwrap) feature to dashboard WIP

* refactor: revert prettier changes coming from last merge

* fix(wallet-dashboard): remove debris

* fix(wallet-dashboard): add current epoch ms to getVestingOverview

* feat(wallet-dashboard): add stale time to get current epoch hook

* feat(wallet-dashboard): add timelock stakes to vesting portfolio logic

* feat(wallet-dashboard): add ptb to unlock all timelocked objects

* fix(wallet-dashboard): improvements

* fix(wallet-dashboard): improve filters to get timelocked objects

* feat(wallet-dashboard): map timelocked staked iota response

* fix(wallet-dashboard): update Date.now by current epoch

* fix(wallet-dashboard): improve transaction to unlock timelocked objects

* fix(wallet-dashboard): improve unlock timelocked objects transaction

* fix(wallet-dashboard): improvements and rename variables

* fix(wallet-dashboard): rename hook

* feat(wallet-dashboard): use new endpoint to get staked timelocked objects

* fix(wallet-dashboard): modify mapped data with the new response

* refactor: separate vesting from timelock

* fix(wallet-dashboard): create a util function to check if a timelocked object is unlocked

* fix(wallet-dashboard): remove redundant "all" in unlockedTimelockedObjects

* fix: mising files from refactor

* fix(wallet-dashboard): remove unnecessary conversion

* refactor: update iota-sdk name

* fix(wallet-dashboard): update logic with new DelegatedTimelockedStake interface

* fix(wallet-dashboard): update test logic with new DelegatedTimelockedStake interface

* feat(wallet-dashboard): add hook to get validators info

* feat(wallet-dashboard): group and display timelocked stakes

* fix(wallet-dashboard): update delegated staked timelocked interface

* fix(wallet-dashboard): rename timelocked staked object and modify the fall back validator name

* fix(wallet-dashboard): group timelocked staked objects

* fix(wallet-dashboard): add waitForTransactionBlock to collect function

* fix(wallet-dashboard): move groupTimelockedStakedObjects to timelock util

* fix(wallet-dashboard): rename useUnlockTimelockedObjects hook

* fix(wallet-dashboard): rename isTimelockedUnlockable function

* fix(wallet-dashboard): vesting tests

* fix(wallet-dashboard): bad merge

* fix(wallet-dashboard): improvement startEpoch value

* fix(wallet-dashboard): rename startEpoch to stakeRequestEpoch in TimelockedStakedObjectsGrouped interface

---------

Co-authored-by: Begoña Álvarez de la Cruz <[email protected]>
Co-authored-by: Bran <[email protected]>
  • Loading branch information
3 people authored Aug 27, 2024
1 parent f221389 commit 6bfce92
Show file tree
Hide file tree
Showing 5 changed files with 140 additions and 31 deletions.
3 changes: 2 additions & 1 deletion apps/core/src/hooks/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ export * from './useGetNFTMeta';
export * from './useIotaAddressValidation';
export * from './useUnlockTimelockedObjectsTransaction';
export * from './useGetAllOwnedObjects';
export * from './useGetStakedTimelockedObjects';
export * from './useGetTimelockedStakedObjects';
export * from './useGetActiveValidatorsInfo';

export * from './stake';
18 changes: 18 additions & 0 deletions apps/core/src/hooks/useGetActiveValidatorsInfo.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
// Copyright (c) 2024 IOTA Stiftung
// SPDX-License-Identifier: Apache-2.0

import { useIotaClient } from '@iota/dapp-kit';
import { useQuery } from '@tanstack/react-query';

export function useGetActiveValidatorsInfo() {
const client = useIotaClient();
return useQuery({
// eslint-disable-next-line @tanstack/query/exhaustive-deps
queryKey: ['get-active-validators-info'],
queryFn: async () => {
const iotaSystemState = await client.getLatestIotaSystemState();
return iotaSystemState.activeValidators;
},
staleTime: 10 * 60 * 1000, // 10 minutes
});
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,10 @@
import { useIotaClient } from '@iota/dapp-kit';
import { useQuery } from '@tanstack/react-query';

export function useGetStakedTimelockedObjects(address: string) {
export function useGetTimelockedStakedObjects(address: string) {
const client = useIotaClient();
return useQuery({
queryKey: ['get-staked-timelocked-objects', address],
queryKey: ['get-timelocked-staked-objects', address],
queryFn: () =>
client.getTimelockedStakes({
owner: address,
Expand Down
111 changes: 83 additions & 28 deletions apps/wallet-dashboard/app/dashboard/vesting/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,17 @@ import { useGetCurrentEpochStartTimestamp, useNotifications } from '@/hooks';
import {
formatDelegatedTimelockedStake,
getVestingOverview,
groupTimelockedStakedObjects,
isTimelockedUnlockable,
mapTimelockObjects,
TimelockedStakedObjectsGrouped,
} from '@/lib/utils';
import { NotificationType } from '@/stores/notificationStore';
import {
TIMELOCK_IOTA_TYPE,
useGetActiveValidatorsInfo,
useGetAllOwnedObjects,
useGetStakedTimelockedObjects,
useGetTimelockedStakedObjects,
useUnlockTimelockedObjectsTransaction,
} from '@iota/core';
import {
Expand All @@ -32,20 +35,32 @@ function VestingDashboardPage(): JSX.Element {

const { addNotification } = useNotifications();
const { data: currentEpochMs } = useGetCurrentEpochStartTimestamp();
const { data: activeValidators } = useGetActiveValidatorsInfo();
const { data: timelockedObjects } = useGetAllOwnedObjects(account?.address || '', {
StructType: TIMELOCK_IOTA_TYPE,
});
const { data: stakedTimelockedObjects } = useGetStakedTimelockedObjects(account?.address || '');
const { data: timelockedStakedObjects } = useGetTimelockedStakedObjects(account?.address || '');
const { mutateAsync: signAndExecuteTransactionBlock } = useSignAndExecuteTransactionBlock();

const timelockedMapped = mapTimelockObjects(timelockedObjects || []);
const stakedTimelockedMapped = formatDelegatedTimelockedStake(stakedTimelockedObjects || []);
const timelockedstakedMapped = formatDelegatedTimelockedStake(timelockedStakedObjects || []);

const timelockedStakedObjectsGrouped: TimelockedStakedObjectsGrouped[] =
groupTimelockedStakedObjects(timelockedstakedMapped || []);

const vestingSchedule = getVestingOverview(
[...timelockedMapped, ...stakedTimelockedMapped],
[...timelockedMapped, ...timelockedstakedMapped],
Number(currentEpochMs),
);

function getValidatorName(validatorAddress: string): string {
return (
activeValidators?.find(
(activeValidator) => activeValidator.iotaAddress === validatorAddress,
)?.name || validatorAddress
);
}

const unlockedTimelockedObjects = timelockedMapped?.filter((timelockedObject) =>
isTimelockedUnlockable(timelockedObject, Number(currentEpochMs)),
);
Expand Down Expand Up @@ -103,35 +118,75 @@ function VestingDashboardPage(): JSX.Element {
console.log('Stake');
};

function handleUnstake(delegatedTimelockedStake: TimelockedStakedObjectsGrouped): void {
// TODO: handle unstake logic
console.info('delegatedTimelockedStake', delegatedTimelockedStake);
}

return (
<div className="flex flex-col items-center justify-center space-y-4 pt-12">
<h1>VESTING</h1>
<div className="flex flex-row space-x-4">
<div className="flex flex-col items-center rounded-lg border p-4">
<span>Total Vested</span>
<span>{vestingSchedule.totalVested}</span>
</div>
<div className="flex flex-col items-center rounded-lg border p-4">
<span>Total Locked</span>
<span>{vestingSchedule.totalLocked}</span>
</div>
<div className="flex flex-col items-center rounded-lg border p-4">
<span>Total Unlocked</span>
<span>{vestingSchedule.totalUnlocked}</span>
<div className="flex flex-row">
<div className="flex w-1/2 flex-col items-center justify-center space-y-4 pt-12">
<h1>VESTING</h1>
<div className="flex flex-row space-x-4">
<div className="flex flex-col items-center rounded-lg border p-4">
<span>Total Vested</span>
<span>{vestingSchedule.totalVested}</span>
</div>
<div className="flex flex-col items-center rounded-lg border p-4">
<span>Total Locked</span>
<span>{vestingSchedule.totalLocked}</span>
</div>
</div>
<div className="flex flex-col items-center rounded-lg border p-4">
<span>Total Staked</span>
<span>{vestingSchedule.totalStaked}</span>
<hr />
<div className="flex flex-row space-x-4">
<div className="flex flex-col items-center rounded-lg border p-4">
<span>Available Claiming</span>
<span>{vestingSchedule.availableClaiming}</span>
</div>
<div className="flex flex-col items-center rounded-lg border p-4">
<span>Available Staking</span>
<span>{vestingSchedule.availableStaking}</span>
</div>
</div>
</div>
<div className="flex flex-row space-x-4">
<div className="flex flex-col items-center rounded-lg border p-4">
<span>Available Claiming</span>
<span>{vestingSchedule.availableClaiming}</span>
<div className="flex w-1/2 flex-col items-center justify-center space-y-4 pt-12">
<h1>Staked Vesting</h1>
<div className="flex flex-row space-x-4">
<div className="flex flex-col items-center rounded-lg border p-4">
<span>Your stake</span>
<span>{vestingSchedule.totalStaked}</span>
</div>
<div className="flex flex-col items-center rounded-lg border p-4">
<span>Total Unlocked</span>
<span>{vestingSchedule.totalUnlocked}</span>
</div>
</div>
<div className="flex flex-col items-center rounded-lg border p-4">
<span>Available Staking</span>
<span>{vestingSchedule.availableStaking}</span>
<div className="flex w-full flex-col items-center justify-center space-y-4 pt-4">
{timelockedStakedObjectsGrouped?.map((timelockedStakedObject) => {
return (
<div
key={
timelockedStakedObject.validatorAddress +
timelockedStakedObject.stakeRequestEpoch +
timelockedStakedObject.label
}
className="flex w-full flex-row items-center justify-center space-x-4"
>
<span>
Validator:{' '}
{getValidatorName(timelockedStakedObject.validatorAddress)}
</span>
<span>
Stake Request Epoch: {timelockedStakedObject.stakeRequestEpoch}
</span>
<span>Stakes: {timelockedStakedObject.stakes.length}</span>

<Button onClick={() => handleUnstake(timelockedStakedObject)}>
Unstake
</Button>
</div>
);
})}
</div>
</div>
{account?.address && (
Expand Down
35 changes: 35 additions & 0 deletions apps/wallet-dashboard/lib/utils/timelock.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,13 @@ export type ExtendedDelegatedTimelockedStake = TimelockedStake & {
stakingPool: string;
};

export type TimelockedStakedObjectsGrouped = {
validatorAddress: string;
stakeRequestEpoch: string;
label: string | null | undefined;
stakes: ExtendedDelegatedTimelockedStake[];
};

export function isTimelockedStakedIota(
obj: TimelockedObject | ExtendedDelegatedTimelockedStake,
): obj is ExtendedDelegatedTimelockedStake {
Expand Down Expand Up @@ -69,3 +76,31 @@ export function formatDelegatedTimelockedStake(
});
});
}

export function groupTimelockedStakedObjects(
extendedDelegatedTimelockedStake: ExtendedDelegatedTimelockedStake[],
): TimelockedStakedObjectsGrouped[] {
const groupedArray: TimelockedStakedObjectsGrouped[] = [];

extendedDelegatedTimelockedStake.forEach((obj) => {
let group = groupedArray.find(
(g) =>
g.validatorAddress === obj.validatorAddress &&
g.stakeRequestEpoch === obj.stakeRequestEpoch &&
g.label === obj.label,
);

if (!group) {
group = {
validatorAddress: obj.validatorAddress,
stakeRequestEpoch: obj.stakeRequestEpoch,
label: obj.label,
stakes: [],
};
groupedArray.push(group);
}
group.stakes.push(obj);
});

return groupedArray;
}

0 comments on commit 6bfce92

Please sign in to comment.