diff --git a/frontend/src/components/GeyserFirst/GeyserStats.tsx b/frontend/src/components/GeyserFirst/GeyserStats.tsx index aea0130..ef921af 100644 --- a/frontend/src/components/GeyserFirst/GeyserStats.tsx +++ b/frontend/src/components/GeyserFirst/GeyserStats.tsx @@ -10,16 +10,14 @@ import { DAY_IN_SEC, TOTAL_REWARDS_MSG } from '../../constants' export const GeyserStats = () => { const { - geyserStats: { duration, totalDeposit, totalRewards }, + geyserStats: { duration, totalDepositVal, totalRewards, totalRewardsVal }, } = useContext(StatsContext) const { selectedGeyserInfo: { - rewardTokenInfo: { symbol: rewardTokenSymbol, price: rewardTokenPrice }, + rewardTokenInfo: { symbol: rewardTokenSymbol }, }, } = useContext(GeyserContext) - const baseRewards = totalRewards * rewardTokenPrice - return (
Geyser Stats
@@ -34,7 +32,7 @@ export const GeyserStats = () => { safeNumeral(val, '0,0')} /> @@ -43,7 +41,7 @@ export const GeyserStats = () => { safeNumeral(val, '0,0')} tooltipMessage={{ @@ -55,12 +53,12 @@ export const GeyserStats = () => { rows={[ { label: `${rewardTokenSymbol} (${safeNumeral(totalRewards, '0,0')})`, - value: `${safeNumeral(baseRewards, '0,0.00')} USD`, + value: `${safeNumeral(totalRewardsVal, '0,0.00')} USD`, }, { label: 'bonus (0)', value: `${safeNumeral(0, '0,0.00')} USD` }, ]} totalLabel="Total" - totalValue={`${safeNumeral(baseRewards, '0,0.00')} USD`} + totalValue={`${safeNumeral(totalRewardsVal, '0,0.00')} USD`} /> ), diff --git a/frontend/src/components/GeyserFirst/MyStats.tsx b/frontend/src/components/GeyserFirst/MyStats.tsx index 302c345..3c92b98 100644 --- a/frontend/src/components/GeyserFirst/MyStats.tsx +++ b/frontend/src/components/GeyserFirst/MyStats.tsx @@ -42,7 +42,7 @@ export const MyStats = () => { setFinalAPY(Math.min(geyserAPYNew + lpAPYNew, 100000)) }, [selectedGeyser, apy]) - // NOTE: removed bonus tokens + // TODO: handle bonus tokens const baseRewards = currentReward * rewardTokenPrice return ( diff --git a/frontend/src/components/GeyserFirst/UnstakeSummary.tsx b/frontend/src/components/GeyserFirst/UnstakeSummary.tsx index 0423eba..200925a 100644 --- a/frontend/src/components/GeyserFirst/UnstakeSummary.tsx +++ b/frontend/src/components/GeyserFirst/UnstakeSummary.tsx @@ -20,18 +20,15 @@ export const UnstakeSummary: React.FC = ({ userInput, parsedUserInput }) stakingTokenInfo: { symbol: stakingTokenSymbol, price: stakingTokenPrice }, }, } = useContext(GeyserContext) - const { - geyserStats: { bonusRewards }, - computeRewardsFromUnstake, - computeRewardsShareFromUnstake, - } = useContext(StatsContext) + const { computeRewardsFromUnstake, computeRewardsShareFromUnstake } = useContext(StatsContext) const [rewardAmount, setRewardAmount] = useState(0.0) - const [rewardsShare, setRewardsShare] = useState(0.0) + // const [rewardsShare, setRewardsShare] = useState(0.0) const unstakeUSD = parseFloat(userInput) * stakingTokenPrice - const rewardUSD = rewardAmount * rewardTokenPrice + bonusRewards.reduce((m, b) => m + rewardsShare * b.value, 0) - + const rewardUSD = rewardAmount * rewardTokenPrice + // TODO: handle bonus rewards + // bonusRewards.reduce((m, b) => m + rewardsShare * b.value, 0) useEffect(() => { let isMounted = true ;(async () => { @@ -78,12 +75,12 @@ export const UnstakeSummary: React.FC = ({ userInput, parsedUserInput }) {rewardTokenSymbol} - {bonusRewards.map((b) => ( + {/* {bonusRewards.map((b) => ( {safeNumeral(rewardsShare * b.balance, '0.000')} {b.symbol} - ))} + ))} */} diff --git a/frontend/src/components/Home.tsx b/frontend/src/components/Home.tsx index 0ae41fa..edbf2f7 100644 --- a/frontend/src/components/Home.tsx +++ b/frontend/src/components/Home.tsx @@ -10,18 +10,14 @@ import { Loader } from 'styling/styles' import { toChecksumAddress } from 'web3-utils' import TokenIcons from 'components/TokenIcons' import { safeNumeral } from 'utils/numeral' -import { getGeyserTotalDeposit } from 'utils/stats' -import { formatUnits } from 'ethers/lib/utils' +import { getGeyserTotalDeposit, getGeyserTotalRewards } from 'utils/stats' import { getPlatformConfig } from 'config/app' -const nowInSeconds = () => Math.round(Date.now() / 1000) - export const Home = () => { const { geysers, getGeyserConfig, allTokensInfos, stakeAPYs } = useContext(GeyserContext) const { selectedVault } = useContext(VaultContext) const navigate = useNavigate() const stakedGeysers = selectedVault ? selectedVault.locks.map((l) => l.geyser) : [] - const now = nowInSeconds() const tokensByAddress = allTokensInfos.reduce((acc, t) => { acc[toChecksumAddress(t.address)] = t return acc @@ -39,16 +35,7 @@ export const Home = () => { const apy = lpAPY + geyserAPY const programName = extractProgramName(config.name) const platform = getPlatformConfig(config) - - let rewards = 0 - if (rewardTokenInfo) { - // NOTE: This math doesn't work if AMPL is the reward token as it doesn't account for rebasing! - const rewardAmt = g.rewardSchedules - .filter((s) => parseInt(s.start, 10) + parseInt(s.duration, 10) > now) - .reduce((m, s) => m + parseFloat(formatUnits(s.rewardAmount, rewardTokenInfo.decimals)), 0) - rewards = rewardAmt * rewardTokenInfo.price - } - + const rewards = rewardTokenInfo ? getGeyserTotalRewards(g, rewardTokenInfo) : 0 const isStablePool = config.name.includes('USDC') && config.name.includes('SPOT') const poolType = `${isStablePool ? 'Stable' : 'Vol'}[${stakingTokens.join('/')}]` return { diff --git a/frontend/src/types.ts b/frontend/src/types.ts index f26fd28..8e7715c 100644 --- a/frontend/src/types.ts +++ b/frontend/src/types.ts @@ -88,7 +88,6 @@ export type StakingTokenInfo = TokenInfo & { export type RewardTokenInfo = TokenInfo & { price: number - getTotalRewards: (rewardSchedules: RewardSchedule[]) => Promise } export type BonusTokenInfo = TokenInfo & { @@ -114,10 +113,13 @@ export type RewardStats = { export type GeyserStats = { duration: number totalDeposit: number + totalDepositVal: number totalRewards: number + totalRewardVal: number calcPeriodInDays: number unlockedRewards: number bonusRewards: RewardStats[] + bonusRewardsVal: number } export type VaultTokenBalance = TokenInfo & { diff --git a/frontend/src/utils/rewardToken.ts b/frontend/src/utils/rewardToken.ts index 2a7ef7c..33c9919 100644 --- a/frontend/src/utils/rewardToken.ts +++ b/frontend/src/utils/rewardToken.ts @@ -1,28 +1,14 @@ -import { Contract } from 'ethers' -import { formatUnits } from 'ethers/lib/utils' import { RewardToken, MIN_IN_MS } from '../constants' -import { RewardSchedule, RewardTokenInfo, SignerOrProvider } from '../types' -import { UFRAGMENTS_ABI } from './abis/UFragments' -import { UFRAGMENTS_POLICY_ABI } from './abis/UFragmentsPolicy' -import { XC_AMPLE_ABI } from './abis/XCAmple' -import { XC_CONTROLLER_ABI } from './abis/XCController' -import { computeAMPLRewardShares } from './ampleforth' +import { RewardTokenInfo, SignerOrProvider } from '../types' import { defaultTokenInfo, getTokenInfo } from './token' import { getCurrentPrice } from './price' import * as ls from './cache' const cacheTimeMs = 30 * MIN_IN_MS -const nowInSeconds = () => Math.round(Date.now() / 1000) -function filterActiveRewardSchedules(rewardSchedules) { - const now = nowInSeconds() - return rewardSchedules.filter((s) => parseInt(s.start, 10) + parseInt(s.duration, 10) > now) -} - export const defaultRewardTokenInfo = (): RewardTokenInfo => ({ ...defaultTokenInfo(), price: 1, - getTotalRewards: async () => 0, }) export const getRewardTokenInfo = async ( @@ -36,7 +22,7 @@ export const getRewardTokenInfo = async ( case RewardToken.AMPL: return getAMPLToken(tokenAddress, signerOrProvider) case RewardToken.XCAMPLE: - return getXCAMPLToken(tokenAddress, signerOrProvider) + return getAMPLToken(tokenAddress, signerOrProvider) case RewardToken.WAMPL: return getBasicToken(tokenAddress, signerOrProvider) case RewardToken.SPOT: @@ -48,8 +34,8 @@ export const getRewardTokenInfo = async ( } } -const getBasicToken = async (tokenAddress: string, signerOrProvider: SignerOrProvider): Promise => { - const rewardTokenInfo = await ls.computeAndCache( +const getBasicToken = async (tokenAddress: string, signerOrProvider: SignerOrProvider): Promise => + ls.computeAndCache( async function () { const tokenInfo = await getTokenInfo(tokenAddress, signerOrProvider) const price = await getCurrentPrice(tokenInfo.symbol) @@ -58,95 +44,14 @@ const getBasicToken = async (tokenAddress: string, signerOrProvider: SignerOrPro `rewardTokenInfo:${tokenAddress}`, cacheTimeMs, ) - rewardTokenInfo.getTotalRewards = async (rewardSchedules: RewardSchedule[]) => - filterActiveRewardSchedules(rewardSchedules).reduce( - (acc, schedule) => acc + parseFloat(formatUnits(schedule.rewardAmount, 0)), - 0, - ) - return rewardTokenInfo -} - -// TODO: use subgraph to get AMPL supply history -const getAMPLToken = async (tokenAddress: string, signerOrProvider: SignerOrProvider): Promise => { - const rewardTokenInfo = await ls.computeAndCache( - async function () { - const tokenInfo = await getTokenInfo(tokenAddress, signerOrProvider) - const price = await getCurrentPrice('AMPL') - return { price, ...tokenInfo } - }, - `rewardTokenInfo:${tokenAddress}`, - cacheTimeMs, - ) - - rewardTokenInfo.amplInfo = await ls.computeAndCache( - async function () { - const contract = new Contract(tokenAddress, UFRAGMENTS_ABI, signerOrProvider) - const policyAddress: string = await contract.monetaryPolicy() - const policy = new Contract(policyAddress, UFRAGMENTS_POLICY_ABI, signerOrProvider) - const totalSupply = await contract.totalSupply() - const epoch = await policy.epoch() - return { policyAddress, epoch, totalSupply } - }, - `amplRewardTokenInfo:${tokenAddress}`, - cacheTimeMs, - ) - - rewardTokenInfo.getTotalRewards = async (rewardSchedules: RewardSchedule[]) => { - const tokenInfo = rewardTokenInfo - const ampl = rewardTokenInfo.amplInfo - const totalRewardShares = await computeAMPLRewardShares( - filterActiveRewardSchedules(rewardSchedules), - tokenAddress, - ampl.policyAddress, - false, - parseInt(ampl.epoch, 10), - tokenInfo.decimals, - signerOrProvider, - ) - return totalRewardShares * formatUnits(ampl.totalSupply, tokenInfo.decimals) - } - - return rewardTokenInfo -} -const getXCAMPLToken = async (tokenAddress: string, signerOrProvider: SignerOrProvider): Promise => { - const rewardTokenInfo = await ls.computeAndCache( +const getAMPLToken = async (tokenAddress: string, signerOrProvider: SignerOrProvider): Promise => + ls.computeAndCache( async function () { const tokenInfo = await getTokenInfo(tokenAddress, signerOrProvider) const price = await getCurrentPrice('AMPL') return { price, ...tokenInfo } }, `rewardTokenInfo:${tokenAddress}`, - 0, - ) - - rewardTokenInfo.amplInfo = await ls.computeAndCache( - async function () { - const token = new Contract(tokenAddress, XC_AMPLE_ABI, signerOrProvider) - const controllerAddress: string = await token.controller() - const controller = new Contract(controllerAddress, XC_CONTROLLER_ABI, signerOrProvider) - const totalSupply = await token.globalAMPLSupply() - const epoch = await controller.globalAmpleforthEpoch() - return { epoch, totalSupply } - }, - `xcAmplRewardTokenInfo:${tokenAddress}`, cacheTimeMs, ) - - rewardTokenInfo.getTotalRewards = async (rewardSchedules: RewardSchedule[]) => { - const tokenInfo = rewardTokenInfo - const ampl = rewardTokenInfo.amplInfo - const totalRewardShares = await computeAMPLRewardShares( - filterActiveRewardSchedules(rewardSchedules), - tokenAddress, - controllerAddress, - true, - parseInt(ampl.epoch, 10), - tokenInfo.decimals, - signerOrProvider, - ) - return totalRewardShares * formatUnits(ampl.totalSupply, tokenInfo.decimals) - } - - return rewardTokenInfo -} diff --git a/frontend/src/utils/stats.ts b/frontend/src/utils/stats.ts index 6b2b4cb..34359cb 100644 --- a/frontend/src/utils/stats.ts +++ b/frontend/src/utils/stats.ts @@ -36,10 +36,13 @@ export const defaultUserStats = (): UserStats => ({ export const defaultGeyserStats = (): GeyserStats => ({ duration: 0, totalDeposit: 0, + totalDepositVal: 0, totalRewards: 0, + totalRewardsVal: 0, calcPeriodInDays: 0, unlockedRewards: 0, bonusRewards: [], + bonusRewardsVal: 0, }) export const defaultVaultStats = (): VaultStats => ({ @@ -74,6 +77,13 @@ export const getGeyserTotalDeposit = (geyser: Geyser, stakingTokenInfo: StakingT return stakingTokenAmount * stakingTokenInfo.price } +export const getGeyserTotalRewards = (geyser: Geyser, rewardTokenInfo: RewardTokenInfo) => { + const { rewardBalance } = geyser + const { decimals } = rewardTokenInfo + const rewardAmt = parseFloat(formatUnits(rewardBalance, decimals)) + return rewardAmt * rewardTokenInfo.price +} + export const getGeyserStats = async ( geyser: Geyser, stakingTokenInfo: StakingTokenInfo, @@ -83,16 +93,17 @@ export const getGeyserStats = async ( ls.computeAndCache( async () => ({ duration: getGeyserDuration(geyser), - totalDeposit: getGeyserTotalDeposit(geyser, stakingTokenInfo), - totalRewards: - (await rewardTokenInfo.getTotalRewards(geyser.rewardSchedules)) / 10 ** (rewardTokenInfo.decimals || 1), calcPeriodInDays: getCalcPeriod(geyser) / DAY_IN_SEC, - unlockedRewards: parseFloat(geyser.unlockedReward) / 10 ** (rewardTokenInfo.decimals || 1), + totalDeposit: parseFloat(formatUnits(geyser.totalStake, stakingTokenInfo.decimals)), + totalDepositVal: getGeyserTotalDeposit(geyser, stakingTokenInfo), + totalRewards: parseFloat(formatUnits(geyser.rewardBalance, rewardTokenInfo.decimals)), + totalRewardsVal: getGeyserTotalRewards(geyser, rewardTokenInfo), + unlockedRewards: parseFloat(formatUnits(geyser.unlockedReward, rewardTokenInfo.decimals)), bonusRewards: geyser.rewardPoolBalances.length === bonusTokensInfo.length ? geyser.rewardPoolBalances.map((b, i) => { const info = bonusTokensInfo[i] - const balance = parseFloat(b.balance) / 10 ** info.decimals + const balance = parseFloat(formatUnits(b.balance, info.decimals)) return { name: info.name, symbol: info.symbol, @@ -101,6 +112,14 @@ export const getGeyserStats = async ( } }) : [], + bonusRewardsVal: + geyser.rewardPoolBalances.length === bonusTokensInfo.length + ? geyser.rewardPoolBalances.reduce((m, b, i) => { + const info = bonusTokensInfo[i] + const balance = parseFloat(formatUnits(b.balance, info.decimals)) + return m + info.price * balance + }, 0) + : 0, }), `${toChecksumAddress(geyser.id)}|stats`, statsCacheTimeMs, @@ -234,8 +253,6 @@ export const getUserAPY = async ( const rewardPool = parseFloat(geyser.rewardBalance) / 10 ** rewardTokenDecimals const rewardShare = outflowReward / rewardPool - - // TODO: data layer should guarantee that rewardPoolBalances and bonusTokensInfo are inline const outflowWithBonus = outflow + geyser.rewardPoolBalances.reduce((m, b, i) => {