From 7b972ef588b99ba998ce7ff564917631645cd505 Mon Sep 17 00:00:00 2001 From: rtj99 Date: Tue, 10 Sep 2024 02:16:36 +0200 Subject: [PATCH 01/17] feat: add staking page --- src/app/stake/page.js | 216 ++++++++++++++++ src/components/BookmarkAdded.js | 10 +- src/components/Header.js | 6 +- src/components/staking/UserAssets.js | 354 ++++++++++++++++++++++++++ src/components/wallet/AssetsValues.js | 4 +- 5 files changed, 580 insertions(+), 10 deletions(-) create mode 100644 src/app/stake/page.js create mode 100644 src/components/staking/UserAssets.js diff --git a/src/app/stake/page.js b/src/app/stake/page.js new file mode 100644 index 0000000..8071690 --- /dev/null +++ b/src/app/stake/page.js @@ -0,0 +1,216 @@ +"use client"; +import {Image} from "@chakra-ui/react"; +import React, {useState} from "react"; +import Lottie from "react-lottie-player"; +import lottieJson from "@/assets/animations/PE2.json"; +import XeonStakingPoolABI from "@/abi/XeonStakingPool.abi.json"; +import {Constants} from "@/abi/constants"; +import Header from "@/components/Header"; +import UserAssets from "@/components/staking/UserAssets"; +import {ethers} from "ethers"; +import { + Modal, + ModalOverlay, + ModalContent, + ModalHeader, + ModalBody, + ModalFooter, + Spinner, + useDisclosure, +} from "@chakra-ui/react"; +import BookmarkAdded from "@/components/BookmarkAdded"; +function Page() { + const [voteValue, setVoteValue] = useState(5); + const [loading, setLoading] = useState(false); + const [message, setMessage] = useState(""); + + const {isOpen, onOpen, onClose} = useDisclosure(); + const provider = new ethers.providers.Web3Provider(window.ethereum); + const signer = provider.getSigner(); + const XeonStakingPool = new ethers.Contract( + Constants.testnet.XeonStakingPool, + XeonStakingPoolABI, + signer + ); + + const handleIncrement = () => { + setVoteValue((prevValue) => Math.min(prevValue + 1, 100)); + }; + + const handleDecrement = () => { + setVoteValue((prevValue) => Math.max(prevValue - 1, 1)); + }; + + const handleVote = async () => { + if (voteValue < 1 || voteValue > 100) { + setMessage("Please enter a value between 1 and 100"); + return; + } + + setLoading(true); + onOpen(); + + try { + const tx = await XeonStakingPool.voteForBuybackPercentage(voteValue); + await tx.wait(); + setLoading(false); + setMessage(`Vote successful for ${voteValue}% buyback`); + } catch (error) { + console.error("Vote failed", error); + setLoading(false); + setMessage("Vote failed, please try again."); + } + }; + + const handleVoteChange = (e) => { + const value = parseInt(e.target.value); + if (Number.isNaN(value)) { + setVoteValue(1); + } else { + setVoteValue(Math.min(Math.max(value, 1), 100)); + } + }; + + return ( +
+
+
+
+

Stake

+

+ Xeon +

+ + container + +
+
+

+ Stake your XEON tokens in just two simple steps. +

+
+

+ Stake XEON tokens to be eligible for rewards sharing. The staking + window opens 3 days only each month-end, during which users can + stake or unstake XEON. Revenue is deposited to staking contract + for claiming. +

+
+ container +
+
+
+ +
+
+
+
+

+ Settle +

+

+ Close expired positions and collect fess into the staking pool +

+
+ +
+
+
+
+
+

+ $XEON Buyback +

+

+ What percentage of protocol revenue should be used to buyback + $XEON token? +

+
+ + + + + + +
+ +

Current 5.00%

+
+
+
+ + + + + Vote Feedback + + + {loading ? ( + + ) : ( + + )} + + + + + + +
+ ); +} + +export default Page; diff --git a/src/components/BookmarkAdded.js b/src/components/BookmarkAdded.js index 2e12a64..7f2e9e8 100644 --- a/src/components/BookmarkAdded.js +++ b/src/components/BookmarkAdded.js @@ -1,14 +1,14 @@ -import { Image } from '@chakra-ui/react'; +import {Image} from "@chakra-ui/react"; -function BookmarkAdded({ message, status }) { +function BookmarkAdded({message, status}) { return (

{status}

failed popup diff --git a/src/components/Header.js b/src/components/Header.js index 1dbce5b..4582a79 100644 --- a/src/components/Header.js +++ b/src/components/Header.js @@ -77,7 +77,7 @@ function Header() {
Analytics
- Guide + Stake Claim - -

Guide

+ +

Stake

Claim

diff --git a/src/components/staking/UserAssets.js b/src/components/staking/UserAssets.js new file mode 100644 index 0000000..82c0278 --- /dev/null +++ b/src/components/staking/UserAssets.js @@ -0,0 +1,354 @@ +"use client"; +import { + FormControl, + FormLabel, + Switch, + Spinner, + Modal, + ModalOverlay, + ModalContent, + ModalHeader, + ModalBody, + ModalCloseButton, + ModalFooter, + useDisclosure, +} from "@chakra-ui/react"; +import {motion} from "framer-motion"; +import {useState, useEffect} from "react"; +import {FaEthereum} from "react-icons/fa"; +import AssetsValues from "../wallet/AssetsValues"; +import {useActiveAccount} from "thirdweb/react"; +import {ethers} from "ethers"; +import XeonStakingPoolABI from "@/abi/XeonStakingPool.abi.json"; +import {Constants} from "@/abi/constants"; +import BookmarkAdded from "../BookmarkAdded"; + +function UserAssets() { + const [isSwitched, setIsSwitched] = useState(false); + const [walletBalance, setWalletBalance] = useState(0); + const [stakedBalance, setStakedBalance] = useState(0); + const [stakeAmount, setStakeAmount] = useState(""); + const [isApproved, setIsApproved] = useState(false); + const [buttonText, setButtonText] = useState("APPROVE"); + const [loading, setLoading] = useState(false); + const [message, setMessage] = useState(""); + const [status, setStatus] = useState(""); + const [epoch, setEpoch] = useState("0.00"); + const [ethInPool, setEthInPool] = useState("0.00"); + const [buyBackPercentage, setBuyBackPercentage] = useState("0.00"); + const [teamPercentage, setTeamPercentage] = useState("0.00"); + const [walletXeonBalance, setWalletXeonBalance] = useState("0.00"); + const [stakedXeonBalance, setStakedXeonBalance] = useState("0.00"); + const {isOpen, onOpen, onClose} = useDisclosure(); + const wallet = useActiveAccount(); + const connectedAddress = wallet?.address; + const provider = new ethers.providers.Web3Provider(window.ethereum); + const signer = provider.getSigner(); + const XeonToken = new ethers.Contract( + Constants.testnet.XeonToken, + XeonStakingPoolABI, + signer + ); + const XeonStakingPool = new ethers.Contract( + Constants.testnet.XeonStakingPool, + XeonStakingPoolABI, + signer + ); + const WETH = new ethers.Contract( + "0x949B2156916A63686835DaF66518C22D497bf8B0", + XeonStakingPoolABI, + provider + ); + useEffect(() => { + const fetchData = async () => { + try { + const epoch = await XeonStakingPool.epoch(); + setEpoch(ethers.utils.formatUnits(epoch, 0)); + + const ethBalance = await WETH.balanceOf( + Constants.testnet.XeonStakingPool + ); + setEthInPool(ethers.utils.formatEther(ethBalance)); + + const buyBackPercentage = await XeonStakingPool.buyBackPercentage(); + setBuyBackPercentage(ethers.utils.formatUnits(buyBackPercentage, 0)); + + const teamPercentage = await XeonStakingPool.teamPercentage(); + setTeamPercentage(ethers.utils.formatUnits(teamPercentage, 0)); + + const xeonBalance = await XeonToken.balanceOf(connectedAddress); + setWalletXeonBalance(ethers.utils.formatEther(xeonBalance)); + + const stakedXeonBalance = await XeonStakingPool.balanceOf( + connectedAddress + ); + setStakedXeonBalance(ethers.utils.formatEther(stakedXeonBalance)); + } catch (error) { + console.error("Error fetching asset values:", error); + } + }; + + if (connectedAddress) { + fetchData(); + } + }, [connectedAddress, XeonStakingPool, XeonToken, WETH]); + useEffect(() => { + if (wallet) { + XeonToken.balanceOf(wallet.address).then((balance) => { + setWalletBalance(ethers.utils.formatEther(balance)); + }); + + XeonStakingPool.stakedAmounts(wallet.address).then((balance) => { + setStakedBalance(ethers.utils.formatEther(balance)); + }); + + XeonToken.allowance(wallet.address, XeonStakingPool.address).then( + (allowance) => { + if (ethers.utils.formatEther(allowance) > 0) { + setIsApproved(true); + setButtonText("STAKE"); + } + } + ); + } + }, [wallet, XeonToken, XeonStakingPool]); + + const switchHandler = () => { + setIsSwitched(!isSwitched); + }; + + const handleApprove = async () => { + setLoading(true); + try { + if (!isApproved) { + const tx = await XeonToken.approve( + XeonStakingPool.address, + ethers.utils.parseEther(stakeAmount) + ); + await tx.wait(); + setIsApproved(true); + setButtonText("STAKE"); + setStatus("success"); + setMessage("Approval successful!"); + } + } catch (error) { + setStatus("error"); + setMessage("Approval failed."); + console.error("Approval failed", error); + } finally { + setLoading(false); + onOpen(); + } + }; + + const handleStake = async () => { + setLoading(true); + try { + if (isApproved) { + const tx = await XeonStakingPool.stake( + ethers.utils.parseEther(stakeAmount) + ); + await tx.wait(); + setStatus("success"); + setMessage("Stake successful!"); + } + } catch (error) { + setStatus("error"); + setMessage("Staking failed."); + console.error("Staking failed", error); + } finally { + setLoading(false); + onOpen(); + } + }; + const handleUnstake = async () => { + setLoading(true); + try { + if (parseFloat(stakeAmount) > parseFloat(stakedBalance)) { + throw new Error("Unstake amount exceeds staked balance"); + } + + const tx = await XeonStakingPool.unstake( + ethers.utils.parseEther(stakeAmount) + ); + await tx.wait(); + setMessage("Unstake successful"); + } catch (error) { + console.error("Unstaking failed", error); + setMessage(error.message || "Unstaking failed"); + } finally { + setLoading(false); + onOpen(); + } + }; + + const handleButtonClick = () => { + if (isSwitched) { + handleUnstake(); + } else if (isApproved) { + handleStake(); + } else { + handleApprove(); + } + }; + + const handleStakeAmountChange = (e) => { + const value = e.target.value; + if (isSwitched && parseFloat(value) > parseFloat(stakedBalance)) { + alert("Unstake amount exceeds your staked balance"); + } else if (!isSwitched && parseFloat(value) > parseFloat(walletBalance)) { + alert("Amount exceeds wallet balance"); + } else { + setStakeAmount(value); + } + }; + + return ( +
+
+ + + + {isSwitched ? "Unstaking mode" : "Staking mode"} + + +
+ {isSwitched ? "Unstake Tokens" : "Stake Tokens"} +
+ {isSwitched ? ( + + +
+ +
+
+ ) : ( + +
+ + +
+ +
+
+
+ )} +
+ +
+
+
+
+

+

+ {wallet?.address.slice(0, 6) + + "..." + + wallet?.address.slice(-4)} +

+
+
+ +
+
+ + + + +
+
+
+

Staked

+
+

$XEON {stakedBalance}

+
+
+ +
+

Wallet

+
+

$XEON {walletBalance}

+
+
+
+
+
+
+ + + + + + {status === "success" ? "Success" : "Error"} + + + + {loading ? ( + + ) : ( + + )} + + + + + + + +
+ ); +} + +export default UserAssets; diff --git a/src/components/wallet/AssetsValues.js b/src/components/wallet/AssetsValues.js index 1bd32dd..e97c5f3 100644 --- a/src/components/wallet/AssetsValues.js +++ b/src/components/wallet/AssetsValues.js @@ -1,4 +1,4 @@ -function AssetsValues({ label, value }) { +function AssetsValues({label, value}) { return (
@@ -8,7 +8,7 @@ function AssetsValues({ label, value }) {

- ${value} + {value} {`}`} From ca578cdca38b52c605d45e1fe1ad8b963fcbbc49 Mon Sep 17 00:00:00 2001 From: Jon Date: Thu, 12 Sep 2024 00:55:40 -0400 Subject: [PATCH 02/17] fix: add useEffect hook --- src/app/stake/page.js | 76 +++++++++++++++++++++++++------------------ 1 file changed, 44 insertions(+), 32 deletions(-) diff --git a/src/app/stake/page.js b/src/app/stake/page.js index 8071690..ac8e3fc 100644 --- a/src/app/stake/page.js +++ b/src/app/stake/page.js @@ -1,13 +1,13 @@ -"use client"; -import {Image} from "@chakra-ui/react"; -import React, {useState} from "react"; -import Lottie from "react-lottie-player"; -import lottieJson from "@/assets/animations/PE2.json"; -import XeonStakingPoolABI from "@/abi/XeonStakingPool.abi.json"; -import {Constants} from "@/abi/constants"; -import Header from "@/components/Header"; -import UserAssets from "@/components/staking/UserAssets"; -import {ethers} from "ethers"; +'use client'; +import { useEffect, useState } from 'react'; +import { Image } from '@chakra-ui/react'; +import Lottie from 'react-lottie-player'; +import lottieJson from '@/assets/animations/PE2.json'; +import XeonStakingPoolABI from '@/abi/XeonStakingPool.abi.json'; +import { Constants } from '@/abi/constants'; +import Header from '@/components/Header'; +import UserAssets from '@/components/staking/UserAssets'; +import { ethers } from 'ethers'; import { Modal, ModalOverlay, @@ -17,21 +17,33 @@ import { ModalFooter, Spinner, useDisclosure, -} from "@chakra-ui/react"; -import BookmarkAdded from "@/components/BookmarkAdded"; +} from '@chakra-ui/react'; +import BookmarkAdded from '@/components/BookmarkAdded'; + function Page() { const [voteValue, setVoteValue] = useState(5); const [loading, setLoading] = useState(false); - const [message, setMessage] = useState(""); + const [message, setMessage] = useState(''); + const [provider, setProvider] = useState(null); + const [signer, setSigner] = useState(null); + const { isOpen, onOpen, onClose } = useDisclosure(); - const {isOpen, onOpen, onClose} = useDisclosure(); - const provider = new ethers.providers.Web3Provider(window.ethereum); - const signer = provider.getSigner(); - const XeonStakingPool = new ethers.Contract( - Constants.testnet.XeonStakingPool, - XeonStakingPoolABI, - signer - ); + useEffect(() => { + if (typeof window !== 'undefined' && window.ethereum) { + const web3Provider = new ethers.providers.Web3Provider(window.ethereum); + const signer = web3Provider.getSigner(); + setProvider(web3Provider); + setSigner(signer); + } + }, []); + + const XeonStakingPool = provider + ? new ethers.Contract( + Constants.testnet.XeonStakingPool, + XeonStakingPoolABI, + signer + ) + : null; const handleIncrement = () => { setVoteValue((prevValue) => Math.min(prevValue + 1, 100)); @@ -42,8 +54,8 @@ function Page() { }; const handleVote = async () => { - if (voteValue < 1 || voteValue > 100) { - setMessage("Please enter a value between 1 and 100"); + if (!XeonStakingPool || voteValue < 1 || voteValue > 100) { + setMessage('Please enter a value between 1 and 100'); return; } @@ -56,9 +68,9 @@ function Page() { setLoading(false); setMessage(`Vote successful for ${voteValue}% buyback`); } catch (error) { - console.error("Vote failed", error); + console.error('Vote failed', error); setLoading(false); - setMessage("Vote failed, please try again."); + setMessage('Vote failed, please try again.'); } }; @@ -109,9 +121,9 @@ function Page() { src="/card-109.svg" // w={"100%"} h={{ - base: "150px", - md: "200px", - lg: "185px", + base: '150px', + md: '200px', + lg: '185px', }} alt="container" className="relative hidden md:block ml-[-20px]" @@ -183,17 +195,17 @@ function Page() {
- - + + Vote Feedback - + {loading ? ( ) : ( )} From 9622b15f8aeaba3ab1460d6141d02ccb4b655a7d Mon Sep 17 00:00:00 2001 From: Jon Date: Thu, 12 Sep 2024 01:15:44 -0400 Subject: [PATCH 03/17] fix: add useMemo hook to `components/wallet/UserAssets.js` --- src/components/BookmarkAdded.js | 22 ++-- src/components/wallet/UserAssets.js | 153 +++++++++++++++------------- 2 files changed, 90 insertions(+), 85 deletions(-) diff --git a/src/components/BookmarkAdded.js b/src/components/BookmarkAdded.js index bfda0d7..993dc88 100644 --- a/src/components/BookmarkAdded.js +++ b/src/components/BookmarkAdded.js @@ -1,13 +1,6 @@ -import {Image} from "@chakra-ui/react"; -const explorerUrls = { - 0: "https://sepolia.basescan.org/tx/", - 1: "https://etherscan.io/tx/", - 56: "https://bscscan.com/tx/", - 137: "https://polygonscan.com/tx/", - 84532: "https://sepolia.basescan.org/tx/", -}; +import Image from 'next/image'; -function BookmarkAdded({message, status, chainId, txHash}) { +const BookmarkAdded = ({ message, status, chainId, txHash }) => { const explorerUrl = explorerUrls[chainId] ? `${explorerUrls[chainId]}${txHash}` : null; @@ -16,14 +9,13 @@ function BookmarkAdded({message, status, chainId, txHash}) {

{status}

transaction status -

{message}

- - {status === "success" && explorerUrl && ( + {status === 'success' && explorerUrl && ( ); -} +}; export default BookmarkAdded; diff --git a/src/components/wallet/UserAssets.js b/src/components/wallet/UserAssets.js index 00596cc..fec543c 100644 --- a/src/components/wallet/UserAssets.js +++ b/src/components/wallet/UserAssets.js @@ -1,17 +1,94 @@ 'use client'; import { FormControl, FormLabel, Switch } from '@chakra-ui/react'; import { motion } from 'framer-motion'; -import { useState } from 'react'; +import { useState, useMemo } from 'react'; import { FaEthereum } from 'react-icons/fa'; import AssetsValues from './AssetsValues'; function UserAssets() { + // Keep track of the switch state const [isSwitched, setIsSwitched] = useState(false); + // Switch handler const switchHandler = () => { setIsSwitched(!isSwitched); }; + // Memoized values for deposit and withdraw sections + const depositSection = useMemo( + () => ( + +
+ Deposit Tokens +
+
+ + +
+

Deposited:

+

Withdrawn:

+
+
+ +
+
+
+ ), + [] + ); + + const withdrawSection = useMemo( + () => ( + +
+ Withdraw Tokens +
+
+ + +
+

Deposited:

+

Withdrawn:

+
+
+ +
+
+
+ ), + [] + ); + return (
@@ -26,78 +103,14 @@ function UserAssets() { - {isSwitched ? ( - -
- Withdraw Tokens -
-
- - -
-

Deposited:

-

Withdrawn:

-
-
- -
-
-
- ) : ( - -
- Deposit Tokens -
-
- - -
-

Deposited:

-

Withdrawn:

-
-
- -
-
-
- )} + {/* Conditionally render deposit or withdraw section */} + {isSwitched ? withdrawSection : depositSection}
+
- {' '}
- {' '}

Net Worth

$0.00

@@ -110,8 +123,8 @@ function UserAssets() {
- - + +
From b91285f8f1425efbff8dd111afdba6d654c0a125 Mon Sep 17 00:00:00 2001 From: Jon Date: Thu, 12 Sep 2024 01:19:18 -0400 Subject: [PATCH 04/17] fix: add useMemo --- src/components/wallet/UserAssets.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/components/wallet/UserAssets.js b/src/components/wallet/UserAssets.js index fec543c..b5e72b2 100644 --- a/src/components/wallet/UserAssets.js +++ b/src/components/wallet/UserAssets.js @@ -6,15 +6,15 @@ import { FaEthereum } from 'react-icons/fa'; import AssetsValues from './AssetsValues'; function UserAssets() { - // Keep track of the switch state + // keep track of the switch state const [isSwitched, setIsSwitched] = useState(false); - // Switch handler + // switch handler const switchHandler = () => { setIsSwitched(!isSwitched); }; - // Memoized values for deposit and withdraw sections + // memoized values for deposit and withdraw sections const depositSection = useMemo( () => ( - {/* Conditionally render deposit or withdraw section */} + {/* conditionally render deposit or withdraw section */} {isSwitched ? withdrawSection : depositSection}
From b7f32b4a06643a3e4106cf5234a98dec43207c72 Mon Sep 17 00:00:00 2001 From: Jon Date: Thu, 12 Sep 2024 01:21:14 -0400 Subject: [PATCH 05/17] fix: add explorerUrls back to BookmarkAdded --- src/components/BookmarkAdded.js | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/src/components/BookmarkAdded.js b/src/components/BookmarkAdded.js index 993dc88..b7e21e6 100644 --- a/src/components/BookmarkAdded.js +++ b/src/components/BookmarkAdded.js @@ -1,6 +1,13 @@ -import Image from 'next/image'; +import { Image } from '@chakra-ui/react'; +const explorerUrls = { + 0: 'https://sepolia.basescan.org/tx/', + 1: 'https://etherscan.io/tx/', + 56: 'https://bscscan.com/tx/', + 137: 'https://polygonscan.com/tx/', + 84532: 'https://sepolia.basescan.org/tx/', +}; -const BookmarkAdded = ({ message, status, chainId, txHash }) => { +function BookmarkAdded({ message, status, chainId, txHash }) { const explorerUrl = explorerUrls[chainId] ? `${explorerUrls[chainId]}${txHash}` : null; @@ -11,10 +18,11 @@ const BookmarkAdded = ({ message, status, chainId, txHash }) => { transaction status +

{message}

+ {status === 'success' && explorerUrl && (
{ )}
); -}; +} export default BookmarkAdded; From a844e1ce2d9f444a7a01410d4749da0303d07eb0 Mon Sep 17 00:00:00 2001 From: Jon Date: Thu, 12 Sep 2024 01:23:18 -0400 Subject: [PATCH 06/17] fix: revert UserAssets.js to previous code --- src/components/wallet/UserAssets.js | 153 +++++++++++++--------------- 1 file changed, 70 insertions(+), 83 deletions(-) diff --git a/src/components/wallet/UserAssets.js b/src/components/wallet/UserAssets.js index b5e72b2..00596cc 100644 --- a/src/components/wallet/UserAssets.js +++ b/src/components/wallet/UserAssets.js @@ -1,94 +1,17 @@ 'use client'; import { FormControl, FormLabel, Switch } from '@chakra-ui/react'; import { motion } from 'framer-motion'; -import { useState, useMemo } from 'react'; +import { useState } from 'react'; import { FaEthereum } from 'react-icons/fa'; import AssetsValues from './AssetsValues'; function UserAssets() { - // keep track of the switch state const [isSwitched, setIsSwitched] = useState(false); - // switch handler const switchHandler = () => { setIsSwitched(!isSwitched); }; - // memoized values for deposit and withdraw sections - const depositSection = useMemo( - () => ( - -
- Deposit Tokens -
-
- - -
-

Deposited:

-

Withdrawn:

-
-
- -
-
-
- ), - [] - ); - - const withdrawSection = useMemo( - () => ( - -
- Withdraw Tokens -
-
- - -
-

Deposited:

-

Withdrawn:

-
-
- -
-
-
- ), - [] - ); - return (
@@ -103,14 +26,78 @@ function UserAssets() { - {/* conditionally render deposit or withdraw section */} - {isSwitched ? withdrawSection : depositSection} + {isSwitched ? ( + +
+ Withdraw Tokens +
+
+ + +
+

Deposited:

+

Withdrawn:

+
+
+ +
+
+
+ ) : ( + +
+ Deposit Tokens +
+
+ + +
+

Deposited:

+

Withdrawn:

+
+
+ +
+
+
+ )}
-
+ {' '}
+ {' '}

Net Worth

$0.00

@@ -123,8 +110,8 @@ function UserAssets() {
- - + +
From 143b3e558569821597a2dbf7bfb2bb902e0fed17 Mon Sep 17 00:00:00 2001 From: Jon Date: Thu, 12 Sep 2024 01:29:34 -0400 Subject: [PATCH 07/17] feat: memoize XeonStakingPool contract interaction --- src/app/stake/page.js | 33 +++++++++++++++++++-------------- 1 file changed, 19 insertions(+), 14 deletions(-) diff --git a/src/app/stake/page.js b/src/app/stake/page.js index ac8e3fc..45dde63 100644 --- a/src/app/stake/page.js +++ b/src/app/stake/page.js @@ -1,5 +1,5 @@ 'use client'; -import { useEffect, useState } from 'react'; +import { useEffect, useState, useMemo } from 'react'; import { Image } from '@chakra-ui/react'; import Lottie from 'react-lottie-player'; import lottieJson from '@/assets/animations/PE2.json'; @@ -28,6 +28,7 @@ function Page() { const [signer, setSigner] = useState(null); const { isOpen, onOpen, onClose } = useDisclosure(); + // init provider and signer useEffect(() => { if (typeof window !== 'undefined' && window.ethereum) { const web3Provider = new ethers.providers.Web3Provider(window.ethereum); @@ -37,14 +38,17 @@ function Page() { } }, []); - const XeonStakingPool = provider - ? new ethers.Contract( - Constants.testnet.XeonStakingPool, - XeonStakingPoolABI, - signer - ) - : null; + // memoize the XeonStakingPool contract instance to avoid re-creating it on every render + const XeonStakingPool = useMemo(() => { + if (!provider || !signer) return null; + return new ethers.Contract( + Constants.testnet.XeonStakingPool, + XeonStakingPoolABI, + signer + ); + }, [provider, signer]); + // handle increment and decrement of vote value const handleIncrement = () => { setVoteValue((prevValue) => Math.min(prevValue + 1, 100)); }; @@ -53,6 +57,7 @@ function Page() { setVoteValue((prevValue) => Math.max(prevValue - 1, 1)); }; + // handle voting functionality const handleVote = async () => { if (!XeonStakingPool || voteValue < 1 || voteValue > 100) { setMessage('Please enter a value between 1 and 100'); @@ -74,6 +79,7 @@ function Page() { } }; + // handle vote value change const handleVoteChange = (e) => { const value = parseInt(e.target.value); if (Number.isNaN(value)) { @@ -111,15 +117,14 @@ function Page() {

- Stake XEON tokens to be eligible for rewards sharing. The staking - window opens 3 days only each month-end, during which users can - stake or unstake XEON. Revenue is deposited to staking contract - for claiming. + Stake XEON tokens to be eligible for revenue sharing. The staking + window opens for 3 days at the end of each epoch, at which time + XEON can be staked or unstaked. Protocol revenue is deposited is + deposited into the staking pool.

- + {/* todo: percentage should be obtained from contract data */}

Current 5.00%

From 0791cd7f92ac3d85bbac865992140789f72112d3 Mon Sep 17 00:00:00 2001 From: Jon Date: Thu, 12 Sep 2024 01:38:56 -0400 Subject: [PATCH 08/17] feat: fetch current buyback percentage from XeonStakingPool --- src/app/stake/page.js | 30 ++++++++++++++++++++++++------ 1 file changed, 24 insertions(+), 6 deletions(-) diff --git a/src/app/stake/page.js b/src/app/stake/page.js index 45dde63..209ace6 100644 --- a/src/app/stake/page.js +++ b/src/app/stake/page.js @@ -21,7 +21,8 @@ import { import BookmarkAdded from '@/components/BookmarkAdded'; function Page() { - const [voteValue, setVoteValue] = useState(5); + const [voteValue, setVoteValue] = useState(5); // state for user vote + const [currentPercentage, setCurrentPercentage] = useState(5); // state for current buyback percentage const [loading, setLoading] = useState(false); const [message, setMessage] = useState(''); const [provider, setProvider] = useState(null); @@ -48,19 +49,35 @@ function Page() { ); }, [provider, signer]); + // fetch current buyback percentage from contract + useEffect(() => { + const fetchBuybackPercentage = async () => { + if (XeonStakingPool) { + try { + const percentage = await XeonStakingPool.buyBackPercentage(); // assume integer value from contract + setCurrentPercentage(percentage.toNumber()); // update state with value + } catch (error) { + console.error('Error fetching buyback percentage:', error); + } + } + }; + + fetchBuybackPercentage(); + }, [XeonStakingPool]); + // handle increment and decrement of vote value const handleIncrement = () => { - setVoteValue((prevValue) => Math.min(prevValue + 1, 100)); + setVoteValue((prevValue) => Math.min(prevValue + 1, 100)); // todo: ensure vote value is clamped to current contract min/max }; const handleDecrement = () => { - setVoteValue((prevValue) => Math.max(prevValue - 1, 1)); + setVoteValue((prevValue) => Math.max(prevValue - 1, 1)); // todo: ensure vote value is clamped to current contract min/max }; // handle voting functionality const handleVote = async () => { if (!XeonStakingPool || voteValue < 1 || voteValue > 100) { - setMessage('Please enter a value between 1 and 100'); + setMessage('Please enter a value between 1 and 100'); // todo: ensure vote value is clamped to current contract min/max return; } @@ -193,8 +210,9 @@ function Page() { Vote
- {/* todo: percentage should be obtained from contract data */} -

Current 5.00%

+

+ Current Buyback Percentage: {currentPercentage}% +

From 0e886f89b9e81523b6af1a25fc05c4390a354133 Mon Sep 17 00:00:00 2001 From: Jon Date: Thu, 12 Sep 2024 01:54:11 -0400 Subject: [PATCH 09/17] feat: add todo comments for mainnet --- src/app/stake/page.js | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/app/stake/page.js b/src/app/stake/page.js index 209ace6..9fba44c 100644 --- a/src/app/stake/page.js +++ b/src/app/stake/page.js @@ -22,12 +22,14 @@ import BookmarkAdded from '@/components/BookmarkAdded'; function Page() { const [voteValue, setVoteValue] = useState(5); // state for user vote + // todo: for mainnet, ensure current percentage is correct const [currentPercentage, setCurrentPercentage] = useState(5); // state for current buyback percentage const [loading, setLoading] = useState(false); const [message, setMessage] = useState(''); const [provider, setProvider] = useState(null); const [signer, setSigner] = useState(null); const { isOpen, onOpen, onClose } = useDisclosure(); + // todo: add epoch number // init provider and signer useEffect(() => { @@ -54,6 +56,7 @@ function Page() { const fetchBuybackPercentage = async () => { if (XeonStakingPool) { try { + // todo: for mainnet, ensure value is formatted correctly (N/10000) const percentage = await XeonStakingPool.buyBackPercentage(); // assume integer value from contract setCurrentPercentage(percentage.toNumber()); // update state with value } catch (error) { @@ -66,18 +69,19 @@ function Page() { }, [XeonStakingPool]); // handle increment and decrement of vote value + // todo: for mainnet, ensure vote value is clamped to contract min/max const handleIncrement = () => { - setVoteValue((prevValue) => Math.min(prevValue + 1, 100)); // todo: ensure vote value is clamped to current contract min/max + setVoteValue((prevValue) => Math.min(prevValue + 1, 100)); }; const handleDecrement = () => { - setVoteValue((prevValue) => Math.max(prevValue - 1, 1)); // todo: ensure vote value is clamped to current contract min/max + setVoteValue((prevValue) => Math.max(prevValue - 1, 1)); }; // handle voting functionality const handleVote = async () => { if (!XeonStakingPool || voteValue < 1 || voteValue > 100) { - setMessage('Please enter a value between 1 and 100'); // todo: ensure vote value is clamped to current contract min/max + setMessage('Please enter a value between 1 and 100'); return; } From 078837d8d19977aa6247295d0f25546495884207 Mon Sep 17 00:00:00 2001 From: Jon Date: Thu, 12 Sep 2024 02:00:47 -0400 Subject: [PATCH 10/17] fix: try initializing provider in useEffect --- src/app/stake/page.js | 25 +++++++++++++++---------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/src/app/stake/page.js b/src/app/stake/page.js index 9fba44c..4b5e287 100644 --- a/src/app/stake/page.js +++ b/src/app/stake/page.js @@ -1,4 +1,5 @@ 'use client'; + import { useEffect, useState, useMemo } from 'react'; import { Image } from '@chakra-ui/react'; import Lottie from 'react-lottie-player'; @@ -21,8 +22,8 @@ import { import BookmarkAdded from '@/components/BookmarkAdded'; function Page() { - const [voteValue, setVoteValue] = useState(5); // state for user vote - // todo: for mainnet, ensure current percentage is correct + const [voteValue, setVoteValue] = useState(5); // state for user buyback vote value + // todo: for mainnet, ensure currentPercentage is proper default const [currentPercentage, setCurrentPercentage] = useState(5); // state for current buyback percentage const [loading, setLoading] = useState(false); const [message, setMessage] = useState(''); @@ -32,13 +33,18 @@ function Page() { // todo: add epoch number // init provider and signer + useEffect(() => { - if (typeof window !== 'undefined' && window.ethereum) { - const web3Provider = new ethers.providers.Web3Provider(window.ethereum); - const signer = web3Provider.getSigner(); - setProvider(web3Provider); - setSigner(signer); - } + const initializeProvider = async () => { + if (typeof window !== 'undefined' && window.ethereum) { + const web3Provider = new ethers.providers.Web3Provider(window.ethereum); + const signer = web3Provider.getSigner(); + setProvider(web3Provider); + setSigner(signer); + } + }; + + initializeProvider(); }, []); // memoize the XeonStakingPool contract instance to avoid re-creating it on every render @@ -51,8 +57,8 @@ function Page() { ); }, [provider, signer]); - // fetch current buyback percentage from contract useEffect(() => { + // fetch current buyback percentage from contract const fetchBuybackPercentage = async () => { if (XeonStakingPool) { try { @@ -78,7 +84,6 @@ function Page() { setVoteValue((prevValue) => Math.max(prevValue - 1, 1)); }; - // handle voting functionality const handleVote = async () => { if (!XeonStakingPool || voteValue < 1 || voteValue > 100) { setMessage('Please enter a value between 1 and 100'); From 7e6125445503841b4f42435a6bc7e60a9545cce9 Mon Sep 17 00:00:00 2001 From: Jon Date: Thu, 12 Sep 2024 02:08:09 -0400 Subject: [PATCH 11/17] fix: update hardcoded WETH address to read from `constants.js` --- src/app/stake/page.js | 1 - src/components/staking/UserAssets.js | 117 +++++++++++++-------------- 2 files changed, 58 insertions(+), 60 deletions(-) diff --git a/src/app/stake/page.js b/src/app/stake/page.js index 4b5e287..44f6e4a 100644 --- a/src/app/stake/page.js +++ b/src/app/stake/page.js @@ -33,7 +33,6 @@ function Page() { // todo: add epoch number // init provider and signer - useEffect(() => { const initializeProvider = async () => { if (typeof window !== 'undefined' && window.ethereum) { diff --git a/src/components/staking/UserAssets.js b/src/components/staking/UserAssets.js index 82c0278..3976f99 100644 --- a/src/components/staking/UserAssets.js +++ b/src/components/staking/UserAssets.js @@ -1,4 +1,4 @@ -"use client"; +'use client'; import { FormControl, FormLabel, @@ -12,34 +12,34 @@ import { ModalCloseButton, ModalFooter, useDisclosure, -} from "@chakra-ui/react"; -import {motion} from "framer-motion"; -import {useState, useEffect} from "react"; -import {FaEthereum} from "react-icons/fa"; -import AssetsValues from "../wallet/AssetsValues"; -import {useActiveAccount} from "thirdweb/react"; -import {ethers} from "ethers"; -import XeonStakingPoolABI from "@/abi/XeonStakingPool.abi.json"; -import {Constants} from "@/abi/constants"; -import BookmarkAdded from "../BookmarkAdded"; +} from '@chakra-ui/react'; +import { motion } from 'framer-motion'; +import { useState, useEffect } from 'react'; +import { FaEthereum } from 'react-icons/fa'; +import AssetsValues from '../wallet/AssetsValues'; +import { useActiveAccount } from 'thirdweb/react'; +import { ethers } from 'ethers'; +import XeonStakingPoolABI from '@/abi/XeonStakingPool.abi.json'; +import { Constants } from '@/abi/constants'; +import BookmarkAdded from '../BookmarkAdded'; function UserAssets() { const [isSwitched, setIsSwitched] = useState(false); const [walletBalance, setWalletBalance] = useState(0); const [stakedBalance, setStakedBalance] = useState(0); - const [stakeAmount, setStakeAmount] = useState(""); + const [stakeAmount, setStakeAmount] = useState(''); const [isApproved, setIsApproved] = useState(false); - const [buttonText, setButtonText] = useState("APPROVE"); + const [buttonText, setButtonText] = useState('APPROVE'); const [loading, setLoading] = useState(false); - const [message, setMessage] = useState(""); - const [status, setStatus] = useState(""); - const [epoch, setEpoch] = useState("0.00"); - const [ethInPool, setEthInPool] = useState("0.00"); - const [buyBackPercentage, setBuyBackPercentage] = useState("0.00"); - const [teamPercentage, setTeamPercentage] = useState("0.00"); - const [walletXeonBalance, setWalletXeonBalance] = useState("0.00"); - const [stakedXeonBalance, setStakedXeonBalance] = useState("0.00"); - const {isOpen, onOpen, onClose} = useDisclosure(); + const [message, setMessage] = useState(''); + const [status, setStatus] = useState(''); + const [epoch, setEpoch] = useState('0.00'); + const [ethInPool, setEthInPool] = useState('0.00'); + const [buyBackPercentage, setBuyBackPercentage] = useState('0.00'); + const [teamPercentage, setTeamPercentage] = useState('0.00'); + const [walletXeonBalance, setWalletXeonBalance] = useState('0.00'); + const [stakedXeonBalance, setStakedXeonBalance] = useState('0.00'); + const { isOpen, onOpen, onClose } = useDisclosure(); const wallet = useActiveAccount(); const connectedAddress = wallet?.address; const provider = new ethers.providers.Web3Provider(window.ethereum); @@ -55,7 +55,7 @@ function UserAssets() { signer ); const WETH = new ethers.Contract( - "0x949B2156916A63686835DaF66518C22D497bf8B0", + Constants.testnet.WETH, XeonStakingPoolABI, provider ); @@ -79,12 +79,11 @@ function UserAssets() { const xeonBalance = await XeonToken.balanceOf(connectedAddress); setWalletXeonBalance(ethers.utils.formatEther(xeonBalance)); - const stakedXeonBalance = await XeonStakingPool.balanceOf( - connectedAddress - ); + const stakedXeonBalance = + await XeonStakingPool.balanceOf(connectedAddress); setStakedXeonBalance(ethers.utils.formatEther(stakedXeonBalance)); } catch (error) { - console.error("Error fetching asset values:", error); + console.error('Error fetching asset values:', error); } }; @@ -106,7 +105,7 @@ function UserAssets() { (allowance) => { if (ethers.utils.formatEther(allowance) > 0) { setIsApproved(true); - setButtonText("STAKE"); + setButtonText('STAKE'); } } ); @@ -127,14 +126,14 @@ function UserAssets() { ); await tx.wait(); setIsApproved(true); - setButtonText("STAKE"); - setStatus("success"); - setMessage("Approval successful!"); + setButtonText('STAKE'); + setStatus('success'); + setMessage('Approval successful!'); } } catch (error) { - setStatus("error"); - setMessage("Approval failed."); - console.error("Approval failed", error); + setStatus('error'); + setMessage('Approval failed.'); + console.error('Approval failed', error); } finally { setLoading(false); onOpen(); @@ -149,13 +148,13 @@ function UserAssets() { ethers.utils.parseEther(stakeAmount) ); await tx.wait(); - setStatus("success"); - setMessage("Stake successful!"); + setStatus('success'); + setMessage('Stake successful!'); } } catch (error) { - setStatus("error"); - setMessage("Staking failed."); - console.error("Staking failed", error); + setStatus('error'); + setMessage('Staking failed.'); + console.error('Staking failed', error); } finally { setLoading(false); onOpen(); @@ -165,17 +164,17 @@ function UserAssets() { setLoading(true); try { if (parseFloat(stakeAmount) > parseFloat(stakedBalance)) { - throw new Error("Unstake amount exceeds staked balance"); + throw new Error('Unstake amount exceeds staked balance'); } const tx = await XeonStakingPool.unstake( ethers.utils.parseEther(stakeAmount) ); await tx.wait(); - setMessage("Unstake successful"); + setMessage('Unstake successful'); } catch (error) { - console.error("Unstaking failed", error); - setMessage(error.message || "Unstaking failed"); + console.error('Unstaking failed', error); + setMessage(error.message || 'Unstaking failed'); } finally { setLoading(false); onOpen(); @@ -195,9 +194,9 @@ function UserAssets() { const handleStakeAmountChange = (e) => { const value = e.target.value; if (isSwitched && parseFloat(value) > parseFloat(stakedBalance)) { - alert("Unstake amount exceeds your staked balance"); + alert('Unstake amount exceeds your staked balance'); } else if (!isSwitched && parseFloat(value) > parseFloat(walletBalance)) { - alert("Amount exceeds wallet balance"); + alert('Amount exceeds wallet balance'); } else { setStakeAmount(value); } @@ -213,18 +212,18 @@ function UserAssets() { id="mode-switch" /> - {isSwitched ? "Unstaking mode" : "Staking mode"} + {isSwitched ? 'Unstaking mode' : 'Staking mode'}
- {isSwitched ? "Unstake Tokens" : "Stake Tokens"} + {isSwitched ? 'Unstake Tokens' : 'Stake Tokens'}
{isSwitched ? ( - {loading ? : "Unstake"} + {loading ? : 'Unstake'}
) : (

{wallet?.address.slice(0, 6) + - "..." + + '...' + wallet?.address.slice(-4)}

@@ -320,12 +319,12 @@ function UserAssets() { - - - {status === "success" ? "Success" : "Error"} + + + {status === 'success' ? 'Success' : 'Error'} - + {loading ? ( ) : ( From 8cf84e0e4ff1d53a648a8bb605072128c2fd8a92 Mon Sep 17 00:00:00 2001 From: Jon Date: Thu, 12 Sep 2024 02:13:35 -0400 Subject: [PATCH 12/17] fix: wrap provider and signer in useEffect and memoize contracts --- src/components/staking/UserAssets.js | 69 +++++++++++++++++++--------- 1 file changed, 48 insertions(+), 21 deletions(-) diff --git a/src/components/staking/UserAssets.js b/src/components/staking/UserAssets.js index 3976f99..9bcb5e4 100644 --- a/src/components/staking/UserAssets.js +++ b/src/components/staking/UserAssets.js @@ -14,7 +14,7 @@ import { useDisclosure, } from '@chakra-ui/react'; import { motion } from 'framer-motion'; -import { useState, useEffect } from 'react'; +import { useState, useEffect, useMemo } from 'react'; import { FaEthereum } from 'react-icons/fa'; import AssetsValues from '../wallet/AssetsValues'; import { useActiveAccount } from 'thirdweb/react'; @@ -42,26 +42,51 @@ function UserAssets() { const { isOpen, onOpen, onClose } = useDisclosure(); const wallet = useActiveAccount(); const connectedAddress = wallet?.address; - const provider = new ethers.providers.Web3Provider(window.ethereum); - const signer = provider.getSigner(); - const XeonToken = new ethers.Contract( - Constants.testnet.XeonToken, - XeonStakingPoolABI, - signer - ); - const XeonStakingPool = new ethers.Contract( - Constants.testnet.XeonStakingPool, - XeonStakingPoolABI, - signer - ); - const WETH = new ethers.Contract( - Constants.testnet.WETH, - XeonStakingPoolABI, - provider - ); + const [provider, setProvider] = useState(null); + const [signer, setSigner] = useState(null); + + useEffect(() => { + if (typeof window !== 'undefined' && window.ethereum) { + const web3Provider = new ethers.providers.Web3Provider(window.ethereum); + const signer = web3Provider.getSigner(); + setProvider(web3Provider); + setSigner(signer); + } + }, []); + + const XeonToken = useMemo(() => { + if (!provider || !signer) return null; + return new ethers.Contract( + Constants.testnet.XeonToken, + XeonStakingPoolABI, + signer + ); + }, [provider, signer]); + + const XeonStakingPool = useMemo(() => { + if (!provider || !signer) return null; + return new ethers.Contract( + Constants.testnet.XeonStakingPool, + XeonStakingPoolABI, + signer + ); + }, [provider, signer]); + + const WETH = useMemo(() => { + if (!provider) return null; + return new ethers.Contract( + Constants.testnet.WETH, + XeonStakingPoolABI, + provider + ); + }, [provider]); + useEffect(() => { const fetchData = async () => { try { + if (!XeonStakingPool || !WETH || !XeonToken || !connectedAddress) + return; + const epoch = await XeonStakingPool.epoch(); setEpoch(ethers.utils.formatUnits(epoch, 0)); @@ -91,8 +116,9 @@ function UserAssets() { fetchData(); } }, [connectedAddress, XeonStakingPool, XeonToken, WETH]); + useEffect(() => { - if (wallet) { + if (wallet && XeonToken && XeonStakingPool) { XeonToken.balanceOf(wallet.address).then((balance) => { setWalletBalance(ethers.utils.formatEther(balance)); }); @@ -119,7 +145,7 @@ function UserAssets() { const handleApprove = async () => { setLoading(true); try { - if (!isApproved) { + if (!isApproved && XeonToken) { const tx = await XeonToken.approve( XeonStakingPool.address, ethers.utils.parseEther(stakeAmount) @@ -143,7 +169,7 @@ function UserAssets() { const handleStake = async () => { setLoading(true); try { - if (isApproved) { + if (isApproved && XeonStakingPool) { const tx = await XeonStakingPool.stake( ethers.utils.parseEther(stakeAmount) ); @@ -160,6 +186,7 @@ function UserAssets() { onOpen(); } }; + const handleUnstake = async () => { setLoading(true); try { From c880c8c97a46e5dba75fbc1ac3c40a6e623a5e8e Mon Sep 17 00:00:00 2001 From: Jon Date: Thu, 12 Sep 2024 02:32:55 -0400 Subject: [PATCH 13/17] feat: add todo items to UserAssets.js --- src/components/staking/UserAssets.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/components/staking/UserAssets.js b/src/components/staking/UserAssets.js index 9bcb5e4..aa6e061 100644 --- a/src/components/staking/UserAssets.js +++ b/src/components/staking/UserAssets.js @@ -33,12 +33,12 @@ function UserAssets() { const [loading, setLoading] = useState(false); const [message, setMessage] = useState(''); const [status, setStatus] = useState(''); - const [epoch, setEpoch] = useState('0.00'); + const [epoch, setEpoch] = useState('0.00'); // todo: app doesn't set epoch, only reads it (value is whole number integer) const [ethInPool, setEthInPool] = useState('0.00'); const [buyBackPercentage, setBuyBackPercentage] = useState('0.00'); const [teamPercentage, setTeamPercentage] = useState('0.00'); - const [walletXeonBalance, setWalletXeonBalance] = useState('0.00'); - const [stakedXeonBalance, setStakedXeonBalance] = useState('0.00'); + const [walletXeonBalance, setWalletXeonBalance] = useState('0.00'); // todo: display user's contract balance + const [stakedXeonBalance, setStakedXeonBalance] = useState('0.00'); // todo: display user's staked balance const { isOpen, onOpen, onClose } = useDisclosure(); const wallet = useActiveAccount(); const connectedAddress = wallet?.address; From ac752a6f11ba9806ecaead2a11a83b81708c8714 Mon Sep 17 00:00:00 2001 From: Jon Date: Thu, 12 Sep 2024 02:33:19 -0400 Subject: [PATCH 14/17] chore: remove unused todo in page.js --- src/app/stake/page.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/app/stake/page.js b/src/app/stake/page.js index 44f6e4a..ceca21d 100644 --- a/src/app/stake/page.js +++ b/src/app/stake/page.js @@ -30,7 +30,6 @@ function Page() { const [provider, setProvider] = useState(null); const [signer, setSigner] = useState(null); const { isOpen, onOpen, onClose } = useDisclosure(); - // todo: add epoch number // init provider and signer useEffect(() => { From 4cc8166d300645359053628ff48e6acec203db6b Mon Sep 17 00:00:00 2001 From: rtj99 Date: Sat, 14 Sep 2024 01:12:59 +0200 Subject: [PATCH 15/17] style: change ui design of the staking page --- src/app/stake/page.js | 266 +++++++--------- src/components/staking/UserAssets.js | 439 +++++++++++++++------------ 2 files changed, 354 insertions(+), 351 deletions(-) diff --git a/src/app/stake/page.js b/src/app/stake/page.js index ceca21d..4ce7f05 100644 --- a/src/app/stake/page.js +++ b/src/app/stake/page.js @@ -1,14 +1,11 @@ -'use client'; - -import { useEffect, useState, useMemo } from 'react'; -import { Image } from '@chakra-ui/react'; -import Lottie from 'react-lottie-player'; -import lottieJson from '@/assets/animations/PE2.json'; -import XeonStakingPoolABI from '@/abi/XeonStakingPool.abi.json'; -import { Constants } from '@/abi/constants'; -import Header from '@/components/Header'; -import UserAssets from '@/components/staking/UserAssets'; -import { ethers } from 'ethers'; +"use client"; + +import {useEffect, useState, useMemo} from "react"; +import XeonStakingPoolABI from "@/abi/XeonStakingPool.abi.json"; +import {Constants} from "@/abi/constants"; +import Header from "@/components/Header"; +import UserAssets from "@/components/staking/UserAssets"; +import {ethers} from "ethers"; import { Modal, ModalOverlay, @@ -18,23 +15,29 @@ import { ModalFooter, Spinner, useDisclosure, -} from '@chakra-ui/react'; -import BookmarkAdded from '@/components/BookmarkAdded'; +} from "@chakra-ui/react"; +import BookmarkAdded from "@/components/BookmarkAdded"; +import {useActiveAccount} from "thirdweb/react"; function Page() { - const [voteValue, setVoteValue] = useState(5); // state for user buyback vote value - // todo: for mainnet, ensure currentPercentage is proper default - const [currentPercentage, setCurrentPercentage] = useState(5); // state for current buyback percentage const [loading, setLoading] = useState(false); - const [message, setMessage] = useState(''); + const [message, setMessage] = useState(""); + const [epoch, setEpoch] = useState("0.00"); // todo: app doesn't set epoch, only reads it (value is whole number integer) + const [ethInPool, setEthInPool] = useState("0.00"); + const [buyBackPercentage, setBuyBackPercentage] = useState("0.00"); + const [teamPercentage, setTeamPercentage] = useState("0.00"); + const [walletXeonBalance, setWalletXeonBalance] = useState("0.00"); // todo: display user's contract balance + const [stakedXeonBalance, setStakedXeonBalance] = useState("0.00"); // todo: display user's staked balance + const wallet = useActiveAccount(); + const connectedAddress = wallet?.address; const [provider, setProvider] = useState(null); const [signer, setSigner] = useState(null); - const { isOpen, onOpen, onClose } = useDisclosure(); + const {isOpen, onOpen, onClose} = useDisclosure(); // init provider and signer useEffect(() => { const initializeProvider = async () => { - if (typeof window !== 'undefined' && window.ethereum) { + if (typeof window !== "undefined" && window.ethereum) { const web3Provider = new ethers.providers.Web3Provider(window.ethereum); const signer = web3Provider.getSigner(); setProvider(web3Provider); @@ -55,6 +58,59 @@ function Page() { ); }, [provider, signer]); + const XeonToken = useMemo(() => { + if (!provider || !signer) return null; + return new ethers.Contract( + Constants.testnet.XeonToken, + XeonStakingPoolABI, + signer + ); + }, [provider, signer]); + const WETH = useMemo(() => { + if (!provider) return null; + return new ethers.Contract( + Constants.testnet.WETH, + XeonStakingPoolABI, + provider + ); + }, [provider]); + useEffect(() => { + const fetchData = async () => { + try { + if (!XeonStakingPool || !WETH || !XeonToken || !connectedAddress) + return; + + const epoch = await XeonStakingPool.epoch(); + setEpoch(ethers.utils.formatUnits(epoch, 0)); + + const ethBalance = await WETH.balanceOf( + Constants.testnet.XeonStakingPool + ); + setEthInPool(ethers.utils.formatEther(ethBalance)); + + const buyBackPercentage = await XeonStakingPool.buyBackPercentage(); + setBuyBackPercentage(ethers.utils.formatUnits(buyBackPercentage, 0)); + + const teamPercentage = await XeonStakingPool.teamPercentage(); + setTeamPercentage(ethers.utils.formatUnits(teamPercentage, 0)); + + const xeonBalance = await XeonToken.balanceOf(connectedAddress); + setWalletXeonBalance(ethers.utils.formatEther(xeonBalance)); + + const stakedXeonBalance = await XeonStakingPool.balanceOf( + connectedAddress + ); + setStakedXeonBalance(ethers.utils.formatEther(stakedXeonBalance)); + } catch (error) { + console.error("Error fetching asset values:", error); + } + }; + + if (connectedAddress) { + fetchData(); + } + }, [connectedAddress, XeonStakingPool, XeonToken, WETH]); + useEffect(() => { // fetch current buyback percentage from contract const fetchBuybackPercentage = async () => { @@ -64,7 +120,7 @@ function Page() { const percentage = await XeonStakingPool.buyBackPercentage(); // assume integer value from contract setCurrentPercentage(percentage.toNumber()); // update state with value } catch (error) { - console.error('Error fetching buyback percentage:', error); + console.error("Error fetching buyback percentage:", error); } } }; @@ -72,170 +128,66 @@ function Page() { fetchBuybackPercentage(); }, [XeonStakingPool]); - // handle increment and decrement of vote value - // todo: for mainnet, ensure vote value is clamped to contract min/max - const handleIncrement = () => { - setVoteValue((prevValue) => Math.min(prevValue + 1, 100)); - }; - - const handleDecrement = () => { - setVoteValue((prevValue) => Math.max(prevValue - 1, 1)); - }; - - const handleVote = async () => { - if (!XeonStakingPool || voteValue < 1 || voteValue > 100) { - setMessage('Please enter a value between 1 and 100'); - return; - } - - setLoading(true); - onOpen(); - - try { - const tx = await XeonStakingPool.voteForBuybackPercentage(voteValue); - await tx.wait(); - setLoading(false); - setMessage(`Vote successful for ${voteValue}% buyback`); - } catch (error) { - console.error('Vote failed', error); - setLoading(false); - setMessage('Vote failed, please try again.'); - } - }; - - // handle vote value change - const handleVoteChange = (e) => { - const value = parseInt(e.target.value); - if (Number.isNaN(value)) { - setVoteValue(1); - } else { - setVoteValue(Math.min(Math.max(value, 1), 100)); - } - }; - return (
-
-
-

Stake

-

- Xeon -

- - container - -
-
-

+

+
+

Stake your XEON tokens in just two simple steps.

-
-

+

+

Stake XEON tokens to be eligible for revenue sharing. The staking window opens for 3 days at the end of each epoch, at which time XEON can be staked or unstaked. Protocol revenue is deposited is deposited into the staking pool.

- container
-
-
- -
-
-
-
-

- Settle -

-

- Close expired positions and collect fess into the staking pool -

-
- +
+
+
+
+

+

+ {wallet?.address.slice(0, 6) + + "..." + + wallet?.address.slice(-4)} +

+
-
-
-
-
-

- $XEON Buyback -

-

- What percentage of protocol revenue should be used to buyback - $XEON token? -

-
- - - - - +
+

Epoch # {epoch}

+

{ethInPool} ETH in pool

+

+ $XEON Buyback: {buyBackPercentage}% +

+

+ Team Percentage: {teamPercentage}% +

-

- Current Buyback Percentage: {currentPercentage}% -

+
+ +
+ - - + + Vote Feedback - + {loading ? ( ) : ( )} diff --git a/src/components/staking/UserAssets.js b/src/components/staking/UserAssets.js index aa6e061..c03db8f 100644 --- a/src/components/staking/UserAssets.js +++ b/src/components/staking/UserAssets.js @@ -1,4 +1,4 @@ -'use client'; +"use client"; import { FormControl, FormLabel, @@ -12,41 +12,36 @@ import { ModalCloseButton, ModalFooter, useDisclosure, -} from '@chakra-ui/react'; -import { motion } from 'framer-motion'; -import { useState, useEffect, useMemo } from 'react'; -import { FaEthereum } from 'react-icons/fa'; -import AssetsValues from '../wallet/AssetsValues'; -import { useActiveAccount } from 'thirdweb/react'; -import { ethers } from 'ethers'; -import XeonStakingPoolABI from '@/abi/XeonStakingPool.abi.json'; -import { Constants } from '@/abi/constants'; -import BookmarkAdded from '../BookmarkAdded'; +} from "@chakra-ui/react"; +import {motion} from "framer-motion"; +import {useState, useEffect, useMemo} from "react"; + +import {ethers} from "ethers"; +import XeonStakingPoolABI from "@/abi/XeonStakingPool.abi.json"; +import {Constants} from "@/abi/constants"; +import BookmarkAdded from "../BookmarkAdded"; +import {useActiveAccount} from "thirdweb/react"; function UserAssets() { const [isSwitched, setIsSwitched] = useState(false); const [walletBalance, setWalletBalance] = useState(0); const [stakedBalance, setStakedBalance] = useState(0); - const [stakeAmount, setStakeAmount] = useState(''); + const [stakeAmount, setStakeAmount] = useState(""); const [isApproved, setIsApproved] = useState(false); - const [buttonText, setButtonText] = useState('APPROVE'); + const [buttonText, setButtonText] = useState("APPROVE"); const [loading, setLoading] = useState(false); - const [message, setMessage] = useState(''); - const [status, setStatus] = useState(''); - const [epoch, setEpoch] = useState('0.00'); // todo: app doesn't set epoch, only reads it (value is whole number integer) - const [ethInPool, setEthInPool] = useState('0.00'); - const [buyBackPercentage, setBuyBackPercentage] = useState('0.00'); - const [teamPercentage, setTeamPercentage] = useState('0.00'); - const [walletXeonBalance, setWalletXeonBalance] = useState('0.00'); // todo: display user's contract balance - const [stakedXeonBalance, setStakedXeonBalance] = useState('0.00'); // todo: display user's staked balance - const { isOpen, onOpen, onClose } = useDisclosure(); - const wallet = useActiveAccount(); - const connectedAddress = wallet?.address; + const [message, setMessage] = useState(""); + const [status, setStatus] = useState(""); + const {isOpen, onOpen, onClose} = useDisclosure(); const [provider, setProvider] = useState(null); const [signer, setSigner] = useState(null); + const wallet = useActiveAccount(); + const [voteValue, setVoteValue] = useState(5); // state for user buyback vote value + // todo: for mainnet, ensure currentPercentage is proper default + const [currentPercentage, setCurrentPercentage] = useState(5); // state for current buyback percentage useEffect(() => { - if (typeof window !== 'undefined' && window.ethereum) { + if (typeof window !== "undefined" && window.ethereum) { const web3Provider = new ethers.providers.Web3Provider(window.ethereum); const signer = web3Provider.getSigner(); setProvider(web3Provider); @@ -72,51 +67,6 @@ function UserAssets() { ); }, [provider, signer]); - const WETH = useMemo(() => { - if (!provider) return null; - return new ethers.Contract( - Constants.testnet.WETH, - XeonStakingPoolABI, - provider - ); - }, [provider]); - - useEffect(() => { - const fetchData = async () => { - try { - if (!XeonStakingPool || !WETH || !XeonToken || !connectedAddress) - return; - - const epoch = await XeonStakingPool.epoch(); - setEpoch(ethers.utils.formatUnits(epoch, 0)); - - const ethBalance = await WETH.balanceOf( - Constants.testnet.XeonStakingPool - ); - setEthInPool(ethers.utils.formatEther(ethBalance)); - - const buyBackPercentage = await XeonStakingPool.buyBackPercentage(); - setBuyBackPercentage(ethers.utils.formatUnits(buyBackPercentage, 0)); - - const teamPercentage = await XeonStakingPool.teamPercentage(); - setTeamPercentage(ethers.utils.formatUnits(teamPercentage, 0)); - - const xeonBalance = await XeonToken.balanceOf(connectedAddress); - setWalletXeonBalance(ethers.utils.formatEther(xeonBalance)); - - const stakedXeonBalance = - await XeonStakingPool.balanceOf(connectedAddress); - setStakedXeonBalance(ethers.utils.formatEther(stakedXeonBalance)); - } catch (error) { - console.error('Error fetching asset values:', error); - } - }; - - if (connectedAddress) { - fetchData(); - } - }, [connectedAddress, XeonStakingPool, XeonToken, WETH]); - useEffect(() => { if (wallet && XeonToken && XeonStakingPool) { XeonToken.balanceOf(wallet.address).then((balance) => { @@ -131,7 +81,7 @@ function UserAssets() { (allowance) => { if (ethers.utils.formatEther(allowance) > 0) { setIsApproved(true); - setButtonText('STAKE'); + setButtonText("STAKE"); } } ); @@ -145,6 +95,10 @@ function UserAssets() { const handleApprove = async () => { setLoading(true); try { + if (parseFloat(stakeAmount) > parseFloat(walletBalance)) { + throw new Error("Amount exceeds wallet balance"); + } + if (!isApproved && XeonToken) { const tx = await XeonToken.approve( XeonStakingPool.address, @@ -152,14 +106,14 @@ function UserAssets() { ); await tx.wait(); setIsApproved(true); - setButtonText('STAKE'); - setStatus('success'); - setMessage('Approval successful!'); + setButtonText("STAKE"); + setStatus("success"); + setMessage("Approval successful!"); } } catch (error) { - setStatus('error'); - setMessage('Approval failed.'); - console.error('Approval failed', error); + setStatus("error"); + setMessage(error.message || "Approval failed."); + console.error("Approval failed", error); } finally { setLoading(false); onOpen(); @@ -169,18 +123,22 @@ function UserAssets() { const handleStake = async () => { setLoading(true); try { + if (parseFloat(stakeAmount) > parseFloat(walletBalance)) { + throw new Error("Amount exceeds wallet balance"); + } + if (isApproved && XeonStakingPool) { const tx = await XeonStakingPool.stake( ethers.utils.parseEther(stakeAmount) ); await tx.wait(); - setStatus('success'); - setMessage('Stake successful!'); + setStatus("success"); + setMessage("Stake successful!"); } } catch (error) { - setStatus('error'); - setMessage('Staking failed.'); - console.error('Staking failed', error); + setStatus("error"); + setMessage(error.message || "Staking failed."); + console.error("Staking failed", error); } finally { setLoading(false); onOpen(); @@ -191,17 +149,17 @@ function UserAssets() { setLoading(true); try { if (parseFloat(stakeAmount) > parseFloat(stakedBalance)) { - throw new Error('Unstake amount exceeds staked balance'); + throw new Error("Unstake amount exceeds staked balance"); } const tx = await XeonStakingPool.unstake( ethers.utils.parseEther(stakeAmount) ); await tx.wait(); - setMessage('Unstake successful'); + setMessage("Unstake successful"); } catch (error) { - console.error('Unstaking failed', error); - setMessage(error.message || 'Unstaking failed'); + console.error("Unstaking failed", error); + setMessage(error.message || "Unstaking failed"); } finally { setLoading(false); onOpen(); @@ -220,125 +178,218 @@ function UserAssets() { const handleStakeAmountChange = (e) => { const value = e.target.value; - if (isSwitched && parseFloat(value) > parseFloat(stakedBalance)) { - alert('Unstake amount exceeds your staked balance'); - } else if (!isSwitched && parseFloat(value) > parseFloat(walletBalance)) { - alert('Amount exceeds wallet balance'); + setStakeAmount(value); + }; + // handle increment and decrement of vote value + // todo: for mainnet, ensure vote value is clamped to contract min/max + const handleIncrement = () => { + setVoteValue((prevValue) => Math.min(prevValue + 1, 100)); + }; + + const handleDecrement = () => { + setVoteValue((prevValue) => Math.max(prevValue - 1, 1)); + }; + + const handleVote = async () => { + if (!XeonStakingPool || voteValue < 1 || voteValue > 100) { + setMessage("Please enter a value between 1 and 100"); + return; + } + + setLoading(true); + onOpen(); + + try { + const tx = await XeonStakingPool.voteForBuybackPercentage(voteValue); + await tx.wait(); + setLoading(false); + setMessage(`Vote successful for ${voteValue}% buyback`); + } catch (error) { + console.error("Vote failed", error); + setLoading(false); + setMessage("Vote failed, please try again."); + } + }; + + // handle vote value change + const handleVoteChange = (e) => { + const value = parseInt(e.target.value); + if (Number.isNaN(value)) { + setVoteValue(1); } else { - setStakeAmount(value); + setVoteValue(Math.min(Math.max(value, 1), 100)); } }; return ( -
-
- - - - {isSwitched ? 'Unstaking mode' : 'Staking mode'} - - -
- {isSwitched ? 'Unstake Tokens' : 'Stake Tokens'} +
+
+
+ {isSwitched ? "Unstake Tokens" : "Stake Tokens"}
- {isSwitched ? ( - - -
- -
-
- ) : ( - -
- +
+ {isSwitched ? ( + +
+
+
+

+ Staked: $XEON {stakedBalance} +

+
-
- +
+

+ Wallet: $XEON {walletBalance} +

+
+
+
+ + +
+ + + + +
+
-
-
- )} -
+ + ) : ( + +
+
+
+

+ Staked: $XEON {stakedBalance} +

+
+ +
+

+ Wallet: $XEON {walletBalance} +

+
+
+
+ -
-
-
-
-

-

- {wallet?.address.slice(0, 6) + - '...' + - wallet?.address.slice(-4)} +

+ + + + +
+
+
+ + )} +
+
+
+
+
+

Settle

+
+

+ Close expired positions and collect fess into the staking pool

+
+ +
+
+

+ $XEON Buyback +

+
+

+ What percentage of protocol revenue should be used to buyback + $XEON token? +

+
+ -
-
- - - - -
-
-
-

Staked

-
-

$XEON {stakedBalance}

-
-
+ -
-

Wallet

-
-

$XEON {walletBalance}

-
+ +
+

+ Current Buyback Percentage: {currentPercentage}% +

@@ -346,12 +397,12 @@ function UserAssets() { - - - {status === 'success' ? 'Success' : 'Error'} + + + {status === "success" ? "Success" : "Error"} - + {loading ? ( ) : ( From 146aa2c05bfec3b28ad957763209ec595579b7df Mon Sep 17 00:00:00 2001 From: Jon Date: Wed, 18 Sep 2024 12:27:06 -0400 Subject: [PATCH 16/17] fix: update stake page to match design --- src/app/stake/page.js | 67 ++++++++--------- src/components/staking/UserAssets.js | 108 +++++++++++++-------------- 2 files changed, 84 insertions(+), 91 deletions(-) diff --git a/src/app/stake/page.js b/src/app/stake/page.js index 4ce7f05..2d1d30d 100644 --- a/src/app/stake/page.js +++ b/src/app/stake/page.js @@ -1,11 +1,11 @@ -"use client"; - -import {useEffect, useState, useMemo} from "react"; -import XeonStakingPoolABI from "@/abi/XeonStakingPool.abi.json"; -import {Constants} from "@/abi/constants"; -import Header from "@/components/Header"; -import UserAssets from "@/components/staking/UserAssets"; -import {ethers} from "ethers"; +'use client'; + +import { useEffect, useState, useMemo } from 'react'; +import XeonStakingPoolABI from '@/abi/XeonStakingPool.abi.json'; +import { Constants } from '@/abi/constants'; +import Header from '@/components/Header'; +import UserAssets from '@/components/staking/UserAssets'; +import { ethers } from 'ethers'; import { Modal, ModalOverlay, @@ -15,29 +15,29 @@ import { ModalFooter, Spinner, useDisclosure, -} from "@chakra-ui/react"; -import BookmarkAdded from "@/components/BookmarkAdded"; -import {useActiveAccount} from "thirdweb/react"; +} from '@chakra-ui/react'; +import BookmarkAdded from '@/components/BookmarkAdded'; +import { useActiveAccount } from 'thirdweb/react'; function Page() { const [loading, setLoading] = useState(false); - const [message, setMessage] = useState(""); - const [epoch, setEpoch] = useState("0.00"); // todo: app doesn't set epoch, only reads it (value is whole number integer) - const [ethInPool, setEthInPool] = useState("0.00"); - const [buyBackPercentage, setBuyBackPercentage] = useState("0.00"); - const [teamPercentage, setTeamPercentage] = useState("0.00"); - const [walletXeonBalance, setWalletXeonBalance] = useState("0.00"); // todo: display user's contract balance - const [stakedXeonBalance, setStakedXeonBalance] = useState("0.00"); // todo: display user's staked balance + const [message, setMessage] = useState(''); + const [epoch, setEpoch] = useState('0.00'); // todo: app doesn't set epoch, only reads it (value is whole number integer) + const [ethInPool, setEthInPool] = useState('0.00'); + const [buyBackPercentage, setBuyBackPercentage] = useState('0.00'); + const [teamPercentage, setTeamPercentage] = useState('0.00'); + const [walletXeonBalance, setWalletXeonBalance] = useState('0.00'); // todo: display user's contract balance + const [stakedXeonBalance, setStakedXeonBalance] = useState('0.00'); // todo: display user's staked balance const wallet = useActiveAccount(); const connectedAddress = wallet?.address; const [provider, setProvider] = useState(null); const [signer, setSigner] = useState(null); - const {isOpen, onOpen, onClose} = useDisclosure(); + const { isOpen, onOpen, onClose } = useDisclosure(); // init provider and signer useEffect(() => { const initializeProvider = async () => { - if (typeof window !== "undefined" && window.ethereum) { + if (typeof window !== 'undefined' && window.ethereum) { const web3Provider = new ethers.providers.Web3Provider(window.ethereum); const signer = web3Provider.getSigner(); setProvider(web3Provider); @@ -97,12 +97,11 @@ function Page() { const xeonBalance = await XeonToken.balanceOf(connectedAddress); setWalletXeonBalance(ethers.utils.formatEther(xeonBalance)); - const stakedXeonBalance = await XeonStakingPool.balanceOf( - connectedAddress - ); + const stakedXeonBalance = + await XeonStakingPool.balanceOf(connectedAddress); setStakedXeonBalance(ethers.utils.formatEther(stakedXeonBalance)); } catch (error) { - console.error("Error fetching asset values:", error); + console.error('Error fetching asset values:', error); } }; @@ -120,7 +119,7 @@ function Page() { const percentage = await XeonStakingPool.buyBackPercentage(); // assume integer value from contract setCurrentPercentage(percentage.toNumber()); // update state with value } catch (error) { - console.error("Error fetching buyback percentage:", error); + console.error('Error fetching buyback percentage:', error); } } }; @@ -145,26 +144,20 @@ function Page() {

-
+

-

- {wallet?.address.slice(0, 6) + - "..." + - wallet?.address.slice(-4)} -

-

Epoch # {epoch}

{ethInPool} ETH in pool

$XEON Buyback: {buyBackPercentage}%

-

+

Team Percentage: {teamPercentage}%

@@ -177,17 +170,17 @@ function Page() { - - + + Vote Feedback - + {loading ? ( ) : ( )} diff --git a/src/components/staking/UserAssets.js b/src/components/staking/UserAssets.js index c03db8f..36b3d7b 100644 --- a/src/components/staking/UserAssets.js +++ b/src/components/staking/UserAssets.js @@ -1,4 +1,4 @@ -"use client"; +'use client'; import { FormControl, FormLabel, @@ -12,27 +12,27 @@ import { ModalCloseButton, ModalFooter, useDisclosure, -} from "@chakra-ui/react"; -import {motion} from "framer-motion"; -import {useState, useEffect, useMemo} from "react"; +} from '@chakra-ui/react'; +import { motion } from 'framer-motion'; +import { useState, useEffect, useMemo } from 'react'; -import {ethers} from "ethers"; -import XeonStakingPoolABI from "@/abi/XeonStakingPool.abi.json"; -import {Constants} from "@/abi/constants"; -import BookmarkAdded from "../BookmarkAdded"; -import {useActiveAccount} from "thirdweb/react"; +import { ethers } from 'ethers'; +import XeonStakingPoolABI from '@/abi/XeonStakingPool.abi.json'; +import { Constants } from '@/abi/constants'; +import BookmarkAdded from '../BookmarkAdded'; +import { useActiveAccount } from 'thirdweb/react'; function UserAssets() { const [isSwitched, setIsSwitched] = useState(false); const [walletBalance, setWalletBalance] = useState(0); const [stakedBalance, setStakedBalance] = useState(0); - const [stakeAmount, setStakeAmount] = useState(""); + const [stakeAmount, setStakeAmount] = useState(''); const [isApproved, setIsApproved] = useState(false); - const [buttonText, setButtonText] = useState("APPROVE"); + const [buttonText, setButtonText] = useState('APPROVE'); const [loading, setLoading] = useState(false); - const [message, setMessage] = useState(""); - const [status, setStatus] = useState(""); - const {isOpen, onOpen, onClose} = useDisclosure(); + const [message, setMessage] = useState(''); + const [status, setStatus] = useState(''); + const { isOpen, onOpen, onClose } = useDisclosure(); const [provider, setProvider] = useState(null); const [signer, setSigner] = useState(null); const wallet = useActiveAccount(); @@ -41,7 +41,7 @@ function UserAssets() { const [currentPercentage, setCurrentPercentage] = useState(5); // state for current buyback percentage useEffect(() => { - if (typeof window !== "undefined" && window.ethereum) { + if (typeof window !== 'undefined' && window.ethereum) { const web3Provider = new ethers.providers.Web3Provider(window.ethereum); const signer = web3Provider.getSigner(); setProvider(web3Provider); @@ -81,7 +81,7 @@ function UserAssets() { (allowance) => { if (ethers.utils.formatEther(allowance) > 0) { setIsApproved(true); - setButtonText("STAKE"); + setButtonText('STAKE'); } } ); @@ -96,7 +96,7 @@ function UserAssets() { setLoading(true); try { if (parseFloat(stakeAmount) > parseFloat(walletBalance)) { - throw new Error("Amount exceeds wallet balance"); + throw new Error('Amount exceeds wallet balance'); } if (!isApproved && XeonToken) { @@ -106,14 +106,14 @@ function UserAssets() { ); await tx.wait(); setIsApproved(true); - setButtonText("STAKE"); - setStatus("success"); - setMessage("Approval successful!"); + setButtonText('STAKE'); + setStatus('success'); + setMessage('Approval successful!'); } } catch (error) { - setStatus("error"); - setMessage(error.message || "Approval failed."); - console.error("Approval failed", error); + setStatus('error'); + setMessage(error.message || 'Approval failed.'); + console.error('Approval failed', error); } finally { setLoading(false); onOpen(); @@ -124,7 +124,7 @@ function UserAssets() { setLoading(true); try { if (parseFloat(stakeAmount) > parseFloat(walletBalance)) { - throw new Error("Amount exceeds wallet balance"); + throw new Error('Amount exceeds wallet balance'); } if (isApproved && XeonStakingPool) { @@ -132,13 +132,13 @@ function UserAssets() { ethers.utils.parseEther(stakeAmount) ); await tx.wait(); - setStatus("success"); - setMessage("Stake successful!"); + setStatus('success'); + setMessage('Stake successful!'); } } catch (error) { - setStatus("error"); - setMessage(error.message || "Staking failed."); - console.error("Staking failed", error); + setStatus('error'); + setMessage(error.message || 'Staking failed.'); + console.error('Staking failed', error); } finally { setLoading(false); onOpen(); @@ -149,17 +149,17 @@ function UserAssets() { setLoading(true); try { if (parseFloat(stakeAmount) > parseFloat(stakedBalance)) { - throw new Error("Unstake amount exceeds staked balance"); + throw new Error('Unstake amount exceeds staked balance'); } const tx = await XeonStakingPool.unstake( ethers.utils.parseEther(stakeAmount) ); await tx.wait(); - setMessage("Unstake successful"); + setMessage('Unstake successful'); } catch (error) { - console.error("Unstaking failed", error); - setMessage(error.message || "Unstaking failed"); + console.error('Unstaking failed', error); + setMessage(error.message || 'Unstaking failed'); } finally { setLoading(false); onOpen(); @@ -192,7 +192,7 @@ function UserAssets() { const handleVote = async () => { if (!XeonStakingPool || voteValue < 1 || voteValue > 100) { - setMessage("Please enter a value between 1 and 100"); + setMessage('Please enter a value between 1 and 100'); return; } @@ -205,9 +205,9 @@ function UserAssets() { setLoading(false); setMessage(`Vote successful for ${voteValue}% buyback`); } catch (error) { - console.error("Vote failed", error); + console.error('Vote failed', error); setLoading(false); - setMessage("Vote failed, please try again."); + setMessage('Vote failed, please try again.'); } }; @@ -225,27 +225,27 @@ function UserAssets() {
- {isSwitched ? "Unstake Tokens" : "Stake Tokens"} + {isSwitched ? 'Unstake' : 'Stake'}
{isSwitched ? (

- Staked: $XEON {stakedBalance} + Staked: {stakedBalance} $XEON

- Wallet: $XEON {walletBalance} + Wallet: {walletBalance} $XEON

@@ -264,12 +264,12 @@ function UserAssets() { onClick={handleButtonClick} disabled={loading} > - {loading ? : "Unstake"} + {loading ? : 'Unstake'}

- Staked: $XEON {stakedBalance} + Staked: {stakedBalance} $XEON

- Wallet: $XEON {walletBalance} + Wallet: {walletBalance} $XEON

@@ -320,7 +320,7 @@ function UserAssets() { - - - {status === "success" ? "Success" : "Error"} + + + {status === 'success' ? 'Success' : 'Error'} - + {loading ? ( ) : ( From 58978e04ffee9103781127ef7c0c94eefc5de5bc Mon Sep 17 00:00:00 2001 From: rtj99 Date: Wed, 18 Sep 2024 23:30:10 +0200 Subject: [PATCH 17/17] style: fix alignment and padding issues on stake, settle and buyback boxes --- src/app/stake/page.js | 61 ++++---- src/components/staking/UserAssets.js | 223 +++++++++++++-------------- 2 files changed, 138 insertions(+), 146 deletions(-) diff --git a/src/app/stake/page.js b/src/app/stake/page.js index 2d1d30d..073f8c9 100644 --- a/src/app/stake/page.js +++ b/src/app/stake/page.js @@ -1,11 +1,11 @@ -'use client'; - -import { useEffect, useState, useMemo } from 'react'; -import XeonStakingPoolABI from '@/abi/XeonStakingPool.abi.json'; -import { Constants } from '@/abi/constants'; -import Header from '@/components/Header'; -import UserAssets from '@/components/staking/UserAssets'; -import { ethers } from 'ethers'; +"use client"; + +import {useEffect, useState, useMemo} from "react"; +import XeonStakingPoolABI from "@/abi/XeonStakingPool.abi.json"; +import {Constants} from "@/abi/constants"; +import Header from "@/components/Header"; +import UserAssets from "@/components/staking/UserAssets"; +import {ethers} from "ethers"; import { Modal, ModalOverlay, @@ -15,29 +15,29 @@ import { ModalFooter, Spinner, useDisclosure, -} from '@chakra-ui/react'; -import BookmarkAdded from '@/components/BookmarkAdded'; -import { useActiveAccount } from 'thirdweb/react'; +} from "@chakra-ui/react"; +import BookmarkAdded from "@/components/BookmarkAdded"; +import {useActiveAccount} from "thirdweb/react"; function Page() { const [loading, setLoading] = useState(false); - const [message, setMessage] = useState(''); - const [epoch, setEpoch] = useState('0.00'); // todo: app doesn't set epoch, only reads it (value is whole number integer) - const [ethInPool, setEthInPool] = useState('0.00'); - const [buyBackPercentage, setBuyBackPercentage] = useState('0.00'); - const [teamPercentage, setTeamPercentage] = useState('0.00'); - const [walletXeonBalance, setWalletXeonBalance] = useState('0.00'); // todo: display user's contract balance - const [stakedXeonBalance, setStakedXeonBalance] = useState('0.00'); // todo: display user's staked balance + const [message, setMessage] = useState(""); + const [epoch, setEpoch] = useState("0.00"); // todo: app doesn't set epoch, only reads it (value is whole number integer) + const [ethInPool, setEthInPool] = useState("0.00"); + const [buyBackPercentage, setBuyBackPercentage] = useState("0.00"); + const [teamPercentage, setTeamPercentage] = useState("0.00"); + const [walletXeonBalance, setWalletXeonBalance] = useState("0.00"); // todo: display user's contract balance + const [stakedXeonBalance, setStakedXeonBalance] = useState("0.00"); // todo: display user's staked balance const wallet = useActiveAccount(); const connectedAddress = wallet?.address; const [provider, setProvider] = useState(null); const [signer, setSigner] = useState(null); - const { isOpen, onOpen, onClose } = useDisclosure(); + const {isOpen, onOpen, onClose} = useDisclosure(); // init provider and signer useEffect(() => { const initializeProvider = async () => { - if (typeof window !== 'undefined' && window.ethereum) { + if (typeof window !== "undefined" && window.ethereum) { const web3Provider = new ethers.providers.Web3Provider(window.ethereum); const signer = web3Provider.getSigner(); setProvider(web3Provider); @@ -97,11 +97,12 @@ function Page() { const xeonBalance = await XeonToken.balanceOf(connectedAddress); setWalletXeonBalance(ethers.utils.formatEther(xeonBalance)); - const stakedXeonBalance = - await XeonStakingPool.balanceOf(connectedAddress); + const stakedXeonBalance = await XeonStakingPool.balanceOf( + connectedAddress + ); setStakedXeonBalance(ethers.utils.formatEther(stakedXeonBalance)); } catch (error) { - console.error('Error fetching asset values:', error); + console.error("Error fetching asset values:", error); } }; @@ -119,7 +120,7 @@ function Page() { const percentage = await XeonStakingPool.buyBackPercentage(); // assume integer value from contract setCurrentPercentage(percentage.toNumber()); // update state with value } catch (error) { - console.error('Error fetching buyback percentage:', error); + console.error("Error fetching buyback percentage:", error); } } }; @@ -131,12 +132,12 @@ function Page() {
-
+

Stake your XEON tokens in just two simple steps.

-

+

Stake XEON tokens to be eligible for revenue sharing. The staking window opens for 3 days at the end of each epoch, at which time XEON can be staked or unstaked. Protocol revenue is deposited is @@ -170,17 +171,17 @@ function Page() { - - + + Vote Feedback - + {loading ? ( ) : ( )} diff --git a/src/components/staking/UserAssets.js b/src/components/staking/UserAssets.js index 36b3d7b..1e0a693 100644 --- a/src/components/staking/UserAssets.js +++ b/src/components/staking/UserAssets.js @@ -1,4 +1,4 @@ -'use client'; +"use client"; import { FormControl, FormLabel, @@ -12,27 +12,27 @@ import { ModalCloseButton, ModalFooter, useDisclosure, -} from '@chakra-ui/react'; -import { motion } from 'framer-motion'; -import { useState, useEffect, useMemo } from 'react'; +} from "@chakra-ui/react"; +import {motion} from "framer-motion"; +import {useState, useEffect, useMemo} from "react"; -import { ethers } from 'ethers'; -import XeonStakingPoolABI from '@/abi/XeonStakingPool.abi.json'; -import { Constants } from '@/abi/constants'; -import BookmarkAdded from '../BookmarkAdded'; -import { useActiveAccount } from 'thirdweb/react'; +import {ethers} from "ethers"; +import XeonStakingPoolABI from "@/abi/XeonStakingPool.abi.json"; +import {Constants} from "@/abi/constants"; +import BookmarkAdded from "../BookmarkAdded"; +import {useActiveAccount} from "thirdweb/react"; function UserAssets() { const [isSwitched, setIsSwitched] = useState(false); const [walletBalance, setWalletBalance] = useState(0); const [stakedBalance, setStakedBalance] = useState(0); - const [stakeAmount, setStakeAmount] = useState(''); + const [stakeAmount, setStakeAmount] = useState(""); const [isApproved, setIsApproved] = useState(false); - const [buttonText, setButtonText] = useState('APPROVE'); + const [buttonText, setButtonText] = useState("APPROVE"); const [loading, setLoading] = useState(false); - const [message, setMessage] = useState(''); - const [status, setStatus] = useState(''); - const { isOpen, onOpen, onClose } = useDisclosure(); + const [message, setMessage] = useState(""); + const [status, setStatus] = useState(""); + const {isOpen, onOpen, onClose} = useDisclosure(); const [provider, setProvider] = useState(null); const [signer, setSigner] = useState(null); const wallet = useActiveAccount(); @@ -41,7 +41,7 @@ function UserAssets() { const [currentPercentage, setCurrentPercentage] = useState(5); // state for current buyback percentage useEffect(() => { - if (typeof window !== 'undefined' && window.ethereum) { + if (typeof window !== "undefined" && window.ethereum) { const web3Provider = new ethers.providers.Web3Provider(window.ethereum); const signer = web3Provider.getSigner(); setProvider(web3Provider); @@ -81,7 +81,7 @@ function UserAssets() { (allowance) => { if (ethers.utils.formatEther(allowance) > 0) { setIsApproved(true); - setButtonText('STAKE'); + setButtonText("STAKE"); } } ); @@ -96,7 +96,7 @@ function UserAssets() { setLoading(true); try { if (parseFloat(stakeAmount) > parseFloat(walletBalance)) { - throw new Error('Amount exceeds wallet balance'); + throw new Error("Amount exceeds wallet balance"); } if (!isApproved && XeonToken) { @@ -106,14 +106,14 @@ function UserAssets() { ); await tx.wait(); setIsApproved(true); - setButtonText('STAKE'); - setStatus('success'); - setMessage('Approval successful!'); + setButtonText("STAKE"); + setStatus("success"); + setMessage("Approval successful!"); } } catch (error) { - setStatus('error'); - setMessage(error.message || 'Approval failed.'); - console.error('Approval failed', error); + setStatus("error"); + setMessage(error.message || "Approval failed."); + console.error("Approval failed", error); } finally { setLoading(false); onOpen(); @@ -124,7 +124,7 @@ function UserAssets() { setLoading(true); try { if (parseFloat(stakeAmount) > parseFloat(walletBalance)) { - throw new Error('Amount exceeds wallet balance'); + throw new Error("Amount exceeds wallet balance"); } if (isApproved && XeonStakingPool) { @@ -132,13 +132,13 @@ function UserAssets() { ethers.utils.parseEther(stakeAmount) ); await tx.wait(); - setStatus('success'); - setMessage('Stake successful!'); + setStatus("success"); + setMessage("Stake successful!"); } } catch (error) { - setStatus('error'); - setMessage(error.message || 'Staking failed.'); - console.error('Staking failed', error); + setStatus("error"); + setMessage(error.message || "Staking failed."); + console.error("Staking failed", error); } finally { setLoading(false); onOpen(); @@ -149,17 +149,17 @@ function UserAssets() { setLoading(true); try { if (parseFloat(stakeAmount) > parseFloat(stakedBalance)) { - throw new Error('Unstake amount exceeds staked balance'); + throw new Error("Unstake amount exceeds staked balance"); } const tx = await XeonStakingPool.unstake( ethers.utils.parseEther(stakeAmount) ); await tx.wait(); - setMessage('Unstake successful'); + setMessage("Unstake successful"); } catch (error) { - console.error('Unstaking failed', error); - setMessage(error.message || 'Unstaking failed'); + console.error("Unstaking failed", error); + setMessage(error.message || "Unstaking failed"); } finally { setLoading(false); onOpen(); @@ -192,7 +192,7 @@ function UserAssets() { const handleVote = async () => { if (!XeonStakingPool || voteValue < 1 || voteValue > 100) { - setMessage('Please enter a value between 1 and 100'); + setMessage("Please enter a value between 1 and 100"); return; } @@ -205,9 +205,9 @@ function UserAssets() { setLoading(false); setMessage(`Vote successful for ${voteValue}% buyback`); } catch (error) { - console.error('Vote failed', error); + console.error("Vote failed", error); setLoading(false); - setMessage('Vote failed, please try again.'); + setMessage("Vote failed, please try again."); } }; @@ -222,18 +222,18 @@ function UserAssets() { }; return ( -

-
-
- {isSwitched ? 'Unstake' : 'Stake'} +
+
+
+ {isSwitched ? "Unstake" : "Stake"}
{isSwitched ? (
@@ -242,7 +242,6 @@ function UserAssets() { Staked: {stakedBalance} $XEON

-

Wallet: {walletBalance} $XEON @@ -257,19 +256,18 @@ function UserAssets() { onChange={handleStakeAmountChange} className="border-[1px] bg-[#71637f4d] mt-5 rounded-xl border-grey p-2 focus:outline-lime w-full" /> -

-

+

Staked: {stakedBalance} $XEON

-
-

+

Wallet: {walletBalance} $XEON

@@ -308,7 +305,6 @@ function UserAssets() { onChange={handleStakeAmountChange} className="border-[1px] bg-[#71637f4d] mt-5 rounded-xl border-grey p-2 focus:outline-lime w-full" /> -
-
-
-
-

Settle

-
-

- Close expired positions and collect fess into the staking pool -

-
- -
-
-
-
-

- $XEON Buyback -

-
-

- What percentage of protocol revenue should be used to buyback - $XEON token? -

-
- - - - - -
-

- Current Buyback Percentage: {currentPercentage}% -

+
+
+

Settle

+
+

+ Close expired positions and collect fees into the staking pool +

+
+
+
+

$XEON Buyback

+
+

+ What percentage of protocol revenue should be used to buyback $XEON + token? +

+
+ + + + +
+

+ Current Buyback Percentage: {currentPercentage}% +

+
+
- - - {status === 'success' ? 'Success' : 'Error'} + + + {status === "success" ? "Success" : "Error"} - + {loading ? ( ) : (