Skip to content

Commit

Permalink
feat(wallet-dashboard): hook logic to staking overview (#459)
Browse files Browse the repository at this point in the history
* feat(wallet-dashboard): hook stake logic and add StakeCard component

* feat(wallet-dashboard): cleanup

* feat(wallet-dasboard): add Stake interface

* feat(wallet-dashboard): remove interface and add a type

* feat(wallet-dashboard): add hook and updtae type

* feat(wallet-dashboard): add props interface

* feat(wallet-dashboard): cleanup code

* feat(wallet-dashboard):remove headers

* apply suggestion and use @iota

* bring back List and better than before

* feat(wallet-dashboard): remove List component

* feat(wallet-dashboard): rename names, update types and rename file

* feat(wallet-dashboard): rename constant

* feat(wallet-dashboard): add stakedIotaId

* feat(wallet-dashboard): remove unnecessary id

* feat(wallet-dashboard): remove derbis

* feat(wallet-dashboard): remove debris

* feat(wallet-dashboard): add total hooks, format util and move hook to different folder

* feat(wallet): update components to use new hooks

* feat(wallet): cleanup

* feat(core):cleanup

* feat(explorer): cleanup

* feat(wallet-dashboard): undo unnecessary change

* feat(apps/core): move to stake folder

* feat(wallet-dashboard): rename variables

* feat(wallet-dashboard): rename type

* feat(wallet-dashboard): fix build

---------

Co-authored-by: Marc Espin <[email protected]>
Co-authored-by: cpl121 <[email protected]>
Co-authored-by: Begoña Álvarez de la Cruz <[email protected]>
  • Loading branch information
4 people authored Jun 19, 2024
1 parent aad5ff9 commit f8d666f
Show file tree
Hide file tree
Showing 16 changed files with 210 additions and 170 deletions.
17 changes: 17 additions & 0 deletions apps/core/src/hooks/stake/useTotalDelegatedRewards.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
// Copyright (c) 2024 IOTA Stiftung
// SPDX-License-Identifier: Apache-2.0

import { useMemo } from 'react';
import { type ExtendedDelegatedStake } from '../../utils/stake/formatDelegatedStake';

export function useTotalDelegatedRewards(delegatedStake: ExtendedDelegatedStake[]) {
return useMemo(() => {
if (!delegatedStake) return 0n;
return delegatedStake.reduce((acc, curr) => {
if (curr.status === 'Active' && curr.estimatedReward) {
return acc + BigInt(curr.estimatedReward);
}
return acc;
}, 0n);
}, [delegatedStake]);
}
12 changes: 12 additions & 0 deletions apps/core/src/hooks/stake/useTotalDelegatedStake.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
// Copyright (c) 2024 IOTA Stiftung
// SPDX-License-Identifier: Apache-2.0

import { useMemo } from 'react';
import { type ExtendedDelegatedStake } from '../../utils/stake/formatDelegatedStake';

export function useTotalDelegatedStake(delegatedStake: ExtendedDelegatedStake[]) {
return useMemo(() => {
if (!delegatedStake) return 0n;
return delegatedStake.reduce((acc, curr) => acc + BigInt(curr.principal), 0n);
}, [delegatedStake]);
}
5 changes: 4 additions & 1 deletion apps/core/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,11 @@ export * from './utils/kiosk';
export * from './hooks/useElementDimensions';
export * from './hooks/useIotaCoinData';
export * from './hooks/useLocalStorage';
export * from './hooks/useGetDelegatedStake';
export * from './hooks/stake/useGetDelegatedStake';
export * from './hooks/useTokenPrice';
export * from './hooks/useKioskClient';
export * from './components/KioskClientProvider';
export * from './utils/stake/formatDelegatedStake';
export * from './hooks/stake/useTotalDelegatedRewards';
export * from './hooks/stake/useTotalDelegatedStake';
export * from './hooks/useQueryTransactionsByAddress';
27 changes: 27 additions & 0 deletions apps/core/src/utils/stake/formatDelegatedStake.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
// Copyright (c) 2024 IOTA Stiftung
// SPDX-License-Identifier: Apache-2.0

import { DelegatedStake, StakeObject } from '@iota/iota.js/client';

export type ExtendedDelegatedStake = StakeObject & {
validatorAddress: string;
estimatedReward?: string;
};

export function formatDelegatedStake(
delegatedStakeData: DelegatedStake[],
): ExtendedDelegatedStake[] {
return delegatedStakeData.flatMap((delegatedStake) => {
return delegatedStake.stakes.map((stake) => {
return {
validatorAddress: delegatedStake.validatorAddress,
estimatedReward: stake.status === 'Active' ? stake.estimatedReward : '',
stakeActiveEpoch: stake.stakeActiveEpoch,
stakeRequestEpoch: stake.stakeRequestEpoch,
status: stake.status,
stakedIotaId: stake.stakedIotaId,
principal: stake.principal,
};
});
});
}
28 changes: 14 additions & 14 deletions apps/explorer/src/pages/address-result/TotalStaked.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,12 @@
// Modifications Copyright (c) 2024 IOTA Stiftung
// SPDX-License-Identifier: Apache-2.0

import { useFormatCoin, useGetDelegatedStake } from '@iota/core';
import { useMemo } from 'react';
import {
formatDelegatedStake,
useFormatCoin,
useGetDelegatedStake,
useTotalDelegatedStake,
} from '@iota/core';
import { IOTA_TYPE_ARG } from '@iota/iota.js/utils';
import { Text, Heading } from '@iota/ui';
import { Iota } from '@iota/icons';
Expand All @@ -13,26 +17,22 @@ export function TotalStaked({ address }: { address: string }): JSX.Element | nul
address,
});

