From 557b6543caecc4dfdde8a171725992d2950558ba Mon Sep 17 00:00:00 2001 From: Zak Date: Thu, 31 Oct 2024 13:26:22 +0000 Subject: [PATCH 1/8] feat: move to legacy WIP --- src/App.css | 8 +- src/App.jsx | 6 +- .../tst-staking/ClaimingRewardsModal.jsx | 182 ++++++++++ src/components/tst-staking/StakingAssets.jsx | 112 ++++++ .../tst-staking/StakingDecreaseModal.jsx | 292 +++++++++++++++ .../tst-staking/StakingIncrease.jsx | 335 ++++++++++++++++++ src/components/tst-staking/StakingRewards.jsx | 194 ++++++++++ src/components/ui/SideNav.jsx | 43 +-- src/pages/legacy-pools/LegacyPools.jsx | 63 ++++ src/pages/tst-staking/TstStaking.jsx | 184 ++++++++++ 10 files changed, 1394 insertions(+), 25 deletions(-) create mode 100644 src/components/tst-staking/ClaimingRewardsModal.jsx create mode 100644 src/components/tst-staking/StakingAssets.jsx create mode 100644 src/components/tst-staking/StakingDecreaseModal.jsx create mode 100644 src/components/tst-staking/StakingIncrease.jsx create mode 100644 src/components/tst-staking/StakingRewards.jsx create mode 100644 src/pages/legacy-pools/LegacyPools.jsx create mode 100644 src/pages/tst-staking/TstStaking.jsx diff --git a/src/App.css b/src/App.css index 11169d4..78b7344 100644 --- a/src/App.css +++ b/src/App.css @@ -605,7 +605,9 @@ button.btn.btn-success:not(:disabled) { } .btn.btn-primary:hover, -button.btn.btn-primary:hover { +button.btn.btn-primary:hover, +.btn.btn-primary.active, +button.btn.btn-primary.active { color: rgb(var(--btn-solid-color)); background: rgba(var(--primary-color), 1); box-shadow: var(--btn-solid-outline-hover), @@ -667,7 +669,9 @@ button.btn.btn-outline:not(:disabled) { } .btn.btn-outline:hover, -button.btn.btn-outline:hover { +button.btn.btn-outline:hover, +.btn.btn-outline.active, +button.btn.btn-outline.active { color: var(--alt-btn-txt-color); background: rgba(255,255,255, 0.1); box-shadow: var(--btn-outline-outline-hover), diff --git a/src/App.jsx b/src/App.jsx index fd891e8..cb1470a 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -10,7 +10,8 @@ import Vaults from './pages/vaults/Vaults'; import Vault from './pages/vault/Vault'; import VaultHistory from './pages/vault/VaultHistory'; import LiquidationPools from './pages/liquidation-pools/LiquidationPools'; -import StakingPool from './pages/staking-pool/StakingPool'; +import TstStaking from './pages/tst-staking/TstStaking'; +import LegacyPools from './pages/legacy-pools/LegacyPools'; import TermsOfUse from './pages/TermsOfUse'; import Dex from './pages/dex/Dex'; @@ -30,8 +31,9 @@ function App() { } /> } /> } /> + } /> } /> - } /> + } /> } /> } /> } /> diff --git a/src/components/tst-staking/ClaimingRewardsModal.jsx b/src/components/tst-staking/ClaimingRewardsModal.jsx new file mode 100644 index 0000000..ad9d7c4 --- /dev/null +++ b/src/components/tst-staking/ClaimingRewardsModal.jsx @@ -0,0 +1,182 @@ +import { useState, useEffect, useRef } from "react"; +import { + useWriteContract, + useChainId, +} from "wagmi"; +import { arbitrumSepolia } from "wagmi/chains"; +import { toast } from 'react-toastify'; + +import { + useStakingPoolv2AddressStore, + useStakingPoolv2AbiStore +} from "../../store/Store"; + +import Button from "../ui/Button"; +import Modal from "../ui/Modal"; +import Typography from "../ui/Typography"; +import CenterLoader from "../ui/CenterLoader"; +import Checkbox from "../ui/Checkbox"; + +const ClaimingRewardsModal = ({ + isOpen, + handleCloseModal, +}) => { + const { + arbitrumSepoliaStakingPoolv2Address, + arbitrumStakingPoolv2Address, + } = useStakingPoolv2AddressStore(); + const { stakingPoolv2Abi } = useStakingPoolv2AbiStore(); + const [claimLoading, setClaimLoading] = useState(false); + const [showError, setShowError] = useState(false); + const [compound, setCompound] = useState(false); + const chainId = useChainId(); + + const stakingPoolv2Address = chainId === arbitrumSepolia.id ? arbitrumSepoliaStakingPoolv2Address : + arbitrumStakingPoolv2Address; + + const { writeContract, isError, isPending, isSuccess, error } = useWriteContract(); + + const handleApproveClaim = async () => { + try { + writeContract({ + abi: stakingPoolv2Abi, + address: stakingPoolv2Address, + functionName: "claim", + args: [compound], + }); + } catch (error) { + let errorMessage = ''; + if (error && error.shortMessage) { + errorMessage = error.shortMessage; + } + toast.error(errorMessage || 'There was an error'); + } + }; + + useEffect(() => { + if (isPending) { + setClaimLoading(true); + } else if (isSuccess) { + toast.success('Success!'); + setClaimLoading(false); + handleCloseModal(); + } else if (isError) { + setShowError(true) + setClaimLoading(false); + } + }, [ + isPending, + isSuccess, + isError, + error, + ]); + + if (showError) { + return ( + <> + { + setShowError(false); + handleCloseModal(); + }} + > + <> + {claimLoading ? ( + <> + + Claiming Your Rewards + + + + ) : ( + <> +
+ + Reward Claim Unsuccessful + + + There was a problem processing your reward claim request. + +
+ + + + + )} + +
+ + ) + } + + return ( + <> + { + handleCloseModal(); + }} + > + <> + {claimLoading ? ( + <> + + Claiming Your Rewards + + + + ) : ( + <> +
+ + Claim Your Rewards + + + Claiming your rewards will end your current staking period and restart a new one. + + + By opting to compound your EUROs rewards, those EUROs will be added to the EUROs in your new stake. + +
+ setCompound(!compound)} + label="I would like to compound my EUROs rewards" + /> +
+
+ + + + )} + +
+ + ) +}; + +export default ClaimingRewardsModal; \ No newline at end of file diff --git a/src/components/tst-staking/StakingAssets.jsx b/src/components/tst-staking/StakingAssets.jsx new file mode 100644 index 0000000..264eafb --- /dev/null +++ b/src/components/tst-staking/StakingAssets.jsx @@ -0,0 +1,112 @@ +import { useState } from "react"; + +import { ethers } from "ethers"; + +import Button from "../ui/Button"; +import Card from "../ui/Card"; +import Typography from "../ui/Typography"; +import CenterLoader from "../ui/CenterLoader"; + +import StakingDecreaseModal from "./StakingDecreaseModal"; + +const StakingAssets = ({ + positions, +}) => { + const [open, setOpen] = useState(false); + + if (!positions) { + return ( + +
+ +
+
+ ) + } + + const tstAmount = positions[1] || 0; + const eurosAmount = positions[2] || 0; + + const useRows = [ + { + asset: 'TST', + amount: tstAmount || 0 + }, + { + asset: 'EUROs', + amount: eurosAmount || 0 + }, + ] + + const handleCloseModal = () => { + setOpen(false) + }; + + const rows = useRows || []; + + let noStaked = true; + if (rows.some(e => e.amount > 0)) { + noStaked = false; + } + + return ( + +
+ + Staked Assets + + +
+ + + + + + + + {!positions ? (null) : ( + + {rows.map(function(asset, index) { + const amount = asset?.amount; + const decimals = asset?.dec; + const symbol = asset?.asset; + + return( + + + + + )} + )} + + )} +
AssetAmount
+ {symbol} + + {ethers.formatUnits(amount, decimals)} +
+ {!positions ? ( + + ) : (null)} +
+ +
+ +
+ +
+
+ ) +}; + +export default StakingAssets; diff --git a/src/components/tst-staking/StakingDecreaseModal.jsx b/src/components/tst-staking/StakingDecreaseModal.jsx new file mode 100644 index 0000000..415383e --- /dev/null +++ b/src/components/tst-staking/StakingDecreaseModal.jsx @@ -0,0 +1,292 @@ +import { useState, useEffect, useRef } from "react"; +import { toast } from 'react-toastify'; + +import { + useWriteContract, + useChainId, +} from "wagmi"; +import { arbitrumSepolia } from "wagmi/chains"; +import { formatEther, parseEther } from "viem"; + +import { + useStakingPoolv2AddressStore, + useStakingPoolv2AbiStore +} from "../../store/Store"; + +import Button from "../ui/Button"; +import Modal from "../ui/Modal"; +import Typography from "../ui/Typography"; +import Input from "../ui/Input"; +import CenterLoader from "../ui/CenterLoader"; + +const StakingDecreaseModal = ({ + stakedPositions, + isOpen, + handleCloseModal, +}) => { + const { + arbitrumSepoliaStakingPoolv2Address, + arbitrumStakingPoolv2Address, + } = useStakingPoolv2AddressStore(); + const { stakingPoolv2Abi } = useStakingPoolv2AbiStore(); + const [claimLoading, setClaimLoading] = useState(false); + const [showError, setShowError] = useState(false); + const [tstWithdrawAmount, setTstWithdrawAmount] = useState(0); + const [eurosWithdrawAmount, setEurosWithdrawAmount] = useState(0); + const chainId = useChainId(); + + const tstInputRef = useRef(null); + const eurosInputRef = useRef(null); + + const tstPosition = stakedPositions?.find((item) => item.asset === 'TST'); + const eurosPosition = stakedPositions?.find((item) => item.asset === 'EUROs'); + + const tstStakedAmount = tstPosition?.amount; + const eurosStakedAmount = eurosPosition?.amount; + + const useTstStakedAmount = formatEther(tstStakedAmount.toString()); + const useEurosStakedAmount = formatEther(eurosStakedAmount.toString()); + + const stakingPoolv2Address = chainId === arbitrumSepolia.id ? arbitrumSepoliaStakingPoolv2Address : + arbitrumStakingPoolv2Address; + + const { writeContract, isError, isPending, isSuccess, error } = useWriteContract(); + + const handleApproveWithdraw = async () => { + try { + writeContract({ + abi: stakingPoolv2Abi, + address: stakingPoolv2Address, + functionName: "decreaseStake", + args: [ + tstWithdrawAmount, + eurosWithdrawAmount, + ], + }); + } catch (error) { + let errorMessage = ''; + if (error && error.shortMessage) { + errorMessage = error.shortMessage; + } + toast.error(errorMessage || 'There was a problem'); + } + }; + + useEffect(() => { + if (isPending) { + setClaimLoading(true); + } else if (isSuccess) { + toast.success('Success!'); + setClaimLoading(false); + setTstWithdrawAmount(0); + setEurosWithdrawAmount(0); + handleCloseModal(); + } else if (isError) { + setShowError(true) + setClaimLoading(false); + setTstWithdrawAmount(0); + setEurosWithdrawAmount(0); + } + }, [ + isPending, + isSuccess, + isError, + error, + ]); + + const handleTstAmount = (e) => { + if (Number(e.target.value) < 10n ** 21n) { + setTstWithdrawAmount(parseEther(e.target.value.toString())); + } + }; + + const handleTstInputMax = () => { + const formatBalance = formatEther(tstStakedAmount); + tstInputRef.current.value = formatBalance; + handleTstAmount({target: {value: formatBalance}}); + } + + const handleEurosAmount = (e) => { + if (Number(e.target.value) < 10n ** 21n) { + setEurosWithdrawAmount(parseEther(e.target.value.toString())); + } + }; + + const handleEurosInputMax = () => { + const formatBalance = formatEther(eurosStakedAmount); + eurosInputRef.current.value = formatBalance; + handleEurosAmount({target: {value: formatBalance}}); + } + + if (showError) { + return ( + <> + { + setShowError(false); + handleCloseModal(); + }} + > +
+ {claimLoading ? ( + <> + + Withdrawing Your Tokens + + + + ) : ( + <> +
+ + Withdraw Unsuccessful + + + There was a problem processing your withdraw request. + + + It is possible that your withdraw request exceeds the amount of tokens you have staked. + +
+ +
+ + +
+ + )} +
+
+ + ) + } + + return ( + <> + { + handleCloseModal(); + }} + > + <> + {claimLoading ? ( + <> + + Claiming Your Rewards + + + + ) : ( + <> +
+ + Withdraw Your Tokens + + + Here you can reduce your position by withdrawing your tokens. + + + Any withdrawals will automatically claim your existing rewards, ending your current staking period and restarting a new one. + +
+
+ +
+ + Available TST: + + + {useTstStakedAmount || '0'} + +
+
+ + Available EUROs: + + + {useEurosStakedAmount || '0'} + +
+ +
+ + + Withdraw Amounts: + +
+ + +
+
+ + +
+ + + + )} + +
+ + ) +}; + +export default StakingDecreaseModal; diff --git a/src/components/tst-staking/StakingIncrease.jsx b/src/components/tst-staking/StakingIncrease.jsx new file mode 100644 index 0000000..62659a6 --- /dev/null +++ b/src/components/tst-staking/StakingIncrease.jsx @@ -0,0 +1,335 @@ +import { useRef, useState, useEffect } from "react"; +import { toast } from 'react-toastify'; + +import { + useAccount, + useReadContracts, + useWriteContract, + useChainId, + useWatchBlockNumber, +} from "wagmi"; +import { arbitrumSepolia } from "wagmi/chains"; +import { ethers } from "ethers"; + +import { + useTstAddressStore, + useErc20AbiStore, + usesEuroAddressStore, + useStakingPoolv2AbiStore, + useStakingPoolv2AddressStore, +} from "../../store/Store.jsx"; + +import Card from "../ui/Card"; +import Typography from "../ui/Typography"; +import Button from "../ui/Button"; +import Input from "../ui/Input"; + +const StakingIncrease = () => { + const chainId = useChainId(); + const { + arbitrumTstAddress, + arbitrumSepoliaTstAddress, + } = useTstAddressStore(); + const { + arbitrumsEuroAddress, + arbitrumSepoliasEuroAddress, + } = usesEuroAddressStore(); + const { + arbitrumSepoliaStakingPoolv2Address, + arbitrumStakingPoolv2Address, + } = useStakingPoolv2AddressStore(); + const { address } = useAccount(); + const { erc20Abi } = useErc20AbiStore(); + const { stakingPoolv2Abi } = useStakingPoolv2AbiStore(); + const [tstStakeAmount, setTstStakeAmount] = useState(0); + const [eurosStakeAmount, setEurosStakeAmount] = useState(0); + const [stage, setStage] = useState(''); + const [helpOpen, setHelpOpen] = useState(false); + + const tstInputRef = useRef(null); + const eurosInputRef = useRef(null); + + const tstAddress = chainId === arbitrumSepolia.id ? + arbitrumSepoliaTstAddress : + arbitrumTstAddress; + + const eurosAddress = chainId === arbitrumSepolia.id ? + arbitrumSepoliasEuroAddress : + arbitrumsEuroAddress; + + const stakingPoolv2Address = chainId === arbitrumSepolia.id ? arbitrumSepoliaStakingPoolv2Address : + arbitrumStakingPoolv2Address; + + const tstContract = { + address: tstAddress, + abi: erc20Abi, + } + + const { data: tstData, refetch: refetchTst } = useReadContracts({ + contracts: [{ + ... tstContract, + functionName: "allowance", + args: [address, stakingPoolv2Address] + },{ + ... tstContract, + functionName: "balanceOf", + args: [address] + }], + }); + + const eurosContract = { + address: eurosAddress, + abi: erc20Abi, + } + + const { data: eurosData, refetch: refetchEuros } = useReadContracts({ + contracts: [{ + ... eurosContract, + functionName: "allowance", + args: [address, stakingPoolv2Address] + },{ + ... eurosContract, + functionName: "balanceOf", + args: [address] + }], + }); + + useWatchBlockNumber({ + onBlockNumber() { + refetchTst(); + refetchEuros(); + }, + }) + + const existingTstAllowance = tstData && tstData[0].result; + const tstBalance = tstData && tstData[1].result; + + const existingEurosAllowance = eurosData && eurosData[0].result; + const eurosBalance = eurosData && eurosData[1].result; + + const { writeContract, isError, isPending, isSuccess, error } = useWriteContract(); + + const handleApproveTst = async () => { + setStage('APPROVE_TST'); + try { + writeContract({ + abi: erc20Abi, + address: tstAddress, + functionName: "approve", + args: [stakingPoolv2Address, tstStakeAmount], + }); + } catch (error) { + let errorMessage = ''; + if (error && error.shortMessage) { + errorMessage = error.shortMessage; + } + toast.error(errorMessage || 'There was a problem'); + } + }; + + const handleApproveEuros = async () => { + setStage('APPROVE_EUROS'); + setTimeout(() => { + try { + writeContract({ + abi: erc20Abi, + address: eurosAddress, + functionName: "approve", + args: [stakingPoolv2Address, eurosStakeAmount], + }); + + } catch (error) { + let errorMessage = ''; + if (error && error.shortMessage) { + errorMessage = error.shortMessage; + } + toast.error(errorMessage || 'There was a problem'); + } + }, 1000); + }; + + const handleDepositToken = async () => { + setStage('DEPOSIT_TOKEN'); + setTimeout(() => { + try { + writeContract({ + abi: stakingPoolv2Abi, + address: stakingPoolv2Address, + functionName: "increaseStake", + args: [ + tstStakeAmount, + eurosStakeAmount + ], + }); + } catch (error) { + let errorMessage = ''; + if (error && error.shortMessage) { + errorMessage = error.shortMessage; + } + toast.error(errorMessage || 'There was a problem'); + } + }, 1000); + }; + + const handleLetsStake = async () => { + if (existingTstAllowance < tstStakeAmount) { + handleApproveTst(); + } else { + if (existingEurosAllowance < eurosStakeAmount) { + handleApproveEuros(); + } else { + handleDepositToken(); + } + } + }; + + useEffect(() => { + if (stage === 'APPROVE_TST') { + if (isPending) { + // + } else if (isSuccess) { + setStage(''); + toast.success('TST Approved'); + handleApproveEuros(); + } else if (isError) { + setStage(''); + toast.error('There was a problem'); + } + } + if (stage === 'APPROVE_EUROS') { + if (isPending) { + // + } else if (isSuccess) { + setStage(''); + toast.success('EUROs Approved'); + handleDepositToken(); + } else if (isError) { + setStage(''); + toast.error('There was a problem'); + } + } + if (stage === 'DEPOSIT_TOKEN') { + if (isPending) { + // + } else if (isSuccess) { + setStage(''); + toast.success('Deposited Successfully!'); + eurosInputRef.current.value = ""; + tstInputRef.current.value = ""; + setTstStakeAmount(0); + setEurosStakeAmount(0); + } else if (isError) { + setStage(''); + toast.error('There was a problem'); + eurosInputRef.current.value = ""; + tstInputRef.current.value = ""; + setTstStakeAmount(0); + setEurosStakeAmount(0); + } + } + }, [ + isPending, + isSuccess, + isError, + error + ]); + + const handleTstAmount = (e) => { + if (Number(e.target.value) < 10n ** 21n) { + setTstStakeAmount(ethers.parseEther(e.target.value.toString())); + } + }; + + const handleTstInputMax = () => { + const formatBalance = ethers.formatEther(tstBalance); + tstInputRef.current.value = formatBalance; + handleTstAmount({target: {value: formatBalance}}); + } + + const handleEurosAmount = (e) => { + if (Number(e.target.value) < 10n ** 21n) { + setEurosStakeAmount(ethers.parseEther(e.target.value.toString())); + } + }; + + const handleEurosInputMax = () => { + const formatBalance = ethers.formatEther(eurosBalance); + eurosInputRef.current.value = formatBalance; + handleEurosAmount({target: {value: formatBalance}}); + } + + return ( + <> + +
+ + Deposit + + + Increase your TST position to earn EUROs rewards, and increase your EUROs position to earn an assortment of other tokens. + + + Depositing will automatically claim your existing rewards & compound any EUROs, ending your current staking period and restarting a new one. + +
+
+ + TST Deposit Amount: + +
+ + +
+ + EUROs Deposit Amount: + +
+ + +
+
+ +
+
+
+
+ + ); +}; + +export default StakingIncrease; \ No newline at end of file diff --git a/src/components/tst-staking/StakingRewards.jsx b/src/components/tst-staking/StakingRewards.jsx new file mode 100644 index 0000000..bfab1a9 --- /dev/null +++ b/src/components/tst-staking/StakingRewards.jsx @@ -0,0 +1,194 @@ +import { useState } from "react"; +import { ethers } from "ethers"; +import { + useReadContracts, +} from "wagmi"; + +import { + useErc20AbiStore, +} from "../../store/Store"; + +import Button from "../ui/Button"; +import Card from "../ui/Card"; +import Typography from "../ui/Typography"; +import CenterLoader from "../ui/CenterLoader"; + +import ClaimingRewardsModal from "./ClaimingRewardsModal"; + +const StakingRewards = ({ + stakedSince, + rewardRate, + sEuroAmount, + collaterals, + poolRewardsLoading, + sEuroDaily, + collatDaily, +}) => { + const [open, setOpen] = useState(false); + const { erc20Abi } = useErc20AbiStore(); + + if (poolRewardsLoading) { + return ( + +
+ +
+
+ ) + } + + const { data: rewardDecimals } = useReadContracts({ + contracts:collaterals.map((item) =>({ + address: item.token, + abi: erc20Abi, + functionName: "decimals", + args: [], + })) + }) + + const { data: rewardSymbols } = useReadContracts({ + contracts:collaterals.map((item) =>({ + address: item.token, + abi: erc20Abi, + functionName: "symbol", + args: [], + })) + }) + + const rewardData = collaterals?.map((item, index) => { + const amount = item.amount; + let decimals = 18; + if (rewardDecimals) { + if (rewardDecimals[index]) { + if (rewardDecimals[index].result) { + decimals = rewardDecimals[index].result; + } + } + } + let symbol = 'ETH'; + if (rewardSymbols) { + if (rewardSymbols[index]) { + if (rewardSymbols[index].result) { + symbol = rewardSymbols[index].result; + } + } + } + let useDailyReward = 0n; + + const rewardItem = collatDaily?.find((reward) => reward.token === item.token); + + if (rewardItem && rewardItem.amount) { + useDailyReward = rewardItem.amount; + } + + return { + key: index, + asset: symbol, + amount: amount, + decimals: decimals, + dailyReward: useDailyReward, + } + }); + + const useRows = [ + { + key: 'EUROs', + asset: 'EUROs', + amount: sEuroAmount, + decimals: 18, + dailyReward: sEuroDaily, + }, + ...rewardData + ] + + const handleCloseModal = () => { + setOpen(false) + }; + + const rows = useRows || []; + + let noRewards = true; + if (rows.some(e => e.amount > 0)) { + noRewards = false; + } + + return ( + +
+ + Projected Rewards + +
+ + You can earn rewards every 24 hours after your staking period begins. + + + Your reward rates are based on a the number of tokens you have staked. + + {stakedSince ? ( + + Staked Since: + {stakedSince} + + ) : null} +
+ +
+ + + + + + + + + {poolRewardsLoading ? (null) : ( + + {rows.map(function(asset, index) { + const amount = asset?.amount; + const decimals = asset?.decimals; + const symbol = asset?.asset; + const dailyReward = asset?.dailyReward; + + return( + + + + + + )} + )} + + )} +
AssetAmountDaily Reward Per Token
+ {symbol} + + {ethers.formatUnits(amount, decimals)} + + {ethers.formatUnits(dailyReward, decimals)} + / {symbol === 'EUROs' ? ('TST') : ('EUROs')} +
+ {poolRewardsLoading ? ( + + ) : (null)} +
+ +
+ +
+ +
+
+ ) +}; + +export default StakingRewards; diff --git a/src/components/ui/SideNav.jsx b/src/components/ui/SideNav.jsx index a1b8043..d516600 100644 --- a/src/components/ui/SideNav.jsx +++ b/src/components/ui/SideNav.jsx @@ -8,7 +8,8 @@ import { BanknotesIcon, XMarkIcon, Square3Stack3DIcon, - ArrowPathRoundedSquareIcon + ArrowPathRoundedSquareIcon, + ArchiveBoxIcon } from '@heroicons/react/24/outline'; import { @@ -82,30 +83,30 @@ const SideNav = (props) => { isActive || - location.pathname.includes('/liquidation-pools') ? + location.pathname.includes('/dex') ? 'navbar-item active' : 'navbar-item' } > - - - Liquidation Pools - + + Cross-Chain Dex isActive || - location.pathname.includes('/dex') ? + location.pathname.includes('/legacy-pools') ? 'navbar-item active' : 'navbar-item' } > - - Cross-Chain Dex + + + Legacy Pools + @@ -154,40 +155,40 @@ const SideNav = (props) => { isActive || - location.pathname.includes('/liquidation-pools') ? + location.pathname.includes('/dex') ? 'navbar-item active' : 'navbar-item' } > - + - Liquidation Pools + Cross-Chain Dex isActive || - location.pathname.includes('/dex') ? + location.pathname.includes('/legacy-pools') ? 'navbar-item active' : 'navbar-item' } > - + - Cross-Chain Dex + Legacy Pools diff --git a/src/pages/legacy-pools/LegacyPools.jsx b/src/pages/legacy-pools/LegacyPools.jsx new file mode 100644 index 0000000..071d542 --- /dev/null +++ b/src/pages/legacy-pools/LegacyPools.jsx @@ -0,0 +1,63 @@ +import { useState } from "react"; + +import LiquidationPools from '../liquidation-pools/LiquidationPools'; +import StakingPool from '../staking-pool/StakingPool'; + +import Card from "../../components/ui/Card"; +import Typography from "../../components/ui/Typography"; +import Button from "../../components/ui/Button"; + +const LegacyPools = (props) => { + const [ showPool, setShowPool ] = useState('STAKE-EUROSTST'); + + return ( +
+ + +
+ + Legacy Pools + + + + These pools have been depreciated and will no longer be generating new rewards. + + + + We recommend collecting any outstanding rewards and withdrawing your staked tokens. + + +
+ + +
+ + +
+
+ +
+ {showPool === 'STAKE-EUROSTST' ? ( + + ) : (null)} + {showPool === 'LIQUIDITY-POOL' ? ( + + ) : (null)} +
+ +
+ ); +}; + +export default LegacyPools; \ No newline at end of file diff --git a/src/pages/tst-staking/TstStaking.jsx b/src/pages/tst-staking/TstStaking.jsx new file mode 100644 index 0000000..a5fb0f1 --- /dev/null +++ b/src/pages/tst-staking/TstStaking.jsx @@ -0,0 +1,184 @@ +import { useState } from "react"; +import moment from 'moment'; +import { + useReadContract, + useAccount, + useChainId, + useWatchBlockNumber +} from "wagmi"; +import { arbitrumSepolia } from "wagmi/chains"; +import { + ArrowTrendingUpIcon, + BanknotesIcon, + Square3Stack3DIcon, +} from '@heroicons/react/24/outline'; + +import { + useStakingPoolv2AbiStore, + useStakingPoolv2AddressStore, +} from "../../store/Store"; + +import StakingIncrease from "../../components/tst-staking/StakingIncrease"; +import StakingAssets from "../../components/tst-staking/StakingAssets"; +import StakingRewards from "../../components/tst-staking/StakingRewards"; + +import Card from "../../components/ui/Card"; +import CenterLoader from "../../components/ui/CenterLoader"; +import Typography from "../../components/ui/Typography"; +import Button from "../../components/ui/Button"; + +const TstStaking = (props) => { + const { stakingPoolv2Abi } = useStakingPoolv2AbiStore(); + const [showValue, setShowValue] = useState(false); + + const { + arbitrumSepoliaStakingPoolv2Address, + arbitrumStakingPoolv2Address, + } = useStakingPoolv2AddressStore(); + + const { address } = useAccount(); + const chainId = useChainId(); + + const stakingPoolv2Address = + chainId === arbitrumSepolia.id + ? arbitrumSepoliaStakingPoolv2Address + : arbitrumStakingPoolv2Address; + + const { data: poolPositions, refetch: refetchPositions } = useReadContract({ + address: stakingPoolv2Address, + abi: stakingPoolv2Abi, + functionName: "positions", + args: [address], + }); + + const { data: poolRewards, isLoading: poolRewardsLoading, refetch: refetchRewards } = useReadContract({ + address: stakingPoolv2Address, + abi: stakingPoolv2Abi, + functionName: "projectedEarnings", + args: [address], + }); + + const { data: dailyYield, refetch: refetchDailyReward } = useReadContract({ + address: stakingPoolv2Address, + abi: stakingPoolv2Abi, + functionName: "dailyYield", + args: [], + }); + + useWatchBlockNumber({ + onBlockNumber() { + refetchPositions(); + refetchRewards(); + refetchDailyReward(); + }, + }) + + const positions = poolPositions; + const rewards = poolRewards; + const dailyRewards = dailyYield; + + + let sEuroAmount = 0n; + let collaterals = []; + + if (rewards && rewards[0]) { + sEuroAmount = rewards[0] || 0n; + } + if (rewards && rewards[1]) { + collaterals = rewards[1] || []; + } + + let sEuroDaily = 0n; + let collatDaily = []; + + if (dailyRewards && dailyRewards[0]) { + sEuroDaily = dailyRewards[0] || 0n; + } + if (dailyRewards && dailyRewards[1]) { + collatDaily = dailyRewards[1] || []; + } + + let stakedSince = 0; + if (positions && positions[0]) { + stakedSince = positions[0] || 0; + } + + let useStakedSince; + if (stakedSince) { + useStakedSince = moment.unix(Number(stakedSince)).format('Do MMM YYYY'); + } + + return ( + <> + +
+ + + Staking Pool + + + + These pools have been depreciated and will no longer be generating new rewards. + + + + We recommend collecting any outstanding rewards and withdrawing your staked tokens. + + +
+ + +
+ + +
+
+ +
+ +
+ +
+ +
+ +
+ +
+ {poolRewardsLoading ? ( + +
+ +
+
+ ) : ( + + )} +
+ +
+ + + ); +}; + +export default TstStaking; \ No newline at end of file From 8ac9f906adaaf4dc47e3befa65b05cfe8383a565 Mon Sep 17 00:00:00 2001 From: Zak Date: Thu, 31 Oct 2024 14:52:00 +0000 Subject: [PATCH 2/8] feat: remove unused from legacy pools --- src/pages/legacy-pools/LegacyPools.jsx | 3 +- .../liquidation-pools/LiquidationPools.jsx | 74 ------------------- src/pages/staking-pool/StakingPool.jsx | 52 +------------ 3 files changed, 3 insertions(+), 126 deletions(-) diff --git a/src/pages/legacy-pools/LegacyPools.jsx b/src/pages/legacy-pools/LegacyPools.jsx index 071d542..128d413 100644 --- a/src/pages/legacy-pools/LegacyPools.jsx +++ b/src/pages/legacy-pools/LegacyPools.jsx @@ -31,13 +31,14 @@ const LegacyPools = (props) => { diff --git a/src/pages/liquidation-pools/LiquidationPools.jsx b/src/pages/liquidation-pools/LiquidationPools.jsx index 8f3af1c..ccadfdd 100644 --- a/src/pages/liquidation-pools/LiquidationPools.jsx +++ b/src/pages/liquidation-pools/LiquidationPools.jsx @@ -5,12 +5,7 @@ import { useChainId, useWatchBlockNumber } from "wagmi"; -import axios from "axios"; import { arbitrumSepolia } from "wagmi/chains"; -import { - ArrowTrendingUpIcon, - BanknotesIcon, -} from '@heroicons/react/24/outline'; import { useLiquidationPoolAbiStore, @@ -19,11 +14,6 @@ import { import StakedAssets from "../../components/liquidation-pools/StakedAssets"; import ClaimTokens from "../../components/liquidation-pools/ClaimTokens"; -import VolumeChart from "../../components/liquidation-pools/VolumeChart"; -import ValueChart from "../../components/liquidation-pools/ValueChart"; -import Card from "../../components/ui/Card"; -import Typography from "../../components/ui/Typography"; -import Button from "../../components/ui/Button"; const LiquidationPools = () => { const [chartData, setChartData] = useState(undefined); @@ -60,37 +50,8 @@ const LiquidationPools = () => { const pending = liquidationPool && liquidationPool[1]; const rewards = liquidationPool && liquidationPool[2]; - const getChartData = async () => { - try { - const response = await axios.get( - `https://smart-vault-api.thestandard.io/liquidation_pools/${address}` - ); - setChartData(response?.data); - } catch (error) { - console.log(error); - } - }; - - useEffect(() => { - getChartData(); - }, []); - return ( <> - -
-
- - Changes With Earning Fees & New Staking Pool - - - With the recent release of our new & improved Staking Pool, from 17th June '24, we will be moving all earnable fees over to it. -
- The existing Liquidation Pool will continue to exist past this date so you can withdraw your staked assets and claim any outstanding rewards. -
-
-
-
{ rewards={rewards || []} />
-
- -
- - {showValue ? ( - 'Liquidation Pool Asset Value' - ) : ( - 'Liquidation Pool Asset Totals' - )} - - - {showValue ? ( - <> - - - ) : ( - <> - - - )} -
-
-
); diff --git a/src/pages/staking-pool/StakingPool.jsx b/src/pages/staking-pool/StakingPool.jsx index 09380d4..dbdbcc3 100644 --- a/src/pages/staking-pool/StakingPool.jsx +++ b/src/pages/staking-pool/StakingPool.jsx @@ -7,26 +7,17 @@ import { useWatchBlockNumber } from "wagmi"; import { arbitrumSepolia } from "wagmi/chains"; -import { - ArrowTrendingUpIcon, - BanknotesIcon, -} from '@heroicons/react/24/outline'; import { useStakingPoolv2AbiStore, useStakingPoolv2AddressStore, } from "../../store/Store"; -import StakingIncrease from "../../components/staking-pool/StakingIncrease"; import StakingAssets from "../../components/staking-pool/StakingAssets"; import StakingRewards from "../../components/staking-pool/StakingRewards"; -import VolumeChart from "../../components/staking-pool/VolumeChart"; -import ValueChart from "../../components/staking-pool/ValueChart"; import Card from "../../components/ui/Card"; import CenterLoader from "../../components/ui/CenterLoader"; -import Typography from "../../components/ui/Typography"; -import Button from "../../components/ui/Button"; const StakingPool = (props) => { const { stakingPoolv2Abi } = useStakingPoolv2AbiStore(); @@ -111,52 +102,12 @@ const StakingPool = (props) => { return (
-
- -
-
- -
- - {showValue ? ( - 'Asset Value' - ) : ( - 'Asset Totals' - )} - - - {showValue ? ( - <> - - - ) : ( - <> - - - )} -
-
-
- -
- {poolRewardsLoading ? ( + {poolRewardsLoading ? (
@@ -174,7 +125,6 @@ const StakingPool = (props) => { /> )}
-
); }; From 6fb11d9d0554f5ccf8570bdaefbff6c8a4d47699 Mon Sep 17 00:00:00 2001 From: Zak Date: Thu, 31 Oct 2024 15:50:32 +0000 Subject: [PATCH 3/8] feat: update abis and functions to just TST --- src/abis/stakingPoolV3.jsx | 130 +++++++++++++++ .../tst-staking/ClaimingRewardsModal.jsx | 34 ++-- src/components/tst-staking/StakingAssets.jsx | 5 - .../tst-staking/StakingDecreaseModal.jsx | 100 +++-------- .../tst-staking/StakingIncrease.jsx | 157 +++++------------- src/components/tst-staking/StakingRewards.jsx | 22 +-- src/pages/tst-staking/TstStaking.jsx | 80 +++------ src/store/Store.jsx | 18 ++ 8 files changed, 252 insertions(+), 294 deletions(-) create mode 100644 src/abis/stakingPoolV3.jsx diff --git a/src/abis/stakingPoolV3.jsx b/src/abis/stakingPoolV3.jsx new file mode 100644 index 0000000..52029fd --- /dev/null +++ b/src/abis/stakingPoolV3.jsx @@ -0,0 +1,130 @@ +export const abi = [ + { + "inputs": [], + "name": "claim", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "dailyYield", + "outputs": [ + { + "components": [ + { + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "internalType": "struct StakingTST.Reward[]", + "name": "_rewards", + "type": "tuple[]" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "_tst", + "type": "uint256" + } + ], + "name": "decreaseStake", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "_tst", + "type": "uint256" + } + ], + "name": "increaseStake", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "positions", + "outputs": [ + { + "internalType": "uint256", + "name": "start", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "TST", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_holder", + "type": "address" + } + ], + "name": "projectedEarnings", + "outputs": [ + { + "components": [ + { + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "internalType": "struct StakingTST.Reward[]", + "name": "_rewards", + "type": "tuple[]" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "start", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + } +] + +export default abi; \ No newline at end of file diff --git a/src/components/tst-staking/ClaimingRewardsModal.jsx b/src/components/tst-staking/ClaimingRewardsModal.jsx index ad9d7c4..1e7b3f0 100644 --- a/src/components/tst-staking/ClaimingRewardsModal.jsx +++ b/src/components/tst-staking/ClaimingRewardsModal.jsx @@ -7,42 +7,40 @@ import { arbitrumSepolia } from "wagmi/chains"; import { toast } from 'react-toastify'; import { - useStakingPoolv2AddressStore, - useStakingPoolv2AbiStore + useStakingPoolv3AddressStore, + useStakingPoolv3AbiStore } from "../../store/Store"; import Button from "../ui/Button"; import Modal from "../ui/Modal"; import Typography from "../ui/Typography"; import CenterLoader from "../ui/CenterLoader"; -import Checkbox from "../ui/Checkbox"; const ClaimingRewardsModal = ({ isOpen, handleCloseModal, }) => { const { - arbitrumSepoliaStakingPoolv2Address, - arbitrumStakingPoolv2Address, - } = useStakingPoolv2AddressStore(); - const { stakingPoolv2Abi } = useStakingPoolv2AbiStore(); + arbitrumSepoliaStakingPoolv3Address, + arbitrumStakingPoolv3Address, + } = useStakingPoolv3AddressStore(); + const { stakingPoolv3Abi } = useStakingPoolv3AbiStore(); const [claimLoading, setClaimLoading] = useState(false); const [showError, setShowError] = useState(false); - const [compound, setCompound] = useState(false); const chainId = useChainId(); - const stakingPoolv2Address = chainId === arbitrumSepolia.id ? arbitrumSepoliaStakingPoolv2Address : - arbitrumStakingPoolv2Address; + const stakingPoolv3Address = chainId === arbitrumSepolia.id ? arbitrumSepoliaStakingPoolv3Address : + arbitrumStakingPoolv3Address; const { writeContract, isError, isPending, isSuccess, error } = useWriteContract(); const handleApproveClaim = async () => { try { writeContract({ - abi: stakingPoolv2Abi, - address: stakingPoolv2Address, + abi: stakingPoolv3Abi, + address: stakingPoolv3Address, functionName: "claim", - args: [compound], + args: [], }); } catch (error) { let errorMessage = ''; @@ -148,16 +146,6 @@ const ClaimingRewardsModal = ({ Claiming your rewards will end your current staking period and restart a new one. - - By opting to compound your EUROs rewards, those EUROs will be added to the EUROs in your new stake. - -
- setCompound(!compound)} - label="I would like to compound my EUROs rewards" - /> -
-
- - -
diff --git a/src/components/tst-staking/StakingIncrease.jsx b/src/components/tst-staking/StakingIncrease.jsx index 62659a6..e4684db 100644 --- a/src/components/tst-staking/StakingIncrease.jsx +++ b/src/components/tst-staking/StakingIncrease.jsx @@ -15,8 +15,8 @@ import { useTstAddressStore, useErc20AbiStore, usesEuroAddressStore, - useStakingPoolv2AbiStore, - useStakingPoolv2AddressStore, + useStakingPoolv3AbiStore, + useStakingPoolv3AddressStore, } from "../../store/Store.jsx"; import Card from "../ui/Card"; @@ -35,30 +35,24 @@ const StakingIncrease = () => { arbitrumSepoliasEuroAddress, } = usesEuroAddressStore(); const { - arbitrumSepoliaStakingPoolv2Address, - arbitrumStakingPoolv2Address, - } = useStakingPoolv2AddressStore(); + arbitrumSepoliaStakingPoolv3Address, + arbitrumStakingPoolv3Address, + } = useStakingPoolv3AddressStore(); const { address } = useAccount(); const { erc20Abi } = useErc20AbiStore(); - const { stakingPoolv2Abi } = useStakingPoolv2AbiStore(); + const { stakingPoolv3Abi } = useStakingPoolv3AbiStore(); const [tstStakeAmount, setTstStakeAmount] = useState(0); - const [eurosStakeAmount, setEurosStakeAmount] = useState(0); const [stage, setStage] = useState(''); const [helpOpen, setHelpOpen] = useState(false); const tstInputRef = useRef(null); - const eurosInputRef = useRef(null); const tstAddress = chainId === arbitrumSepolia.id ? arbitrumSepoliaTstAddress : arbitrumTstAddress; - const eurosAddress = chainId === arbitrumSepolia.id ? - arbitrumSepoliasEuroAddress : - arbitrumsEuroAddress; - - const stakingPoolv2Address = chainId === arbitrumSepolia.id ? arbitrumSepoliaStakingPoolv2Address : - arbitrumStakingPoolv2Address; + const stakingPoolv3Address = chainId === arbitrumSepolia.id ? arbitrumSepoliaStakingPoolv3Address : + arbitrumStakingPoolv3Address; const tstContract = { address: tstAddress, @@ -69,7 +63,7 @@ const StakingIncrease = () => { contracts: [{ ... tstContract, functionName: "allowance", - args: [address, stakingPoolv2Address] + args: [address, stakingPoolv3Address] },{ ... tstContract, functionName: "balanceOf", @@ -77,36 +71,15 @@ const StakingIncrease = () => { }], }); - const eurosContract = { - address: eurosAddress, - abi: erc20Abi, - } - - const { data: eurosData, refetch: refetchEuros } = useReadContracts({ - contracts: [{ - ... eurosContract, - functionName: "allowance", - args: [address, stakingPoolv2Address] - },{ - ... eurosContract, - functionName: "balanceOf", - args: [address] - }], - }); - useWatchBlockNumber({ onBlockNumber() { refetchTst(); - refetchEuros(); }, }) const existingTstAllowance = tstData && tstData[0].result; const tstBalance = tstData && tstData[1].result; - const existingEurosAllowance = eurosData && eurosData[0].result; - const eurosBalance = eurosData && eurosData[1].result; - const { writeContract, isError, isPending, isSuccess, error } = useWriteContract(); const handleApproveTst = async () => { @@ -116,7 +89,7 @@ const StakingIncrease = () => { abi: erc20Abi, address: tstAddress, functionName: "approve", - args: [stakingPoolv2Address, tstStakeAmount], + args: [stakingPoolv3Address, tstStakeAmount], }); } catch (error) { let errorMessage = ''; @@ -127,38 +100,16 @@ const StakingIncrease = () => { } }; - const handleApproveEuros = async () => { - setStage('APPROVE_EUROS'); - setTimeout(() => { - try { - writeContract({ - abi: erc20Abi, - address: eurosAddress, - functionName: "approve", - args: [stakingPoolv2Address, eurosStakeAmount], - }); - - } catch (error) { - let errorMessage = ''; - if (error && error.shortMessage) { - errorMessage = error.shortMessage; - } - toast.error(errorMessage || 'There was a problem'); - } - }, 1000); - }; - const handleDepositToken = async () => { setStage('DEPOSIT_TOKEN'); setTimeout(() => { try { writeContract({ - abi: stakingPoolv2Abi, - address: stakingPoolv2Address, + abi: stakingPoolv3Abi, + address: stakingPoolv3Address, functionName: "increaseStake", args: [ tstStakeAmount, - eurosStakeAmount ], }); } catch (error) { @@ -175,11 +126,7 @@ const StakingIncrease = () => { if (existingTstAllowance < tstStakeAmount) { handleApproveTst(); } else { - if (existingEurosAllowance < eurosStakeAmount) { - handleApproveEuros(); - } else { - handleDepositToken(); - } + handleDepositToken(); } }; @@ -190,23 +137,11 @@ const StakingIncrease = () => { } else if (isSuccess) { setStage(''); toast.success('TST Approved'); - handleApproveEuros(); - } else if (isError) { - setStage(''); - toast.error('There was a problem'); - } - } - if (stage === 'APPROVE_EUROS') { - if (isPending) { - // - } else if (isSuccess) { - setStage(''); - toast.success('EUROs Approved'); handleDepositToken(); } else if (isError) { setStage(''); toast.error('There was a problem'); - } + } } if (stage === 'DEPOSIT_TOKEN') { if (isPending) { @@ -214,17 +149,13 @@ const StakingIncrease = () => { } else if (isSuccess) { setStage(''); toast.success('Deposited Successfully!'); - eurosInputRef.current.value = ""; tstInputRef.current.value = ""; setTstStakeAmount(0); - setEurosStakeAmount(0); } else if (isError) { setStage(''); toast.error('There was a problem'); - eurosInputRef.current.value = ""; tstInputRef.current.value = ""; setTstStakeAmount(0); - setEurosStakeAmount(0); } } }, [ @@ -246,16 +177,10 @@ const StakingIncrease = () => { handleTstAmount({target: {value: formatBalance}}); } - const handleEurosAmount = (e) => { - if (Number(e.target.value) < 10n ** 21n) { - setEurosStakeAmount(ethers.parseEther(e.target.value.toString())); - } - }; + let maxTst = 0; - const handleEurosInputMax = () => { - const formatBalance = ethers.formatEther(eurosBalance); - eurosInputRef.current.value = formatBalance; - handleEurosAmount({target: {value: formatBalance}}); + if (tstBalance) { + maxTst = ethers.formatEther(tstBalance.toString()); } return ( @@ -266,16 +191,29 @@ const StakingIncrease = () => { Deposit - Increase your TST position to earn EUROs rewards, and increase your EUROs position to earn an assortment of other tokens. + Increase your TST position to earn USDs & more rewards. - Depositing will automatically claim your existing rewards & compound any EUROs, ending your current staking period and restarting a new one. + Depositing will automatically claim your existing rewards, ending your current staking period and restarting a new one. -
- - TST Deposit Amount: - + {/* + TST Deposit Amount + */} +
+ + TST Deposit Amount + + + Available: {maxTst || '0'} + +
@@ -294,32 +232,11 @@ const StakingIncrease = () => { Max
- - EUROs Deposit Amount: - -
- - -
- -
- -
@@ -149,10 +125,9 @@ const TstStaking = (props) => {
-
- -
- +
+ +
@@ -166,13 +141,12 @@ const TstStaking = (props) => { )} +
diff --git a/src/store/Store.jsx b/src/store/Store.jsx index dae70f6..53b9331 100644 --- a/src/store/Store.jsx +++ b/src/store/Store.jsx @@ -7,6 +7,7 @@ import smartVaultV4ABI from "../abis/smartVaultV4"; import stakingAbi from "../abis/staking"; import liquidationPoolAbi from "../abis/liquidationPool"; import stakingPoolv2Abi from "../abis/stakingPoolV2"; +import stakingPoolv3Abi from "../abis/stakingPoolV3"; export const useCurrentWagmiConfig = create( (set) => ({ @@ -110,6 +111,15 @@ export const useStakingPoolv2AddressStore = create()( }) ); +export const useStakingPoolv3AddressStore = create()( + (set) => ({ + arbitrumStakingPoolv3Address: "0xA27A9F6Bac7f3C530EAF324Ae45F33Bc113c1E83", + arbitrumSepoliaStakingPoolv3Address: "0x9bfEADec553110AbB9e2fbE54ccD9AD903f21961", + getStakingPoolv3Address: (arbitrumStakingPoolv3Address) => + set(() => ({ stakingPoolv3Address: arbitrumStakingPoolv3Address })), + }) +); + export const useChainlinkAbiStore = create() ( (set) => ({ chainlinkAbi: chainlinkAbi, @@ -180,6 +190,14 @@ export const useStakingPoolv2AbiStore = create()( }) ); +export const useStakingPoolv3AbiStore = create()( + (set) => ({ + stakingPoolv3Abi: stakingPoolv3Abi, + getLiquidationPoolAbi: (stakingPoolv3Abi) => + set(() => ({ stakingPoolv3Abi: stakingPoolv3Abi })), + }) +); + export const useErc20AbiStore = create() ( (set) => ({ erc20Abi: erc20Abi, From 636db4c2de08f8ca5032a2b36178889f6c33947b Mon Sep 17 00:00:00 2001 From: Zak Date: Thu, 31 Oct 2024 13:34:45 +0000 Subject: [PATCH 4/8] feat: add more explanation to yield ratio explanation --- .../vault/yield/YieldDepositModal.jsx | 23 +++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/src/components/vault/yield/YieldDepositModal.jsx b/src/components/vault/yield/YieldDepositModal.jsx index 26b1acb..f0322d6 100644 --- a/src/components/vault/yield/YieldDepositModal.jsx +++ b/src/components/vault/yield/YieldDepositModal.jsx @@ -161,18 +161,33 @@ const YieldDepositModal = (props) => { > - Choose Stable Ratio + Choose Your Allocation + + + + Balancing Stability & Growth Potential - Choose how much volatile collateral you want to use to earn a yield, and what percentage you would like in safer, correlated, stable asset yield strategies. -
- Recommended 10% stable. + Volatile pools (like {assetYield.pair[0]}/{assetYield.pair[1]}) can unlock high returns from UNISWAP trading fees, though short-term price shifts may temporarily impact returns. Over time, fees often outpace impermanent loss (IL) fluctuations, leading to positive yields.
+ + We require at least 10% of your collateral in the correlated stable pool (USDs/USDC). Stable pools provide consistent, lower-risk yields, shielding a portion of your assets from the effects of impermanent loss. You can increase this allocation for even more stability. + + + + By choosing your mix, you're setting up for both the potential rewards of volatility and the steady benefits of stability. +
From d7e76e51a16199014934de44c87599e970be49e5 Mon Sep 17 00:00:00 2001 From: Zak Date: Fri, 1 Nov 2024 10:01:36 +0000 Subject: [PATCH 5/8] feat: remove option to compound --- src/components/staking-pool/ClaimingRewardsModal.jsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/staking-pool/ClaimingRewardsModal.jsx b/src/components/staking-pool/ClaimingRewardsModal.jsx index ad9d7c4..ab85edf 100644 --- a/src/components/staking-pool/ClaimingRewardsModal.jsx +++ b/src/components/staking-pool/ClaimingRewardsModal.jsx @@ -148,7 +148,7 @@ const ClaimingRewardsModal = ({ Claiming your rewards will end your current staking period and restart a new one. - + {/* By opting to compound your EUROs rewards, those EUROs will be added to the EUROs in your new stake.
@@ -157,7 +157,7 @@ const ClaimingRewardsModal = ({ onChange={() => setCompound(!compound)} label="I would like to compound my EUROs rewards" /> -
+
*/}
+ + + + + + ) +}; + +export default YieldPerformanceModal; \ No newline at end of file From 3f4c4235edd98c0903390d96d14a19cafdfb1a82 Mon Sep 17 00:00:00 2001 From: Zak Date: Wed, 6 Nov 2024 09:59:04 +0000 Subject: [PATCH 7/8] feat: improved yield pool ux --- src/components/vault/yield/YieldParent.jsx | 176 +++---------- .../vault/yield/YieldPerformanceModal.jsx | 8 - src/components/vault/yield/YieldSummary.jsx | 245 ++++++++++++++++++ 3 files changed, 277 insertions(+), 152 deletions(-) create mode 100644 src/components/vault/yield/YieldSummary.jsx diff --git a/src/components/vault/yield/YieldParent.jsx b/src/components/vault/yield/YieldParent.jsx index daff187..10a5dd1 100644 --- a/src/components/vault/yield/YieldParent.jsx +++ b/src/components/vault/yield/YieldParent.jsx @@ -8,19 +8,15 @@ import axios from "axios"; import { AdjustmentsHorizontalIcon, - QuestionMarkCircleIcon } from '@heroicons/react/24/outline'; -import { - Tooltip, -} from 'react-daisyui'; - import { useVaultAddressStore, useSmartVaultABIStore, } from "../../../store/Store"; import YieldList from "./YieldList"; +import YieldSummary from "./YieldSummary"; import Card from "../../ui/Card"; import Typography from "../../ui/Typography"; @@ -168,6 +164,9 @@ const Vault = (props) => { const netMarketReturnsUSD = userHypervisor?.returns?.netMarketReturnsUSD; const hypervisorReturnsUSD = userHypervisor?.returns?.hypervisorReturnsUSD; + const hypervisorReturnsPercentage = userHypervisor?.returns?.hypervisorReturnsPercentage; + const netMarketReturnsPercentage = userHypervisor?.returns?.netMarketReturnsPercentage; + totalUSD.initialTokenUSD = totalUSD.initialTokenUSD + initialTokenUSD; totalUSD.currentUSD = totalUSD.currentUSD + currentUSD; totalUSD.netMarketReturnsUSD = totalUSD.netMarketReturnsUSD + netMarketReturnsUSD; @@ -177,6 +176,10 @@ const Vault = (props) => { hypervisor: useAddress, initialTokenUSD: initialTokenUSD, currentUSD: currentUSD, + netMarketReturnsUSD: netMarketReturnsUSD, + hypervisorReturnsUSD: hypervisorReturnsUSD, + hypervisorReturnsPercentage: hypervisorReturnsPercentage, + netMarketReturnsPercentage: netMarketReturnsPercentage, }); }); @@ -191,146 +194,31 @@ const Vault = (props) => { {yieldEnabled ? ( <> {yieldData && yieldData.length ? ( - -
- - {/* */} - -
- -
- - Total Yield Earned - - - - - - {gammaUserLoading ? ( - <> - - - ) : ( - <> - {userSummary?.totalUSD?.hypervisorReturnsUSD ? ( - <> - {userSummary?.totalUSD?.hypervisorReturnsUSD.toFixed(2) < 0 ? ( - '-$' - ) : ( - '$' - )} - { - Math.abs( - userSummary?.totalUSD?.hypervisorReturnsUSD - )?.toFixed(2) || '' - } - - ) : ( - '' - )} - - )} - -
-
- - Total Yield Earned (Market) - - - - - - {gammaUserLoading ? ( - <> - - - ) : ( - - {userSummary?.totalUSD?.netMarketReturnsUSD ? ( - <> - {userSummary?.totalUSD?.netMarketReturnsUSD.toFixed(2) < 0 ? ( - '-$' - ) : ( - '$' - )} - { - Math.abs( - userSummary?.totalUSD?.netMarketReturnsUSD - )?.toFixed(2) || '' - } - - ) : ( - '' - )} - - )} - -
-
- - Total Balance - - - - - - {gammaUserLoading ? ( - <> - - - ) : ( - <> - ${userSummary?.totalUSD?.currentUSD?.toFixed(2) || ''} - - )} - -
- + <> + +
+
-
- + + +
+ +
+
+ ) : (
diff --git a/src/components/vault/yield/YieldPerformanceModal.jsx b/src/components/vault/yield/YieldPerformanceModal.jsx index 4e51916..0d6c71f 100644 --- a/src/components/vault/yield/YieldPerformanceModal.jsx +++ b/src/components/vault/yield/YieldPerformanceModal.jsx @@ -3,14 +3,6 @@ import Modal from "../../ui/Modal"; import Typography from "../../ui/Typography"; import TokenIcon from "../../ui/TokenIcon"; -import { - QuestionMarkCircleIcon -} from '@heroicons/react/24/outline'; - -import { - Tooltip, -} from 'react-daisyui'; - const formatUSD = (value) => { if (value) { return new Intl.NumberFormat('en-US', { diff --git a/src/components/vault/yield/YieldSummary.jsx b/src/components/vault/yield/YieldSummary.jsx new file mode 100644 index 0000000..19fbd92 --- /dev/null +++ b/src/components/vault/yield/YieldSummary.jsx @@ -0,0 +1,245 @@ +import Typography from "../../ui/Typography"; + +import { + PresentationChartLineIcon +} from '@heroicons/react/24/outline'; + +const formatUSD = (value) => { + if (value) { + return new Intl.NumberFormat('en-US', { + style: 'currency', + currency: 'USD', + minimumFractionDigits: 2, + maximumFractionDigits: 2 + }).format(Math.abs(value)); + } +}; + +const formatPercentage = (value) => { + if (value) { + return `${value >= 0 ? '+' : ''}${value.toFixed(2)}%`; + } +}; + +const YieldSummary = ({ + gammaUserLoading, + userSummary +}) => { + + + let positions = []; + + if (userSummary && userSummary.hypervisors && userSummary.hypervisors.length) { + positions = userSummary.hypervisors; + } + + const calculateMetrics = (positions) => { + let totalBalance = 0; + let totalYieldEarned = 0; + let totalMarketYield = 0; + let weightedYieldSum = 0; + let weightedMarketYieldSum = 0; + + // First pass - calculate totals + positions.forEach(position => { + totalBalance += position.currentUSD; + totalYieldEarned += position.hypervisorReturnsUSD; + totalMarketYield += position.netMarketReturnsUSD; + }); + + // Second pass - calculate weighted percentages + positions.forEach(position => { + const weight = position.currentUSD / totalBalance; + weightedYieldSum += (parseFloat(position.hypervisorReturnsPercentage) * weight); + weightedMarketYieldSum += (parseFloat(position.netMarketReturnsPercentage) * weight); + }); + + return { + totalBalance, + totalYieldEarned, + totalMarketYield, + weightedAverageYieldAPY: weightedYieldSum, + weightedAverageMarketAPY: weightedMarketYieldSum + }; + }; + + const getPerformanceMessage = (yieldEarned, marketYield) => { + if (yieldEarned > 0 && yieldEarned > marketYield) { + return "Your assets are earning additional yield through TheStandard's smart pools! 🎉"; + } else if (yieldEarned > 0) { + return "Steadily earning yields while protecting your assets 📈"; + } else if (yieldEarned > marketYield) { + return "TheStandard's yield pools are helping protect your assets during market movement"; + } else { + return "Building trading fees to optimize your returns 🔄"; + } + }; + + const getAnalysisMessage = (metrics) => { + const { + totalYieldEarned, + totalMarketYield, + weightedAverageYieldAPY, + weightedAverageMarketAPY, + totalBalance + } = metrics; + + // Market is up, strategy outperforming + if (totalMarketYield > 0 && totalYieldEarned > totalMarketYield) { + return ( + + Your {formatUSD(totalBalance)} position has gained {formatUSD(totalMarketYield)} ({formatPercentage(weightedAverageMarketAPY)}) from market movement, plus an additional {formatUSD(totalYieldEarned - totalMarketYield)} through TheStandard's yield generation, bringing your total gains to {formatUSD(totalYieldEarned)} ({formatPercentage(weightedAverageYieldAPY)}). + + ); + } + + // Market is up, strategy underperforming but positive + if (totalMarketYield > 0 && totalYieldEarned > 0) { + return ( + + While the market has moved up {formatUSD(totalMarketYield)} ({formatPercentage(weightedAverageMarketAPY)}), your position has generated {formatUSD(totalYieldEarned)} ({formatPercentage(weightedAverageYieldAPY)}) in yields as TheStandard's pools continue accumulating trading fees. This difference typically balances out over time as more fees are collected. + + ); + } + + // Market is down, strategy positive + if (totalMarketYield < 0 && totalYieldEarned > 0) { + return ( + + Despite a market decline that would have resulted in a {formatUSD(totalMarketYield)} loss ({formatPercentage(weightedAverageMarketAPY)}), TheStandard's yield strategy has generated {formatUSD(totalYieldEarned)} ({formatPercentage(weightedAverageYieldAPY)}) in positive returns through effective fee collection, helping protect your {formatUSD(totalBalance)} position. + + ); + } + + // Market is down, strategy down but outperforming + if (totalMarketYield < 0 && totalYieldEarned > totalMarketYield) { + return ( + + During this market downturn that would have resulted in a {formatUSD(totalMarketYield)} loss ({formatPercentage(weightedAverageMarketAPY)}), TheStandard's yield pools have significantly reduced the impact to just {formatUSD(totalYieldEarned)} ({formatPercentage(weightedAverageYieldAPY)}) through active fee generation, protecting your {formatUSD(totalBalance)} position from {formatUSD(totalMarketYield - totalYieldEarned)} in additional losses. + + ); + } + + // Market is neutral/down, strategy building + return ( + + Your {formatUSD(totalBalance)} position is actively accumulating trading fees in TheStandard's yield pools. While the market impact is {formatUSD(totalMarketYield)} ({formatPercentage(weightedAverageMarketAPY)}), the strategy is building reserves through trading fees, currently at {formatUSD(totalYieldEarned)} ({formatPercentage(weightedAverageYieldAPY)}). This approach typically shows its strength over longer holding periods as fees accumulate. + + ); + }; + + const getYieldColor = (value) => value > 0 ? 'text-green-500' : 'text-amber-500'; + + const metrics = calculateMetrics(positions); + + return ( + <> + +
+
+ + + Yield Pool Summary + + + {getPerformanceMessage(metrics.totalYieldEarned, metrics.totalMarketYield)} + +
+
+ + Assets in Yield Pools + + {gammaUserLoading ? ( + <> + + + ) : ( + <> + + {formatUSD(metrics.totalBalance)} + + + )} +
+
+ + Yield Generated + +
+ {gammaUserLoading ? ( + <> + + + ) : ( + <> + + {formatUSD(metrics.totalYieldEarned)} + + + {formatPercentage(metrics.weightedAverageYieldAPY)} APY + + + )} +
+
+
+
+ + Market Movement + + + If held without yield pools + +
+
+ {gammaUserLoading ? ( + <> + + + ) : ( + <> + = 0 ? 'text-green-500' : 'text-red-400'}`} + > + {formatUSD(metrics.totalMarketYield)} + + = 0 ? 'text-green-500' : 'text-red-400'}`} + > + {formatPercentage(metrics.weightedAverageMarketAPY)} + + + )} +
+
+
+ +
+
+
+
+ 💡 +
+
+ Position Analysis: + {getAnalysisMessage(metrics)} +
+
+
+
+ + + ) +}; + +export default YieldSummary; \ No newline at end of file From a05eaf84fd5186f594aab4872e797f1c619cc89c Mon Sep 17 00:00:00 2001 From: Zak Date: Wed, 6 Nov 2024 10:09:14 +0000 Subject: [PATCH 8/8] chore: temp hide new staking in frontend --- src/App.jsx | 6 +- src/components/ui/SideNav.jsx | 8 +- src/pages/staking-pool/StakingPoolLegacy.jsx | 182 +++++++++++++++++++ 3 files changed, 190 insertions(+), 6 deletions(-) create mode 100644 src/pages/staking-pool/StakingPoolLegacy.jsx diff --git a/src/App.jsx b/src/App.jsx index cb1470a..3029833 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -11,6 +11,7 @@ import Vault from './pages/vault/Vault'; import VaultHistory from './pages/vault/VaultHistory'; import LiquidationPools from './pages/liquidation-pools/LiquidationPools'; import TstStaking from './pages/tst-staking/TstStaking'; +import StakingPool from './pages/staking-pool/StakingPoolLegacy'; import LegacyPools from './pages/legacy-pools/LegacyPools'; import TermsOfUse from './pages/TermsOfUse'; import Dex from './pages/dex/Dex'; @@ -32,8 +33,9 @@ function App() { } /> } /> } /> - } /> - } /> + {/* } /> */} + {/* } /> */} + } /> } /> } /> } /> diff --git a/src/components/ui/SideNav.jsx b/src/components/ui/SideNav.jsx index 97e0fcb..9041c89 100644 --- a/src/components/ui/SideNav.jsx +++ b/src/components/ui/SideNav.jsx @@ -93,7 +93,7 @@ const SideNav = (props) => { Cross-Chain Dex - + {/* @@ -107,7 +107,7 @@ const SideNav = (props) => { Legacy Pools - + */}
{/* Med + */}
@@ -172,7 +172,7 @@ const SideNav = (props) => { - @@ -191,7 +191,7 @@ const SideNav = (props) => { - + */}
diff --git a/src/pages/staking-pool/StakingPoolLegacy.jsx b/src/pages/staking-pool/StakingPoolLegacy.jsx new file mode 100644 index 0000000..09380d4 --- /dev/null +++ b/src/pages/staking-pool/StakingPoolLegacy.jsx @@ -0,0 +1,182 @@ +import { useState } from "react"; +import moment from 'moment'; +import { + useReadContract, + useAccount, + useChainId, + useWatchBlockNumber +} from "wagmi"; +import { arbitrumSepolia } from "wagmi/chains"; +import { + ArrowTrendingUpIcon, + BanknotesIcon, +} from '@heroicons/react/24/outline'; + +import { + useStakingPoolv2AbiStore, + useStakingPoolv2AddressStore, +} from "../../store/Store"; + +import StakingIncrease from "../../components/staking-pool/StakingIncrease"; +import StakingAssets from "../../components/staking-pool/StakingAssets"; +import StakingRewards from "../../components/staking-pool/StakingRewards"; +import VolumeChart from "../../components/staking-pool/VolumeChart"; +import ValueChart from "../../components/staking-pool/ValueChart"; + +import Card from "../../components/ui/Card"; +import CenterLoader from "../../components/ui/CenterLoader"; +import Typography from "../../components/ui/Typography"; +import Button from "../../components/ui/Button"; + +const StakingPool = (props) => { + const { stakingPoolv2Abi } = useStakingPoolv2AbiStore(); + const [showValue, setShowValue] = useState(false); + + const { + arbitrumSepoliaStakingPoolv2Address, + arbitrumStakingPoolv2Address, + } = useStakingPoolv2AddressStore(); + + const { address } = useAccount(); + const chainId = useChainId(); + + const stakingPoolv2Address = + chainId === arbitrumSepolia.id + ? arbitrumSepoliaStakingPoolv2Address + : arbitrumStakingPoolv2Address; + + const { data: poolPositions, refetch: refetchPositions } = useReadContract({ + address: stakingPoolv2Address, + abi: stakingPoolv2Abi, + functionName: "positions", + args: [address], + }); + + const { data: poolRewards, isLoading: poolRewardsLoading, refetch: refetchRewards } = useReadContract({ + address: stakingPoolv2Address, + abi: stakingPoolv2Abi, + functionName: "projectedEarnings", + args: [address], + }); + + const { data: dailyYield, refetch: refetchDailyReward } = useReadContract({ + address: stakingPoolv2Address, + abi: stakingPoolv2Abi, + functionName: "dailyYield", + args: [], + }); + + useWatchBlockNumber({ + onBlockNumber() { + refetchPositions(); + refetchRewards(); + refetchDailyReward(); + }, + }) + + const positions = poolPositions; + const rewards = poolRewards; + const dailyRewards = dailyYield; + + + let sEuroAmount = 0n; + let collaterals = []; + + if (rewards && rewards[0]) { + sEuroAmount = rewards[0] || 0n; + } + if (rewards && rewards[1]) { + collaterals = rewards[1] || []; + } + + let sEuroDaily = 0n; + let collatDaily = []; + + if (dailyRewards && dailyRewards[0]) { + sEuroDaily = dailyRewards[0] || 0n; + } + if (dailyRewards && dailyRewards[1]) { + collatDaily = dailyRewards[1] || []; + } + + let stakedSince = 0; + if (positions && positions[0]) { + stakedSince = positions[0] || 0; + } + + let useStakedSince; + if (stakedSince) { + useStakedSince = moment.unix(Number(stakedSince)).format('Do MMM YYYY'); + } + + return ( +
+
+ +
+ +
+ +
+ +
+ +
+ + {showValue ? ( + 'Asset Value' + ) : ( + 'Asset Totals' + )} + + + {showValue ? ( + <> + + + ) : ( + <> + + + )} +
+
+
+ +
+ {poolRewardsLoading ? ( + +
+ +
+
+ ) : ( + + )} +
+ +
+ ); +}; + +export default StakingPool; \ No newline at end of file