Fee Auction

The FeeAuction distributes protocol yield — collected as backing assets from Operator interest payments — to stcUSD holders via a Dutch auction mechanism. stcUSD holders bid cUSD at a continuously decaying price to purchase the accumulated backing assets at a discount, creating a fixed-rate yield experience. Each successful purchase resets the auction at double the settlement price, establishing a self-calibrating price discovery cycle. The FeeReceiver acts as the upstream collector, receiving vault interest from the Lender and holding it until the FeeAuction sweeps it.

Mechanics

Dutch Auction Price Curve

The auction price decays linearly from startPrice to 10% of startPrice over duration seconds:

price = startPrice × (1 - 0.9 × elapsed / duration)
  • At t = 0: price = startPrice (100%)

  • At t = duration: price = startPrice × 0.1 (10%)

  • Price never reaches zero — it floors at 10% of the start price

All price values are in the paymentToken (cUSD) with 18 decimals.

Buy Flow

  1. Price check: currentPrice() is compared against caller's _maxPrice. Reverts with InvalidPrice if currentPrice > _maxPrice.

  2. Validation: Asset list must be non-empty and match _minAmounts length; _receiver must be non-zero; deadline must be in the future.

  3. New auction reset: startTimestamp is set to block.timestamp; startPrice is set to max(currentPrice × 2, minStartPrice).

  4. Asset transfer: Full balances of each requested _asset held by the FeeAuction are transferred to _receiver. Reverts with InsufficientBalance if any asset balance is below _minAmounts[i].

  5. Payment: currentPrice in paymentToken is pulled from msg.sender and sent to paymentRecipient (the stcUSD distribution contract or treasury).

Self-Calibrating Start Price

Each successful purchase sets the next auction's startPrice to 2 × settlementPrice. This means:

  • If buyers wait for a deep discount, the next auction starts low.

  • If buyers buy early, the next auction starts high.

  • The auction converges on a market-clearing rate for protocol yield.

The minStartPrice floor prevents the auction from becoming trivially cheap during low-activity periods.

Interest Flow End-to-End


Architecture

FeeAuction

Core Functions

buy(uint256 _maxPrice, address[] _assets, uint256[] _minAmounts, address _receiver, uint256 _deadline)

Purchase all accumulated fee assets at the current Dutch auction price.

Parameter
Type
Description

_maxPrice

uint256

Maximum cUSD to pay; reverts if currentPrice > _maxPrice

_assets

address[]

List of backing assets to receive from the auction contract

_minAmounts

uint256[]

Minimum balance of each asset to accept (slippage protection per asset)

_receiver

address

Address to receive the purchased backing assets

_deadline

uint256

Unix timestamp after which the tx reverts


currentPrice()

Returns the current auction price in paymentToken units. Decays linearly from startPrice to startPrice × 0.1 over duration. Use this before calling buy to set _maxPrice.


Admin Functions

Function
Description

setStartPrice(uint256)

Manually reset the auction start price (must be ≥ minStartPrice)

setDuration(uint256)

Update the auction duration in seconds

setMinStartPrice(uint256)

Update the minimum allowed start price floor

setPaymentToken(address)

Change the payment token (cUSD by default)


View Functions

Function
Returns
Description

currentPrice()

uint256

Current auction price in payment token

startPrice()

uint256

Starting price of the current auction

startTimestamp()

uint256

When the current auction started

duration()

uint256

Auction duration in seconds

minStartPrice()

uint256

Floor price for future auction starts

paymentToken()

address

Token buyers pay with (cUSD)

paymentRecipient()

address

Receives the cUSD payment from buyers


Data Structures

FeeAuctionStorage

Field
Description

paymentToken

ERC20 token buyers pay with — cUSD

paymentRecipient

Receives cUSD from every purchase — distributes to stcUSD holders

startPrice

Current auction starting price in paymentToken (18 decimals)

startTimestamp

block.timestamp of the last purchase (or initialization)

duration

Seconds for the price to decay from startPrice to startPrice × 0.1

minStartPrice

Minimum startPrice floor — prevents the auction going below a meaningful level


Events

Event
Parameters
Emitted when

Buy

buyer, price, assets[], balances[]

A successful purchase is made

SetDuration

duration

Auction duration updated

SetMinStartPrice

minStartPrice

Minimum start price updated

SetPaymentToken

paymentToken

Payment token changed

SetStartPrice

startPrice

Start price manually reset


Errors

Error
Condition

InvalidPrice

currentPrice > _maxPrice

InvalidAssets

Empty asset list or assets.length ≠ minAmounts.length

InvalidDeadline

_deadline < block.timestamp

InvalidReceiver

_receiver == address(0)

InvalidStartPrice

setStartPrice called with value below minStartPrice

InvalidPaymentToken

setPaymentToken called with zero address

InsufficientBalance

Asset balance in the contract is below _minAmounts[i]

NoMinStartPrice

initialize called with _minStartPrice == 0

NoDuration

initialize or setDuration called with _duration == 0


FeeReceiver

The FeeReceiver is a simple holding contract that sits between the Lender and the FeeAuction. The Lender sends vault interest to the FeeReceiver, which holds the assets until a buyer triggers the FeeAuction sweep.

It has no complex logic — its purpose is to batch up interest from multiple reserves before each auction, so buyers receive a meaningful bundle of assets per purchase rather than tiny per-asset drips.


Usage Examples

1. Buying auction assets (stcUSD holder / arbitrageur)

2. Monitoring the auction price off-chain

3. Full interest flow — from Operator repayment to stcUSD yield

Last updated