Skip to content

FeeDistributor

Fees used to be distributed to veCRV in the form of 3CRV tokens, the LP token of the threepool, which consists of USDT, USDC, and DAI. After the release of Curve's own stablecoin crvUSD and following a successful DAO vote to change the reward token to it, a new FeeDistributor contract was deployed to distribute fees in the form of crvUSD tokens. Fee claiming always takes place on Ethereum.

FeeDistributor.vy

The source code for the FeeDistributor.vy contract can be found on GitHub. The contract is written using Vyper version 0.2.7 and 0.3.7.

There are two different FeeDistributor contracts deployed on Ethereum, depending on the reward token:

Unclaimed 3CRV Tokens

Old unclaimed 3CRV tokens are not lost with the introduction of crvUSD as the reward token. They can still be claimed from the old FeeDistributor contract and will remain there until they are claimed.


The source code of both contracts is almost identical. The difference with respect to the first FeeDistributor is that the token variable was initialized with the crvUSD token address instead of 3CRV, and the rounded_timestamp calculation was modified as follows:

# 3CRV Distributor
rounded_timestamp: uint256 = block.timestamp / WEEK * WEEK

# crvUSD Distributor
rounded_timestamp: uint256 = (block.timestamp - 1) / WEEK * WEEK

Fees are distributed on a weekly basis. The proportional amount of fees that each user is to receive is calculated based on their veCRV balance relative to the total veCRV supply. This amount is calculated at the start of the week. The actual distribution occurs at the end of the week based on the fees that were collected. As such, a user that creates a new vote-lock should expect to receive their first fee payout at the end of the following epoch week. To facilitate this process, the contract keeps track of the reward token balance and the total veCRV balance using a system of checkpoints.


Claiming Fees

The claim and claim_many functions allow users to claim their share of distributed fees based on their veCRV balance. These functions calculate the amount of fees a user is entitled to by considering their veCRV balance and the total veCRV supply at various checkpoints. Fees do not need to be claimed weekly; they are accumulated and can be claimed at any point in time.

  • claim: This function allows a single user to claim their rewards. It calculates the user's share of the distributed fees based on their veCRV balance and the total veCRV supply.
  • claim_many: This function enables the claiming of rewards for up to 20 addresses in a single transaction. It is optimized for gas efficiency by processing multiple claims at once.

Google Colab Notebook

Unfortunately, there is no external method to directly check the claimable rewards for an address. The claimable rewards can either be checked in the Curve UI or by simulating a claim transaction and comparing the reward token balances before and after the claim. A Google Colab notebook that simulates such a transaction can be found here: Google Colab Notebook.

claim

FeeDistributor.claim(_addr: address = msg.sender) -> uint256

Function to claim the accrued fees for an address.

Returns: amount of rewards claimed (uint256).

Emits: Claimed

Input Type Description
_addr address Addresses to claim for; defaults to msg.sender

Info

For off-chain integrators, this function can be called as though it were a view method in order to check the claimable amount.

Every veCRV related action (locking, extending a lock, increasing the locktime) increments a user’s veCRV epoch. A call to claim will consider at most 50 user epochs. For accounts that performed many veCRV actions, it may be required to call claim more than once to receive the fees. In such cases it can be more efficient to use claim_many.

Source code
event Claimed:
    recipient: indexed(address)
    amount: uint256
    claim_epoch: uint256
    max_epoch: uint256

@external
@nonreentrant('lock')
def claim(_addr: address = msg.sender) -> uint256:
    """
    @notice Claim fees for `_addr`
    @dev Each call to claim look at a maximum of 50 user veCRV points.
        For accounts with many veCRV related actions, this function
        may need to be called more than once to claim all available
        fees. In the `Claimed` event that fires, if `claim_epoch` is
        less than `max_epoch`, the account may claim again.
    @param _addr Address to claim fees for
    @return uint256 Amount of fees claimed in the call
    """
    assert not self.is_killed

    if block.timestamp >= self.time_cursor:
        self._checkpoint_total_supply()

    last_token_time: uint256 = self.last_token_time

    if self.can_checkpoint_token and (block.timestamp > last_token_time + TOKEN_CHECKPOINT_DEADLINE):
        self._checkpoint_token()
        last_token_time = block.timestamp

    last_token_time = last_token_time / WEEK * WEEK

    amount: uint256 = self._claim(_addr, self.voting_escrow, last_token_time)
    if amount != 0:
        token: address = self.token
        assert ERC20(token).transfer(_addr, amount)
        self.token_last_balance -= amount

    return amount

