-
Notifications
You must be signed in to change notification settings - Fork 25
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #268 from curvefi/task/add-crv-incentives-apr
task: add CRV and Incentives APR to Stake form
- Loading branch information
Showing
8 changed files
with
251 additions
and
11 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
{"abi":[{"name":"totalSupply","outputs":[{"type":"uint256","name":""}],"inputs":[],"stateMutability":"view","type":"function"}]} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,126 @@ | ||
import { t } from '@lingui/macro' | ||
|
||
import React, { useMemo } from 'react' | ||
import styled from 'styled-components' | ||
|
||
import { FORMAT_OPTIONS, formatNumber } from '@/ui/utils' | ||
import { INVALID_ADDRESS } from '@/constants' | ||
import useAbiTotalSupply from '@/hooks/useAbiTotalSupply' | ||
import useStore from '@/store/useStore' | ||
import useSupplyTotalApr from '@/hooks/useSupplyTotalApr' | ||
|
||
import DetailInfo from '@/ui/DetailInfo' | ||
import Icon from '@/ui/Icon' | ||
import TooltipIcon from '@/ui/Tooltip/TooltipIcon' | ||
|
||
type Data = { | ||
label: string | ||
tooltip: string | ||
skeleton: [number, number] | ||
aprCurr: string | ||
aprNew: string | ||
ratio: number | ||
} | ||
|
||
const DetailInfoCrvIncentives = ({ | ||
rChainId, | ||
rOwmId, | ||
lpTokenAmount, | ||
}: { | ||
rChainId: ChainId | ||
rOwmId: string | ||
lpTokenAmount: string | ||
}) => { | ||
const { tooltipValues } = useSupplyTotalApr(rChainId, rOwmId) | ||
const owmData = useStore((state) => state.markets.owmDatasMapper[rChainId]?.[rOwmId]) | ||
const { gauge: gaugeAddress } = owmData?.owm?.addresses ?? {} | ||
const gaugeTotalSupply = useAbiTotalSupply(rChainId, gaugeAddress) | ||
const isGaugeAddressInvalid = gaugeAddress === INVALID_ADDRESS | ||
|
||
const { crvBase = '', incentivesObj = [] } = tooltipValues ?? {} | ||
|
||
const data = useMemo(() => { | ||
let data: Data[] = [] | ||
|
||
if (!isGaugeAddressInvalid) { | ||
if (+crvBase > 0) { | ||
data.push({ | ||
label: t`CRV APR:`, | ||
tooltip: t`As the number of staked vault shares increases, the CRV APR will decrease.`, | ||
skeleton: [50, 23], | ||
..._getDataApr(crvBase, gaugeTotalSupply, lpTokenAmount), | ||
}) | ||
} | ||
|
||
if (incentivesObj.length > 0) { | ||
incentivesObj.forEach(({ apy, symbol }) => { | ||
data.push({ | ||
label: t`Incentives ${symbol} APR:`, | ||
tooltip: t`As the number of staked vault shares increases, the ${symbol} APR will decrease.`, | ||
skeleton: [60, 23], | ||
..._getDataApr(apy, gaugeTotalSupply, lpTokenAmount), | ||
}) | ||
}) | ||
} | ||
} | ||
|
||
return data | ||
}, [crvBase, gaugeTotalSupply, incentivesObj, isGaugeAddressInvalid, lpTokenAmount]) | ||
|
||
if (data.length === 0 || isGaugeAddressInvalid) { | ||
return null | ||
} | ||
|
||
return ( | ||
<> | ||
{data.map(({ label, tooltip, skeleton, aprCurr, aprNew, ratio }, idx) => { | ||
return ( | ||
<DetailInfo | ||
key={`${label}${idx}`} | ||
loading={aprCurr === ''} | ||
loadingSkeleton={skeleton ?? [140, 23]} | ||
label={label} | ||
tooltip={ | ||
<StyledTooltipIcon minWidth="200px" textAlign="left" iconStyles={{ $svgTop: '0.2rem' }}> | ||
{tooltip} | ||
</StyledTooltipIcon> | ||
} | ||
> | ||
<strong>{aprCurr}</strong> | ||
{ratio > 1.25 && ( | ||
<> | ||
<StyledIcon name="ArrowRight" size={16} /> <strong>{aprNew}</strong> | ||
</> | ||
)}{' '} | ||
</DetailInfo> | ||
) | ||
})} | ||
</> | ||
) | ||
} | ||
|
||
const StyledTooltipIcon = styled(TooltipIcon)` | ||
margin-left: var(--spacing-1); | ||
` | ||
|
||
const StyledIcon = styled(Icon)` | ||
margin: 0 var(--spacing-1); | ||
` | ||
|
||
export default DetailInfoCrvIncentives | ||
|
||
function _getDataApr( | ||
currApr: string | number | undefined = '', | ||
gaugeTotalSupply: number | null, | ||
lpTokenAmount: string | ||
) { | ||
let resp = { aprCurr: formatNumber(currApr, FORMAT_OPTIONS.PERCENT), aprNew: '', ratio: 0 } | ||
|
||
if (+currApr > 0 && gaugeTotalSupply && +(gaugeTotalSupply || '0') > 0 && +lpTokenAmount > 0) { | ||
const newGaugeTotalLocked = Number(lpTokenAmount) + gaugeTotalSupply | ||
const aprNew = (gaugeTotalSupply / newGaugeTotalLocked) * +currApr | ||
resp.aprNew = formatNumber(aprNew, FORMAT_OPTIONS.PERCENT) | ||
resp.ratio = +currApr / aprNew | ||
} | ||
return resp | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
import type { Contract } from 'ethers' | ||
|
||
import { useCallback, useEffect, useState } from 'react' | ||
|
||
import { INVALID_ADDRESS, REFRESH_INTERVAL } from '@/constants' | ||
import { weiToEther } from '@/shared/curve-lib' | ||
import useContract from '@/hooks/useContract' | ||
import usePageVisibleInterval from '@/ui/hooks/usePageVisibleInterval' | ||
import useStore from '@/store/useStore' | ||
|
||
const useAbiTotalSupply = (rChainId: ChainId, contractAddress: string | undefined) => { | ||
const contract = useContract(rChainId, false, 'totalSupply', contractAddress) | ||
const isValidAddress = contractAddress !== INVALID_ADDRESS | ||
|
||
const isPageVisible = useStore((state) => state.isPageVisible) | ||
|
||
const [totalSupply, settotalSupply] = useState<number | null>(null) | ||
|
||
const getTotalSupply = useCallback(async (contract: Contract) => { | ||
try { | ||
const totalSupply = await contract.totalSupply() | ||
settotalSupply(weiToEther(Number(totalSupply))) | ||
} catch (error) { | ||
console.error(error) | ||
} | ||
}, []) | ||
|
||
useEffect(() => { | ||
if (contract && isValidAddress) getTotalSupply(contract) | ||
}, [contract, isValidAddress, getTotalSupply]) | ||
|
||
usePageVisibleInterval( | ||
() => { | ||
if (contract && isValidAddress) getTotalSupply(contract) | ||
}, | ||
REFRESH_INTERVAL['1m'], | ||
isPageVisible | ||
) | ||
|
||
return totalSupply | ||
} | ||
|
||
export default useAbiTotalSupply |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
import { Contract, Interface, JsonRpcProvider } from 'ethers' | ||
import { useCallback, useEffect, useState } from 'react' | ||
|
||
import networks from '@/networks' | ||
import useStore from '@/store/useStore' | ||
|
||
const useAbiGaugeTotalSupply = ( | ||
rChainId: ChainId, | ||
signerRequired: boolean, | ||
jsonModuleName: string, | ||
contractAddress: string | undefined | ||
) => { | ||
const getProvider = useStore((state) => state.wallet.getProvider) | ||
|
||
const [contract, setContract] = useState<Contract | null>(null) | ||
|
||
const getContract = useCallback( | ||
async (jsonModuleName: string, contractAddress: string, provider: Provider | JsonRpcProvider) => { | ||
try { | ||
const abi = await import(`@/abis/${jsonModuleName}.json`).then((module) => module.default.abi) | ||
|
||
if (!abi) { | ||
console.error('cannot find abi') | ||
return null | ||
} else { | ||
const iface = new Interface(abi) | ||
return new Contract(contractAddress, iface.format(), provider) | ||
} | ||
} catch (error) { | ||
console.error(error) | ||
return null | ||
} | ||
}, | ||
[] | ||
) | ||
|
||
useEffect(() => { | ||
if (rChainId) { | ||
const provider = signerRequired | ||
? getProvider('') | ||
: getProvider('') || new JsonRpcProvider(networks[rChainId].rpcUrl) | ||
|
||
if (jsonModuleName && contractAddress && provider) { | ||
;(async () => setContract(await getContract(jsonModuleName, contractAddress, provider)))() | ||
} | ||
} | ||
}, [contractAddress, getContract, getProvider, jsonModuleName, rChainId, signerRequired]) | ||
|
||
return contract | ||
} | ||
|
||
export default useAbiGaugeTotalSupply |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters