feat(trading): show rank table in dispatch strategy tooltip (#6078)
This commit is contained in:
parent
5f8c725c5b
commit
bfa6be4c1c
@ -0,0 +1,41 @@
|
||||
import { usePayoutPerRank } from './rank-table';
|
||||
|
||||
// Test case as per https://docs.google.com/spreadsheets/d/1RpWKnGEf4eYMxjI-feauRa9vWz3pNGdP05ejQrwWatg/
|
||||
describe('usePayoutPerRank', () => {
|
||||
it('should return payouts per person', () => {
|
||||
const rankTable = [
|
||||
{
|
||||
startRank: 1,
|
||||
shareRatio: 10,
|
||||
},
|
||||
{
|
||||
startRank: 2,
|
||||
shareRatio: 5,
|
||||
},
|
||||
{
|
||||
startRank: 5,
|
||||
shareRatio: 5,
|
||||
},
|
||||
{
|
||||
startRank: 10,
|
||||
shareRatio: 3,
|
||||
},
|
||||
{
|
||||
startRank: 20,
|
||||
shareRatio: 0,
|
||||
},
|
||||
];
|
||||
|
||||
const result = usePayoutPerRank(rankTable);
|
||||
|
||||
expect(result).toEqual({
|
||||
numPayouts: [1, 3, 5, 10, 0],
|
||||
ratioShares: [10, 15, 25, 30, 0],
|
||||
payoutsPerTier: [0.125, 0.1875, 0.3125, 0.375, 0],
|
||||
payoutsPerWinner: [0.125, 0.0625, 0.0625, 0.0375],
|
||||
payoutsPerWinnerAsPercentage: [12.5, 6.25, 6.25, 3.75],
|
||||
endRanks: [2, 5, 10, 20],
|
||||
startRanks: [1, 2, 5, 10, 20],
|
||||
});
|
||||
});
|
||||
});
|
184
apps/trading/components/rewards-container/rank-table.tsx
Normal file
184
apps/trading/components/rewards-container/rank-table.tsx
Normal file
@ -0,0 +1,184 @@
|
||||
import type { RankTable } from '@vegaprotocol/types';
|
||||
import { formatNumber } from '@vegaprotocol/utils';
|
||||
import { useT } from '../../lib/use-t';
|
||||
|
||||
export const RankPayoutTable = ({
|
||||
rankTable,
|
||||
}: {
|
||||
rankTable: (RankTable | null)[] | null;
|
||||
}) => {
|
||||
const t = useT();
|
||||
const { payoutsPerWinnerAsPercentage, numPayouts, endRanks } =
|
||||
usePayoutPerRank(rankTable);
|
||||
let isOpenFinalTier = false;
|
||||
|
||||
return (
|
||||
rankTable &&
|
||||
rankTable.length > 0 && (
|
||||
<>
|
||||
<table className="min-w-full divide-y divide-gray-200">
|
||||
<thead className="">
|
||||
<tr className="text-right text-xs divide-x">
|
||||
<th scope="col">
|
||||
<span className="flex p-1 w-full justify-end">
|
||||
{t('Rank tier')}
|
||||
</span>
|
||||
</th>
|
||||
<th scope="col">
|
||||
<span className="flex p-1 w-full justify-end">
|
||||
{t('Start rank')}
|
||||
</span>
|
||||
</th>
|
||||
<th scope="col">
|
||||
<span className="flex p-1 w-full justify-end">
|
||||
{t('End rank')}
|
||||
</span>
|
||||
</th>
|
||||
<th scope="col">
|
||||
<span className="flex p-1 w-full justify-end">
|
||||
{t('Places paid')}
|
||||
</span>
|
||||
</th>
|
||||
<th scope="col">
|
||||
<span className="flex p-1 w-full justify-end">
|
||||
{t('Payout')}
|
||||
</span>
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody className="divide-y divide-gray-500 text-xs">
|
||||
{rankTable?.map((rank: RankTable | null, tier: number) => {
|
||||
const endRank = endRanks?.[tier];
|
||||
const placesPaid = numPayouts?.[tier];
|
||||
const isFinalTier = tier === rankTable.length - 1;
|
||||
isOpenFinalTier =
|
||||
isFinalTier && rank ? rank.shareRatio > 0 : false;
|
||||
|
||||
if (isFinalTier && rank?.shareRatio === 0) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
rank && (
|
||||
<tr
|
||||
key={`rank-table-row-${tier}`}
|
||||
className="whitespace-nowrap text-right divide-x p-4"
|
||||
>
|
||||
<td>
|
||||
<span className="flex p-1 justify-end">{tier + 1}</span>
|
||||
</td>
|
||||
<td>
|
||||
<span className="flex p-1 justify-end">
|
||||
{rank.startRank}
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
<span className="flex p-1 justify-end">
|
||||
{endRank || 'TBC*'}
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
<span className="flex p-1 justify-end">
|
||||
{placesPaid || 'TBC*'}
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
<span className="flex p-1 justify-end">
|
||||
{payoutsPerWinnerAsPercentage?.[tier]
|
||||
? `${formatNumber(
|
||||
payoutsPerWinnerAsPercentage[tier],
|
||||
2
|
||||
)} %`
|
||||
: 'TBC*'}
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
)
|
||||
);
|
||||
})}
|
||||
</tbody>
|
||||
</table>
|
||||
<p>
|
||||
{isOpenFinalTier &&
|
||||
t(
|
||||
'*Note: Final tier will payout to all game participants, therefore all payout estimates will lower depending on how many participants there are.'
|
||||
)}
|
||||
</p>
|
||||
</>
|
||||
)
|
||||
);
|
||||
};
|
||||
|
||||
// Use rank table to calculate payouts per rank
|
||||
// as per https://docs.google.com/spreadsheets/d/1RpWKnGEf4eYMxjI-feauRa9vWz3pNGdP05ejQrwWatg/
|
||||
export const usePayoutPerRank = (
|
||||
rankTable?: (RankTable | null)[] | null
|
||||
): {
|
||||
numPayouts?: number[];
|
||||
startRanks?: number[];
|
||||
endRanks?: number[];
|
||||
ratioShares?: number[];
|
||||
payoutsPerTier?: number[];
|
||||
payoutsPerWinner?: number[];
|
||||
payoutsPerWinnerAsPercentage?: number[];
|
||||
} => {
|
||||
if (!rankTable) return {};
|
||||
|
||||
const numPayouts: number[] = [],
|
||||
ratioShares: number[] = [],
|
||||
startRanks: number[] = [],
|
||||
endRanks: number[] = [],
|
||||
payoutsPerTier: number[] = [],
|
||||
payoutsPerWinner: number[] = [],
|
||||
payoutsPerWinnerAsPercentage: number[] = [];
|
||||
|
||||
let totalRatio = 0;
|
||||
|
||||
// We have to work out how many ppl in each bucket to get the total for the ratio
|
||||
rankTable.forEach((rank, tier) => {
|
||||
const startRank = rank?.startRank;
|
||||
startRank && startRanks.push(startRank);
|
||||
const endRank =
|
||||
tier < rankTable.length - 1 ? rankTable[tier + 1]?.startRank : undefined;
|
||||
endRank && endRanks.push(endRank);
|
||||
const numPayout = endRank && startRank ? endRank - startRank : 0;
|
||||
|
||||
numPayouts.push(numPayout);
|
||||
|
||||
const shareRatio = rank?.shareRatio;
|
||||
const ratioShare = shareRatio ? shareRatio * numPayout : 0;
|
||||
|
||||
ratioShares.push(ratioShare);
|
||||
|
||||
totalRatio += ratioShare;
|
||||
});
|
||||
|
||||
rankTable.forEach((_rank, tier) => {
|
||||
const ratioShare = ratioShares[tier];
|
||||
|
||||
// Then we multiply back out to get the total payout per tier
|
||||
const payoutPerTierValue = totalRatio ? ratioShare / totalRatio : 0;
|
||||
payoutsPerTier.push(payoutPerTierValue);
|
||||
|
||||
const numPayout = numPayouts[tier];
|
||||
|
||||
// And then divide by the payouts to get each individual winner payout
|
||||
const payoutPerWinnerValue =
|
||||
numPayout && payoutPerTierValue
|
||||
? payoutPerTierValue / numPayout
|
||||
: undefined;
|
||||
payoutPerWinnerValue && payoutsPerWinner.push(payoutPerWinnerValue);
|
||||
|
||||
payoutPerWinnerValue &&
|
||||
payoutsPerWinnerAsPercentage.push(payoutPerWinnerValue * 100);
|
||||
});
|
||||
|
||||
return {
|
||||
numPayouts,
|
||||
ratioShares,
|
||||
startRanks,
|
||||
endRanks,
|
||||
payoutsPerTier,
|
||||
payoutsPerWinner,
|
||||
payoutsPerWinnerAsPercentage,
|
||||
};
|
||||
};
|
@ -33,6 +33,7 @@ import { type EnrichedRewardTransfer } from '../../lib/hooks/use-rewards';
|
||||
import compact from 'lodash/compact';
|
||||
import BigNumber from 'bignumber.js';
|
||||
import { useTWAPQuery } from '../../lib/hooks/__generated__/Rewards';
|
||||
import { RankPayoutTable } from './rank-table';
|
||||
|
||||
const Tick = () => (
|
||||
<VegaIcon
|
||||
@ -92,6 +93,7 @@ const RewardCard = ({
|
||||
gameId?: string | null;
|
||||
}) => {
|
||||
const t = useT();
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div
|
||||
@ -135,11 +137,29 @@ const RewardCard = ({
|
||||
|
||||
{/** DISTRIBUTION STRATEGY */}
|
||||
<Tooltip
|
||||
description={t(
|
||||
DistributionStrategyDescriptionMapping[
|
||||
dispatchStrategy.distributionStrategy
|
||||
]
|
||||
)}
|
||||
description={
|
||||
<div className="flex flex-col gap-4">
|
||||
<p>
|
||||
{t(
|
||||
DistributionStrategyDescriptionMapping[
|
||||
dispatchStrategy.distributionStrategy
|
||||
]
|
||||
)}
|
||||
.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
{dispatchStrategy.rankTable &&
|
||||
t(
|
||||
'Payout percentages are base estimates assuming no individual reward multipliers are active. If users in teams have active multipliers, the reward amounts may vary.'
|
||||
)}
|
||||
</p>
|
||||
|
||||
{dispatchStrategy.rankTable && (
|
||||
<RankPayoutTable rankTable={dispatchStrategy.rankTable} />
|
||||
)}
|
||||
</div>
|
||||
}
|
||||
underline={true}
|
||||
>
|
||||
<span className="text-xs" data-testid="distribution-strategy">
|
||||
|
@ -2,6 +2,7 @@
|
||||
"(Combined set volume {{runningVolume}} over last {{epochs}} epochs)": "(Combined set volume {{runningVolume}} over last {{epochs}} epochs)",
|
||||
"(Created at: {{createdAt}})": "(Created at: {{createdAt}})",
|
||||
"(Tier {{tier}} as of last epoch)": "(Tier {{tier}} as of last epoch)",
|
||||
"*Note: Final tier will payout to all game participants, therefore all payout estimates will lower depending on how many participants there are.": "*Note: Final tier will payout to all game participants, therefore all payout estimates will lower depending on how many participants there are.",
|
||||
"24h vol": "24h vol",
|
||||
"24h volume": "24h volume",
|
||||
"A percentage of commission earned by the referrer": "A percentage of commission earned by the referrer",
|
||||
@ -108,6 +109,7 @@
|
||||
"Enactment date reached and usual auction exit checks pass": "Enactment date reached and usual auction exit checks pass",
|
||||
"End time": "End time",
|
||||
"Ends in": "Ends in",
|
||||
"End rank": "End rank",
|
||||
"Entity scope": "Entity scope",
|
||||
"Environment not configured": "Environment not configured",
|
||||
"Epoch": "Epoch",
|
||||
@ -275,6 +277,7 @@
|
||||
"Page not found": "Page not found",
|
||||
"Parent market": "Parent market",
|
||||
"Parent of a market": "Parent of a market",
|
||||
"Payout": "Payout",
|
||||
"Pennant": "Pennant",
|
||||
"Perpetuals": "Perpetuals",
|
||||
"Please choose another market from the <0>market list</0>": "Please choose another market from the <0>market list</0>",
|
||||
@ -294,6 +297,7 @@
|
||||
"Public key allow list": "Public key allow list",
|
||||
"Purpose built proof of stake blockchain": "Purpose built proof of stake blockchain",
|
||||
"Rank": "Rank",
|
||||
"Rank tier": "Rank tier",
|
||||
"Read the terms": "Read the terms",
|
||||
"Ready to trade": "Ready to trade",
|
||||
"Ready to trade with real funds? <0>Switch to Mainnet</0>": "Ready to trade with real funds? <0>Switch to Mainnet</0>",
|
||||
@ -323,6 +327,7 @@
|
||||
"Rewards history": "Rewards history",
|
||||
"Rewards multipliers": "Rewards multipliers",
|
||||
"Rewards paid out": "Rewards paid out",
|
||||
"Rewards funded using the rank strategy": "Rewards funded using the rank strategy",
|
||||
"Reward will be capped at {{capRewardFeeMultiple}} X of taker fees paid in the epoch": "Reward will be capped at {{capRewardFeeMultiple}} X of taker fees paid in the epoch",
|
||||
"SCCR": "SCCR",
|
||||
"Search": "Search",
|
||||
@ -337,12 +342,14 @@
|
||||
"Settlement price": "Settlement price",
|
||||
"Share data": "Share data",
|
||||
"Share usage data": "Share usage data",
|
||||
"Share ratio": "Share ratio",
|
||||
"Show closed markets": "Show closed markets",
|
||||
"Solo team / lone wolf": "Solo team / lone wolf",
|
||||
"Something went wrong": "Something went wrong",
|
||||
"Spot": "Spot",
|
||||
"Spot markets coming soon.": "Spot markets coming soon.",
|
||||
"Spread": "Spread",
|
||||
"Start rank": "Start rank",
|
||||
"Stake a minimum of {{minimumStakedTokens}} $VEGA tokens": "Stake a minimum of {{minimumStakedTokens}} $VEGA tokens",
|
||||
"Stake some $VEGA now": "Stake some $VEGA now",
|
||||
"Staked VEGA": "Staked VEGA",
|
||||
@ -502,6 +509,7 @@
|
||||
"{{reward}}x": "{{reward}}x",
|
||||
"Private": "Private",
|
||||
"Public": "Public",
|
||||
"Places paid": "Places paid",
|
||||
"Copy this page url.": "Copy this page url.",
|
||||
"Visit the team's page.": "Visit the team's page."
|
||||
}
|
||||
|
@ -713,7 +713,7 @@ export const IndividualScopeMapping: { [e in IndividualScope]: string } = {
|
||||
export const IndividualScopeDescriptionMapping: {
|
||||
[e in IndividualScope]: string;
|
||||
} = {
|
||||
INDIVIDUAL_SCOPE_ALL: 'All parties are eligble',
|
||||
INDIVIDUAL_SCOPE_ALL: 'All parties are eligible',
|
||||
INDIVIDUAL_SCOPE_IN_TEAM: 'Parties in teams are eligible',
|
||||
INDIVIDUAL_SCOPE_NOT_IN_TEAM: 'Only parties not in teams are eligible',
|
||||
};
|
||||
|
Loading…
Reference in New Issue
Block a user