@internal
def _claim(addr: address, ve: address, _last_token_time: uint256) -> uint256:
    # Minimal user_epoch is 0 (if user had no point)
    user_epoch: uint256 = 0
    to_distribute: uint256 = 0

    max_user_epoch: uint256 = VotingEscrow(ve).user_point_epoch(addr)
    _start_time: uint256 = self.start_time

    if max_user_epoch == 0:
        # No lock = no fees
        return 0

    week_cursor: uint256 = self.time_cursor_of[addr]
    if week_cursor == 0:
        # Need to do the initial binary search
        user_epoch = self._find_timestamp_user_epoch(ve, addr, _start_time, max_user_epoch)
    else:
        user_epoch = self.user_epoch_of[addr]

    if user_epoch == 0:
        user_epoch = 1

    user_point: Point = VotingEscrow(ve).user_point_history(addr, user_epoch)

    if week_cursor == 0:
        week_cursor = (user_point.ts + WEEK - 1) / WEEK * WEEK

    if week_cursor >= _last_token_time:
        return 0

    if week_cursor < _start_time:
        week_cursor = _start_time
    old_user_point: Point = empty(Point)

    # Iterate over weeks
    for i in range(50):
        if week_cursor >= _last_token_time:
            break

        if week_cursor >= user_point.ts and user_epoch <= max_user_epoch:
            user_epoch += 1
            old_user_point = user_point
            if user_epoch > max_user_epoch:
                user_point = empty(Point)
            else:
                user_point = VotingEscrow(ve).user_point_history(addr, user_epoch)

        else:
            # Calc
            # + i * 2 is for rounding errors
            dt: int128 = convert(week_cursor - old_user_point.ts, int128)
            balance_of: uint256 = convert(max(old_user_point.bias - dt * old_user_point.slope, 0), uint256)
            if balance_of == 0 and user_epoch > max_user_epoch:
                break
            if balance_of > 0:
                to_distribute += balance_of * self.tokens_per_week[week_cursor] / self.ve_supply[week_cursor]

            week_cursor += WEEK

    user_epoch = min(max_user_epoch, user_epoch - 1)
    self.user_epoch_of[addr] = user_epoch
    self.time_cursor_of[addr] = week_cursor

    log Claimed(addr, to_distribute, user_epoch, max_user_epoch)

    return to_distribute

This example claims the accrued fees for the caller.

>>> FeeDistributor.claim()

claim_many

FeeDistributor.claim_many(_receivers: address[20]) -> bool

Function to perform multiple claims in a single call. This is useful to claim for multiple accounts at once, or for making many claims against the same account if that account has performed more than 50 veCRV related actions.

Returns: true (boolean).

Emits: Claimed

Input Type Description
_addr address[20] List of 20 addresses to claim for. When claiming for less than 20 wallets, the remainig addresses need to be set to 'ZERO_ADDRESS'
Source code
event Claimed:
    recipient: indexed(address)
    amount: uint256
    claim_epoch: uint256
    max_epoch: uint256

@external
@nonreentrant('lock')
def claim_many(_receivers: address[20]) -> bool:
    """
    @notice Make multiple fee claims in a single call
    @dev Used to claim for many accounts at once, or to make
        multiple claims for the same address when that address
        has significant veCRV history
    @param _receivers List of addresses to claim for. Claiming
                    terminates at the first `ZERO_ADDRESS`.
    @return bool success
    """
    assert not self.is_killed

    if block.timestamp >= self.time_cursor:
        self._checkpoint_total_supply()

    last_token_time: uint256 = self.last_token_time

    if self.can_checkpoint_token and (block.timestamp > last_token_time + TOKEN_CHECKPOINT_DEADLINE):
        self._checkpoint_token()
        last_token_time = block.timestamp

    last_token_time = last_token_time / WEEK * WEEK
    voting_escrow: address = self.voting_escrow
    token: address = self.token
    total: uint256 = 0

    for addr in _receivers:
        if addr == ZERO_ADDRESS:
            break

        amount: uint256 = self._claim(addr, voting_escrow, last_token_time)
        if amount != 0:
            assert ERC20(token).transfer(addr, amount)
            total += amount

    if total != 0:
        self.token_last_balance -= total

    return True

