🔎Methodology

vaults.fyi utilizes onchain data to track yields over a given period of time. We query blockchains hourly and obtain the share price of the specific DeFi vaults curated on our platform. This share price, indicative of a lender’s stake in a vault, can be used to calculate yield over time. By sourcing data directly and avoiding external API endpoints, we strive for accuracy and dependability.

For example, when calculating a vault’s 7-day APY on January 7th, we compare the share price on that day with the share price from January 1st and then extrapolate this difference over an entire year. This method is similarly applied when determining 1-day and 30-day APYs.

To efficiently automate this process, we implement specialized smart contracts that fetch and transform data using a single function. These contracts are deployed in a static call, which is temporary and does not affect blockchain storage, thereby incurring any cost. This method allows us to read data directly from the blockchain. Our approach saves on calls and enables the use of one result to calculate the next within the same call, a technique not possible with standard multi-call processes. We use services like Infura, Alchemy, and Ankr RPCs to execute these calls. Once the data is retrieved, it is stored in our backend database.

Calculating APY from historical data

Due to the vast amount of data involved and the limitations in indexing speed, it is impractical to query blockchains every time a new block is added. Instead, we calculate yield using accrued interest, an obtainable figure for most yield-bearing products. This allows APY to be extrapolated from limited data points.

Simple interest

We use the following formula to calculate APY for simple (i.e., not compounding) interest:

APY=interest rate×yeartime \text{APY} = \text{interest rate} \times \frac{\text{year}}{\text{time}}

Compounding interest

To determine compounding interest, we use the following formula:

APY=[(1+interest rate)yeartime]−1\text{APY} = \left[ \left(1 + \text{interest rate}\right)^\frac{\text{year}}{\text{time}} \right] - 1

Interest Rate Calculation

The interest rate at any given point in time is determined by:

interest rate=current share priceprevious share price−1\text{interest rate} = \frac{\text{current share price}}{\text{previous share price}} - 1

Where the share price is:

share price=total shares valuetotal shares\text{share price} = \frac{\text{total shares value}}{\text{total shares}}

Range APY

To offer a more representative APY, especially in scenarios with fluctuating yields, we compute a range APY. This involves calculating a weighted average of interest rates over a specified period (x days).

Weighting Mechanism for Range APY

In calculating the weighted average interest rate for the range APY, each point in time within the range is assigned a weight. This weight is determined by taking the minimum of the TVL at that specific time (TVLti)(TVL_{t_{i}}) and the TVL at the preceding time (TVLti−1)(TVL_{t_{i-1}}).

weightti=min(TVLti,TVLti−1)\text{weight}_{t_{i}}=min(\text{TVL}_{t_{i}},\text{TVL}_{t_{i-1}})

Rationale

The TVL between two consecutive points in time (t_{i} and t_{i-1}) is inherently uncertain. To maintain a conservative approach and avoid overestimating the yield, we choose to underestimate the TVL during that interval by selecting the lower of the two TVL values. This ensures that the calculated APY reflects a more realistic, if not slightly lower, yield.

With this weighting method the average interest rate over a given period is determined by

interest rateti,xday=(∑j:ti−tj=xdayishare pricetjshare pricetj−1∗weighttj∑j:ti−tj=xdayiweighttj)i−j−1\text{interest rate}_{t_{i},xday}=(\frac{\sum_{j:t_{i}-t_{j}=xday}^{i}\frac{\text{share price}_{t_{j}}}{\text{share price}_{t_{j-1}}}*weight_{t_{j}}}{\sum_{j:t_{i}-t_{j}=xday}^{i}weight_{t_{j}}})^{i-j} - 1

Rewards APY

To calculate the rewards APY for a desired time range (1 day, 7 days, 30 days), we:

  1. Estimate the number of reward tokens distributed per token deposited in the vault during the chosen period by taking a weighted average of emissions based on the TVL.

  2. Calculate the average price ratio between the reward token and the deposited token for that period.

  3. Use this ratio to convert the accumulated reward tokens' value to a single deposited token's value.

  4. Compare this converted value to the initial deposit to determine the APY.

The average of the reward token price (PIT - Price In Token) to deposited token price is calculated using the formula:

PIT‾ti,xday=∑j:ti−tj=xdayirewardTokenPriceInUSDtj−1underlyingTokenPriceInUSDtj−1∗(tj−tj−1)ti−tj\overline{PIT}_{t_{i},xday}=\frac{\sum_{j:t_{i}-t_{j}=xday}^{i}\frac{rewardTokenPriceInUSD_{t_{j-1}}}{underlyingTokenPriceInUSD_{t_{j-1}}} * (t_{j}-t_{j-1})}{t_{i}-t_{j}}

The APY at time range of xday is calculated using the formula:

