Skip to content

Commit

Permalink
Merge pull request #6 from dmccartney/daniel-periodic-rewards
Browse files Browse the repository at this point in the history
feat: periodic rewards, node page redesign
  • Loading branch information
dmccartney authored May 12, 2023
2 parents 2d44944 + b6f1c32 commit 61b6c70
Show file tree
Hide file tree
Showing 29 changed files with 2,374 additions and 580 deletions.
224 changes: 224 additions & 0 deletions web/src/components/ClaimAndStakeForm.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,224 @@
import { bnSum } from "../utils";
import { useEffect, useState } from "react";
import { ethers } from "ethers";
import {
Button,
FormHelperText,
Slider,
Stack,
Tooltip,
Typography,
useTheme,
} from "@mui/material";
import CurrencyValue from "./CurrencyValue";
import useK from "../hooks/useK";
import _ from "lodash";
import useGasPrice from "../hooks/useGasPrice";
import GasInfoFooter from "./GasInfoFooter";
import useCanConnectedAccountWithdraw from "../hooks/useCanConnectedAccountWithdraw";

function ClaimButtonTooltip({ gasAmount, ethTotal, rplTotal, stakeAmountRpl }) {
const gasPrice = useGasPrice();
const estGas = gasPrice.mul(gasAmount);
return (
<Stack direction="column" spacing={3} sx={{ m: 1 }}>
<Stack direction="column" spacing={0} sx={{ m: 0 }}>
<Stack
direction="row"
spacing={2}
alignItems="baseline"
justifyContent="space-between"
>
{!!ethTotal?.gt(0) && (
<CurrencyValue
value={ethTotal.sub(estGas)}
currency="eth"
placeholder="0"
/>
)}
<CurrencyValue
value={rplTotal.sub(stakeAmountRpl)}
currency="rpl"
placeholder="0"
/>
</Stack>
<FormHelperText sx={{ m: 0 }}>
approximate receipts (after gas)
</FormHelperText>
</Stack>
<GasInfoFooter gasAmount={gasAmount} />
</Stack>
);
}

function ClaimExecutorButton({
label,
claiming,
ethTotal,
rplTotal,
stakeAmountRpl,
gasAmount,
...props
}) {
let theme = useTheme();
return (
<Button
onClick={() => claiming.writeAsync()}
disabled={claiming.isExecuting || !claiming.writeAsync}
variant="outlined"
color="primary"
sx={{
"&.Mui-disabled": {
borderColor: theme.palette.gray.main,
color: theme.palette.gray.main,
},
...(props.sx || {}),
}}
{...props}
>
{label}
</Button>
);
}

function useDistributeGasEstimate({
nodeAddress,
args,
hasProofs,
stakeAmountRpl,
}) {
let canWithdraw = useCanConnectedAccountWithdraw(nodeAddress);
let [estimateGasAmount, setEstimateGasAmount] = useState(
ethers.BigNumber.from(297000)
);
let distributor = useK.RocketMerkleDistributorMainnet.Raw();
useEffect(() => {
if (!distributor || canWithdraw || !hasProofs) {
return;
}
let cancelled = false;
distributor.estimateGas
.claimAndStake(...args, { from: nodeAddress })
.then((estimate) => !cancelled && setEstimateGasAmount(estimate))
.catch((err) => !cancelled && console.log("error estimating gas", err));
return () => (cancelled = true);
/* eslint-disable react-hooks/exhaustive-deps */
}, [
nodeAddress,
canWithdraw,
distributor,
hasProofs,
stakeAmountRpl.toString(),
]);
/* eslint-enable react-hooks/exhaustive-deps */
return estimateGasAmount;
}

