diff --git a/apps/core/src/hooks/useGetDelegatedStake.tsx b/apps/core/src/hooks/stake/useGetDelegatedStake.tsx
similarity index 100%
rename from apps/core/src/hooks/useGetDelegatedStake.tsx
rename to apps/core/src/hooks/stake/useGetDelegatedStake.tsx
diff --git a/apps/core/src/hooks/stake/useTotalDelegatedRewards.ts b/apps/core/src/hooks/stake/useTotalDelegatedRewards.ts
new file mode 100644
index 00000000000..f7523c1c191
--- /dev/null
+++ b/apps/core/src/hooks/stake/useTotalDelegatedRewards.ts
@@ -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]);
+}
diff --git a/apps/core/src/hooks/stake/useTotalDelegatedStake.ts b/apps/core/src/hooks/stake/useTotalDelegatedStake.ts
new file mode 100644
index 00000000000..25182bc3688
--- /dev/null
+++ b/apps/core/src/hooks/stake/useTotalDelegatedStake.ts
@@ -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]);
+}
diff --git a/apps/core/src/index.ts b/apps/core/src/index.ts
index fa8d5df3d49..c2521436690 100644
--- a/apps/core/src/index.ts
+++ b/apps/core/src/index.ts
@@ -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';
diff --git a/apps/core/src/utils/stake/formatDelegatedStake.ts b/apps/core/src/utils/stake/formatDelegatedStake.ts
new file mode 100644
index 00000000000..fbdeee85013
--- /dev/null
+++ b/apps/core/src/utils/stake/formatDelegatedStake.ts
@@ -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,
+ };
+ });
+ });
+}
diff --git a/apps/explorer/src/pages/address-result/TotalStaked.tsx b/apps/explorer/src/pages/address-result/TotalStaked.tsx
index 21704fc9799..b5f4f78e53c 100644
--- a/apps/explorer/src/pages/address-result/TotalStaked.tsx
+++ b/apps/explorer/src/pages/address-result/TotalStaked.tsx
@@ -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';
@@ -13,18 +17,14 @@ 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 ? (
@@ -32,7 +32,7 @@ export function TotalStaked({ address }: { address: string }): JSX.Element | nul
Staking
- {formatted} {symbol}
+ {queryResultStake.isPending ? '-' : `${formattedDelegatedStake} ${symbol}`}
diff --git a/apps/wallet-dashboard/app/dashboard/staking/page.tsx b/apps/wallet-dashboard/app/dashboard/staking/page.tsx
index 4442bd8da9a..e2959c454ec 100644
--- a/apps/wallet-dashboard/app/dashboard/staking/page.tsx
+++ b/apps/wallet-dashboard/app/dashboard/staking/page.tsx
@@ -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();
};
@@ -35,22 +45,29 @@ function StakingDashboardPage(): JSX.Element {
return (
+
+
+
+
+
List of stakes
+ {delegatedStakes?.map((stake) => (
+
+ ))}
+
+
-
);
}
diff --git a/apps/wallet-dashboard/components/Cards/StakeCard.tsx b/apps/wallet-dashboard/components/Cards/StakeCard.tsx
new file mode 100644
index 00000000000..04b70f687d6
--- /dev/null
+++ b/apps/wallet-dashboard/components/Cards/StakeCard.tsx
@@ -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 (
+
+ Validator: {stake.validatorAddress}
+ Stake: {stake.principal}
+ {stake.status === 'Active' && Estimated reward: {stake.estimatedReward}
}
+ Status: {stake.status}
+
+
+ );
+}
+
+export default StakeCard;
diff --git a/apps/wallet-dashboard/components/Cards/index.ts b/apps/wallet-dashboard/components/Cards/index.ts
index 665e7e0e190..59d8c5dd155 100644
--- a/apps/wallet-dashboard/components/Cards/index.ts
+++ b/apps/wallet-dashboard/components/Cards/index.ts
@@ -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';
diff --git a/apps/wallet-dashboard/components/List.tsx b/apps/wallet-dashboard/components/List.tsx
deleted file mode 100644
index cfd32ea32ad..00000000000
--- a/apps/wallet-dashboard/components/List.tsx
+++ /dev/null
@@ -1,37 +0,0 @@
-// Copyright (c) 2024 IOTA Stiftung
-// SPDX-License-Identifier: Apache-2.0
-
-import React from 'react';
-import { Button } from '.';
-
-interface ListProps {
- data: { id: string; [key: string]: React.ReactNode }[];
- title?: string;
- onItemClick?: (item: { id: string; [key: string]: React.ReactNode }) => void;
- actionText?: string;
-}
-
-function List({ data, title, onItemClick, actionText }: ListProps): JSX.Element {
- return (
-
- {title &&
{title}
}
-
- {data.map((item) => (
- -
- {Object.entries(item).map(([key, value]) => (
-
- ))}
- {onItemClick && (
-
- )}
-
- ))}
-
-
- );
-}
-
-export default List;
diff --git a/apps/wallet-dashboard/components/Popup/Popups/StakeDetailsPopup.tsx b/apps/wallet-dashboard/components/Popup/Popups/StakeDetailsPopup.tsx
index bdbece7d390..cb5388d652d 100644
--- a/apps/wallet-dashboard/components/Popup/Popups/StakeDetailsPopup.tsx
+++ b/apps/wallet-dashboard/components/Popup/Popups/StakeDetailsPopup.tsx
@@ -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 {
@@ -28,9 +24,12 @@ function StakeDetailsPopup({ stake }: StakeDetailsPopupProps): JSX.Element {
return (
-
{stake.validator}
-
Stake: {stake.stake}
-
Rewards: {stake.rewards}
+
{stake.validatorAddress}
+
Stake: {stake.principal}
+
Stake Active Epoch: {stake.stakeActiveEpoch}
+
Stake Request Epoch: {stake.stakeRequestEpoch}
+ {stake.status === 'Active' &&
Estimated reward: {stake.estimatedReward}
}
+
Status: {stake.status}
diff --git a/apps/wallet-dashboard/components/Popup/Popups/UnstakePopup.tsx b/apps/wallet-dashboard/components/Popup/Popups/UnstakePopup.tsx
index a1bfed4c2b6..6fb7f4f8f61 100644
--- a/apps/wallet-dashboard/components/Popup/Popups/UnstakePopup.tsx
+++ b/apps/wallet-dashboard/components/Popup/Popups/UnstakePopup.tsx
@@ -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 (
-
{validator}
-
Stake: {stake}
-
Rewards: {rewards}
-
+
{stake.validatorAddress}
+
Stake: {stake.principal}
+ {stake.status === 'Active' &&
Estimated reward: {stake.estimatedReward}
}
+
);
}
diff --git a/apps/wallet-dashboard/components/index.ts b/apps/wallet-dashboard/components/index.ts
index 5c82a2d8a89..cbbec6ddbd7 100644
--- a/apps/wallet-dashboard/components/index.ts
+++ b/apps/wallet-dashboard/components/index.ts
@@ -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';
diff --git a/apps/wallet/src/ui/app/pages/home/tokens/TokenIconLink.tsx b/apps/wallet/src/ui/app/pages/home/tokens/TokenIconLink.tsx
index 7a414f8b429..816559af6d6 100644
--- a/apps/wallet/src/ui/app/pages/home/tokens/TokenIconLink.tsx
+++ b/apps/wallet/src/ui/app/pages/home/tokens/TokenIconLink.tsx
@@ -9,10 +9,14 @@ import {
DELEGATED_STAKES_QUERY_STALE_TIME,
} from '_src/shared/constants';
import { Text } from '_src/ui/app/shared/text';
-import { useFormatCoin, useGetDelegatedStake } from '@iota/core';
+import {
+ formatDelegatedStake,
+ useFormatCoin,
+ useGetDelegatedStake,
+ useTotalDelegatedStake,
+} from '@iota/core';
import { WalletActionStake24 } from '@iota/icons';
import { IOTA_TYPE_ARG } from '@iota/iota.js/utils';
-import { useMemo } from 'react';
export function TokenIconLink({
accountAddress,
@@ -28,41 +32,37 @@ export function TokenIconLink({
});
// 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 [formatted, symbol, queryResult] = useFormatCoin(totalActivePendingStake, IOTA_TYPE_ARG);
+ const delegatedStakes = delegatedStake ? formatDelegatedStake(delegatedStake) : [];
+ const totalDelegatedStake = useTotalDelegatedStake(delegatedStakes);
+ const [formattedDelegatedStake, symbol, queryResultStake] = useFormatCoin(
+ totalDelegatedStake,
+ IOTA_TYPE_ARG,
+ );
return (
{
ampli.clickedStakeIota({
- isCurrentlyStaking: totalActivePendingStake > 0,
+ isCurrentlyStaking: totalDelegatedStake > 0,
sourceFlow: 'Home page',
});
}}
- loading={isPending || queryResult.isPending}
+ loading={isPending || queryResultStake.isPending}
before={}
- data-testid={`stake-button-${formatted}-${symbol}`}
+ data-testid={`stake-button-${formattedDelegatedStake}-${symbol}`}
>
- {totalActivePendingStake ? 'Currently Staked' : 'Stake and Earn IOTA'}
+ {totalDelegatedStake ? 'Currently Staked' : 'Stake and Earn IOTA'}
- {!!totalActivePendingStake && (
+ {!!totalDelegatedStake && (
- {formatted} {symbol}
+ {formattedDelegatedStake} {symbol}
)}
diff --git a/apps/wallet/src/ui/app/staking/home/StakedCard.tsx b/apps/wallet/src/ui/app/staking/home/StakedCard.tsx
index 039bfd1189b..1f729700966 100644
--- a/apps/wallet/src/ui/app/staking/home/StakedCard.tsx
+++ b/apps/wallet/src/ui/app/staking/home/StakedCard.tsx
@@ -6,8 +6,11 @@ import { NUM_OF_EPOCH_BEFORE_STAKING_REWARDS_REDEEMABLE } from '_src/shared/cons
import { CountDownTimer } from '_src/ui/app/shared/countdown-timer';
import { Text } from '_src/ui/app/shared/text';
import { IconTooltip } from '_src/ui/app/shared/tooltip';
-import { useFormatCoin, useGetTimeBeforeEpochNumber } from '@iota/core';
-import { type StakeObject } from '@iota/iota.js/client';
+import {
+ useFormatCoin,
+ useGetTimeBeforeEpochNumber,
+ type ExtendedDelegatedStake,
+} from '@iota/core';
import { IOTA_TYPE_ARG } from '@iota/iota.js/utils';
import { cva, cx, type VariantProps } from 'class-variance-authority';
import type { ReactNode } from 'react';
@@ -39,10 +42,6 @@ const STATUS_VARIANT = {
[StakeState.IN_ACTIVE]: 'inActive',
} as const;
-export type DelegationObjectWithValidator = Extract & {
- validatorAddress: string;
-};
-
const cardStyle = cva(
[
'group flex no-underline flex-col p-3.75 pr-2 py-3 box-border w-full rounded-2xl border border-solid h-36',
@@ -107,7 +106,7 @@ function StakeCardContent({
}
interface StakeCardProps {
- delegationObject: DelegationObjectWithValidator;
+ delegationObject: ExtendedDelegatedStake;
currentEpoch: number;
inactiveValidator?: boolean;
}
diff --git a/apps/wallet/src/ui/app/staking/validators/ValidatorsCard.tsx b/apps/wallet/src/ui/app/staking/validators/ValidatorsCard.tsx
index ea4f6bab264..100679744a2 100644
--- a/apps/wallet/src/ui/app/staking/validators/ValidatorsCard.tsx
+++ b/apps/wallet/src/ui/app/staking/validators/ValidatorsCard.tsx
@@ -13,21 +13,24 @@ import {
DELEGATED_STAKES_QUERY_REFETCH_INTERVAL,
DELEGATED_STAKES_QUERY_STALE_TIME,
} from '_src/shared/constants';
-import { useGetDelegatedStake } from '@iota/core';
+import {
+ formatDelegatedStake,
+ useGetDelegatedStake,
+ useTotalDelegatedRewards,
+ useTotalDelegatedStake,
+} from '@iota/core';
import { useIotaClientQuery } from '@iota/dapp-kit';
import { Plus12 } from '@iota/icons';
-import type { StakeObject } from '@iota/iota.js/client';
import { useMemo } from 'react';
import { useActiveAddress } from '../../hooks/useActiveAddress';
-import { getAllStakeIota } from '../getAllStakeIota';
import { StakeAmount } from '../home/StakeAmount';
-import { StakeCard, type DelegationObjectWithValidator } from '../home/StakedCard';
+import { StakeCard } from '../home/StakedCard';
export function ValidatorsCard() {
const accountAddress = useActiveAddress();
const {
- data: delegatedStake,
+ data: delegatedStakeData,
isPending,
isError,
error,
@@ -39,15 +42,13 @@ export function ValidatorsCard() {
const { data: system } = useIotaClientQuery('getLatestIotaSystemState');
const activeValidators = system?.activeValidators;
+ const delegatedStake = delegatedStakeData ? formatDelegatedStake(delegatedStakeData) : [];
// Total active stake for all Staked validators
- const totalStake = useMemo(() => {
- if (!delegatedStake) return 0n;
- return getAllStakeIota(delegatedStake);
- }, [delegatedStake]);
+ const totalDelegatedStake = useTotalDelegatedStake(delegatedStake);
const delegations = useMemo(() => {
- return delegatedStake?.flatMap((delegation) => {
+ return delegatedStakeData?.flatMap((delegation) => {
return delegation.stakes.map((d) => ({
...d,
// flag any inactive validator for the stakeIota object
@@ -65,23 +66,11 @@ export function ValidatorsCard() {
({ inactiveValidator }) => inactiveValidator,
);
- // Get total rewards for all delegations
- const totalEarnTokenReward = useMemo(() => {
- if (!delegatedStake || !activeValidators) return 0n;
- return (
- delegatedStake.reduce(
- (acc, curr) =>
- curr.stakes.reduce(
- (total, { estimatedReward }: StakeObject & { estimatedReward?: string }) =>
- total + BigInt(estimatedReward || 0),
- acc,
- ),
- 0n,
- ) || 0n
- );
- }, [delegatedStake, activeValidators]);
+ // // Get total rewards for all delegations
+ const delegatedStakes = delegatedStakeData ? formatDelegatedStake(delegatedStakeData) : [];
+ const totalDelegatedRewards = useTotalDelegatedRewards(delegatedStakes);
- const numberOfValidators = delegatedStake?.length || 0;
+ const numberOfValidators = delegatedStakeData?.length || 0;
if (isPending) {
return (
@@ -120,9 +109,7 @@ export function ValidatorsCard() {
?.filter(({ inactiveValidator }) => inactiveValidator)
.map((delegation) => (
-
+
@@ -164,9 +151,7 @@ export function ValidatorsCard() {
?.filter(({ inactiveValidator }) => !inactiveValidator)
.map((delegation) => (