import minBy from 'lodash/minBy'; import { CodeTile, StatTile } from './tile'; import { VegaIcon, VegaIconNames, truncateMiddle, ExternalLink, Tooltip, } from '@vegaprotocol/ui-toolkit'; import { useVegaWallet } from '@vegaprotocol/wallet'; import { DEFAULT_AGGREGATION_DAYS, useReferral } from './hooks/use-referral'; import { CreateCodeContainer } from './create-code-form'; import classNames from 'classnames'; import { Table } from './table'; import { addDecimalsFormatNumber, getDateFormat, getDateTimeFormat, getNumberFormat, getUserLocale, removePaginationWrapper, } from '@vegaprotocol/utils'; import { useReferralSetStatsQuery } from './hooks/__generated__/ReferralSetStats'; import compact from 'lodash/compact'; import { useReferralProgram } from './hooks/use-referral-program'; import { useStakeAvailable } from './hooks/use-stake-available'; import sortBy from 'lodash/sortBy'; import { useLayoutEffect, useMemo, useRef, useState } from 'react'; import { useCurrentEpochInfoQuery } from './hooks/__generated__/Epoch'; import BigNumber from 'bignumber.js'; import { DocsLinks } from '@vegaprotocol/environment'; import { useT, ns } from '../../lib/use-t'; import { Trans } from 'react-i18next'; import { ApplyCodeForm } from './apply-code-form'; export const ReferralStatistics = () => { const { pubKey } = useVegaWallet(); const program = useReferralProgram(); const { data: referee } = useReferral({ pubKey, role: 'referee', aggregationEpochs: program.details?.windowLength, }); const { data: referrer } = useReferral({ pubKey, role: 'referrer', aggregationEpochs: program.details?.windowLength, }); if (referee?.code) { return ( <> ; {!referee.isEligible && } ); } if (referrer?.code) { return ( <> ; ); } return ; }; export const useStats = ({ data, program, as, }: { data?: NonNullable['data']>; program: ReturnType; as?: 'referrer' | 'referee'; }) => { const { benefitTiers } = program; const { data: epochData } = useCurrentEpochInfoQuery(); const { data: statsData } = useReferralSetStatsQuery({ variables: { code: data?.code || '', }, skip: !data?.code, fetchPolicy: 'cache-and-network', }); const currentEpoch = Number(epochData?.epoch.id); const stats = statsData?.referralSetStats.edges && compact(removePaginationWrapper(statsData.referralSetStats.edges)); const refereeInfo = data?.referee; const refereeStats = stats?.find( (r) => r.partyId === data?.referee?.refereeId ); const statsAvailable = stats && stats.length > 0 && stats[0]; const baseCommissionValue = statsAvailable ? Number(statsAvailable.rewardFactor) : 0; const runningVolumeValue = statsAvailable ? Number(statsAvailable.referralSetRunningNotionalTakerVolume) : 0; const referrerVolumeValue = statsAvailable ? Number(statsAvailable.referrerTakerVolume) : 0; const multiplier = statsAvailable ? Number(statsAvailable.rewardsMultiplier) : 1; const finalCommissionValue = isNaN(multiplier) ? baseCommissionValue : multiplier * baseCommissionValue; const discountFactorValue = refereeStats?.discountFactor ? Number(refereeStats.discountFactor) : 0; const currentBenefitTierValue = benefitTiers.find( (t) => !isNaN(discountFactorValue) && !isNaN(t.discountFactor) && t.discountFactor === discountFactorValue ); const nextBenefitTierValue = currentBenefitTierValue ? benefitTiers.find((t) => t.tier === currentBenefitTierValue.tier - 1) : minBy(benefitTiers, (bt) => bt.tier); // min tier number is lowest tier const epochsValue = !isNaN(currentEpoch) && refereeInfo?.atEpoch ? currentEpoch - refereeInfo?.atEpoch : 0; const nextBenefitTierVolumeValue = nextBenefitTierValue ? nextBenefitTierValue.minimumVolume - runningVolumeValue : 0; const nextBenefitTierEpochsValue = nextBenefitTierValue ? nextBenefitTierValue.epochs - epochsValue : 0; return { baseCommissionValue, runningVolumeValue, referrerVolumeValue, multiplier, finalCommissionValue, discountFactorValue, currentBenefitTierValue, nextBenefitTierValue, epochsValue, nextBenefitTierVolumeValue, nextBenefitTierEpochsValue, }; }; export const Statistics = ({ data, program, as, }: { data: NonNullable['data']>; program: ReturnType; as: 'referrer' | 'referee'; }) => { const t = useT(); const { baseCommissionValue, runningVolumeValue, referrerVolumeValue, multiplier, finalCommissionValue, discountFactorValue, currentBenefitTierValue, epochsValue, nextBenefitTierVolumeValue, nextBenefitTierEpochsValue, } = useStats({ data, program, as }); const isApplyCodePreview = useMemo( () => data.referee === null, [data.referee] ); const { benefitTiers } = useReferralProgram(); const { stakeAvailable, isEligible } = useStakeAvailable(); const { details } = program; const compactNumFormat = new Intl.NumberFormat(getUserLocale(), { minimumFractionDigits: 0, maximumFractionDigits: 2, notation: 'compact', compactDisplay: 'short', }); const baseCommissionTile = ( {baseCommissionValue * 100}% ); const stakingMultiplierTile = ( {t('{{amount}} $VEGA staked', { amount: addDecimalsFormatNumber( stakeAvailable?.toString() || 0, 18 ), })} } > {multiplier || t('None')} ); const finalCommissionTile = ( {finalCommissionValue * 100}% ); const numberOfTradersValue = data.referees.length; const numberOfTradersTile = ( {numberOfTradersValue} ); const codeTile = ( ); const referrerVolumeTile = ( {compactNumFormat.format(referrerVolumeValue)} ); const totalCommissionValue = data.referees .map((r) => new BigNumber(r.totalRefereeGeneratedRewards)) .reduce((all, r) => all.plus(r), new BigNumber(0)); const totalCommissionTile = ( } > {getNumberFormat(0).format(Number(totalCommissionValue))} ); const referrerTiles = ( <>
{baseCommissionTile} {stakingMultiplierTile} {finalCommissionTile}
{codeTile} {referrerVolumeTile} {numberOfTradersTile} {totalCommissionTile}
); const currentBenefitTierTile = ( {isApplyCodePreview ? currentBenefitTierValue?.tier || benefitTiers[0]?.tier || 'None' : currentBenefitTierValue?.tier || 'None'} ); const discountFactorTile = ( {isApplyCodePreview ? benefitTiers[0].discountFactor * 100 : discountFactorValue * 100} % ); const runningVolumeTile = ( {compactNumFormat.format(runningVolumeValue)} ); const epochsTile = ( {epochsValue} ); const nextTierVolumeTile = ( {nextBenefitTierVolumeValue <= 0 ? '0' : compactNumFormat.format(nextBenefitTierVolumeValue)} ); const nextTierEpochsTile = ( {nextBenefitTierEpochsValue <= 0 ? '0' : nextBenefitTierEpochsValue} ); const refereeTiles = ( <>
{currentBenefitTierTile} {runningVolumeTile} {codeTile}
{discountFactorTile} {nextTierVolumeTile} {epochsTile} {nextTierEpochsTile}
); const eligibilityWarning = as === 'referee' && !isEligible && (

{t('Referral code no longer valid')}

{t( 'Your referral code is no longer valid as the referrer no longer meets the minimum requirements. Apply a new code to continue receiving discounts.' )}

); return (
{as === 'referrer' && referrerTiles} {as === 'referee' && refereeTiles}
{eligibilityWarning}
); }; export const RefereesTable = ({ data, program, }: { data: NonNullable['data']>; program: ReturnType; }) => { const t = useT(); const [collapsed, setCollapsed] = useState(false); const tableRef = useRef(null); const { details } = program; useLayoutEffect(() => { if ((tableRef.current?.getBoundingClientRect().height || 0) > 384) { setCollapsed(true); } }, []); return ( <> {/* Referees (only for referrer view) */} {data.referees.length > 0 && (

{t('Referees')}

), }, ]} data={sortBy( data.referees.map((r) => ({ party: ( {truncateMiddle(r.refereeId)} ), joined: getDateTimeFormat().format(new Date(r.joinedAt)), volume: Number(r.totalRefereeNotionalTakerVolume), commission: Number(r.totalRefereeGeneratedRewards), })), (r) => r.volume ) .map((r) => ({ ...r, volume: getNumberFormat(0).format(r.volume), commission: getNumberFormat(0).format(r.commission), })) .reverse()} /> )} ); }; export const QUSDTooltip = () => { const t = useT(); return (

{t( 'qUSD provides a rough USD equivalent of balances across all assets using the value of "Quantum" for that asset' )}

{DocsLinks && ( {t('Find out more')} )} } underline={true} > {t('qUSD')}
); };