export default function ClaimAndStakeForm({
sx,
spacing = 1,
nodeAddress,
rewardIndexes,
amountsEth,
amountsRpl,
merkleProofs,
buttonProps = {},
sliderProps = {},
helperProps = {},
}) {
let theme = useTheme();
let totalRpl = bnSum(amountsRpl);
let [stakeAmountRpl, setStakeAmountRpl] = useState(totalRpl);
// If we get an updated `totalRpl` later, we want to use it as the default.
// eslint-disable-next-line react-hooks/exhaustive-deps
useEffect(() => setStakeAmountRpl(totalRpl), [totalRpl.toString()]);
let canWithdraw = useCanConnectedAccountWithdraw(nodeAddress);
let hasProofs = merkleProofs.length && _.every(merkleProofs);
let args = [
nodeAddress,
rewardIndexes,
amountsRpl,
amountsEth,
merkleProofs,
stakeAmountRpl,
];
let claiming = useK.RocketMerkleDistributorMainnet.Write.claimAndStake({
args,
// don't prepare until we have the proofs
enabled: canWithdraw && hasProofs,
});
let estimateGasAmount = useDistributeGasEstimate({
nodeAddress,
args,
hasProofs,
stakeAmountRpl,
});
let gasAmount = claiming.prepareData?.request?.gasLimit || estimateGasAmount;
let rplTotal = bnSum(amountsRpl);
let ethTotal = bnSum(amountsEth);
let stakeAmountRplF = Number(ethers.utils.formatUnits(stakeAmountRpl));
let maxStakeAmountRplF = Number(ethers.utils.formatUnits(totalRpl));
return (
<Tooltip
arrow
position="bottom"
slotProps={{
tooltip: { sx: { backgroundColor: theme.palette.grey[800] } },
arrow: { sx: { color: theme.palette.grey[800] } },
}}
title={
<ClaimButtonTooltip
gasAmount={gasAmount}
ethTotal={ethTotal}
rplTotal={rplTotal}
stakeAmountRpl={stakeAmountRpl}
/>
}
>
<Stack sx={sx} direction="row" spacing={spacing} alignItems="center">
<ClaimExecutorButton
label="Claim"
claiming={claiming}
ethTotal={ethTotal}
rplTotal={rplTotal}
stakeAmountRpl={stakeAmountRpl}
gasAmount={gasAmount}
{...buttonProps}
/>
<Stack direction="column">
<Slider
value={stakeAmountRplF}
color="rpl"
valueLabelDisplay="off"
onChange={(e) =>
setStakeAmountRpl(
e.target.value >= maxStakeAmountRplF
? totalRpl
: ethers.utils.parseUnits(String(e.target.value))
)
}
min={0}
max={maxStakeAmountRplF}
{...sliderProps}
/>
<Stack
direction="row"
alignItems="baseline"
spacing={1}
{...helperProps}
>
<FormHelperText sx={{ m: 0 }}>and stake</FormHelperText>
<Typography variant="default">
<CurrencyValue
placeholder="0"
value={stakeAmountRpl}
size="xsmall"
currency="rpl"
/>
</Typography>
</Stack>
</Stack>
</Stack>
</Tooltip>
);
}
30 changes: 18 additions & 12 deletions web/src/components/ConnectedWalletButton.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,22 +29,27 @@ export default function ConnectedWalletButton({
return null;
}
return (
<Stack direction="row" alignItems="center" spacing={1}>
<Stack sx={sx} direction="row" alignItems="center" spacing={1}>
<Button size={"small"} onClick={() => disconnect()}>
Disconnect
</Button>
<WalletAvatar walletAddress={address} size={36} />
<Stack direction="column" spacing={0.25}>
<Typography variant="body2">
{ensName ?? shortenAddress(address)}
</Typography>
<Stack direction="row" alignItems="center">
{connector?.id === "safe" ? (
<SafeIcon color="text.secondary" sx={{ width: 16, height: 16 }} />
) : null}
<Typography color="text.secondary" variant="caption">
{connector?.name}
<Stack direction="row" alignItems="center" spacing={1}>
<WalletAvatar walletAddress={address} size={36} />
<Stack direction="column" spacing={0.25}>
<Typography variant="body2">
{ensName ?? shortenAddress(address)}
</Typography>
<Stack direction="row" alignItems="center">
{connector?.id === "safe" ? (
<SafeIcon
color="text.secondary"
sx={{ width: 16, height: 16 }}
/>
) : null}
<Typography color="text.secondary" variant="caption">
{connector?.name}
</Typography>
</Stack>
</Stack>
</Stack>
</Stack>
Expand All @@ -53,6 +58,7 @@ export default function ConnectedWalletButton({
return (
<>
<Button
sx={sx}
variant="contained"
onClick={() => setShowingOptions(true)}
{...props}
Expand Down
75 changes: 75 additions & 0 deletions web/src/components/CurrencyValue.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import { Stack, Typography } from "@mui/material";
import { trimValue } from "../utils";
import { ethers } from "ethers";

const typeVariantBySize = {
xsmall: {
value: "caption",
currency: "caption",
spacing: 0.65,
},
small: {
value: "body1",
currency: "caption",
spacing: 0.65,
},
medium: {
value: "h6",
currency: "subtitle2",
spacing: 0.8,
},
large: {
value: "h4",
currency: "subtitle1",
spacing: 1,
},
};
export default function CurrencyValue({
value = ethers.constants.Zero,
currency = "eth",
size = (v) => (v.gte(ethers.utils.parseUnits("1000")) ? "small" : "medium"),
placeholder = "-.---",
decimalPlaces = 3,
...props
}) {
let computedSize = size;
if (typeof size === "function") {
computedSize = size(value || ethers.constants.Zero);
}
let typeVariants =
typeVariantBySize[computedSize] || typeVariantBySize["medium"];
let valueText = placeholder;
if (value && !value.isZero()) {
valueText = trimValue(
ethers.utils.formatUnits(value.abs() || ethers.constants.Zero)
);
}
if (value && value.isNegative()) {
valueText = `(${valueText})`;
}
return (
<Stack
direction="row"
alignItems="baseline"
spacing={typeVariants.spacing}
{...props}
>
<Typography
variant={typeVariants.value}
color={(theme) => theme.palette.text.primary}
>
{valueText}
</Typography>
<Typography
component={"span"}
variant={typeVariants.currency}
color={(theme) =>
theme.palette[currency] ? theme.palette[currency].main : "default"
}
>
{" "}
{currency.toUpperCase()}
</Typography>
</Stack>
);
}
Loading

0 comments on commit 61b6c70

Please sign in to comment.