A market sends every fee it charges to its fee treasury, a SplitterTreasury_v1 contract that divides each payment between elevation and a list of recipients in the same transaction. The buy fee, sell fee, origination fee and LTV are parameters of a market. Their values set the cost of trading and borrowing and the amount of fee the market collects. The splitter’s settings decide how much of that fee goes to recipients and how much goes to elevation, which raises the reference price.

Fees a market charges

The code calls the origination fee the borrowing fee (setBorrowingFeeRate, getBorrowingFeeRate). This page uses “origination fee” for the same parameter. One further payment reaches the market fee treasury. When a sell takes supply to 0 and the reserve manager records no outstanding draws, the market sends any collateral left on the curve contract to the fee treasury (Markets and curves).

The fee treasury address

The factory creates the market fee treasury when it creates the market (ReferenceCurveFactory_v1). It passes the same address to the curve and to every module it creates with the market, including the credit facility and the staking manager. Each contract stores the address at initialization (__Module_init). No contract has a function that changes it. Every fee follows the same path. The contract that charges the fee approves the fee treasury for the amount and calls the treasury’s fetchFunds (_sendFeesToTreasury). The treasury pulls the amount and divides it in the same call. The early-access module is attached to the market later, with its own SplitterTreasury_v1 (attachPresaleWithTreasury). Its fee goes to that treasury, not to the market fee treasury. The launch route reverts if the two addresses are the same.

Origination fee

The credit facility charges the origination fee on every loan it opens: borrow, each round of buyAndBorrow, and the collateral released by rebalanceLoan. The fee is taken from the loan amount, and the borrower receives the rest. fee=⌈L⋅forig10 000⌉,paid=L−fee\text{fee} = \left\lceil L \cdot \frac{f_{\text{orig}}}{10\,000} \right\rceil, \qquad \text{paid} = L - \text{fee} where:
  • LL is the loan amount, in collateral
  • forigf_{\text{orig}} is the origination fee in basis points (getBorrowingFeeRate)
A loan below ⌈10 000/forig⌉\lceil 10\,000 / f_{\text{orig}} \rceil of the collateral token’s smallest unit reverts when forigf_{\text{orig}} is above 0. Example. The origination fee is 200 bps (2%) and a borrower takes a loan of 1,000.00 collateral. The loan amount follows from the locked issuance tokens, the reference price and the LTV. Credit and looping explains that calculation.

The split

The splitter holds three settings:
  • an elevation percentage: a share of each payment, in basis points, that it sends first to the elevation fee address
  • a list of recipients, each with a share count
  • an optional elevation treasury share: a destination address (getElevationTreasury) with its own share count, counted together with the recipient shares. In the shares launch mode the destination is the market’s ElevationTreasury_v1
When fetchFunds is called, the splitter pulls the payment, then works out its distributable amount: its whole balance of that token, less any elevation value already pending (see Elevation treasury share payments). It sends the elevation percentage of the distributable amount to the elevation fee address. It divides the rest by share count between the destination of the elevation treasury share and the recipients. D=B−Q,Ecut=⌊D⋅e10 000⌋,R=D−EcutD = B - Q, \qquad E_{\text{cut}} = \left\lfloor D \cdot \frac{e}{10\,000} \right\rfloor, \qquad R = D - E_{\text{cut}} Eshare=⌊R⋅sES⌋,ri=⌊R⋅siS⌋,S=sE+∑isiE_{\text{share}} = \left\lfloor R \cdot \frac{s_E}{S} \right\rfloor, \qquad r_i = \left\lfloor R \cdot \frac{s_i}{S} \right\rfloor, \qquad S = s_E + \sum_i s_i where:
  • BB is the splitter’s balance of the token after it pulls the payment
  • QQ is the elevation value already pending for that token (getPendingElevationReserved)
  • DD is the distributable amount
  • ee is the elevation percentage in basis points (getElevationFeePercentage)
  • EcutE_{\text{cut}} is the amount sent to the elevation fee address (getElevationFeeTreasury)
  • sEs_E is the share count of the elevation treasury share (getElevationShares), 0 when none is set
  • sis_i is recipient ii‘s share count (getRecipientsAndShares)
  • SS is the total share count (getTotalShares)
  • EshareE_{\text{share}} is the amount offered to the destination of the elevation treasury share
  • rir_i is the amount sent to recipient ii
