chore(governance): vesting balances (#5237)

Co-authored-by: Madalina Raicu <madalina@raygroup.uk>
This commit is contained in:
Art 2023-11-15 19:15:05 +01:00 committed by GitHub
parent 3294c0cabe
commit 1a7682c3c9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 179 additions and 76 deletions

View File

@ -302,14 +302,17 @@ context(
cy.getByTestId(vegaWalletCurrencyTitle) cy.getByTestId(vegaWalletCurrencyTitle)
.contains(name) .contains(name)
.parent() .parent() // back to currency-title
.siblings(txTimeout) .parent() // back to container
.should((elementAmount) => { .within(() => {
const displayedAmount = parseFloat(elementAmount.text()); cy.get(
// @ts-ignore clash between jest and cypress '[data-account-type="account_type_general"] [data-value]'
expect(displayedAmount).be.gte(expectedAmount); ).should((elementAmount) => {
const displayedAmount = parseFloat(elementAmount.text());
// @ts-ignore clash between jest and cypress
expect(displayedAmount).be.gte(expectedAmount);
});
}); });
cy.getByTestId(vegaWalletCurrencyTitle) cy.getByTestId(vegaWalletCurrencyTitle)
.contains(name) .contains(name)
.parent() .parent()

View File

@ -256,10 +256,7 @@ export function validateWalletCurrency(
.parent() .parent()
.parent() .parent()
.within(() => { .within(() => {
cy.getByTestId('currency-value', txTimeout).should( cy.get('[data-value]', txTimeout).should('have.text', expectedAmount);
'have.text',
expectedAmount
);
}); });
} }

View File

@ -114,6 +114,16 @@ export const usePollForDelegations = () => {
isAssetTypeERC20(a.asset) && isAssetTypeERC20(a.asset) &&
a.asset.source.contractAddress === vegaToken.address; a.asset.source.contractAddress === vegaToken.address;
const isVesting =
a.type === Schema.AccountType.ACCOUNT_TYPE_VESTED_REWARDS ||
a.type === Schema.AccountType.ACCOUNT_TYPE_VESTING_REWARDS;
let icon = noIcon;
if (isVega) {
if (isVesting) icon = vegaVesting;
else icon = vegaBlack;
}
return { return {
isVega, isVega,
name: a.asset.name, name: a.asset.name,
@ -124,14 +134,7 @@ export const usePollForDelegations = () => {
balance: new BigNumber( balance: new BigNumber(
addDecimal(a.balance, a.asset.decimals) addDecimal(a.balance, a.asset.decimals)
), ),
image: isVega image: icon,
? vegaBlack
: a.type ===
Schema.AccountType.ACCOUNT_TYPE_VESTED_REWARDS ||
a.type ===
Schema.AccountType.ACCOUNT_TYPE_VESTING_REWARDS
? vegaVesting
: noIcon,
border: isVega, border: isVega,
address: isAssetTypeERC20(a.asset) address: isAssetTypeERC20(a.asset)
? a.asset.source.contractAddress ? a.asset.source.contractAddress

View File

@ -11,7 +11,10 @@ import { BigNumber } from '../../lib/bignumber';
import { truncateMiddle } from '../../lib/truncate-middle'; import { truncateMiddle } from '../../lib/truncate-middle';
import Routes from '../../routes/routes'; import Routes from '../../routes/routes';
import { BulletHeader } from '../bullet-header'; import { BulletHeader } from '../bullet-header';
import type { WalletCardAssetProps } from '../wallet-card'; import type {
WalletCardAssetProps,
WalletCardAssetWithMultipleBalancesProps,
} from '../wallet-card';
import { import {
WalletCard, WalletCard,
WalletCardActions, WalletCardActions,
@ -27,6 +30,7 @@ import { Button, ButtonLink } from '@vegaprotocol/ui-toolkit';
import { toBigNum } from '@vegaprotocol/utils'; import { toBigNum } from '@vegaprotocol/utils';
import { usePendingBalancesStore } from '../../hooks/use-pending-balances-manager'; import { usePendingBalancesStore } from '../../hooks/use-pending-balances-manager';
import { StakingEventType } from '../../hooks/use-get-association-breakdown'; import { StakingEventType } from '../../hooks/use-get-association-breakdown';
import omit from 'lodash/omit';
export const VegaWallet = () => { export const VegaWallet = () => {
const { t } = useTranslation(); const { t } = useTranslation();
@ -99,12 +103,29 @@ const VegaWalletAssetList = ({ accounts }: VegaWalletAssetsListProps) => {
if (!accounts.length) { if (!accounts.length) {
return null; return null;
} }
const groupedByAsset = accounts.reduce((all, a) => {
const foundIndex = all.findIndex((acc) => acc.assetId === a.assetId);
if (foundIndex > -1) {
const found = all[foundIndex];
all[foundIndex] = {
...found,
balances: [...found.balances, { balance: a.balance, type: a.type }],
};
return all;
}
const acc = {
...omit(a, 'balance', 'type'),
balances: [{ balance: a.balance, type: a.type }],
};
return [...all, acc];
}, [] as WalletCardAssetWithMultipleBalancesProps[]);
return ( return (
<> <>
<WalletCardHeader> <WalletCardHeader>
<BulletHeader tag="h2">{t('assets')}</BulletHeader> <BulletHeader tag="h2">{t('assets')}</BulletHeader>
</WalletCardHeader> </WalletCardHeader>
{accounts.map((a, i) => ( {groupedByAsset.map((a, i) => (
<WalletCardAsset key={i} {...a} /> <WalletCardAsset key={i} {...a} />
))} ))}
</> </>
@ -182,6 +203,7 @@ const VegaWalletConnected = ({ vegaKeys }: VegaWalletConnectedProps) => {
subheading={t('Associated')} subheading={t('Associated')}
symbol="VEGA" symbol="VEGA"
balance={currentStakeAvailable} balance={currentStakeAvailable}
allowZeroBalance={true}
/> />
{totalPending.eq(0) ? null : ( {totalPending.eq(0) ? null : (
<> <>
@ -192,6 +214,7 @@ const VegaWalletConnected = ({ vegaKeys }: VegaWalletConnectedProps) => {
subheading={t('Pending association')} subheading={t('Pending association')}
symbol="VEGA" symbol="VEGA"
balance={totalPending} balance={totalPending}
allowZeroBalance={true}
/> />
<WalletCardAsset <WalletCardAsset
image={vegaWhite} image={vegaWhite}
@ -200,6 +223,7 @@ const VegaWalletConnected = ({ vegaKeys }: VegaWalletConnectedProps) => {
subheading={t('Total associated after pending')} subheading={t('Total associated after pending')}
symbol="VEGA" symbol="VEGA"
balance={pendingStakeAmount} balance={pendingStakeAmount}
allowZeroBalance={true}
/> />
</> </>
)} )}

View File

@ -103,7 +103,7 @@ export const WalletCardActions = ({
return <div className="flex justify-end gap-2 mb-4">{children}</div>; return <div className="flex justify-end gap-2 mb-4">{children}</div>;
}; };
export interface WalletCardAssetProps { export type WalletCardAssetProps = {
image: string; image: string;
name: string; name: string;
symbol: string; symbol: string;
@ -113,42 +113,61 @@ export interface WalletCardAssetProps {
border?: boolean; border?: boolean;
subheading?: string; subheading?: string;
type?: Schema.AccountType; type?: Schema.AccountType;
} allowZeroBalance?: boolean;
};
export type WalletCardAssetWithMultipleBalancesProps = Omit<
WalletCardAssetProps,
'balance' | 'type'
> & {
balances: { balance: BigNumber; type?: Schema.AccountType }[];
};
export const WalletCardAsset = ({ export const WalletCardAsset = ({
image, image,
name, name,
symbol, symbol,
balance,
decimals, decimals,
assetId, assetId,
border, border,
subheading, subheading,
type, allowZeroBalance = false,
}: WalletCardAssetProps) => { ...props
const [integers, decimalsPlaces, separator] = useNumberParts( }: WalletCardAssetProps | WalletCardAssetWithMultipleBalancesProps) => {
balance, const balance = 'balance' in props ? props.balance : undefined;
decimals const type = 'type' in props ? props.type : undefined;
); const balances =
const { t } = useTranslation(); 'balances' in props
const consoleLink = useLinks(DApp.Console); ? props.balances
const transferAssetLink = (assetId: string) => : balance
consoleLink(CONSOLE_TRANSFER_ASSET.replace(':assetId', assetId)); ? [{ balance, type }]
const { param: baseRate } = useNetworkParam('rewards_vesting_baseRate'); : undefined;
const isRedeemable = const values =
type === Schema.AccountType.ACCOUNT_TYPE_VESTED_REWARDS && assetId; balances &&
balances.length > 0 &&
balances
.filter((b) => allowZeroBalance || !b.balance.isZero())
.sort((a, b) => {
const order = [
Schema.AccountType.ACCOUNT_TYPE_VESTING_REWARDS,
Schema.AccountType.ACCOUNT_TYPE_VESTED_REWARDS,
Schema.AccountType.ACCOUNT_TYPE_GENERAL,
undefined,
];
return order.indexOf(a.type) - order.indexOf(b.type);
})
.map(({ balance, type }, i) => (
<CurrencyValue
key={i}
balance={balance}
decimals={decimals}
type={type}
assetId={assetId}
/>
));
const accountTypeTooltip = useMemo(() => { if (!values || values.length === 0) return;
if (type === Schema.AccountType.ACCOUNT_TYPE_VESTED_REWARDS) {
return t('VestedRewardsTooltip');
}
if (type === Schema.AccountType.ACCOUNT_TYPE_VESTING_REWARDS && baseRate) {
return t('VestingRewardsTooltip', { baseRate });
}
return null;
}, [baseRate, t, type]);
return ( return (
<div className="flex flex-nowrap gap-2 mt-2 mb-4"> <div className="flex flex-nowrap gap-2 mt-2 mb-4">
@ -169,35 +188,92 @@ export const WalletCardAsset = ({
{subheading || symbol} {subheading || symbol}
</div> </div>
</div> </div>
{type ? ( {values}
<div className="mb-[2px] flex gap-2 items-baseline"> </div>
<Tooltip description={accountTypeTooltip}> </div>
<span className="px-2 py-1 leading-none text-xs bg-vega-cdark-700 rounded"> );
{Schema.AccountTypeMapping[type]} };
</span>
</Tooltip> const useAccountTypeTooltip = (type?: Schema.AccountType) => {
{isRedeemable ? ( const { t } = useTranslation();
<Tooltip description={t('RedeemRewardsTooltip')}> const { param: baseRate } = useNetworkParam('rewards_vesting_baseRate');
<AnchorButton const accountTypeTooltip = useMemo(() => {
variant="primary" if (type === Schema.AccountType.ACCOUNT_TYPE_VESTED_REWARDS) {
size="xs" return t('VestedRewardsTooltip');
href={transferAssetLink(assetId)} }
target="_blank" if (type === Schema.AccountType.ACCOUNT_TYPE_VESTING_REWARDS && baseRate) {
className="px-2 py-1 leading-none text-xs bg-vega-yellow text-black rounded" return t('VestingRewardsTooltip', { baseRate });
> }
{t('Redeem')}
</AnchorButton> return null;
</Tooltip> }, [baseRate, t, type]);
) : null}
</div> return accountTypeTooltip;
) : null} };
<div className="basis-full font-mono" data-testid="currency-value">
<span> const CurrencyValue = ({
{integers} balance,
{separator} decimals,
</span> type,
<span className="text-neutral-400">{decimalsPlaces}</span> assetId,
</div> }: {
balance: BigNumber;
decimals: number;
type?: Schema.AccountType;
assetId?: string;
}) => {
const { t } = useTranslation();
const consoleLink = useLinks(DApp.Console);
const transferAssetLink = (assetId: string) =>
consoleLink(CONSOLE_TRANSFER_ASSET.replace(':assetId', assetId));
const [integers, decimalsPlaces, separator] = useNumberParts(
balance,
decimals
);
const accountTypeTooltip = useAccountTypeTooltip(type);
const accountType = type && (
<Tooltip description={accountTypeTooltip}>
<span className="px-2 py-1 leading-none text-xs bg-vega-cdark-700 rounded">
{Schema.AccountTypeMapping[type]}
</span>
</Tooltip>
);
const isRedeemable =
type === Schema.AccountType.ACCOUNT_TYPE_VESTED_REWARDS && assetId;
const redeemBtn = isRedeemable ? (
<Tooltip description={t('RedeemRewardsTooltip')}>
<AnchorButton
variant="primary"
size="xs"
href={transferAssetLink(assetId)}
target="_blank"
className="px-2 py-1 leading-none text-xs bg-vega-yellow text-black rounded"
>
{t('Redeem')}
</AnchorButton>
</Tooltip>
) : null;
return (
<div
className="basis-full font-mono mb-1"
data-account-type={type?.toLowerCase() || 'unspecified'}
data-testid="currency-value"
>
{type && (
<div data-type className="flex gap-1">
{accountType}
{redeemBtn}
</div>
)}
<div data-value>
<span>
{integers}
{separator}
</span>
<span className="text-neutral-400">{decimalsPlaces}</span>
</div> </div>
</div> </div>
); );