Skip to content

Commit

Permalink
feat(wallet-dashboard): add staking confirmation screen
Browse files Browse the repository at this point in the history
  • Loading branch information
VmMad committed Nov 14, 2024
1 parent 7f66cd0 commit 1507c81
Show file tree
Hide file tree
Showing 7 changed files with 159 additions and 54 deletions.
10 changes: 6 additions & 4 deletions apps/core/src/hooks/useGetValidatorsApy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,13 @@ import { roundFloat } from '../utils/roundFloat';

const DEFAULT_APY_DECIMALS = 2;

export interface ValidatorApyData {
apy: number;
isApyApproxZero: boolean;
}

export interface ApyByValidator {
[validatorAddress: string]: {
apy: number;
isApyApproxZero: boolean;
};
[validatorAddress: string]: ValidatorApyData;
}
// For small APY, show ~0% instead of 0%
// If APY falls below 0.001, show ~0% instead of 0% since we round to 2 decimal places
Expand Down
4 changes: 2 additions & 2 deletions apps/ui-kit/src/lib/components/organisms/dialog/Dialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ const DialogOverlay = React.forwardRef<
>(({ showCloseIcon, ...props }, ref) => (
<RadixDialog.Overlay
ref={ref}
className="absolute inset-0 z-[99998] bg-shader-neutral-light-48 backdrop-blur-md"
className="fixed inset-0 z-[99998] bg-shader-neutral-light-48 backdrop-blur-md"
{...props}
>
<DialogClose className={cx('fixed right-3 top-3', { hidden: !showCloseIcon })}>
Expand Down Expand Up @@ -70,7 +70,7 @@ const DialogContent = React.forwardRef<
<RadixDialog.Content
ref={ref}
className={cx(
'absolute z-[99999] flex flex-col justify-center overflow-hidden bg-primary-100 dark:bg-neutral-6 md:w-96',
'fixed z-[99999] flex flex-col justify-center overflow-hidden bg-primary-100 dark:bg-neutral-6 md:w-96',
positionClass,
)}
{...props}
Expand Down
28 changes: 20 additions & 8 deletions apps/wallet-dashboard/components/Dialogs/Staking/StakeDialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,19 +21,20 @@ import { useCurrentAccount, useSignAndExecuteTransaction } from '@iota/dapp-kit'
import { IOTA_TYPE_ARG } from '@iota/iota-sdk/utils';
import { NotificationType } from '@/stores/notificationStore';
import { prepareObjectsForTimelockedStakingTransaction } from '@/lib/utils';
import { Dialog } from '@iota/apps-ui-kit';
import { DetailsView, UnstakeView } from './views';
import { Dialog } from '@iota/apps-ui-kit';
import { SuccessScreenView } from './views/ConfirmAndExit';

export enum StakeDialogView {
Details,
SelectValidator,
EnterAmount,
Unstake,
TransactionDetails,
}

interface StakeDialogProps {
isTimelockedStaking?: boolean;
onSuccess?: (digest: string) => void;
isOpen: boolean;
handleClose: () => void;
view: StakeDialogView;
Expand All @@ -42,7 +43,6 @@ interface StakeDialogProps {
}

function StakeDialog({
onSuccess,
isTimelockedStaking,
isOpen,
handleClose,
Expand Down Expand Up @@ -86,6 +86,9 @@ function StakeDialog({

const validators = Object.keys(rollingAverageApys ?? {}) ?? [];

const validatorApy =
rollingAverageApys && selectedValidator ? rollingAverageApys[selectedValidator] : null;

function handleBack(): void {
setView(StakeDialogView.SelectValidator);
}
Expand Down Expand Up @@ -122,10 +125,8 @@ function StakeDialog({
transaction: newStakeData?.transaction,
},
{
onSuccess: (tx) => {
if (onSuccess) {
onSuccess(tx.digest);
}
onSuccess: () => {
setView(StakeDialogView.TransactionDetails);
},
},
)
Expand Down Expand Up @@ -156,14 +157,15 @@ function StakeDialog({
onNext={selectValidatorHandleNext}
/>
)}
{view === StakeDialogView.EnterAmount && (
{view === StakeDialogView.EnterAmount && validatorApy !== null && (
<EnterAmountView
selectedValidator={selectedValidator}
amount={amount}
handleClose={handleClose}
onChange={(e) => setAmount(e.target.value)}
onBack={handleBack}
onStake={handleStake}
validatorApy={validatorApy}
/>
)}
{view === StakeDialogView.Unstake && stakedDetails && (
Expand All @@ -173,6 +175,16 @@ function StakeDialog({
showActiveStatus
/>
)}
{view === StakeDialogView.TransactionDetails && validatorApy !== null && (
<SuccessScreenView
validatorAddress={selectedValidator}
gasBudget={newStakeData?.gasBudget}
onConfirm={handleClose}
amount={amount}
symbol={metadata?.symbol}
validatorApy={validatorApy}
/>
)}
</Dialog>
);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
// Copyright (c) 2024 IOTA Stiftung
// SPDX-License-Identifier: Apache-2.0

import {
Button,
ButtonType,
Card,
CardBody,
CardImage,
CardType,
ImageShape,
ImageType,
} from '@iota/apps-ui-kit';
import { ValidatorApyData } from '@iota/core';
import { StakingTransactionDetails } from './StakingTransactionDetails';
import { Validator } from './Validator';
import { IotaLogoMark } from '@iota/ui-icons';

interface SuccessScreenViewProps {
validatorAddress: string;
gasBudget: string | number | null | undefined;
onConfirm: () => void;
amount: string;
symbol: string | undefined;
validatorApy: ValidatorApyData;
}

export function SuccessScreenView({
validatorAddress,
gasBudget,
onConfirm,
amount,
symbol,
validatorApy: { apy, isApyApproxZero },
}: SuccessScreenViewProps): React.JSX.Element {
return (
<div className="flex flex-1 flex-col">
<div className="flex w-full flex-1 flex-col justify-between">
<div className="flex flex-col gap-y-md">
<Validator address={validatorAddress} isSelected showAction={false} />

<Card type={CardType.Outlined}>
<CardImage type={ImageType.BgSolid} shape={ImageShape.Rounded}>
<IotaLogoMark className="h-5 w-5 text-neutral-10" />
</CardImage>
<CardBody title={`${amount} ${symbol}`} subtitle="Stake" />
</Card>

<StakingTransactionDetails
gasBudget={gasBudget}
apy={apy}
isApyApproxZero={isApyApproxZero}
/>
</div>
</div>

<div className="flex w-full">
<Button
type={ButtonType.Primary}
fullWidth
onClick={onConfirm}
text="Confirm & Exit"
/>
</div>
</div>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,23 +2,14 @@
// SPDX-License-Identifier: Apache-2.0

import React from 'react';
import { useFormatCoin, useBalance, CoinFormat } from '@iota/core';
import { useFormatCoin, useBalance, CoinFormat, ValidatorApyData } from '@iota/core';
import { IOTA_TYPE_ARG } from '@iota/iota-sdk/utils';
import {
Button,
ButtonType,
KeyValueInfo,
Panel,
Divider,
Input,
InputType,
Header,
} from '@iota/apps-ui-kit';
import { useStakeTxnInfo } from '../hooks';
import { useCurrentAccount, useIotaClientQuery } from '@iota/dapp-kit';
import { Button, ButtonType, Input, InputType, Header } from '@iota/apps-ui-kit';
import { useCurrentAccount } from '@iota/dapp-kit';
import { Validator } from './Validator';
import { StakedInfo } from './StakedInfo';
import { Layout, LayoutBody, LayoutFooter } from './Layout';
import { StakingTransactionDetails } from './StakingTransactionDetails';

interface EnterAmountViewProps {
selectedValidator: string;
Expand All @@ -29,6 +20,7 @@ interface EnterAmountViewProps {
showActiveStatus?: boolean;
gasBudget?: string | number | null;
handleClose: () => void;
validatorApy: ValidatorApyData;
}

function EnterAmountView({
Expand All @@ -39,11 +31,11 @@ function EnterAmountView({
onStake,
gasBudget = 0,
handleClose,
validatorApy,
}: EnterAmountViewProps): JSX.Element {
const account = useCurrentAccount();
const accountAddress = account?.address;

const { data: system } = useIotaClientQuery('getLatestIotaSystemState');
const { data: iotaBalance } = useBalance(accountAddress!);

const coinBalance = BigInt(iotaBalance?.totalBalance || 0);
Expand All @@ -53,10 +45,6 @@ function EnterAmountView({
IOTA_TYPE_ARG,
CoinFormat.FULL,
);
const [gas, symbol] = useFormatCoin(gasBudget, IOTA_TYPE_ARG);
const { stakedRewardsStartEpoch, timeBeforeStakeRewardsRedeemableAgoDisplay } = useStakeTxnInfo(
system?.epoch,
);

return (
<Layout>
Expand All @@ -83,28 +71,7 @@ function EnterAmountView({
caption={`${maxTokenFormatted} ${maxTokenFormattedSymbol} Available`}
/>
</div>

<Panel hasBorder>
<div className="flex flex-col gap-y-sm p-md">
<KeyValueInfo
keyText="Staking Rewards Start"
value={stakedRewardsStartEpoch}
fullwidth
/>
<KeyValueInfo
keyText="Redeem Rewards"
value={timeBeforeStakeRewardsRedeemableAgoDisplay}
fullwidth
/>
<Divider />
<KeyValueInfo
keyText="Gas fee"
value={gas || '--'}
supportingLabel={symbol}
fullwidth
/>
</div>
</Panel>
<StakingTransactionDetails gasBudget={gasBudget} {...validatorApy} />
</div>
</div>
</LayoutBody>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
// Copyright (c) 2024 IOTA Stiftung
// SPDX-License-Identifier: Apache-2.0

import { Divider, KeyValueInfo, Panel } from '@iota/apps-ui-kit';
import { useFormatCoin, ValidatorApyData } from '@iota/core';
import { useIotaClientQuery } from '@iota/dapp-kit';
import { IOTA_TYPE_ARG } from '@iota/iota-sdk/utils';
import { useStakeTxnInfo } from '../hooks';

interface StakingTransactionDetailsProps extends ValidatorApyData {
gasBudget: string | number | null | undefined;
}

export function StakingTransactionDetails({
gasBudget,
apy,
isApyApproxZero,
}: StakingTransactionDetailsProps): React.JSX.Element {
const [gas, gasSymbol] = useFormatCoin(gasBudget, IOTA_TYPE_ARG);
const { data: system } = useIotaClientQuery('getLatestIotaSystemState');

const { stakedRewardsStartEpoch, timeBeforeStakeRewardsRedeemableAgoDisplay } = useStakeTxnInfo(
system?.epoch,
);

const apyDisplay = `${isApyApproxZero ? '~' : ''}${apy}%`;

return (
<Panel hasBorder>
<div className="flex flex-col gap-y-sm p-md">
{apy !== null && apy !== undefined ? (
<KeyValueInfo keyText="APY" value={apyDisplay} fullwidth />
) : null}

<KeyValueInfo
keyText="Staking Rewards Start"
value={stakedRewardsStartEpoch}
fullwidth
/>
<KeyValueInfo
keyText="Redeem Rewards"
value={timeBeforeStakeRewardsRedeemableAgoDisplay}
fullwidth
/>
<Divider />
<KeyValueInfo
keyText="Gas fee"
value={gas || '--'}
supportingLabel={gasSymbol}
fullwidth
/>
</div>
</Panel>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,7 @@

export { default as EnterAmountView } from './EnterAmountView';
export { default as SelectValidatorView } from './SelectValidatorView';

export * from './DetailsView';
export * from './UnstakeView';
export * from './StakingTransactionDetails';

0 comments on commit 1507c81

Please sign in to comment.