@internal
def _claim(addr: address, ve: address, _last_token_time: uint256) -> uint256:
    # Minimal user_epoch is 0 (if user had no point)
    user_epoch: uint256 = 0
    to_distribute: uint256 = 0

    max_user_epoch: uint256 = VotingEscrow(ve).user_point_epoch(addr)
    _start_time: uint256 = self.start_time

    if max_user_epoch == 0:
        # No lock = no fees
        return 0

    week_cursor: uint256 = self.time_cursor_of[addr]
    if week_cursor == 0:
        # Need to do the initial binary search
        user_epoch = self._find_timestamp_user_epoch(ve, addr, _start_time, max_user_epoch)
    else:
        user_epoch = self.user_epoch_of[addr]

    if user_epoch == 0:
        user_epoch = 1

    user_point: Point = VotingEscrow(ve).user_point_history(addr, user_epoch)

    if week_cursor == 0:
        week_cursor = (user_point.ts + WEEK - 1) / WEEK * WEEK

    if week_cursor >= _last_token_time:
        return 0

    if week_cursor < _start_time:
        week_cursor = _start_time
    old_user_point: Point = empty(Point)

    # Iterate over weeks
    for i in range(50):
        if week_cursor >= _last_token_time:
            break

        if week_cursor >= user_point.ts and user_epoch <= max_user_epoch:
            user_epoch += 1
            old_user_point = user_point
            if user_epoch > max_user_epoch:
                user_point = empty(Point)
            else:
                user_point = VotingEscrow(ve).user_point_history(addr, user_epoch)

        else:
            # Calc
            # + i * 2 is for rounding errors
            dt: int128 = convert(week_cursor - old_user_point.ts, int128)
            balance_of: uint256 = convert(max(old_user_point.bias - dt * old_user_point.slope, 0), uint256)
            if balance_of == 0 and user_epoch > max_user_epoch:
                break
            if balance_of > 0:
                to_distribute += balance_of * self.tokens_per_week[week_cursor] / self.ve_supply[week_cursor]

            week_cursor += WEEK

    user_epoch = min(max_user_epoch, user_epoch - 1)
    self.user_epoch_of[addr] = user_epoch
    self.time_cursor_of[addr] = week_cursor

    log Claimed(addr, to_distribute, user_epoch, max_user_epoch)

    return to_distribute

This examples claims fees from two addresses.

>>> FeeDistributor.claim_many(["0x7a16fF8270133F063aAb6C9977183D9e72835428", "0xEDF7b675a2fE3c27efb263fb4c204A3f0fb17D46"])

burn

FeeDistributor.burn(_coin: address) -> bool

Function to receive 3CRV or crvUSD into the contract and trigger a token checkpoint. This ensures that the tokens are accounted for in the fee distribution. Simply sending tokens to the contract is not sufficient; a checkpoint is needed to register the tokens properly.

Return: true (bool).

Input Type Description
_coin address Address of the coin being received
Source code
@external
def burn(_coin: address) -> bool:
    """
    @notice Receive 3CRV into the contract and trigger a token checkpoint
    @param _coin Address of the coin being received (must be 3CRV)
    @return bool success
    """
    assert _coin == self.token
    assert not self.is_killed

    amount: uint256 = ERC20(_coin).balanceOf(msg.sender)
    if amount != 0:
        ERC20(_coin).transferFrom(msg.sender, self, amount)
        if self.can_checkpoint_token and (block.timestamp > self.last_token_time + TOKEN_CHECKPOINT_DEADLINE):
            self._checkpoint_token()

    return True
>>> FeeDistributor.burn("0xf939E0A03FB07F59A73314E73794Be0E57ac1b4E")

Checkpoints

Checkpointing is a critical process in the contract that ensures accurate tracking and distribution of reward tokens. The available reward token balance for distribution is tracked via the token checkpoint, which is updated at least every 24 hours. Fees received between the last checkpoint of the previous week and the first checkpoint of the new week are split evenly between the weeks.

Reward Token Checkpoint

checkpoint_token

FeeDistributor.checkpoint_token()

Function to update the token checkpoint. The token checkpoint tracks the balance of 3CRV/crvUSD within the distributor to determine the amount of fees to distribute in the given week. The checkpoint can be updated at most once every 24 hours. Fees that are received between the last checkpoint of the previous week and first checkpoint of the new week will be split evenly between the weeks. To ensure full distribution of fees in the current week, the burn process must be completed prior to the last checkpoint within the week. Aditionally, a token checkpoint is automatically taken during any claim action, if the last checkpoint is more than 24 hours old.