APYti,xday=∑j:ti−tj=xdayiemissionsPerSecondtj−1×year×PIT‾tj,xday×(tj−tj−1)∑j:ti−tj=xdayiTVLtj×(tj−tj−1)\text{APY}_{t_{i},xday}=\frac{\sum_{j:t_{i}-t_{j}=xday}^{i}emissionsPerSecond_{t_{j-1}}\times year\times\overline{PIT}_{t_{j},xday}\times (t_{j}-t_{j-1}) }{\sum_{j:t_{i}-t_{j}=xday}^{i}TVL_{t_{j}}\times (t_{j}-t_{j-1}) }

Obtaining the data we need

In order to execute the calculations outlined above, accessing onchain data is essential. Our method of obtaining a vault's share price depends on the type of vault. You can read more about each method below.

ERC-4626

In the case of ERC-4626 vaults, we can call the totalSupply and totalAssets smart contract functions to calculate share price.

totalSupply = total shares

totalAssets = total shares value

Not ERC-4626

If a vault is not ERC-4626 compliant, proxies for the above functions can usually be found in the following functions:

  • mint

  • deposit

  • burn

  • redeem

You can see an example of this in the code snippets below.

function deposit(uint256 _amount, address _receiver) 
    external 
    nonReentrant 
    isNotPastMaturity 
    whenNotPaused 
    approvedLender(_receiver) 
    returns (uint256 _sharesReceived)
{
    _addInterest();
    VaultAccount memory _totalAsset = totalAsset;
    _sharesReceived = _totalAsset.toShares(_amount, false);
    _deposit(_totalAsset, _amount.toUint128(), _sharesReceived, _receiver);
}
function toShares(
    VaultAccount memory total,
    uint256 amount,
    bool roundUp
) internal pure returns (uint256 shares) {
    if (total.amount == 0) {
        shares = amount;
    } else {
        shares = (amount * total.shares) / total.amount;
        if (roundUp && (shares * total.amount) / total.shares < amount) {
            shares = shares + 1;
        }
    }
}

The functions provided are typical examples and not an exhaustive list; some vaults may require individual solutions.

Liquid staking tokens

To calculate APY for LSTs (e.g., Lido) and other vaults that periodically update their earnings (e.g., Yearn vaults), we use the totalSupply and getEthValue(totalSupply)functions.

totalSupply = total shares

getEthValue(totalSupply)= total shares value

One challenge we face is accounting for the irregular intervals at which some vaults update their earnings — leading to sudden and significant fluctuations in APY. Understanding this nuance is essential for accurately interpreting discrepancies on vaults.fyi.

Rebasing supply vaults

Vaults that gradually increase the user's LP token balance, such as those on Aave, are known as rebasing supply vaults. We calculate the share price for rebasing supply vaults with the totalSupply and scaledTotalSupply functions.

scaledTotalSupply = total shares

totalSupply = total shares value

Staking mechanism vaults

Vaults that do not compound automatically (e.g., StakeWise) are known as staking mechanism vaults. These vaults do not compound interest automatically, instead making users periodically harvest and redeposit it to maximize yield. Calculating returns in such scenarios can be complex, as the total shares:value ratio does not reflect share price accurately.

Instead, we can ascertain the actual share price with the following formulas:

total principal value=sETH2.totalSupply()total rewards value=rETH2.totalSupply()TVL=total principal value+total rewards valueshare price=1+rETH2.rewardPerToken()totalSupply=TVLshare price\text{total principal value} = \text{sETH2.totalSupply()} \\ \text{total rewards value} = \text{rETH2.totalSupply()} \\ \text{TVL} = \text{total principal value} + \text{total rewards value} \\ \text{share price} = 1 + \text{rETH2.rewardPerToken()} \\ \text{totalSupply} = \frac{\text{TVL}}{\text{share price}}

These formulas result in a simulated calculation of totalSupply. It is not directly fetched from the blockchain.

Real-world assets

RWAs on vaults.fyi are ERC-20 tokens that reflect investments in offchain financial assets. To assess the yield of RWAs we use:

  • The totalSupply function to calculate total shares.

  • The RWAOracle.price function to fetch share price.

  • The StablecoinOracle.price function to fetch the underlying asset's (i.e, a stablecoin) price.

These functions are used in the following formulas to calculate yield:

total shares=totalSupply()share asset price=RWAOracle.price()underlying asset price=StablecoinOracle.price()total shares in USD=total shares×share asset pricetotal shares in asset=total shares in USDunderlying asset pricetotal shares value=total shares in asset\begin{align*} \text{total shares} &= \text{totalSupply()} \\ \text{share asset price} &= \text{RWAOracle.price()} \\ \text{underlying asset price} &= \text{StablecoinOracle.price()} \\ \text{total shares in USD} &= \text{total shares} \times \text{share asset price} \\ \text{total shares in asset} &= \frac{\text{total shares in USD}}{\text{underlying asset price}} \\ \text{total shares value} &= \text{total shares in asset} \end{align*}

Last updated