FeeDistributor
The FeeDistributor pays admin fees to veYB holders in weekly epochs. veYB is vote-escrowed YB, the voting power that the VotingEscrow gives for locked YB. A holder's share of an epoch follows its veYB voting power at that epoch.
The fees are yb-LP shares, the share tokens that LT, the vault contract of each market, mints. fill_epochs() allocates new balances only for the tokens in the current token set: the list of fee tokens that the owner sets with add_token_set().
The FeeDistributor does not receive fees from the markets directly. Factory.fee_receiver() is the FeeSplitter, listed in Contract addresses. To check, call fee_receiver() on the Factory.
The FeeSplitter sends a DAO-set share of admin fees to the net-pressure reserve and the rest to veYB holders through the FeeDistributor. To read the share, call split_fraction() on the FeeSplitter (1e18 = 100%). For the fee flow and the admin-fee curve, read Fees and veYB revenue. For the FeeSplitter calls that collect the fees and then call FeeDistributor.fill_epochs(), read FeeSplitter in Net pressure (PID).
The owner of the FeeDistributor is the YB DAO (decentralized autonomous organization), listed in Contract addresses. To check, call owner(). Only the owner can call add_token_set(), kill_toggle(), and recover_token().
The contract spreads each new fee balance over four future weekly epochs (OVER_WEEKS = 4). Thus a large fee arrival does not land in one claim window.
Constants and immutables
The contract has these constants and immutables:
WEEK= 604,800 seconds. Epochs start on whole multiples ofWEEK.OVER_WEEKS= 4. The number of future epochs over whichfill_epochs()spreads each new balance.MAX_TOKENS= 100. The maximum number of tokens in one token set.INITIAL_EPOCH,VE,VESTING_ESCROWS: the first epoch, the VotingEscrow, and the vesting escrows that the constructor sets. Vesting escrows explains the vesting escrows.
Functions
Token amounts in this section are raw token units. yb-LP shares use 18 decimals. Each function section lists its own revert strings, and Error codes holds the shared revert table.
fill_epochs()
The function takes no inputs: fill_epochs(). Anyone can call it.
The function reads the new balances of the tokens in the current token set. It allocates each new balance across the next OVER_WEEKS epochs: each epoch gets (balance - old_balance) // OVER_WEEKS of each token.
The function emits FundEpoch once for each epoch and each token with a new balance.
fill_epochs() reverts with Ded when the contract is killed. kill_toggle() sets the kill status.
preview_distribution()
The function has this signature: preview_distribution(week_shift: int256 = 0) -> (DynArray[IERC20, MAX_TOKENS * 4], DynArray[uint256, MAX_TOKENS * 4]).
The function returns the tokens and the token amounts that all veYB holders share in one week. week_shift selects the week: 0 is the current week, 1 the next, and -1 the last.
The function has no revert strings.
preview_claim()
The function has this signature: preview_claim(receiver: address, epoch_count: uint256 = 50, use_vest: bool = False) -> (DynArray[IERC20, MAX_TOKENS * 4], DynArray[uint256, MAX_TOKENS * 4]).
The function simulates a claim and returns the tokens and amounts that receiver gets. The compiler marks the function as state-changing, because it runs the claim logic. The source says that ABI tooling must expose it as a view call, so call it with eth_call.
The function returns two arrays of the same length: the token addresses, and the raw amount of each token. Claim basis gives the formula and a converted example.
The function takes these inputs:
| Input | Type | Description |
|---|---|---|
receiver | address | The address to claim for. It gets the tokens. |
epoch_count | uint256 | The number of epochs to claim. The default is 50. One call processes at most 50 epochs, whatever the value of epoch_count. |
use_vest | bool | Set it to True to claim for a cliff recipient. With True, the claim can go through a cliff escrow, which Vesting escrows describes. The default is False. |
preview_claim() reverts in these cases:
Ded- The contract is killed.Might be a vest-receiveris not the caller, no cliff escrow matched, andreceiveris a contract whoseVE()returns the VotingEscrow address.- Without a message -
epoch_countis 0.
claim()
The function has this signature: claim(receiver: address = msg.sender, epoch_count: uint256 = 50, use_vest: bool = False).
The function claims the epoch allocations for receiver and transfers the tokens to receiver. Its inputs are the same as the inputs of preview_claim(), and receiver defaults to the caller.
The function emits Claim once for each token with a non-zero amount.
claim() reverts in the same cases as preview_claim(). A failed token transfer also reverts without a message.
add_token_set()
The function has this signature: add_token_set(token_set: DynArray[IERC20, MAX_TOKENS]).
Only the owner can call it. The function fills epochs with the old set, then makes token_set the current set. The new set replaces the old set, so a token that is not in the new set no longer goes into epochs. Include every token that must stay in distribution.
The function emits AddTokenSet.
add_token_set() reverts in these cases:
ownable: caller is not the owner- The caller is not the owner.Ded- The contract is killed. The function runsfill_epochsfirst.
kill_toggle()
The function has this signature: kill_toggle(is_killed: bool).
kill_toggle() is the owner-only kill switch. It sets the kill status to is_killed. While the contract is killed, fill_epochs, claim, and preview_claim revert with Ded. recover_token can then take the full token balance.
The function emits Kill.
kill_toggle() reverts with ownable: caller is not the owner when the caller is not the owner.
recover_token()
The function has this signature: recover_token(token: IERC20, receiver: address).
Only the owner can call it. The function sends to receiver the part of the token balance that fill_epochs has not allocated to epochs. While the contract is killed, it sends the full balance.
recover_token() reverts in these cases:
ownable: caller is not the owner- The caller is not the owner.- Without a message - The token transfer fails.
Events
The FeeDistributor emits four events. indexed marks an indexed field.
| Event | Fields |
|---|---|
FundEpoch | epoch: uint256 (indexed), token: address (indexed), amount: uint256 |
AddTokenSet | token_set_id: uint256 (indexed), token_set: address[] |
Claim | user: address (indexed), token: address (indexed), amount: uint256 |
Kill | is_killed: bool |
Claim basis
For each epoch, the contract reads historical voting power from VotingEscrow:
VE.getPastVotes(user, epoch)VE.getPastTotalSupply(epoch)
Each token allocation is epoch_token_amount * user_votes / total_votes, rounded down.
In this example, fill_epochs() finds 160 × 10^18 new units of one yb-LP token. It adds 40 × 10^18 to each of the next four epochs. In one of those epochs, a receiver has getPastVotes = 1,000 × 10^18, and getPastTotalSupply is 200,000 × 10^18. The receiver's amount for that epoch is 40 × 10^18 × 1,000 / 200,000 = 2 × 10^17, or 0.2 yb-LP. preview_claim returns the sum over the unclaimed epochs, up to epoch_count.
Vesting escrows
A vesting escrow is a contract that releases a YB allocation over time. During the cliff of a team or investor allocation, the recipient's YB stays in a cliff escrow (CliffEscrow) and cannot leave it. From the cliff escrow, the recipient can lock YB into veYB and claim the veYB share of admin fees.
To claim for a cliff recipient, set use_vest = True. Claims can go through cliff escrows when use_vest = True. If no cliff escrow matches and receiver is not the caller, the claim can revert with Might be a vest. It reverts when receiver is a contract whose VE() returns the VotingEscrow address.
For what a cliff escrow allows, read Cliff rules in Tokenomics.
Related
- VotingEscrow: the source of
getPastVotesandgetPastTotalSupply. - FeeSplitter in Net pressure (PID): the contract that collects admin fees and calls
fill_epochs(). - Error codes: the shared revert table.
- Event catalog: events of every contract, for indexers.
- Fees and veYB revenue: how admin fees reach the FeeSplitter.