Skip to content

Commit d388137

Browse files
committed
reward calculator
1 parent 8bf84db commit d388137

File tree

3 files changed

+324
-0
lines changed

3 files changed

+324
-0
lines changed

apps/developer-hub/content/docs/oracle-integrity-staking/reward-examples.mdx

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -203,3 +203,11 @@ C_{p_{option2}} &= 100 + 100 \cdot \sum_{s \in \text{\{$s_{6}$,.., $s_{10}$\}}}
203203
&= 100 + 50 = 150
204204
\end{aligned}
205205
$$
206+
207+
## Reward Calculator
208+
209+
Use the calculator below to calculate publisher and delegator rewards based on your inputs.
210+
211+
import RewardSimulator from "../../../src/components/RewardSimulator";
212+
213+
<RewardSimulator />
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
@use "@pythnetwork/component-library/theme";
2+
3+
.card {
4+
margin-top: theme.spacing(6);
5+
}
6+
7+
.inputGrid {
8+
display: grid;
9+
grid-template-columns: 1fr;
10+
gap: theme.spacing(6);
11+
padding: theme.spacing(4);
12+
13+
@media (width >= 640px) {
14+
grid-template-columns: 1fr 1fr;
15+
}
16+
}
17+
18+
.inputGroup {
19+
display: flex;
20+
flex-direction: column;
21+
gap: theme.spacing(2);
22+
}
23+
24+
.input {
25+
padding: theme.spacing(2) theme.spacing(3);
26+
border: 1px solid var(--color-fd-border);
27+
border-radius: theme.border-radius("lg");
28+
background-color: var(--color-fd-background);
29+
color: var(--color-fd-foreground);
30+
transition: all 0.15s ease-in-out;
31+
32+
&:focus {
33+
outline: 2px solid var(--color-fd-accent);
34+
outline-offset: -2px;
35+
border-color: transparent;
36+
}
37+
}
38+
39+
.resultsSection {
40+
border-top: 1px solid var(--color-fd-border);
41+
margin-top: theme.spacing(6);
42+
padding-top: theme.spacing(6);
43+
}
44+
45+
.resultsGrid {
46+
display: grid;
47+
grid-template-columns: 1fr;
48+
gap: theme.spacing(6);
49+
50+
@media (width >= 640px) {
51+
grid-template-columns: 1fr 1fr;
52+
}
53+
}
54+
55+
.resultGroup {
56+
display: flex;
57+
flex-direction: column;
58+
gap: theme.spacing(3);
59+
}
60+
61+
.resultTitle {
62+
font-size: theme.font-size("lg");
63+
font-weight: theme.font-weight("semibold");
64+
color: var(--color-fd-foreground);
65+
}
66+
67+
.resultValues {
68+
display: flex;
69+
flex-direction: column;
70+
gap: theme.spacing(2);
71+
}
72+
73+
.resultItem {
74+
font-size: theme.font-size("sm");
75+
color: var(--color-fd-muted-foreground);
76+
}
77+
78+
.resultLabel {
79+
font-weight: theme.font-weight("medium");
80+
}
81+
82+
.resultValue {
83+
font-family: ui-monospace, SFMono-Regular, Consolas, "Liberation Mono", Menlo, monospace;
84+
}
Lines changed: 232 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,232 @@
1+
"use client";
2+
3+
import { Card } from "@pythnetwork/component-library/Card";
4+
import { Label } from "@pythnetwork/component-library/unstyled/Label";
5+
import { Input } from "@pythnetwork/component-library/unstyled/TextField";
6+
import clsx from "clsx";
7+
import React, { useState, useEffect } from "react";
8+
9+
import styles from "./index.module.scss";
10+
11+
// Simple Math component for inline mathematical expressions
12+
const MathExpression: React.FC<{ children: React.ReactNode }> = ({ children }) => {
13+
return <span className="katex">{children}</span>;
14+
};
15+
16+
// Component for subscripts and superscripts
17+
const Sub: React.FC<{ children: React.ReactNode }> = ({ children }) => (
18+
<sub>{children}</sub>
19+
);
20+
const Sup: React.FC<{ children: React.ReactNode }> = ({ children }) => (
21+
<sup>{children}</sup>
22+
);
23+
24+
const RewardSimulator: React.FC = () => {
25+
const [publisherStake, setPublisherStake] = useState(200);
26+
const [delegatorStake, setDelegatorStake] = useState(300);
27+
const [maxCap, setMaxCap] = useState(500);
28+
const [delegatorFee, setDelegatorFee] = useState(20);
29+
const [rewardRate, setRewardRate] = useState(10);
30+
31+
const [publisherReward, setPublisherReward] = useState(0);
32+
const [delegatorReward, setDelegatorReward] = useState(0);
33+
const [publisherRewardRate, setPublisherRewardRate] = useState(0);
34+
const [delegatorRewardRate, setDelegatorRewardRate] = useState(0);
35+
36+
useEffect(() => {
37+
const calculateRewards = () => {
38+
const totalStake = publisherStake + delegatorStake;
39+
const eligibleAmount = Math.min(totalStake, maxCap);
40+
const totalReward = (rewardRate / 100) * eligibleAmount;
41+
42+
const publisherRewardBase =
43+
(rewardRate / 100) * Math.min(publisherStake, maxCap);
44+
const delegatorRewardBase = totalReward - publisherRewardBase;
45+
46+
const delegatorFeeAmount = (delegatorFee / 100) * delegatorRewardBase;
47+
48+
const finalDelegatorReward = delegatorRewardBase - delegatorFeeAmount;
49+
const finalPublisherReward = publisherRewardBase + delegatorFeeAmount;
50+
51+
setPublisherReward(Number(finalPublisherReward.toFixed(2)));
52+
setDelegatorReward(Number(finalDelegatorReward.toFixed(2)));
53+
setPublisherRewardRate(
54+
Number(((finalPublisherReward * 100) / publisherStake).toFixed(2)),
55+
);
56+
setDelegatorRewardRate(
57+
Number(((finalDelegatorReward * 100) / delegatorStake).toFixed(2)),
58+
);
59+
};
60+
61+
calculateRewards();
62+
}, [publisherStake, delegatorStake, maxCap, delegatorFee, rewardRate]);
63+
64+
return (
65+
<Card
66+
variant="secondary"
67+
title="Reward Simulator"
68+
nonInteractive
69+
className={clsx(styles.card)}
70+
>
71+
<div className={clsx(styles.inputGrid)}>
72+
<div className={clsx(styles.inputGroup)}>
73+
<Label htmlFor="publisher-stake">
74+
Publisher Stake (
75+
<MathExpression>
76+
S<Sub>p</Sub><Sup>p</Sup>
77+
</MathExpression>
78+
):
79+
</Label>
80+
<Input
81+
id="publisher-stake"
82+
type="number"
83+
value={publisherStake}
84+
onChange={(e) => {
85+
setPublisherStake(Number(e.target.value));
86+
}}
87+
className={clsx(styles.input)}
88+
min="0"
89+
/>
90+
</div>
91+
<div className={clsx(styles.inputGroup)}>
92+
<Label htmlFor="delegator-stake">
93+
Delegator Stake (
94+
<MathExpression>
95+
S<Sub>p</Sub><Sup>d</Sup>
96+
</MathExpression>
97+
):
98+
</Label>
99+
<Input
100+
id="delegator-stake"
101+
type="number"
102+
value={delegatorStake}
103+
onChange={(e) => {
104+
setDelegatorStake(Number(e.target.value));
105+
}}
106+
className={clsx(styles.input)}
107+
min="0"
108+
/>
109+
</div>
110+
<div className={clsx(styles.inputGroup)}>
111+
<Label htmlFor="max-cap">
112+
Maximum Cap (
113+
<MathExpression>
114+
C<Sub>p</Sub>
115+
</MathExpression>
116+
):
117+
</Label>
118+
<Input
119+
id="max-cap"
120+
type="number"
121+
value={maxCap}
122+
onChange={(e) => {
123+
setMaxCap(Number(e.target.value));
124+
}}
125+
className={clsx(styles.input)}
126+
min="0"
127+
/>
128+
</div>
129+
<div className={clsx(styles.inputGroup)}>
130+
<Label htmlFor="delegator-fee">
131+
Delegator Fee (<MathExpression>f</MathExpression>) (%):
132+
</Label>
133+
<Input
134+
id="delegator-fee"
135+
type="number"
136+
value={delegatorFee}
137+
onChange={(e) => {
138+
setDelegatorFee(Number(e.target.value));
139+
}}
140+
className={clsx(styles.input)}
141+
min="0"
142+
max="100"
143+
step="0.1"
144+
/>
145+
</div>
146+
<div className={clsx(styles.inputGroup)}>
147+
<Label htmlFor="reward-rate">
148+
Reward Rate (<MathExpression>r</MathExpression>) (%):
149+
</Label>
150+
<Input
151+
id="reward-rate"
152+
type="number"
153+
value={rewardRate}
154+
onChange={(e) => {
155+
setRewardRate(Number(e.target.value));
156+
}}
157+
className={clsx(styles.input)}
158+
min="0"
159+
max="100"
160+
step="0.1"
161+
/>
162+
</div>
163+
</div>
164+
<div className={clsx(styles.resultsSection)}>
165+
<div className={clsx(styles.resultsGrid)}>
166+
<div className={clsx(styles.resultGroup)}>
167+
<h4 className={clsx(styles.resultTitle)}>Calculated Rewards</h4>
168+
<div className={clsx(styles.resultValues)}>
169+
<p className={clsx(styles.resultItem)}>
170+
<span className={clsx(styles.resultLabel)}>
171+
Publisher Reward (
172+
<MathExpression>
173+
R<Sup>p</Sup><Sub>p</Sub>
174+
</MathExpression>
175+
):
176+
</span>{" "}
177+
<span className={clsx(styles.resultValue)}>
178+
{publisherReward}
179+
</span>
180+
</p>
181+
<p className={clsx(styles.resultItem)}>
182+
<span className={clsx(styles.resultLabel)}>
183+
Delegator Reward (
184+
<MathExpression>
185+
R<Sup>d</Sup><Sub>p</Sub>
186+
</MathExpression>
187+
):
188+
</span>{" "}
189+
<span className={clsx(styles.resultValue)}>
190+
{delegatorReward}
191+
</span>
192+
</p>
193+
</div>
194+
</div>
195+
<div className={clsx(styles.resultGroup)}>
196+
<h4 className={clsx(styles.resultTitle)}>
197+
Calculated Reward Rates (Yearly)
198+
</h4>
199+
<div className={clsx(styles.resultValues)}>
200+
<p className={clsx(styles.resultItem)}>
201+
<span className={clsx(styles.resultLabel)}>
202+
Publisher Rate (
203+
<MathExpression>
204+
r<Sup>p</Sup><Sub>p</Sub>
205+
</MathExpression>
206+
):
207+
</span>{" "}
208+
<span className={clsx(styles.resultValue)}>
209+
{publisherRewardRate}%
210+
</span>
211+
</p>
212+
<p className={clsx(styles.resultItem)}>
213+
<span className={clsx(styles.resultLabel)}>
214+
Delegator Rate (
215+
<MathExpression>
216+
r<Sup>d</Sup><Sub>p</Sub>
217+
</MathExpression>
218+
):
219+
</span>{" "}
220+
<span className={clsx(styles.resultValue)}>
221+
{delegatorRewardRate}%
222+
</span>
223+
</p>
224+
</div>
225+
</div>
226+
</div>
227+
</div>
228+
</Card>
229+
);
230+
};
231+
232+
export default RewardSimulator;

0 commit comments

Comments
 (0)