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 compact from 'lodash/compact';
|
||||||
import BigNumber from 'bignumber.js';
|
import BigNumber from 'bignumber.js';
|
||||||
import { useTWAPQuery } from '../../lib/hooks/__generated__/Rewards';
|
import { useTWAPQuery } from '../../lib/hooks/__generated__/Rewards';
|
||||||
|
import { RankPayoutTable } from './rank-table';
|
||||||
|
|
||||||
const Tick = () => (
|
const Tick = () => (
|
||||||
<VegaIcon
|
<VegaIcon
|
||||||
@ -92,6 +93,7 @@ const RewardCard = ({
|
|||||||
gameId?: string | null;
|
gameId?: string | null;
|
||||||
}) => {
|
}) => {
|
||||||
const t = useT();
|
const t = useT();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<div
|
<div
|
||||||
@ -135,11 +137,29 @@ const RewardCard = ({
|
|||||||
|
|
||||||
{/** DISTRIBUTION STRATEGY */}
|
{/** DISTRIBUTION STRATEGY */}
|
||||||
<Tooltip
|
<Tooltip
|
||||||
description={t(
|
description={
|
||||||
DistributionStrategyDescriptionMapping[
|
<div className="flex flex-col gap-4">
|
||||||
dispatchStrategy.distributionStrategy
|
<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}
|
underline={true}
|
||||||
>
|
>
|
||||||
<span className="text-xs" data-testid="distribution-strategy">
|
<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)",
|
"(Combined set volume {{runningVolume}} over last {{epochs}} epochs)": "(Combined set volume {{runningVolume}} over last {{epochs}} epochs)",
|
||||||
"(Created at: {{createdAt}})": "(Created at: {{createdAt}})",
|
"(Created at: {{createdAt}})": "(Created at: {{createdAt}})",
|
||||||
"(Tier {{tier}} as of last epoch)": "(Tier {{tier}} as of last epoch)",
|
"(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 vol": "24h vol",
|
||||||
"24h volume": "24h volume",
|
"24h volume": "24h volume",
|
||||||
"A percentage of commission earned by the referrer": "A percentage of commission earned by the referrer",
|
"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",
|
"Enactment date reached and usual auction exit checks pass": "Enactment date reached and usual auction exit checks pass",
|
||||||
"End time": "End time",
|
"End time": "End time",
|
||||||
"Ends in": "Ends in",
|
"Ends in": "Ends in",
|
||||||
|
"End rank": "End rank",
|
||||||
"Entity scope": "Entity scope",
|
"Entity scope": "Entity scope",
|
||||||
"Environment not configured": "Environment not configured",
|
"Environment not configured": "Environment not configured",
|
||||||
"Epoch": "Epoch",
|
"Epoch": "Epoch",
|
||||||
@ -275,6 +277,7 @@
|
|||||||
"Page not found": "Page not found",
|
"Page not found": "Page not found",
|
||||||
"Parent market": "Parent market",
|
"Parent market": "Parent market",
|
||||||
"Parent of a market": "Parent of a market",
|
"Parent of a market": "Parent of a market",
|
||||||
|
"Payout": "Payout",
|
||||||
"Pennant": "Pennant",
|
"Pennant": "Pennant",
|
||||||
"Perpetuals": "Perpetuals",
|
"Perpetuals": "Perpetuals",
|
||||||
"Please choose another market from the <0>market list</0>": "Please choose another market from the <0>market list</0>",
|
"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",
|
"Public key allow list": "Public key allow list",
|
||||||
"Purpose built proof of stake blockchain": "Purpose built proof of stake blockchain",
|
"Purpose built proof of stake blockchain": "Purpose built proof of stake blockchain",
|
||||||
"Rank": "Rank",
|
"Rank": "Rank",
|
||||||
|
"Rank tier": "Rank tier",
|
||||||
"Read the terms": "Read the terms",
|
"Read the terms": "Read the terms",
|
||||||
"Ready to trade": "Ready to trade",
|
"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>",
|
"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 history": "Rewards history",
|
||||||
"Rewards multipliers": "Rewards multipliers",
|
"Rewards multipliers": "Rewards multipliers",
|
||||||
"Rewards paid out": "Rewards paid out",
|
"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",
|
"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",
|
"SCCR": "SCCR",
|
||||||
"Search": "Search",
|
"Search": "Search",
|
||||||
@ -337,12 +342,14 @@
|
|||||||
"Settlement price": "Settlement price",
|
"Settlement price": "Settlement price",
|
||||||
"Share data": "Share data",
|
"Share data": "Share data",
|
||||||
"Share usage data": "Share usage data",
|
"Share usage data": "Share usage data",
|
||||||
|
"Share ratio": "Share ratio",
|
||||||
"Show closed markets": "Show closed markets",
|
"Show closed markets": "Show closed markets",
|
||||||
"Solo team / lone wolf": "Solo team / lone wolf",
|
"Solo team / lone wolf": "Solo team / lone wolf",
|
||||||
"Something went wrong": "Something went wrong",
|
"Something went wrong": "Something went wrong",
|
||||||
"Spot": "Spot",
|
"Spot": "Spot",
|
||||||
"Spot markets coming soon.": "Spot markets coming soon.",
|
"Spot markets coming soon.": "Spot markets coming soon.",
|
||||||
"Spread": "Spread",
|
"Spread": "Spread",
|
||||||
|
"Start rank": "Start rank",
|
||||||
"Stake a minimum of {{minimumStakedTokens}} $VEGA tokens": "Stake a minimum of {{minimumStakedTokens}} $VEGA tokens",
|
"Stake a minimum of {{minimumStakedTokens}} $VEGA tokens": "Stake a minimum of {{minimumStakedTokens}} $VEGA tokens",
|
||||||
"Stake some $VEGA now": "Stake some $VEGA now",
|
"Stake some $VEGA now": "Stake some $VEGA now",
|
||||||
"Staked VEGA": "Staked VEGA",
|
"Staked VEGA": "Staked VEGA",
|
||||||
@ -502,6 +509,7 @@
|
|||||||
"{{reward}}x": "{{reward}}x",
|
"{{reward}}x": "{{reward}}x",
|
||||||
"Private": "Private",
|
"Private": "Private",
|
||||||
"Public": "Public",
|
"Public": "Public",
|
||||||
|
"Places paid": "Places paid",
|
||||||
"Copy this page url.": "Copy this page url.",
|
"Copy this page url.": "Copy this page url.",
|
||||||
"Visit the team's page.": "Visit the team's page."
|
"Visit the team's page.": "Visit the team's page."
|
||||||
}
|
}
|
||||||
|
@ -713,7 +713,7 @@ export const IndividualScopeMapping: { [e in IndividualScope]: string } = {
|
|||||||
export const IndividualScopeDescriptionMapping: {
|
export const IndividualScopeDescriptionMapping: {
|
||||||
[e in IndividualScope]: string;
|
[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_IN_TEAM: 'Parties in teams are eligible',
|
||||||
INDIVIDUAL_SCOPE_NOT_IN_TEAM: 'Only parties not in teams are eligible',
|
INDIVIDUAL_SCOPE_NOT_IN_TEAM: 'Only parties not in teams are eligible',
|
||||||
};
|
};
|
||||||
|
Loading…
Reference in New Issue
Block a user