Treasury Model
Core Concept: All accrued yield 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 Options
The Treasury Model offers two EVM implementations to fit different collateral strategies:
| Feature | MYieldToOne (Standard) | JMIExtension (Multi-Collateral) |
|---|---|---|
| Collateral | Single-collateral | Multi-collateral (USDC, USDT, etc.) |
| Asset Caps | N/A | Configurable per asset |
| Unwrap Logic | Always 1:1 backed | Limited to base backing portion |
| Additional Operations | None | replaceAssetWithM for rebalancing |
| Best For | Simple treasury setups | Diversified 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 yield to a single recipient. Learn more about JMI →
Default: MYieldToOne
The MYieldToOne extension 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 yield 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 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 through the SwapFacility and receives an equal amount of your new extension token (e.g.,
wrap 100, receive 100
YourToken). TheMYieldToOnecontract now holds the underlying balance. - Accruing Yield: Your deployed
MYieldToOnecontract must callenableEarning()to start earning yield. It begins to accrue yield from the underlying eligible collateral. - Creating a Surplus: As yield accrues, 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()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 balance 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 yielddisableEarning()- 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 with MYieldToOne? Follow the implementation guide →
Need multi-collateral support? See the JMI implementation guide →
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 backed 1:1 by eligible collateral, while directing 100% of the accrued 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 M0-powered 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 and receives an equal amount of the new extension token. TheNoYieldcontract's vault (m_vault) now holds the underlying balance. - Yield Accrual: The contract's vault is registered as an earner in the base program. It begins to accrue yield through Solana's rebasing mechanism.
- Creating a Surplus: As yield accrues, 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
totalSupplyof 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 into the extension token. Requires the caller to be a whitelistedwrap_authority.unwrap(amount: u64): Unwraps the extension token. 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 balance.
Ready to build? Follow the implementation guide to deploy your NoYield extension.

