Back
Dec 22, 2024

Python and the Point Rush in DeFi

Disclaimer: Everything stated in this article does not constitute financial advice. Please consider the presented material solely as a demonstration of using Python in the field of DeFi (decentralized finance). We strongly encourage you to conduct your research, analyze the issue carefully, and account for your personal financial goals and risks. Setting personal parameters and coefficients is a crucial aspect of working with DeFi instruments. All decisions should be made deliberately and independently, with a clear mind and a full understanding of the potential consequences.

The cryptocurrency sphere is striking with its diversity and opportunities. In 2024, there are many different ways to interact with the blockchain. One popular direction is DeFi (decentralized finance), which main idea is to provide liquidity in exchange for rewards. Each protocol offers its own unique reward distribution system for active users, aiming to attract them through various incentive methods.

Nearly all projects provide rewards in their native tokens. Some offer fixed rates akin to bank deposits, while others provide floating rates, which fluctuate based on market conditions and user activity. Additionally, some reward retrospectively, revealing the criteria for earning rewards at the time of token distribution.

At the beginning of 2024, the concept of issuing rewards in the form of points became popular. These points can be exchanged for the project’s tokens at the end of the incentive program. This approach combines elements of all previous models. Users can calculate the number of points earned over the participation period, but the final value of these points remains undetermined until the program concludes. Many factors affect the final reward price: the protocol valuation (FDV — Fully Diluted Valuation), the liquidity volume in the protocol (TVL — Total Value Locked), the duration of the program, the percentage of tokens allocated for rewards, the rules for converting points, and much more. The more of these factors are known, the more accurately one can estimate the potential rewards and make a well-considered decision — whether to participate in the project or wait for more favorable opportunities.

Today, I would like to consider one method of automated yield calculation using the Python language. As an example, we will use the Renzo liquid restaking protocol and the Pendle platform, which allows separating the principal asset (PT — Principal Tokens) and the yield (YT — Yield Tokens), exchanging one for the other. In this case, the principal asset (ETH), purchased for USD, will be exchanged for YT tokens that generate yield from the Renzo protocol. At the end of the pool’s term, the YT tokens will lose their value (their price will become 0), but during that period, they will generate yield that we will calculate.

Input Data

On July 27, 2024, the Renzo project announced the end of the second incentive season and the start of the third. According to the provided data, the third season will last four months, during which 400 million Rez tokens will be distributed as rewards to participants of the restaking program via the platform. As in previous seasons, Renzo rewards users linearly, based on accumulated points, without providing advantages to users with small or large deposits. This creates more equal conditions for all participants.

The project has already conducted its TGE (Token Generation Event), so the token price is known and available for analysis. The interaction model with the project assumes obtaining rewards both from staking in EigenLayer and from restaking in Renzo. The mechanism for distributing Eigen tokens from EigenLayer is also known: it is a linear weekly distribution of a fixed amount of tokens among all users who have deposited assets into the protocol.

Now, with the input data gathered, we can proceed with the calculations.

Step 1: Obtaining the Current Price of 1 YT Token in the ezETH Pool