// Total active stake for all delegations
const totalActivePendingStake = useMemo(() => {
if (!delegatedStake) return 0n;
return delegatedStake.reduce(
(acc, curr) =>
curr.stakes.reduce((total, { principal }) => total + BigInt(principal), acc),
0n,
);
}, [delegatedStake]);
const delegatedStakes = delegatedStake ? formatDelegatedStake(delegatedStake) : [];
const totalDelegatedStake = useTotalDelegatedStake(delegatedStakes);
const [formattedDelegatedStake, symbol, queryResultStake] = useFormatCoin(
totalDelegatedStake,
IOTA_TYPE_ARG,
);

const [formatted, symbol] = useFormatCoin(totalActivePendingStake, IOTA_TYPE_ARG);
return totalActivePendingStake ? (
return totalDelegatedStake ? (
<div className="flex min-w-44 items-center justify-start gap-3 rounded-xl bg-white/60 px-4 py-3 backdrop-blur-sm">
<Iota className="flex h-8 w-8 items-center justify-center rounded-full bg-iota-primaryBlue2023 py-1.5 text-white" />
<div className="flex flex-col">
<Text variant="pBody/semibold" color="steel-dark" uppercase>
Staking
</Text>
<Heading variant="heading6/semibold" color="hero-darkest" as="div">
{formatted} {symbol}
{queryResultStake.isPending ? '-' : `${formattedDelegatedStake} ${symbol}`}
</Heading>
</div>
</div>
Expand Down
85 changes: 51 additions & 34 deletions apps/wallet-dashboard/app/dashboard/staking/page.tsx
Original file line number Diff line number Diff line change
@@ -1,31 +1,41 @@
// Copyright (c) 2024 IOTA Stiftung
// SPDX-License-Identifier: Apache-2.0

'use client';

import { AmountBox, Box, List, NewStakePopup, StakeDetailsPopup, Button } from '@/components';
import { AmountBox, Box, StakeCard, NewStakePopup, StakeDetailsPopup, Button } from '@/components';
import { usePopups } from '@/hooks';
import {
ExtendedDelegatedStake,
formatDelegatedStake,
useFormatCoin,
useGetDelegatedStake,
useTotalDelegatedRewards,
useTotalDelegatedStake,
} from '@iota/core';
import { useCurrentAccount } from '@iota/dapp-kit';
import { IOTA_TYPE_ARG } from '@iota/iota.js/utils';

function StakingDashboardPage(): JSX.Element {
const account = useCurrentAccount();
const { openPopup, closePopup } = usePopups();
const { data: delegatedStakeData } = useGetDelegatedStake({
address: account?.address || '',
});

const HARCODED_STAKE_DATA = {
title: 'Your Stake',
value: '100 IOTA',
};
const HARCODED_REWARDS_DATA = {
title: 'Earned',
value: '0.297 IOTA',
};
const HARCODED_STAKING_LIST_TITLE = 'List of stakes';
const HARCODED_STAKING_LIST = [
{ id: '0', validator: 'Validator 1', stake: '50 IOTA', rewards: '0.15 IOTA' },
{ id: '1', validator: 'Validator 2', stake: '30 IOTA', rewards: '0.09 IOTA' },
{ id: '2', validator: 'Validator 3', stake: '20 IOTA', rewards: '0.06 IOTA' },
];

// Use `Stake` when https://github.com/iotaledger/iota/pull/459 gets merged
// @ts-expect-error TODO improve typing here
const viewStakeDetails = (stake) => {
const delegatedStakes = delegatedStakeData ? formatDelegatedStake(delegatedStakeData) : [];
const totalDelegatedStake = useTotalDelegatedStake(delegatedStakes);
const totalDelegatedRewards = useTotalDelegatedRewards(delegatedStakes);
const [formattedDelegatedStake, stakeSymbol, stakeResult] = useFormatCoin(
totalDelegatedStake,
IOTA_TYPE_ARG,
);
const [formattedDelegatedRewards, rewardsSymbol, rewardsResult] = useFormatCoin(
totalDelegatedRewards,
IOTA_TYPE_ARG,
);

const viewStakeDetails = (stake: ExtendedDelegatedStake) => {
openPopup(<StakeDetailsPopup stake={stake} />);
};

Expand All @@ -35,22 +45,29 @@ function StakingDashboardPage(): JSX.Element {

return (
<div className="flex flex-col items-center justify-center gap-4 pt-12">
<AmountBox
title="Currently staked"
amount={stakeResult.isPending ? '-' : `${formattedDelegatedStake} ${stakeSymbol}`}
/>
<AmountBox
title="Earned"
amount={`${
rewardsResult.isPending ? '-' : formattedDelegatedRewards
} ${rewardsSymbol}`}
/>
<Box title="Stakes">
<div className="flex flex-col items-center gap-4">
<h1>List of stakes</h1>
{delegatedStakes?.map((stake) => (
<StakeCard
key={stake.stakedIotaId}
stake={stake}
onDetailsClick={viewStakeDetails}
/>
))}
</div>
</Box>
<Button onClick={addNewStake}>New Stake</Button>
<div className="flex items-center justify-center gap-4">
{' '}
<AmountBox title={HARCODED_STAKE_DATA.title} amount={HARCODED_STAKE_DATA.value} />
<AmountBox
title={HARCODED_REWARDS_DATA.title}
amount={HARCODED_REWARDS_DATA.value}
/>
<Box title={HARCODED_STAKING_LIST_TITLE}>
<List
data={HARCODED_STAKING_LIST}
onItemClick={viewStakeDetails}
actionText="View Details"
/>
</Box>
</div>
</div>
);
}
Expand Down
25 changes: 25 additions & 0 deletions apps/wallet-dashboard/components/Cards/StakeCard.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
// Copyright (c) 2024 IOTA Stiftung
// SPDX-License-Identifier: Apache-2.0

import React from 'react';
import { Box, Button } from '@/components/index';
import { ExtendedDelegatedStake } from '@iota/core';

interface StakeCardProps {
stake: ExtendedDelegatedStake;
onDetailsClick: (stake: ExtendedDelegatedStake) => void;
}

function StakeCard({ stake, onDetailsClick }: StakeCardProps): JSX.Element {
return (
<Box>
<div>Validator: {stake.validatorAddress}</div>
<div>Stake: {stake.principal}</div>
{stake.status === 'Active' && <p>Estimated reward: {stake.estimatedReward}</p>}
<div>Status: {stake.status}</div>
<Button onClick={() => onDetailsClick(stake)}>Details</Button>
</Box>
);
}

export default StakeCard;
1 change: 1 addition & 0 deletions apps/wallet-dashboard/components/Cards/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
// Copyright (c) 2024 IOTA Stiftung
// SPDX-License-Identifier: Apache-2.0

export { default as StakeCard } from './StakeCard';
export { default as AssetCard } from './AssetCard';
37 changes: 0 additions & 37 deletions apps/wallet-dashboard/components/List.tsx

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,10 @@ import React from 'react';
import { Button } from '@/components/index';
import { usePopups } from '@/hooks';
import UnstakePopup from './UnstakePopup';
import { ExtendedDelegatedStake } from '@iota/core';

interface StakeDetailsPopupProps {
stake: {
id: string;
validator: string;
stake: string;
rewards: string;
};
stake: ExtendedDelegatedStake;
}

function StakeDetailsPopup({ stake }: StakeDetailsPopupProps): JSX.Element {
Expand All @@ -28,9 +24,12 @@ function StakeDetailsPopup({ stake }: StakeDetailsPopupProps): JSX.Element {

return (
<div className="flex min-w-[400px] flex-col gap-2">
<p>{stake.validator}</p>
<p>Stake: {stake.stake}</p>
<p>Rewards: {stake.rewards}</p>
<p>{stake.validatorAddress}</p>
<p>Stake: {stake.principal}</p>
<p>Stake Active Epoch: {stake.stakeActiveEpoch}</p>
<p>Stake Request Epoch: {stake.stakeRequestEpoch}</p>
{stake.status === 'Active' && <p>Estimated reward: {stake.estimatedReward}</p>}
<p>Status: {stake.status}</p>
<div className="flex justify-between gap-2">
<Button onClick={openUnstakePopup}>Unstake</Button>
<Button onClick={() => console.log('Stake more')}>Stake more</Button>
Expand Down
21 changes: 7 additions & 14 deletions apps/wallet-dashboard/components/Popup/Popups/UnstakePopup.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,27 +3,20 @@

import React from 'react';
import { Button } from '@/components';
import { ExtendedDelegatedStake } from '@iota/core';

interface UnstakePopupProps {
stake: {
id: string;
validator: string;
stake: string;
rewards: string;
};
stake: ExtendedDelegatedStake;
onUnstake: (id: string) => void;
}

function UnstakePopup({
stake: { id, validator, stake, rewards },
onUnstake,
}: UnstakePopupProps): JSX.Element {
function UnstakePopup({ stake, onUnstake }: UnstakePopupProps): JSX.Element {
return (
<div className="flex min-w-[300px] flex-col gap-2">
<p>{validator}</p>
<p>Stake: {stake}</p>
<p>Rewards: {rewards}</p>
<Button onClick={() => onUnstake(id)}>Confirm Unstake</Button>
<p>{stake.validatorAddress}</p>
<p>Stake: {stake.principal}</p>
{stake.status === 'Active' && <p>Estimated reward: {stake.estimatedReward}</p>}
<Button onClick={() => onUnstake(stake.stakedIotaId)}>Confirm Unstake</Button>
</div>
);
}
Expand Down
1 change: 0 additions & 1 deletion apps/wallet-dashboard/components/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ export { default as Notifications } from './Notifications/Notifications';
export { default as TransactionTile } from './TransactionTile';
export { default as Box } from './Box';
export { default as AmountBox } from './AmountBox';
export { default as List } from './List';
export { default as Input } from './Input';
export { default as VirtualList } from './VirtualList';
export { default as TransactionIcon } from './TransactionIcon';
Expand Down
Loading

0 comments on commit f8d666f

Please sign in to comment.