Design Your Stablecoin

Getting started

Deep dive into the Treasury Model template where all accrued rewards are captured by the use case owner.

Core Concept: All accrued rewards are captured by the use case owner. Token holders observe a stable, non-rebasing token with 1:1 backing.

When to Use:

  • Simple business models where all rewards go to one entity.
  • Corporate treasuries requiring centralized revenue management.
  • Protocol-owned stablecoins where rewards fund protocol development.
  • Ecosystem development funds or grants programs.

EVM Implementation Options

The Treasury Model offers two EVM implementations to fit different collateral strategies:

FeatureMYieldToOne (Standard)JMIExtension (Multi-Collateral)
CollateralSingle-collateralMulti-collateral (liquid stablecoins such as USDC, USDT also supported)
Asset CapsN/AConfigurable per asset
Unwrap LogicAlways 1:1 backedLimited to base backing portion
Additional OperationsNonereplaceAssetWithM for rebalancing
Best ForSimple treasury setupsDiversified collateral pools

Choose MYieldToOne for single-collateral deployments with the simplest implementation.

Choose JMIExtension if you need flexibility to accept multiple stablecoins while still directing all rewards to a single recipient. Learn more about JMI

Default: MYieldToOne

The MYieldToOne template is the simplest and most direct way to build on M0. It creates an upgradeable ERC-20 token backed 1:1 by eligible collateral, while directing 100% of the accrued rewards to a single, designated recipient address.

This model is perfect for builders who want the stability and liquidity of an M0-powered token within their ecosystem, but prefer to centralize the rewards for strategic purposes like funding operational expenses, protocol development, ecosystem grants.

Source Code: MYieldToOne.sol

Architecture and Mechanism

The beauty of MYieldToOne lies in its simplicity. It separates the token holding experience from the rewards 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 rewards go. 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 through the SwapFacility and receives an equal amount of your new extension token (e.g., wrap 100, receive 100 YourToken). The MYieldToOne contract now holds the underlying balance.
  2. Accruing Rewards: Your deployed MYieldToOne contract must call enableEarning() to start earning rewards. It begins to accrue rewards from the underlying eligible collateral.
  3. Creating a Surplus: As rewards accrue, the contract's balance becomes greater than the total supply of your extension token. For example, the contract might hold 100.05 while the totalSupply() of YourToken is still 100.
  4. Claiming Rewards: This surplus is the claimable rewards. Anyone can call the claimYield() function to realize this revenue.
  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 rewards are 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 Rewards Functions

  • yield() external view returns (uint256) This view function calculates the amount of claimable rewards at any moment. It returns the difference between the contract's current balance and the totalSupply of the extension token: mBalance > totalSupply ? mBalance - totalSupply : 0.
  • claimYield() external returns (uint256) This is the function that triggers the rewards distribution. It calculates the rewards, mints that amount of new tokens, and transfers them to the yieldRecipient. Returns 0 if there are no rewards to claim.

Management Functions

  • setYieldRecipient(address yieldRecipient) external Callable only by the YIELD_RECIPIENT_MANAGER_ROLE, this function updates the address that receives the rewards. It automatically claims any existing rewards 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 rewards.
  • disableEarning() - Stops earning rewards.

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 with MYieldToOne? Follow the implementation guide

Need multi-collateral support? See the JMI implementation guide

SVM Implementation: NoYield

The NoYield extension template is the most direct way to build a treasury-focused stablecoin on Solana. It creates an upgradeable, non-rebasing token backed 1:1 by eligible collateral, while directing 100% of the accrued rewards 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 M0-powered token within their ecosystem but prefer to centralize rewards for strategic purposes, such as funding operational expenses, protocol development, ecosystem grants.

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 rewards generation, which is managed centrally by the platform.

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 rewards 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 and receives an equal amount of the new extension token. The NoYield contract's vault (m_vault) now holds the underlying balance.
  2. Rewards Accrual: The contract's vault is registered as an earner in the base program. It begins to accrue rewards through Solana's rebasing mechanism.
  3. Creating a Surplus: As rewards accrue, the contract's balance in its vault becomes greater than the total supply of the extension token. For example, the contract might hold 100.05 while the totalSupply of the extension token remains 100. This surplus represents the claimable rewards.
  4. Claiming Rewards: 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 rewards are 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 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 Rewards Function

  • claim_fees(): The primary instruction for capturing rewards. It calculates the total rewards 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 Extension Functions

The contract inherits essential functionalities for interacting with the M0 ecosystem:

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

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

Copyright © M0 Foundation 2026