diff --git a/src/app/stake/page.js b/src/app/stake/page.js index ceca21d..073f8c9 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,60 @@ 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 -

-
- +
+
+
+
+

+
-
-
-
-
-

- $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..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,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,138 +178,222 @@ 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" : "Stake"}
- {isSwitched ? ( - - -
- -
-
- ) : ( - -
- - -
- -
-
-
- )} -
- -
-
-
-
-

-

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

-
-
- -
-
- - - - -
-
-
-

Staked

-
-

$XEON {stakedBalance}

+
+ {isSwitched ? ( + +
+
+
+

+ Staked: {stakedBalance} $XEON +

+
+
+

+ Wallet: {walletBalance} $XEON +

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

Wallet

-
-

$XEON {walletBalance}

+ + ) : ( + +
+
+
+

+ Staked: {stakedBalance} $XEON +

+
+
+

+ Wallet: {walletBalance} $XEON +

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

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 ? ( ) : (