From 1a7682c3c9ae2c6850677f148a095c1b24ac3229 Mon Sep 17 00:00:00 2001 From: Art Date: Wed, 15 Nov 2023 19:15:05 +0100 Subject: [PATCH] chore(governance): vesting balances (#5237) Co-authored-by: Madalina Raicu --- .../src/integration/view/wallet-vega.cy.ts | 17 +- .../src/support/staking.functions.ts | 5 +- .../src/components/vega-wallet/hooks.ts | 19 +- .../components/vega-wallet/vega-wallet.tsx | 28 ++- .../components/wallet-card/wallet-card.tsx | 186 ++++++++++++------ 5 files changed, 179 insertions(+), 76 deletions(-) diff --git a/apps/governance-e2e/src/integration/view/wallet-vega.cy.ts b/apps/governance-e2e/src/integration/view/wallet-vega.cy.ts index aeeb1bf97..58011cc60 100644 --- a/apps/governance-e2e/src/integration/view/wallet-vega.cy.ts +++ b/apps/governance-e2e/src/integration/view/wallet-vega.cy.ts @@ -302,14 +302,17 @@ context( cy.getByTestId(vegaWalletCurrencyTitle) .contains(name) - .parent() - .siblings(txTimeout) - .should((elementAmount) => { - const displayedAmount = parseFloat(elementAmount.text()); - // @ts-ignore clash between jest and cypress - expect(displayedAmount).be.gte(expectedAmount); + .parent() // back to currency-title + .parent() // back to container + .within(() => { + cy.get( + '[data-account-type="account_type_general"] [data-value]' + ).should((elementAmount) => { + const displayedAmount = parseFloat(elementAmount.text()); + // @ts-ignore clash between jest and cypress + expect(displayedAmount).be.gte(expectedAmount); + }); }); - cy.getByTestId(vegaWalletCurrencyTitle) .contains(name) .parent() diff --git a/apps/governance-e2e/src/support/staking.functions.ts b/apps/governance-e2e/src/support/staking.functions.ts index 81a0452a9..c4cf49619 100644 --- a/apps/governance-e2e/src/support/staking.functions.ts +++ b/apps/governance-e2e/src/support/staking.functions.ts @@ -256,10 +256,7 @@ export function validateWalletCurrency( .parent() .parent() .within(() => { - cy.getByTestId('currency-value', txTimeout).should( - 'have.text', - expectedAmount - ); + cy.get('[data-value]', txTimeout).should('have.text', expectedAmount); }); } diff --git a/apps/governance/src/components/vega-wallet/hooks.ts b/apps/governance/src/components/vega-wallet/hooks.ts index df62a3d19..38f9f9d10 100644 --- a/apps/governance/src/components/vega-wallet/hooks.ts +++ b/apps/governance/src/components/vega-wallet/hooks.ts @@ -114,6 +114,16 @@ export const usePollForDelegations = () => { isAssetTypeERC20(a.asset) && 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 { isVega, name: a.asset.name, @@ -124,14 +134,7 @@ export const usePollForDelegations = () => { balance: new BigNumber( addDecimal(a.balance, a.asset.decimals) ), - image: isVega - ? vegaBlack - : a.type === - Schema.AccountType.ACCOUNT_TYPE_VESTED_REWARDS || - a.type === - Schema.AccountType.ACCOUNT_TYPE_VESTING_REWARDS - ? vegaVesting - : noIcon, + image: icon, border: isVega, address: isAssetTypeERC20(a.asset) ? a.asset.source.contractAddress diff --git a/apps/governance/src/components/vega-wallet/vega-wallet.tsx b/apps/governance/src/components/vega-wallet/vega-wallet.tsx index b03e37409..0e6d5c8fe 100644 --- a/apps/governance/src/components/vega-wallet/vega-wallet.tsx +++ b/apps/governance/src/components/vega-wallet/vega-wallet.tsx @@ -11,7 +11,10 @@ import { BigNumber } from '../../lib/bignumber'; import { truncateMiddle } from '../../lib/truncate-middle'; import Routes from '../../routes/routes'; import { BulletHeader } from '../bullet-header'; -import type { WalletCardAssetProps } from '../wallet-card'; +import type { + WalletCardAssetProps, + WalletCardAssetWithMultipleBalancesProps, +} from '../wallet-card'; import { WalletCard, WalletCardActions, @@ -27,6 +30,7 @@ import { Button, ButtonLink } from '@vegaprotocol/ui-toolkit'; import { toBigNum } from '@vegaprotocol/utils'; import { usePendingBalancesStore } from '../../hooks/use-pending-balances-manager'; import { StakingEventType } from '../../hooks/use-get-association-breakdown'; +import omit from 'lodash/omit'; export const VegaWallet = () => { const { t } = useTranslation(); @@ -99,12 +103,29 @@ const VegaWalletAssetList = ({ accounts }: VegaWalletAssetsListProps) => { if (!accounts.length) { 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 ( <> {t('assets')} - {accounts.map((a, i) => ( + {groupedByAsset.map((a, i) => ( ))} @@ -182,6 +203,7 @@ const VegaWalletConnected = ({ vegaKeys }: VegaWalletConnectedProps) => { subheading={t('Associated')} symbol="VEGA" balance={currentStakeAvailable} + allowZeroBalance={true} /> {totalPending.eq(0) ? null : ( <> @@ -192,6 +214,7 @@ const VegaWalletConnected = ({ vegaKeys }: VegaWalletConnectedProps) => { subheading={t('Pending association')} symbol="VEGA" balance={totalPending} + allowZeroBalance={true} /> { subheading={t('Total associated after pending')} symbol="VEGA" balance={pendingStakeAmount} + allowZeroBalance={true} /> )} diff --git a/apps/governance/src/components/wallet-card/wallet-card.tsx b/apps/governance/src/components/wallet-card/wallet-card.tsx index b2346dcfd..eabf2c737 100644 --- a/apps/governance/src/components/wallet-card/wallet-card.tsx +++ b/apps/governance/src/components/wallet-card/wallet-card.tsx @@ -103,7 +103,7 @@ export const WalletCardActions = ({ return
{children}
; }; -export interface WalletCardAssetProps { +export type WalletCardAssetProps = { image: string; name: string; symbol: string; @@ -113,42 +113,61 @@ export interface WalletCardAssetProps { border?: boolean; subheading?: string; type?: Schema.AccountType; -} + allowZeroBalance?: boolean; +}; + +export type WalletCardAssetWithMultipleBalancesProps = Omit< + WalletCardAssetProps, + 'balance' | 'type' +> & { + balances: { balance: BigNumber; type?: Schema.AccountType }[]; +}; export const WalletCardAsset = ({ image, name, symbol, - balance, decimals, assetId, border, subheading, - type, -}: WalletCardAssetProps) => { - const [integers, decimalsPlaces, separator] = useNumberParts( - balance, - decimals - ); - const { t } = useTranslation(); - const consoleLink = useLinks(DApp.Console); - const transferAssetLink = (assetId: string) => - consoleLink(CONSOLE_TRANSFER_ASSET.replace(':assetId', assetId)); - const { param: baseRate } = useNetworkParam('rewards_vesting_baseRate'); + allowZeroBalance = false, + ...props +}: WalletCardAssetProps | WalletCardAssetWithMultipleBalancesProps) => { + const balance = 'balance' in props ? props.balance : undefined; + const type = 'type' in props ? props.type : undefined; + const balances = + 'balances' in props + ? props.balances + : balance + ? [{ balance, type }] + : undefined; - const isRedeemable = - type === Schema.AccountType.ACCOUNT_TYPE_VESTED_REWARDS && assetId; + const values = + 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) => ( + + )); - const accountTypeTooltip = useMemo(() => { - 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]); + if (!values || values.length === 0) return; return (
@@ -169,35 +188,92 @@ export const WalletCardAsset = ({ {subheading || symbol}
- {type ? ( -
- - - {Schema.AccountTypeMapping[type]} - - - {isRedeemable ? ( - - - {t('Redeem')} - - - ) : null} -
- ) : null} -
- - {integers} - {separator} - - {decimalsPlaces} -
+ {values} + + + ); +}; + +const useAccountTypeTooltip = (type?: Schema.AccountType) => { + const { t } = useTranslation(); + const { param: baseRate } = useNetworkParam('rewards_vesting_baseRate'); + const accountTypeTooltip = useMemo(() => { + 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 accountTypeTooltip; +}; + +const CurrencyValue = ({ + balance, + decimals, + type, + assetId, +}: { + 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 && ( + + + {Schema.AccountTypeMapping[type]} + + + ); + const isRedeemable = + type === Schema.AccountType.ACCOUNT_TYPE_VESTED_REWARDS && assetId; + const redeemBtn = isRedeemable ? ( + + + {t('Redeem')} + + + ) : null; + + return ( +
+ {type && ( +
+ {accountType} + {redeemBtn} +
+ )} +
+ + {integers} + {separator} + + {decimalsPlaces}
);