Skip to content

Commit

Permalink
Wallet staking bug (MystenLabs#7565)
Browse files Browse the repository at this point in the history
- Show accurate earned SUI.
- Show network average APY
- Show total stake SUI on stake button
- fix scrolling issue 
- Update toolip
- fix validator name sorting 

<img width="460" alt="Screenshot 2023-01-24 at 3 37 24 PM"
src="https://user-images.githubusercontent.com/8676844/214406427-f886e00d-79b5-4e9a-a17b-e0ddcc0b292d.png">
<img width="486" alt="Screenshot 2023-01-24 at 3 37 33 PM"
src="https://user-images.githubusercontent.com/8676844/214406430-8a023c8f-7e00-4a32-abff-4375aa911345.png">
<img width="427" alt="Screenshot 2023-01-24 at 3 48 23 PM"
src="https://user-images.githubusercontent.com/8676844/214408461-7f46713d-6a2c-435f-80a2-cb30f6a51f86.png">
  • Loading branch information
Jibz1 authored Jan 25, 2023
1 parent d515c54 commit 19cf105
Show file tree
Hide file tree
Showing 19 changed files with 425 additions and 141 deletions.
1 change: 1 addition & 0 deletions apps/wallet/src/ui/app/helpers/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,4 @@ export { default as notEmpty } from './notEmptyCheck';
export { parseAmount } from './parseAmount';
export { getEventsSummary } from './getEventsSummary';
export { getAmount } from './getAmount';
export { roundFloat } from './roundFloat';
6 changes: 6 additions & 0 deletions apps/wallet/src/ui/app/helpers/roundFloat.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
// Copyright (c) Mysten Labs, Inc.
// SPDX-License-Identifier: Apache-2.0

export function roundFloat(num: number, precision = 4) {
return parseFloat(num.toFixed(precision));
}
99 changes: 99 additions & 0 deletions apps/wallet/src/ui/app/pages/home/tokens/TokenIconLink.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
// Copyright (c) Mysten Labs, Inc.
// SPDX-License-Identifier: Apache-2.0

import { useFeature } from '@growthbook/growthbook-react';
import { SUI_TYPE_ARG, type SuiAddress } from '@mysten/sui.js';
import cl from 'classnames';
import { useMemo } from 'react';
import { Link } from 'react-router-dom';

import { DelegatedAPY } from '_app/shared/delegated-apy';
import { Text } from '_app/shared/text';
import { useGetDelegatedStake } from '_app/staking/useGetDelegatedStake';
import Icon from '_components/icon';
import LoadingIndicator from '_components/loading/LoadingIndicator';
import { SuiIcons } from '_font-icons/output/sui-icons';
import { useFormatCoin } from '_hooks';
import { FEATURES } from '_src/shared/experimentation/features';

export function TokenIconLink({
accountAddress,
}: {
accountAddress: SuiAddress;
}) {
const stakingEnabled = useFeature(FEATURES.STAKING_ENABLED).on;
const { data: delegations, isLoading } =
useGetDelegatedStake(accountAddress);

const totalActivePendingStake = useMemo(() => {
if (!delegations) return 0n;
return delegations.reduce(
(acc, { staked_sui }) => acc + BigInt(staked_sui.principal.value),
0n
);
}, [delegations]);

const stakedValidators = useMemo(() => {
if (!delegations) return [];
return delegations.map(
({ staked_sui }) => staked_sui.validator_address
);
}, [delegations]);

const [formatted, symbol, queryResult] = useFormatCoin(
totalActivePendingStake,
SUI_TYPE_ARG
);

return (
<Link
to="/stake"
className={cl(
!stakingEnabled && '!bg-gray-40',
'flex mb-5 rounded-2xl w-full p-3.75 justify-between no-underline bg-sui/10 '
)}
tabIndex={!stakingEnabled ? -1 : undefined}
>
{isLoading || queryResult.isLoading ? (
<div className="p-2 w-full flex justify-start items-center h-full">
<LoadingIndicator />
</div>
) : (
<div className="flex gap-2.5 items-center">
<Icon
icon={SuiIcons.Union}
className={cl(
!stakingEnabled ? 'text-gray-60' : 'text-hero',
'text-heading4 font-normal '
)}
/>
<div className="flex flex-col gap-1.25">
<Text
variant="body"
weight="semibold"
color={!stakingEnabled ? 'gray-60' : 'hero'}
>
{totalActivePendingStake
? 'Currently Staked'
: 'Stake & Earn SUI'}
</Text>
{!!totalActivePendingStake && (
<Text
variant="body"
weight="semibold"
color={!stakingEnabled ? 'gray-60' : 'hero'}
>
{formatted} {symbol}
</Text>
)}
</div>
</div>
)}
<div className="flex">
{stakingEnabled && (
<DelegatedAPY stakedValidators={stakedValidators} />
)}
</div>
</Link>
);
}
17 changes: 4 additions & 13 deletions apps/wallet/src/ui/app/pages/home/tokens/TokensDetails.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
// Copyright (c) Mysten Labs, Inc.
// SPDX-License-Identifier: Apache-2.0

import { useFeature } from '@growthbook/growthbook-react';
import cl from 'classnames';
import { useMemo } from 'react';

import { TokenIconLink } from './TokenIconLink';
import CoinBalance from './coin-balance';
import IconLink from './icon-link';
import FaucetRequestButton from '_app/shared/faucet/request-button';
Expand All @@ -17,7 +17,6 @@ import { SuiIcons } from '_font-icons/output/sui-icons';
import { useAppSelector, useObjectsState } from '_hooks';
import { accountAggregateBalancesSelector } from '_redux/slices/account';
import { GAS_TYPE_ARG, Coin } from '_redux/slices/sui-objects/Coin';
import { FEATURES } from '_src/shared/experimentation/features';

import st from './TokensPage.module.scss';

Expand Down Expand Up @@ -82,6 +81,7 @@ function MyTokens({
function TokenDetails({ coinType }: TokenDetailsProps) {
const { loading, error, showError } = useObjectsState();
const activeCoinType = coinType || GAS_TYPE_ARG;
const accountAddress = useAppSelector(({ account }) => account.address);
const balances = useAppSelector(accountAggregateBalancesSelector);
const tokenBalance = balances[activeCoinType] || BigInt(0);
const allCoinTypes = useMemo(() => Object.keys(balances), [balances]);
Expand All @@ -93,8 +93,6 @@ function TokenDetails({ coinType }: TokenDetailsProps) {
[activeCoinType]
);

const stakingEnabled = useFeature(FEATURES.STAKING_ENABLED).on;

return (
<>
{coinType && (
Expand Down Expand Up @@ -151,15 +149,8 @@ function TokenDetails({ coinType }: TokenDetailsProps) {
/>
</div>

{activeCoinType === GAS_TYPE_ARG ? (
<div className={st.staking}>
<IconLink
icon={SuiIcons.Union}
to="/stake"
disabled={!stakingEnabled}
text="Stake & Earn SUI"
/>
</div>
{activeCoinType === GAS_TYPE_ARG && accountAddress ? (
<TokenIconLink accountAddress={accountAddress} />
) : null}

{!coinType ? (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
flex-flow: column nowrap;
align-items: center;
background-color: color.adjust(colors.$sui-blue, $alpha: -0.9);
border-radius: 10px;
border-radius: 15px;
padding: 10px 14px;
text-decoration: none;
color: colors.$cta-blue;
Expand Down
4 changes: 2 additions & 2 deletions apps/wallet/src/ui/app/shared/card/CardItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,14 @@ export function CardItem({ title, children }: CardItemProps) {
return (
<div
className={
'flex flex-col flex-nowrap max-w-full p-3.5 gap-1.5 flex-1 justify-center items-center'
'flex flex-col flex-nowrap max-w-full py-3.5 px-2.5 gap-1.5 flex-1 justify-center items-center'
}
>
<Text variant="captionSmall" weight="semibold" color="steel-darker">
{title}
</Text>

<div className="overflow-x-hidden text-ellipsis">{children}</div>
{children}
</div>
);
}
84 changes: 84 additions & 0 deletions apps/wallet/src/ui/app/shared/delegated-apy/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
// Copyright (c) Mysten Labs, Inc.
// SPDX-License-Identifier: Apache-2.0

import { type SuiAddress } from '@mysten/sui.js';
import { useMemo } from 'react';

import { calculateAPY } from '../../staking/calculateAPY';
import { STATE_OBJECT } from '../../staking/usePendingDelegation';
import { Text } from '_app/shared/text';
import { IconTooltip } from '_app/shared/tooltip';
import { validatorsFields } from '_app/staking/validatorsFields';
import LoadingIndicator from '_components/loading/LoadingIndicator';
import { roundFloat } from '_helpers';
import { useGetObject } from '_hooks';

const APY_DECIMALS = 3;

type DelegatedAPYProps = {
stakedValidators: SuiAddress[];
};

export function DelegatedAPY({ stakedValidators }: DelegatedAPYProps) {
const { data, isLoading } = useGetObject(STATE_OBJECT);

const validatorsData = data && validatorsFields(data);

const averageNetworkAPY = useMemo(() => {
if (!validatorsData) return 0;
const validators = validatorsData.validators.fields.active_validators;

let stakedAPYs = 0;

validators.forEach((validator) => {
if (
stakedValidators.includes(
validator.fields.delegation_staking_pool.fields
.validator_address
)
) {
stakedAPYs += calculateAPY(validator, +validatorsData.epoch);
}
});

const averageAPY = stakedAPYs / stakedValidators.length;

return roundFloat(averageAPY || 0, APY_DECIMALS);
}, [stakedValidators, validatorsData]);

if (isLoading) {
return (
<div className="p-2 w-full flex justify-center items-center h-full">
<LoadingIndicator />
</div>
);
}
return (
<div className="flex gap-0.5 items-center">
{averageNetworkAPY > 0 ? (
<>
<Text variant="body" weight="semibold" color="steel-dark">
{averageNetworkAPY}
</Text>
<Text
variant="subtitle"
weight="medium"
color="steel-darker"
>
% APY
</Text>
<div className="text-steel items-baseline text-body flex">
<IconTooltip
tip="The average APY of all validators you are currently staking your SUI on."
placement="top"
/>
</div>
</>
) : (
<Text variant="subtitle" weight="medium" color="steel-dark">
--
</Text>
)}
</div>
);
}
2 changes: 2 additions & 0 deletions apps/wallet/src/ui/app/shared/heading/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,12 +30,14 @@ const headingStyles = cva(
'gray-75': 'text-gray-75',
'gray-70': 'text-gray-70',
'gray-65': 'text-gray-65',
'gray-60': 'text-gray-60',
'sui-dark': 'text-sui-dark',
sui: 'text-sui',
'sui-light': 'text-sui-light',
steel: 'text-steel',
'steel-dark': 'text-steel-dark',
'steel-darker': 'text-steel-darker',
'success-dark': 'text-success-dark',
},
weight: {
medium: 'font-medium',
Expand Down
3 changes: 2 additions & 1 deletion apps/wallet/src/ui/app/shared/tooltip/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ export function Tooltip({ tip, children, placement = 'top' }: TooltipProps) {
<AnimatePresence>
{open ? (
<motion.div
className="pointer-events-none left-0 top-0 z-50 text-subtitleSmall font-semibold text-white"
className="pointer-events-none left-0 top-0 z-50 text-subtitleSmall font-semibold text-white leading-130"
initial={{
opacity: 0,
scale: 0,
Expand All @@ -131,6 +131,7 @@ export function Tooltip({ tip, children, placement = 'top' }: TooltipProps) {
top: y ?? 0,
left: x ?? 0,
width: 'max-content',
maxWidth: '200px',
}}
{...getFloatingProps({ ref: floating })}
>
Expand Down
4 changes: 3 additions & 1 deletion apps/wallet/src/ui/app/staking/calculateAPY.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@

import { type ActiveValidator } from '@mysten/sui.js';

import { roundFloat } from '_helpers';

const APY_DECIMALS = 4;

export function calculateAPY(validators: ActiveValidator, epoch: number) {
Expand All @@ -16,5 +18,5 @@ export function calculateAPY(validators: ActiveValidator, epoch: number) {
+delegation_token_supply.fields.value,
365 / num_epochs_participated - 1
);
return apy ? parseFloat(apy.toFixed(APY_DECIMALS)) : 0;
return apy ? roundFloat(apy, APY_DECIMALS) : 0;
}
Loading

0 comments on commit 19cf105

Please sign in to comment.