Compare commits

...

11 Commits

Author SHA1 Message Date
Madalina Raicu
84a5353387
fix: format requirements 2024-02-29 16:41:09 +00:00
Madalina Raicu
25542ba557
fix: dispatch strategy can be null 2024-02-29 16:08:04 +00:00
Madalina Raicu
952e935b68
chore: add extra check for dispatch strategy 2024-02-29 15:49:34 +00:00
Madalina Raicu
ad1dc3ceb1
fix: reward cards dividers 2024-02-29 11:44:00 +00:00
Madalina Raicu
a993273019
fix: use smantic elements and add translation 2024-02-29 11:32:41 +00:00
Madalina Raicu
1f2827b46f
Merge branch 'develop' of github.com:vegaprotocol/frontend-monorepo into fix/staking-rewards 2024-02-29 10:54:13 +00:00
Madalina Raicu
831651e7f3
fix: staking rewards filter 2024-02-28 20:15:12 +00:00
Madalina Raicu
966390f19a
fix: update the apply filter 2024-02-28 13:19:18 +00:00
Madalina Raicu
dee26f83e8
fix: update the apply filter 2024-02-28 13:13:38 +00:00
Madalina Raicu
f4693e3e61
fix: remove comments 2024-02-27 17:06:00 +00:00
Madalina Raicu
05c96af42b
feat(trading): staking rewards 2024-02-27 16:38:32 +00:00
3 changed files with 240 additions and 20 deletions

View File