Emits: CheckpointToken

Source code
event CheckpointToken:
    time: uint256
    tokens: uint256

WEEK: constant(uint256) = 7 * 86400
TOKEN_CHECKPOINT_DEADLINE: constant(uint256) = 86400

can_checkpoint_token: public(bool)

@external
def checkpoint_token():
    """
    @notice Update the token checkpoint
    @dev Calculates the total number of tokens to be distributed in a given week.
        During setup for the initial distribution this function is only callable
        by the contract owner. Beyond initial distro, it can be enabled for anyone
        to call.
    """
    assert (msg.sender == self.admin) or\
        (self.can_checkpoint_token and (block.timestamp > self.last_token_time + TOKEN_CHECKPOINT_DEADLINE))
    self._checkpoint_token()

@internal
def _checkpoint_token():
    token_balance: uint256 = ERC20(self.token).balanceOf(self)
    to_distribute: uint256 = token_balance - self.token_last_balance
    self.token_last_balance = token_balance

    t: uint256 = self.last_token_time
    since_last: uint256 = block.timestamp - t
    self.last_token_time = block.timestamp
    this_week: uint256 = t / WEEK * WEEK
    next_week: uint256 = 0

    for i in range(20):
        next_week = this_week + WEEK
        if block.timestamp < next_week:
            if since_last == 0 and block.timestamp == t:
                self.tokens_per_week[this_week] += to_distribute
            else:
                self.tokens_per_week[this_week] += to_distribute * (block.timestamp - t) / since_last
            break
        else:
            if since_last == 0 and next_week == t:
                self.tokens_per_week[this_week] += to_distribute
            else:
                self.tokens_per_week[this_week] += to_distribute * (next_week - t) / since_last
        t = next_week
        this_week = next_week

    log CheckpointToken(block.timestamp, to_distribute)
>>> FeeDistributor.checkpoint_token()

tokens_per_week

FeeDistributor.tokens_per_week(arg0: uint256) -> uint256: view

Getter for the number of tokens available for claiming within a specific week.

Returns: reward tokens added for claiming (uint256).

Input Type Description
arg0 address Timestamp of the epoch start
Source code
tokens_per_week: public(uint256[1000000000000000])
>>> FeeDistributor.tokens_per_week(1718236800)      # 3CRV Distributor 06/13/2024
792049644381038889300744

>>> FeeDistributor.tokens_per_week(1718236800)      # crvUSD Distributor 06/13/2024
0

>>> FeeDistributor.tokens_per_week(1718841600)      # 3CRV Distributor 06/20/2024
16521060448369221

>>> FeeDistributor.tokens_per_week(1718841600)      # crvUSD Distributor 06/20/2024
548960496313176804866678

token_last_balance

FeeDistributor.token_last_balance() -> uint256: view

Getter for the balance of reward tokens after the latest claim.

Returns: last token balance (uint256).

Source code
token_last_balance: public(uint256)

This example returns the token_last_balance.

>>> FeeDistributor.token_last_balance() 

last_token_time

FeeDistributor.last_token_time() -> uint256: view

Getter for the timestamp of the last token checkpoint.

Returns: timestamp (uint256).

Source code
last_token_time: public(uint256)

This example returns the last_token_time.

>>> FeeDistributor.last_token_time() 

can_checkpoint_token

FeeDistributor.can_checkpoint_token() -> bool: view

Function to check whether the checkpoint_token function can be called by anyone or only by the admin. The state of this variable can be changed using the toggle_allow_checkpoint_token function.

Returns: true or flase (bool).

Source code
can_checkpoint_token: public(bool)

This example returns the can_checkpoint_token state.

>>> FeeDistributor.can_checkpoint_token() 

toggle_allow_checkpoint_token

FeeDistributor.toggle_allow_checkpoint_token()

Guarded Method

This function is only callable by the admin of the contract.

Funtion to toggle permission for checkpointing by an account.

Source code
event ToggleAllowCheckpointToken:
    toggle_flag: bool

@external
def toggle_allow_checkpoint_token():
    """
    @notice Toggle permission for checkpointing by any account
    """
    assert msg.sender == self.admin
    flag: bool = not self.can_checkpoint_token
    self.can_checkpoint_token = flag
    log ToggleAllowCheckpointToken(flag)