The divisions round down. The remainder, R−Eshare−∑iriR - E_{\text{share}} - \sum_i r_i, stays on the splitter. Because DD is the whole balance less pending value, that remainder is part of the distributable amount of the next fetchFunds call. A slice that rounds to 0 is not sent. Example. A splitter has an elevation percentage of 500 bps (5%), two recipients A and B with 3 shares and 1 share, and an elevation treasury share of 1 share. Nothing is pending and the splitter holds nothing before a fee of 1,000.00 collateral arrives. Of the 1,000.00 fee, 240.00 goes toward elevation (50.00 by the elevation percentage, 190.00 by share) and 760.00 goes to the two recipients. Example with a remainder. The same splitter receives 1,001 of the collateral token’s smallest unit. The 1 unit stays on the splitter and is included in the distributable amount of the next call.

Elevation treasury share payments

The two elevation routes move funds differently:
  • Elevation percentage: the splitter transfers EcutE_{\text{cut}} to the elevation fee address directly.
  • Elevation treasury share: the splitter records EshareE_{\text{share}} as pending for the share’s destination, approves the destination for its whole pending amount, and calls the destination’s fetchFunds. The destination pulls the amount.
The splitter measures what the destination pulled from the change in its own balance. Anything not pulled stays pending, recorded against that token and that destination address (getPendingElevationPayment). A failed call to the destination does not revert the fee payment. The recipients are paid in the same call either way. Pending value is excluded from the distributable amount of later calls. Each later fetchFunds call offers the current destination its pending amount plus the new slice. Any address can also call retryElevationPayment(token, treasury) to offer a destination its pending amount without a new payment. The call does nothing when nothing is pending.

From the elevation treasury to the curve

ElevationTreasury_v1 holds the collateral it receives. Its fetchFunds only accumulates and does not start an elevation. An elevation happens when an elevation keeper calls the treasury’s elevate. That call passes collateral to the curve’s elevate, which raises the reference price. How the reference price increases covers the conditions on that call and what it does to the curve.

Allocations made after the split

The splitter’s on-chain split consists of the elevation percentage, the recipients and the elevation treasury share. A recipient can send the collateral it receives to any address, including the market’s elevation treasury. The splitter does not record or restrict what a recipient does with that collateral. Any address can transfer collateral to the market’s elevation treasury, and the treasury treats that collateral the same as collateral from the splitter.

Parameters

Each splitter’s first recipients, shares, elevation percentage and elevation fee address come from its initialization data. The launch route checks them against the launch configuration before it hands the admin role over. DEFAULT_ADMIN_ROLE can call every setter on this page. Authority lists every role.

Scenarios

Elevation at launch

The launch route wires elevation on both splitters, the market fee treasury and the early-access fee treasury, in one of two modes set by the launch configuration (elevationMode).

Elevation treasury share payment outcomes

Edge cases

  • Fee treasury fixed. No contract has a setter for its fee treasury address.
  • Elevation share cannot be removed. setElevation reverts for a share of 0, so after an elevation treasury share is set, its share count can change but cannot return to 0.
  • Pending value destination. Pending value moves only to the address it was recorded for. If that destination never pulls, the value stays reserved on the splitter.
  • Direct transfers. Collateral sent to a splitter without fetchFunds is part of the distributable amount of the next fetchFunds call.
  • Who can call fetchFunds. Any address can call it. It reverts while the market is frozen and for a zero token address or a zero amount.
  • Recipient list. setRecipients reverts for an empty list, more than 36 recipients, mismatched array lengths, a zero address, a zero share, a duplicate, or the address of the elevation treasury share’s destination.
  • Elevation treasury share. setElevation reverts for the zero address, an address without code, the splitter itself, or an address that is a recipient.
  • Small loans. With an origination fee above 0, a loan below ⌈10,000 / rate⌉ smallest units reverts. Inside buyAndBorrow, a round whose loan fee would round to 0 ends the loop instead.
  • LTV of 100%. setLoanToValueRatio reverts at 10,000 bps and above, and at 0.

Code

Source: house-markets/contracts at 0ebbc89a (branch integration/v12-post-qa).