import { useActiveRewardsQuery, useMarketForRewardsQuery, } from './__generated__/Rewards'; import { useT } from '../../lib/use-t'; import { addDecimalsFormatNumber } from '@vegaprotocol/utils'; import classNames from 'classnames'; import { Icon, type IconName, Intent, Tooltip, VegaIcon, VegaIconNames, type VegaIconSize, TradingInput, } from '@vegaprotocol/ui-toolkit'; import { IconNames } from '@blueprintjs/icons'; import { DistributionStrategyDescriptionMapping, DistributionStrategyMapping, EntityScope, EntityScopeMapping, type Maybe, type Transfer, type TransferNode, TransferStatus, TransferStatusMapping, DispatchMetric, DispatchMetricDescription, DispatchMetricLabels, type RecurringTransfer, EntityScopeLabelMapping, } from '@vegaprotocol/types'; import { Card } from '../card/card'; import { useMemo, useState } from 'react'; import { type AssetFieldsFragment, useAssetDataProvider, useAssetsMapProvider, } from '@vegaprotocol/assets'; import { type MarketFieldsFragment, useMarketsMapProvider, } from '@vegaprotocol/markets'; export type Filter = { searchTerm: string; }; export 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; } return true; }; export const applyFilter = ( node: TransferNode & { asset?: AssetFieldsFragment | null; marketIds?: (MarketFieldsFragment | null)[]; }, filter: Filter ) => { const { transfer } = node; if ( transfer.kind.__typename !== 'RecurringTransfer' || !transfer.kind.dispatchStrategy?.dispatchMetric ) { return false; } if ( DispatchMetricLabels[transfer.kind.dispatchStrategy.dispatchMetric] .toLowerCase() .includes(filter.searchTerm.toLowerCase()) || transfer.asset?.symbol .toLowerCase() .includes(filter.searchTerm.toLowerCase()) || EntityScopeLabelMapping[transfer.kind.dispatchStrategy.entityScope] .toLowerCase() .includes(filter.searchTerm.toLowerCase()) || node.asset?.name .toLocaleLowerCase() .includes(filter.searchTerm.toLowerCase()) || node.marketIds?.some((m) => m?.tradableInstrument?.instrument?.name .toLocaleLowerCase() .includes(filter.searchTerm.toLowerCase()) ) ) { return true; } return false; }; export const ActiveRewards = ({ currentEpoch }: { currentEpoch: number }) => { const t = useT(); const { data: activeRewardsData } = useActiveRewardsQuery({ variables: { isReward: true, }, }); const [filter, setFilter] = useState({ searchTerm: '', }); const { data: assets } = useAssetsMapProvider(); const { data: markets } = useMarketsMapProvider(); const transfers = activeRewardsData?.transfersConnection?.edges ?.map((e) => e?.node as TransferNode) .filter((node) => isActiveReward(node, currentEpoch)) .map((node) => { if (node.transfer.kind.__typename !== 'RecurringTransfer') { return node; } const asset = assets && assets[ node.transfer.kind.dispatchStrategy?.dispatchMetricAssetId || '' ]; const marketIds = node.transfer.kind.dispatchStrategy?.marketIdsInScope?.map( (id) => markets && markets[id] ); return { ...node, asset, marketIds }; }); if (!transfers || !transfers.length) return null; return (
{transfers.length > 1 && ( setFilter((curr) => ({ ...curr, searchTerm: e.target.value })) } value={filter.searchTerm} type="text" placeholder={t( 'Search by reward dispatch metric, entity scope or asset name' )} data-testid="search-term" className="mb-4 w-20" prependElement={} /> )}
{transfers .filter((n) => applyFilter(n, filter)) .map((node, i) => { const { transfer } = node; if ( transfer.kind.__typename !== 'RecurringTransfer' || !transfer.kind.dispatchStrategy?.dispatchMetric ) { 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, }: { status: TransferStatus; reason?: Maybe | undefined; }) => { const t = useT(); const getIconIntent = (status: string) => { switch (status) { case TransferStatus.STATUS_DONE: return { icon: IconNames.TICK_CIRCLE, intent: Intent.Success }; case TransferStatus.STATUS_CANCELLED: return { icon: IconNames.MOON, intent: Intent.None }; case TransferStatus.STATUS_PENDING: return { icon: IconNames.HELP, intent: Intent.Primary }; case TransferStatus.STATUS_REJECTED: return { icon: IconNames.ERROR, intent: Intent.Danger }; case TransferStatus.STATUS_STOPPED: return { icon: IconNames.ERROR, intent: Intent.Danger }; default: return { icon: IconNames.HELP, intent: Intent.Primary }; } }; const { icon, intent } = getIconIntent(status); return ( {t('Transfer status: {{status}} {{reason}}', { status: TransferStatusMapping[status], reason: reason ? `(${reason})` : '', })} } > ); }; export const ActiveRewardCard = ({ transferNode, currentEpoch, kind, }: { transferNode: TransferNode; currentEpoch: number; kind: RecurringTransfer; }) => { const t = useT(); const { transfer } = transferNode; 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]); const { data: dispatchAsset } = useAssetDataProvider( dispatchStrategy?.dispatchMetricAssetId || '' ); if (!dispatchStrategy) { return null; } const { gradientClassName, mainClassName } = getGradientClasses( dispatchStrategy.dispatchMetric ); const entityScope = dispatchStrategy.entityScope; return (
{entityScope && ( {EntityScopeLabelMapping[entityScope] || t('Unspecified')} )}

{addDecimalsFormatNumber( transferNode.transfer.amount, transferNode.transfer.asset?.decimals || 0, 6 )} {transferNode.transfer.asset?.symbol}

{ { DistributionStrategyMapping[ dispatchStrategy.distributionStrategy ] } }
{t('numberEpochs', '{{count}} epochs', { count: kind.dispatchStrategy?.lockPeriod, })}
{DispatchMetricLabels[dispatchStrategy.dispatchMetric]} {marketName ? ` • ${marketName}` : ` • ${dispatchAsset?.name}`}
{kind.endEpoch && ( {t('Ends in')} {t('numberEpochs', '{{count}} epochs', { count: kind.endEpoch - currentEpoch, })} )} { {t('Assessed over')} {t('numberEpochs', '{{count}} epochs', { count: dispatchStrategy.windowLength, })} }
{dispatchStrategy?.dispatchMetric && ( {t(DispatchMetricDescription[dispatchStrategy?.dispatchMetric])} )}
{t('Entity scope')}{' '} {kind.dispatchStrategy?.teamScope && ( {kind.dispatchStrategy?.teamScope} } > {} )} {kind.dispatchStrategy?.individualScope && ( {kind.dispatchStrategy?.individualScope} } > {} )} {/* Shows transfer status */} {/* */} {t('Staked VEGA')}{' '} {addDecimalsFormatNumber( kind.dispatchStrategy?.stakingRequirement || 0, transfer.asset?.decimals || 0 )} {t('Average position')}{' '} {addDecimalsFormatNumber( kind.dispatchStrategy ?.notionalTimeWeightedAveragePositionRequirement || 0, transfer.asset?.decimals || 0 )}
); }; const getGradientClasses = (d: DispatchMetric | undefined) => { switch (d) { case DispatchMetric.DISPATCH_METRIC_AVERAGE_POSITION: return { gradientClassName: 'from-vega-pink-500 to-vega-purple-400', mainClassName: 'from-vega-pink-400 dark:from-vega-pink-600 to-20%', }; case DispatchMetric.DISPATCH_METRIC_LP_FEES_RECEIVED: 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_MAKER_FEES_PAID: return { gradientClassName: 'from-vega-orange-500 to-vega-pink-400', mainClassName: 'from-vega-orange-400 dark:from-vega-orange-600 to-20%', }; case DispatchMetric.DISPATCH_METRIC_MARKET_VALUE: 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', mainClassName: 'from-vega-blue-400 dark:from-vega-blue-600 to-20%', }; case DispatchMetric.DISPATCH_METRIC_VALIDATOR_RANKING: default: return { gradientClassName: 'from-vega-pink-500 to-vega-purple-400', mainClassName: 'from-vega-pink-400 dark:from-vega-pink-600 to-20%', }; } }; const CardIcon = ({ size = 18, iconName, tooltip, }: { size?: VegaIconSize; iconName: VegaIconNames; tooltip: string; }) => { return ( {tooltip}}> ); }; const EntityIcon = ({ transfer, size = 18, }: { transfer: Transfer; size?: VegaIconSize; }) => { if (transfer.kind.__typename !== 'RecurringTransfer') { return null; } const entityScope = transfer.kind.dispatchStrategy?.entityScope; const getIconName = () => { switch (entityScope) { case EntityScope.ENTITY_SCOPE_TEAMS: return VegaIconNames.TEAM; case EntityScope.ENTITY_SCOPE_INDIVIDUALS: return VegaIconNames.MAN; default: return VegaIconNames.QUESTION_MARK; } }; const iconName = getIconName(); return ( {entityScope ? EntityScopeMapping[entityScope] : ''} } > {iconName && } ); };