Deep Dive: MYieldToOne Extension
The MYieldToOne
extension is the simplest and most direct way to build on M0. It creates an upgradeable ERC-20 token that wraps $M
on a 1:1 basis, while directing 100% of the accrued $M
yield to a single, designated recipient address.
This model is perfect for builders who want the stability and liquidity of an $M
-backed token within their ecosystem, but prefer to centralize the yield for strategic purposes like funding protocol development, ecosystem grants, or operational expenses.
Source Code: MYieldToOne.sol
Architecture and Mechanism
The beauty of MYieldToOne
lies in its simplicity. It separates the token holding experience from the yield generation, while incorporating freezing capabilities for compliance.
Roles (Access Control)
MYieldToOne
inherits from both MExtension
and Freezable
, using OpenZeppelin's AccessControl
to manage permissions with these distinct roles:
DEFAULT_ADMIN_ROLE
: The super-user. This role can grant and revoke any other role. It should be held by a secure multi-sig or governance contract.YIELD_RECIPIENT_MANAGER_ROLE
: This role has one job: to manage where the yield goes. It can callsetYieldRecipient()
to change the beneficiary address.FREEZE_MANAGER_ROLE
: This role can block specific addresses from wrapping, unwrapping, or transferring the token, adding a layer of compliance through thefreeze()
andunfreeze()
functions.
How It Works
- Wrapping: A user wraps
$M
through the SwapFacility and receives an equal amount of your new extension token (e.g., wrap 100$M
, receive 100YourToken
). TheMYieldToOne
contract now holds 100$M
. - Accruing Yield: Your deployed
MYieldToOne
contract must callenableEarning()
to start earning$M
. The 100$M
it holds begins to accrue yield from the underlying US Treasury bills. - Creating a Surplus: As yield accrues, the contract's
$M
balance becomes greater than the total supply of your extension token. For example, the contract might hold 100.05$M
while thetotalSupply()
ofYourToken
is still 100. - Claiming Yield: This surplus is the claimable yield. Anyone can call the
claimYield()
function to realize this yield. - Distribution: When
claimYield()
is called, the contract mints newYourToken
equivalent to the surplus amount (0.05 in our example) and sends them directly to the designatedyieldRecipient
address. ThetotalSupply
now becomes 100.05.
For end-users holding your token, their balance remains stable and unchanged. The yield is handled completely behind the scenes.
Key Interface & Functions
While MYieldToOne
is a full ERC-20 token, its unique logic revolves around a few key functions.
Storage Layout
The state is kept minimal and efficient.
struct MYieldToOneStorageStruct {
uint256 totalSupply;
address yieldRecipient;
mapping(address account => uint256 balance) balanceOf;
}
Core Yield Functions
-
yield() external view returns (uint256)
This view function calculates the amount of claimable yield at any moment. It returns the difference between the contract's current$M
balance and thetotalSupply
of the extension token:mBalance > totalSupply ? mBalance - totalSupply : 0
. -
claimYield() external returns (uint256)
This is the function that triggers the yield distribution. It calculates the yield, mints that amount of new tokens, and transfers them to theyieldRecipient
. Returns 0 if there is no yield to claim.
Management Functions
setYieldRecipient(address yieldRecipient) external
Callable only by theYIELD_RECIPIENT_MANAGER_ROLE
, this function updates the address that receives the yield. It automatically claims any existing yield for the previous recipient before making the change.
Freezing Functions (Inherited from Freezable)
freeze(address account) external
Callable by theFREEZE_MANAGER_ROLE
, this function prevents an address from interacting with the token. Reverts if the account is already frozen.unfreeze(address account) external
Removes an address from the frozen list. Reverts if the account is not currently frozen.isFrozen(address account) external view returns (bool)
Checks if an address is currently frozen.freezeAccounts(address[] calldata accounts) external
Batch version for freezing multiple accounts.unfreezeAccounts(address[] calldata accounts) external
Batch version for unfreezing multiple accounts.
Standard MExtension Functions
As a child of MExtension.sol
, MYieldToOne
automatically inherits all the essential functionalities for interacting with the M0 ecosystem:
wrap(address recipient, uint256 amount)
- Called by SwapFacility onlyunwrap(address recipient, uint256 amount)
- Called by SwapFacility onlyenableEarning()
- Starts earning yield on held$M
disableEarning()
- Stops earning yield
Hook Functions
The contract implements several internal hook functions that enforce freezing:
_beforeApprove()
- Checks both account and spender aren't frozen_beforeWrap()
- Checks both depositor and recipient aren't frozen_beforeUnwrap()
- Checks the account isn't frozen_beforeTransfer()
- Checks sender, recipient, and msg.sender aren't frozen
Constants
YIELD_RECIPIENT_MANAGER_ROLE
:keccak256("YIELD_RECIPIENT_MANAGER_ROLE")
FREEZE_MANAGER_ROLE
:keccak256("FREEZE_MANAGER_ROLE")
(inherited from Freezable)
Ready to build? Follow the implementation guide to deploy your MYieldToOne extension.