diff --git a/apps/core/src/hooks/useCountdownByTimestamp.ts b/apps/core/src/hooks/useCountdownByTimestamp.ts index 3b79b69b915..18a5a598292 100644 --- a/apps/core/src/hooks/useCountdownByTimestamp.ts +++ b/apps/core/src/hooks/useCountdownByTimestamp.ts @@ -9,7 +9,17 @@ import { MILLISECONDS_PER_SECOND, } from '../constants'; -export function useCountdownByTimestamp(initialTimestamp: number | null): string { +interface FormatCountdownOptions { + showSeconds?: boolean; + showMinutes?: boolean; + showHours?: boolean; + showDays?: boolean; +} + +export function useCountdownByTimestamp( + initialTimestamp: number | null, + options?: FormatCountdownOptions, +): string { const [timeRemainingMs, setTimeRemainingMs] = useState(0); useEffect(() => { @@ -22,11 +32,19 @@ export function useCountdownByTimestamp(initialTimestamp: number | null): string return () => clearInterval(interval); }, [initialTimestamp]); - const formattedCountdown = formatCountdown(timeRemainingMs); + const formattedCountdown = formatCountdown(timeRemainingMs, options); return formattedCountdown; } -function formatCountdown(totalMilliseconds: number) { +function formatCountdown( + totalMilliseconds: number, + { + showSeconds = true, + showMinutes = true, + showHours = true, + showDays = true, + }: FormatCountdownOptions = {}, +) { const days = Math.floor(totalMilliseconds / MILLISECONDS_PER_DAY); const hours = Math.floor((totalMilliseconds % MILLISECONDS_PER_DAY) / MILLISECONDS_PER_HOUR); const minutes = Math.floor( @@ -36,11 +54,11 @@ function formatCountdown(totalMilliseconds: number) { (totalMilliseconds % MILLISECONDS_PER_MINUTE) / MILLISECONDS_PER_SECOND, ); - const timeUnits = []; - if (days > 0) timeUnits.push(`${days}d`); - if (hours > 0) timeUnits.push(`${hours}h`); - if (minutes > 0) timeUnits.push(`${minutes}m`); - if (seconds > 0 || timeUnits.length === 0) timeUnits.push(`${seconds}s`); + const timeUnits: string[] = []; + if (showDays && days > 0) timeUnits.push(`${days}d`); + if (showHours && hours > 0) timeUnits.push(`${hours}h`); + if (showMinutes && minutes > 0) timeUnits.push(`${minutes}m`); + if (showSeconds && (seconds > 0 || timeUnits.length === 0)) timeUnits.push(`${seconds}s`); return timeUnits.join(' '); } diff --git a/apps/wallet-dashboard/app/(protected)/home/page.tsx b/apps/wallet-dashboard/app/(protected)/home/page.tsx index 61f55475702..774edce294c 100644 --- a/apps/wallet-dashboard/app/(protected)/home/page.tsx +++ b/apps/wallet-dashboard/app/(protected)/home/page.tsx @@ -8,6 +8,7 @@ import { TransactionsOverview, StakingOverview, MigrationOverview, + SupplyIncreaseVestingOverview, } from '@/components'; import { useFeature } from '@growthbook/growthbook-react'; import { Feature } from '@iota/core'; @@ -18,6 +19,7 @@ function HomeDashboardPage(): JSX.Element { const account = useCurrentAccount(); const stardustMigrationEnabled = useFeature(Feature.StardustMigration).value; + const supplyIncreaseVestingEnabled = useFeature(Feature.SupplyIncreaseVesting).value; return (
@@ -34,9 +36,7 @@ function HomeDashboardPage(): JSX.Element {
-
- Vesting -
+ {supplyIncreaseVestingEnabled && }
diff --git a/apps/wallet-dashboard/app/(protected)/vesting/page.tsx b/apps/wallet-dashboard/app/(protected)/vesting/page.tsx index cf53a40fe85..e29d94a7fef 100644 --- a/apps/wallet-dashboard/app/(protected)/vesting/page.tsx +++ b/apps/wallet-dashboard/app/(protected)/vesting/page.tsx @@ -13,17 +13,8 @@ import { } from '@/components'; import { UnstakeDialogView } from '@/components/Dialogs/unstake/enums'; import { useUnstakeDialog } from '@/components/Dialogs/unstake/hooks'; -import { useGetCurrentEpochStartTimestamp, useNotifications } from '@/hooks'; -import { - buildSupplyIncreaseVestingSchedule, - formatDelegatedTimelockedStake, - getLatestOrEarliestSupplyIncreaseVestingPayout, - getVestingOverview, - groupTimelockedStakedObjects, - isTimelockedUnlockable, - mapTimelockObjects, - TimelockedStakedObjectsGrouped, -} from '@/lib/utils'; +import { useGetSupplyIncreaseVestingObjects, useNotifications } from '@/hooks'; +import { groupTimelockedStakedObjects, TimelockedStakedObjectsGrouped } from '@/lib/utils'; import { NotificationType } from '@/stores/notificationStore'; import { useFeature } from '@growthbook/growthbook-react'; import { @@ -46,13 +37,9 @@ import { } from '@iota/apps-ui-kit'; import { Theme, - TIMELOCK_IOTA_TYPE, useFormatCoin, useGetActiveValidatorsInfo, - useGetAllOwnedObjects, - useGetTimelockedStakedObjects, useTheme, - useUnlockTimelockedObjectsTransaction, useCountdownByTimestamp, Feature, } from '@iota/core'; @@ -74,24 +61,13 @@ export default function VestingDashboardPage(): JSX.Element { const [timelockedObjectsToUnstake, setTimelockedObjectsToUnstake] = useState(null); const account = useCurrentAccount(); + const address = account?.address || ''; const iotaClient = useIotaClient(); const router = useRouter(); const { data: system } = useIotaClientQuery('getLatestIotaSystemState'); const [isVestingScheduleDialogOpen, setIsVestingScheduleDialogOpen] = useState(false); const { addNotification } = useNotifications(); - const { data: currentEpochMs } = useGetCurrentEpochStartTimestamp(); const { data: activeValidators } = useGetActiveValidatorsInfo(); - const { data: timelockedObjects, refetch: refetchGetAllOwnedObjects } = useGetAllOwnedObjects( - account?.address || '', - { - StructType: TIMELOCK_IOTA_TYPE, - }, - ); - const { - data: timelockedStakedObjects, - isLoading: istimelockedStakedObjectsLoading, - refetch: refetchTimelockedStakedObjects, - } = useGetTimelockedStakedObjects(account?.address || ''); const { mutateAsync: signAndExecuteTransaction } = useSignAndExecuteTransaction(); const { theme } = useTheme(); @@ -102,16 +78,19 @@ export default function VestingDashboardPage(): JSX.Element { const supplyIncreaseVestingEnabled = useFeature(Feature.SupplyIncreaseVesting).value; - const timelockedMapped = mapTimelockObjects(timelockedObjects || []); - const timelockedstakedMapped = formatDelegatedTimelockedStake(timelockedStakedObjects || []); + const { + nextPayout, + supplyIncreaseVestingPortfolio, + supplyIncreaseVestingSchedule, + supplyIncreaseVestingMapped, + supplyIncreaseVestingStakedMapped, + isTimelockedStakedObjectsLoading, + unlockAllSupplyIncreaseVesting, + refreshStakeList, + } = useGetSupplyIncreaseVestingObjects(address); const timelockedStakedObjectsGrouped: TimelockedStakedObjectsGrouped[] = - groupTimelockedStakedObjects(timelockedstakedMapped || []); - - const vestingSchedule = getVestingOverview( - [...timelockedMapped, ...timelockedstakedMapped], - Number(currentEpochMs), - ); + groupTimelockedStakedObjects(supplyIncreaseVestingStakedMapped || []); const { isDialogStakeOpen, @@ -132,37 +111,22 @@ export default function VestingDashboardPage(): JSX.Element { setView: setUnstakeDialogView, } = useUnstakeDialog(); - const nextPayout = getLatestOrEarliestSupplyIncreaseVestingPayout( - [...timelockedMapped, ...timelockedstakedMapped], - Number(currentEpochMs), - false, - ); - - const lastPayout = getLatestOrEarliestSupplyIncreaseVestingPayout( - [...timelockedMapped, ...timelockedstakedMapped], - Number(currentEpochMs), - true, - ); - - const vestingPortfolio = - lastPayout && buildSupplyIncreaseVestingSchedule(lastPayout, Number(currentEpochMs)); - const formattedLastPayoutExpirationTime = useCountdownByTimestamp( Number(nextPayout?.expirationTimestampMs), ); const [formattedTotalVested, vestedSymbol] = useFormatCoin( - vestingSchedule.totalVested, + supplyIncreaseVestingSchedule.totalVested, IOTA_TYPE_ARG, ); const [formattedTotalLocked, lockedSymbol] = useFormatCoin( - vestingSchedule.totalLocked, + supplyIncreaseVestingSchedule.totalLocked, IOTA_TYPE_ARG, ); const [formattedAvailableClaiming, availableClaimingSymbol] = useFormatCoin( - vestingSchedule.availableClaiming, + supplyIncreaseVestingSchedule.availableClaiming, IOTA_TYPE_ARG, ); @@ -178,30 +142,15 @@ export default function VestingDashboardPage(): JSX.Element { } const [totalStakedFormatted, totalStakedSymbol] = useFormatCoin( - vestingSchedule.totalStaked, + supplyIncreaseVestingSchedule.totalStaked, IOTA_TYPE_ARG, ); const [totalEarnedFormatted, totalEarnedSymbol] = useFormatCoin( - vestingSchedule.totalEarned, + supplyIncreaseVestingSchedule.totalEarned, IOTA_TYPE_ARG, ); - const unlockedTimelockedObjects = timelockedMapped?.filter((timelockedObject) => - isTimelockedUnlockable(timelockedObject, Number(currentEpochMs)), - ); - const unlockedTimelockedObjectIds: string[] = - unlockedTimelockedObjects.map((timelocked) => timelocked.id.id) || []; - const { data: unlockAllTimelockedObjects } = useUnlockTimelockedObjectsTransaction( - account?.address || '', - unlockedTimelockedObjectIds, - ); - - function refreshStakeList() { - refetchTimelockedStakedObjects(); - refetchGetAllOwnedObjects(); - } - function handleOnSuccess(digest: string): void { setTimelockedObjectsToUnstake(null); @@ -213,13 +162,13 @@ export default function VestingDashboardPage(): JSX.Element { } const handleCollect = () => { - if (!unlockAllTimelockedObjects?.transactionBlock) { + if (!unlockAllSupplyIncreaseVesting?.transactionBlock) { addNotification('Failed to create a Transaction', NotificationType.Error); return; } signAndExecuteTransaction( { - transaction: unlockAllTimelockedObjects.transactionBlock, + transaction: unlockAllSupplyIncreaseVesting.transactionBlock, }, { onSuccess: (tx) => { @@ -258,7 +207,7 @@ export default function VestingDashboardPage(): JSX.Element { } }, [router, supplyIncreaseVestingEnabled]); - if (istimelockedStakedObjectsLoading) { + if (isTimelockedStakedObjectsLoading) { return (
@@ -304,8 +253,8 @@ export default function VestingDashboardPage(): JSX.Element { title="Collect" buttonType={ButtonType.Primary} buttonDisabled={ - !vestingSchedule.availableClaiming || - vestingSchedule.availableClaiming === 0n + !supplyIncreaseVestingSchedule.availableClaiming || + supplyIncreaseVestingSchedule.availableClaiming === 0n } /> @@ -329,20 +278,20 @@ export default function VestingDashboardPage(): JSX.Element { onClick={openReceiveTokenDialog} title="See All" buttonType={ButtonType.Secondary} - buttonDisabled={!vestingPortfolio} + buttonDisabled={!supplyIncreaseVestingPortfolio} /> - {vestingPortfolio && ( + {supplyIncreaseVestingPortfolio && ( )}
- {timelockedstakedMapped.length === 0 ? ( + {supplyIncreaseVestingMapped.length === 0 ? ( - {timelockedstakedMapped.length !== 0 ? ( + {supplyIncreaseVestingMapped.length !== 0 ? (
{ setStakeDialogView(StakeDialogView.SelectValidator); }} @@ -422,8 +373,9 @@ export default function VestingDashboardPage(): JSX.Element { setView={setStakeDialogView} selectedValidator={selectedValidator} setSelectedValidator={setSelectedValidator} - maxStakableTimelockedAmount={BigInt(vestingSchedule.availableStaking)} - onUnstakeClick={openUnstakeDialog} + maxStakableTimelockedAmount={BigInt( + supplyIncreaseVestingSchedule.availableStaking, + )} /> )} diff --git a/apps/wallet-dashboard/app/globals.css b/apps/wallet-dashboard/app/globals.css index 04c6811f389..2d87e758bc0 100644 --- a/apps/wallet-dashboard/app/globals.css +++ b/apps/wallet-dashboard/app/globals.css @@ -24,7 +24,24 @@ body { 'balance' 'staking' 'coins' + 'activity'; + + & + > *:where( + [style*='grid-area: balance'], + [style*='grid-area: staking'], + [style*='grid-area: migration'] + ) { + height: 200px; + } + } + .home-page-grid-container:has(.with-vesting) { + @apply grid grid-cols-1 gap-lg; + grid-template-areas: + 'balance' + 'staking' 'vesting' + 'coins' 'activity'; & @@ -42,7 +59,16 @@ body { 'staking' 'migration' 'coins' + 'activity'; + } + .home-page-grid-container:has(.with-migration), + .home-page-grid-container:has(.with-vesting) { + grid-template-areas: + 'balance' + 'staking' + 'migration' 'vesting' + 'coins' 'activity'; } @@ -53,7 +79,15 @@ body { 'balance balance' 'staking staking' 'coins coins' + 'activity activity'; + } + .home-page-grid-container:has(.with-vesting) { + @apply grid-cols-2; + grid-template-areas: + 'balance balance' + 'staking staking' 'vesting vesting' + 'coins coins' 'activity activity'; } .home-page-grid-container:has(.with-migration) { @@ -61,7 +95,15 @@ body { 'balance balance' 'staking migration' 'coins coins' + 'activity activity'; + } + .home-page-grid-container:has(.with-migration), + .home-page-grid-container:has(.with-vesting) { + grid-template-areas: + 'balance balance' + 'staking migration' 'vesting vesting' + 'coins coins' 'activity activity'; } } @@ -69,12 +111,24 @@ body { @screen md { .home-page-grid-container { @apply grid-cols-3; + grid-template-areas: + 'balance staking staking' + 'coins activity activity'; + } + .home-page-grid-container:has(.with-vesting) { grid-template-areas: 'balance staking staking' 'coins vesting vesting' 'coins activity activity'; } .home-page-grid-container:has(.with-migration) { + @apply grid-cols-3; + grid-template-areas: + 'balance staking migration' + 'coins activity activity'; + } + .home-page-grid-container:has(.with-migration), + .home-page-grid-container:has(.with-vesting) { grid-template-areas: 'balance staking migration' 'coins vesting vesting' diff --git a/apps/wallet-dashboard/components/SupplyIncreaseVestingOverview.tsx b/apps/wallet-dashboard/components/SupplyIncreaseVestingOverview.tsx new file mode 100644 index 00000000000..7608bda1a3a --- /dev/null +++ b/apps/wallet-dashboard/components/SupplyIncreaseVestingOverview.tsx @@ -0,0 +1,151 @@ +// Copyright (c) 2024 IOTA Stiftung +// SPDX-License-Identifier: Apache-2.0 + +import { useCurrentAccount, useIotaClient } from '@iota/dapp-kit'; +import { useGetSupplyIncreaseVestingObjects } from '@/hooks'; +import { + ButtonType, + Card, + CardAction, + CardActionType, + CardBody, + CardType, + LabelText, + LabelTextSize, + Panel, + Title, +} from '@iota/apps-ui-kit'; +import { StakeDialog, useStakeDialog } from './Dialogs'; +import { TIMELOCK_IOTA_TYPE, useCountdownByTimestamp, useFormatCoin } from '@iota/core'; +import { IOTA_TYPE_ARG } from '@iota/iota-sdk/utils'; +import SvgClock from '@iota/ui-icons/src/Clock'; +import { useQueryClient } from '@tanstack/react-query'; + +export function SupplyIncreaseVestingOverview() { + const account = useCurrentAccount(); + const address = account?.address || ''; + const iotaClient = useIotaClient(); + const queryClient = useQueryClient(); + const { + nextPayout, + supplyIncreaseVestingSchedule, + supplyIncreaseVestingMapped, + supplyIncreaseVestingStakedMapped, + } = useGetSupplyIncreaseVestingObjects(address); + + const { + stakeDialogView, + setStakeDialogView, + selectedStake, + selectedValidator, + setSelectedValidator, + handleCloseStakeDialog, + handleNewStake, + } = useStakeDialog(); + + const formattedLastPayoutExpirationTime = useCountdownByTimestamp( + Number(nextPayout?.expirationTimestampMs), + { showSeconds: false, showMinutes: false }, + ); + const [formattedNextPayout, nextPayoutSymbol, nextPayoutResult] = useFormatCoin( + nextPayout?.amount, + IOTA_TYPE_ARG, + ); + + const [formattedAvailableStaking, availableStakingSymbol] = useFormatCoin( + supplyIncreaseVestingSchedule.availableStaking, + IOTA_TYPE_ARG, + ); + + const showSupplyIncreaseVestingOverview = + supplyIncreaseVestingMapped.length > 0 || supplyIncreaseVestingStakedMapped.length > 0; + + function handleOnSuccess(digest: string): void { + iotaClient + .waitForTransaction({ + digest, + }) + .then(() => { + queryClient.invalidateQueries({ + queryKey: ['get-timelocked-staked-objects', account?.address], + }); + queryClient.invalidateQueries({ + queryKey: [ + 'get-all-owned-objects', + account?.address, + { + StructType: TIMELOCK_IOTA_TYPE, + }, + ], + }); + }); + } + + return showSupplyIncreaseVestingOverview ? ( + <div style={{ gridArea: 'vesting' }} className="with-vesting flex grow overflow-hidden"> + <Panel> + <Title title="Vesting" /> + <div className="flex h-full w-full items-center gap-md p-md--rs"> + <div className="w-1/2"> + <Card type={CardType.Filled}> + <CardBody + title="" + subtitle={ + <LabelText + size={LabelTextSize.Large} + label="Next reward" + text={ + nextPayoutResult.isPending + ? '-' + : `${formattedNextPayout} ` + } + supportingLabel={nextPayoutSymbol} + /> + } + /> + <CardAction + type={CardActionType.Button} + buttonType={ButtonType.Ghost} + title={formattedLastPayoutExpirationTime} + icon={<SvgClock />} + /> + </Card> + </div> + <div className="w-1/2"> + <Card type={CardType.Filled}> + <CardBody + title="" + subtitle={ + <LabelText + size={LabelTextSize.Large} + label="Available for staking" + text={formattedAvailableStaking} + supportingLabel={availableStakingSymbol} + /> + } + /> + <CardAction + type={CardActionType.Button} + buttonType={ButtonType.Primary} + title={'Stake'} + onClick={() => handleNewStake()} + buttonDisabled={!supplyIncreaseVestingSchedule.availableStaking} + /> + </Card> + </div> + </div> + </Panel> + <StakeDialog + isTimelockedStaking={true} + stakedDetails={selectedStake} + onSuccess={handleOnSuccess} + handleClose={handleCloseStakeDialog} + view={stakeDialogView} + setView={setStakeDialogView} + selectedValidator={selectedValidator} + setSelectedValidator={setSelectedValidator} + maxStakableTimelockedAmount={BigInt(supplyIncreaseVestingSchedule.availableStaking)} + /> + </div> + ) : null; +} diff --git a/apps/wallet-dashboard/components/index.ts b/apps/wallet-dashboard/components/index.ts index e39bf76a350..fcccf7b3a95 100644 --- a/apps/wallet-dashboard/components/index.ts +++ b/apps/wallet-dashboard/components/index.ts @@ -27,4 +27,5 @@ export * from './Toaster'; export * from './Banner'; export * from './StakeRewardsPanel'; export * from './MigrationOverview'; +export * from './SupplyIncreaseVestingOverview'; export * from './staked-timelock-object'; diff --git a/apps/wallet-dashboard/hooks/index.ts b/apps/wallet-dashboard/hooks/index.ts index 2c8fed3331f..5ad872afeed 100644 --- a/apps/wallet-dashboard/hooks/index.ts +++ b/apps/wallet-dashboard/hooks/index.ts @@ -10,5 +10,6 @@ export * from './useGetCurrentEpochStartTimestamp'; export * from './useTimelockedUnstakeTransaction'; export * from './useExplorerLinkGetter'; export * from './useGetStardustMigratableObjects'; +export * from './useGetSupplyIncreaseVestingObjects'; export * from './useGroupedMigrationObjectsByExpirationDate'; export * from './useTransferTransaction'; diff --git a/apps/wallet-dashboard/hooks/useGetSupplyIncreaseVestingObjects.ts b/apps/wallet-dashboard/hooks/useGetSupplyIncreaseVestingObjects.ts new file mode 100644 index 00000000000..58d19d63641 --- /dev/null +++ b/apps/wallet-dashboard/hooks/useGetSupplyIncreaseVestingObjects.ts @@ -0,0 +1,112 @@ +// Copyright (c) 2024 IOTA Stiftung +// SPDX-License-Identifier: Apache-2.0 + +import { useGetCurrentEpochStartTimestamp } from '@/hooks'; +import { + SupplyIncreaseVestingPayout, + SupplyIncreaseVestingPortfolio, + TimelockedObject, + VestingOverview, +} from '@/lib/interfaces'; +import { + buildSupplyIncreaseVestingSchedule, + ExtendedDelegatedTimelockedStake, + formatDelegatedTimelockedStake, + getLatestOrEarliestSupplyIncreaseVestingPayout, + getVestingOverview, + isSupplyIncreaseVestingObject, + isTimelockedUnlockable, + mapTimelockObjects, +} from '@/lib/utils'; +import { + TIMELOCK_IOTA_TYPE, + useGetAllOwnedObjects, + useGetTimelockedStakedObjects, + useUnlockTimelockedObjectsTransaction, +} from '@iota/core'; +import { Transaction } from '@iota/iota-sdk/transactions'; + +export function useGetSupplyIncreaseVestingObjects(address: string): { + nextPayout: SupplyIncreaseVestingPayout | undefined; + lastPayout: SupplyIncreaseVestingPayout | undefined; + supplyIncreaseVestingSchedule: VestingOverview; + supplyIncreaseVestingPortfolio: SupplyIncreaseVestingPortfolio | undefined; + supplyIncreaseVestingMapped: TimelockedObject[]; + supplyIncreaseVestingStakedMapped: ExtendedDelegatedTimelockedStake[]; + isTimelockedStakedObjectsLoading: boolean; + unlockAllSupplyIncreaseVesting: + | { + transactionBlock: Transaction; + } + | undefined; + refreshStakeList: () => void; +} { + const { data: currentEpochMs } = useGetCurrentEpochStartTimestamp(); + + const { data: timelockedObjects, refetch: refetchGetAllOwnedObjects } = useGetAllOwnedObjects( + address || '', + { + StructType: TIMELOCK_IOTA_TYPE, + }, + ); + const { + data: timelockedStakedObjects, + isLoading: isTimelockedStakedObjectsLoading, + refetch: refetchTimelockedStakedObjects, + } = useGetTimelockedStakedObjects(address || ''); + + const supplyIncreaseVestingMapped = mapTimelockObjects(timelockedObjects || []).filter( + isSupplyIncreaseVestingObject, + ); + const supplyIncreaseVestingStakedMapped = formatDelegatedTimelockedStake( + timelockedStakedObjects || [], + ).filter(isSupplyIncreaseVestingObject); + + const supplyIncreaseVestingSchedule = getVestingOverview( + [...supplyIncreaseVestingMapped, ...supplyIncreaseVestingStakedMapped], + Number(currentEpochMs), + ); + + const nextPayout = getLatestOrEarliestSupplyIncreaseVestingPayout( + [...supplyIncreaseVestingMapped, ...supplyIncreaseVestingStakedMapped], + Number(currentEpochMs), + false, + ); + + const lastPayout = getLatestOrEarliestSupplyIncreaseVestingPayout( + [...supplyIncreaseVestingMapped, ...supplyIncreaseVestingStakedMapped], + Number(currentEpochMs), + true, + ); + + const supplyIncreaseVestingPortfolio = + lastPayout && buildSupplyIncreaseVestingSchedule(lastPayout, Number(currentEpochMs)); + + const supplyIncreaseVestingUnlocked = supplyIncreaseVestingMapped?.filter( + (supplyIncreaseVestingObject) => + isTimelockedUnlockable(supplyIncreaseVestingObject, Number(currentEpochMs)), + ); + const supplyIncreaseVestingUnlockedObjectIds: string[] = + supplyIncreaseVestingUnlocked.map((unlockedObject) => unlockedObject.id.id) || []; + const { data: unlockAllSupplyIncreaseVesting } = useUnlockTimelockedObjectsTransaction( + address || '', + supplyIncreaseVestingUnlockedObjectIds, + ); + + function refreshStakeList() { + refetchTimelockedStakedObjects(); + refetchGetAllOwnedObjects(); + } + + return { + nextPayout, + lastPayout, + supplyIncreaseVestingSchedule, + supplyIncreaseVestingPortfolio, + supplyIncreaseVestingMapped, + supplyIncreaseVestingStakedMapped, + isTimelockedStakedObjectsLoading, + unlockAllSupplyIncreaseVesting, + refreshStakeList, + }; +}