diff --git a/apps/trading/components/rewards-container/Rewards.graphql b/apps/trading/components/rewards-container/Rewards.graphql index fbadb4583..e6a0b4c6e 100644 --- a/apps/trading/components/rewards-container/Rewards.graphql +++ b/apps/trading/components/rewards-container/Rewards.graphql @@ -196,3 +196,18 @@ query VestingDetails($partyId: ID!) { } } } + +query MarketForRewards($marketId: ID!) { + market(id: $marketId) { + tradableInstrument { + instrument { + id + name + code + metadata { + tags + } + } + } + } +} diff --git a/apps/trading/components/rewards-container/__generated__/Rewards.ts b/apps/trading/components/rewards-container/__generated__/Rewards.ts index b2477a812..7fe19835f 100644 --- a/apps/trading/components/rewards-container/__generated__/Rewards.ts +++ b/apps/trading/components/rewards-container/__generated__/Rewards.ts @@ -50,6 +50,13 @@ export type VestingDetailsQueryVariables = Types.Exact<{ export type VestingDetailsQuery = { __typename?: 'Query', party?: { __typename?: 'Party', vestingStats?: { __typename?: 'PartyVestingStats', rewardBonusMultiplier: string, quantumBalance: string, epochSeq: number } | null } | null }; +export type MarketForRewardsQueryVariables = Types.Exact<{ + marketId: Types.Scalars['ID']; +}>; + + +export type MarketForRewardsQuery = { __typename?: 'Query', market?: { __typename?: 'Market', tradableInstrument: { __typename?: 'TradableInstrument', instrument: { __typename?: 'Instrument', id: string, name: string, code: string, metadata: { __typename?: 'InstrumentMetadata', tags?: Array | null } } } } | null }; + export const RewardsPageDocument = gql` query RewardsPage($partyId: ID!) { @@ -415,4 +422,48 @@ export function useVestingDetailsLazyQuery(baseOptions?: Apollo.LazyQueryHookOpt } export type VestingDetailsQueryHookResult = ReturnType; export type VestingDetailsLazyQueryHookResult = ReturnType; -export type VestingDetailsQueryResult = Apollo.QueryResult; \ No newline at end of file +export type VestingDetailsQueryResult = Apollo.QueryResult; +export const MarketForRewardsDocument = gql` + query MarketForRewards($marketId: ID!) { + market(id: $marketId) { + tradableInstrument { + instrument { + id + name + code + metadata { + tags + } + } + } + } +} + `; + +/** + * __useMarketForRewardsQuery__ + * + * To run a query within a React component, call `useMarketForRewardsQuery` and pass it any options that fit your needs. + * When your component renders, `useMarketForRewardsQuery` returns an object from Apollo Client that contains loading, error, and data properties + * you can use to render your UI. + * + * @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options; + * + * @example + * const { data, loading, error } = useMarketForRewardsQuery({ + * variables: { + * marketId: // value for 'marketId' + * }, + * }); + */ +export function useMarketForRewardsQuery(baseOptions: Apollo.QueryHookOptions) { + const options = {...defaultOptions, ...baseOptions} + return Apollo.useQuery(MarketForRewardsDocument, options); + } +export function useMarketForRewardsLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions) { + const options = {...defaultOptions, ...baseOptions} + return Apollo.useLazyQuery(MarketForRewardsDocument, options); + } +export type MarketForRewardsQueryHookResult = ReturnType; +export type MarketForRewardsLazyQueryHookResult = ReturnType; +export type MarketForRewardsQueryResult = Apollo.QueryResult; \ No newline at end of file diff --git a/apps/trading/components/rewards-container/active-rewards.tsx b/apps/trading/components/rewards-container/active-rewards.tsx index 2c60bfae4..78f9c5247 100644 --- a/apps/trading/components/rewards-container/active-rewards.tsx +++ b/apps/trading/components/rewards-container/active-rewards.tsx @@ -1,6 +1,9 @@ -import { useActiveRewardsQuery } from './__generated__/Rewards'; +import { + useActiveRewardsQuery, + useMarketForRewardsQuery, +} from './__generated__/Rewards'; import { useT } from '../../lib/use-t'; -import { addDecimalsFormatNumber } from '@vegaprotocol/utils'; +import { addDecimalsFormatNumber, formatNumber } from '@vegaprotocol/utils'; import classNames from 'classnames'; import { Icon, @@ -25,9 +28,39 @@ import { DispatchMetric, DispatchMetricDescription, DispatchMetricLabels, + type RecurringTransfer, } from '@vegaprotocol/types'; +import { Card } from '../card/card'; +import { useMemo } from 'react'; + +const isActiveReward = (node: TransferNode, currentEpoch: number) => { + const { transfer } = node; + if (transfer.kind.__typename !== 'RecurringTransfer') { + return false; + } + const { dispatchStrategy } = transfer.kind; + + if (!dispatchStrategy) { + return false; + } + + if (transfer.kind.endEpoch && transfer.kind.endEpoch < currentEpoch) { + return false; + } + + if (transfer.status !== TransferStatus.STATUS_PENDING) { + return false; + } + + if (node.transfer.reference !== 'reward') { + return false; + } + + return true; +}; export const ActiveRewards = ({ currentEpoch }: { currentEpoch: number }) => { + const t = useT(); const { data: activeRewardsData } = useActiveRewardsQuery({ variables: { isReward: true, @@ -36,27 +69,42 @@ export const ActiveRewards = ({ currentEpoch }: { currentEpoch: number }) => { const transfers = activeRewardsData?.transfersConnection?.edges ?.map((e) => e?.node as TransferNode) - .filter((node) => node.transfer.reference === 'reward'); + .filter((node) => isActiveReward(node, currentEpoch)); - if (!transfers) return null; + if (!transfers || !transfers.length) return null; return ( -
- {transfers.map((node, i) => { - return ( - node && ( - - ) - ); - })} -
+ +
+ {transfers.map((node, i) => { + const { transfer } = node; + + if (!isActiveReward(node, currentEpoch)) { + return null; + } + + if (transfer.kind.__typename !== 'RecurringTransfer') { + return null; + } + + return ( + node && ( + + ) + ); + })} +
+
); }; +// This was built to be a status indicator for the rewards based on the transfer status +// eslint-disable-next-line @typescript-eslint/no-unused-vars const StatusIndicator = ({ status, reason, @@ -114,26 +162,42 @@ const StatusIndicator = ({ export const ActiveRewardCard = ({ transferNode, currentEpoch, + kind, }: { transferNode: TransferNode; currentEpoch: number; + kind: RecurringTransfer; }) => { const t = useT(); const { transfer } = transferNode; - if (transfer.kind.__typename !== 'RecurringTransfer') { - return null; - } - const { dispatchStrategy } = transfer.kind; + const { dispatchStrategy } = kind; + const marketIds = dispatchStrategy?.marketIdsInScope; + + const { data: marketNameData } = useMarketForRewardsQuery({ + variables: { + marketId: marketIds ? marketIds[0] : '', + }, + }); + + const marketName = useMemo(() => { + if (marketNameData && marketIds && marketIds.length > 1) { + return 'Specific markets'; + } else if ( + marketNameData && + marketIds && + marketNameData && + marketIds.length === 1 + ) { + return marketNameData?.market?.tradableInstrument?.instrument?.name || ''; + } + return ''; + }, [marketIds, marketNameData]); if (!dispatchStrategy) { return null; } - if (transfer.kind.endEpoch && transfer.kind.endEpoch < currentEpoch) { - return null; - } - const { gradientClassName, mainClassName } = getGradientClasses( dispatchStrategy.dispatchMetric ); @@ -170,7 +234,7 @@ export const ActiveRewardCard = ({
- + {addDecimalsFormatNumber( transferNode.transfer.amount, @@ -210,40 +274,44 @@ export const ActiveRewardCard = ({ /> {t('{{lock}} epochs', { - lock: transfer.kind.dispatchStrategy?.lockPeriod, + lock: kind.dispatchStrategy?.lockPeriod, })}
- + {/* TODO use market symbol or market name */} - {DispatchMetricLabels[dispatchStrategy.dispatchMetric]} •{' '} - {transfer.asset?.symbol} • {transfer.asset?.name} + {DispatchMetricLabels[dispatchStrategy.dispatchMetric]} + {marketName && ` • ${marketName}`}
- { + {kind.endEpoch && ( {t('Ends in')} {t('{{epochs}} epochs', { - epochs: transfer.kind.endEpoch - ? transfer.kind.endEpoch - currentEpoch + epochs: kind.endEpoch + ? formatNumber(kind.endEpoch - currentEpoch) : '-', })} - } + )} { {t('Assessed over')} - {t('{{epochs}} epochs', { - epochs: transfer.kind.dispatchStrategy?.windowLength, - })} + {dispatchStrategy.windowLength === 1 + ? t('{{epochs}} epoch', { + epochs: formatNumber(dispatchStrategy.windowLength), + }) + : t('{{epochs}} epoch(s)', { + epochs: formatNumber(dispatchStrategy.windowLength), + })} } @@ -264,10 +332,10 @@ export const ActiveRewardCard = ({ - {transfer.kind.dispatchStrategy?.teamScope && ( + {kind.dispatchStrategy?.teamScope && ( {transfer.kind.dispatchStrategy?.teamScope} + {kind.dispatchStrategy?.teamScope} } > @@ -275,12 +343,10 @@ export const ActiveRewardCard = ({ )} - {transfer.kind.dispatchStrategy?.individualScope && ( + {kind.dispatchStrategy?.individualScope && ( - {transfer.kind.dispatchStrategy?.individualScope} - + {kind.dispatchStrategy?.individualScope} } > @@ -288,10 +354,11 @@ export const ActiveRewardCard = ({ )} - + /> */} @@ -301,32 +368,24 @@ export const ActiveRewardCard = ({ {addDecimalsFormatNumber( - transfer.kind.dispatchStrategy?.stakingRequirement || 0, + kind.dispatchStrategy?.stakingRequirement || 0, transfer.asset?.decimals || 0 )}{' '} {transfer.asset?.symbol} - {/* */} - {t('Notional TWAP Requirement')}{' '} + {t('Notional TWAP')}{' '} {addDecimalsFormatNumber( - transfer.kind.dispatchStrategy + kind.dispatchStrategy ?.notionalTimeWeightedAveragePositionRequirement || 0, transfer.asset?.decimals || 0 )}{' '} {transfer.asset?.symbol} - {/* */}
@@ -345,8 +404,8 @@ const getGradientClasses = (d: DispatchMetric | undefined) => { }; case DispatchMetric.DISPATCH_METRIC_LP_FEES_RECEIVED: return { - gradientClassName: 'from-vega-purple-500 to-vega-blue-400', - mainClassName: 'from-vega-purple-400 dark:from-vega-purple-600 to-20%', + gradientClassName: 'from-vega-green-500 to-vega-yellow-500', + mainClassName: 'from-vega-green-400 dark:from-vega-green-600 to-20%', }; case DispatchMetric.DISPATCH_METRIC_MAKER_FEES_PAID: return { @@ -354,11 +413,11 @@ const getGradientClasses = (d: DispatchMetric | undefined) => { mainClassName: 'from-vega-orange-400 dark:from-vega-orange-600 to-20%', }; case DispatchMetric.DISPATCH_METRIC_MARKET_VALUE: - return { - gradientClassName: 'from-vega-green-500 to-vega-yellow-500', - mainClassName: 'from-vega-green-400 dark:from-vega-green-600 to-20%', - }; case DispatchMetric.DISPATCH_METRIC_RELATIVE_RETURN: + return { + gradientClassName: 'from-vega-purple-500 to-vega-blue-400', + mainClassName: 'from-vega-purple-400 dark:from-vega-purple-600 to-20%', + }; case DispatchMetric.DISPATCH_METRIC_RETURN_VOLATILITY: return { gradientClassName: 'from-vega-blue-500 to-vega-green-400', @@ -367,8 +426,8 @@ const getGradientClasses = (d: DispatchMetric | undefined) => { case DispatchMetric.DISPATCH_METRIC_VALIDATOR_RANKING: default: return { - gradientClassName: 'from-vega-purple-500 to-vega-blue-400', - mainClassName: 'from-vega-purple-400 dark:from-vega-purple-600 to-20%', + gradientClassName: 'from-vega-pink-500 to-vega-purple-400', + mainClassName: 'from-vega-pink-400 dark:from-vega-pink-600 to-20%', }; } }; diff --git a/apps/trading/components/rewards-container/rewards-container.tsx b/apps/trading/components/rewards-container/rewards-container.tsx index 6806a83b5..3f57bd84f 100644 --- a/apps/trading/components/rewards-container/rewards-container.tsx +++ b/apps/trading/components/rewards-container/rewards-container.tsx @@ -254,7 +254,7 @@ export const RewardsContainer = () => { ); })} {pubKey && streaks && ( - + {streaks.map((streak, i) => ( { )} - - - + { @@ -62,6 +64,11 @@ export const ActivityStreak = ({ .toNumber(); }; + const epochsStreak = + tiers[userTierIndex].minimum_activity_streak - streak.activeFor >= 0 + ? tiers[userTierIndex].minimum_activity_streak - streak.activeFor + : 0; + const progressBarHeight = 'h-10'; return ( @@ -179,13 +186,7 @@ export const ActivityStreak = ({ {t('{{epochs}} epochs streak', { - epochs: - tiers[userTierIndex].minimum_activity_streak - - streak.activeFor >= - 0 - ? tiers[userTierIndex].minimum_activity_streak - - streak.activeFor - : 0, + epochs: formatNumber(epochsStreak), })}  {' '} diff --git a/apps/trading/components/rewards-container/streaks/reward-hoarder-bonus.tsx b/apps/trading/components/rewards-container/streaks/reward-hoarder-bonus.tsx index c3eb47009..463be9f19 100644 --- a/apps/trading/components/rewards-container/streaks/reward-hoarder-bonus.tsx +++ b/apps/trading/components/rewards-container/streaks/reward-hoarder-bonus.tsx @@ -32,9 +32,12 @@ export const RewardHoarderBonus = ({ ); return userTier; }; - + if (!tiers || tiers.length === 0) return null; const userTierIndex = getUserTier() - 1; + // TODO: extract qUSD from the API + const qAsset = 'qUSD'; + const safeProgress = (i: number) => { if (i < userTierIndex) return 100; if (i > userTierIndex) return 0; @@ -71,9 +74,7 @@ export const RewardHoarderBonus = ({ })} - {t('{{epochs}} qUSD', { - epochs: formatNumber(tier.minimum_quantum_balance), - })} + {formatNumber(tier.minimum_quantum_balance)} {qAsset}
@@ -147,7 +148,9 @@ export const RewardHoarderBonus = ({
- {vestingDetails.quantumBalance} qUSD + + {formatNumber(vestingDetails.quantumBalance)} {qAsset} +