From dde667e7e1e9ed51815cb89f04e7dbfbe4a4cbb9 Mon Sep 17 00:00:00 2001 From: asalef10 Date: Tue, 22 Apr 2025 11:21:55 +0300 Subject: [PATCH 01/18] remove "Staked Amount" --- src/pages/stakingPage/components/RewardsCard.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/pages/stakingPage/components/RewardsCard.tsx b/src/pages/stakingPage/components/RewardsCard.tsx index 3012995..21649ea 100644 --- a/src/pages/stakingPage/components/RewardsCard.tsx +++ b/src/pages/stakingPage/components/RewardsCard.tsx @@ -120,7 +120,7 @@ export const RewardsCard = ({ data, refetchData }: RewardsCardProps): JSX.Elemen ≤{(0.0001).toFixed(4)} WETH (≤${(0.01).toFixed(2)}) - +{/* @@ -131,7 +131,7 @@ export const RewardsCard = ({ data, refetchData }: RewardsCardProps): JSX.Elemen {data?.stakedAmount ? Number(data.stakedAmount).toFixed(4) : '0.0000'} GRIX - + */} From 568c4ab406e86330dcfb30f051dc0711d009caa6 Mon Sep 17 00:00:00 2001 From: asalef10 Date: Tue, 22 Apr 2025 11:22:08 +0300 Subject: [PATCH 02/18] * Remove "Staked Amount" --- src/pages/stakingPage/components/RewardsCard.tsx | 13 +------------ 1 file changed, 1 insertion(+), 12 deletions(-) diff --git a/src/pages/stakingPage/components/RewardsCard.tsx b/src/pages/stakingPage/components/RewardsCard.tsx index 21649ea..41c0642 100644 --- a/src/pages/stakingPage/components/RewardsCard.tsx +++ b/src/pages/stakingPage/components/RewardsCard.tsx @@ -120,18 +120,7 @@ export const RewardsCard = ({ data, refetchData }: RewardsCardProps): JSX.Elemen ≤{(0.0001).toFixed(4)} WETH (≤${(0.01).toFixed(2)}) -{/* - - - - - Staked Amount - - - - {data?.stakedAmount ? Number(data.stakedAmount).toFixed(4) : '0.0000'} GRIX - - */} + From 90a0791970d5d20051a03f4a8ce8b0923f3255a8 Mon Sep 17 00:00:00 2001 From: asalef10 Date: Tue, 22 Apr 2025 16:13:42 +0300 Subject: [PATCH 03/18] Add new model --- .../stakingPage/components/RewardsCard.tsx | 1 - .../stakingPage/components/VestingCard.tsx | 234 +++++++------- .../VestingComponents/VestingStats.tsx | 184 +++++++---- .../stakingPage/components/VestingModal.tsx | 291 ++++++++++++++++++ src/pages/stakingPage/hooks/useVesting.ts | 84 +++++ src/pages/stakingPage/index.tsx | 2 +- src/web3Config/staking/hooks.ts | 34 +- 7 files changed, 624 insertions(+), 206 deletions(-) create mode 100644 src/pages/stakingPage/components/VestingModal.tsx create mode 100644 src/pages/stakingPage/hooks/useVesting.ts diff --git a/src/pages/stakingPage/components/RewardsCard.tsx b/src/pages/stakingPage/components/RewardsCard.tsx index 41c0642..fcce7c6 100644 --- a/src/pages/stakingPage/components/RewardsCard.tsx +++ b/src/pages/stakingPage/components/RewardsCard.tsx @@ -120,7 +120,6 @@ export const RewardsCard = ({ data, refetchData }: RewardsCardProps): JSX.Elemen ≤{(0.0001).toFixed(4)} WETH (≤${(0.01).toFixed(2)}) - diff --git a/src/pages/stakingPage/components/VestingCard.tsx b/src/pages/stakingPage/components/VestingCard.tsx index 23d7922..2f8686e 100644 --- a/src/pages/stakingPage/components/VestingCard.tsx +++ b/src/pages/stakingPage/components/VestingCard.tsx @@ -1,4 +1,14 @@ -import { Box, Button, HStack, Input, InputGroup, InputRightElement, useToast, VStack } from '@chakra-ui/react'; +import { + Box, + Button, + HStack, + Input, + InputGroup, + InputRightElement, + useToast, + VStack, + useDisclosure, +} from '@chakra-ui/react'; import React, { useCallback, useEffect, useState } from 'react'; import { parseEther } from 'viem'; import { useAccount } from 'wagmi'; @@ -18,16 +28,25 @@ import { import { VestingHeader } from './VestingComponents/VestingHeader'; import { VestingInfo } from './VestingComponents/VestingInfo'; import { VestingStats } from './VestingComponents/VestingStats'; +import { VestingModal } from './VestingModal'; type VestingCardProps = { onActionComplete?: () => void; + userRewardData?: { + claimable: string; + stakedAmount: string; + cumulativeRewards: string; + averageStaked: string; + } | null; }; -export const VestingCard: React.FC = ({ onActionComplete }) => { +export const VestingCard: React.FC = ({ onActionComplete, userRewardData }) => { + const { isOpen, onOpen, onClose } = useDisclosure(); const { address } = useAccount(); const toast = useToast(); const [amount, setAmount] = useState(''); const [esGrixBalance, setEsGrixBalance] = useState('0'); + const [grixBalance, setGrixBalance] = useState('0'); const [isApproving, setIsApproving] = useState(false); const [isVesting, setIsVesting] = useState(false); const [needsApproval, setNeedsApproval] = useState(true); @@ -60,16 +79,16 @@ export const VestingCard: React.FC = ({ onActionComplete }) => ); const checkAllowance = useCallback(async () => { - if (!address || !amount) return; + if (!address) return; try { const allowance = await checkVestingAllowance(address, stakingContracts.esGRIXToken.address); - const amountBigInt = parseEther(amount); - setNeedsApproval(allowance < amountBigInt); + setNeedsApproval(allowance === 0n); } catch (error) { + console.error('Error checking allowance:', error); setNeedsApproval(true); } - }, [address, amount]); + }, [address]); useEffect(() => { void fetchVestingData(); @@ -81,6 +100,12 @@ export const VestingCard: React.FC = ({ onActionComplete }) => void checkAllowance(); }, [checkAllowance]); + useEffect(() => { + if (!isApproving) { + void checkAllowance(); + } + }, [isApproving, checkAllowance]); + const fetchBalance = useCallback(async () => { if (!address) return; const balance = await getTokenBalance(stakingContracts.esGRIXToken.address, address); @@ -93,56 +118,61 @@ export const VestingCard: React.FC = ({ onActionComplete }) => return () => clearInterval(interval); }, [fetchBalance]); - const handleAction = useCallback( - async (action: 'approve' | 'vest' | 'withdraw') => { - if (!address || (!amount && action !== 'withdraw')) return; + const fetchGrixBalance = useCallback(async () => { + if (!address) return; + const balance = await getTokenBalance(stakingContracts.grixToken.address, address); + setGrixBalance(balance); + }, [address]); - const actions = { - approve: async () => { + useEffect(() => { + void fetchGrixBalance(); + const interval = setInterval(() => void fetchGrixBalance(), 15000); + return () => clearInterval(interval); + }, [fetchGrixBalance]); + + const handleVest = useCallback( + async (vestAmount: string) => { + if (!address) return; + setAmount(vestAmount); + + try { + if (needsApproval) { setIsApproving(true); - await approveVesting(stakingContracts.esGRIXToken.address, parseEther(amount)); - setNeedsApproval(false); - }, - vest: async () => { + await approveVesting(stakingContracts.esGRIXToken.address, parseEther(vestAmount)); + await checkAllowance(); + } else { setIsVesting(true); - await vestEsGrix(parseEther(amount)); + await vestEsGrix(parseEther(vestAmount)); setAmount(''); - }, - withdraw: async () => { - setIsWithdrawing(true); - await withdrawEsGrix(); - }, - }; - - try { - await actions[action](); - await Promise.all([fetchBalance(), fetchVestingData(true)]); - - if (onActionComplete) { - onActionComplete(); } + await Promise.all([fetchBalance(), fetchVestingData(true), fetchGrixBalance()]); + toast({ - title: `${action.charAt(0).toUpperCase() + action.slice(1)} Successful`, + title: needsApproval ? 'Approval Successful' : 'Vesting Successful', status: 'success', duration: 5000, isClosable: true, }); + + if (!needsApproval) { + onClose(); + } } catch (error) { + console.error('Error in handleVest:', error); toast({ - title: `${action.charAt(0).toUpperCase() + action.slice(1)} Failed`, - description: `There was an error during the ${action} process`, + title: needsApproval ? 'Approval Failed' : 'Vesting Failed', + description: `There was an error during the ${needsApproval ? 'approval' : 'vesting'} process`, status: 'error', duration: 5000, isClosable: true, }); } finally { - if (action === 'approve') setIsApproving(false); - if (action === 'vest') setIsVesting(false); - if (action === 'withdraw') setIsWithdrawing(false); + setIsApproving(false); + setIsVesting(false); } }, - [address, amount, fetchVestingData, toast, fetchBalance, onActionComplete] + [address, needsApproval, fetchBalance, fetchVestingData, fetchGrixBalance, toast, onClose, checkAllowance] ); const handleMaxClick = useCallback(async () => { @@ -181,6 +211,33 @@ export const VestingCard: React.FC = ({ onActionComplete }) => }; }, [lastVestingTime, vestingDuration, vestingData]); + const handleWithdraw = useCallback(async () => { + if (!address) return; + + try { + setIsWithdrawing(true); + await withdrawEsGrix(); + await Promise.all([fetchBalance(), fetchVestingData(true), fetchGrixBalance()]); + + toast({ + title: 'Withdrawal Successful', + status: 'success', + duration: 5000, + isClosable: true, + }); + } catch (error) { + toast({ + title: 'Withdrawal Failed', + description: 'There was an error during the withdrawal process', + status: 'error', + duration: 5000, + isClosable: true, + }); + } finally { + setIsWithdrawing(false); + } + }, [address, fetchBalance, fetchVestingData, fetchGrixBalance, toast]); + return ( = ({ onActionComplete }) => vestingProgress: calculateVestingProgress(), } } + onVestClick={onOpen} + isVesting={isVesting || isApproving} + needsApproval={needsApproval} + onWithdraw={handleWithdraw} + isWithdrawing={isWithdrawing} + /> + + - - - - - - - - - - - {needsApproval && amount ? ( - - ) : ( - - )} - - - - + ); diff --git a/src/pages/stakingPage/components/VestingComponents/VestingStats.tsx b/src/pages/stakingPage/components/VestingComponents/VestingStats.tsx index dd4811b..d0460fe 100644 --- a/src/pages/stakingPage/components/VestingComponents/VestingStats.tsx +++ b/src/pages/stakingPage/components/VestingComponents/VestingStats.tsx @@ -1,4 +1,4 @@ -import { Grid, GridItem, Progress, Text, VStack } from '@chakra-ui/react'; +import { Grid, GridItem, Progress, Text, VStack, Button, HStack } from '@chakra-ui/react'; import React from 'react'; import { formatBalance } from '../../utils/formatters'; @@ -19,78 +19,128 @@ type VestingStatsProps = { lastVestingTime?: bigint; vestingProgress?: VestingProgress; } | null; + onVestClick: () => void; + isVesting: boolean; + needsApproval: boolean; + onWithdraw: () => void; + isWithdrawing: boolean; }; -export const VestingStats: React.FC = ({ vestingData }) => ( - - - - - - - - {vestingData ? formatBalance(vestingData.claimable) : '0.0000'} - - - +export const VestingStats: React.FC = ({ + vestingData, + onVestClick, + isVesting, + needsApproval, + onWithdraw, + isWithdrawing, +}) => ( + + + + + + + + + {vestingData ? formatBalance(vestingData.claimable) : '0.0000'} + + + - - - - - - - {vestingData ? formatBalance(vestingData.totalVested) : '0.0000'} - - - + + + + + + + {vestingData ? formatBalance(vestingData.totalVested) : '0.0000'} + + + - - - - - - - {vestingData?.esGrixBalance ? formatBalance(vestingData.esGrixBalance) : '0.0000'} - - - + + + + + + + {vestingData?.esGrixBalance ? formatBalance(vestingData.esGrixBalance) : '0.0000'} + + + - - - - - - - {vestingData ? formatBalance(vestingData.maxVestableAmount) : '0.0000'} - - - + + + + + + + {vestingData ? formatBalance(vestingData.maxVestableAmount) : '0.0000'} + + + - - - - Vesting Progress - - {vestingData?.vestingProgress?.isVesting ? ( - <> - + + + + Vesting Progress + + {vestingData?.vestingProgress?.isVesting ? ( + <> + + + {vestingData.vestingProgress.remainingDays} days remaining + + + ) : ( - {vestingData.vestingProgress.remainingDays} days remaining + No active vesting - - ) : ( - - No active vesting - - )} - - - + )} + + + + + + + + + + ); diff --git a/src/pages/stakingPage/components/VestingModal.tsx b/src/pages/stakingPage/components/VestingModal.tsx new file mode 100644 index 0000000..064c54d --- /dev/null +++ b/src/pages/stakingPage/components/VestingModal.tsx @@ -0,0 +1,291 @@ +import { + Box, + Button, + HStack, + Text, + Modal, + ModalOverlay, + ModalContent, + ModalHeader, + ModalCloseButton, + Input, + VStack, + useToast, + Icon, + Flex, +} from '@chakra-ui/react'; +import { useCallback, useEffect, useState } from 'react'; +import { FaChevronDown, FaPlus, FaEquals } from 'react-icons/fa'; +import { getVestingDuration } from '@/web3Config/staking/hooks'; + +type VestingModalProps = { + isOpen: boolean; + onClose: () => void; + esGrixBalance: string; + grixBalance: string; + vestingAllowance: string; + isLoading: boolean; + onVest: (amount: string) => Promise; + claimableRewards: string; +}; + +type VestingProjection = { + days: number; + amount: string; +}; + +type DexScreenerResponse = { + grix: { + usd: number; + }; +}; + +export const VestingModal = ({ + isOpen, + onClose, + esGrixBalance, + grixBalance, + vestingAllowance, + isLoading, + onVest, + claimableRewards, +}: VestingModalProps): JSX.Element => { + const [amount, setAmount] = useState(''); + const [vestingDuration, setVestingDuration] = useState(null); + const [grixPrice, setGrixPrice] = useState(null); + const toast = useToast(); + + // Fetch vesting duration when modal opens + useEffect(() => { + const fetchVestingDuration = async () => { + const duration = await getVestingDuration(); + setVestingDuration(duration); + }; + if (isOpen) { + void fetchVestingDuration(); + } + }, [isOpen]); + + // Fetch GRIX price from CoinGecko + useEffect(() => { + const fetchPrice = async () => { + try { + const res = await fetch('https://api.coingecko.com/api/v3/simple/price?ids=grix&vs_currencies=usd'); + const json = (await res.json()) as DexScreenerResponse; + const price = json.grix.usd; + setGrixPrice(price); + } catch { + setGrixPrice(null); + } + }; + void fetchPrice(); + }, []); + + // Calculate expected GRIX at different time periods + const calculateVestingProjections = useCallback((inputAmount: string): VestingProjection[] => { + if (!inputAmount || Number(inputAmount) <= 0) return []; + + const amount = Number(inputAmount); + const oneDay = 24 * 60 * 60; // seconds in a day + const totalDays = 30; // Default to 30 days if vestingDuration not available + + return [ + { + days: 1, + amount: ((amount * oneDay) / (totalDays * oneDay)).toFixed(3), + }, + { + days: 15, + amount: ((amount * 15) / totalDays).toFixed(3), + }, + { + days: totalDays, + amount: amount.toFixed(3), + }, + ]; + }, []); + + const formatUsdValue = (tokenAmount: string | number) => { + if (!grixPrice) return ''; + const amount = Number(tokenAmount); + return `($${(amount * grixPrice).toFixed(2)})`; + }; + + const handleVest = async () => { + if (!amount || Number(amount) <= 0) { + toast({ + title: 'Invalid amount', + description: 'Please enter a valid amount to vest', + status: 'error', + duration: 3000, + isClosable: true, + }); + return; + } + + if (Number(amount) > Number(esGrixBalance)) { + toast({ + title: 'Insufficient balance', + description: 'You do not have enough esGRIX to vest this amount', + status: 'error', + duration: 3000, + isClosable: true, + }); + return; + } + + try { + await onVest(amount); + setAmount(''); + onClose(); + toast({ + title: 'Success', + description: 'Successfully vested esGRIX', + status: 'success', + duration: 3000, + isClosable: true, + }); + } catch (error) { + toast({ + title: 'Error', + description: 'Failed to vest esGRIX. Please try again.', + status: 'error', + duration: 3000, + isClosable: true, + }); + } + }; + + const handleSetMaxAmount = () => { + setAmount(esGrixBalance); + }; + + const vestingProjections = calculateVestingProjections(amount); + + return ( + + + + + + Vesting + + + + + + + {/* Token Selection */} + + + + esGRIX + + + Enter Amount + + + + {/* Input Field */} + setAmount(e.target.value)} + placeholder="Enter Amount" + variant="unstyled" + textAlign="left" + color="white" + fontSize="lg" + p={3} + bg="#1A1A1A" + borderRadius="md" + _placeholder={{ color: 'gray.500' }} + /> + + {/* Vestable Balance */} + + + Vestable Balance + + + {Number(esGrixBalance).toFixed(4)} esGRIX + + + + {/* Available to Reserve Section */} + + + Available to Reserve for Vesting + + + + + GRIX tokens + + + + {Number(grixBalance).toFixed(4)} GRIX {formatUsdValue(grixBalance)} + + + + + + + esGRIX tokens + + + + {Number(claimableRewards).toFixed(4)} esGRIX {formatUsdValue(claimableRewards)} + + + + + + + + Total + + + + {(Number(grixBalance) + Number(claimableRewards)).toFixed(4)} Tokens{' '} + {formatUsdValue(Number(grixBalance) + Number(claimableRewards))} + + + + + + + + + Reserving + + + {amount ? `${Number(amount).toFixed(4)} Token ${formatUsdValue(amount)}` : '-'} + + + + + + + {/* Vest Button */} + + + + + + ); +}; diff --git a/src/pages/stakingPage/hooks/useVesting.ts b/src/pages/stakingPage/hooks/useVesting.ts new file mode 100644 index 0000000..afb9a24 --- /dev/null +++ b/src/pages/stakingPage/hooks/useVesting.ts @@ -0,0 +1,84 @@ +import { useCallback, useEffect, useState } from 'react'; +import { useAccount } from 'wagmi'; +import { parseEther } from 'viem'; + +import { + checkVestingAllowance, + approveVesting, + vestEsGs, + getTokenBalance, + getVestingData, +} from '@/web3Config/staking/hooks'; +import { stakingContracts } from '@/web3Config/staking/config'; + +export const useVesting = () => { + const { address } = useAccount(); + const [isVestingModalOpen, setIsVestingModalOpen] = useState(false); + const [vestingAllowance, setVestingAllowance] = useState('0'); + const [esGrixBalance, setEsGrixBalance] = useState('0'); + const [grixBalance, setGrixBalance] = useState('0'); + const [vestingData, setVestingData] = useState(null); + const [isLoading, setIsLoading] = useState(false); + + const fetchVestingData = useCallback(async () => { + if (!address) return; + + try { + const [allowance, esGrixBal, grixBal, vesting] = await Promise.all([ + checkVestingAllowance(address, stakingContracts.esGRIXToken.address), + getTokenBalance(stakingContracts.esGRIXToken.address, address), + getTokenBalance(stakingContracts.grixToken.address, address), + getVestingData(address as `0x${string}`), + ]); + + setVestingAllowance(allowance.toString()); + setEsGrixBalance(esGrixBal); + setGrixBalance(grixBal); + setVestingData(vesting); + } catch (error) { + console.error('Error fetching vesting data:', error); + } + }, [address]); + + useEffect(() => { + void fetchVestingData(); + }, [fetchVestingData]); + + const handleVest = async (amount: string) => { + if (!address || !amount) return; + + setIsLoading(true); + try { + const parsedAmount = parseEther(amount); + + // Check if approval is needed + if (BigInt(vestingAllowance) < parsedAmount) { + const approveTx = await approveVesting(stakingContracts.esGRIXToken.address, parsedAmount); + // await approveTx.wait(); + } + + // Perform vesting + const vestTx = await vestEsGs(parsedAmount); + // await vestTx.wait(); + + // Refresh data + await fetchVestingData(); + } catch (error) { + console.error('Error vesting:', error); + } finally { + setIsLoading(false); + } + }; + + return { + isVestingModalOpen, + setIsVestingModalOpen, + vestingAllowance, + esGrixBalance, + grixBalance, + vestingData, + isLoading, + handleVest, + fetchVestingData, + }; +}; diff --git a/src/pages/stakingPage/index.tsx b/src/pages/stakingPage/index.tsx index 58ca768..82f1d8e 100644 --- a/src/pages/stakingPage/index.tsx +++ b/src/pages/stakingPage/index.tsx @@ -127,7 +127,7 @@ export const StakingPage: React.FC = () => { Vesting - + diff --git a/src/web3Config/staking/hooks.ts b/src/web3Config/staking/hooks.ts index f66ebef..8a28190 100644 --- a/src/web3Config/staking/hooks.ts +++ b/src/web3Config/staking/hooks.ts @@ -106,7 +106,15 @@ export const getCoreTracker = async () => vester: string; loanVester: string; }; - +const getCumulativeRewards = async (address: string) => { + const cumulativeRewards = await readContract(wagmiConfig, { + abi: rewardTrackerAbi, + address: normalizeAddress(stakingContracts.rewardTracker.address), + functionName: 'cumulativeRewards', + args: [address], + }); + return cumulativeRewards; +}; // Get user reward tracker data export const getUserRewardTrackerData = async (address: string) => { if (!address) return null; @@ -126,12 +134,7 @@ export const getUserRewardTrackerData = async (address: string) => { args: [address], }); - const cumulativeRewards = await readContract(wagmiConfig, { - abi: rewardTrackerAbi, - address: normalizeAddress(stakingContracts.rewardTracker.address), - functionName: 'cumulativeRewards', - args: [address], - }); + const cumulativeRewards = await getCumulativeRewards(address); const averageStaked = await readContract(wagmiConfig, { abi: rewardTrackerAbi, @@ -295,16 +298,21 @@ export const approveVesting = async (token: string, amount: bigint) => args: [normalizeAddress(stakingContracts.vester.address), amount], }); +const getClaimable = async (address: `0x${string}`) => { + const claimable = await readContract(wagmiConfig, { + abi: vesterAbi, + address: normalizeAddress(stakingContracts.vester.address), + functionName: 'claimable', + args: [address], + }); + return claimable; +}; + // Get vesting data export const getVestingData = async (address: `0x${string}`) => { try { const [claimable, totalVested, maxVestableAmount] = await Promise.all([ - readContract(wagmiConfig, { - abi: vesterAbi, - address: normalizeAddress(stakingContracts.vester.address), - functionName: 'claimable', - args: [address], - }), + getClaimable(address), readContract(wagmiConfig, { abi: vesterAbi, address: normalizeAddress(stakingContracts.vester.address), From 08aa78f2b5cb6e6b0e739985d1e039c5172730e6 Mon Sep 17 00:00:00 2001 From: asalef10 Date: Tue, 22 Apr 2025 16:27:21 +0300 Subject: [PATCH 04/18] v2 --- .../stakingPage/components/VestingModal.tsx | 60 ++++++++++--------- 1 file changed, 32 insertions(+), 28 deletions(-) diff --git a/src/pages/stakingPage/components/VestingModal.tsx b/src/pages/stakingPage/components/VestingModal.tsx index 064c54d..f26119a 100644 --- a/src/pages/stakingPage/components/VestingModal.tsx +++ b/src/pages/stakingPage/components/VestingModal.tsx @@ -17,6 +17,7 @@ import { import { useCallback, useEffect, useState } from 'react'; import { FaChevronDown, FaPlus, FaEquals } from 'react-icons/fa'; import { getVestingDuration } from '@/web3Config/staking/hooks'; +import { GrixLogo } from '@/components/commons/Logo'; type VestingModalProps = { isOpen: boolean; @@ -175,38 +176,41 @@ export const VestingModal = ({ - {/* Token Selection */} - + - + esGRIX - - Enter Amount - + setAmount(e.target.value)} + placeholder="0.0" + variant="unstyled" + color="white" + fontSize="md" + width="auto" + textAlign="right" + sx={{ + '::placeholder': { + color: 'gray.400', + opacity: 1, + }, + }} + /> - {/* Input Field */} - setAmount(e.target.value)} - placeholder="Enter Amount" - variant="unstyled" - textAlign="left" - color="white" - fontSize="lg" - p={3} - bg="#1A1A1A" - borderRadius="md" - _placeholder={{ color: 'gray.500' }} - /> - - {/* Vestable Balance */} - + Vestable Balance - + {Number(esGrixBalance).toFixed(4)} esGRIX @@ -259,7 +263,7 @@ export const VestingModal = ({ Reserving - {amount ? `${Number(amount).toFixed(4)} Token ${formatUsdValue(amount)}` : '-'} + {amount ? `${Number(amount).toFixed(2)} Token ${formatUsdValue(amount)}` : '-'} @@ -271,15 +275,15 @@ export const VestingModal = ({ onClick={() => void handleVest()} isLoading={isLoading} loadingText="Vesting" - bg="blue.500" + bg="teal.400" color="white" size="lg" width="full" height="40px" fontSize="sm" isDisabled={!amount || Number(amount) <= 0 || Number(amount) > Number(esGrixBalance)} - _hover={{ bg: 'blue.600' }} - _active={{ bg: 'blue.700' }} + _hover={{ bg: 'teal.500' }} + _active={{ bg: 'teal.600' }} > Vest From 4d78c790d43c7d0ac7b405572821b5355c6fbf16 Mon Sep 17 00:00:00 2001 From: asalef10 Date: Tue, 22 Apr 2025 16:31:00 +0300 Subject: [PATCH 05/18] lint --- .../stakingPage/components/VestingCard.tsx | 2 +- .../VestingComponents/VestingStats.tsx | 2 +- .../stakingPage/components/VestingModal.tsx | 19 ++++++++++--------- src/pages/stakingPage/hooks/useVesting.ts | 10 +++++----- 4 files changed, 17 insertions(+), 16 deletions(-) diff --git a/src/pages/stakingPage/components/VestingCard.tsx b/src/pages/stakingPage/components/VestingCard.tsx index 2f8686e..1ddb808 100644 --- a/src/pages/stakingPage/components/VestingCard.tsx +++ b/src/pages/stakingPage/components/VestingCard.tsx @@ -5,9 +5,9 @@ import { Input, InputGroup, InputRightElement, + useDisclosure, useToast, VStack, - useDisclosure, } from '@chakra-ui/react'; import React, { useCallback, useEffect, useState } from 'react'; import { parseEther } from 'viem'; diff --git a/src/pages/stakingPage/components/VestingComponents/VestingStats.tsx b/src/pages/stakingPage/components/VestingComponents/VestingStats.tsx index d0460fe..0719bee 100644 --- a/src/pages/stakingPage/components/VestingComponents/VestingStats.tsx +++ b/src/pages/stakingPage/components/VestingComponents/VestingStats.tsx @@ -1,4 +1,4 @@ -import { Grid, GridItem, Progress, Text, VStack, Button, HStack } from '@chakra-ui/react'; +import { Button, Grid, GridItem, HStack,Progress, Text, VStack } from '@chakra-ui/react'; import React from 'react'; import { formatBalance } from '../../utils/formatters'; diff --git a/src/pages/stakingPage/components/VestingModal.tsx b/src/pages/stakingPage/components/VestingModal.tsx index f26119a..3875fae 100644 --- a/src/pages/stakingPage/components/VestingModal.tsx +++ b/src/pages/stakingPage/components/VestingModal.tsx @@ -1,23 +1,24 @@ import { Box, Button, + Flex, HStack, - Text, + Icon, + Input, Modal, - ModalOverlay, + ModalCloseButton, ModalContent, ModalHeader, - ModalCloseButton, - Input, - VStack, + ModalOverlay, + Text, useToast, - Icon, - Flex, + VStack, } from '@chakra-ui/react'; import { useCallback, useEffect, useState } from 'react'; -import { FaChevronDown, FaPlus, FaEquals } from 'react-icons/fa'; -import { getVestingDuration } from '@/web3Config/staking/hooks'; +import { FaChevronDown, FaEquals,FaPlus } from 'react-icons/fa'; + import { GrixLogo } from '@/components/commons/Logo'; +import { getVestingDuration } from '@/web3Config/staking/hooks'; type VestingModalProps = { isOpen: boolean; diff --git a/src/pages/stakingPage/hooks/useVesting.ts b/src/pages/stakingPage/hooks/useVesting.ts index afb9a24..ee14423 100644 --- a/src/pages/stakingPage/hooks/useVesting.ts +++ b/src/pages/stakingPage/hooks/useVesting.ts @@ -1,15 +1,15 @@ import { useCallback, useEffect, useState } from 'react'; -import { useAccount } from 'wagmi'; import { parseEther } from 'viem'; +import { useAccount } from 'wagmi'; +import { stakingContracts } from '@/web3Config/staking/config'; import { - checkVestingAllowance, approveVesting, - vestEsGs, + checkVestingAllowance, getTokenBalance, getVestingData, + vestEsGs, } from '@/web3Config/staking/hooks'; -import { stakingContracts } from '@/web3Config/staking/config'; export const useVesting = () => { const { address } = useAccount(); @@ -28,7 +28,7 @@ export const useVesting = () => { checkVestingAllowance(address, stakingContracts.esGRIXToken.address), getTokenBalance(stakingContracts.esGRIXToken.address, address), getTokenBalance(stakingContracts.grixToken.address, address), - getVestingData(address as `0x${string}`), + getVestingData(address), ]); setVestingAllowance(allowance.toString()); From 7fbb1bb8d9506034deb017dda54424a4bd7c082a Mon Sep 17 00:00:00 2001 From: asalef10 Date: Tue, 22 Apr 2025 16:32:03 +0300 Subject: [PATCH 06/18] lint --- .../stakingPage/components/VestingCard.tsx | 27 +------------------ 1 file changed, 1 insertion(+), 26 deletions(-) diff --git a/src/pages/stakingPage/components/VestingCard.tsx b/src/pages/stakingPage/components/VestingCard.tsx index 1ddb808..d54aeff 100644 --- a/src/pages/stakingPage/components/VestingCard.tsx +++ b/src/pages/stakingPage/components/VestingCard.tsx @@ -1,14 +1,4 @@ -import { - Box, - Button, - HStack, - Input, - InputGroup, - InputRightElement, - useDisclosure, - useToast, - VStack, -} from '@chakra-ui/react'; +import { Box, useDisclosure, useToast, VStack } from '@chakra-ui/react'; import React, { useCallback, useEffect, useState } from 'react'; import { parseEther } from 'viem'; import { useAccount } from 'wagmi'; @@ -85,7 +75,6 @@ export const VestingCard: React.FC = ({ onActionComplete, user const allowance = await checkVestingAllowance(address, stakingContracts.esGRIXToken.address); setNeedsApproval(allowance === 0n); } catch (error) { - console.error('Error checking allowance:', error); setNeedsApproval(true); } }, [address]); @@ -159,7 +148,6 @@ export const VestingCard: React.FC = ({ onActionComplete, user onClose(); } } catch (error) { - console.error('Error in handleVest:', error); toast({ title: needsApproval ? 'Approval Failed' : 'Vesting Failed', description: `There was an error during the ${needsApproval ? 'approval' : 'vesting'} process`, @@ -175,19 +163,6 @@ export const VestingCard: React.FC = ({ onActionComplete, user [address, needsApproval, fetchBalance, fetchVestingData, fetchGrixBalance, toast, onClose, checkAllowance] ); - const handleMaxClick = useCallback(async () => { - await fetchBalance(); - await fetchVestingData(); - setAmount(esGrixBalance); - }, [esGrixBalance, fetchVestingData, fetchBalance]); - - const handleAmountChange = (e: React.ChangeEvent) => { - const value = e.target.value; - if (!value || /^\d*\.?\d*$/.test(value)) { - setAmount(value); - } - }; - // Calculate remaining days and progress const calculateVestingProgress = useCallback(() => { if (!lastVestingTime || !vestingDuration || !vestingData || parseFloat(vestingData.totalVested) === 0) { From 602bf91d1a35961c79e33c8a523e23467498ce2f Mon Sep 17 00:00:00 2001 From: asalef10 Date: Tue, 22 Apr 2025 16:33:21 +0300 Subject: [PATCH 07/18] lint - 3 --- src/pages/stakingPage/components/VestingCard.tsx | 3 --- .../stakingPage/components/VestingComponents/VestingStats.tsx | 2 +- src/pages/stakingPage/components/VestingModal.tsx | 2 +- 3 files changed, 2 insertions(+), 5 deletions(-) diff --git a/src/pages/stakingPage/components/VestingCard.tsx b/src/pages/stakingPage/components/VestingCard.tsx index d54aeff..ca0238c 100644 --- a/src/pages/stakingPage/components/VestingCard.tsx +++ b/src/pages/stakingPage/components/VestingCard.tsx @@ -34,7 +34,6 @@ export const VestingCard: React.FC = ({ onActionComplete, user const { isOpen, onOpen, onClose } = useDisclosure(); const { address } = useAccount(); const toast = useToast(); - const [amount, setAmount] = useState(''); const [esGrixBalance, setEsGrixBalance] = useState('0'); const [grixBalance, setGrixBalance] = useState('0'); const [isApproving, setIsApproving] = useState(false); @@ -122,7 +121,6 @@ export const VestingCard: React.FC = ({ onActionComplete, user const handleVest = useCallback( async (vestAmount: string) => { if (!address) return; - setAmount(vestAmount); try { if (needsApproval) { @@ -132,7 +130,6 @@ export const VestingCard: React.FC = ({ onActionComplete, user } else { setIsVesting(true); await vestEsGrix(parseEther(vestAmount)); - setAmount(''); } await Promise.all([fetchBalance(), fetchVestingData(true), fetchGrixBalance()]); diff --git a/src/pages/stakingPage/components/VestingComponents/VestingStats.tsx b/src/pages/stakingPage/components/VestingComponents/VestingStats.tsx index 0719bee..1dcf26e 100644 --- a/src/pages/stakingPage/components/VestingComponents/VestingStats.tsx +++ b/src/pages/stakingPage/components/VestingComponents/VestingStats.tsx @@ -1,4 +1,4 @@ -import { Button, Grid, GridItem, HStack,Progress, Text, VStack } from '@chakra-ui/react'; +import { Button, Grid, GridItem, HStack, Progress, Text, VStack } from '@chakra-ui/react'; import React from 'react'; import { formatBalance } from '../../utils/formatters'; diff --git a/src/pages/stakingPage/components/VestingModal.tsx b/src/pages/stakingPage/components/VestingModal.tsx index 3875fae..184c74c 100644 --- a/src/pages/stakingPage/components/VestingModal.tsx +++ b/src/pages/stakingPage/components/VestingModal.tsx @@ -15,7 +15,7 @@ import { VStack, } from '@chakra-ui/react'; import { useCallback, useEffect, useState } from 'react'; -import { FaChevronDown, FaEquals,FaPlus } from 'react-icons/fa'; +import { FaChevronDown, FaEquals, FaPlus } from 'react-icons/fa'; import { GrixLogo } from '@/components/commons/Logo'; import { getVestingDuration } from '@/web3Config/staking/hooks'; From e4e6753adc563ab13963f9c3951d574df05f2ca2 Mon Sep 17 00:00:00 2001 From: asalef10 Date: Tue, 22 Apr 2025 16:37:36 +0300 Subject: [PATCH 08/18] refactor --- .../stakingPage/components/VestingCard.tsx | 2 +- .../VestingComponents/VestingStats.tsx | 4 +- .../stakingPage/components/VestingModal.tsx | 44 ++++++++++--------- src/pages/stakingPage/hooks/useVesting.ts | 21 +++++---- 4 files changed, 40 insertions(+), 31 deletions(-) diff --git a/src/pages/stakingPage/components/VestingCard.tsx b/src/pages/stakingPage/components/VestingCard.tsx index ca0238c..7ee2bd0 100644 --- a/src/pages/stakingPage/components/VestingCard.tsx +++ b/src/pages/stakingPage/components/VestingCard.tsx @@ -234,7 +234,7 @@ export const VestingCard: React.FC = ({ onActionComplete, user onVestClick={onOpen} isVesting={isVesting || isApproving} needsApproval={needsApproval} - onWithdraw={handleWithdraw} + onWithdraw={() => void handleWithdraw()} isWithdrawing={isWithdrawing} /> diff --git a/src/pages/stakingPage/components/VestingComponents/VestingStats.tsx b/src/pages/stakingPage/components/VestingComponents/VestingStats.tsx index 1dcf26e..ba9fe06 100644 --- a/src/pages/stakingPage/components/VestingComponents/VestingStats.tsx +++ b/src/pages/stakingPage/components/VestingComponents/VestingStats.tsx @@ -111,7 +111,7 @@ export const VestingStats: React.FC = ({ + + + + + ); +}; From 185e6498c7edaac63b184062cbde7b1a33e1948a Mon Sep 17 00:00:00 2001 From: asalef10 Date: Tue, 22 Apr 2025 17:30:54 +0300 Subject: [PATCH 16/18] lint --- src/pages/stakingPage/components/VestingCard.tsx | 2 +- src/pages/stakingPage/components/WithdrawModal.tsx | 4 +--- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/src/pages/stakingPage/components/VestingCard.tsx b/src/pages/stakingPage/components/VestingCard.tsx index d3d20d7..309b328 100644 --- a/src/pages/stakingPage/components/VestingCard.tsx +++ b/src/pages/stakingPage/components/VestingCard.tsx @@ -254,7 +254,7 @@ export const VestingCard: React.FC = ({ onActionComplete, user void handleWithdraw()} isLoading={isWithdrawing} claimableGS={vestingData?.claimable || '0'} vestingAmount={vestingData?.totalVested || '0'} diff --git a/src/pages/stakingPage/components/WithdrawModal.tsx b/src/pages/stakingPage/components/WithdrawModal.tsx index eb68581..4835caa 100644 --- a/src/pages/stakingPage/components/WithdrawModal.tsx +++ b/src/pages/stakingPage/components/WithdrawModal.tsx @@ -32,8 +32,7 @@ export const WithdrawModal: React.FC = ({ claimableGS, vestingAmount, totalReserved, -}) => { - return ( +}) => ( @@ -97,4 +96,3 @@ export const WithdrawModal: React.FC = ({ ); -}; From 21cf6e51bf9dc8a437d4d4f9ba440bdda007d986 Mon Sep 17 00:00:00 2001 From: asalef10 Date: Tue, 22 Apr 2025 17:31:33 +0300 Subject: [PATCH 17/18] lint --- .../stakingPage/components/WithdrawModal.tsx | 110 +++++++++--------- 1 file changed, 55 insertions(+), 55 deletions(-) diff --git a/src/pages/stakingPage/components/WithdrawModal.tsx b/src/pages/stakingPage/components/WithdrawModal.tsx index 4835caa..72523c4 100644 --- a/src/pages/stakingPage/components/WithdrawModal.tsx +++ b/src/pages/stakingPage/components/WithdrawModal.tsx @@ -33,66 +33,66 @@ export const WithdrawModal: React.FC = ({ vestingAmount, totalReserved, }) => ( - - - - - - Withdraw - - - + + + + + + Withdraw + + + - - - - - - Claimable GRIX - - - {formatBalance(claimableGS)} GRIX - - + + + + + + Claimable GRIX + + + {formatBalance(claimableGS)} GRIX + + - + + + Vesting + + + {formatBalance(vestingAmount)} esGS + + + + + - Vesting + Total reserved - {formatBalance(vestingAmount)} esGS + {formatBalance(totalReserved)} tokens - - - - - Total reserved - - - {formatBalance(totalReserved)} tokens - - - - - - + - - - - ); + + + + + + +); From 017cbccf48dbbb4df6d0553c422a7f39c73c9f5c Mon Sep 17 00:00:00 2001 From: asalef10 Date: Tue, 22 Apr 2025 17:48:25 +0300 Subject: [PATCH 18/18] v5 --- .../stakingPage/components/RewardsCard.tsx | 9 +++---- src/pages/stakingPage/index.tsx | 24 ++++++++++--------- 2 files changed, 18 insertions(+), 15 deletions(-) diff --git a/src/pages/stakingPage/components/RewardsCard.tsx b/src/pages/stakingPage/components/RewardsCard.tsx index fcce7c6..9051f46 100644 --- a/src/pages/stakingPage/components/RewardsCard.tsx +++ b/src/pages/stakingPage/components/RewardsCard.tsx @@ -103,6 +103,7 @@ export const RewardsCard = ({ data, refetchData }: RewardsCardProps): JSX.Elemen bg="gray.950" borderRadius="md" p={4} + minW="360px" height="fit-content" border="1px solid" borderColor="gray.900" @@ -111,24 +112,24 @@ export const RewardsCard = ({ data, refetchData }: RewardsCardProps): JSX.Elemen - + WETH - + ≤{(0.0001).toFixed(4)} WETH (≤${(0.01).toFixed(2)}) - + Claimable Rewards - + {data?.claimable ? Number(data.claimable).toFixed(4) : '0.0000'} esGRIX {grixPrice && data?.claimable && ( diff --git a/src/pages/stakingPage/index.tsx b/src/pages/stakingPage/index.tsx index 82f1d8e..026cddd 100644 --- a/src/pages/stakingPage/index.tsx +++ b/src/pages/stakingPage/index.tsx @@ -59,16 +59,18 @@ export const StakingPage: React.FC = () => { return ( - - - Staking - - - - - + + + + + Staking + + + + + + - { - - + + Vesting