Skip to content

YieldToken

contracts/src/agents/YieldToken.sol

Transferable ERC-20 that represents a permanent yield share in an agent. Holders receive USDC proportional to their balance whenever AgentToken.harvest() is called.

Why a separate token?

Tracking yield share as an internal mapping inside AgentToken creates a composability problem: if a contract burns AT to earn yield, and that contract is later upgraded (new address), the yield is permanently stuck at the old address.

YieldToken is a standard ERC-20 — it can be transferred to a new contract, traded on secondary markets, or used in other DeFi protocols. The yield share moves with whoever holds the token.

Minting

YieldToken is minted exclusively by AgentToken:

  • AgentToken.burnForYield(amount) → mints amount YT to caller.
  • AgentToken.burnForYieldTo(amount, recipient) → mints amount YT to recipient.

The total YT supply equals the total AT ever burned for yield. YT can never be minted without burning AT first.

Yield distribution (MasterChef accumulator)

solidity
uint256 public accUsdcPerShare;  // grows with each receiveYield() call

// When receiveYield(amount) is called:
accUsdcPerShare += amount * PRECISION / totalSupply();

// User's pending USDC:
pending = balance * (accUsdcPerShare - userAccUsdcPerShare[user]) / PRECISION

receiveYield(uint256 amount)

Called by AgentToken after claiming USDC from Pool. USDC must already be in the YieldToken contract before this is called. Updates the accumulator proportionally to total YT supply.

solidity
// Only AgentToken can call this
function receiveYield(uint256 amount) external onlyAgentToken

claim() → uint256

Settles the accumulator for msg.sender and transfers accumulated USDC.

solidity
uint256 earned = yieldToken.claim();

pendingEarnings(address user) → uint256

View function — returns unsettled USDC including the current accumulator delta.

Settlement before transfers

_update(from, to, amount) is overridden to settle both from and to before any balance change. This ensures the accumulator is always correct regardless of when transfers happen:

Before any mint / burn / transfer:
  _settle(from)  → pendingUsdc[from] += balance[from] * delta / PRECISION
  _settle(to)    → pendingUsdc[to]   += balance[to]   * delta / PRECISION
  userAccUsdcPerShare[user] = accUsdcPerShare  (for both)

Transferability and composability

Because YieldToken is a standard ERC-20:

  • It can be listed and traded — "buy exposure to an agent's yield stream."
  • A DAO can pool AT burns across many members, hold the YT collectively, and distribute USDC to DAO members by whatever rules they choose.
  • An aggregator can hold YT across multiple agents and batch claim() calls.

Released under the MIT License.