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.')}
);
};