>>> FeeDistributor.toggle_allow_checkpoint_token()

ve-Supply Checkpoint

Checkpointing the ve-Supply is an essential process to ensure fair reward distribution. It involves periodically recording the total supply of veCRV for each epoch. This process is crucial for accurately distributing fees to veCRV holders based on their balances.

checkpoint_total_supply

FeeDistributor.checkpoint_total_supply()

Function to update the total supply checkpoint of veCRV for each epoch. The checkpoint is also updated by the first claimant of each new epoch week. This function can be called independently of a claim to reduce claiming gas costs. It ensures that the contract maintains an accurate record of the total veCRV supply at the start of each week, which is essential for correctly distributing fees based on veCRV holdings.

Source code
@external
def checkpoint_total_supply():
    """
    @notice Update the veCRV total supply checkpoint
    @dev The checkpoint is also updated by the first claimant each
        new epoch week. This function may be called independently
        of a claim, to reduce claiming gas costs.
    """
    self._checkpoint_total_supply()

@internal
def _checkpoint_total_supply():
    ve: address = self.voting_escrow
    t: uint256 = self.time_cursor
    rounded_timestamp: uint256 = block.timestamp / WEEK * WEEK
    VotingEscrow(ve).checkpoint()

    for i in range(20):
        if t > rounded_timestamp:
            break
        else:
            epoch: uint256 = self._find_timestamp_epoch(ve, t)
            pt: Point = VotingEscrow(ve).point_history(epoch)
            dt: int128 = 0
            if t > pt.ts:
                # If the point is at 0 epoch, it can actually be earlier than the first deposit
                # Then make dt 0
                dt = convert(t - pt.ts, int128)
            self.ve_supply[t] = convert(max(pt.bias - pt.slope * dt, 0), uint256)
        t += WEEK

    self.time_cursor = t

This example checkpoints the total supply of veCRV.

>>> FeeDistributor.checkpoint_total_supply()

time_cursor

FeeDistributor.time_cursor() -> uint256: view

Getter for the timestamp of the last checkpoint_total_supply of veCRV.

Returns: timestamp (uint256).

Source code
time_cursor: public(uint256)

This example returns the time_cursor.

>>> FeeDistributor.time_cursor() 

time_cursor_of

FeeDistributor.time_cursor_of(arg0: address) -> uint256: view

Getter for the timestamp of the last checkpoint_total_supply of veCRV.

Returns: timestamp (uin256).

Input Type Description
arg0 address Address to check for
Source code
time_cursor_of: public(HashMap[address, uint256])

This example returns the time_cursor_of for a given address.

>>> FeeDistributor.time_cursor_of('0x7a16fF8270133F063aAb6C9977183D9e72835428')
1719446400

ve_for_at

FeeDistributor.ve_for_at(_user: address, _timestamp: uint256) -> uint256

Getter for the veCRV balance of a user at a certain timestamp.

Returns: veCRV balance (uint256).

Input Type Description
_user address Address to query the veCRV balance for
_timestamp uint256 Timestamp
Source code
@view
@external
def ve_for_at(_user: address, _timestamp: uint256) -> uint256:
    """
    @notice Get the veCRV balance for `_user` at `_timestamp`
    @param _user Address to query balance for
    @param _timestamp Epoch time
    @return uint256 veCRV balance
    """
    ve: address = self.voting_escrow
    max_user_epoch: uint256 = VotingEscrow(ve).user_point_epoch(_user)
    epoch: uint256 = self._find_timestamp_user_epoch(ve, _user, _timestamp, max_user_epoch)
    pt: Point = VotingEscrow(ve).user_point_history(_user, epoch)
    return convert(max(pt.bias - pt.slope * convert(_timestamp - pt.ts, int128), 0), uint256)
>>> FeeDistributor.ve_for_at("0x989AEb4d175e16225E39E87d0D97A3360524AD80", 1685972555)
290896146145001156884162140

ve_supply

FeeDistributor.ve_supply(arg0: uint256) -> uint256: view

Getter for the total supply of veCRV at the beginning of an epoch.

Returns: vecrv supply (uint256).

Input Type Description
arg0 uint256 Timestamp of the epoch start
Source code
ve_supply: public(uint256[1000000000000000])  # VE total supply at week bounds
>>> FeeDistributor.ve_supply(1718841600)
667140493408797243694521600

Killing The FeeDistributor