@ -1,5 +1,5 @@
import { useT } from '../../lib/use-t'; import { useT } from '../../lib/use-t';
import { addDecimalsFormatNumber } from '@vegaprotocol/utils'; import { addDecimalsFormatNumber, formatNumber } from '@vegaprotocol/utils';
import classNames from 'classnames'; import classNames from 'classnames';
import { import {
type VegaIconSize, type VegaIconSize,
@ -24,6 +24,9 @@ import {
type DispatchStrategy, type DispatchStrategy,
IndividualScopeMapping, IndividualScopeMapping,
IndividualScopeDescriptionMapping, IndividualScopeDescriptionMapping,
AccountType,
DistributionStrategy,
IndividualScope,
type Asset, type Asset,
} from '@vegaprotocol/types'; } from '@vegaprotocol/types';
import { Card } from '../card/card'; import { Card } from '../card/card';
@ -62,22 +65,27 @@ export const applyFilter = (
filter: Filter filter: Filter
) => { ) => {
const { transfer } = node; const { transfer } = node;
if (
transfer.kind.__typename !== 'RecurringTransfer' || // if the transfer is a staking reward then it should be displayed
!transfer.kind.dispatchStrategy?.dispatchMetric if (transfer.toAccountType === AccountType.ACCOUNT_TYPE_GLOBAL_REWARD) {
) { return true;
}
if (transfer.kind.__typename !== 'RecurringTransfer') {
return false; return false;
} }
if ( if (
DispatchMetricLabels[transfer.kind.dispatchStrategy.dispatchMetric] (transfer.kind.dispatchStrategy?.dispatchMetric &&
.toLowerCase() DispatchMetricLabels[transfer.kind.dispatchStrategy.dispatchMetric]
.includes(filter.searchTerm.toLowerCase()) || .toLowerCase()
.includes(filter.searchTerm.toLowerCase())) ||
transfer.asset?.symbol transfer.asset?.symbol
.toLowerCase() .toLowerCase()
.includes(filter.searchTerm.toLowerCase()) || .includes(filter.searchTerm.toLowerCase()) ||
( (
EntityScopeLabelMapping[transfer.kind.dispatchStrategy.entityScope] || (transfer.kind.dispatchStrategy &&
EntityScopeLabelMapping[transfer.kind.dispatchStrategy.entityScope]) ||
'Unspecified' 'Unspecified'
) )
.toLowerCase() .toLowerCase()
@ -93,6 +101,7 @@ export const applyFilter = (
) { ) {
return true; return true;
} }
return false; return false;
}; };
@ -174,6 +183,29 @@ export const ActiveRewardCard = ({
return null; return null;
} }
if (
!transferNode.transfer.kind.dispatchStrategy &&
transferNode.transfer.toAccountType ===
AccountType.ACCOUNT_TYPE_GLOBAL_REWARD
) {
return (
<StakingRewardCard
colour={CardColour.WHITE}
rewardAmount={addDecimalsFormatNumber(
transferNode.transfer.amount,
transferNode.transfer.asset?.decimals || 0,
6
)}
rewardAsset={transferNode.transfer.asset || undefined}
endsIn={
transferNode.transfer.kind.endEpoch != null
? transferNode.transfer.kind.endEpoch - currentEpoch
: undefined
}
/>
);
}
let colour = let colour =
DispatchMetricColourMap[ DispatchMetricColourMap[
transferNode.transfer.kind.dispatchStrategy.dispatchMetric transferNode.transfer.kind.dispatchStrategy.dispatchMetric
@ -318,7 +350,7 @@ const RewardCard = ({
</div> </div>
</div> </div>
<span className="border-[0.5px] border-gray-700" /> <span className="border-[0.5px] dark:border-vega-cdark-500 border-vega-clight-500" />
{/** DISPATCH METRIC */} {/** DISPATCH METRIC */}
{dispatchMetricInfo ? ( {dispatchMetricInfo ? (
dispatchMetricInfo dispatchMetricInfo
@ -355,11 +387,11 @@ const RewardCard = ({
</div> </div>
{/** DISPATCH METRIC DESCRIPTION */} {/** DISPATCH METRIC DESCRIPTION */}
{dispatchStrategy?.dispatchMetric && ( {dispatchStrategy?.dispatchMetric && (
<span className="text-muted text-sm h-[3rem]"> <p className="text-muted text-sm h-[3rem]">
{t(DispatchMetricDescription[dispatchStrategy?.dispatchMetric])} {t(DispatchMetricDescription[dispatchStrategy?.dispatchMetric])}
</span> </p>
)} )}
<span className="border-[0.5px] border-gray-700" /> <span className="border-[0.5px] dark:border-vega-cdark-500 border-vega-clight-500" />
{/** REQUIREMENTS */} {/** REQUIREMENTS */}
{dispatchStrategy && ( {dispatchStrategy && (
<RewardRequirements <RewardRequirements
@ -374,6 +406,190 @@ const RewardCard = ({
); );
}; };
const StakingRewardCard = ({
colour,
rewardAmount,
rewardAsset,
endsIn,
}: {
colour: CardColour;
rewardAmount: string;
/** The asset linked to the dispatch strategy via `dispatchMetricAssetId` property. */
rewardAsset?: Asset;
/** The number of epochs until the transfer stops. */
endsIn?: number;
/** The VEGA asset details, required to format the min staking amount. */
vegaAsset?: BasicAssetDetails;
}) => {
const t = useT();
return (
<div>
<div
className={classNames(
'bg-gradient-to-r col-span-full p-0.5 lg:col-auto h-full',
'rounded-lg',
CardColourStyles[colour].gradientClassName
)}
data-testid="active-rewards-card"
>
<div
className={classNames(
CardColourStyles[colour].mainClassName,
'bg-gradient-to-b bg-vega-clight-800 dark:bg-vega-cdark-800 h-full w-full rounded-md p-4 flex flex-col gap-4'
)}
>
<div className="flex justify-between gap-4">
{/** ENTITY SCOPE */}
<div className="flex flex-col gap-2 items-center text-center">
<EntityIcon entityScope={EntityScope.ENTITY_SCOPE_INDIVIDUALS} />
{
<span className="text-muted text-xs" data-testid="entity-scope">
{EntityScopeLabelMapping[
EntityScope.ENTITY_SCOPE_INDIVIDUALS
] || t('Unspecified')}
</span>
}
</div>
{/** AMOUNT AND DISTRIBUTION STRATEGY */}
<div className="flex flex-col gap-2 items-center text-center">
{/** AMOUNT */}
<h3 className="flex flex-col gap-1 text-2xl shrink-1 text-center">
<span className="font-glitch" data-testid="reward-value">
{rewardAmount}
</span>
<span className="font-alpha">{rewardAsset?.symbol || ''}</span>
</h3>
{/** DISTRIBUTION STRATEGY */}
<Tooltip
description={t(
DistributionStrategyDescriptionMapping[
DistributionStrategy.DISTRIBUTION_STRATEGY_PRO_RATA
]
)}
underline={true}
>
<span className="text-xs" data-testid="distribution-strategy">
{
DistributionStrategyMapping[
DistributionStrategy.DISTRIBUTION_STRATEGY_PRO_RATA
]
}
</span>
</Tooltip>
</div>
{/** DISTRIBUTION DELAY */}
<div className="flex flex-col gap-2 items-center text-center">
<CardIcon
iconName={VegaIconNames.LOCK}
tooltip={t(
'Number of epochs after distribution to delay vesting of rewards by'
)}
/>
<span
className="text-muted text-xs whitespace-nowrap"
data-testid="locked-for"
>
{t('numberEpochs', '{{count}} epochs', {
count: 0,
})}
</span>
</div>
</div>
<span className="border-[0.5px] dark:border-vega-cdark-500 border-vega-clight-500" />
{/** DISPATCH METRIC */}
{
<span data-testid="dispatch-metric-info">
{t('Staking rewards')}
</span>
}
<div className="flex items-center gap-8 flex-wrap">
{/** ENDS IN */}
{endsIn != null && (
<span className="flex flex-col">
<span className="text-muted text-xs">{t('Ends in')} </span>
<span data-testid="ends-in" data-endsin={endsIn}>
{endsIn >= 0
? t('numberEpochs', '{{count}} epochs', {
count: endsIn,
})
: t('Ended')}
</span>
</span>
)}
{/** WINDOW LENGTH */}
<span className="flex flex-col">
<span className="text-muted text-xs">{t('Assessed over')}</span>
<span data-testid="assessed-over">
{t('numberEpochs', '{{count}} epochs', {
count: 1,
})}
</span>
</span>
</div>
{/** DISPATCH METRIC DESCRIPTION */}
{
<p className="text-muted text-sm h-[3rem]">
{t(
'Global staking reward for staking $VEGA on the network via the Governance app'
)}
</p>
}
<span className="border-[0.5px] dark:border-vega-cdark-500 border-vega-clight-500" />
{/** REQUIREMENTS */}
<dl className="flex justify-between flex-wrap items-center gap-3 text-xs">
<div className="flex flex-col gap-1">
<dt className="flex items-center gap-1 text-muted">
{t('Team scope')}
</dt>
<dd className="flex items-center gap-1" data-testid="scope">
<Tooltip
description={
IndividualScopeDescriptionMapping[
IndividualScope.INDIVIDUAL_SCOPE_ALL
]
}
>
<span>{t('Individual')}</span>
</Tooltip>
</dd>
</div>
<div className="flex flex-col gap-1">
<dt className="flex items-center gap-1 text-muted">
{t('Staked VEGA')}
</dt>
<dd
className="flex items-center gap-1"
data-testid="staking-requirement"
>
{formatNumber(1, 2)}
</dd>
</div>
<div className="flex flex-col gap-1">
<dt className="flex items-center gap-1 text-muted">
{t('Average position')}
</dt>
<dd
className="flex items-center gap-1"
data-testid="average-position"
>
{formatNumber(0, 2)}
</dd>
</div>
</dl>
</div>
</div>
</div>
);
};
export const DispatchMetricInfo = ({ export const DispatchMetricInfo = ({
reward, reward,
}: { }: {

View File

@ -16,6 +16,7 @@ import {
EntityScope, EntityScope,
IndividualScope, IndividualScope,
MarketState, MarketState,
AccountType,
} from '@vegaprotocol/types'; } from '@vegaprotocol/types';
import { type ApolloError } from '@apollo/client'; import { type ApolloError } from '@apollo/client';
import compact from 'lodash/compact'; import compact from 'lodash/compact';
@ -46,8 +47,9 @@ export type EnrichedRewardTransfer = RewardTransfer & {
*/ */
export const isReward = (node: TransferNode): node is RewardTransfer => { export const isReward = (node: TransferNode): node is RewardTransfer => {
if ( if (
node.transfer.kind.__typename === 'RecurringTransfer' && (node.transfer.kind.__typename === 'RecurringTransfer' &&
node.transfer.kind.dispatchStrategy != null node.transfer.kind.dispatchStrategy != null) ||
node.transfer.toAccountType === AccountType.ACCOUNT_TYPE_GLOBAL_REWARD
) { ) {
return true; return true;
} }
@ -79,13 +81,13 @@ export const isActiveReward = (node: RewardTransfer, currentEpoch: number) => {
*/ */
export const isScopedToTeams = (node: EnrichedRewardTransfer) => export const isScopedToTeams = (node: EnrichedRewardTransfer) =>
// scoped to teams // scoped to teams
node.transfer.kind.dispatchStrategy.entityScope === node.transfer.kind.dispatchStrategy?.entityScope ===
EntityScope.ENTITY_SCOPE_TEAMS || EntityScope.ENTITY_SCOPE_TEAMS ||
// or to individuals // or to individuals
(node.transfer.kind.dispatchStrategy.entityScope === (node.transfer.kind.dispatchStrategy?.entityScope ===
EntityScope.ENTITY_SCOPE_INDIVIDUALS && EntityScope.ENTITY_SCOPE_INDIVIDUALS &&
// but they have to be in a team // but they have to be in a team
node.transfer.kind.dispatchStrategy.individualScope === node.transfer.kind.dispatchStrategy?.individualScope ===
IndividualScope.INDIVIDUAL_SCOPE_IN_TEAM); IndividualScope.INDIVIDUAL_SCOPE_IN_TEAM);
/** Retrieves rewards (transfers) */ /** Retrieves rewards (transfers) */
@ -142,6 +144,7 @@ export const useRewards = ({
.filter((node) => (scopeToTeams ? isScopedToTeams(node) : true)) .filter((node) => (scopeToTeams ? isScopedToTeams(node) : true))
// enrich with dispatch asset and markets in scope details // enrich with dispatch asset and markets in scope details
.map((node) => { .map((node) => {
if (!node.transfer.kind.dispatchStrategy) return node;
const dispatchAsset = const dispatchAsset =
(assets && (assets &&
assets[node.transfer.kind.dispatchStrategy.dispatchMetricAssetId]) || assets[node.transfer.kind.dispatchStrategy.dispatchMetricAssetId]) ||
@ -170,7 +173,7 @@ export const useRewards = ({
...node, ...node,
dispatchAsset, dispatchAsset,
isAssetTraded: isAssetTraded != null ? isAssetTraded : undefined, isAssetTraded: isAssetTraded != null ? isAssetTraded : undefined,
markets: marketsInScope.length > 0 ? marketsInScope : undefined, markets: marketsInScope?.length > 0 ? marketsInScope : undefined,
}; };
}); });

View File

@ -136,6 +136,7 @@
"Governance vote for this market is valid and has been accepted": "Governance vote for this market is valid and has been accepted", "Governance vote for this market is valid and has been accepted": "Governance vote for this market is valid and has been accepted",
"Governance vote has passed and market is awaiting opening auction exit": "Governance vote has passed and market is awaiting opening auction exit", "Governance vote has passed and market is awaiting opening auction exit": "Governance vote has passed and market is awaiting opening auction exit",
"Governance vote passed to close the market": "Governance vote passed to close the market", "Governance vote passed to close the market": "Governance vote passed to close the market",
"Global staking reward for staking $VEGA on the network via the Governance app": "Global staking reward for staking $VEGA on the network via the Governance app",
"Help identify bugs and improve the service by sharing anonymous usage data.": "Help identify bugs and improve the service by sharing anonymous usage data.", "Help identify bugs and improve the service by sharing anonymous usage data.": "Help identify bugs and improve the service by sharing anonymous usage data.",
"Help us identify bugs and improve Vega Governance by sharing anonymous usage data.": "Help us identify bugs and improve Vega Governance by sharing anonymous usage data.", "Help us identify bugs and improve Vega Governance by sharing anonymous usage data.": "Help us identify bugs and improve Vega Governance by sharing anonymous usage data.",
"Hide closed markets": "Hide closed markets", "Hide closed markets": "Hide closed markets",
@ -368,12 +369,12 @@
"TradingView": "TradingView", "TradingView": "TradingView",
"Transfer": "Transfer", "Transfer": "Transfer",
"Type": "Type", "Type": "Type",
"Staking rewards": "Staking rewards",
"Unknown": "Unknown", "Unknown": "Unknown",
"Unknown settlement date": "Unknown settlement date", "Unknown settlement date": "Unknown settlement date",
"Update team": "Update team", "Update team": "Update team",
"URL": "URL", "URL": "URL",
"Use a comma separated list to allow only specific public keys to join the team": "Use a comma separated list to allow only specific public keys to join the team", "Use a comma separated list to allow only specific public keys to join the team": "Use a comma separated list to allow only specific public keys to join the team",
"Vega chart": "Vega chart",
"Vega Reward pot": "Vega Reward pot", "Vega Reward pot": "Vega Reward pot",
"Vega Wallet <0>full featured</0>": "Vega Wallet <0>full featured</0>", "Vega Wallet <0>full featured</0>": "Vega Wallet <0>full featured</0>",
"Vega chart": "Vega chart", "Vega chart": "Vega chart",