First, it is necessary to obtain the current price of one YT token in the ezETH pool with an expiration date of December 26, 2024. The pool in the ERC20 network is used for the search, and the contract address is stored in advance. Additional information about other pools on different networks can be found in the Pendle documentation (https://api-v2.pendle.finance/core/docs).

def get_pendle_pool_info() -> float:
    chain_id = 1
    renzo_yt_contract = "0x7749f5ed1e356edc63d469c2fcac9adeb56d1c2b"
    url = f"https://api-v2.pendle.finance/core/v1/{chain_id}/assets/prices?addresses={renzo_yt_contract}"
    return get(url).json()['prices'][renzo_yt_contract]

Step 2: Calculating the Time for Point Accumulation

Next, it is necessary to calculate the number of hours during which points will be accrued in the selected pool. For this, a specific season end date is used.

season_date_end = datetime.fromisoformat("2024-11-27T00:00:00.000")
hours_before_season_ends = int((season_date_end - datetime.utcnow()).total_seconds() / 3600)

Step 3: Calculating the Number of YT Tokens That Can Be Purchased for $1000

Then, it is necessary to compute the number of YT tokens that can be purchased with a nominal amount of $1000.

amount_to_invest = 1000
yt_amount = amount_to_invest / rez_yt_price

Step 4: Calculating the Number of Points Until Expiration

Next, the number of points that will be accrued until the end of the season is calculated. The standard number of points per 1 ETH is used for the calculation. The pool has a multiplier of x2, which should be taken into account in the calculations (the multiplier should be confirmed in the Pendle or Renzo Discord channels, as project documentation may indicate different values, but in such calculations it is better to use the lower bound).

def calculate_point_amount(yt_amount: float, hours_before_season_ends: int) -> float:
    points_per_hour = 2
    return (
        yt_amount
        * points_per_hour
        * hours_before_season_ends
    )

Step 5: Obtaining the Current Prices of REZ and EIGEN Tokens

For further calculations, it is necessary to know the current prices of REZ tokens (for Renzo rewards) and EIGEN tokens (for EigenLayer rewards). It is important to note that these prices can be volatile and may change by the time the rewards are received.

def get_token_prices() -> Tuple[float, float]:
    response = get("https://coins.llama.fi/prices/current/coingecko:renzo,coingecko:eigenlayer")
    return response.json()["coins"]["coingecko:renzo"]["price"],\
           response.json()["coins"]["coingecko:eigenlayer"]["price"]

Step 6: Estimating the Total Number of Points in the Season

To estimate the total number of points that will be distributed by the end of the season, several key factors must be considered: the current amount of ETH locked in the protocol, the expected duration of the season, and the average multiplier. The process begins by obtaining data on the current state of the protocol. Using the API provided by the Renzo project, one can get information on the amount of ETH that is staked. This value reflects the current state of the protocol's liquidity and is used to calculate the total number of points that will be distributed among participants. 

Next, the duration of the season is determined, which, in our case, is 123 days. This value is taken based on public data on the duration of the current staking season in the protocol. The specific number of hours until the end of the season can be calculated using the season end date and the current time. 

Another important parameter is the multiplier. The multiplier is a coefficient that affects the rate of point accumulation depending on the specific pool or activity. In this example, an average multiplier value of 2 is used, as it is the most common value for most active pools. However, it should be noted that different assets and pools may have different multipliers, and their values should be confirmed directly in the project or community (for example, via Discord). 

It is important to note that this calculation is approximate and cannot claim absolute accuracy. Various factors may influence the total number of points accrued by the end of the season, such as:

  • Liquidity inflow or outflow: If a large number of users deposit or withdraw their assets from the protocol, this will change the dynamics of point distribution.
  • Changes in liquidity usage: In some cases, assets may simply be locked in the protocol, while in others they may be used in additional protocols, which can increase their yield due to bonuses or additional multipliers.
  • Introduction of new assets: During the season, the project may add support for new assets that will also participate in the point distribution, affecting the overall balance.
  • Minimum thresholds for point accrual: Closer to the end of the season, the project may announce a minimum number of points required to earn rewards, which will also affect the final results.
  • Exclusion of users for Sybil activity: Users suspected of manipulating the system to artificially increase rewards may be excluded from the reward program, which will also adjust the distribution of points. 

Based on previous seasons, such a calculation gives an error margin of approximately 5-10%. For a more precise estimate, specialized analytical tools such as Dune Analytics can be used, allowing for an in-depth analysis of liquidity dynamics, asset distribution, and user participation. Such analysis can reduce the error margin and provide a more accurate picture of potential rewards by the end of the season.

This approach to estimating points allows for a forecast that will become more accurate with regular updates of data on liquidity, token prices, and protocol participants.

def get_eth_based_season_points() -> float:
    response = get("https://app.renzoprotocol.com/api/stats")
    tvl_in_eth = response.json()["data"]["restakedTVL"]["data"]["eth"]
    season_hours_length = 123 * 24
    points_per_hour = 1 
    average_multiplier = 2
    return tvl_in_eth * season_hours_length * average_multiplier * points_per_hour

Step 7: Accounting for the Coefficient from Staking Previously Received REZ Tokens

After calculating the number of points earned based on the ETH locked in the protocol, it is necessary to account for an additional coefficient related to staking previously received REZ tokens. This step is important because REZ tokens received by participants in previous distributions can also be used to earn additional points. This adds another layer of rewards for participants who continue to hold or stake their REZ tokens in the system.

Mechanism for Earning Points from REZ Staking

The Renzo project incentivizes participants not only for restaking ETH but also for holding and staking REZ tokens received as rewards. This creates a situation where rewards from previous seasons can be used to increase future income. To account for this factor, an additional coefficient is introduced.

At the time of writing, the ratio between the points earned from ETH staking and those from REZ staking is approximately 1 to 25. This means that for every 25 points earned from ETH, there is 1 point from REZ staking. This coefficient may vary depending on market dynamics and the protocol’s policies. In our case, for simplicity, a coefficient of 1.04 is used. This means that the final number of points earned from ETH is increased by 4% due to the points from staking REZ tokens.

How to Calculate the Exact Coefficient

For a more accurate coefficient, one can use more detailed data available through analytical services such as Dune Analytics or the protocol’s internal dashboards. These services allow tracking the daily dynamics of point distribution between ETH and REZ, analyzing how the share of REZ stakers changes, and calculating the impact of these factors on the overall coefficient. A deeper analysis will allow taking into account changes in the number of REZ tokens held by users and their impact on the total volume of rewards.

def get_total_season_points() -> float:
    rez_coefficient = 1.04
    return get_eth_based_season_points() * rez_coefficient

Final Formula for Calculating the Total Number of Points

To obtain the final result, the calculated number of points based on ETH is multiplied by the REZ staking coefficient:

def calculate_point_price(rez_price: float) -> float:
    airdrop_token_distribution = 400_000_000 
    total_season_points = get_total_season_points()
    tokens_per_point = airdrop_token_distribution / total_season_points
    point_price = rez_price * tokens_per_point
    return point_price

Additional Factors to Consider

It is important to note that the REZ staking coefficient may change over time, depending on how many REZ tokens participants decide to keep staked. Over time, the number of participants using their REZ to earn additional points may increase or decrease, which will directly affect the coefficient. Thus, for more accurate calculations, it is important to regularly update the data and conduct further analysis based on the current situation in the protocol. Accounting for this coefficient allows for a more complete picture of how many points may be earned by the end of the season and how the reward distribution affects the overall yield for participants.

Step 8: Calculating the Potential Profit from Renzo

Once the price of points and their number are known, the potential profit can be calculated.

def calculate_renzo_profit(renzo_points: float, point_price: float) -> float:
    return renzo_points * point_price

Step 9: Calculating the Potential Profit from EigenLayer

EigenLayer operates on a system of linear distribution of fixed rewards every week among all participants staking ETH. To calculate the potential yield, several parameters are used:

  1. Annual reward: EigenLayer allocates 66,945,866 EIGEN tokens per year.
  2. TVL: the total amount of ETH locked in the protocol. At the time of calculation, this is 4,220,000 ETH (this data can be obtained dynamically through services similar to Defilama).
  3. Number of YT tokens: the user’s share in ETH through YT tokens.
  4. Remaining weeks until the season ends: based on the remaining time until the end of the season, the number of weeks is calculated.
def calculate_eigen_profit(eigen_price: float, yt_amount: float, hours_before_season_ends: int) -> float:
    eigen_rewards_per_year = 66_945_866
    total_eth_locked = 4_220_000
    one_week_rewards = eigen_rewards_per_year / 52
    weekly_rewards_per_one_eth = one_week_rewards / total_eth_locked
    weeks_until_pool_ends = hours_before_season_ends / 24 / 7
    return weeks_until_pool_ends * yt_amount * weekly_rewards_per_one_eth * eigen_price

Step 10: Final Calculation

All intermediate values are combined to obtain the final profit value.

def calculate_renzo_pool_profit():
    amount_to_invest = 1000
    season_date_end = datetime.fromisoformat("2024-11-27T00:00:00.000")  # predicted time according to docs
    hours_before_season_ends = int((season_date_end - datetime.utcnow()).total_seconds() / 3600)
    if hours_before_season_ends < 0:
        print("Pool is expired")
        return
    rez_yt_price = get_pendle_pool_info()
    yt_amount = amount_to_invest / rez_yt_price
    rez_price, eigen_price = get_token_prices()
    renzo_points = calculate_point_amount(yt_amount, hours_before_season_ends)
    point_price = calculate_point_price(rez_price)
    renzo_profit = calculate_renzo_profit(renzo_points, point_price)
    el_profit = calculate_eigen_profit(eigen_price, yt_amount, hours_before_season_ends)
    value_delta = (
        renzo_profit + el_profit - amount_to_invest
    )
    percent_delta = (value_delta / amount_to_invest) * 100
    print(f"Amount invested {amount_to_invest}")
    print(f"Price of 1 renzo YT {rez_yt_price}")
    print(f"Renzo rewards {renzo_profit}")
    print(f"Eigen rewards {el_profit}")
    print(f"Delta {value_delta}")
    print(f"Delta in percents {percent_delta}")
Знімок екрана 2025-02-27 о 14.17.14.png

Conclusion

Based on the current calculations, purchasing this asset may lead to a loss of funds. However, indicators such as multipliers, project TVL, token prices, and other parameters may change, which will affect the situation in the future. It is also worth noting that the Renzo yield calculation was conducted for only the third season and did not take into account additional rewards from subsequent campaigns.

In conclusion, it is essential to regularly update all data and conduct analyses based on current information and sound judgment. All calculations depend on numerous dynamic factors, such as liquidity, token prices, and reward policies, which can change over time. Making investment decisions requires careful calculation and thorough research.
 

And, as the saying goes, in the times of the gold rush, the one who sells the pickaxes earns the most. Good luck!

from typing import Tuple

from requests import get
from datetime import datetime


def get_pendle_pool_info() -> float:
    """Getting data about required asset.

    More info: https://api-v2.pendle.finance/core/docs
    """
    # use only erc20 chain for example
    chain_id = 1
    # ezETH YT contract
    renzo_yt_contract = "0x7749f5ed1e356edc63d469c2fcac9adeb56d1c2b"

    url = f"https://api-v2.pendle.finance/core/v1/{chain_id}/assets/prices?addresses={renzo_yt_contract}"

    return get(url).json()['prices'][renzo_yt_contract]


def calculate_point_amount(yt_amount: float, hours_before_season_ends: int) -> float:
    # default amount is 1 but this pool has a 2x multiplier
    points_per_hour = 2
    predicted_protocol_points = (
        yt_amount
        * points_per_hour
        * hours_before_season_ends
    )
    return predicted_protocol_points


def calculate_point_price(rez_price: float) -> float:
    airdrop_token_distribution = 400_000_000  # according to season 3 rules

    total_season_points = get_total_season_points()
    tokens_per_point = airdrop_token_distribution / total_season_points

    point_price = rez_price * tokens_per_point

    return point_price


def get_token_prices() -> Tuple[float, float]:
    """Fetch current REZ and EIGEN price from coingecko."""

    response = get("https://coins.llama.fi/prices/current/coingecko:renzo,coingecko:eigenlayer")
    return response.json()["coins"]["coingecko:renzo"]["price"],\
           response.json()["coins"]["coingecko:eigenlayer"]["price"]


def calculate_renzo_profit(renzo_points: float, point_price: float) -> float:
    return renzo_points * point_price


def calculate_eigen_profit(eigen_price: float, yt_amount: float, hours_before_season_ends: int) -> float:
    eigen_rewards_per_year = 66_945_866
    # amount on the date of article creation
    total_eth_locked = 4_220_000
    # tokens distributed ones per week
    one_week_rewards = eigen_rewards_per_year / 52
    weekly_rewards_per_one_eth = one_week_rewards / total_eth_locked
    weeks_until_pool_ends = hours_before_season_ends / 24 / 7
    return weeks_until_pool_ends * yt_amount * weekly_rewards_per_one_eth * eigen_price


def calculate_renzo_pool_profit():
    """Check renzo pendle pool price.

    Steps:
    1. Get renzo yt price
    2. Calculate renzo point that will be farmed until target date
    3. Calculate renzo point price
    4. Calculate renzo profit
    5. Calculate eigen profit
    """
    amount_to_invest = 1000  # could be changed to any value
    season_date_end = datetime.fromisoformat("2024-11-27T00:00:00.000")  # predicted time according to docs
    hours_before_season_ends = int((season_date_end - datetime.utcnow()).total_seconds() / 3600)

    if hours_before_season_ends < 0:
        print("Pool is expired")
        return

    rez_yt_price = get_pendle_pool_info()

    yt_amount = amount_to_invest / rez_yt_price

    rez_price, eigen_price = get_token_prices()

    renzo_points = calculate_point_amount(yt_amount, hours_before_season_ends)

    point_price = calculate_point_price(rez_price)

    renzo_profit = calculate_renzo_profit(renzo_points, point_price)

    el_profit = calculate_eigen_profit(eigen_price, yt_amount, hours_before_season_ends)

    value_delta = (
        renzo_profit + el_profit - amount_to_invest
    )

    percent_delta = (value_delta / amount_to_invest) * 100

    print(f"Amount invested {amount_to_invest}")
    print(f"Price of 1 renzo YT {rez_yt_price}")
    print(f"Renzo rewards {renzo_profit}")
    print(f"Eigen rewards {el_profit}")
    print(f"Delta {value_delta}")
    print(f"Delta in percents {percent_delta}")

    return value_delta, percent_delta


def get_total_season_points() -> float:
    # approximate coefficient of farming points through rez token staking
    # this proportion is not accurate cause proportion of farming changes
    rez_coefficient = 1.04
    return get_eth_based_season_points() * rez_coefficient


def get_eth_based_season_points() -> float:
    response = get("https://app.renzoprotocol.com/api/stats")
    tvl_in_eth = response.json()["data"]["restakedTVL"]["data"]["eth"]
    season_hours_length = 123 * 24  # approximate season length
    points_per_hour = 1  # amount of points farmed per hour per 1 eth
    # different strategies gives from 1 to 4 multiplier for earned points.
    # most people just store assets on wallets with minimal multiplier,
    # but some use DEFi and earns 4x.
    # for demo purposes i use 2 as an average
    # but it's possible to calculate it more accurately using renzo DEFi dashboard or dune analytics
    average_multiplier = 2
    return tvl_in_eth * season_hours_length * average_multiplier * points_per_hour


if __name__ == '__main__':
    calculate_renzo_pool_profit()

Subscribe for the news and updates

More thoughts
Sep 26, 2023TechnologyBusiness
13 Web Development Innovations to Adopt in the Next Year

Web development has undergone significant changes over the past five years due to a combination of technological advancements and changing user needs. Let's look at the drivers of these changes and the key technologies that have played a decisive role.

Apr 19, 2022Technology
Improve efficiency of your SELECT queries

SQL is a fairly complicated language with a steep learning curve. For a large number of people who make use of SQL, learning to apply it efficiently takes lots of trials and errors. Here are some tips on how you can make your SELECT queries better. The majority of tips should be applicable to any relational database management system, but the terminology and exact namings will be taken from PostgreSQL.

May 9, 2018Technology
How to Generate PDF Files in Python with Xhtml2pdf, WeasyPrint or Unoconv

Programmatic generation of PDF files is a frequent task when developing applications that can export reports, bills, or questionnaires. In this article, we will consider three common tools for creating PDFs, including their installation and converting principles.

Aug 25, 2017Technology
How to Upload Files With Django

File upload works differently from simple form inputs, which can be somewhat troublesome for beginners. Here I'll show you how to handle uploads with ease.

Sep 22, 2016Technology
Angular Form Validation

In this article, we will describe some useful scripts and directives we use with angular form validation in our projects.

Mar 4, 2011Technology
Css sprite generation

I've created this small sprite to create css sprites. It glues images from directory directory into single file and generates corresponding css.