Skip to content

Commit

Permalink
fix(wallet,wallet-dashboard): fix total stake/unstake amount in timel…
Browse files Browse the repository at this point in the history
…ocks transaction summary (#4824)

* fix: display banner correctly

* fix total stake/unstake amount in timelocks transaction summary

* feat: update to use events instead of event and move logic to StakeTransactionDetails

* feat: use reduce

* fix: display correctly unstake amount

* remove principalAmount and rewardAmount

* fix: improvements

* feat: improve naming
  • Loading branch information
evavirseda authored Jan 23, 2025
1 parent 9527118 commit a85d8cd
Show file tree
Hide file tree
Showing 12 changed files with 120 additions and 78 deletions.
4 changes: 2 additions & 2 deletions apps/core/src/components/transaction/TransactionReceipt.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ export function TransactionReceipt({
{stakeTypeTransaction ? (
<StakeTransactionDetails
activeAddress={activeAddress}
event={stakeTypeTransaction}
events={events ?? []}
gasSummary={summary?.gas}
renderExplorerLink={renderExplorerLink}
/>
Expand All @@ -54,7 +54,7 @@ export function TransactionReceipt({
{unstakeTypeTransaction ? (
<UnstakeTransactionInfo
activeAddress={activeAddress}
event={unstakeTypeTransaction}
events={events ?? []}
gasSummary={summary?.gas}
renderExplorerLink={renderExplorerLink}
/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

import { CardType } from '@iota/apps-ui-kit';
import { IotaEvent } from '@iota/iota-sdk/client';
import { formatPercentageDisplay, getStakeDetailsFromEvent } from '../../../utils';
import { formatPercentageDisplay, getStakeDetailsFromEvents } from '../../../utils';
import { useGetValidatorsApy } from '../../../hooks';
import { TransactionAmount } from '../amount';
import { StakeTransactionInfo } from '../info';
Expand All @@ -12,19 +12,21 @@ import { Validator } from '../../../';
import type { GasSummaryType, RenderExplorerLink } from '../../../types';

interface StakeTransactionDetailsProps {
event: IotaEvent;
events: IotaEvent[];
activeAddress: string | null;
renderExplorerLink: RenderExplorerLink;
gasSummary?: GasSummaryType;
}

export function StakeTransactionDetails({
event,
events,
gasSummary,
activeAddress,
renderExplorerLink,
}: StakeTransactionDetailsProps) {
const { stakedAmount, validatorAddress, epoch } = getStakeDetailsFromEvent(event);
const stakeDetails = getStakeDetailsFromEvents(events);

const { totalStakedAmount, validatorAddress, epoch } = stakeDetails;
const { data: rollingAverageApys } = useGetValidatorsApy();
const { apy, isApyApproxZero } = rollingAverageApys?.[validatorAddress] ?? {
apy: null,
Expand All @@ -42,9 +44,9 @@ export function StakeTransactionDetails({
isSelected
/>
)}
{stakedAmount && (
{totalStakedAmount && (
<TransactionAmount
amount={stakedAmount}
amount={totalStakedAmount}
coinType={IOTA_TYPE_ARG}
subtitle="Stake"
/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,38 +8,41 @@ import { IOTA_TYPE_ARG } from '@iota/iota-sdk/utils';
import type { GasSummaryType, RenderExplorerLink } from '../../../types';
import { useFormatCoin } from '../../../hooks';
import { Divider, KeyValueInfo, Panel, CardType } from '@iota/apps-ui-kit';
import { GasSummary, getUnstakeDetailsFromEvent, Validator } from '../../..';
import { GasSummary, getUnstakeDetailsFromEvents, Validator } from '../../..';

interface UnstakeTransactionInfoProps {
activeAddress: string | null;
event: IotaEvent;
events: IotaEvent[];
renderExplorerLink: RenderExplorerLink;
gasSummary?: GasSummaryType;
}

export function UnstakeTransactionInfo({
activeAddress,
event,
events,
gasSummary,
renderExplorerLink,
}: UnstakeTransactionInfoProps) {
const { principalAmount, rewardAmount, totalAmount, validatorAddress } =
getUnstakeDetailsFromEvent(event);

const [formatPrinciple, symbol] = useFormatCoin(principalAmount, IOTA_TYPE_ARG);
const [formatRewards] = useFormatCoin(rewardAmount || 0, IOTA_TYPE_ARG);
const unstakeDetails = getUnstakeDetailsFromEvents(events);
const { totalUnstakeAmount, validatorAddress, unstakeAmount, unstakeRewards } = unstakeDetails;

const [formatTotalAmountWithoutRewards, symbol] = useFormatCoin(unstakeAmount, IOTA_TYPE_ARG);
const [formatRewards] = useFormatCoin(unstakeRewards || 0, IOTA_TYPE_ARG);
return (
<div className="flex flex-col gap-y-md">
{validatorAddress && <Validator address={validatorAddress} type={CardType.Filled} />}
{totalAmount !== 0n && (
<TransactionAmount amount={totalAmount} coinType={IOTA_TYPE_ARG} subtitle="Total" />
{totalUnstakeAmount !== 0n && (
<TransactionAmount
amount={totalUnstakeAmount}
coinType={IOTA_TYPE_ARG}
subtitle="Total"
/>
)}
<Panel hasBorder>
<div className="flex flex-col gap-y-sm p-md">
<KeyValueInfo
keyText="Your Stake"
value={`${formatPrinciple} ${symbol}`}
value={`${formatTotalAmountWithoutRewards} ${symbol}`}
fullwidth
/>
<KeyValueInfo
Expand Down
18 changes: 0 additions & 18 deletions apps/core/src/utils/stake/getStakeDetailsFromEvent.ts

This file was deleted.

24 changes: 24 additions & 0 deletions apps/core/src/utils/stake/getStakeDetailsFromEvents.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
// Copyright (c) 2024 IOTA Stiftung
// SPDX-License-Identifier: Apache-2.0

import { STAKING_REQUEST_EVENT } from '../../constants';
import { StakeEventJson } from '../../interfaces';
import type { IotaEvent } from '@iota/iota-sdk/client';

export function getStakeDetailsFromEvents(events: IotaEvent[]): {
totalStakedAmount: string;
validatorAddress: string;
epoch: number;
} {
const stakeEvent = events.find((event) => event.type === STAKING_REQUEST_EVENT);

const eventJson = stakeEvent?.parsedJson as StakeEventJson;
const totalStakedAmount = events?.reduce((sum, event) => {
return sum + Number((event.parsedJson as { amount: number }).amount || 0);
}, 0);
return {
totalStakedAmount: totalStakedAmount.toString(),
validatorAddress: eventJson.validator_address || '',
epoch: Number(eventJson.epoch || '0'),
};
}
17 changes: 7 additions & 10 deletions apps/core/src/utils/stake/getTransactionAmountForTimelocked.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,23 +2,20 @@
// SPDX-License-Identifier: Apache-2.0

import type { IotaEvent } from '@iota/iota-sdk/client';
import {
getStakeDetailsFromEvent,
getUnstakeDetailsFromEvent,
checkIfIsTimelockedStaking,
} from '.';
import { getUnstakeDetailsFromEvents, getStakeDetailsFromEvents } from '.';

export function getTransactionAmountForTimelocked(
events: IotaEvent[],
isTimelockedStaking: boolean,
isTimelockedUnstaking: boolean,
): bigint | undefined | string {
if (!events) return;
const { isTimelockedStaking, isTimelockedUnstaking } = checkIfIsTimelockedStaking(events);

if (isTimelockedStaking) {
const { stakedAmount } = getStakeDetailsFromEvent(events[0]);
return stakedAmount;
const { totalStakedAmount } = getStakeDetailsFromEvents(events);
return totalStakedAmount;
} else if (isTimelockedUnstaking) {
const { totalAmount } = getUnstakeDetailsFromEvent(events[0]);
return totalAmount;
const { totalUnstakeAmount } = getUnstakeDetailsFromEvents(events);
return totalUnstakeAmount;
}
}
23 changes: 0 additions & 23 deletions apps/core/src/utils/stake/getUnstakeDetailsFromEvent.ts

This file was deleted.

35 changes: 35 additions & 0 deletions apps/core/src/utils/stake/getUnstakeDetailsFromEvents.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
// Copyright (c) 2024 IOTA Stiftung
// SPDX-License-Identifier: Apache-2.0

import { UNSTAKING_REQUEST_EVENT } from '../../constants';
import { UnstakeEventJson } from '../../interfaces';
import type { IotaEvent } from '@iota/iota-sdk/client';

export function getUnstakeDetailsFromEvents(events: IotaEvent[]): {
validatorAddress: string;
unstakeAmount: bigint;
totalUnstakeAmount: bigint;
unstakeRewards: bigint;
} {
const unstakeEvent = events.find(({ type }) => type === UNSTAKING_REQUEST_EVENT);
const unstakeEventJson = unstakeEvent?.parsedJson as UnstakeEventJson;

const unstakeAmount = events?.reduce((sum, event) => {
return (
sum + Number((event.parsedJson as { principal_amount: number }).principal_amount || 0)
);
}, 0);

const unstakeRewards = events?.reduce((sum, event) => {
return sum + Number((event.parsedJson as { reward_amount: number }).reward_amount || 0);
}, 0);

const totalUnstakeAmount = BigInt(unstakeAmount) + BigInt(unstakeRewards);

return {
validatorAddress: unstakeEventJson.validator_address || '',
unstakeAmount: BigInt(unstakeAmount),
unstakeRewards: BigInt(unstakeRewards),
totalUnstakeAmount,
};
}
4 changes: 2 additions & 2 deletions apps/core/src/utils/stake/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ export * from './createStakeTransaction';
export * from './createTimelockedUnstakeTransaction';
export * from './createTimelockedStakeTransaction';
export * from './createValidationSchema';
export * from './getStakeDetailsFromEvent';
export * from './getStakeDetailsFromEvents';
export * from './checkIfIsTimelockedStaking';
export * from './getUnstakeDetailsFromEvent';
export * from './getUnstakeDetailsFromEvents';
export * from './getTransactionAmountForTimelocked';
4 changes: 3 additions & 1 deletion apps/wallet-dashboard/app/(protected)/vesting/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ export default function VestingDashboardPage(): JSX.Element {
unlockAllSupplyIncreaseVesting,
refreshStakeList,
isSupplyIncreaseVestingScheduleEmpty,
supplyIncreaseVestingMapped,
} = useGetSupplyIncreaseVestingObjects(address);

const timelockedStakedObjectsGrouped: TimelockedStakedObjectsGrouped[] =
Expand Down Expand Up @@ -297,7 +298,8 @@ export default function VestingDashboardPage(): JSX.Element {
</div>
</Panel>

{isSupplyIncreaseVestingScheduleEmpty ? (
{supplyIncreaseVestingMapped.length > 0 &&
supplyIncreaseVestingSchedule.totalStaked === 0n ? (
<Banner
videoSrc={videoSrc}
title="Stake Vested Tokens"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,11 @@ export function TransactionTile({ transaction }: TransactionTileProps): JSX.Elem

function getAmount(tx: ExtendedTransaction) {
if ((isTimelockedStaking || isTimelockedUnstaking) && tx.raw.events) {
return getTransactionAmountForTimelocked(tx.raw.events);
return getTransactionAmountForTimelocked(
tx.raw.events,
isTimelockedStaking,
isTimelockedUnstaking,
);
} else {
return address && balanceChanges?.[address]?.[0]?.amount
? Math.abs(Number(balanceChanges?.[address]?.[0]?.amount))
Expand Down
24 changes: 20 additions & 4 deletions apps/wallet/src/ui/app/components/transactions-card/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ import {
useFormatCoin,
useTransactionSummary,
TransactionIcon,
checkIfIsTimelockedStaking,
getTransactionAmountForTimelocked,
} from '@iota/core';
import type { IotaTransactionBlockResponse } from '@iota/iota-sdk/client';
import { Link } from 'react-router-dom';
Expand Down Expand Up @@ -39,14 +41,28 @@ export function TransactionCard({ txn, address }: TransactionCardProps) {
currentAddress: address,
recognizedPackagesList,
});
const { isTimelockedStaking, isTimelockedUnstaking } = checkIfIsTimelockedStaking(txn.events);

// we only show IOTA Transfer amount or the first non-IOTA transfer amount
// Get the balance changes for the transaction and the amount
const balanceChanges = getBalanceChangeSummary(txn, recognizedPackagesList);
const [formatAmount, symbol] = useFormatCoin(
Math.abs(Number(balanceChanges?.[address]?.[0]?.amount ?? 0)),
IOTA_TYPE_ARG,
);

function getAmount(tx: IotaTransactionBlockResponse) {
if ((isTimelockedStaking || isTimelockedUnstaking) && tx.events) {
return getTransactionAmountForTimelocked(
tx.events,
isTimelockedStaking,
isTimelockedUnstaking,
);
} else {
return address && balanceChanges?.[address]?.[0]?.amount
? Math.abs(Number(balanceChanges?.[address]?.[0]?.amount))
: 0;
}
}

const transactionAmount = getAmount(txn);
const [formatAmount, symbol] = useFormatCoin(transactionAmount, IOTA_TYPE_ARG);

const error = txn.effects?.status.error;

Expand Down

0 comments on commit a85d8cd

Please sign in to comment.