The FeeDistributor can be killed by the admin of the contract, which is the Curve DAO. Doing so, transfers the entire token balance to the emergency_return address and block the ability to claim or burn. The contract can not be unkilled.

Google Colab Notebook

A Google Colab notebook that simulates killing the FeeDistributor and its respective consequences can be found here: Google Colab Notebook.

is_killed

FeeDistributor.is_killed() -> bool: view

Getter method to check if the FeeDistributor contract is killed. When killed, the contract blocks claim and burn and the entire token balance is transfered to the emergency_return address.

Returns: true or flase (bool).

Source code
is_killed: public(bool)

This example fetches the is_killed status.

>>> FeeDistributor.is_killed() 

kill_me

FeeDistributor.kill_me()

By killing the FeeDistributor, the entire token balance is transferred to the emergency_return address, and the ability to further call the claim, claim_many, or burn functions is blocked.

Guarded Method

This function is only callable by the admin of the contract.

Function to kill the FeeDistributor contract.

Source code
is_killed: public(bool)

@external
def kill_me():
    """
    @notice Kill the contract
    @dev Killing transfers the entire 3CRV balance to the emergency return address
        and blocks the ability to claim or burn. The contract cannot be unkilled.
    """
    assert msg.sender == self.admin

    self.is_killed = True

    token: address = self.token
    assert ERC20(token).transfer(self.emergency_return, ERC20(token).balanceOf(self))
>>> FeeDistributor.kill_me()

emergency_return

FeeDistributor.emergency_return() -> address: view

Getter for the emergency return address. This address can not be changed.

Returns: emergency return (address).

Source code
emergency_return: public(address)

Due to the fact that the emergency return address can not be changed and Curve used a ownership agent back then when the distributor contract for 3CRV was deployed, this one was set as the emergency return address.

The second fee distributor contract (crvUSD) uses a 5 of 9 multisig, which replaced the ownership agent.

>>> FeeDistributor.emergency_return()               # 3CRV distributor
'0x00669DF67E4827FCc0E48A1838a8d5AB79281909'

>>> FeeDistributor.emergency_return()               # crvUSD distributor
'0x467947EE34aF926cF1DCac093870f613C96B1E0c'

recover_balance

FeeDistributor.recover_balance(_coin: address) -> bool

Function to recover ERC20 tokens from the contract. Tokens are sent to the emergency return address. This function only works for tokens other than the address set for token. E.g. this function on the 3CRV distributor contract can not be called to transfer 3CRV. The same applied to crvUSD distributor.

Returns: true (bool).

Input Type Description
_coin address Tokens to recover
Source code
@external
def recover_balance(_coin: address) -> bool:
    """
    @notice Recover ERC20 tokens from this contract
    @dev Tokens are sent to the emergency return address.
    @param _coin Token address
    @return bool success
    """
    assert msg.sender == self.admin
    assert _coin != self.token

    amount: uint256 = ERC20(_coin).balanceOf(self)
    response: Bytes[32] = raw_call(
        _coin,
        concat(
            method_id("transfer(address,uint256)"),
            convert(self.emergency_return, bytes32),
            convert(amount, bytes32),
        ),
        max_outsize=32,
    )
    if len(response) != 0:
        assert convert(response, bool)

    return True

This example recovers the balance of a given token.

>>> FeeDistributor.recover_balance("0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2")
true

Admin Ownership

admin

FeeDistributor.admin() -> address: view

Getter for the admin of the contract.

Returns: admin (address).

Source code
admin: public(address)

This example returns the current admin.

>>> FeeDistributor.admin() 

future_admin

FeeDistributor.future_admin() -> address: view

Getter for the future admin of the contract.

Returns: future admin (address).

Source code
future_admin: public(address)

This example returns the current future_admin.

>>> FeeDistributor.future_admin() 

commit_admin

FeeDistributor.commit_admin(_addr: address)

Guarded Method

This function is only callable by the admin of the contract.

Function to commit transfer of the ownership.

Emits: CommitAdmin

Input Type Description
_addr address Address to commit the ownership transfer to.
Source code
event CommitAdmin:
    admin: address

admin: public(address)
future_admin: public(address)

@external
def commit_admin(_addr: address):
    """
    @notice Commit transfer of ownership
    @param _addr New admin address
    """
    assert msg.sender == self.admin  # dev: access denied
    self.future_admin = _addr
    log CommitAdmin(_addr)

This example commits the transfer of the ownership.

