diff --git a/src/app/stake/page.js b/src/app/stake/page.js index ceca21d..2d1d30d 100644 --- a/src/app/stake/page.js +++ b/src/app/stake/page.js @@ -1,9 +1,6 @@ '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'; @@ -20,13 +17,19 @@ import { useDisclosure, } 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 [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(); @@ -55,6 +58,58 @@ 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 () => { @@ -72,157 +127,47 @@ 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}% -

+
+ +
+ diff --git a/src/components/staking/UserAssets.js b/src/components/staking/UserAssets.js index aa6e061..36b3d7b 100644 --- a/src/components/staking/UserAssets.js +++ b/src/components/staking/UserAssets.js @@ -15,13 +15,12 @@ import { } 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'; +import { useActiveAccount } from 'thirdweb/react'; function UserAssets() { const [isSwitched, setIsSwitched] = useState(false); @@ -33,17 +32,13 @@ function UserAssets() { 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 [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) { @@ -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) => { @@ -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, @@ -158,7 +112,7 @@ function UserAssets() { } } catch (error) { setStatus('error'); - setMessage('Approval failed.'); + setMessage(error.message || 'Approval failed.'); console.error('Approval failed', error); } finally { setLoading(false); @@ -169,6 +123,10 @@ 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) @@ -179,7 +137,7 @@ function UserAssets() { } } catch (error) { setStatus('error'); - setMessage('Staking failed.'); + setMessage(error.message || 'Staking failed.'); console.error('Staking failed', error); } finally { setLoading(false); @@ -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' : 'Stake'}
- {isSwitched ? ( - - -
- -
-
- ) : ( - -
- +
+ {isSwitched ? ( + +
+
+
+

+ Staked: {stakedBalance} $XEON +

+
-
- +
+

+ Wallet: {walletBalance} $XEON +

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

+ Staked: {stakedBalance} $XEON +

+
+ +
+

+ Wallet: {walletBalance} $XEON +

+
+
+
+ -
-
-
-
-

-

- {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}% +