|
|
@@ -3,7 +3,7 @@
|
|
|
import { Card } from "@pythnetwork/component-library/Card";
|
|
|
import { Label } from "@pythnetwork/component-library/unstyled/Label";
|
|
|
import { Input } from "@pythnetwork/component-library/unstyled/TextField";
|
|
|
-import { useState, useEffect } from "react";
|
|
|
+import { useState, useCallback } from "react";
|
|
|
|
|
|
import styles from "./index.module.scss";
|
|
|
|
|
|
@@ -22,40 +22,55 @@ const Sup = ({ children }: { children: React.ReactNode }) => (
|
|
|
);
|
|
|
|
|
|
const RewardSimulator: React.FC = () => {
|
|
|
- const [publisherStake, setPublisherStake] = useState(200);
|
|
|
- const [delegatorStake, setDelegatorStake] = useState(300);
|
|
|
- const [maxCap, setMaxCap] = useState(500);
|
|
|
- const [delegatorFee, setDelegatorFee] = useState(20);
|
|
|
- const [rewardRate, setRewardRate] = useState(10);
|
|
|
-
|
|
|
- const [publisherReward, setPublisherReward] = useState(0);
|
|
|
- const [delegatorReward, setDelegatorReward] = useState(0);
|
|
|
- const [publisherRewardRate, setPublisherRewardRate] = useState(0);
|
|
|
- const [delegatorRewardRate, setDelegatorRewardRate] = useState(0);
|
|
|
-
|
|
|
- useEffect(() => {
|
|
|
- const totalStake = publisherStake + delegatorStake;
|
|
|
- const eligibleAmount = Math.min(totalStake, maxCap);
|
|
|
- const totalReward = (rewardRate / 100) * eligibleAmount;
|
|
|
-
|
|
|
- const publisherRewardBase =
|
|
|
- (rewardRate / 100) * Math.min(publisherStake, maxCap);
|
|
|
- const delegatorRewardBase = totalReward - publisherRewardBase;
|
|
|
-
|
|
|
- const delegatorFeeAmount = (delegatorFee / 100) * delegatorRewardBase;
|
|
|
-
|
|
|
- const finalDelegatorReward = delegatorRewardBase - delegatorFeeAmount;
|
|
|
- const finalPublisherReward = publisherRewardBase + delegatorFeeAmount;
|
|
|
-
|
|
|
- setPublisherReward(Number(finalPublisherReward.toFixed(2)));
|
|
|
- setDelegatorReward(Number(finalDelegatorReward.toFixed(2)));
|
|
|
- setPublisherRewardRate(
|
|
|
- Number(((finalPublisherReward * 100) / publisherStake).toFixed(2)),
|
|
|
- );
|
|
|
- setDelegatorRewardRate(
|
|
|
- Number(((finalDelegatorReward * 100) / delegatorStake).toFixed(2)),
|
|
|
- );
|
|
|
- }, [publisherStake, delegatorStake, maxCap, delegatorFee, rewardRate]);
|
|
|
+ const [rewards, setRewards] = useState({
|
|
|
+ // These are the initial values for the reward simulator based on default values
|
|
|
+ publisher: 26,
|
|
|
+ delegator: 24,
|
|
|
+ publisherRate: 13,
|
|
|
+ delegatorRate: 8,
|
|
|
+ });
|
|
|
+
|
|
|
+ const doSubmit = useCallback((e: React.FormEvent<HTMLFormElement>) => {
|
|
|
+ e.currentTarget.requestSubmit();
|
|
|
+ }, []);
|
|
|
+
|
|
|
+ const recalculateRewards = useCallback(
|
|
|
+ (e: React.FormEvent<HTMLFormElement>) => {
|
|
|
+ e.preventDefault();
|
|
|
+ const formData = new FormData(e.currentTarget);
|
|
|
+
|
|
|
+ const publisherStake = Number(formData.get("publisherStake")) || 0;
|
|
|
+ const delegatorStake = Number(formData.get("delegatorStake")) || 0;
|
|
|
+ const maxCap = Number(formData.get("maxCap")) || 0;
|
|
|
+ const delegatorFee = Number(formData.get("delegatorFee")) || 0;
|
|
|
+ const rewardRate = Number(formData.get("rewardRate")) || 0;
|
|
|
+
|
|
|
+ const totalStake = publisherStake + delegatorStake;
|
|
|
+ const eligibleAmount = Math.min(totalStake, maxCap);
|
|
|
+ const totalReward = (rewardRate / 100) * eligibleAmount;
|
|
|
+
|
|
|
+ const publisherRewardBase =
|
|
|
+ (rewardRate / 100) * Math.min(publisherStake, maxCap);
|
|
|
+ const delegatorRewardBase = totalReward - publisherRewardBase;
|
|
|
+
|
|
|
+ const delegatorFeeAmount = (delegatorFee / 100) * delegatorRewardBase;
|
|
|
+
|
|
|
+ const finalDelegatorReward = delegatorRewardBase - delegatorFeeAmount;
|
|
|
+ const finalPublisherReward = publisherRewardBase + delegatorFeeAmount;
|
|
|
+
|
|
|
+ setRewards({
|
|
|
+ publisher: Number(finalPublisherReward.toFixed(2)),
|
|
|
+ delegator: Number(finalDelegatorReward.toFixed(2)),
|
|
|
+ publisherRate: Number(
|
|
|
+ ((finalPublisherReward * 100) / publisherStake).toFixed(2),
|
|
|
+ ),
|
|
|
+ delegatorRate: Number(
|
|
|
+ ((finalDelegatorReward * 100) / delegatorStake).toFixed(2),
|
|
|
+ ),
|
|
|
+ });
|
|
|
+ },
|
|
|
+ [],
|
|
|
+ );
|
|
|
|
|
|
return (
|
|
|
<Card
|
|
|
@@ -64,171 +79,151 @@ const RewardSimulator: React.FC = () => {
|
|
|
nonInteractive
|
|
|
className={styles.card}
|
|
|
>
|
|
|
- <div className={styles.inputGrid}>
|
|
|
- <div className={styles.inputGroup}>
|
|
|
- <Label htmlFor="publisher-stake">
|
|
|
- Publisher Stake (
|
|
|
- <MathExpression>
|
|
|
- S<Sub>p</Sub>
|
|
|
- <Sup>p</Sup>
|
|
|
- </MathExpression>
|
|
|
- ):
|
|
|
- </Label>
|
|
|
- <Input
|
|
|
- id="publisher-stake"
|
|
|
- type="number"
|
|
|
- value={publisherStake}
|
|
|
- onChange={(e) => {
|
|
|
- setPublisherStake(Number(e.target.value));
|
|
|
- }}
|
|
|
- className={styles.input ?? ""}
|
|
|
- min="0"
|
|
|
- />
|
|
|
- </div>
|
|
|
+ <form onSubmit={recalculateRewards} onChange={doSubmit}>
|
|
|
+ <div className={styles.inputGrid}>
|
|
|
+ <div className={styles.inputGroup}>
|
|
|
+ <Label htmlFor="publisher-stake">
|
|
|
+ Publisher Stake (
|
|
|
+ <MathExpression>
|
|
|
+ S<Sub>p</Sub>
|
|
|
+ <Sup>p</Sup>
|
|
|
+ </MathExpression>
|
|
|
+ ):
|
|
|
+ </Label>
|
|
|
+ <Input
|
|
|
+ id="publisher-stake"
|
|
|
+ name="publisherStake"
|
|
|
+ type="number"
|
|
|
+ defaultValue={200}
|
|
|
+ className={styles.input ?? ""}
|
|
|
+ min="0"
|
|
|
+ />
|
|
|
+ </div>
|
|
|
|
|
|
- <div className={styles.inputGroup}>
|
|
|
- <Label htmlFor="delegator-stake">
|
|
|
- Delegator Stake (
|
|
|
- <MathExpression>
|
|
|
- S<Sub>p</Sub>
|
|
|
- <Sup>d</Sup>
|
|
|
- </MathExpression>
|
|
|
- ):
|
|
|
- </Label>
|
|
|
- <Input
|
|
|
- id="delegator-stake"
|
|
|
- type="number"
|
|
|
- value={delegatorStake}
|
|
|
- onChange={(e) => {
|
|
|
- setDelegatorStake(Number(e.target.value));
|
|
|
- }}
|
|
|
- className={styles.input ?? ""}
|
|
|
- min="0"
|
|
|
- />
|
|
|
- </div>
|
|
|
+ <div className={styles.inputGroup}>
|
|
|
+ <Label htmlFor="delegator-stake">
|
|
|
+ Delegator Stake (
|
|
|
+ <MathExpression>
|
|
|
+ S<Sub>p</Sub>
|
|
|
+ <Sup>d</Sup>
|
|
|
+ </MathExpression>
|
|
|
+ ):
|
|
|
+ </Label>
|
|
|
+ <Input
|
|
|
+ id="delegator-stake"
|
|
|
+ name="delegatorStake"
|
|
|
+ type="number"
|
|
|
+ defaultValue={300}
|
|
|
+ className={styles.input ?? ""}
|
|
|
+ min="0"
|
|
|
+ />
|
|
|
+ </div>
|
|
|
|
|
|
- <div className={styles.inputGroup}>
|
|
|
- <Label htmlFor="max-cap">
|
|
|
- Maximum Cap (
|
|
|
- <MathExpression>
|
|
|
- C<Sub>p</Sub>
|
|
|
- </MathExpression>
|
|
|
- ):
|
|
|
- </Label>
|
|
|
- <Input
|
|
|
- id="max-cap"
|
|
|
- type="number"
|
|
|
- value={maxCap}
|
|
|
- onChange={(e) => {
|
|
|
- setMaxCap(Number(e.target.value));
|
|
|
- }}
|
|
|
- className={styles.input ?? ""}
|
|
|
- min="0"
|
|
|
- />
|
|
|
- </div>
|
|
|
+ <div className={styles.inputGroup}>
|
|
|
+ <Label htmlFor="max-cap">
|
|
|
+ Maximum Cap (
|
|
|
+ <MathExpression>
|
|
|
+ C<Sub>p</Sub>
|
|
|
+ </MathExpression>
|
|
|
+ ):
|
|
|
+ </Label>
|
|
|
+ <Input
|
|
|
+ id="max-cap"
|
|
|
+ name="maxCap"
|
|
|
+ type="number"
|
|
|
+ defaultValue={500}
|
|
|
+ className={styles.input ?? ""}
|
|
|
+ min="0"
|
|
|
+ />
|
|
|
+ </div>
|
|
|
|
|
|
- <div className={styles.inputGroup}>
|
|
|
- <Label htmlFor="delegator-fee">
|
|
|
- Delegator Fee (<MathExpression>f</MathExpression>) (%):
|
|
|
- </Label>
|
|
|
- <Input
|
|
|
- id="delegator-fee"
|
|
|
- type="number"
|
|
|
- value={delegatorFee}
|
|
|
- onChange={(e) => {
|
|
|
- setDelegatorFee(Number(e.target.value));
|
|
|
- }}
|
|
|
- className={styles.input ?? ""}
|
|
|
- min="0"
|
|
|
- max="100"
|
|
|
- step="0.1"
|
|
|
- />
|
|
|
- </div>
|
|
|
+ <div className={styles.inputGroup}>
|
|
|
+ <Label htmlFor="delegator-fee">
|
|
|
+ Delegator Fee (<MathExpression>f</MathExpression>) (%):
|
|
|
+ </Label>
|
|
|
+ <Input
|
|
|
+ id="delegator-fee"
|
|
|
+ name="delegatorFee"
|
|
|
+ type="number"
|
|
|
+ defaultValue={20}
|
|
|
+ className={styles.input ?? ""}
|
|
|
+ min="0"
|
|
|
+ max="100"
|
|
|
+ step="0.1"
|
|
|
+ />
|
|
|
+ </div>
|
|
|
|
|
|
- <div className={styles.inputGroup}>
|
|
|
- <Label htmlFor="reward-rate">
|
|
|
- Reward Rate (<MathExpression>r</MathExpression>) (%):
|
|
|
- </Label>
|
|
|
- <Input
|
|
|
- id="reward-rate"
|
|
|
- type="number"
|
|
|
- value={rewardRate}
|
|
|
- onChange={(e) => {
|
|
|
- setRewardRate(Number(e.target.value));
|
|
|
- }}
|
|
|
- className={styles.input ?? ""}
|
|
|
- min="0"
|
|
|
- max="100"
|
|
|
- step="0.1"
|
|
|
- />
|
|
|
+ <div className={styles.inputGroup}>
|
|
|
+ <Label htmlFor="reward-rate">
|
|
|
+ Reward Rate (<MathExpression>r</MathExpression>) (%):
|
|
|
+ </Label>
|
|
|
+ <Input
|
|
|
+ id="reward-rate"
|
|
|
+ name="rewardRate"
|
|
|
+ type="number"
|
|
|
+ defaultValue={10}
|
|
|
+ className={styles.input ?? ""}
|
|
|
+ min="0"
|
|
|
+ max="100"
|
|
|
+ step="0.1"
|
|
|
+ />
|
|
|
+ </div>
|
|
|
</div>
|
|
|
- </div>
|
|
|
-
|
|
|
- <div className={styles.resultsSection}>
|
|
|
- <div className={styles.resultsGrid}>
|
|
|
- <div className={styles.resultGroup}>
|
|
|
- <h4 className={styles.resultTitle}>Calculated Rewards</h4>
|
|
|
- <div className={styles.resultValues}>
|
|
|
- <p className={styles.resultItem}>
|
|
|
- <span className={styles.resultLabel}>
|
|
|
+
|
|
|
+ <div className={styles.resultsSection}>
|
|
|
+ <div className={styles.resultsGrid}>
|
|
|
+ <div className={styles.resultGroup}>
|
|
|
+ <h4 className={styles.resultTitle}>Calculated Rewards</h4>
|
|
|
+ <dl className={styles.resultList}>
|
|
|
+ <dt className={styles.resultTerm}>
|
|
|
Publisher Reward (
|
|
|
<MathExpression>
|
|
|
R<Sup>p</Sup>
|
|
|
<Sub>p</Sub>
|
|
|
</MathExpression>
|
|
|
):
|
|
|
- </span>{" "}
|
|
|
- <span className={styles.resultValue}>{publisherReward}</span>
|
|
|
- </p>
|
|
|
- <p className={styles.resultItem}>
|
|
|
- <span className={styles.resultLabel}>
|
|
|
+ </dt>
|
|
|
+ <dd className={styles.resultValue}>{rewards.publisher}</dd>
|
|
|
+ <dt className={styles.resultTerm}>
|
|
|
Delegator Reward (
|
|
|
<MathExpression>
|
|
|
R<Sup>d</Sup>
|
|
|
<Sub>p</Sub>
|
|
|
</MathExpression>
|
|
|
):
|
|
|
- </span>{" "}
|
|
|
- <span className={styles.resultValue}>{delegatorReward}</span>
|
|
|
- </p>
|
|
|
+ </dt>
|
|
|
+ <dd className={styles.resultValue}>{rewards.delegator}</dd>
|
|
|
+ </dl>
|
|
|
</div>
|
|
|
- </div>
|
|
|
|
|
|
- <div className={styles.resultGroup}>
|
|
|
- <h4 className={styles.resultTitle}>
|
|
|
- Calculated Reward Rates (Yearly)
|
|
|
- </h4>
|
|
|
- <div className={styles.resultValues}>
|
|
|
- <p className={styles.resultItem}>
|
|
|
- <span className={styles.resultLabel}>
|
|
|
+ <div className={styles.resultGroup}>
|
|
|
+ <h4 className={styles.resultTitle}>
|
|
|
+ Calculated Reward Rates (Yearly)
|
|
|
+ </h4>
|
|
|
+ <dl className={styles.resultList}>
|
|
|
+ <dt className={styles.resultTerm}>
|
|
|
Publisher Rate (
|
|
|
<MathExpression>
|
|
|
r<Sup>p</Sup>
|
|
|
<Sub>p</Sub>
|
|
|
</MathExpression>
|
|
|
):
|
|
|
- </span>{" "}
|
|
|
- <span className={styles.resultValue}>
|
|
|
- {publisherRewardRate}%
|
|
|
- </span>
|
|
|
- </p>
|
|
|
- <p className={styles.resultItem}>
|
|
|
- <span className={styles.resultLabel}>
|
|
|
+ </dt>
|
|
|
+ <dd className={styles.resultValue}>{rewards.publisherRate}%</dd>
|
|
|
+ <dt className={styles.resultTerm}>
|
|
|
Delegator Rate (
|
|
|
<MathExpression>
|
|
|
r<Sup>d</Sup>
|
|
|
<Sub>p</Sub>
|
|
|
</MathExpression>
|
|
|
):
|
|
|
- </span>{" "}
|
|
|
- <span className={styles.resultValue}>
|
|
|
- {delegatorRewardRate}%
|
|
|
- </span>
|
|
|
- </p>
|
|
|
+ </dt>
|
|
|
+ <dd className={styles.resultValue}>{rewards.delegatorRate}%</dd>
|
|
|
+ </dl>
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
- </div>
|
|
|
+ </form>
|
|
|
</Card>
|
|
|
);
|
|
|
};
|