import groupBy from 'lodash/groupBy'; import type { Account } from '@vegaprotocol/accounts'; import { useAccounts } from '@vegaprotocol/accounts'; import { NetworkParams, useNetworkParams, } from '@vegaprotocol/network-parameters'; import { AccountType } from '@vegaprotocol/types'; import { useVegaWallet } from '@vegaprotocol/wallet'; import BigNumber from 'bignumber.js'; import { Card, CardStat, CardTable, CardTableTD, CardTableTH, } from '../card/card'; import { type RewardsPageQuery, useRewardsPageQuery, useRewardsEpochQuery, } from './__generated__/Rewards'; import { TradingButton, VegaIcon, VegaIconNames, } from '@vegaprotocol/ui-toolkit'; import { formatPercentage } from '../fees-container/utils'; import { addDecimalsFormatNumberQuantum } from '@vegaprotocol/utils'; import { ViewType, useSidebar } from '../sidebar'; import { useGetCurrentRouteId } from '../../lib/hooks/use-get-current-route-id'; import { RewardsHistoryContainer } from './rewards-history'; import { useT } from '../../lib/use-t'; export const RewardsContainer = () => { const t = useT(); const { pubKey } = useVegaWallet(); const { params, loading: paramsLoading } = useNetworkParams([ NetworkParams.reward_asset, NetworkParams.rewards_activityStreak_benefitTiers, NetworkParams.rewards_vesting_baseRate, ]); const { data: accounts, loading: accountsLoading } = useAccounts(pubKey); const { data: epochData } = useRewardsEpochQuery(); // No need to specify the fromEpoch as it will by default give you the last const { data: rewardsData, loading: rewardsLoading } = useRewardsPageQuery({ variables: { partyId: pubKey || '', }, }); if (!epochData?.epoch) return null; const loading = paramsLoading || accountsLoading || rewardsLoading; const rewardAccounts = accounts ? accounts.filter((a) => [ AccountType.ACCOUNT_TYPE_VESTED_REWARDS, AccountType.ACCOUNT_TYPE_VESTING_REWARDS, ].includes(a.type) ) : []; const rewardAssetsMap = groupBy( rewardAccounts.filter((a) => a.asset.id !== params.reward_asset), 'asset.id' ); return (
{/* Always show reward information for vega */} {/* Show all other reward pots, most of the time users will not have other rewards */} {Object.keys(rewardAssetsMap).map((assetId) => { const asset = rewardAssetsMap[assetId][0].asset; return ( ); })}
); }; type VestingBalances = NonNullable< RewardsPageQuery['party'] >['vestingBalancesSummary']; export type RewardPotProps = { pubKey: string | null; accounts: Account[] | null; assetId: string; // VEGA vestingBalancesSummary: VestingBalances | undefined; }; export const RewardPot = ({ pubKey, accounts, assetId, vestingBalancesSummary, }: RewardPotProps) => { const t = useT(); // TODO: Opening the sidebar for the first time works, but then clicking on redeem // for a different asset does not update the form const currentRouteId = useGetCurrentRouteId(); const setViews = useSidebar((store) => store.setViews); // All vested rewards accounts const availableRewardAssetAccounts = accounts ? accounts.filter((a) => { return ( a.asset.id === assetId && a.type === AccountType.ACCOUNT_TYPE_VESTED_REWARDS ); }) : []; // Sum of all vested reward account balances const totalVestedRewardsByRewardAsset = BigNumber.sum.apply( null, availableRewardAssetAccounts.length ? availableRewardAssetAccounts.map((a) => a.balance) : [0] ); const lockedEntries = vestingBalancesSummary?.lockedBalances?.filter( (b) => b.asset.id === assetId ); const lockedBalances = lockedEntries?.length ? lockedEntries.map((e) => e.balance) : [0]; const totalLocked = BigNumber.sum.apply(null, lockedBalances); const vestingEntries = vestingBalancesSummary?.vestingBalances?.filter( (b) => b.asset.id === assetId ); const vestingBalances = vestingEntries?.length ? vestingEntries.map((e) => e.balance) : [0]; const totalVesting = BigNumber.sum.apply(null, vestingBalances); const totalRewards = totalLocked.plus(totalVesting); let rewardAsset = undefined; if (availableRewardAssetAccounts.length) { rewardAsset = availableRewardAssetAccounts[0].asset; } else if (lockedEntries?.length) { rewardAsset = lockedEntries[0].asset; } else if (vestingEntries?.length) { rewardAsset = vestingEntries[0].asset; } if (!pubKey) { return (

{t('Not connected')}

); } return (
{rewardAsset ? ( <>
{t('Locked {{assetSymbol}}', { assetSymbol: rewardAsset.symbol, })} {addDecimalsFormatNumberQuantum( totalLocked.toString(), rewardAsset.decimals, rewardAsset.quantum )} {t('Vesting {{assetSymbol}}', { assetSymbol: rewardAsset.symbol, })} {addDecimalsFormatNumberQuantum( totalVesting.toString(), rewardAsset.decimals, rewardAsset.quantum )} {t('Available to withdraw this epoch')} {addDecimalsFormatNumberQuantum( totalVestedRewardsByRewardAsset.toString(), rewardAsset.decimals, rewardAsset.quantum )} {totalVestedRewardsByRewardAsset.isGreaterThan(0) && (
setViews( { type: ViewType.Transfer, assetId }, currentRouteId ) } size="small" > {t('Redeem rewards')}
)}
) : (

{t('No rewards')}

)}
); }; export const Vesting = ({ pubKey, baseRate, multiplier = '1', }: { pubKey: string | null; baseRate: string; multiplier?: string; }) => { const t = useT(); const rate = new BigNumber(baseRate).times(multiplier); const rateFormatted = formatPercentage(Number(rate)); const baseRateFormatted = formatPercentage(Number(baseRate)); return (
{t('Base rate')} {baseRateFormatted}% {pubKey && ( {t('Vesting multiplier')} {multiplier}x )}
); }; export const Multipliers = ({ pubKey, streakMultiplier = '1', hoarderMultiplier = '1', }: { pubKey: string | null; streakMultiplier?: string; hoarderMultiplier?: string; }) => { const t = useT(); const combinedMultiplier = new BigNumber(streakMultiplier).times( hoarderMultiplier ); if (!pubKey) { return (

{t('Not connected')}

); } return (
{t('Streak reward multiplier')} {streakMultiplier}x {t('Hoarder reward multiplier')} {hoarderMultiplier}x
); };