Skip to content

Commit

Permalink
feat: show warning if wallet staked another node already + fix unstak…
Browse files Browse the repository at this point in the history
…able references (#63)
  • Loading branch information
PudgyPug authored Nov 18, 2024
1 parent 853e9a0 commit 82606e3
Show file tree
Hide file tree
Showing 6 changed files with 138 additions and 134 deletions.
12 changes: 6 additions & 6 deletions components/molecules/RewardsCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import useModalStore from "../../hooks/useModalStore";
import { ConfirmRedemptionModal } from "./ConfirmRedemptionModal";
import { MobileModalWrapper } from "../layouts/MobileModalWrapper";
import { BgImage } from "../atoms/BgImage";
import {useAccountStakeInfo} from '../../hooks/useAccountStakeInfo';

function formatDate(date: Date) {
// Format date and time separately
Expand Down Expand Up @@ -37,13 +38,14 @@ export const RewardsCard = () => {
);
const { nodeStatus } = useNodeStatus();
const { address, isConnected } = useAccount();
const { stakeInfo } = useAccountStakeInfo(address);
const { chain } = useNetwork();
const [canRedeem, setCanRedeem] = useState(
isConnected &&
chain?.id === CHAIN_ID &&
nodeStatus?.state === "stopped" &&
parseFloat(nodeStatus?.lockedStake || "0") > 0 &&
nodeStatus?.unstakable?.canUnstake
nodeStatus?.stakeState?.canUnstake
);
useEffect(() => {
setCanRedeem(
Expand All @@ -63,7 +65,7 @@ export const RewardsCard = () => {
<div className="flex flex-col w-full gap-y-2">
<span className="font-semibold text-2xl flex gap-x-2">
<span>
{parseFloat(nodeStatus?.currentRewards || "0").toFixed(2)}{" "}
{parseFloat(stakeInfo?.rewards ?? nodeStatus?.currentRewards ?? "0").toFixed(2)}{" "}
SHM
</span>
{/* <span className="text-xs leading-9 bodyFg">(~0.00$)</span> */}
Expand Down Expand Up @@ -107,10 +109,8 @@ export const RewardsCard = () => {
>
<ConfirmRedemptionModal
nominator={address?.toString() || ""}
nominee={nodeStatus?.nomineeAddress || ""}
currentRewards={parseFloat(
nodeStatus?.currentRewards || "0"
)}
nominee={stakeInfo?.nominee ?? nodeStatus?.nomineeAddress ?? ""}
currentRewards={parseFloat(stakeInfo?.rewards ?? nodeStatus?.currentRewards ?? "0")}
currentStake={parseFloat(nodeStatus?.lockedStake || "0")}
></ConfirmRedemptionModal>
</MobileModalWrapper>
Expand Down
251 changes: 128 additions & 123 deletions components/molecules/StakeDisplay.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,13 +26,7 @@ export const StakeDisplay = () => {
);
const [hasNodeStopped, setHasNodeStopped] = useState(false);

const minimumStakeRequirement = useMemo(() => {
return Math.max(
parseFloat(nodeStatus?.stakeRequirement || "10") -
parseFloat(nodeStatus?.lockedStake || "0"),
0
);
}, [nodeStatus?.stakeRequirement, nodeStatus?.lockedStake]);
const minimumStakeRequirement = parseFloat(nodeStatus?.stakeRequirement || "10")

useEffect(() => {
if (nodeStatus?.state === "stopped") {
Expand All @@ -48,130 +42,141 @@ export const StakeDisplay = () => {
return `${minutes}m ${seconds}s`;
};

const stakeForConnectedAddressOrNode = parseFloat((stakeInfo?.stake?.trim() || nodeStatus?.lockedStake || "0"));
const hasStakeOnDifferentNode = (stakeInfo?.stake ?? "0.0") > "0.0" &&
nodeStatus?.nomineeAddress != null &&
stakeInfo?.nominee !== nodeStatus?.nomineeAddress;

const isRemoveButtonDisabled = !hasStakeOnDifferentNode && (
!hasNodeStopped ||
!nodeStatus?.stakeState) ||
stakeForConnectedAddressOrNode === 0;

return (
<Card>
<div className="flex flex-col text-subtleFg">
<div className={`flex flex-col gap-y-2 p-3 font-semibold text-xl`}>
<span>
{parseFloat(
nodeStatus?.lockedStake ? nodeStatus?.lockedStake : "0"
).toFixed(2)}{" "}
SHM
</span>
<div className="flex gap-x-1">
<span className="font-light text-xs">Min. requirement: </span>
<span className="text-xs">{minimumStakeRequirement} SHM</span>
</div>
</div>
<hr className="my-1 mx-3" />
<div className="flex flex-col p-3 gap-y-2">
<span className="font-semibold text-sm">Stake Address</span>
<div className="flex justify-between items-center">
<div className="flex items-center gap-x-1">
<span className="font-light text-xs" ref={addressRef}>
{address}
</span>
<>
<div className="flex flex-col text-subtleFg">
<div className={`flex flex-col gap-y-2 p-3 font-semibold text-xl`}>
<span>
{stakeForConnectedAddressOrNode.toFixed(2)}{" "}
SHM
</span>
<div className="flex gap-x-1">
<span className="font-light text-xs">Min. requirement: </span>
<span className="text-xs">{minimumStakeRequirement} SHM</span>
</div>
<button
onClick={() => {
if (addressRef.current) {
navigator.clipboard.writeText(
addressRef.current?.innerText || ""
);
}
}}
className="h-3 w-3"
>
<ClipboardIcon fillColor={"black"} />
</button>
</div>
<div className="flex flex-col gap-y-3 mt-2">
<div className="w-full">
{isConnected && chain?.id === CHAIN_ID ? (
<div className="flex justify-end gap-x-2">
<button
className={`
bg-white border border-bodyFg text-sm px-3 py-2 rounded basis-0 grow
${hasNodeStopped && parseFloat(nodeStatus?.lockedStake || "0") > 0
? nodeStatus?.unstakable?.canUnstake
? "text-primary"
: "text-yellow-500"
: "text-gray-400"
<hr className="my-1 mx-3"/>
<div className="flex flex-col p-3 gap-y-2">
<span className="font-semibold text-sm">Stake Address</span>
<div className="flex justify-between items-center">
<div className="flex items-center gap-x-1">
<span className="font-light text-xs" ref={addressRef}>
{address}
</span>
</div>
<button
onClick={() => {
if (addressRef.current) {
navigator.clipboard.writeText(
addressRef.current?.innerText || ""
);
}
}}
className="h-3 w-3"
>
<ClipboardIcon fillColor={"black"} />
</button>
</div>
<div className="flex flex-col gap-y-3 mt-2">
<div className="w-full">
{isConnected && chain?.id === CHAIN_ID ? (
<div className="flex justify-end gap-x-2">
<button
className={`
bg-white border border-bodyFg text-sm px-3 py-2 rounded basis-0 grow
${!isRemoveButtonDisabled
? nodeStatus?.stakeState?.canUnstake
? "text-primary"
: "text-yellow-500"
: "text-gray-400"
}
${isRemoveButtonDisabled ? "tooltip tooltip-bottom" : ""}
`}
data-tip={(
hasNodeStopped &&
nodeStatus?.stakeState?.canUnstake === false &&
nodeStatus?.stakeState?.remainingTime > 0)
? `Node is currently stopped and is being removed from the active validator list. Please wait for another ${formatRemainingTime(nodeStatus.stakeState.remainingTime)} before you can remove your stake.`
: "It is not recommended to unstake while validating. If absolutely necessary, use the force remove option in settings to remove stake (Not Recommended)."
}
disabled={isRemoveButtonDisabled}
onClick={() => {
resetModal();
setContent(
<MobileModalWrapper
closeButtonRequired={false}
contentOnTop={false}
wrapperClassName="fixed bottom-0 flex flex-col items-center justify-start p-3 rounded-t-2xl min-h-2/3 overflow-scroll bg-white w-screen dropdown-300 text-black"
>
<ConfirmUnstakeModal
nominator={address?.toString() || ""}
nominee={stakeInfo?.nominee || ""}
isNormalUnstake={true}
currentRewards={parseFloat(stakeInfo?.rewards ?? "0")}
currentStake={stakeForConnectedAddressOrNode}
></ConfirmUnstakeModal>
</MobileModalWrapper>
);
setShowModal(true);
}}
>
Remove Stake
</button>
<button
className={
"px-3 py-2 rounded text-sm basis-0 grow max-w-[12rem] " +
(hasNodeStopped || !nodeStatus?.nomineeAddress
? "text-gray-400 border border-bodyFg"
: "bg-primary text-white")
}
${!nodeStatus?.unstakable?.canUnstake && parseFloat(nodeStatus?.lockedStake || "0") > 0 ? "tooltip tooltip-bottom" : ""}
`}
data-tip={(
hasNodeStopped &&
nodeStatus?.unstakable?.canUnstake === false &&
nodeStatus?.unstakable?.remainingTime > 0)
? `Node is currently stopped and is being removed from the active validator list. Please wait for another ${formatRemainingTime(nodeStatus.unstakable.remainingTime)} before you can remove your stake.`
: "It is not recommended to unstake while validating. If absolutely necessary, use the force remove option in settings to remove stake (Not Recommended)."
}
disabled={
!hasNodeStopped ||
parseFloat(nodeStatus?.lockedStake || "0") === 0 ||
!nodeStatus?.unstakable
}
onClick={() => {
resetModal();
setContent(
<MobileModalWrapper
closeButtonRequired={false}
contentOnTop={false}
wrapperClassName="fixed bottom-0 flex flex-col items-center justify-start p-3 rounded-t-2xl min-h-2/3 overflow-scroll bg-white w-screen dropdown-300 text-black"
>
<ConfirmUnstakeModal
nominator={address?.toString() || ""}
nominee={stakeInfo?.nominee || ""}
isNormalUnstake={hasNodeStopped}
currentRewards={parseFloat(
nodeStatus?.currentRewards || "0"
)}
currentStake={parseFloat(
nodeStatus?.lockedStake || "0"
)}
></ConfirmUnstakeModal>
</MobileModalWrapper>
);
setShowModal(true);
}}
>
Remove Stake
</button>
<button
className={
"px-3 py-2 rounded text-sm basis-0 grow max-w-[12rem] " +
(hasNodeStopped || !nodeStatus?.nomineeAddress
? "text-gray-400 border border-bodyFg"
: "bg-primary text-white")
}
disabled={hasNodeStopped || !nodeStatus?.nomineeAddress}
onClick={() => {
resetModal();
setContent(
<MobileModalWrapper
closeButtonRequired={false}
contentOnTop={false}
>
<AddStakeModal />
</MobileModalWrapper>
);
setShowModal(true);
}}
>
Add Stake
</button>
</div>
) : (
<WalletConnectButton
toShowAddress={false}
label="Connect Wallet"
></WalletConnectButton>
)}
disabled={hasNodeStopped || !nodeStatus?.nomineeAddress}
onClick={() => {
resetModal();
setContent(
<MobileModalWrapper
closeButtonRequired={false}
contentOnTop={false}
>
<AddStakeModal/>
</MobileModalWrapper>
);
setShowModal(true);
}}
>
Add Stake
</button>
</div>
) : (
<WalletConnectButton
toShowAddress={false}
label="Connect Wallet"
></WalletConnectButton>
)}
</div>
</div>
</div>
</div>
</div>
{(hasStakeOnDifferentNode &&
<div className={`flex gap-x-2 items-center px-4 py-2 bg-dangerBg border-gray-200 border-t`}>
<span className="bodyFg font-light text-xs ">
This wallet already has an active stake on a different
node. Remove your stake first if you wish to stake for
the current node.
</span>
</div>
)}
</>
</Card>
);
};
2 changes: 1 addition & 1 deletion components/organisms/SettingsDisplay.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ export const SettingsDisplay = () => {
}
nominator={address?.toString() || ""}
nominee={stakeInfo?.nominee || ""}
currentRewards={parseFloat(nodeStatus?.currentRewards || "0")}
currentRewards={parseFloat(stakeInfo?.rewards ?? nodeStatus?.currentRewards ?? "0")}
currentStake={parseFloat(stakeInfo?.stake || "0")}
/>
)}
Expand Down
3 changes: 2 additions & 1 deletion model/account-stake-info.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
export interface AccountStakeInfo {
stake: string,
nominee: string
nominee: string,
rewards: string
}
2 changes: 1 addition & 1 deletion model/node-status.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ export interface NodeStatus {
totalTimeValidating: number;
lastActive: string;
lockedStake: string;
unstakable: {
stakeState: {
canUnstake: boolean;
reason: string,
remainingTime: number,
Expand Down
2 changes: 0 additions & 2 deletions pages/dashboard/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@ import useNotificationsStore from "../../hooks/useNotificationsStore";
import { ToastWindow } from "../../components/molecules/ToastWindow";
import { useDevice } from "../../context/device";
import { NodeStatusRibbon } from "../../components/molecules/NodeStatusRibbon";
import { useAccount } from "wagmi";
import { MobileModalWrapper } from "../../components/layouts/MobileModalWrapper";
import useModalStore from "../../hooks/useModalStore";
import { MobileMenu } from "../../components/molecules/MobileMenu";
Expand Down Expand Up @@ -53,7 +52,6 @@ const Dashboard = () => {
setContentPane(Content.LOGS);
};
const { isMobile } = useDevice();
const { isConnected } = useAccount();
const { setShowModal, setContent } = useModalStore((state: any) => ({
setShowModal: state.setShowModal,
setContent: state.setContent,
Expand Down

0 comments on commit 82606e3

Please sign in to comment.