>>> FeeDistributor.commit_admin("0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045")

apply_admin

FeeDistributor.apply_admin()

Guarded Method

This function is only callable by the admin of the contract.

Function to apply the transfer of the ownership.

Emits: ApplyAdmin

Source code
event ApplyAdmin:
    admin: address

admin: public(address)
future_admin: public(address)

@external
def apply_admin():
    """
    @notice Apply transfer of ownership
    """
    assert msg.sender == self.admin
    assert self.future_admin != ZERO_ADDRESS
    future_admin: address = self.future_admin
    self.admin = future_admin
    log ApplyAdmin(future_admin)

This example applies the transfer of the ownership.

>>> FeeDistributor.apply_admin()

Other Methods

start_time

FeeDistributor.start_time() -> uint256: view

Getter for the epoch time for fee distribution to start.

Returns: epoch time (uint256).

Source code
start_time: public(uint256)

@external
def __init__(
    _voting_escrow: address,
    _start_time: uint256,
    _token: address,
    _admin: address,
    _emergency_return: address
):
    """
    @notice Contract constructor
    @param _voting_escrow VotingEscrow contract address
    @param _start_time Epoch time for fee distribution to start
    @param _token Fee token address (3CRV)
    @param _admin Admin address
    @param _emergency_return Address to transfer `_token` balance to
                            if this contract is killed
    """
    t: uint256 = _start_time / WEEK * WEEK
    self.start_time = t
    self.last_token_time = t
    self.time_cursor = t
    self.token = _token
    self.voting_escrow = _voting_escrow
    self.admin = _admin
    self.emergency_return = _emergency_return

This example returns the start_time of the first distribution of rewards.

>>> FeeDistributor.start_time()         # 3CRV Distributor
1600300800                              # Thu Sep 17 2020 00:00:00 GMT+0000

>>> FeeDistributor.start_time()         # crvUSD Distributor
1718841600                              # Thu Jun 20 2024 00:00:00 GMT+0000

voting_escrow

FeeDistributor.voting_escrow() -> address: view

Getter for the voting escrow contract.

Returns: voting escrow (address).

Source code
voting_escrow: public(address)

@external
def __init__(
    _voting_escrow: address,
    _start_time: uint256,
    _token: address,
    _admin: address,
    _emergency_return: address
):
    """
    @notice Contract constructor
    @param _voting_escrow VotingEscrow contract address
    @param _start_time Epoch time for fee distribution to start
    @param _token Fee token address (3CRV)
    @param _admin Admin address
    @param _emergency_return Address to transfer `_token` balance to
                            if this contract is killed
    """
    t: uint256 = _start_time / WEEK * WEEK
    self.start_time = t
    self.last_token_time = t
    self.time_cursor = t
    self.token = _token
    self.voting_escrow = _voting_escrow
    self.admin = _admin
    self.emergency_return = _emergency_return

This example returns the voting_escrow.

>>> FeeDistributor.voting_escrow() 

token

FeeDistributor.token() -> address: view

Getter for the token address in which the fees are distributed.

Returns: reward token (address).

Source code
token: public(address)

@external
def __init__(
    _voting_escrow: address,
    _start_time: uint256,
    _token: address,
    _admin: address,
    _emergency_return: address
):
    """
    @notice Contract constructor
    @param _voting_escrow VotingEscrow contract address
    @param _start_time Epoch time for fee distribution to start
    @param _token Fee token address (crvUSD)
    @param _admin Admin address
    @param _emergency_return Address to transfer `_token` balance to
                            if this contract is killed
    """
    t: uint256 = _start_time / WEEK * WEEK
    self.start_time = t
    self.last_token_time = t
    self.time_cursor = t
    self.token = _token
    self.voting_escrow = _voting_escrow
    self.admin = _admin
    self.emergency_return = _emergency_return

This example returns the reward token (token) of the FeeDistributor contract.

>>> FeeDistributor.token() 

user_epoch_of

FeeDistributor.user_epoch_of(arg0: address) -> uint256: view

Getter for the user epoch of an address. This value increments by one each time rewards are claimed.

Returns: user epoch (uint256).

Input Type Description
arg0 address Address to get the user epoch for
Source code
user_epoch_of: public(HashMap[address, uint256])

This example returns the user epoch of a given address.

>>> FeeDistributor.user_epoch_of("0x989AEb4d175e16225E39E87d0D97A3360524AD80")
7739