Skip to content

Commit bb4f76a

Browse files
committed
feat: first iteration of stake simulator, abstract jurorbalance, commify jurorbalance
1 parent b722d5e commit bb4f76a

File tree

3 files changed

+139
-18
lines changed

3 files changed

+139
-18
lines changed

web/src/pages/Courts/CourtDetails/StakePanel/JurorStakeDisplay.tsx

+8-15
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,13 @@
1-
import React, { useState, useEffect, useMemo } from "react";
1+
import React, { useState, useEffect, useMemo, FC } from "react";
22
import styled, { css } from "styled-components";
33

44
import { useParams } from "react-router-dom";
55
import { formatEther } from "viem";
6-
import { useAccount } from "wagmi";
76

87
import DiceIcon from "svgs/icons/dice.svg";
98
import PNKIcon from "svgs/icons/pnk.svg";
109

11-
import { REFETCH_INTERVAL } from "consts/index";
12-
import { useReadSortitionModuleGetJurorBalance } from "hooks/contracts/generated";
13-
import { isUndefined } from "utils/index";
10+
import { commify } from "utils/commify";
1411

1512
import { useCourtDetails } from "queries/useCourtDetails";
1613

@@ -68,16 +65,12 @@ const useCalculateJurorOdds = (
6865
}, [jurorBalance, stakedByAllJurors, loading]);
6966
};
7067

