Skip to content

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.

When to Use:
  • 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 call setYieldRecipient() 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 the freeze() and unfreeze() functions.

How It Works

  1. Wrapping: A user wraps $M through the SwapFacility and receives an equal amount of your new extension token (e.g., wrap 100 $M, receive 100 YourToken). The MYieldToOne contract now holds 100 $M.
  2. Accruing Yield: Your deployed MYieldToOne contract must call enableEarning() to start earning $M. The 100 $M it holds begins to accrue yield from the underlying US Treasury bills.
  3. 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 the totalSupply() of YourToken is still 100.
  4. Claiming Yield: This surplus is the claimable yield. Anyone can call the claimYield() function to realize this yield.
  5. Distribution: When claimYield() is called, the contract mints new YourToken equivalent to the surplus amount (0.05 in our example) and sends them directly to the designated yieldRecipient address. The totalSupply 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 the totalSupply 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 the yieldRecipient. Returns 0 if there is no yield to claim.

Management Functions

  • setYieldRecipient(address yieldRecipient) external Callable only by the YIELD_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 the FREEZE_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 only
  • unwrap(address recipient, uint256 amount) - Called by SwapFacility only
  • enableEarning() - 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.

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_fees instruction.
    • Manage a whitelist of wrap_authorities.
  • wrap_authority: An address authorized by the admin to perform wrap and unwrap operations. This is useful for delegating wrapping capabilities to specific programs or services, like the swap facility.

How It Works

  1. Wrapping: A user or program with wrap_authority wraps $M and receives an equal amount of the new extension token. The NoYield contract's vault (m_vault) now holds the underlying $M.
  2. Yield Accrual: The contract's vault is registered as an earner in the base $M earn program. The $M it holds begins to accrue yield through Solana's rebasing mechanism.
  3. Creating a Surplus: As yield accrues, the contract's $M balance in its vault becomes greater than the total supply of the extension token. For example, the contract might hold 100.05 $M while the totalSupply of the extension token remains 100. This surplus represents the claimable yield.
  4. Claiming Yield: The admin calls the claim_fees function. This is the core mechanism for capturing the protocol's revenue.
  5. Distribution: The claim_fees instruction calculates the surplus, mints an equivalent amount of new extension tokens, and sends them directly to a recipient token account controlled by the admin. The totalSupply of 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 the m_vault since the last claim, mints that amount of new extension tokens, and transfers them to an admin-controlled recipient_ext_token_account. This instruction can only be called by the admin.

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 $M into the extension token. Requires the caller to be a whitelisted wrap_authority.
  • unwrap(amount: u64): Unwraps the extension token back into $M. Requires the caller to be a whitelisted wrap_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 $M balance.

Ready to build? Follow the implementation guide to deploy your NoYield extension.