Treasury Model
Core Concept: All accrued yield from the underlying $M is captured by the protocol administrator. Token holders receive a stable, non-rebasing token with 1:1 backing.
- Protocol-owned stablecoins where yield funds protocol development
- Ecosystem development funds or grants programs
- Corporate treasuries requiring centralized revenue management
- Simple business models where all yield goes to one entity
EVM Implementation: MYieldToOne
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
$Mthrough the SwapFacility and receives an equal amount of your new extension token (e.g., wrap 100$M, receive 100YourToken). TheMYieldToOnecontract now holds 100$M. - Accruing Yield: Your deployed
MYieldToOnecontract must callenableEarning()to start earning$M. The 100$Mit holds begins to accrue yield from the underlying US Treasury bills. - Creating a Surplus: As yield accrues, the contract's
$Mbalance becomes greater than the total supply of your extension token. For example, the contract might hold 100.05$Mwhile thetotalSupply()ofYourTokenis 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 newYourTokenequivalent to the surplus amount (0.05 in our example) and sends them directly to the designatedyieldRecipientaddress. ThetotalSupplynow 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$Mbalance and thetotalSupplyof 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) externalCallable 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) externalCallable by theFREEZE_MANAGER_ROLE, this function prevents an address from interacting with the token. Reverts if the account is already frozen.unfreeze(address account) externalRemoves 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) externalBatch version for freezing multiple accounts.unfreezeAccounts(address[] calldata accounts) externalBatch 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$MdisableEarning()- 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.
SVM Implementation: NoYield
The NoYield extension model is the most direct way to build a treasury-focused stablecoin on Solana. It creates an upgradeable, non-rebasing token that wraps $M 1:1, while directing 100% of the accrued $M yield to the extension's administrator. The NoYield variant supports both the Token-2022 standard and the legacy token program.
This model is perfect for builders who want the stability of an $M-backed token within their ecosystem but prefer to centralize yield for strategic purposes, such as funding protocol development, ecosystem grants, or operational expenses.
Source Code: The NoYield model is compiled from the m_ext program using the no-yield Rust feature flag. The source code is available in the solana-m-extensions repository.
Architecture and Mechanism
The NoYield model's strength is its simplicity. It completely separates token holding from yield generation, which is managed centrally by the protocol.
Roles (Access Control)
admin: The root administrator, typically a secure multi-sig or governance contract. This role can:- Configure the extension via
initialize. - Claim all accrued yield using the
claim_feesinstruction. - Manage a whitelist of
wrap_authorities.
- Configure the extension via
wrap_authority: An address authorized by theadminto perform wrap and unwrap operations. This is useful for delegating wrapping capabilities to specific programs or services, like the swap facility.
How It Works
- Wrapping: A user or program with
wrap_authoritywraps$Mand receives an equal amount of the new extension token. TheNoYieldcontract's vault (m_vault) now holds the underlying$M. - Yield Accrual: The contract's vault is registered as an earner in the base
$Mearnprogram. The$Mit holds begins to accrue yield through Solana's rebasing mechanism. - Creating a Surplus: As yield accrues, the contract's
$Mbalance in its vault becomes greater than the total supply of the extension token. For example, the contract might hold 100.05$Mwhile thetotalSupplyof the extension token remains 100. This surplus represents the claimable yield. - Claiming Yield: The
admincalls theclaim_feesfunction. This is the core mechanism for capturing the protocol's revenue. - Distribution: The
claim_feesinstruction calculates the surplus, mints an equivalent amount of new extension tokens, and sends them directly to a recipient token account controlled by theadmin. ThetotalSupplyof the extension token is now updated to reflect the newly minted fee tokens.
For end-users, their token balance remains stable and unchanged. The yield is handled entirely behind the scenes by the protocol's administrator.
Key Interface & Functions
The NoYield model is a feature-complete Token-2022 token. Its unique logic is centered around a few key instructions defined in the generic M Extension program.
Storage Layout
The extension's state is managed by the ExtGlobal account, which is a PDA seeded with b"ext_global".
// State structure from m_ext program
pub struct ExtGlobal {
pub admin: Pubkey,
pub ext_mint: Pubkey,
pub m_mint: Pubkey,
pub m_earn_global_account: Pubkey,
pub bump: u8,
pub m_vault_bump: u8,
pub ext_mint_authority_bump: u8,
pub yield_config: YieldConfig,
pub wrap_authorities: Vec<Pubkey>,
}
pub struct YieldConfig {
pub variant: YieldVariant, // Set to YieldVariant::NoYield
pub last_m_index: u64,
pub last_ext_index: u64,
}Core Yield Function
claim_fees(): The primary instruction for capturing yield. It calculates the total yield accrued in them_vaultsince the last claim, mints that amount of new extension tokens, and transfers them to an admin-controlledrecipient_ext_token_account. This instruction can only be called by theadmin.
Management Functions
initialize(wrap_authorities: Vec<Pubkey>): Sets up the extension's global state with the NoYield configuration.add_wrap_authority(new_wrap_authority: Pubkey): Adds a new address to the list of authorized wrappers.remove_wrap_authority(wrap_authority: Pubkey): Removes an address from the list of authorized wrappers.transfer_admin(new_admin: Pubkey): Transfers admin control to a new address.
Standard M Extension Functions
The contract inherits essential functionalities for interacting with the M0 ecosystem:
wrap(amount: u64): Wraps$Minto the extension token. Requires the caller to be a whitelistedwrap_authority.unwrap(amount: u64): Unwraps the extension token back into$M. Requires the caller to be a whitelistedwrap_authority.
Error Handling
The contract includes robust error handling to ensure secure operation:
Unauthorized: Thrown if a non-admin tries to call a privileged instruction.InvalidAmount: Thrown for invalid wrap or unwrap amounts.InsufficientCollateral: Ensures the contract never mints more extension tokens than its underlying$Mbalance.
Ready to build? Follow the implementation guide to deploy your NoYield extension.

