Getting started
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:
| Feature | MYieldToOne (Standard) | JMIExtension (Multi-Collateral) |
|---|---|---|
| Collateral | Single-collateral | Multi-collateral (liquid stablecoins such as USDC, USDT also supported) |
| 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 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 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 Rewards: Your deployed
MYieldToOnecontract must callenableEarning()to start earning rewards. It begins to accrue rewards from the underlying eligible collateral. - 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()ofYourTokenis still 100. - Claiming Rewards: This surplus is the claimable rewards. Anyone can call the
claimYield()function to realize this revenue. - 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 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 thetotalSupplyof 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 theyieldRecipient. Returns 0 if there are no rewards to claim.
Management Functions
setYieldRecipient(address yieldRecipient) externalCallable only by theYIELD_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) 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 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_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. - 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.
- 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
totalSupplyof the extension token remains 100. This surplus represents the claimable rewards. - Claiming Rewards: 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 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 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 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.
Getting started
Deep dive into the JMI ("Just Mint It") extension template which accepts multiple collateral types while directing 100% of rewards to a single recipient.
Accessing Liquidity
M0's Onchain Orchestration provides seamless access to liquidity for M0-powered stablecoin across multiple chains.