import maxBy from 'lodash/maxBy'; import minBy from 'lodash/minBy'; import { useVegaWallet } from '@vegaprotocol/wallet'; import { useNetworkParams, NetworkParams, } from '@vegaprotocol/network-parameters'; import { useMarketList } from '@vegaprotocol/markets'; import { formatNumber, formatNumberRounded } from '@vegaprotocol/utils'; import { useDiscountProgramsQuery, useFeesQuery } from './__generated__/Fees'; import { Card, CardStat, CardTable, CardTableTD, CardTableTH } from '../card'; import { MarketFees } from './market-fees'; import { useVolumeStats } from './use-volume-stats'; import { useReferralStats } from './use-referral-stats'; import { formatPercentage, getAdjustedFee } from './utils'; import { Table as SimpleTable } from '../../components/table'; import BigNumber from 'bignumber.js'; import { Links } from '../../lib/links'; import { Link } from 'react-router-dom'; import { Tooltip, VegaIcon, VegaIconNames, truncateMiddle, } from '@vegaprotocol/ui-toolkit'; import { useT } from '../../lib/use-t'; import classNames from 'classnames'; import { getTierGradient } from '../helpers/tiers'; export const FeesContainer = () => { const t = useT(); const { pubKey } = useVegaWallet(); const { params, loading: paramsLoading } = useNetworkParams([ NetworkParams.market_fee_factors_makerFee, NetworkParams.market_fee_factors_infrastructureFee, ]); const { data: markets, loading: marketsLoading } = useMarketList(); const { data: programData, loading: programLoading } = useDiscountProgramsQuery({ errorPolicy: 'ignore', fetchPolicy: 'cache-and-network', pollInterval: 15000, }); const volumeDiscountWindowLength = programData?.currentVolumeDiscountProgram?.windowLength || 1; const referralDiscountWindowLength = programData?.currentReferralProgram?.windowLength || 1; const { data: feesData, loading: feesLoading } = useFeesQuery({ variables: { partyId: pubKey || '', }, skip: !pubKey, fetchPolicy: 'cache-and-network', pollInterval: 15000, }); const previousEpoch = (Number(feesData?.epoch.id) || 0) - 1; const { volumeDiscount, volumeTierIndex, volumeInWindow, volumeTiers } = useVolumeStats( previousEpoch, feesData?.volumeDiscountStats.edges?.[0]?.node, programData?.currentVolumeDiscountProgram ); const { referralDiscount, referralVolumeInWindow, referralTierIndex, referralTiers, epochsInSet, code, isReferrer, } = useReferralStats( previousEpoch, feesData?.referralSetStats.edges?.[0]?.node, feesData?.referralSetReferees.edges?.[0]?.node, programData?.currentReferralProgram, feesData?.referrer.edges?.[0]?.node, feesData?.referee.edges?.[0]?.node ); const loading = paramsLoading || feesLoading || programLoading; const isConnected = Boolean(pubKey); const isReferralProgramRunning = Boolean(programData?.currentReferralProgram); const isVolumeDiscountProgramRunning = Boolean( programData?.currentVolumeDiscountProgram ); return (
{isConnected && ( <> {isVolumeDiscountProgramRunning ? ( ) : (

{t('No volume discount program active')}

)}
{isReferrer ? ( ) : isReferralProgramRunning ? ( ) : (

{t('No referral program active')}

)}
)}
); }; export const TradingFees = ({ params, markets, referralDiscount, volumeDiscount, }: { params: { market_fee_factors_infrastructureFee: string; market_fee_factors_makerFee: string; }; markets: Array<{ fees: { factors: { liquidityFee: string } } }> | null; referralDiscount: number; volumeDiscount: number; }) => { const t = useT(); const referralDiscountBigNum = new BigNumber(referralDiscount); const volumeDiscountBigNum = new BigNumber(volumeDiscount); // Show min and max liquidity fees from all markets const minLiq = minBy(markets, (m) => Number(m.fees.factors.liquidityFee)); const maxLiq = maxBy(markets, (m) => Number(m.fees.factors.liquidityFee)); const total = new BigNumber(params.market_fee_factors_makerFee).plus( new BigNumber(params.market_fee_factors_infrastructureFee) ); const adjustedTotal = getAdjustedFee( [total], [referralDiscountBigNum, volumeDiscountBigNum] ); let minTotal; let maxTotal; let minAdjustedTotal; let maxAdjustedTotal; if (minLiq && maxLiq) { const minLiqFee = new BigNumber(minLiq.fees.factors.liquidityFee); const maxLiqFee = new BigNumber(maxLiq.fees.factors.liquidityFee); minTotal = total.plus(minLiqFee); maxTotal = total.plus(maxLiqFee); minAdjustedTotal = getAdjustedFee( [total, minLiqFee], [referralDiscountBigNum, volumeDiscountBigNum] ); maxAdjustedTotal = getAdjustedFee( [total, maxLiqFee], [referralDiscountBigNum, volumeDiscountBigNum] ); } return (

{minAdjustedTotal !== undefined && maxAdjustedTotal !== undefined ? `${formatPercentage(minAdjustedTotal)}%-${formatPercentage( maxAdjustedTotal )}%` : `${formatPercentage(adjustedTotal)}%`}

{t('Total fee before discount')} {minTotal !== undefined && maxTotal !== undefined ? `${formatPercentage(minTotal.toNumber())}%-${formatPercentage( maxTotal.toNumber() )}%` : `${formatPercentage(total.toNumber())}%`} {t('Infrastructure')} {formatPercentage( Number(params.market_fee_factors_infrastructureFee) )} % {t('Maker')} {formatPercentage(Number(params.market_fee_factors_makerFee))}% {minLiq && maxLiq && ( {t('Liquidity')} {formatPercentage(Number(minLiq.fees.factors.liquidityFee))}% {'-'} {formatPercentage(Number(maxLiq.fees.factors.liquidityFee))}% )}
); }; export const CurrentVolume = ({ tiers, tierIndex, windowLengthVolume, windowLength, }: { tiers: Array<{ minimumRunningNotionalTakerVolume: string }>; tierIndex: number; windowLengthVolume: number; windowLength: number; }) => { const t = useT(); const nextTier = tiers[tierIndex + 1]; const requiredForNextTier = nextTier ? new BigNumber(nextTier.minimumRunningNotionalTakerVolume).minus( windowLengthVolume ) : new BigNumber(0); const currentVolume = new BigNumber(windowLengthVolume); return (
{requiredForNextTier.isGreaterThan(0) && ( )}
); }; const ReferralBenefits = ({ epochsInSet, setRunningNotionalTakerVolume, epochs, }: { epochsInSet: number; setRunningNotionalTakerVolume: number; epochs: number; }) => { const t = useT(); return (
); }; const TotalDiscount = ({ referralDiscount, volumeDiscount, isReferralProgramRunning, isVolumeDiscountProgramRunning, }: { referralDiscount: number; volumeDiscount: number; isReferralProgramRunning: boolean; isVolumeDiscountProgramRunning: boolean; }) => { const t = useT(); const totalDiscount = 1 - (1 - volumeDiscount) * (1 - referralDiscount); const totalDiscountDescription = t( 'The total discount is calculated according to the following formula: ' ); const formula = ( 1 - (1 - dvolume) ⋇ (1 - dreferral) ); return (
{totalDiscountDescription} {formula} } value={formatPercentage(totalDiscount) + '%'} highlight={true} testId="total-discount" /> {t('Volume discount')} {formatPercentage(volumeDiscount)}% {!isVolumeDiscountProgramRunning && ( {' '} )} {t('Referral discount')} {formatPercentage(referralDiscount)}% {!isReferralProgramRunning && ( {' '} )}
); }; const VolumeTiers = ({ tiers, tierIndex, lastEpochVolume, windowLength, }: { tiers: Array<{ volumeDiscountFactor: string; minimumRunningNotionalTakerVolume: string; }>; tierIndex: number; lastEpochVolume: number; windowLength: number; }) => { const t = useT(); if (!tiers.length) { return (

{t('No volume discount program active')}

); } return (
{ const isUserTier = tierIndex === i; const indicator = isUserTier ? ( ) : null; const tierIndicator = (
{i + 1} {indicator}
); return { tier: tierIndicator, discount: ( <>{formatPercentage(Number(tier.volumeDiscountFactor))}% ), minTradingVolume: ( <>{formatNumber(tier.minimumRunningNotionalTakerVolume)} ), myVolume: isUserTier ? ( formatNumber(lastEpochVolume) ) : ( - ), indicator: indicator, className: classNames( getTierGradient(i + 1, tiers.length), 'text-xs' ), }; })} />
); }; const ReferralTiers = ({ tiers, tierIndex, epochsInSet, referralVolumeInWindow, referralDiscountWindowLength, }: { tiers: Array<{ referralDiscountFactor: string; minimumRunningNotionalTakerVolume: string; minimumEpochs: number; }>; tierIndex: number; epochsInSet: number; referralVolumeInWindow: number; referralDiscountWindowLength: number; }) => { const t = useT(); if (!tiers.length) { return (

{t('No referral program active')}

); } return (
{ const isUserTier = tierIndex === i; const requiredVolume = Number(tier.minimumRunningNotionalTakerVolume); const indicator = isUserTier ? ( ) : referralVolumeInWindow >= requiredVolume && epochsInSet < tier.minimumEpochs ? ( Unlocks in {tier.minimumEpochs - epochsInSet} epochs ) : null; const tierIndicator = (
{i + 1} {indicator}
); return { tier: tierIndicator, discount: ( <>{formatPercentage(Number(tier.referralDiscountFactor))}% ), volume: formatNumber(tier.minimumRunningNotionalTakerVolume), epochs: tier.minimumEpochs, indicator, className: classNames( getTierGradient(i + 1, tiers.length), 'text-xs' ), }; })} />
); }; interface YourTierProps { testId?: string; } const YourTier = ({ testId }: YourTierProps) => { const t = useT(); return ( {t('Your tier')} ); }; const ReferrerInfo = ({ code }: { code?: string }) => { const t = useT(); return (

{t('Connected key is owner of the referral set')} {code && ( <> {' '} {truncateMiddle(code)} )} {'. '} {t('As owner, it is eligible for commission not fee discounts.')}

{t('See')}{' '} {t('Referrals')} {' '} {t('for more information.')}

); };