71-
const JurorBalanceDisplay = () => {
68+
interface IJurorBalanceDisplay {
69+
jurorBalance: readonly [bigint, bigint, bigint, bigint] | undefined;
70+
}
71+
72+
const JurorBalanceDisplay: FC<IJurorBalanceDisplay> = ({ jurorBalance }) => {
7273
const { id } = useParams();
73-
const { address } = useAccount();
74-
const { data: jurorBalance } = useReadSortitionModuleGetJurorBalance({
75-
query: {
76-
enabled: !isUndefined(address),
77-
refetchInterval: REFETCH_INTERVAL,
78-
},
79-
args: [address ?? "0x", BigInt(id ?? 0)],
80-
});
8174
const { data: courtDetails } = useCourtDetails(id);
8275
const stakedByAllJurors = courtDetails?.court?.stake;
8376

@@ -107,7 +100,7 @@ const JurorBalanceDisplay = () => {
107100
{
108101
icon: PNKIcon,
109102
name: "My Stake",
110-
value: `${format(jurorBalance?.[2])} PNK`,
103+
value: `${commify(format(jurorBalance?.[2]))} PNK`,
111104
},
112105
{
113106
icon: DiceIcon,
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
import React, { useEffect, useState } from "react";
2+
import styled from "styled-components";
3+
4+
import { useParams } from "react-router-dom";
5+
6+
import { CoinIds } from "consts/coingecko";
7+
8+
import { formatUSD } from "utils/format";
9+
import { commify } from "utils/commify";
10+
11+
import { useHomePageExtraStats } from "queries/useHomePageExtraStats";
12+
import { useCoinPrice } from "hooks/useCoinPrice";
13+
14+
const SimulatorPopupContainer = styled.div`
15+
position: absolute;
16+
top: 100px;
17+
right: 50px;
18+
width: 400px;
19+
background-color: ${({ theme }) => theme.lightBlue};
20+
box-shadow: 0px 4px 12px rgba(0, 0, 0, 0.1);
21+
padding: 20px;
22+
border-radius: 8px;
23+
border: 1px ${({ theme }) => theme.mediumBlue};
24+
`;
25+
26+
const SimulatorItem = styled.div`
27+
display: flex;
28+
flex-direction: row;
29+
font-size: 14px;
30+
margin-bottom: 16px;
31+
color: ${({ theme }) => theme.secondaryText};
32+
gap: 4px;
33+
`;
34+
35+
function beautifyStatNumber(value: number): string {
36+
const absValue = Math.abs(value);
37+
38+
if (absValue >= 1e9) {
39+
return `${commify((value / 1e9).toFixed(2))}B`;
40+
} else if (absValue >= 1e6) {
41+
return `${commify((value / 1e6).toFixed(2))}M`;
42+
} else if (absValue >= 1e3) {
43+
return `${commify((value / 1e3).toFixed(0))}K`;
44+
} else if (absValue > 0 && absValue < 1) {
45+
return value.toFixed(2);
46+
}
47+
48+
return commify(value.toFixed(0));
49+
}
50+
51+
const calculateJurorOdds = (stakingAmount: number, totalStake: number): string => {
52+
const odds = (stakingAmount * 100) / totalStake;
53+
return `${odds.toFixed(2)}%`;
54+
};
55+
56+
const SimulatorPopup: React.FC<{ stakingAmount: number }> = ({ stakingAmount }) => {
57+
const [courtData, setCourtData] = useState<any>(null);
58+
const { id } = useParams();
59+
60+
const timeframedCourtData = useHomePageExtraStats(30);
61+
const { prices: pricesData } = useCoinPrice([CoinIds.ETH]);
62+
const ethPriceUSD = pricesData ? pricesData[CoinIds.ETH]?.price : undefined;
63+
64+
useEffect(() => {
65+
if (timeframedCourtData?.data?.courts) {
66+
const foundCourt = timeframedCourtData?.data?.courts.find((c) => c.id === id);
67+
if (foundCourt) {
68+
setCourtData(foundCourt);
69+
}
70+
}
71+
}, [timeframedCourtData, id]);
72+
73+
if (!courtData) return null;
74+
75+
const effectiveStakeAsNumber = Number(courtData.effectiveStake) / 1e18;
76+
const expectedCases = beautifyStatNumber(stakingAmount * courtData.treeDisputesPerPnk);
77+
const expectedVotes = beautifyStatNumber(stakingAmount * courtData.treeVotesPerPnk);
78+
const expectedRewardsUSD = formatUSD(courtData.treeExpectedRewardPerPnk * stakingAmount * ethPriceUSD);
79+
const jurorOdds = calculateJurorOdds(stakingAmount, effectiveStakeAsNumber + stakingAmount);
80+
81+
return (
82+
<SimulatorPopupContainer>
83+
<SimulatorItem>
84+
<strong>Cases</strong>
85+
You would have been drawn in: {expectedCases} cases
86+
</SimulatorItem>
87+
<SimulatorItem>
88+
<strong>Votes</strong>
89+
You would have: {expectedVotes} votes
90+
</SimulatorItem>
91+
<SimulatorItem>
92+
<strong>Rewards</strong>
93+
Potential earnings: {expectedRewardsUSD}
94+
</SimulatorItem>
95+
<SimulatorItem>
96+
<strong>Juror Odds</strong>
97+
Your juror odds: {jurorOdds}
98+
</SimulatorItem>
99+
</SimulatorPopupContainer>
100+
);
101+
};
102+
103+
export default SimulatorPopup;

web/src/pages/Courts/CourtDetails/StakePanel/index.tsx

+28-3
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,27 @@
11
import React, { useState } from "react";
22
import styled, { css } from "styled-components";
3+
import { landscapeStyle } from "styles/landscapeStyle";
4+
5+
import { useAccount } from "wagmi";
6+
import { formatEther } from "viem";
37

48
import BalanceIcon from "svgs/icons/balance.svg";
59
import ThreePnksIcon from "svgs/styled/three-pnks.svg";
610

711
import { useLockOverlayScroll } from "hooks/useLockOverlayScroll";
8-
9-
import { landscapeStyle } from "styles/landscapeStyle";
12+
import { useReadSortitionModuleGetJurorBalance } from "hooks/contracts/generated";
1013

1114
import Popup, { PopupType } from "components/Popup/index";
1215
import Tag from "components/Tag";
1316

17+
import { uncommify } from "utils/commify";
18+
import { isUndefined } from "utils/index";
19+
import { REFETCH_INTERVAL } from "consts/index";
20+
1421
import InputDisplay from "./InputDisplay";
1522
import JurorBalanceDisplay from "./JurorStakeDisplay";
1623
import { ActionType } from "./StakeWithdrawButton";
24+
import SimulatorPopup from "./SimulatorPopup";
1725

1826
const Container = styled.div`
1927
position: relative;
@@ -79,6 +87,14 @@ const StakePanel: React.FC<{ courtName: string; id: string }> = ({ courtName = "
7987
const [isPopupOpen, setIsPopupOpen] = useState(false);
8088
const [isActive, setIsActive] = useState<boolean>(true);
8189
const [action, setAction] = useState<ActionType>(ActionType.stake);
90+
const { address } = useAccount();
91+
const { data: jurorBalance } = useReadSortitionModuleGetJurorBalance({
92+
query: {
93+
enabled: !isUndefined(address),
94+
refetchInterval: REFETCH_INTERVAL,
95+
},
96+
args: [address ?? "0x", BigInt(id ?? 0)],
97+
});
8298

8399
useLockOverlayScroll(isPopupOpen);
84100

@@ -101,8 +117,17 @@ const StakePanel: React.FC<{ courtName: string; id: string }> = ({ courtName = "
101117
</TextArea>
102118
<StakeArea>
103119
<InputDisplay {...{ action, isSending, setIsSending, setIsPopupOpen, amount, setAmount }} />
104-
<JurorBalanceDisplay />
120+
<JurorBalanceDisplay {...{ jurorBalance }} />
105121
</StakeArea>
122+
{isStaking && Number(uncommify(amount)) > 0 ? (
123+
<SimulatorPopup
124+
stakingAmount={
125+
!isUndefined(jurorBalance)
126+
? Number(formatEther(jurorBalance?.[2])) + Number(uncommify(amount))
127+
: Number(uncommify(amount))
128+
}
129+
/>
130+
) : null}
106131
{isPopupOpen && (
107132
<Popup
108133
title={isStaking ? "Stake Confirmed" : "Withdraw Confirmed"}

0 commit comments

Comments
 (0)